@@ -3,13 +3,74 @@ import { readFileSync, writeFileSync, mkdirSync, rmSync, cpSync } from "fs";
33import { createHash } from "crypto" ;
44import { basename , dirname , join , relative } from "path" ;
55import { glob } from "node:fs/promises" ;
6- import Ajv2020 from "ajv/dist/2020.js" ;
7- import addFormats from "ajv-formats" ;
6+ import Ajv2020Import from "ajv/dist/2020.js" ;
7+ import addFormatsImport from "ajv-formats" ;
88import stripJsonComments from "strip-json-comments" ;
9- import { red , yellow , green , cyan } from "./utils.mjs" ;
9+ import { red , yellow , green , cyan } from "./utils.mts" ;
10+
11+ // ajv and ajv-formats are CommonJS; under NodeNext their ESM default import is
12+ // the module namespace, so the constructor/function lives on `.default`.
13+ const Ajv2020 = Ajv2020Import . default ;
14+ const addFormats = addFormatsImport . default ;
1015
1116const DIST = "dist" ;
1217
18+ // ---------------------------------------------------------------------------
19+ // Types
20+ // ---------------------------------------------------------------------------
21+
22+ /** Parsed source data for a Map (`<name>.jsonc`). Treated generically here. */
23+ interface MapSourceData {
24+ schemaVersion ?: string ;
25+ hosts ?: Record < string , unknown > ;
26+ [ key : string ] : unknown ;
27+ }
28+
29+ /** A migration projecting the latest source shape onto an older major. */
30+ type MigrationFn = ( data : MapSourceData ) => MapSourceData ;
31+
32+ /** The subset of a JSON Schema document the build reads. */
33+ interface SchemaJson {
34+ $id ?: string ;
35+ deprecated ?: boolean ;
36+ properties ?: { schemaVersion ?: { const ?: string } } ;
37+ [ key : string ] : unknown ;
38+ }
39+
40+ interface SchemaEntry {
41+ file : string ;
42+ schema : SchemaJson ;
43+ major : number ;
44+ expectedVersion : string ;
45+ }
46+
47+ interface BuildEntry {
48+ target : SchemaEntry ;
49+ payload : MapSourceData ;
50+ }
51+
52+ interface MapEntry {
53+ name : string ;
54+ dir : string ;
55+ dataFile : string ;
56+ schemas : SchemaEntry [ ] ;
57+ builds : BuildEntry [ ] ;
58+ }
59+
60+ interface ManifestMapVersion {
61+ filename : string ;
62+ cid : string ;
63+ schema : string ;
64+ deprecated ?: boolean ;
65+ }
66+
67+ interface Manifest {
68+ buildId : string ;
69+ timestamp : string ;
70+ gitSha : string ;
71+ maps : Record < string , Record < string , ManifestMapVersion > > ;
72+ }
73+
1374// ---------------------------------------------------------------------------
1475// Per-Map backwards-compatibility migrations
1576//
@@ -38,7 +99,7 @@ const DIST = "dist";
3899// To drop support for an older schema major, either remove its migration
39100// entry below or delete the corresponding `<name>.v<N>.schema.json` file.
40101// ---------------------------------------------------------------------------
41- const MIGRATIONS = {
102+ const MIGRATIONS : Record < string , Record < number , MigrationFn > > = {
42103 forms : {
43104 // 0: (data) => data, // example: latest source projecting to v0
44105 // 1: (data) => data, // example: latest source projecting to v1
@@ -51,7 +112,7 @@ rmSync(DIST, { recursive: true, force: true });
51112
52113// Step 1: Discover Maps and their schemas
53114
54- const mapsByName = new Map ( ) ;
115+ const mapsByName = new Map < string , MapEntry > ( ) ;
55116
56117// Each Map lives one level deep under maps/ (e.g. maps/forms/).
57118// Schema files are versioned: <name>.v<major>.schema.json.
@@ -68,7 +129,9 @@ for await (const schemaFile of glob("maps/*/*.v*.schema.json")) {
68129
69130 const major = parseInt ( majorMatch [ 1 ] , 10 ) ;
70131
71- const schemaJson = JSON . parse ( readFileSync ( schemaFile , "utf-8" ) ) ;
132+ const schemaJson = JSON . parse (
133+ readFileSync ( schemaFile , "utf-8" ) ,
134+ ) as SchemaJson ;
72135 const expectedVersion = schemaJson ?. properties ?. schemaVersion ?. const ;
73136
74137 if ( typeof expectedVersion !== "string" ) {
@@ -110,10 +173,10 @@ for await (const schemaFile of glob("maps/*/*.v*.schema.json")) {
110173 }
111174
112175 if ( ! mapsByName . has ( name ) ) {
113- mapsByName . set ( name , { name, dir, dataFile, schemas : [ ] } ) ;
176+ mapsByName . set ( name , { name, dir, dataFile, schemas : [ ] , builds : [ ] } ) ;
114177 }
115178
116- mapsByName . get ( name ) . schemas . push ( {
179+ mapsByName . get ( name ) ! . schemas . push ( {
117180 file : schemaFile ,
118181 schema : schemaJson ,
119182 major,
@@ -175,7 +238,7 @@ for (const map of maps) {
175238
176239 const sourceData = JSON . parse (
177240 stripJsonComments ( readFileSync ( map . dataFile , "utf-8" ) ) ,
178- ) ;
241+ ) as MapSourceData ;
179242
180243 // Normalize unicode host keys to punycode (once) and warn on www. prefixes.
181244 if ( sourceData . hosts ) {
@@ -232,7 +295,7 @@ for (const map of maps) {
232295 map . builds = [ ] ;
233296
234297 for ( const target of targets ) {
235- let projectedData ;
298+ let projectedData : MapSourceData ;
236299 if ( target . major === sourceSchema . major ) {
237300 projectedData = sourceData ;
238301 } else {
@@ -241,7 +304,7 @@ for (const map of maps) {
241304 console . error (
242305 red (
243306 `${ map . name } : no migration registered for source v${ sourceSchema . major } → v${ target . major } . ` +
244- `Register MIGRATIONS["${ map . name } "][${ target . major } ] in scripts/build.mjs , ` +
307+ `Register MIGRATIONS["${ map . name } "][${ target . major } ] in scripts/build.mts , ` +
245308 `or delete ${ map . dir } /${ map . name } .v${ target . major } .schema.json to drop support for v${ target . major } .` ,
246309 ) ,
247310 ) ;
@@ -260,7 +323,7 @@ for (const map of maps) {
260323 if ( ! validate ( payload ) ) {
261324 console . error ( red ( `Validation failed: ${ map . dataFile } → ${ target . file } ` ) ) ;
262325
263- for ( const err of validate . errors ) {
326+ for ( const err of validate . errors ?? [ ] ) {
264327 console . error ( ` ${ err . instancePath || "/" } : ${ err . message } ` ) ;
265328 }
266329
@@ -307,14 +370,14 @@ const gitSha =
307370 }
308371 } ) ( ) ;
309372
310- const manifest = {
373+ const manifest : Manifest = {
311374 buildId,
312375 timestamp : new Date ( ) . toISOString ( ) ,
313376 gitSha,
314377 maps : { } ,
315378} ;
316379
317- const checksums = [ ] ;
380+ const checksums : string [ ] = [ ] ;
318381
319382mkdirSync ( DIST , { recursive : true } ) ;
320383
@@ -351,13 +414,15 @@ for (const map of maps) {
351414
352415// Validate the assembled manifest against its schema before writing.
353416const manifestSchemaSrc = "scripts/manifest.schema.json" ;
354- const manifestSchema = JSON . parse ( readFileSync ( manifestSchemaSrc , "utf-8" ) ) ;
417+ const manifestSchema = JSON . parse (
418+ readFileSync ( manifestSchemaSrc , "utf-8" ) ,
419+ ) as SchemaJson ;
355420const validateManifest = ajv . compile ( manifestSchema ) ;
356421if ( ! validateManifest ( manifest ) ) {
357422 console . error (
358423 red ( `Manifest failed validation against ${ manifestSchemaSrc } :` ) ,
359424 ) ;
360- for ( const err of validateManifest . errors ) {
425+ for ( const err of validateManifest . errors ?? [ ] ) {
361426 console . error ( ` ${ err . instancePath || "/" } : ${ err . message } ` ) ;
362427 }
363428 process . exit ( 1 ) ;
0 commit comments