fix(javascript): preserve package and source-map boundary identity (#1939)

* fix(javascript): preserve bare package specifier suffixes

* fix(javascript): resolve ESM package self references

* fix(javascript): recover retained prefixed source maps

* test(javascript): cover suffixed ESM self references

* fix(javascript): retain legacy ESM package suffix behavior

* fix(javascript): reject legacy ESM directory and extension fallback

* refactor(javascript): consolidate package resolution and workflow coverage

* fix(javascript): report exact ESM package subpath misses

* fix(javascript): compare parsed names for ESM self references

* refactor(javascript): parse bare package requests once

* fix(javascript): derive ESM dependency lookup from package URLs

---------

Co-authored-by: hhang <169027046+xu91102@users.noreply.github.com>
This commit is contained in:
morlutoandhhang authored and GitHub committed 2026-10-12 04:09:17 +08:00
1 parent 85c1d2587a
commit 14935e4d47
13 files changed
+860 -157

No files matched your search

@@ -176,9 +176,7 @@ const contextualCandidate = (
): string | CandidateResolution => {
const { context } = input;
let declared =
context === "html-reference" ||
context === "url-reference" ||
(context === "module-specifier" && input.moduleKind !== "require")
context === "html-reference" || context === "url-reference"
? stripQueryAndFragment(input.declaredPath)
: input.declaredPath;
if (context === "html-reference") return htmlCandidate(input);
@@ -190,9 +188,9 @@ const contextualCandidate = (
"URL and Node builtin schemes are outside static artifact module resolution.",
]);
if (!declared.startsWith(".") && !declared.startsWith("/"))
return bareModuleCandidate(input, declared);
return bareModuleCandidate(input);
if (input.moduleKind !== "require") {
const decoded = decodeModuleUrlPath(declared);
const decoded = decodeModuleUrlPath(stripQueryAndFragment(declared));
if (typeof decoded !== "string") return decoded;
declared = decoded;
}
@@ -237,34 +235,51 @@ const relativeCandidate = (sourcePath: string, declared: string): string =>
const bareModuleCandidate = (
input: ResolveArtifactPathInput,
declared: string,
): CandidateResolution => {
const packageName = barePackageName(declared);
if (packageName === null || isBuiltin(declared))
const request = parseBarePackageRequest(input.declaredPath, input.moduleKind);
if (request === null || isBuiltin(input.declaredPath))
return unresolvedCandidate("external", [
"The bare specifier is a Node builtin or invalid package name.",
]);
if (input.moduleKind === "import" && packageName.includes("%"))
if (input.moduleKind === "import" && request.packageName.includes("%"))
return unresolvedCandidate("rejected", [
"Percent signs are not admitted in ES module package names.",
]);
const self = commonJsSelfCandidate(input, declared);
const self = packageSelfCandidate(input, request);
if (self !== null) return self;
const subpath =
declared === packageName ? "." : `.${declared.slice(packageName.length)}`;
// ESM constructs a package.json URL; its parsed identity is not a literal
// dependency directory. Self resolution above still uses the exact name.
let dependencyDirectory = posix.join("node_modules", request.packageName);
if (input.moduleKind === "import") {
const metadataUrl = new URL(
`./node_modules/${request.packageName}/package.json`,
PACKAGE_ARTIFACT_ROOT_URL,
);
if (!metadataUrl.pathname.endsWith("/package.json"))
return unresolvedCandidate("external", [
`ESM package name ${JSON.stringify(request.packageName)} produces a dependency URL whose pathname does not name package.json; no literal or suffix-stripped package directory was searched.`,
]);
const decoded = decodeModuleUrlPath(
metadataUrl.pathname.slice(PACKAGE_ARTIFACT_ROOT_URL.pathname.length),
);
if (typeof decoded !== "string") return decoded;
const confined = confineCandidate(decoded);
if (typeof confined !== "string") return confined;
dependencyDirectory = posix.dirname(confined);
}
const source = input.files.get(input.sourcePath);
let directory = posix.dirname(input.sourcePath);
while (true) {
const candidate = posix.join(directory, "node_modules", packageName);
const candidate = posix.join(directory, dependencyDirectory);
const metadata = readPackageMetadata(input, candidate);
if (packageExportsEligible(input) && metadata.kind !== "absent")
return resolvePackageSpecifier(input, candidate, subpath, metadata);
if (subpath === ".") {
if (request.dependencyExportsEligible && metadata.kind !== "absent")
return resolvePackageDependency(input, candidate, request, metadata);
if (request.packageSubpath === ".") {
const direct = resolveFileCandidates(input, fileCandidates(candidate));
if (direct !== null) return direct;
}
if (hasContainerDirectory(input.files, candidate, source?.container_sha256))
return resolvePackageSpecifier(input, candidate, subpath, metadata);
return resolvePackageDependency(input, candidate, request, metadata);
if (directory === "." || directory === "") break;
directory = posix.dirname(directory);
}
@@ -330,24 +345,25 @@ const resolvePackageImportAlias = (
return unresolvedCandidate("external", [
`Wildcard package import aliases remain unresolved; package metadata ${metadata.path} has no exact imports key ${specifier}.`,
]);
const selected = selectPackageImportTarget(
const selected = selectExportTarget(
lookup.target,
input.moduleKind,
metadata.path,
packageExportConditions(input.moduleKind),
packageMetadataUrl(metadata.path),
"imports",
);
if (selected.status === "rejected")
if (selected.kind === "invalid" || selected.kind === "invalid-config")
return unresolvedCandidate("rejected", [
`Package metadata ${metadata.path}: ${selected.limitation}`,
]);
if (selected.status === "unmatched")
if (selected.kind === "unmatched")
return unresolvedCandidate("external", [
`Package metadata ${metadata.path} declares no imports target for alias ${specifier} under the active conditions; it declares ${selected.declared.join(", ") || "no conditions"}.`,
]);
if (selected.status === "blocked")
if (selected.kind === "blocked")
return unresolvedCandidate("external", [
`Package metadata ${metadata.path} blocks import alias ${specifier} with ${selected.cause === "null" ? "an explicit null imports target" : "an empty imports target array"}.`,
]);
if (selected.status === "non-relative") {
if (selected.kind === "non-relative") {
const target = classifyPackageImportTargetString(selected.value);
return unresolvedCandidate("external", [
target.kind === "recursive"
@@ -358,77 +374,44 @@ const resolvePackageImportAlias = (
return resolveExportTarget(input, metadata.path, selected, "imports");
};
const selectPackageImportTarget = (
value: unknown,
moduleKind: ResolveArtifactPathInput["moduleKind"],
packagePath: string,
):
| ({ readonly status: "value" } & PackageFileTarget)
| { readonly status: "non-relative"; readonly value: string }
| { readonly status: "blocked"; readonly cause: PackageTargetBlockCause }
| { readonly status: "unmatched"; readonly declared: readonly string[] }
| { readonly status: "rejected"; readonly limitation: string } => {
const selected = selectExportTarget(
value,
packageExportConditions(moduleKind),
packageMetadataUrl(packagePath),
"imports",
);
if (selected.kind === "invalid" || selected.kind === "invalid-config")
return { status: "rejected", limitation: selected.limitation };
if (selected.kind === "unmatched")
return {
status: "unmatched",
declared: selected.declared,
};
if (selected.kind === "blocked")
return { status: "blocked", cause: selected.cause };
if (selected.kind === "non-relative")
return { status: "non-relative", value: selected.value };
return {
status: "value",
value: selected.value,
url: selected.url,
};
};
/** Self references use the nearest package scope before dependency lookup. */
const commonJsSelfCandidate = (
const packageSelfCandidate = (
input: ResolveArtifactPathInput,
declared: string,
request: BarePackageRequest,
): CandidateResolution | null => {
if (input.moduleKind !== "require" || !input.files.has(input.sourcePath))
if (input.moduleKind === undefined || !input.files.has(input.sourcePath))
return null;
const metadata = enclosingPackageMetadata(input);
if (metadata.kind === "none") return null;
if (metadata.kind === "unavailable")
return unresolvedCandidate("unavailable", [metadata.limitation]);
if (
metadata.name === undefined ||
metadata.exports === undefined ||
(declared !== metadata.name && !declared.startsWith(`${metadata.name}/`))
)
if (metadata.name === undefined || metadata.exports === undefined)
return null;
const subpath =
declared === metadata.name
? "."
: `.${declared.slice(metadata.name.length)}`;
let subpath: string;
if (input.moduleKind === "import") {
if (metadata.name !== request.packageName) return null;
subpath = request.packageSubpath;
} else if (input.declaredPath === metadata.name) {
subpath = ".";
} else if (input.declaredPath.startsWith(`${metadata.name}/`)) {
subpath = `.${input.declaredPath.slice(metadata.name.length)}`;
} else return null;
// Self resolution does not apply the CommonJS dependency-name matcher.
return resolvePackageExports(input, metadata.path, subpath, metadata.exports);
};
/** Package roots and exact subpaths share metadata and exports selection. */
const resolvePackageSpecifier = (
/** Resolve an installed dependency using one parsed request and its metadata. */
const resolvePackageDependency = (
input: ResolveArtifactPathInput,
directory: string,
subpath: string,
request: BarePackageRequest,
metadata: PackageMetadata,
): CandidateResolution => {
const exportsEligible = packageExportsEligible(input);
if (exportsEligible && metadata.kind === "unavailable")
const subpath = request.packageSubpath;
if (request.dependencyExportsEligible && metadata.kind === "unavailable")
return unresolvedCandidate("unavailable", [metadata.limitation]);
const rawExports =
exportsEligible && metadata.kind === "available"
request.dependencyExportsEligible && metadata.kind === "available"
? metadata.exports
: undefined;
if (rawExports === undefined) {
@@ -437,8 +420,17 @@ const resolvePackageSpecifier = (
resolveFileCandidates(input, fileCandidates(directory)) ??
resolveDirectory(input, directory, metadata, new Set())
);
// Preserve the trailing slash for a package directory request.
const candidate = confineCandidate(`${directory}/${subpath.slice(2)}`);
// Resolve legacy ESM subpaths as URLs: suffixes identify the loaded module
// but do not belong to its filesystem path. Keep the declared specifier on
// the relationship; only derive the inventory lookup path here.
const fileSubpath =
input.moduleKind === "import"
? decodeModuleUrlPath(stripQueryAndFragment(subpath.slice(2)))
: subpath.slice(2);
if (typeof fileSubpath !== "string") return fileSubpath;
const candidate = confineCandidate(`${directory}/${fileSubpath}`);
if (input.moduleKind === "import" && typeof candidate === "string")
return resolveLegacyEsmSubpath(input, candidate);
return typeof candidate === "string"
? resolveCandidate(input, candidate, new Set())
: candidate;
@@ -451,6 +443,21 @@ const resolvePackageSpecifier = (
);
};
/** Legacy ESM package subpaths are exact URLs, never extension or directory lookups. */
const resolveLegacyEsmSubpath = (
input: ResolveArtifactPathInput,
candidate: string,
): CandidateResolution => {
const file = resolveFileCandidates(input, [candidate]);
if (file !== null) return file;
const source = input.files.get(input.sourcePath);
if (hasContainerDirectory(input.files, candidate, source?.container_sha256))
return unresolvedCandidate("rejected", [
`ESM package subpath ${candidate} names a directory; directory imports are unsupported and package main or index lookup was not attempted.`,
]);
return exactPackageTargetNotFound(candidate, "ESM subpath");
};
const resolvePackageExports = (
input: ResolveArtifactPathInput,
packagePath: string,
@@ -517,23 +524,36 @@ const resolveExportTarget = (
);
};
const barePackageName = (specifier: string): string | null => {
const segments = specifier.split("/");
if (specifier.startsWith("@"))
return segments.length >= 2 && segments[0] !== "" && segments[1] !== ""
? `${segments[0]}/${segments[1]}`
: null;
return segments[0] === "" ? null : (segments[0] ?? null);
};
/** Derived package identity and dependency policy; input retains the raw request. */
interface BarePackageRequest {
readonly packageName: string;
readonly packageSubpath: string;
readonly dependencyExportsEligible: boolean;
}
const packageExportsEligible = (input: ResolveArtifactPathInput): boolean => {
if (input.moduleKind !== "require") return true;
const parseBarePackageRequest = (
specifier: string,
moduleKind: ResolveArtifactPathInput["moduleKind"],
): BarePackageRequest | null => {
const segments = specifier.split("/");
let packageName = segments[0] ?? "";
if (packageName === "") return null;
if (specifier.startsWith("@")) {
const packageSegment = segments[1];
if (packageSegment === undefined || packageSegment === "") return null;
packageName = `${packageName}/${packageSegment}`;
}
// Node's CommonJS exports matcher excludes percent-containing package names.
// Compare the captured name so a scoped name cannot fall back to its scope.
const match = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/.exec(
input.declaredPath,
);
return match?.[1] === barePackageName(input.declaredPath);
const dependencyExportsEligible =
moduleKind !== "require" ||
/^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/.exec(specifier)?.[1] ===
packageName;
return {
packageName,
packageSubpath: `.${specifier.slice(packageName.length)}`,
dependencyExportsEligible,
};
};
const hasContainerDirectory = (
@@ -1061,12 +1081,12 @@ const notFoundCandidate = (): CandidateResolution => ({
const exactPackageTargetNotFound = (
path: string,
mapKind: "exports" | "imports",
targetKind: "exports" | "imports" | "ESM subpath",
): CandidateResolution => ({
resolvedPath: null,
status: "not-found",
limitations: [
`The selected package ${mapKind === "imports" ? "import" : "exports"} target ${path} does not exist in the inventoried artifact container; ${mapKind === "imports" ? "import" : "exports"} targets are exact files, so no extension or directory index was tried.`,
`The selected package ${targetKind === "imports" ? "import" : targetKind} target ${path} does not exist in the inventoried artifact container; ${targetKind === "imports" ? "import" : targetKind} targets are exact files, so no extension or directory index was tried.`,
],
});