|
20 | 20 | import fs from 'fs'; |
21 | 21 | import path from 'path'; |
22 | 22 |
|
| 23 | +// One staleness rule, shared with the merge driver's pre-commit half and with |
| 24 | +// `check:generated`'s `--fix` refusal — a second copy of "is this artifact older |
| 25 | +// than src" would drift, and the direction it drifts in is the one that renders |
| 26 | +// a confident page from a tree nobody rebuilt (#4675, #4723). |
| 27 | +import { schemaTreeIsStale } from '../../../scripts/check-regen-pending.mjs'; |
| 28 | + |
23 | 29 | import { |
24 | 30 | evaluateBaseline, |
25 | 31 | loadEntrySurfaces, |
@@ -57,6 +63,50 @@ const IMPORT_BASELINE_PATH = path.resolve(__dirname, '../docs-import-surface.bas |
57 | 63 | const CHECK = process.argv.includes('--check'); |
58 | 64 | const UPDATE_IMPORT_BASELINE = process.argv.includes('--update-import-baseline'); |
59 | 65 |
|
| 66 | +// ── The input tree is a BUILD ARTIFACT, and it must be current (#4723) ──────── |
| 67 | +// |
| 68 | +// Every mode below renders from `SCHEMA_DIR` — `packages/spec/json-schema/`, |
| 69 | +// which is gitignored, so no checkout carries it and nothing in git can tell you |
| 70 | +// how old it is. |
| 71 | +// |
| 72 | +// Until #4723 the question could not come up: `check:docs` was |
| 73 | +// `pnpm gen:schema && tsx scripts/build-docs.ts --check`, so the tree was rebuilt |
| 74 | +// on every run. That first step is what made a script called `check:` WRITE two |
| 75 | +// TRACKED files — `json-schema.manifest.json` and `authorable-surface.json` are |
| 76 | +// projections `gen:schema` repairs whenever they are behind — so running the gate |
| 77 | +// silently edited the tree of whoever ran it and left the staleness unreported. |
| 78 | +// #4711 removed exactly that from `--check`; this was the same defect at a |
| 79 | +// different entry, and the fix is the same shape: the check checks, and the |
| 80 | +// CALLER generates (lint.yml's `check:authorable-surface` step, `check:generated`'s |
| 81 | +// declared gate order, `pnpm build`, `apps/docs`' build). |
| 82 | +// |
| 83 | +// What the old first step also provided, silently, was FRESHNESS. Dropping it |
| 84 | +// without asserting freshness would trade a tracked-file write for something |
| 85 | +// worse: a green `check:docs` computed against a tree that predates the edit |
| 86 | +// under test — a false green on precisely the change (`.describe()` added, a key |
| 87 | +// renamed) this gate exists to catch. So the prerequisite is stated, in every |
| 88 | +// mode, and it is fatal rather than a warning: `gen:docs` on a stale tree does |
| 89 | +// not fail, it WRITES stale pages, which is the `readsDist` trap one artifact |
| 90 | +// over (AGENTS.md records what that one cost). |
| 91 | +if (schemaTreeIsStale(path.resolve(__dirname, '..'))) { |
| 92 | + const missing = !fs.existsSync(SCHEMA_DIR); |
| 93 | + console.error( |
| 94 | + `\n❌ ${path.relative(REPO_ROOT, SCHEMA_DIR)} is ${missing ? 'missing' : 'older than packages/spec/src'}.\n\n` + |
| 95 | + ` The reference docs are rendered from that tree, and it is a gitignored build\n` + |
| 96 | + ` artifact — nothing in a checkout carries it, and a merge never brings it along.\n` + |
| 97 | + ` Rendering ${CHECK ? 'a verdict' : 'pages'} from a stale tree would ${ |
| 98 | + CHECK ? 'report the docs in sync with sources this run never read' : 'WRITE pages describing sources this run never read' |
| 99 | + }.\n\n` + |
| 100 | + ` Generate it first:\n\n` + |
| 101 | + ` pnpm --filter @objectstack/spec gen:schema\n\n` + |
| 102 | + ` (\`pnpm --filter @objectstack/spec build\` does this as its first step, and so does\n` + |
| 103 | + ` \`check:authorable-surface\`, which runs before this gate in CI and in check:generated.\n` + |
| 104 | + ` This script no longer runs it for you: a check that regenerates is a check that\n` + |
| 105 | + ` repairs the two tracked projections instead of reporting them — #4711, #4723.)`, |
| 106 | + ); |
| 107 | + process.exit(1); |
| 108 | +} |
| 109 | + |
60 | 110 | // ── Output sink ────────────────────────────────────────────────────────────── |
61 | 111 | // Shared with the spec's other generators — see lib/generated-output.ts for why |
62 | 112 | // the write and --check paths must be the same code. |
@@ -661,7 +711,12 @@ Object.keys(CATEGORIES).forEach(category => { |
661 | 711 | && fs.readdirSync(schemaDir).some(f => f.endsWith('.json')); |
662 | 712 | if (!hasSchemas) { |
663 | 713 | if (fs.existsSync(dir)) { |
664 | | - console.warn(`⚠ Skipping clean of ${category}/ — no JSON schemas found in ${schemaDir}. Run \`pnpm gen:schema\` first.`); |
| 714 | + // NOT "run gen:schema first" any more: the freshness guard at the top of |
| 715 | + // this file has already proved the tree is newer than src, so this is the |
| 716 | + // steady state for a category whose schemas are all unrepresentable in JSON |
| 717 | + // Schema (`contracts/` is the standing example) — the old line sent readers |
| 718 | + // after a regeneration that would change nothing (#4723). |
| 719 | + console.warn(`⚠ Skipping clean of ${category}/ — this build published no JSON Schema under ${schemaDir}; leaving its pages as they are.`); |
665 | 720 | } |
666 | 721 | return; |
667 | 722 | } |
@@ -938,13 +993,19 @@ flush({ |
938 | 993 | regenerate: |
939 | 994 | ' pnpm --filter @objectstack/spec gen:schema && pnpm --filter @objectstack/spec gen:docs\n' + |
940 | 995 | ' git add content/docs/references', |
941 | | - // json-schema/ is gitignored, so a fresh checkout that forgot gen:schema has no |
942 | | - // input at all: every category is skipped, nothing is managed, and "nothing |
943 | | - // differs" would read as success — green while checking no pages. Fail loudly. |
| 996 | + // Backstop to the freshness guard at the top of this file. That one catches the |
| 997 | + // common shape — an absent or stale tree — before a single page is rendered. |
| 998 | + // This one catches what mtimes cannot see: a tree that is NEWER than src and |
| 999 | + // still has no category with schemas in it (a truncated or half-written |
| 1000 | + // generation). Either way "nothing differs" must never read as success — green |
| 1001 | + // while checking no pages is the silent shape this whole file guards against. |
| 1002 | + // `check:docs` no longer regenerates for you, deliberately: that first step is |
| 1003 | + // what made a check repair two tracked projections (#4711, #4723). |
944 | 1004 | guard: () => |
945 | 1005 | managedCount === 0 |
946 | 1006 | ? `No JSON schemas found under ${path.relative(REPO_ROOT, SCHEMA_DIR)} — nothing to check against.\n` + |
947 | | - ' Run `pnpm --filter @objectstack/spec gen:schema` first (`check:docs` does this for you).' |
| 1007 | + ' The tree is newer than packages/spec/src but published no category, which means a\n' + |
| 1008 | + ' partial generation. Run `pnpm --filter @objectstack/spec gen:schema` again.' |
948 | 1009 | : null, |
949 | 1010 | }); |
950 | 1011 |
|
|
0 commit comments