fix(javascript): apply loader-owned module lookup rules
Keep relative ESM imports exact, restrict Node legacy probes to .js/.json/.node, and treat empty package main as absent. Stop Node package-main lookup before nested package reinterpretation, while retaining generic artifact inference. Replace stale recursive and .cjs probe expectations. Verify CLI and MCP graph results against real Node 24 runtime controls, including exact suffixes, index fallback, and nested package main refusal. Refs #2020, #2021, #2022
This commit is contained in:
1 parent
d9d2a17381
commit
c0904b4c1a
3 files changed
+412
-37
No files matched your search
@@ -145,16 +145,21 @@ const resolvePath = (input: ResolveArtifactPathInput): CandidateResolution => {
|
||||
`The exact HTML resource ${confined} was not found among the selected application files.`,
|
||||
])
|
||||
);
|
||||
return resolveCandidate(
|
||||
input,
|
||||
confined,
|
||||
new Set(),
|
||||
const isModuleUrlPath =
|
||||
input.context === "module-specifier" &&
|
||||
input.moduleKind !== undefined &&
|
||||
(input.declaredPath.startsWith(".") || input.declaredPath.startsWith("/"))
|
||||
(input.declaredPath.startsWith(".") ||
|
||||
input.declaredPath.startsWith("/") ||
|
||||
fileUrlPath(input.declaredPath) !== undefined);
|
||||
const emittedSourceCandidates =
|
||||
isModuleUrlPath && input.moduleKind !== undefined
|
||||
? typescriptEmittedSourceCandidates(input.sourcePath, confined)
|
||||
: [],
|
||||
);
|
||||
: [];
|
||||
if (isModuleUrlPath && input.moduleKind === "import")
|
||||
return (
|
||||
resolveFileCandidates(input, [confined, ...emittedSourceCandidates]) ??
|
||||
notFoundCandidate()
|
||||
);
|
||||
return resolveCandidate(input, confined, new Set(), emittedSourceCandidates);
|
||||
};
|
||||
|
||||
const rejectDeclaration = (declared: string): CandidateResolution | null => {
|
||||
@@ -275,7 +280,10 @@ const bareModuleCandidate = (
|
||||
if (request.dependencyExportsEligible && metadata.kind !== "absent")
|
||||
return resolvePackageDependency(input, candidate, request, metadata);
|
||||
if (request.packageSubpath === ".") {
|
||||
const direct = resolveFileCandidates(input, fileCandidates(candidate));
|
||||
const direct = resolveFileCandidates(
|
||||
input,
|
||||
fileCandidates(candidate, input.moduleKind),
|
||||
);
|
||||
if (direct !== null) return direct;
|
||||
}
|
||||
if (hasContainerDirectory(input.files, candidate, source?.container_sha256))
|
||||
@@ -417,8 +425,10 @@ const resolvePackageDependency = (
|
||||
if (rawExports === undefined) {
|
||||
if (subpath === ".")
|
||||
return (
|
||||
resolveFileCandidates(input, fileCandidates(directory)) ??
|
||||
resolveDirectory(input, directory, metadata, new Set())
|
||||
resolveFileCandidates(
|
||||
input,
|
||||
fileCandidates(directory, input.moduleKind),
|
||||
) ?? resolveDirectory(input, directory, metadata, new Set())
|
||||
);
|
||||
// Resolve legacy ESM subpaths as URLs: suffixes identify the loaded module
|
||||
// but do not belong to its filesystem path. Keep the declared specifier on
|
||||
@@ -753,7 +763,7 @@ const resolveCandidate = (
|
||||
emittedSourceCandidates: readonly string[] = [],
|
||||
): CandidateResolution =>
|
||||
resolveFileCandidates(input, [
|
||||
...fileCandidates(candidate),
|
||||
...fileCandidates(candidate, input.moduleKind),
|
||||
...emittedSourceCandidates,
|
||||
]) ??
|
||||
resolveDirectory(
|
||||
@@ -771,8 +781,10 @@ const resolveDirectory = (
|
||||
): CandidateResolution => {
|
||||
if (metadata.kind === "absent")
|
||||
return (
|
||||
resolveFileCandidates(input, indexCandidates(directory)) ??
|
||||
notFoundCandidate()
|
||||
resolveFileCandidates(
|
||||
input,
|
||||
indexCandidates(directory, input.moduleKind),
|
||||
) ?? notFoundCandidate()
|
||||
);
|
||||
if (metadata.kind === "unavailable")
|
||||
return unresolvedCandidate("unavailable", [metadata.limitation]);
|
||||
@@ -780,15 +792,20 @@ const resolveDirectory = (
|
||||
return unresolvedCandidate("unavailable", [
|
||||
`Directory package entrypoint cycle includes ${metadata.path}.`,
|
||||
]);
|
||||
const preferred = [
|
||||
metadata.main,
|
||||
...(input.moduleKind === undefined ? [metadata.module] : []),
|
||||
];
|
||||
const entry = preferred.find((value) => value !== undefined);
|
||||
const entry =
|
||||
metadata.main === ""
|
||||
? undefined
|
||||
: metadata.main !== undefined
|
||||
? metadata.main
|
||||
: input.moduleKind === undefined
|
||||
? metadata.module
|
||||
: undefined;
|
||||
if (entry === undefined)
|
||||
return (
|
||||
resolveFileCandidates(input, indexCandidates(directory)) ??
|
||||
notFoundCandidate()
|
||||
resolveFileCandidates(
|
||||
input,
|
||||
indexCandidates(directory, input.moduleKind),
|
||||
) ?? notFoundCandidate()
|
||||
);
|
||||
if (typeof entry !== "string" || entry.length === 0)
|
||||
return unresolvedCandidate("unavailable", [
|
||||
@@ -809,10 +826,12 @@ const resolveDirectory = (
|
||||
if (typeof confined !== "string") return confined;
|
||||
const selected =
|
||||
resolveFileCandidates(entryInput, [
|
||||
...fileCandidates(confined),
|
||||
...indexCandidates(confined),
|
||||
]) ?? resolveFileCandidates(input, indexCandidates(directory));
|
||||
...fileCandidates(confined, entryInput.moduleKind),
|
||||
...indexCandidates(confined, entryInput.moduleKind),
|
||||
]) ??
|
||||
resolveFileCandidates(input, indexCandidates(directory, input.moduleKind));
|
||||
if (selected !== null) return selected;
|
||||
if (input.moduleKind !== undefined) return notFoundCandidate();
|
||||
return resolveCandidate(
|
||||
entryInput,
|
||||
confined,
|
||||
@@ -837,13 +856,25 @@ const resolveFileCandidates = (
|
||||
return null;
|
||||
};
|
||||
|
||||
const fileCandidates = (candidate: string): readonly string[] => [
|
||||
const NODE_MODULE_EXTENSIONS = [".js", ".json", ".node"] as const;
|
||||
|
||||
const fileCandidates = (
|
||||
candidate: string,
|
||||
moduleKind?: ResolveArtifactPathInput["moduleKind"],
|
||||
): readonly string[] => [
|
||||
candidate,
|
||||
...EXTENSIONS.map((extension) => `${candidate}${extension}`),
|
||||
...(moduleKind === undefined ? EXTENSIONS : NODE_MODULE_EXTENSIONS).map(
|
||||
(extension) => `${candidate}${extension}`,
|
||||
),
|
||||
];
|
||||
|
||||
const indexCandidates = (candidate: string): readonly string[] =>
|
||||
EXTENSIONS.map((extension) => posix.join(candidate, `index${extension}`));
|
||||
const indexCandidates = (
|
||||
candidate: string,
|
||||
moduleKind?: ResolveArtifactPathInput["moduleKind"],
|
||||
): readonly string[] =>
|
||||
(moduleKind === undefined ? EXTENSIONS : NODE_MODULE_EXTENSIONS).map(
|
||||
(extension) => posix.join(candidate, `index${extension}`),
|
||||
);
|
||||
|
||||
type PackageTargetBlockCause = "null" | "empty-array";
|
||||
|
||||
|
||||
Reference in new issue
Block a user