{"record":{"id":"1905b612899681b3","repo":"chenglou/pretext","slug":"built-html-not-found-for-relativepath","errorCode":null,"errorMessage":"Built HTML not found for ${relativePath}","messagePattern":"Built HTML not found for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/build-demo-site.ts","lineNumber":61,"sourceCode":"]\n\nfor (let index = 0; index < targets.length; index++) {\n  const entry = targets[index]!\n  await moveBuiltHtml(entry.source, entry.target)\n}\n\nawait rm(path.join(outdir, 'pages'), { recursive: true, force: true })\n\nasync function resolveBuiltHtmlPath(relativePath: string): Promise<string> {\n  const candidates = [\n    path.join(outdir, relativePath),\n    path.join(outdir, 'pages', 'demos', relativePath),\n  ]\n  for (let index = 0; index < candidates.length; index++) {\n    const candidate = candidates[index]!\n    if (await Bun.file(candidate).exists()) return candidate\n  }\n  throw new Error(`Built HTML not found for ${relativePath}`)\n}\n\nasync function moveBuiltHtml(sourceRelativePath: string, targetRelativePath: string): Promise<void> {\n  const sourcePath = await resolveBuiltHtmlPath(sourceRelativePath)\n  const targetPath = path.join(outdir, targetRelativePath)\n  let html = await readFile(sourcePath, 'utf8')\n  html = rebaseRelativeAssetUrls(html, sourcePath, targetPath)\n  html = rewriteDemoLinksForStaticRoot(html, targetRelativePath)\n\n  await mkdir(path.dirname(targetPath), { recursive: true })\n  await writeFile(targetPath, html)\n  if (sourcePath !== targetPath) await rm(sourcePath)\n}\n\nfunction rebaseRelativeAssetUrls(html: string, sourcePath: string, targetPath: string): string {\n  return html.replace(/\\b(src|href)=\"([^\"]+)\"/g, (_match, attr: string, value: string) => {\n    if (!value.startsWith('.')) return `${attr}=\"${value}\"`\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/chenglou/pretext/blob/ac49b09b7d83ede19581fa94a8b892b07d309baf/scripts/build-demo-site.ts#L43-L79","documentation":"Thrown by resolveBuiltHtmlPath in the demo-site build script. After `bun build` emits HTML for the listed entrypoints into `site/`, the script relocates each output to a pretty URL path. resolveBuiltHtmlPath checks two candidate locations — `site/<relativePath>` and `site/pages/demos/<relativePath>` — and throws if neither exists. This means `bun build` finished (exit code 0 was checked earlier) but did not emit the file the targets table expects.","triggerScenarios":"The targets array (source/target pairs) drives moveBuiltHtml for each entry. Throw when both Bun.file(...).exists() checks return false. Concrete causes: an entry in `entrypoints` was renamed or deleted so `bun build` legitimately produced no output for it but the targets table still references the old name; `bun build` emits nested-path outputs (e.g. masonry/index.html) at a path that matches neither candidate; a Bun version change altered the output layout (e.g. preserving more of the input path under outdir); the file was emitted but with a different extension or case.","commonSituations":"A demo page was added to entrypoints but its target/source names in the targets table don't match the actual emitted filename; a directory-style entrypoint (masonry/index.html) emits to site/masonry/index.html (candidate 1) which works, but a typo in targets sends the script looking for a non-existent candidate; partial build where Bun skipped a failed entrypoint but still exited 0; cross-platform path-separator issues when constructing candidates with path.join.","solutions":["List `site/` and `site/pages/demos/` after the build step to see what Bun actually emitted — the discrepancy between actual and expected filenames is the bug.","Reconcile the `entrypoints` array (lines 6-17) with the `targets` array (lines 32-43): every `source` in targets must correspond to a file bun build produces from an entrypoint.","Run `bun build <entrypoints...> --outdir site` manually and inspect the tree before the move loop runs.","If Bun's output layout changed, add the actual emitted path as a third candidate in resolveBuiltHtmlPath rather than special-casing callers.","Make the error message list the candidates it tried (currently it only prints relativePath) so the mismatch is visible without a debugger."],"exampleFix":"// before\nasync function resolveBuiltHtmlPath(relativePath: string): Promise<string> {\n  const candidates = [\n    path.join(outdir, relativePath),\n    path.join(outdir, 'pages', 'demos', relativePath),\n  ]\n  for (let index = 0; index < candidates.length; index++) {\n    const candidate = candidates[index]!\n    if (await Bun.file(candidate).exists()) return candidate\n  }\n  throw new Error(`Built HTML not found for ${relativePath}`)\n}\n// after — list the candidates tried so the missing path is obvious\nasync function resolveBuiltHtmlPath(relativePath: string): Promise<string> {\n  const candidates = [\n    path.join(outdir, relativePath),\n    path.join(outdir, 'pages', 'demos', relativePath),\n  ]\n  for (const candidate of candidates) {\n    if (await Bun.file(candidate).exists()) return candidate\n  }\n  throw new Error(`Built HTML not found for ${relativePath}. Looked at: ${candidates.join(', ')}`)\n}","handlingStrategy":"validation","validationCode":"// Reconcile entrypoints <-> targets before the move loop runs\nfunction reconcileBuildTargets(entrypoints: string[], targets: { source: string }[]): void {\n  for (const target of targets) {\n    const matchingEntrypoint = entrypoints.find(e => e.endsWith(target.source))\n    if (matchingEntrypoint === undefined) {\n      throw new Error(`targets references ${target.source} but no entrypoint ends with it; build will not emit it.`)\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the entrypoints and targets arrays in lockstep: every targets[].source must correspond to an entry in entrypoints.","After `bun build`, list the outdir tree before invoking moveBuiltHtml so a missing emit is visible.","If Bun's output layout changes across versions, add the new actual path as another candidate in resolveBuiltHtmlPath.","Make resolveBuiltHtmlPath's error list the candidates it tried — the current message hides where it looked."],"tags":["build","demo-site","filesystem","bun-build"],"backgroundTag":null,"analyzedSha":"ac49b09b7d83ede19581fa94a8b892b07d309baf","analyzedAt":"2026-08-12T17:03:16.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}