Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -348,6 +348,29 @@ jobs:
- name: Docs image tags track packages/cli's version
run: pnpm check:docs-image-tag

# #9064 — the self-test of the VERSION-TIME REWRITER that keeps the gate
# above from ever having to fire. The gate alone could not: `changeset
# version` bumps packages/cli on a release PR that (per
# sync-template-versions.mjs's own header) gets NO CI because
# changesets/action opens it with the default GITHUB_TOKEN — so the bump
# merged green and the gate reddened on the NEXT ordinary PR, naming files
# that author never touched. scripts/sync-docs-image-tags.mjs runs in the
# root `version` script, alongside the two sync scripts already there.
#
# Only the --self-test runs here, and that is the whole point: the rewriter
# itself has nothing to do on a green corpus, so CI can never observe it
# working. The self-test is where a STALE fixture is observed going green
# through the gate's own checkSurfaces, and — the control that matters just
# as much — where a CLEAN fixture is observed left byte-identical and
# unwritten, because an over-eager rewriter would silently corrupt the
# documented tag scheme and historical version prose across three files.
#
# Same job as the gate deliberately: they share one SURFACES/PATTERNS list,
# so the change that breaks one breaks the other, and both reds should land
# in the same place on the same PR.
- name: Docs image-tag version-time rewriter self-test
run: pnpm check:docs-image-tag-sync

# #4851: the docs-accuracy-audit workflow carries its default scope inline
# (a workflow script runs in a vm with no filesystem, so it cannot enumerate
# content/docs/ itself). Hand-kept, that list rotted in BOTH directions —
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,7 @@
"setup": "pnpm install && pnpm --filter @objectstack/spec build",
"prepare": "node scripts/setup-git-hooks.mjs",
"check:merge-driver": "node scripts/git-merge-regen.mjs --self-test && node scripts/check-regen-pending.mjs --self-test",
"version": "changeset version && node scripts/sync-protocol-version.mjs && node scripts/sync-template-versions.mjs",
"version": "changeset version && node scripts/sync-protocol-version.mjs && node scripts/sync-template-versions.mjs && node scripts/sync-docs-image-tags.mjs",
"release": "pnpm run build && bash scripts/build-console.sh && bash scripts/release-publish.sh",
"docs:dev": "pnpm --filter @objectstack/docs dev",
"docs:build": "pnpm --filter @objectstack/docs build",
Expand All@@ -40,6 +40,7 @@
"check:docs-audit-scope": "node scripts/docs-audit/affected-docs.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs",
"check:docs-redirects": "node scripts/check-docs-redirects.mjs --self-test && node scripts/check-docs-redirects.mjs",
"check:docs-image-tag": "node scripts/check-docs-image-tag.mjs --self-test && node scripts/check-docs-image-tag.mjs",
"check:docs-image-tag-sync": "node scripts/sync-docs-image-tags.mjs --self-test",
"check:role-word": "node scripts/check-role-word.mjs --self-test && node scripts/check-role-word.mjs",
"check:quick-reference-counts": "node scripts/check-quick-reference-counts.mjs --self-test && node scripts/check-quick-reference-counts.mjs",
"check:skill-frame-sync": "node scripts/check-skill-frame-sync.mjs --self-test && node scripts/check-skill-frame-sync.mjs",
Expand Down
34 changes: 27 additions & 7 deletions scripts/check-docs-image-tag.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,11 +115,18 @@

import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

/** The package whose `version` every concrete pin below must equal. */
const VERSION_SOURCE = 'packages/cli/package.json';
/**
* The package whose `version` every concrete pin below must equal.
*
* Exported for the same reason SURFACES and PATTERNS are (#9064): the version-time
* rewriter `sync-docs-image-tags.mjs` must resolve the SAME source of truth this gate
* compares against. A second literal in the rewriter would be a second contract, and
* the two drifting apart reproduces one layer up the very defect this gate exists for.
*/
export const VERSION_SOURCE = 'packages/cli/package.json';

/**
* The doc surfaces scanned, enumerated explicitly (#9018).
Expand DownExpand Up@@ -718,8 +725,21 @@ function main() {
process.exit(report(findings, stats, expected));
}

if (process.argv.includes('--self-test')) {
await selfTest();
} else {
main();
// Entry-point guard (#9064). Without it, importing this module RUNS the check and
// calls `process.exit()` as an import side effect -- measured: a probe importing
// SURFACES never reached its own next line, because `main()` had already exited the
// process for it. That makes the exports unusable by the one consumer they were added
// for, and the failure is silent in the worst way: the exit code is the CORPUS's
// verdict, so an importer looks fine while the corpus is green and dies with an
// unrelated exit 1 the day a pin goes stale. The idiom is the repo's own, and the
// sibling gates state the same rationale (check-adr-links, check-doc-anchors,
// check-kernel-hook-pairs). Nothing about what this gate ASSERTS changes: both
// `check:docs-image-tag` invocations run this file directly, where argv[1] is this
// file and the branch is taken exactly as before.
if (resolve(process.argv[1] ?? '') === resolve(fileURLToPath(import.meta.url))) {
if (process.argv.includes('--self-test')) {
await selfTest();
} else {
main();
}
}
Loading
Loading