Files
stanza/scripts/check-module-versions.ts
T
jake 6ada5c0a88 fix: correct batch of CLI correctness bugs found in critical-bug sweep
- `stanza init --dry-run` no longer creates the project directory, monorepo shell, or `stanza.json`; the dry-run manifest is built with `emptyManifest` and package.json existence checks are skipped throughout `applyModule`
- Direct-fs codemods (`append-to-file`, `add-package-dep`, `set-tsconfig-paths`) now claim their region before writing so a failed `add` rolls back to true pre-apply bytes and `RegionConflictError` fires before any disk change
- `stanza remove` reads the `consumesPackages` snapshot persisted on each installed record (falling back to a live registry fetch only for legacy records), so package-dir protection holds offline and after upstream renames
- `stanza remove` only applies the legacy bare-id owner fallback when no sibling install of the same module remains, preventing one app's removal from sweeping another app's files on pre-composite-owner manifests
- `stanza doctor` no longer reports false drift for package-home modules installed with an `--app` restriction; `expectedConsumerApps` now intersects targeted apps from the manifest records
- `wrap-root-layout` resolves the framework per dispatched app, fixing multi-app projects with differing frameworks
- The codemod render context now carries `packageManager` so `{{pm}}`/`{{run …}}` templates render bun/npm/pnpm correctly instead of always defaulting to pnpm
2026-06-09 19:04:38 -04:00

79 lines
2.9 KiB
TypeScript

/**
* PR guard (tokenless, read-only). Protects the immutability of the registry's
* per-module version pins: for every changed `registry/modules/<slug>/**`,
* compile that module and compare it against the already-published
* `<base>/<slug>@<version>.json`. If the content changed but the version didn't,
* fail — the author must bump the module's `package.json` version (a published
* pin is immutable). A 404 (new version or new module) passes.
*
* Run via `jiti scripts/check-module-versions.ts`.
*/
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { REGISTRY_BASE_URL } from "@withstanza/schema";
import { compileRegistry } from "./compile-registry.ts";
const repoRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
const registryBase = process.env.STANZA_REGISTRY_BASE ?? REGISTRY_BASE_URL;
const git = (args: string[]) =>
execFileSync("git", args, { cwd: repoRoot, encoding: "utf8" }).trim();
const base = process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : "origin/main";
let range: string;
try {
range = git(["merge-base", base, "HEAD"]);
} catch {
range = "HEAD~1"; // local fallback
}
const changedDirs = new Set<string>();
for (const f of git(["diff", "--name-only", range, "HEAD"]).split("\n")) {
const m = /^registry\/modules\/([^/]+)\//.exec(f);
if (m) changedDirs.add(m[1]!);
}
if (changedDirs.size === 0) {
console.log("Module version guard passed (no module changes).");
process.exit(0);
}
const compiledDir = fs.mkdtempSync(path.join(os.tmpdir(), "stanza-check-"));
const { modules } = await compileRegistry({ outDir: compiledDir });
// Correlate source dirs with compiled output through the compiler's own
// mapping — inferring `<dir>.json` would silently skip (and so bypass the
// guard for) any module whose dir name doesn't match its `<category>-<id>`.
const byDir = new Map(modules.map((m) => [m.dir, m]));
const errors: string[] = [];
for (const dir of changedDirs) {
const entry = byDir.get(dir);
if (!entry) continue; // module deleted — nothing to pin
const { slug, version } = entry;
const text = fs.readFileSync(path.join(compiledDir, `${slug}.json`), "utf8");
const res = await fetch(`${registryBase}/${slug}@${version}.json`);
if (res.status === 404) continue; // new version or new module
if (!res.ok) {
errors.push(`Could not verify "${slug}" (HTTP ${res.status}).`);
continue;
}
if ((await res.text()) !== text) {
errors.push(
`Module "${slug}" changed but version ${version} is already published with different ` +
`content. Bump "version" in registry/modules/${dir}/package.json.`,
);
}
}
if (errors.length > 0) {
console.error("Module version guard failed:");
for (const e of errors) console.error(` • ${e}`);
process.exit(1);
}
console.log("Module version guard passed.");