Archilyzer · Source

archilyzer

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

commit 35814656e122ae95dd0f05d187cd91641befebef
parent 2fea61e240b83155fdd9498fd12b77c58eaba5c8
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 13:13:28 -0400

docker: build-site.sh builds in the container's own .next, publishes the composed public/, and refuses a bundle that is not the site's

(A) export/.next was a symlink to /site/.next. Turbopack's server runtime
imports next's own externals (the icon routes' @vercel/og) from each chunk's
REAL path, under /site, where there is no node_modules, so every container
build died prerendering /favicon.ico — on main's image as on W3's. .next now
stays in the container. Carrying .next/cache between runs was tried and
measured to save nothing (TypeScript 30.3 s -> 32.6 s, the two-site run
90 s -> 88 s), so it is not kept.

(B) next build publishes export/public into out/, and export/public was the
copy baked into the image, not the public/ compose had just written to
/site/public: out/ carried no data, or whatever site the build context had
composed last. export/public is now a link to /site/public, after the tracked
.svg assets are copied in (never over a file there, and nothing else).

Before handing out/ back, builtBundleProblem must pass (site.json and
corpus.json both name SITE_ID); a refusal fails the build and leaves /site/out
as it was.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mdocker/build-site.sh | 55++++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 46 insertions(+), 9 deletions(-)

diff --git a/docker/build-site.sh b/docker/build-site.sh @@ -7,7 +7,7 @@ # mounted READ-ONLY. This container only composes ITS site's public/ and runs # `next build`, writing everything into the per-site mount at /site. # -# Expected run-time mounts (see runDockerBuildAllPhase): +# Expected run-time mounts (see runDockerBuildOne in common/publish/build.ts): # <transcriptsDir> -> /data/transcripts (ro) # <export/.export-index> -> /data/export/.export-index (ro) # <exportBuildsDir>/<siteId> -> /site (rw) @@ -24,14 +24,35 @@ export EXPORT_PUBLIC_DIR=/site/public # never re-generate — see compose-site.ts / archiveTranscripts.ts. export ARCHIVES_READONLY=1 -# Persist Next's build cache (.next) into the per-site mount so incremental -# next builds stay warm across runs. A symlink is safe for .next because Next -# reuses — never wholesale-replaces — its cache dir. The export dir was made -# writable in the image so this symlink (and Next's next-env.d.ts) can be created -# as an arbitrary runtime uid. -mkdir -p /site/.next /site/out /site/public +mkdir -p /site/out /site/public + +# .next stays INSIDE the container, and starts empty. It used to be a symlink to +# /site/.next, but turbopack's server runtime imports next's own externals (the +# icon routes' next/dist/compiled/@vercel/og) from each chunk's REAL path — +# under /site, where there is no node_modules — so `next build` died +# prerendering /favicon.ico. Nothing measurable is lost: a Turbopack production +# build keeps no filesystem cache unless experimental. +# turbopackFileSystemCacheForBuild is set (export/next.config.ts does not), and +# carrying .next/cache (the TypeScript .tsbuildinfo, the fetch cache) between +# runs was measured to save nothing (plans/release-13.md, W3b). rm -rf export/.next -ln -s /site/.next export/.next + +# `next build` publishes export/public into out/, so export/public must BE the +# public/ compose writes for this site (EXPORT_PUBLIC_DIR=/site/public). It used +# to be the copy baked into the image, which shipped as the site's data: none at +# all, or whatever site the image's build context had composed last. The image's +# public/ holds only the repo's tracked assets, all .svg +# (Dockerfile.build.dockerignore); those are copied across first, never over a +# file already there, and nothing else is — whatever else an image's public/ +# held would be data, and not this site's. The export dir was made writable in +# the image so this link (and Next's next-env.d.ts) can be created as an +# arbitrary runtime uid. +for f in export/public/*.svg; do + [ -e "$f" ] || continue + [ -e "/site/public/${f##*/}" ] || cp -a "$f" /site/public/ +done +rm -rf export/public +ln -s /site/public export/public echo "[build-site] building site '${SITE_ID}'" # `build site --nodata` = compose:site + next build, WITHOUT the data phase @@ -40,9 +61,25 @@ echo "[build-site] building site '${SITE_ID}'" # compose through the environment. pnpm --filter yt-dlp-transcript-common exec tsx bin/archilyzer.ts build site "${SITE_ID}" --nodata +# Refuse to hand back a bundle that is not this site's own: its site.json and +# corpus.json must both name SITE_ID (common/lib/builtExport.ts, +# builtBundleProblem — the check the deploy phase makes again on the host). On a +# refusal /site/out keeps whatever it held, and the build fails. +BUILT_OUT_DIR="$PWD/export/out" pnpm --filter yt-dlp-transcript-common exec tsx -e ' + import("./lib/builtExport.ts").then(({ builtBundleProblem }) => { + const problem = builtBundleProblem(process.env.BUILT_OUT_DIR, process.env.SITE_ID); + if (problem) { + console.error(`[build-site] REFUSED: ${problem} — /site/out is left as it was`); + process.exit(1); + } + console.log(`[build-site] out/ is the bundle of ${process.env.SITE_ID} (site.json, corpus.json)`); + }); +' + # next build writes export/out as a FRESH real dir (it removes+recreates out, so # a symlink there wouldn't survive) — publish it into the per-site mount. Reached -# only when the build succeeded (set -e aborts otherwise). +# only when the build succeeded and the bundle is the site's (set -e aborts +# otherwise). echo "[build-site] publishing out/ -> /site/out" rm -rf /site/out mkdir -p /site/out