fix(javascript): resolve relative ESM backslash paths (#4323)

Normalize raw URL separators after admitting an original relative ESM
request, before artifact path validation and root confinement. Preserve
the authored declaration and the existing encoded-separator checks.

Retain Node-backed static and dynamic loader controls and compare the
application graph with the historical importer already present on main.
Narrow PR #4323 to this remaining case after the upstream #4269 fix.

Signed-off-by: Jiakai Xu <jiakaiPeanut@gmail.com>
This commit is contained in:
Jiakai Xu authored and GitHub committed 2026-10-29 02:03:00 +08:00
1 parent f19436c156
commit 19032204aa
3 files changed
+75 -11

No files matched your search

@@ -154,6 +154,41 @@ describe("Windows CommonJS request classification", () => {
});
});
describe("relative ESM URL separators", () => {
it.each([
["./dir\\dep.mjs", "resolved", "app/dir/dep.mjs"],
["../shared\\dep.mjs", "resolved", "shared/dep.mjs"],
["./unused\\..\\dir\\dep.mjs", "resolved", "app/dir/dep.mjs"],
["./..\\..\\outside.mjs", "rejected", null],
["./dir%5cdep.mjs", "rejected", null],
["./dir%2fdep.mjs", "rejected", null],
["./dir\\dep%00.mjs", "rejected", null],
[".\\dir\\dep.mjs", "rejected", null],
] as const)(
"resolves %s with module URL rules",
(declaredPath, status, target) => {
expect(
resolve({
declaredPath,
sourcePath: "app/main.mjs",
context: "module-specifier",
moduleKind: "import",
files: fileMap([
file("app/main.mjs", "root"),
file("app/dir/dep.mjs", "root"),
file("shared/dep.mjs", "root"),
file("outside.mjs", "root"),
]),
}),
).toMatchObject({
declared_path: declaredPath,
resolution_status: status,
resolved_path: target,
});
},
);
});
describe("literal POSIX inventory base segments", () => {
it("validates the final URL after a parent reference removes the encoded base segment", () => {
expect(
@@ -636,7 +636,11 @@ const contextualCandidate = (
}
const hasUrlPathSemantics =
context === "module-specifier" && input.moduleKind === "import";
if (hasUrlPathSemantics) declared = htmlUrlText(declared);
if (hasUrlPathSemantics) {
declared = htmlUrlText(declared);
if (isRelativeJavaScriptModuleSpecifier(input.declaredPath))
declared = declared.replaceAll("\\", "/");
}
const rejected = rejectDeclaration(
declared,
declaresLiteralPercent(input) || hasUrlPathSemantics,