@@ -12,6 +12,8 @@ import {
1212 computeSchemaHash ,
1313 importLegacyContractsJson ,
1414 lintThs ,
15+ listThsMigrations ,
16+ migrateThsSchema ,
1517 validateThsStructural ,
1618 type Issue ,
1719 type ThsSchema
@@ -1283,10 +1285,71 @@ program
12831285program
12841286 . command ( 'migrate' )
12851287 . argument ( '<schema>' , 'Path to THS schema JSON file' )
1286- . description ( 'Apply schema migrations locally (stub)' )
1287- . action ( ( ) => {
1288- console . error ( 'th migrate is not implemented yet (no migrations registry wired).' ) ;
1289- process . exitCode = 1 ;
1288+ . description ( 'Apply local THS schema migrations' )
1289+ . option ( '--list' , 'List known migrations and exit' , false )
1290+ . option ( '--down' , 'Apply down migrations (revert)' , false )
1291+ . option ( '--steps <n>' , 'Number of migrations to apply (default: all for up; 1 for down)' )
1292+ . option ( '--in-place' , 'Overwrite the input schema file' , false )
1293+ . option ( '--out <file>' , 'Write migrated schema JSON to a file (defaults to stdout)' )
1294+ . action ( ( schemaPath : string , opts : { list : boolean ; down : boolean ; steps ?: string ; inPlace : boolean ; out ?: string } ) => {
1295+ if ( opts . list ) {
1296+ const migrations = listThsMigrations ( ) ;
1297+ for ( const m of migrations ) {
1298+ console . log ( `${ m . id } - ${ m . description } ` ) ;
1299+ }
1300+ return ;
1301+ }
1302+
1303+ if ( opts . inPlace && opts . out ) {
1304+ console . error ( 'ERROR: --in-place and --out are mutually exclusive.' ) ;
1305+ process . exitCode = 1 ;
1306+ return ;
1307+ }
1308+
1309+ const input = readJsonFile ( schemaPath ) ;
1310+ const structural = validateThsStructural ( input ) ;
1311+ if ( ! structural . ok ) {
1312+ console . error ( formatIssues ( structural . issues ) ) ;
1313+ process . exitCode = 1 ;
1314+ return ;
1315+ }
1316+
1317+ const schema = structural . data ! ;
1318+
1319+ const steps = ( ( ) => {
1320+ if ( typeof opts . steps !== 'string' || opts . steps . trim ( ) === '' ) return undefined ;
1321+ const n = Number ( opts . steps ) ;
1322+ if ( ! Number . isFinite ( n ) || n < 0 ) throw new Error ( 'Invalid --steps value. Expected a non-negative number.' ) ;
1323+ return Math . floor ( n ) ;
1324+ } ) ( ) ;
1325+
1326+ const direction = opts . down ? 'down' : 'up' ;
1327+ const effectiveSteps = steps ?? ( direction === 'down' ? 1 : undefined ) ;
1328+
1329+ const res = migrateThsSchema ( schema , { direction, steps : effectiveSteps } ) ;
1330+ const migrated = res . schema ;
1331+
1332+ // Ensure the migrated schema still validates and lints cleanly.
1333+ const lintIssues = lintThs ( migrated ) ;
1334+ const errors = lintIssues . filter ( ( i ) => i . severity === 'error' ) ;
1335+ if ( errors . length > 0 ) {
1336+ console . error ( formatIssues ( lintIssues ) ) ;
1337+ process . exitCode = 1 ;
1338+ return ;
1339+ }
1340+
1341+ const outJson = JSON . stringify ( migrated , null , 2 ) ;
1342+ if ( opts . inPlace ) {
1343+ fs . writeFileSync ( schemaPath , outJson ) ;
1344+ } else if ( opts . out ) {
1345+ ensureDir ( path . dirname ( opts . out ) ) ;
1346+ fs . writeFileSync ( opts . out , outJson ) ;
1347+ } else {
1348+ console . log ( outJson ) ;
1349+ }
1350+
1351+ const appliedMsg = res . appliedNow . length > 0 ? res . appliedNow . join ( ', ' ) : '(none)' ;
1352+ console . error ( `migrations (${ direction } ) applied: ${ appliedMsg } ` ) ;
12901353 } ) ;
12911354
12921355program
0 commit comments