Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

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:
Mcommon/lib/paths.ts | 100++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mexport/app/changelog/page.tsx | 2+-
Mhomepage/app/changelog/page.tsx | 2+-
Mhomepage/app/lib/docs.ts | 9++++++---
Mhomepage/app/lib/snapshot.ts | 8++++----
Mhomepage/app/lib/source.ts | 7++++++-
Mhomepage/app/lib/summary.ts | 4++--
Mscripts/next-build-trace.test.mjs | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mumtool/lib/paths.mjs | 2+-
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);