diff --git a/src/application/ReferenceSourceImportEntries.ts b/src/application/ReferenceSourceImportEntries.ts index 1fac687310..717f52b5a3 100644 --- a/src/application/ReferenceSourceImportEntries.ts +++ b/src/application/ReferenceSourceImportEntries.ts @@ -269,9 +269,9 @@ const selectPackageMain = ( // package, and a trailing separator does not make the entry a directory. if (posix.isAbsolute(manifest.main)) return { to: posix.normalize(manifest.main), resolution: "unresolved" }; - const target = posix - .normalize(posix.join(packageDirectory, manifest.main)) - .replace(/(?<=.)\/+$/u, ""); + // Joining "." strips the trailing separator run with path semantics; a + // lookbehind regex kept separators behind line terminators (main "lib\n/"). + const target = posix.join(posix.join(packageDirectory, manifest.main), "."); // Node would load a declared entry outside the imported root. if (logicalPathEscapesRoot(target)) return { to: target, resolution: "unresolved" }; @@ -395,9 +395,15 @@ const resolveInternalSpecifier = ( return packageMainForDirectory(normalized, context) !== undefined ? selectPackageMain(normalized, undefined, context, mode) : { to: normalized, resolution: "unresolved" }; + // posix.normalize preserves the specifier's trailing slash while + // dirname(match) never carries one; derive the directory identity with path + // semantics (joining "." collapses trailing slashes, including ones a regex + // would keep behind a line terminator) so a trailing-slash reference still + // reaches package-main selection the same way the plain spelling does. + const directory = posix.join(normalized, "."); const resolvedThroughIndex = posix.basename(match).startsWith("index.") && - posix.dirname(match).normalize("NFC") === normalized.normalize("NFC"); + posix.dirname(match).normalize("NFC") === directory.normalize("NFC"); return resolvedThroughIndex ? selectPackageMain(posix.dirname(match), match, context, mode) : { to: match, resolution: "internal" }; diff --git a/tests/boundary/cli/referenceSourceDirectoryImports.test.ts b/tests/boundary/cli/referenceSourceDirectoryImports.test.ts index cb2395a15a..aefe55aaea 100644 --- a/tests/boundary/cli/referenceSourceDirectoryImports.test.ts +++ b/tests/boundary/cli/referenceSourceDirectoryImports.test.ts @@ -37,10 +37,14 @@ const prepareReferenceSourceDirectoryFixture = ["legacy-order.cjs", "./dep", "dep/index.js"], ["bare-index.cjs", "./bare", "bare.js"], ["package-main.cjs", "./pkg", "pkg/actual.js"], + ["package-main-slash-specifier.cjs", "./pkg/", "pkg/actual.js"], + ["package-main-newline-slash.cjs", "./pkg\n/", "pkg\n/actual.js"], ["package-main-no-index.cjs", "./no-index-pkg", "no-index-pkg/actual.js"], ["package-main-fallback.cjs", "./stale", "stale/index.js"], + ["package-main-fallback-slash.cjs", "./stale/", "stale/index.js"], ["typed-index-collision.cjs", "./typed", "typed.js"], ["package-main-slash.cjs", "./slashed", "slashed/lib.js"], + ["package-main-manifest-newline-slash.cjs", "./nlpkg/", "nlpkg/lib\n.js"], ["json.cjs", "./data", "data.json"], ["json-explicit.cjs", "./data.json", "data.json"], ["json-before-index.cjs", "./conf", "conf.json"], @@ -70,6 +74,12 @@ const prepareReferenceSourceDirectoryFixture = ["bare/index", 'module.exports = "extensionless index";\n'], ["only/index", 'module.exports = "extensionless index";\n'], ["pkg/package.json", '{"main":"actual.js"}\n'], + ["pkg\n/package.json", '{"main":"actual.js"}\n'], + [ + "pkg\n/actual.js", + 'module.exports = "declared main behind line terminator";\n', + ], + ["pkg\n/index.js", 'module.exports = "index not selected";\n'], ["pkg/actual.js", 'module.exports = "declared main";\n'], ["pkg/index.js", 'module.exports = "ignored index";\n'], ["no-index-pkg/package.json", '{"main":"actual.js"}\n'], @@ -87,6 +97,13 @@ const prepareReferenceSourceDirectoryFixture = ["slashed/lib.js", 'module.exports = "file main";\n'], ["slashed/lib/index.js", 'module.exports = "not selected";\n'], ["slashed/index.js", 'module.exports = "not selected";\n'], + ["nlpkg/package.json", '{"main":"lib\\n/"}\n'], + [ + "nlpkg/lib\n.js", + 'module.exports = "file main behind line terminator";\n', + ], + ["nlpkg/lib\n/index.js", 'module.exports = "not selected";\n'], + ["nlpkg/index.js", 'module.exports = "entry index";\n'], ["absolute/package.json", `${JSON.stringify({ main: outside })}\n`], ["absolute/index.js", 'module.exports = "not selected";\n'], ["data.json", '{"value":1}\n'],