Commit 0551dee5f99 for nodejs

commit 0551dee5f99eab5e09c8bbf9bf4d288b1195f538
Author: Hilary Asogwa <76795262+Dansatch@users.noreply.github.com>
Date:   Fri Sep 25 14:49:42 2026 +0100

    module: normalize package map paths

    Signed-off-by: dansatch <dansatch98@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/66054
    Fixes: https://github.com/nodejs/node/issues/66043
    Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
    Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
    Reviewed-By: James M Snell <jasnell@gmail.com>

diff --git a/lib/internal/modules/package_map.js b/lib/internal/modules/package_map.js
index 38e7e12206c..1e01eb389a2 100644
--- a/lib/internal/modules/package_map.js
+++ b/lib/internal/modules/package_map.js
@@ -105,7 +105,7 @@ class PackageMap {

       let absolutePath;
       try {
-        absolutePath = fileURLToPath(packageURL);
+        absolutePath = pathResolve(fileURLToPath(packageURL));
       } catch (err) {
         const error = new ERR_PACKAGE_MAP_INVALID(
           this.#configPath,
diff --git a/test/es-module/test-esm-package-map-paths.mjs b/test/es-module/test-esm-package-map-paths.mjs
index fc7c45c631a..611fe833314 100644
--- a/test/es-module/test-esm-package-map-paths.mjs
+++ b/test/es-module/test-esm-package-map-paths.mjs
@@ -27,6 +27,18 @@ spawnSyncAndAssert(process.execPath, [
   trim: true,
 });

+// Directory-form URLs match package files even when the map is nested below them.
+spawnSyncAndAssert(process.execPath, [
+  '--no-warnings',
+  '--experimental-package-map',
+  fixtures.path('package-map/nested-project/map-dir/package-map-parent-url.json'),
+  '--input-type=module',
+  '--eval', `import dep from 'dep-a'; console.log(dep);`,
+], { cwd: fixtures.path('package-map/nested-project/src') }, {
+  stdout: /dep-a-value/,
+  trim: true,
+});
+
 // URL fields are decoded as URLs before being converted to paths.
 spawnSyncAndAssert(process.execPath, [
   '--no-warnings',
diff --git a/test/fixtures/package-map/nested-project/map-dir/package-map-parent-url.json b/test/fixtures/package-map/nested-project/map-dir/package-map-parent-url.json
new file mode 100644
index 00000000000..b1647cc8877
--- /dev/null
+++ b/test/fixtures/package-map/nested-project/map-dir/package-map-parent-url.json
@@ -0,0 +1,12 @@
+{
+  "packages": {
+    "app": {
+      "url": "..",
+      "dependencies": {"dep-a": "dep-a"}
+    },
+    "dep-a": {
+      "url": "../../dep-a/",
+      "dependencies": {}
+    }
+  }
+}
diff --git a/test/fixtures/package-map/package-map-directory-urls.json b/test/fixtures/package-map/package-map-directory-urls.json
new file mode 100644
index 00000000000..ef24742fa6c
--- /dev/null
+++ b/test/fixtures/package-map/package-map-directory-urls.json
@@ -0,0 +1,12 @@
+{
+  "packages": {
+    "root": {
+      "url": "./root/",
+      "dependencies": {"dep-a": "dep-a"}
+    },
+    "dep-a": {
+      "url": "./dep-a/",
+      "dependencies": {}
+    }
+  }
+}
diff --git a/test/fixtures/package-map/package-map-duplicate-directory-path.json b/test/fixtures/package-map/package-map-duplicate-directory-path.json
new file mode 100644
index 00000000000..e29d7c955b1
--- /dev/null
+++ b/test/fixtures/package-map/package-map-duplicate-directory-path.json
@@ -0,0 +1,16 @@
+{
+  "packages": {
+    "root": {
+      "url": "./root",
+      "dependencies": {"dep-a": "dep-a"}
+    },
+    "dep-a": {
+      "url": "./dep-a",
+      "dependencies": {}
+    },
+    "dep-a-directory": {
+      "url": "./dep-a/",
+      "dependencies": {}
+    }
+  }
+}
diff --git a/test/parallel/test-require-package-map.js b/test/parallel/test-require-package-map.js
index 87786d62738..08ec289346a 100644
--- a/test/parallel/test-require-package-map.js
+++ b/test/parallel/test-require-package-map.js
@@ -32,6 +32,40 @@ writeFileSync(fileUrlFixturePath, JSON.stringify({
 describe('CJS: --experimental-package-map', { concurrency: !process.env.TEST_PARALLEL }, () => {

   describe('basic resolution', () => {
+    it('resolves dependencies from a parent directory URL', () => {
+      const { status, stdout, stderr } = spawnSync(process.execPath, [
+        '--no-warnings',
+        '--experimental-package-map',
+        fixtures.path('package-map/nested-project/map-dir/package-map-parent-url.json'),
+        '-e',
+        `const dep = require('dep-a'); console.log(dep.default);`,
+      ], {
+        cwd: fixtures.path('package-map/nested-project/src'),
+        encoding: 'utf8',
+      });
+
+      assert.strictEqual(stderr, '');
+      assert.match(stdout, /dep-a-value/);
+      assert.strictEqual(status, 0, stderr);
+    });
+
+    it('resolves packages with directory-form URLs', () => {
+      const { status, stdout, stderr } = spawnSync(process.execPath, [
+        '--no-warnings',
+        '--experimental-package-map',
+        fixtures.path('package-map/package-map-directory-urls.json'),
+        '-e',
+        `const dep = require('dep-a'); console.log(dep.default);`,
+      ], {
+        cwd: fixtures.path('package-map/root'),
+        encoding: 'utf8',
+      });
+
+      assert.strictEqual(stderr, '');
+      assert.match(stdout, /dep-a-value/);
+      assert.strictEqual(status, 0, stderr);
+    });
+
     it('resolves require() through package map', () => {
       const { status, stdout, stderr } = spawnSync(process.execPath, [
         '--no-warnings',
@@ -197,6 +231,23 @@ describe('CJS: --experimental-package-map', { concurrency: !process.env.TEST_PAR
       assert.match(stderr, /pkg-b/);
       assert.notStrictEqual(status, 0, stderr);
     });
+
+    it('throws for package URLs differing only by a trailing separator', () => {
+      const { status, stderr } = spawnSync(process.execPath, [
+        '--no-warnings',
+        '--experimental-package-map',
+        fixtures.path('package-map/package-map-duplicate-directory-path.json'),
+        '-e',
+        `require('dep-a');`,
+      ], {
+        cwd: fixtures.path('package-map/root'),
+        encoding: 'utf8',
+      });
+
+      assert.match(stderr, /ERR_PACKAGE_MAP_INVALID/);
+      assert.match(stderr, /dep-a-directory/);
+      assert.notStrictEqual(status, 0, stderr);
+    });
   });

   describe('conditional exports', () => {