Archilyzer · Source

archilyzer

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

commit 90345b517bbf52739b9b0942ce0e1b4f624dc988
parent 0278e0af2c2dfefde5332afce1cd9ec53772875b
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue,  6 Oct 2026 08:14:01 -0400

common: dirSignature moves to lib/dirSignature.ts, unchanged (compose imports it)

The publish stages' inputSig is computed with the function compose decides
its skips with, so the two can never disagree.

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

Diffstat:
Mcommon/bin/compose-site.ts | 47+----------------------------------------------
Acommon/lib/dirSignature.ts | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+), 46 deletions(-)

diff --git a/common/bin/compose-site.ts b/common/bin/compose-site.ts @@ -23,9 +23,9 @@ import path from "node:path"; import { cp, link, mkdir, rm, readdir, access, readFile, writeFile, stat, rename } from "node:fs/promises"; -import { createHash } from "node:crypto"; import type { Dirent } from "node:fs"; import { getPaths, type Paths } from "../lib/paths"; +import { dirSignature } from "../lib/dirSignature"; import { getSite, resolveSocialLinks, resolveHubUrl, type Site } from "../lib/site"; import { getSettings } from "../lib/settings"; import { @@ -688,51 +688,6 @@ async function writeComposeCache(p: string, cache: ComposeCache): Promise<void> await writeFile(p, JSON.stringify(cache)); } -// A cheap content signature for a directory: relative path + size + mtime of -// every file within, hashed. It reflects exactly what a recursive copy would -// move, so an unchanged source (build:index skipped it incrementally) yields the -// same signature and the copy is skipped. The shared transcript/subs trees hold -// only a bounded handful of paginated page files per channel, so this is far -// cheaper than the copy it guards. Returns "" when the dir is absent/empty. -// -// `ignoreBasename` excludes files by name from the signature. The shared -// per-channel trees carry a manifest.json that build:index rewrites with a fresh -// `generatedAt` for EVERY channel whenever ANY channel mutates (the shared -// page-writer loop runs over all channels). Its pageCount/slugToPage only change -// when the channel's pages change — which the page files already capture — so -// excluding it lets an unchanged channel stay skipped on a partial-change build -// instead of re-copying all 50-odd channels. The tiny stale manifest left behind -// on a skip is structurally identical (only its timestamp differs). -async function dirSignature( - dir: string, - ignoreBasename?: string, -): Promise<string> { - const h = createHash("sha1"); - let any = false; - const walk = async (rel: string): Promise<void> => { - let ents: Dirent[]; - try { - ents = await readdir(path.join(dir, rel), { withFileTypes: true }); - } catch { - return; - } - ents.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); - for (const e of ents) { - if (!e.isDirectory() && e.name === ignoreBasename) continue; - const childRel = rel ? `${rel}/${e.name}` : e.name; - if (e.isDirectory()) { - await walk(childRel); - } else { - any = true; - const s = await stat(path.join(dir, childRel)); - h.update(`${childRel}\t${s.size}\t${s.mtimeMs}\n`); - } - } - }; - await walk(""); - return any ? h.digest("hex") : ""; -} - // Materialize the member subset of a shared per-channel tree into public/, // skipping channels whose source is unchanged since the last compose. Replaces // the previous "rm -rf the whole tree then cp every member" with an in-place diff --git a/common/lib/dirSignature.ts b/common/lib/dirSignature.ts @@ -0,0 +1,54 @@ +// A directory's cheap content signature — moved here, unchanged, from +// compose-site.ts (release 18): the publish stages' `inputSig` is computed +// with the SAME function compose uses to decide what to skip, so "a site is +// fresh" and "compose would skip everything" can never disagree. + +import path from "node:path"; +import { readdir, stat } from "node:fs/promises"; +import { createHash } from "node:crypto"; +import type { Dirent } from "node:fs"; + +// A cheap content signature for a directory: relative path + size + mtime of +// every file within, hashed. It reflects exactly what a recursive copy would +// move, so an unchanged source (build:index skipped it incrementally) yields the +// same signature and the copy is skipped. The shared transcript/subs trees hold +// only a bounded handful of paginated page files per channel, so this is far +// cheaper than the copy it guards. Returns "" when the dir is absent/empty. +// +// `ignoreBasename` excludes files by name from the signature. The shared +// per-channel trees carry a manifest.json that build:index rewrites with a fresh +// `generatedAt` for EVERY channel whenever ANY channel mutates (the shared +// page-writer loop runs over all channels). Its pageCount/slugToPage only change +// when the channel's pages change — which the page files already capture — so +// excluding it lets an unchanged channel stay skipped on a partial-change build +// instead of re-copying all 50-odd channels. The tiny stale manifest left behind +// on a skip is structurally identical (only its timestamp differs). +export async function dirSignature( + dir: string, + ignoreBasename?: string, +): Promise<string> { + const h = createHash("sha1"); + let any = false; + const walk = async (rel: string): Promise<void> => { + let ents: Dirent[]; + try { + ents = await readdir(path.join(dir, rel), { withFileTypes: true }); + } catch { + return; + } + ents.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + for (const e of ents) { + if (!e.isDirectory() && e.name === ignoreBasename) continue; + const childRel = rel ? `${rel}/${e.name}` : e.name; + if (e.isDirectory()) { + await walk(childRel); + } else { + any = true; + const s = await stat(path.join(dir, childRel)); + h.update(`${childRel}\t${s.size}\t${s.mtimeMs}\n`); + } + } + }; + await walk(""); + return any ? h.digest("hex") : ""; +}