From 6874c3b5626fa70826c141a508acffc4735d0d8f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 30 Oct 2026 00:18:39 -0500 Subject: [PATCH] Ship a joined island data path when another directory has its name A module that names fallback/payload.txt through join(here, "fallback", "payload.txt") also names "payload.txt" alone. That name was checked against the first file of that name the directory walk found, so a payload.txt in another subdirectory made the build fail with SC8006 even though the joined path ships. A name whose file a named path of the same module ships is no longer reported. --- .../compiler/src/node-islands/modules.test.ts | 15 +++++++++++++++ packages/compiler/src/node-islands/modules.ts | 9 +++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/node-islands/modules.test.ts b/packages/compiler/src/node-islands/modules.test.ts index 45769cdbe..381fac346 100644 --- a/packages/compiler/src/node-islands/modules.test.ts +++ b/packages/compiler/src/node-islands/modules.test.ts @@ -334,6 +334,21 @@ describe("the collected module tree", () => { expect(paths.filter((path) => !path.endsWith(".cjs"))).toEqual(["assets/config.json"]); }); + test("a joined data path ships even when another directory has a file of that name", () => { + const root = project({ + "src/main.ts": "", + "src/loader.cjs": + 'const { join } = require("node:path");\nrequire("node:fs").readFileSync(join(__dirname, "fallback", "payload.txt"));\n', + "src/a-other/payload.txt": "not named by the module\n", + "src/fallback/payload.txt": "shipped\n", + }); + const collector = new NodeIslandModuleCollector(); + expect(collector.add(join(root, "src/main.ts"), "./loader.cjs")).toBeNull(); + const paths = collector.tree()!.files.map((file) => file.path); + expect(paths).toContain("fallback/payload.txt"); + expect(paths).not.toContain("a-other/payload.txt"); + }); + test("a named file that is only elsewhere in the module's directory is a build error", () => { const root = project({ "src/main.ts": "", diff --git a/packages/compiler/src/node-islands/modules.ts b/packages/compiler/src/node-islands/modules.ts index 4f7569de8..1e3e43ab5 100644 --- a/packages/compiler/src/node-islands/modules.ts +++ b/packages/compiler/src/node-islands/modules.ts @@ -368,6 +368,8 @@ export class NodeIslandModuleCollector { return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel); }; const unresolved: { name: string; at: number }[] = []; + /** The names of the files this module's named paths ship. */ + const shippedNames = new Set(); for (const [name, at] of namedDataPaths(text)) { if (isAbsolute(name) || name.includes("\0")) continue; const path = resolve(root, name); @@ -382,6 +384,7 @@ export class NodeIslandModuleCollector { if (target === null || !inside(target)) continue; // A link inside the directory stays a link in the tree (placed). this.files.add(this.placed(path)); + shippedNames.add(basename(path)); } if (unresolved.length === 0) return null; // The files of the module's directory by name, links leading outside it @@ -405,9 +408,11 @@ export class NodeIslandModuleCollector { }; walk(root); for (const { name, at } of unresolved) { + // A name of a file another named path ships (`"payload.txt"` beside + // `join(here, "fallback", "payload.txt")`) is not missing, wherever + // other files of that name are. + if (shippedNames.has(basename(name))) continue; const found = byName.get(basename(name)); - // A file another named path ships (`"payload.txt"` beside - // `join(here, "fallback", "payload.txt")`) is not missing. if (found === undefined || this.files.has(trackedRealpath(found) ?? found)) continue; const line = text.slice(0, at).split("\n").length; const there = portable(relative(root, found));