commit 65f6e7d3d424374479cacb33a045be9c5fb8c027
parent 4456b4352441ec37d6cf4468c9fd76f0dbf414ea
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 14:34:31 -0400
common: the deploy check keys on the source's artefacts and the /source page, not on out/source existing
A --no-source build still renders the /source PAGE into out/source/index.html,
so "out/source exists" refused exactly the build that must deploy as before
(found by running `build homepage --no-source` and then the check). Now: no
/source page at all means the build's source step refused (buildHomepage took
out/source away) or the build predates the page — refuse, rebuild; the page
with no manifest, mirror, tree or tarball beside it is a --no-source build —
deploy; any artefact — validate as before. source.spec's comment says three
data tests, which is what E2E_EXPECT_SOURCE=1 failed in the empty state.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
4 files changed, 31 insertions(+), 7 deletions(-)
diff --git a/common/publish/build.test.ts b/common/publish/build.test.ts
@@ -232,6 +232,7 @@ test("deployHomepage refuses an out/ whose source has no record of the rules it
const out = path.join(root, "homepage", "out");
mkdirSync(path.join(out, "source"), { recursive: true });
writeFileSync(path.join(out, "index.html"), "<p>home</p>");
+ writeFileSync(path.join(out, "source", "index.html"), "<p>the /source page</p>");
writeFileSync(
path.join(out, "source", "manifest.json"),
JSON.stringify({
@@ -246,9 +247,14 @@ test("deployHomepage refuses an out/ whose source has no record of the rules it
deployHomepage({ paths: p, previewBranch: "r12-source" }),
/homepage\/out's source has no record of the rules it was audited under — run `archilyzer build homepage`/,
);
- // A half-removed source (no manifest) refuses too.
+ // A half-removed source (no manifest) refuses too…
rmSync(path.join(out, "source", "manifest.json"));
+ mkdirSync(path.join(out, "source", "archilyzer.git"));
await assert.rejects(deployHomepage({ paths: p }), /without a valid manifest — run `archilyzer build homepage`/);
+ // …and so does a build whose source step refused (buildHomepage took
+ // out/source away, the page with it).
+ rmSync(path.join(out, "source"), { recursive: true });
+ await assert.rejects(deployHomepage({ paths: p }), /has no \/source page \(its source step refused/);
} finally {
rmSync(root, { recursive: true, force: true });
}
diff --git a/common/publish/source.test.ts b/common/publish/source.test.ts
@@ -403,10 +403,14 @@ test("round trip: --check writes nothing; publish; a dumb clone of the mirror is
// The deploy check (M1): a build's out/ that is this publish deploys…
const out = path.join(dir("out"), "out");
- cpSync(path.join(pub, "source"), path.join(out, "source"), { recursive: true });
- cpSync(path.join(pub, "downloads"), path.join(out, "downloads"), { recursive: true });
const check = { ...files, homeDir: HOME, publicDir: pub, sourceRepo: path.join(repo, ".git") };
const checkPaths = o.paths!;
+ // A build's out/ always has the /source page; a --no-source one, nothing more.
+ mkdirSync(path.join(out, "source"), { recursive: true });
+ writeFileSync(path.join(out, "source", "index.html"), "<p>No source published in this build.</p>");
+ assert.equal(await publishedSourceProblem(checkPaths, out, check), null, "--no-source deploys as before");
+ cpSync(path.join(pub, "source"), path.join(out, "source"), { recursive: true });
+ cpSync(path.join(pub, "downloads"), path.join(out, "downloads"), { recursive: true });
assert.equal(await publishedSourceProblem(checkPaths, out, check), null);
// …a tampered tarball does not…
writeFileSync(path.join(out, "downloads", path.basename(TARBALL_HREF)), "other bytes");
diff --git a/common/publish/source.ts b/common/publish/source.ts
@@ -887,8 +887,9 @@ export async function clearPublishedSource(
* was audited under the rules of its day. It deploys only when the last
* publish (the skip key beside public/) was made under TODAY's rules and step
* (`rulesHash`), of TODAY's `main`, and is the one in `out/` (its mirror head,
- * its tarball). An `out/` with no source at all — a `--no-source` build —
- * deploys as it always has.
+ * its tarball). An `out/` whose /source page shows the empty state — a
+ * `--no-source` build — deploys as it always has; one with no /source page at
+ * all (a refused build) does not.
*/
export async function publishedSourceProblem(
paths: Paths,
@@ -904,8 +905,21 @@ export async function publishedSourceProblem(
): Promise<string | null> {
const outSource = path.join(outDir, "source");
const outTarball = path.join(outDir, "downloads", TARBALL_NAME);
- if (!existsSync(outSource) && !existsSync(outTarball)) return null;
const rebuild = "run `archilyzer build homepage` (it re-audits), then deploy";
+ // The /source/ PAGE is always in a finished build (it renders its empty
+ // state without a publish). Without it, the build's source step refused —
+ // it took out/source away — or the build predates the page.
+ if (!existsSync(path.join(outSource, "index.html"))) {
+ return `homepage/out has no /source page (its source step refused, or the build predates it) — ${rebuild}`;
+ }
+ const artefacts = [
+ path.join(outSource, "manifest.json"),
+ path.join(outSource, MIRROR_DIR),
+ path.join(outSource, "tree"),
+ outTarball,
+ ];
+ // A `--no-source` build: the page's empty state and nothing else.
+ if (!artefacts.some((a) => existsSync(a))) return null;
const manifest = parseSourceManifest(await readJson(path.join(outSource, "manifest.json")));
if (!manifest) return `homepage/out holds a source publish without a valid manifest — ${rebuild}`;
const state = (await readJson(statePath(sourcePublicDir(paths, opts.publicDir)))) as Partial<PublishState> | null;
diff --git a/homepage/e2e/source.spec.ts b/homepage/e2e/source.spec.ts
@@ -13,7 +13,7 @@ import { test, expect, type Page } from "@playwright/test";
// the preview deploy's checks (and to app/lib/headers.test.ts).
//
// E2E_EXPECT_SOURCE=1 (declared in playwright.config.ts) makes the empty state
-// a FAILURE of the four data tests, so a gate that published first cannot
+// a FAILURE of the three data tests, so a gate that published first cannot
// pass with a publish that silently produced nothing, or a loader that always
// says "empty".
const EXPECT_SOURCE = process.env.E2E_EXPECT_SOURCE === "1";