commit 8799ae0a09f20bfcaefae6bf9a01ce5cc6eedf04
parent 428699f16fc9c31be45e3ad196a3404f6f2bb1c9
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 20:49:43 -0400
common, homepage, export, scripts: every cwd-derived path op the three Next apps bundle opts out of Turbopack's tracing; the guard covers them (final review F8)
homepage/app/lib/source.ts' directory join, docs, snapshot, summary and both
changelog pages carry /* turbopackIgnore: true */; common/lib/paths.ts builds
every path through one opted-out under() and findMonorepoRoot's walk and
fallback are opted out (getPaths() returns the same paths). The guard is
scripts/next-build-trace.test.mjs (was umtool-build-trace): it adds the
homepage, export and editor apps and every common/ module but bin/, treats a
same-file function carrying a source as a source, skips regex literals when
matching brackets, and fails on main's source.ts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
9 files changed, 213 insertions(+), 74 deletions(-)
diff --git a/common/lib/paths.ts b/common/lib/paths.ts
@@ -172,78 +172,89 @@ export type Paths = {
let cached: Paths | null = null;
+// Every path in getPaths() is built on the repo root, which findMonorepoRoot()
+// finds by walking up from `process.cwd()`. Turbopack evaluates `process.cwd()`
+// statically, and a path op on a value derived from it becomes an asset
+// reference — to every file under it when the path is a directory
+// (plans/FACTS.md, "A path joined from `process.cwd()` …"). So every join on
+// such a value goes through this one opted-out call. Nothing changes at run
+// time.
+function under(...parts: string[]): string {
+ return path.join(/* turbopackIgnore: true */ ...parts);
+}
+
export function getPaths(): Paths {
if (cached) return cached;
const monorepoRoot = findMonorepoRoot();
const transcriptsDir =
- process.env.TRANSCRIPTS_DIR ?? path.join(monorepoRoot, "transcripts");
- const exportDir = path.join(monorepoRoot, "export");
+ process.env.TRANSCRIPTS_DIR ?? under(monorepoRoot, "transcripts");
+ const exportDir = under(monorepoRoot, "export");
const exportPublicDir =
- process.env.EXPORT_PUBLIC_DIR ?? path.join(exportDir, "public");
+ process.env.EXPORT_PUBLIC_DIR ?? under(exportDir, "public");
// Staging sibling of the served public dir (so it lands inside the test data
// root when EXPORT_PUBLIC_DIR is overridden). Not served; composed into
// exportPublicDir per site by the build:site step.
const exportIndexDir =
process.env.EXPORT_INDEX_DIR ??
- path.join(path.dirname(exportPublicDir), ".export-index");
- const exportSharedDir = path.join(exportIndexDir, "shared");
- const sitesDir = process.env.SITES_DIR ?? path.join(transcriptsDir, "sites");
- const homepageDir = path.join(sitesDir, "_homepage");
+ under(path.dirname(/* turbopackIgnore: true */ exportPublicDir), ".export-index");
+ const exportSharedDir = under(exportIndexDir, "shared");
+ const sitesDir = process.env.SITES_DIR ?? under(transcriptsDir, "sites");
+ const homepageDir = under(sitesDir, "_homepage");
const configDir =
process.env.ARCHILYZER_CONFIG_DIR ??
path.join(os.homedir(), ".config", "archilyzer");
cached = {
monorepoRoot,
transcriptsDir,
- channelsDir: path.join(transcriptsDir, "channels"),
+ channelsDir: under(transcriptsDir, "channels"),
savedVideosDir:
- process.env.SAVED_VIDEOS_DIR ?? path.join(transcriptsDir, "saved-videos"),
+ process.env.SAVED_VIDEOS_DIR ?? under(transcriptsDir, "saved-videos"),
sitesDir,
homepageDir,
- homepageConfigFile: path.join(homepageDir, "homepage.json"),
- homepageChartTemplatesFile: path.join(homepageDir, "chart-templates.json"),
- jobsDir: path.join(transcriptsDir, ".jobs"),
- workerScratchDir: path.join(transcriptsDir, ".worker-scratch"),
- schedulerStateFile: path.join(transcriptsDir, ".scheduler", "state.json"),
- autoQueueStateFile: path.join(transcriptsDir, ".auto-queue", "state.json"),
- workerDefaultsFile: path.join(transcriptsDir, ".workers", "defaults.json"),
- widgetPresetsFile: path.join(transcriptsDir, ".widget", "presets.json"),
- lmdbPath: path.join(transcriptsDir, "index.mdb"),
+ homepageConfigFile: under(homepageDir, "homepage.json"),
+ homepageChartTemplatesFile: under(homepageDir, "chart-templates.json"),
+ jobsDir: under(transcriptsDir, ".jobs"),
+ workerScratchDir: under(transcriptsDir, ".worker-scratch"),
+ schedulerStateFile: under(transcriptsDir, ".scheduler", "state.json"),
+ autoQueueStateFile: under(transcriptsDir, ".auto-queue", "state.json"),
+ workerDefaultsFile: under(transcriptsDir, ".workers", "defaults.json"),
+ widgetPresetsFile: under(transcriptsDir, ".widget", "presets.json"),
+ lmdbPath: under(transcriptsDir, "index.mdb"),
exportDir,
exportPublicDir,
- exportSummariesDir: path.join(exportPublicDir, "summaries"),
- exportTranscriptsDir: path.join(exportPublicDir, "transcripts"),
- exportSubsDir: path.join(exportPublicDir, "subs"),
- exportPostsDir: path.join(exportPublicDir, "posts"),
- exportDigestsDir: path.join(exportPublicDir, "digests"),
- exportStatsDir: path.join(exportPublicDir, "stats"),
+ exportSummariesDir: under(exportPublicDir, "summaries"),
+ exportTranscriptsDir: under(exportPublicDir, "transcripts"),
+ exportSubsDir: under(exportPublicDir, "subs"),
+ exportPostsDir: under(exportPublicDir, "posts"),
+ exportDigestsDir: under(exportPublicDir, "digests"),
+ exportStatsDir: under(exportPublicDir, "stats"),
exportIndexDir,
exportSharedDir,
- exportSharedTranscriptsDir: path.join(exportSharedDir, "transcripts"),
- exportSharedSubsDir: path.join(exportSharedDir, "subs"),
- exportSharedPostsDir: path.join(exportSharedDir, "posts"),
- exportSharedDigestsDir: path.join(exportSharedDir, "digests"),
- exportSitesIndexDir: path.join(exportIndexDir, "sites"),
+ exportSharedTranscriptsDir: under(exportSharedDir, "transcripts"),
+ exportSharedSubsDir: under(exportSharedDir, "subs"),
+ exportSharedPostsDir: under(exportSharedDir, "posts"),
+ exportSharedDigestsDir: under(exportSharedDir, "digests"),
+ exportSitesIndexDir: under(exportIndexDir, "sites"),
exportBuildsDir:
process.env.EXPORT_BUILDS_DIR ??
- path.join(path.dirname(exportPublicDir), ".export-builds"),
+ under(path.dirname(/* turbopackIgnore: true */ exportPublicDir), ".export-builds"),
settingsFile:
- process.env.SETTINGS_FILE ?? path.join(monorepoRoot, "settings.json"),
+ process.env.SETTINGS_FILE ?? under(monorepoRoot, "settings.json"),
editorChangelogFile:
process.env.EDITOR_CHANGELOG_FILE ??
- path.join(monorepoRoot, "editor", "CHANGELOG.md"),
+ under(monorepoRoot, "editor", "CHANGELOG.md"),
exportChangelogFile:
process.env.EXPORT_CHANGELOG_FILE ??
- path.join(exportDir, "CHANGELOG.md"),
+ under(exportDir, "CHANGELOG.md"),
chartsConfigFile:
process.env.CHARTS_CONFIG_FILE ??
- path.join(monorepoRoot, "chart-templates.json"),
+ under(monorepoRoot, "chart-templates.json"),
globalAliasesFile:
process.env.SEARCH_ALIASES_FILE ??
- path.join(transcriptsDir, "search-aliases.json"),
+ under(transcriptsDir, "search-aliases.json"),
globalTagsFile:
process.env.CURATED_TAGS_FILE ??
- path.join(transcriptsDir, TAGS_FILENAME),
+ under(transcriptsDir, TAGS_FILENAME),
ytdlpBin: process.env.YTDLP_BIN ?? "yt-dlp",
whisperBin: process.env.WHISPER_BIN ?? "whisper-cli",
whisperModel:
@@ -263,7 +274,7 @@ export function getPaths(): Paths {
galleryDlBin: process.env.GALLERY_DL_BIN ?? "gallery-dl",
parakeetBin:
process.env.PARAKEET_STITCH_BIN ??
- path.join(monorepoRoot, "scripts", "parakeet-stitch.mjs"),
+ under(monorepoRoot, "scripts", "parakeet-stitch.mjs"),
parakeetCliBin: process.env.PARAKEET_CLI ?? "parakeet-cli",
parakeetModel: process.env.PARAKEET_MODEL ?? "",
// Speaker-diarization wrapper, same shape as parakeetBin: a script we own,
@@ -271,7 +282,7 @@ export function getPaths(): Paths {
// today, pyannote later) without touching any caller.
diarizeBin:
process.env.DIARIZE_BIN ??
- path.join(monorepoRoot, "scripts", "diarize.mjs"),
+ under(monorepoRoot, "scripts", "diarize.mjs"),
ollamaUrl: (process.env.OLLAMA_URL ?? "http://127.0.0.1:11434").replace(
/\/+$/,
"",
@@ -288,13 +299,18 @@ export function getPaths(): Paths {
return cached;
}
+// Walks up from the working directory to the workspace root; falls back to
+// the working directory itself (an app's own directory). Every path op on
+// these cwd-derived values opts out of Turbopack's tracing, so what the
+// fallback evaluates to at build time never becomes an asset reference.
function findMonorepoRoot(): string {
- let dir = process.cwd();
+ const start = path.resolve(/* turbopackIgnore: true */ process.cwd());
+ let dir = start;
for (let i = 0; i < 8; i++) {
- if (fs.existsSync(path.join(dir, "pnpm-workspace.yaml"))) return dir;
- const parent = path.dirname(dir);
+ if (fs.existsSync(path.join(/* turbopackIgnore: true */ dir, "pnpm-workspace.yaml"))) return dir;
+ const parent = path.dirname(/* turbopackIgnore: true */ dir);
if (parent === dir) break;
dir = parent;
}
- return process.cwd();
+ return start;
}
diff --git a/export/app/changelog/page.tsx b/export/app/changelog/page.tsx
@@ -7,7 +7,7 @@ export const metadata: Metadata = { title: "Changelog" };
function loadChangelog(): string {
return readFileSync(
- path.join(process.cwd(), "CHANGELOG.md"),
+ path.join(/* turbopackIgnore: true */ process.cwd(), "CHANGELOG.md"),
"utf8",
);
}
diff --git a/homepage/app/changelog/page.tsx b/homepage/app/changelog/page.tsx
@@ -22,7 +22,7 @@ export const metadata: Metadata = {
function loadChangelog(): string | null {
try {
return readFileSync(
- path.join(process.cwd(), "..", "export", "CHANGELOG.md"),
+ path.join(/* turbopackIgnore: true */ process.cwd(), "..", "export", "CHANGELOG.md"),
"utf8",
);
} catch {
diff --git a/homepage/app/lib/docs.ts b/homepage/app/lib/docs.ts
@@ -13,8 +13,11 @@ export { DOC_GROUP_ORDER };
// Build-time only. `output: "export"` means every one of these reads happens
// during `next build`; nothing here runs per request. Mirrors the
// readFileSync-from-cwd approach in export/app/changelog/page.tsx.
+//
+// Every path op on a cwd-derived path opts out of Turbopack's asset tracing
+// (plans/FACTS.md, "A path joined from `process.cwd()` …").
function docPath(entry: DocEntry): string {
- return path.join(process.cwd(), "content", "docs", entry.file);
+ return path.join(/* turbopackIgnore: true */ process.cwd(), "content", "docs", entry.file);
}
// The manifest entries whose file actually exists.
@@ -27,7 +30,7 @@ function docPath(entry: DocEntry): string {
export function listDocs(): DocEntry[] {
return DOCS.filter((entry) => {
try {
- return fs.statSync(docPath(entry)).isFile();
+ return fs.statSync(/* turbopackIgnore: true */ docPath(entry)).isFile();
} catch {
return false;
}
@@ -42,7 +45,7 @@ export function findDoc(slug: string): DocEntry | null {
// heading so it stands alone as a document; the page renders its own <h1> from
// the manifest, and two titles in a row reads as a mistake.
export function readDoc(entry: DocEntry): string {
- const raw = fs.readFileSync(docPath(entry), "utf8");
+ const raw = fs.readFileSync(/* turbopackIgnore: true */ docPath(entry), "utf8");
return raw.replace(/^?\s*#[^\n#][^\n]*\n+/, "");
}
diff --git a/homepage/app/lib/snapshot.ts b/homepage/app/lib/snapshot.ts
@@ -29,24 +29,24 @@ export const SNAPSHOT_HREF = "/downloads/archilyzer-source.tar.gz";
export function loadSnapshot(): Snapshot | null {
try {
const file = path.join(
- process.cwd(),
+ /* turbopackIgnore: true */ process.cwd(),
"public",
"downloads",
"snapshot.json",
);
- const parsed = JSON.parse(fs.readFileSync(file, "utf8")) as Snapshot;
+ const parsed = JSON.parse(fs.readFileSync(/* turbopackIgnore: true */ file, "utf8")) as Snapshot;
if (!parsed || typeof parsed.bytes !== "number" || !parsed.sha256) {
return null;
}
// Believe the sidecar only if the file it describes is actually present —
// they are written together but deployed as separate assets.
const tarball = path.join(
- process.cwd(),
+ /* turbopackIgnore: true */ process.cwd(),
"public",
"downloads",
path.basename(SNAPSHOT_HREF),
);
- if (!fs.statSync(tarball).isFile()) return null;
+ if (!fs.statSync(/* turbopackIgnore: true */ tarball).isFile()) return null;
return parsed;
} catch {
return null;
diff --git a/homepage/app/lib/source.ts b/homepage/app/lib/source.ts
@@ -19,8 +19,13 @@ import {
// manifest is null too (parseSourceManifest checks every number the page
// reads), so a bad file is the empty state, never a crash in `next build`.
// `pub` is the test's seam.
+//
+// The default is a DIRECTORY join on `process.cwd()`, which Turbopack would
+// trace as every file under `public/` (the source mirror among them, and it
+// grows with every publish): the opt-out keeps it a plain run-time path
+// (plans/FACTS.md, "A path joined from `process.cwd()` …").
export function loadSourceManifest(
- pub: string = path.join(process.cwd(), "public"),
+ pub: string = path.join(/* turbopackIgnore: true */ process.cwd(), "public"),
): SourceManifest | null {
try {
const manifest = parseSourceManifest(
diff --git a/homepage/app/lib/summary.ts b/homepage/app/lib/summary.ts
@@ -24,10 +24,10 @@ export function loadSummary(): HomepageSummary | null {
try {
const file = summaryFile(
process.env,
- path.join(process.cwd(), "public", "homepage-summary.json"),
+ path.join(/* turbopackIgnore: true */ process.cwd(), "public", "homepage-summary.json"),
);
const parsed = JSON.parse(
- fs.readFileSync(file, "utf8"),
+ fs.readFileSync(/* turbopackIgnore: true */ file, "utf8"),
) as HomepageSummary;
// A file that exists but carries no sites/totals is as uninformative as no
// file at all; treat it the same rather than rendering an empty dashboard.
diff --git a/scripts/next-build-trace.test.mjs b/scripts/next-build-trace.test.mjs
@@ -1,4 +1,5 @@
-// umtool's modules must not hand Turbopack a directory to bundle.
+// No module a Next app bundles may hand Turbopack a directory to trace: umtool,
+// the editor, the export and the homepage, and the common/ modules they import.
//
// Turbopack evaluates `process.cwd()` (and a module's own `import.meta.url` /
// `__dirname`) statically, as a path in the project, and a `path.join` /
@@ -15,7 +16,9 @@
// arguments carry a value derived IN THAT FILE from `process.cwd()`,
// `import.meta.url`, `import.meta.dirname|filename` or `__dirname` must open its
// argument list with the documented opt-out, `/* turbopackIgnore: true */`.
-// The comment changes nothing at run time. `os.homedir()` is NOT a source: the
+// The comment changes nothing at run time. A function declared in the file whose
+// body carries a source is a source too (`const ROOT = findMonorepoRoot()`).
+// `os.homedir()` is NOT a source: the
// tracer does not follow it (a build with HOME pointed at a synthetic home full
// of out-of-root symlinks inside the project succeeds), and neither is
// `process.env.*`.
@@ -49,7 +52,30 @@ function prepare(text) {
return s;
}
-/** Index just past the bracket that closes the one at `open`. */
+/** Index just past the regex literal whose opening `/` is at `i`, or -1 when
+ * the `/` there is a division. A `/` opens a regex after an operator, an
+ * opening bracket, a separator or `return` — good enough for this repo. */
+function regexEnd(s, i) {
+ let j = i - 1;
+ while (j >= 0 && /\s/.test(s[j])) j -= 1;
+ const before = j < 0 ? "" : s[j];
+ const word = s.slice(0, j + 1).match(/[A-Za-z_$][\w$]*$/)?.[0];
+ if (!(before === "" || "(,=:[!&|?{};+-*%<>~^".includes(before) || word === "return" || word === "typeof")) return -1;
+ if (s[i + 1] === "/" || s[i + 1] === "*") return -1;
+ let inClass = false;
+ for (let k = i + 1; k < s.length; k += 1) {
+ const c = s[k];
+ if (c === "\n") return -1;
+ if (c === "\\") k += 1;
+ else if (c === "[") inClass = true;
+ else if (c === "]") inClass = false;
+ else if (c === "/" && !inClass) return k + 1;
+ }
+ return -1;
+}
+
+/** Index just past the bracket that closes the one at `open`. Strings and
+ * regex literals are skipped whole. */
function closeOf(s, open) {
let depth = 0;
let quote = null;
@@ -60,6 +86,13 @@ function closeOf(s, open) {
else if (c === quote) quote = null;
continue;
}
+ if (c === "/") {
+ const end = regexEnd(s, i);
+ if (end !== -1) {
+ i = end - 1;
+ continue;
+ }
+ }
if (c === '"' || c === "'" || c === "`") quote = c;
else if (c === "(" || c === "[" || c === "{") depth += 1;
else if (c === ")" || c === "]" || c === "}") {
@@ -90,6 +123,16 @@ function taintedNames(s) {
}
decls.push({ name: m[1], rhs: s.slice(re.lastIndex, end) });
}
+ // A function declared in this file whose body carries a source: a call to it
+ // is a source too (`const ROOT = findMonorepoRoot()`, where the function
+ // walks up from `process.cwd()` and falls back to it).
+ const fnRe = /\bfunction\s*\*?\s*([A-Za-z_$][\w$]*)\s*\(/g;
+ for (let m; (m = fnRe.exec(s)); ) {
+ const params = fnRe.lastIndex - 1;
+ const bodyOpen = s.indexOf("{", closeOf(s, params));
+ if (bodyOpen === -1) continue;
+ decls.push({ name: m[1], rhs: s.slice(params, closeOf(s, bodyOpen)) });
+ }
const names = new Set();
for (let grew = true; grew; ) {
grew = false;
@@ -133,18 +176,21 @@ export function untracedCalls(text) {
return out;
}
+/** Every module under `dir`, tests and declarations aside. */
+function modulesUnder(dir, out = []) {
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
+ if (e.name === "node_modules" || e.name.startsWith(".")) continue;
+ const p = path.join(dir, e.name);
+ if (e.isDirectory()) modulesUnder(p, out);
+ else if (/\.(?:mjs|cjs|js|ts|tsx)$/.test(e.name) && !/\.test\./.test(e.name) && !e.name.endsWith(".d.ts")) out.push(p);
+ }
+ return out;
+}
+
/** umtool's modules that a Next build can reach: the app, its libs, the pipeline. */
-function appModules() {
+function umtoolModules() {
const out = [];
- const walk = (dir) => {
- for (const e of readdirSync(dir, { withFileTypes: true })) {
- if (e.name === "node_modules" || e.name.startsWith(".")) continue;
- const p = path.join(dir, e.name);
- if (e.isDirectory()) walk(p);
- else if (/\.(?:mjs|js|ts|tsx)$/.test(e.name) && !/\.test\./.test(e.name) && !e.name.endsWith(".d.ts")) out.push(p);
- }
- };
- for (const d of ["app", "components", "lib"]) walk(path.join(UMTOOL, d));
+ for (const d of ["app", "components", "lib"]) modulesUnder(path.join(UMTOOL, d), out);
for (const e of readdirSync(path.join(UMTOOL, "report-to-video"))) {
if (e.endsWith(".mjs") && !e.includes(".test.")) out.push(path.join(UMTOOL, "report-to-video", e));
}
@@ -153,6 +199,34 @@ function appModules() {
return out;
}
+/**
+ * The editor's, the export's and the homepage's modules a Next build can
+ * reach: each app's `app/` (the editor's `lib/` and `instrumentation.ts` too),
+ * and every module of common/ but its CLIs (`bin/`, which no app imports). The
+ * common set is wider than what the apps import today, on purpose: a module
+ * that starts being imported is already covered.
+ */
+function nextAppModules() {
+ const out = [];
+ for (const d of ["homepage/app", "export/app", "editor/app", "editor/lib"]) modulesUnder(path.join(REPO, d), out);
+ out.push(path.join(REPO, "editor", "instrumentation.ts"));
+ for (const e of readdirSync(path.join(REPO, "common"), { withFileTypes: true })) {
+ if (!e.isDirectory() || e.name === "bin" || e.name === "node_modules" || e.name.startsWith(".")) continue;
+ modulesUnder(path.join(REPO, "common", e.name), out);
+ }
+ return out;
+}
+
+function untracedIn(files) {
+ const bad = [];
+ for (const file of files) {
+ for (const f of untracedCalls(readFileSync(file, "utf8"))) {
+ bad.push(`${path.relative(REPO, file)}:${f.line} ${f.call}`);
+ }
+ }
+ return bad;
+}
+
test("the check flags slice Q's join and passes the opted-out form", () => {
const bad = [
'const REPO_ROOT = findRepoRoot(process.cwd());',
@@ -180,13 +254,54 @@ test("sources: cwd, import.meta, __dirname; not homedir, env, or a comment", ()
assert.equal(untracedCalls('const cwd = path.join(/* turbopackIgnore: true */ process.cwd(), "s");\nf(path.join(a, { cwd: 1 }));').length, 0);
});
+test("the check flags the homepage's source.ts and paths.ts' walk as main had them", () => {
+ // homepage/app/lib/source.ts before the opt-out: a DIRECTORY join on the cwd.
+ const source = [
+ "export function loadSourceManifest(",
+ ' pub: string = path.join(process.cwd(), "public"),',
+ "): SourceManifest | null {",
+ ' return fs.readFileSync(path.join(pub, "source", "manifest.json"), "utf8");',
+ "}",
+ ].join("\n");
+ assert.ok(untracedCalls(source).some((f) => f.call.includes('path.join(process.cwd(), "public")')));
+ // common/lib/paths.ts before: the walk, and a join on the root it returns.
+ const walk = [
+ "function findMonorepoRoot(): string {",
+ " let dir = process.cwd();",
+ ' if (fs.existsSync(path.join(dir, "pnpm-workspace.yaml"))) return dir;',
+ " return process.cwd();",
+ "}",
+ "const monorepoRoot = findMonorepoRoot();",
+ 'const transcriptsDir = process.env.TRANSCRIPTS_DIR ?? path.join(monorepoRoot, "transcripts");',
+ ].join("\n");
+ const found = untracedCalls(walk).map((f) => f.call);
+ assert.ok(found.some((c) => c.includes('path.join(dir, "pnpm-workspace.yaml")')), JSON.stringify(found));
+ assert.ok(found.some((c) => c.includes('path.join(monorepoRoot, "transcripts")')), JSON.stringify(found));
+});
+
+test("brackets inside a regex literal or a string do not end a call early", () => {
+ // A regex holding a quote used to swallow the rest of the file as one body.
+ const text = [
+ "function esc(s) { return s.replace(/['\"&]/g, \"\"); }",
+ "const out = path.join(dir, esc(x));",
+ "if (import.meta.url === `file://${process.argv[1]}`) main();",
+ ].join("\n");
+ assert.deepEqual(untracedCalls(text), []);
+});
+
test("no umtool module the app can import joins a cwd-derived path without opting out", () => {
- const bad = [];
- for (const file of appModules()) {
- for (const f of untracedCalls(readFileSync(file, "utf8"))) {
- bad.push(`${path.relative(REPO, file)}:${f.line} ${f.call}`);
- }
- }
+ const bad = untracedIn(umtoolModules());
+ assert.deepEqual(
+ bad,
+ [],
+ "add /* turbopackIgnore: true */ as the first argument (see this file's header):\n" + bad.join("\n"),
+ );
+});
+
+test("no module the editor, the export or the homepage can bundle joins a cwd-derived path without opting out", () => {
+ const files = nextAppModules();
+ assert.ok(files.length > 500, `only ${files.length} modules found`);
+ const bad = untracedIn(files);
assert.deepEqual(
bad,
[],
diff --git a/umtool/lib/paths.mjs b/umtool/lib/paths.mjs
@@ -106,7 +106,7 @@ const dedupe = (list) => [...new Set(list.map((p) => path.resolve(p)))];
* symlinked to another drive) and was OOM-killed, or died on the first symlink
* out of the root. A worktree with no transcripts/ builds fine, which is how it
* shipped. The comment is the documented per-expression opt-out; the values at
- * run time are unchanged. scripts/umtool-build-trace.test.mjs holds the line.
+ * run time are unchanged. scripts/next-build-trace.test.mjs holds the line.
*/
export function findRepoRoot(start) {
let dir = path.resolve(/* turbopackIgnore: true */ start);