Archilyzer · Source

archilyzer

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

commit 80c2474810edca4672c4a2a7cbc57bd865fd558d
parent 34a8e5ed6ab309510dc0f48c5ef2d41b6730232a
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 13:43:46 -0400

plans: release 13 slice W3, follow-up W3c recorded — every site deploy checks its bundle, the assets stay in sync; one [Unreleased] bullet

The review's S1 (runDeployIntoLog refuses a bundle that is not the site's own,
builtSiteProblem agrees with builtBundleProblem), L1 (sync_public_assets), L2
(the svg-only test), N2 (PUBLISH.md); L3 and N1 found and left; the two review
questions answered; gates (tsc, common 2,129) and the bites.

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

Diffstat:
Meditor/CHANGELOG.md | 1+
Mplans/release-13.md | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 119 insertions(+), 0 deletions(-)

diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md @@ -5,6 +5,7 @@ - **`archilyzer doctor` checks the image Build all builds sites in.** When a container engine answers, a new **build image** section says whether the image named under **Settings → Build pipeline** is there, when it was built and how big it is. It warns when the image is missing, or older than the last change to its Dockerfile, and prints the one command that rebuilds it. Build all still builds or refreshes the image itself before it builds any site; the warning tells you ahead of time that the next Build all will spend that time. With no container engine the check is skipped in one line, and with no corpus it is only a note. It never fails the doctor. - **The site build image runs Node 22 and pnpm 11**, the versions the rest of the workspace runs on, instead of Node 20 and pnpm 9, which did not read the workspace's install rules. The next Build all rebuilds the image from its first step, reinstalling every dependency, before it builds any site. - **Build all sites works in containers again.** Every site's container build had been failing while it prerendered `/favicon.ico`. Each site now builds from the data composed for it, never from files baked into the build image. A bundle whose `site.json` and `corpus.json` do not both name its site is refused before it is handed back or deployed. The image carries no corpus data, and its build context is about 7 MB from any checkout. +- **A site deploy refuses a bundle that is not the site's own, and says why.** A site's **Build & deploy**, `pnpm ops build-deploy`, and Build & deploy all on a machine without containers used to ship whatever `export/out` held when the deploy began. If another site's build or the hub's had replaced it meanwhile, that is what shipped. Every site deploy now checks, just before handing the bundle to Cloudflare Pages, that its `site.json` and `corpus.json` both name the site. If they do not, it stops with `[deploy] REFUSED —` and what it found, and nothing is sent. Deploying a build that has a `site.json` but no matching `corpus.json` is refused before the job starts, as an incomplete build. ## [0.10.0] - 2026-09-28 - **The homepage can be built and deployed from `/sites`.** Under a new **Homepage** section, after Hub, there is **Build homepage** (tick **Deploy after build** to ship it in the same job, only if the build succeeds) and **Deploy homepage**, which ships the build already in `homepage/out`. A **Preview branch** box beside them sends either deploy to a Cloudflare Pages preview of the `archilyzer` project instead of production, and shows the preview's address as you type; a name Cloudflare would refuse or rewrite, or `main`, greys the deploy buttons out and says why. A line under the buttons says what a deploy would ship: when `homepage/out` was built (or that it holds no build yet), and where it goes, with the live URL. Deploy homepage with nothing built is refused before any job starts. The homepage reads the search index as it stands, so run **Build index** first when its numbers should move. The jobs run the same code as `archilyzer build homepage` / `deploy homepage`, and show on `/jobs` as `build-homepage`, `deploy-homepage` and `build-deploy-homepage`. The Hub section no longer describes the homepage. diff --git a/plans/release-13.md b/plans/release-13.md @@ -404,4 +404,122 @@ same 807 files from the primary. - **W3's "Found and left" 1 and 2 are fixed here.** Item 3 (the root `Dockerfile` and `Dockerfile.test` on node 20 and pnpm 9.15.4) stands, and so does item 4. +#### Follow-up W3c — every site deploy checks its bundle; the assets stay in sync (2026-09-28) + +**The review** (`w3-review.md`) is **SHIP**, with five findings: +- **S1:** a should-fix, release-level and pre-existing. It is now this slice's. +- **L1** and **L2:** fixed here. +- **L3** and **N1:** recorded below as found and left. +- **N2:** a nit, fixed in the doc. + +`main` was not merged again, as instructed. There was no docker build, e2e run or `next build`: +the primary was running a live six-site build and deploy. + +**S1: every SITE deploy checks its bundle right before wrangler** (`common/publish/build.ts`, +`runDeployIntoLog`). +- **The gap.** Two host paths shipped `export/out` with no identity check between their build and + wrangler: + - `buildAndDeployAction`: the Publish tab's Build & deploy, and `pnpm ops build-deploy`; + - `basicBuildAndDeployAll`: Build & deploy all with no engine. + The deploy queue runs beside the build queue. A build of another site, or of the hub, that + rewrote `export/out` during the R2 upload would therefore have shipped to this site's Pages + project. +- **The fix.** + - `runDeployIntoLog` now calls `builtBundleProblem(outDir, site.siteId)` first. Nothing runs + between the check and the spawn. + - A refusal logs `[deploy] REFUSED — <why>. Nothing was sent to Cloudflare Pages; build <id> + again, then deploy.` and returns 1. + - Every caller already turns 1 into a failed deploy: + - Phase C: `failed`; + - `deploySite` and `buildAndDeployAction`: they throw `Deploy failed (exit 1).`; + - `basicBuildAndDeployAll`: `deploy FAILED — exit 1`. + - The hub and the homepage deploy through `runPagesDeployIntoLog`, so they are unaffected. +- **Every caller still passes a real site bundle.** + - Phase C passes the per-site `out/`, already checked first by W3b. + - `deploySite`, `buildAndDeployAction` and `basicBuildAndDeployAll` pass `export/out`, which + after a site build always holds both files: compose writes `corpus.json` whenever it writes + `site.json`, from the same descriptor. +- **The two checks now agree.** `builtSiteProblem` is the fast answer that `deploySite` and the + editor's `deployAction.ts` give before any job. It now also refuses a `site.json` naming the site + whose `corpus.json` does not, as `export/out holds an incomplete build of "<id>" (its corpus.json + does not name it) — build <id> first`. + - So it refuses exactly what `builtBundleProblem` refuses. A new test walks seven bundle shapes + through both: good, nothing, another site, no corpus, torn, unnamed corpus, hub. + - The existing e2e regex (`ops-api.spec.ts:1022`) still holds: the fixture site is never in + `export/out`, so the new sentence cannot arise there. +- **The R2 upload still runs before the check** in the two editor paths (`editor/app` is not + mine). The upload sends this site's own staged archives, from the per-site `.r2-staging/<id>` + and not from `export/out`, so it cannot carry another site's data. A refusal after it leaves + what a wrangler failure leaves today: R2 has the newer archives, and Pages is unchanged. +- **The test** (`build.test.ts`) cannot reach a real wrangler. + - Its `PATH` holds only a fake `pnpm` that records its argv, and the test proves the fake + answers the same `runChildIntoLog` spawn before anything deploys. + - Four refusals are checked: another site, the hub's shape, torn, no corpus. Each returns 1 with + one log line, and the fake is never spawned. + - A good bundle returns 0 with the argv unchanged (`dlx wrangler pages deploy <out> + --project-name …`) and the `[deployed]` line. + +**L1: the tracked assets are synced every run** (`docker/build-site.sh`, `sync_public_assets`). +- Each tracked `.svg` is copied over whatever copy is there, so a changed asset ships. +- A name the previous run copied that the repo no longer has is removed, so a dropped icon stops + shipping. The Ko-fi mark was such an icon. +- The copied names are listed in `/site/.tracked-public-assets`, outside `public/`, so the list + never ships. Only a listed name, and only as a regular file, is ever removed. So nothing compose + wrote, and no svg the image did not put there, is touched. +- The sync runs before compose, so compose would win over any shared name anyway. +- **Review question 1:** no case exists where a file already in `/site/public` must win over the + image's svg. compose writes no top-level `.svg`: its files are `_headers`, `site.json`, + `corpus.json`, `llms.txt`, `robots.txt`, `sitemap.xml`, `sw.js`, the JSON data files and the + data dirs. + +**L2:** a test that every file `git ls-files export/public` lists is a top-level `.svg` +(`common/publish/buildImage.test.ts`, the image contract's own file). +- Its failure names both `Dockerfile.build.dockerignore` and `build-site.sh` + (`sync_public_assets`). +- It skips when the tree is not a git checkout (`Dockerfile.test`'s context has no `.git`). +- L1's test lives beside it: the function is read out of `build-site.sh` and run with bash over + temp dirs. + +**N2** (`PUBLISH.md`): +- The doctor sentence gets its own paragraph break. +- BuildKit reads the per-Dockerfile ignore file. A builder that reads only the shared one sends + several GB and stays safe: `out/` is built from the composed data, and only the svgs are copied. +- **Review question 2:** podman was not checked. There is no podman or buildah on this machine, and + the doc now says it is unverified. + +| sha | what | +|---|---| +| `526e9af0` | `docker:` `sync_public_assets` in `build-site.sh`; `common/publish/buildImage.test.ts` (L1 sync + L2 svg-only, 2 tests) | +| `85929901` | `common:` `runDeployIntoLog` refuses first; `builtSiteProblem` agrees with `builtBundleProblem`; 1 + 1 tests, and the good-bundle test gains its `corpus.json` | +| `c52e116c` | `docs:` `PUBLISH.md` (N2) | +| _this_ | `plans:` this follow-up; one `[Unreleased]` bullet (a refused deploy names why) | + +**Gates.** +- **tsc:** clean before the code commits (`w3c-tsc-2.log`, 39 s, the final tree). +- **common:** **2,129/2,129** (`w3c-common.log`, 66 s): 2,125 + 4 (`buildImage` 2, `build.test` 1, `builtExport` 1). +- **`test:scripts`:** not run. No script was touched. +- **They bite** (`w3c-bite.log`), against `31a74988`, before W3c: + - `build.test.ts` + `builtExport.test.ts`: **22 passed, 2 failed**. + - The two are `runDeployIntoLog refuses…`: on "another site's bundle" the old code returned 0, + having spawned the fake `pnpm`. + - And `builtSiteProblem refuses exactly what builtBundleProblem refuses`. + - `buildImage.test.ts`'s sync test: + - against the old script it fails at "defines sync_public_assets()"; + - with the old no-clobber copy wrapped as the function, it fails at "a changed asset is + shipped" (`v1` where `v2` was expected). + - The svg-only test: with an intent-to-add `export/public/brand.png`, it fails with its message + naming both files. The file was then un-staged and deleted. + +**Found and left.** +- **L3: the doctor's stale rule can stick.** A fully cached `docker build` keeps the image's old + `Created`, so after a comment-only commit to `Dockerfile.build`, with nothing in common/export + changed, the WARN persists. Its "rebuild it now" command cannot clear it; only `--no-cache` or a + context change can. This is rare, since nearly every commit touches common/export. If it ever + matters, bake a `--label` with the Dockerfile's hash in `buildImageArgs` and compare that. +- **N1: the doctor's image block loads the AWS SDK.** It does + `await import("../publish/build")` for `dockerBin` and `buildImageArgs`. A small + `publish/buildImage.ts`, re-exported by `build.ts`, would keep the doctor light. +- **The two editor host paths upload to R2 before the check** (above). Moving a check ahead of the + upload there is an `editor/app` change. + ## Rollout