11/**
2- * Verify the vendored sheet configs in `apps/api/gitsheets/` match the data
3- * repo's `empty` branch, the source of truth for `.gitsheets/*.toml`
2+ * Verify the vendored sheet configs and schema contracts in
3+ * `apps/api/gitsheets/` match the data repo's `empty` branch, the source of
4+ * truth for everything under `.gitsheets/`
45 * (specs/behaviors/storage.md → "Why these path shapes").
56 *
67 * Tests seed every test data repo from the vendored copy, so drift between
1112 * Usage:
1213 * npm run -w apps/api check:sheet-configs [-- --remote=<url>] [-- --ref=<branch>]
1314 *
14- * Exits 1 and prints each differing, missing, or extra config on drift.
15+ * Exits 1 when a vendored config differs from upstream or is missing there.
16+ * A config only upstream (a sheet the app doesn't open yet) is a warning.
1517 */
1618import { execFile } from 'node:child_process' ;
1719import { mkdtemp , readFile , readdir , rm } from 'node:fs/promises' ;
1820import { tmpdir } from 'node:os' ;
19- import { dirname , join } from 'node:path' ;
21+ import { dirname , join , relative } from 'node:path' ;
2022import { fileURLToPath } from 'node:url' ;
2123import { promisify } from 'node:util' ;
2224
@@ -27,6 +29,17 @@ const DEFAULT_REMOTE = 'git@github.com:CodeForPhilly/codeforphilly-data.git';
2729const DEFAULT_REF = 'empty' ;
2830const VENDORED_DIR = join ( dirname ( fileURLToPath ( import . meta. url ) ) , '../gitsheets' ) ;
2931
32+ /** Every `.toml` file under `dir`, keyed by its path relative to `dir`. */
33+ async function readTomlTree ( dir : string ) : Promise < Map < string , string > > {
34+ const out = new Map < string , string > ( ) ;
35+ for ( const entry of await readdir ( dir , { recursive : true , withFileTypes : true } ) ) {
36+ if ( ! entry . isFile ( ) || ! entry . name . endsWith ( '.toml' ) ) continue ;
37+ const abs = join ( entry . parentPath , entry . name ) ;
38+ out . set ( relative ( dir , abs ) , await readFile ( abs , 'utf8' ) ) ;
39+ }
40+ return out ;
41+ }
42+
3043function arg ( name : string , fallback : string ) : string {
3144 const prefix = `--${ name } =` ;
3245 return process . argv . find ( ( a ) => a . startsWith ( prefix ) ) ?. slice ( prefix . length ) ?? fallback ;
@@ -42,34 +55,40 @@ async function main(): Promise<number> {
4255 await exec ( 'git' , [ 'init' , '--bare' , '-q' , gitDir ] ) ;
4356 await git ( 'fetch' , '-q' , '--depth=1' , remote , ref ) ;
4457
45- const { stdout : listing } = await git ( 'ls-tree' , '--name-only' , 'FETCH_HEAD' , '.gitsheets/' ) ;
58+ const { stdout : listing } = await git ( 'ls-tree' , '-r' , '- -name-only', 'FETCH_HEAD' , '.gitsheets/' ) ;
4659 const upstream = new Map < string , string > ( ) ;
4760 for ( const path of listing . split ( '\n' ) . filter ( ( p ) => p . endsWith ( '.toml' ) ) ) {
4861 const { stdout } = await git ( 'show' , `FETCH_HEAD:${ path } ` ) ;
4962 upstream . set ( path . slice ( '.gitsheets/' . length ) , stdout ) ;
5063 }
5164
52- const vendored = new Map < string , string > ( ) ;
53- for ( const name of ( await readdir ( VENDORED_DIR ) ) . filter ( ( f ) => f . endsWith ( '.toml' ) ) ) {
54- vendored . set ( name , await readFile ( join ( VENDORED_DIR , name ) , 'utf8' ) ) ;
55- }
65+ const vendored = await readTomlTree ( VENDORED_DIR ) ;
5666
67+ // A sheet lands in the data repo before the app opens it, so a config
68+ // only upstream is expected mid-change: warn. Anything the app vendors
69+ // must match upstream exactly.
5770 const problems : string [ ] = [ ] ;
71+ const upstreamOnly : string [ ] = [ ] ;
5872 for ( const [ name , contents ] of upstream ) {
5973 const ours = vendored . get ( name ) ;
60- if ( ours === undefined ) problems . push ( `missing locally: ${ name } ` ) ;
74+ if ( ours === undefined ) upstreamOnly . push ( name ) ;
6175 else if ( ours !== contents ) problems . push ( `differs: ${ name } ` ) ;
6276 }
6377 for ( const name of vendored . keys ( ) ) {
6478 if ( ! upstream . has ( name ) ) problems . push ( `not in ${ ref } : ${ name } ` ) ;
6579 }
6680
81+ for ( const name of upstreamOnly ) {
82+ const msg = `${ name } is in ${ ref } but not vendored in apps/api/gitsheets/ (expected while a new sheet is landing)` ;
83+ console . warn ( process . env . GITHUB_ACTIONS ? `::warning title=Sheet config not vendored::${ msg } ` : `warning: ${ msg } ` ) ;
84+ }
85+
6786 if ( problems . length > 0 ) {
6887 console . error ( `Sheet configs in apps/api/gitsheets/ drift from ${ remote } #${ ref } :` ) ;
6988 for ( const p of problems ) console . error ( ` - ${ p } ` ) ;
7089 return 1 ;
7190 }
72- console . log ( `${ vendored . size } sheet configs match ${ remote } #${ ref } .` ) ;
91+ console . log ( `${ vendored . size } files under .gitsheets/ match ${ remote } #${ ref } .` ) ;
7392 return 0 ;
7493 } finally {
7594 await rm ( gitDir , { recursive : true , force : true } ) ;
0 commit comments