77 * checkout present. That only stays honest if something notices when the source
88 * moves, so CI regenerates them here and fails on any difference.
99 *
10+ * **What "difference" means: the data, not the provenance.** The two files record
11+ * which mcpp they came from (`sourceVersion`, `sourceCommit`, and doc links built
12+ * from the commit). Every mcpp commit moves those, so comparing them literally
13+ * made this gate red for reasons no one in this repository caused — and a gate
14+ * that is always red is not a gate. `canonical()` below blanks that provenance and
15+ * compares the rest, so a red run means the API or the schema really moved.
16+ *
1017 * Without a checkout (`MCPP_REPO`, else `../mcpp`) this **skips with a notice**
1118 * rather than passing silently: a green run that checked nothing is worse than a
1219 * yellow one.
@@ -29,6 +36,35 @@ const targets = [
2936 { name : "mcpp.toml schema" , script : "tools/generate-toml-schema.mjs" , file : "data/toml-schema.json" } ,
3037] ;
3138
39+ /**
40+ * One JSON document with its **provenance** replaced by placeholders.
41+ *
42+ * `sourceVersion` and `sourceCommit` are facts about the checkout the snapshot was
43+ * generated from, and any string containing the commit (the documentation links)
44+ * is derived from it. Two clones of the same mcpp agree on everything else.
45+ */
46+ function canonical ( text ) {
47+ const value = JSON . parse ( text ) ;
48+ const commit = typeof value . sourceCommit === "string" ? value . sourceCommit : "" ;
49+ const scrub = ( node ) => {
50+ if ( typeof node === "string" ) {
51+ return commit . length === 0 ? node : node . split ( commit ) . join ( "<commit>" ) ;
52+ }
53+ if ( Array . isArray ( node ) ) {
54+ return node . map ( scrub ) ;
55+ }
56+ if ( node !== null && typeof node === "object" ) {
57+ const out = { } ;
58+ for ( const [ key , child ] of Object . entries ( node ) ) {
59+ out [ key ] = key === "sourceVersion" || key === "sourceCommit" ? "<provenance>" : scrub ( child ) ;
60+ }
61+ return out ;
62+ }
63+ return node ;
64+ } ;
65+ return JSON . stringify ( scrub ( value ) ) ;
66+ }
67+
3268let drifted = 0 ;
3369for ( const target of targets ) {
3470 const before = fs . readFileSync ( path . join ( root , target . file ) , "utf8" ) ;
@@ -41,8 +77,11 @@ for (const target of targets) {
4177 continue ;
4278 }
4379 const after = fs . readFileSync ( path . join ( root , target . file ) , "utf8" ) ;
44- if ( before !== after ) {
45- console . error ( `error: ${ target . file } is out of date — run \`npm run gen:…\` and commit the result` ) ;
80+ if ( canonical ( before ) !== canonical ( after ) ) {
81+ console . error (
82+ `error: ${ target . file } is out of date — the mcpp it was generated from now answers differently; ` +
83+ `run \`npm run gen:…\` and commit the result` ,
84+ ) ;
4685 drifted += 1 ;
4786 } else {
4887 console . log ( `check-generators: ${ target . name } is current` ) ;
0 commit comments