11#!/usr/bin/env node
22import { build , validate } from '../src/build.mjs' ;
33import { formatDiagnostic } from '../src/validate.mjs' ;
4- import { readFileSync } from 'node:fs' ;
4+ import { readFileSync , watch as fsWatch } from 'node:fs' ;
55import { dirname , resolve } from 'node:path' ;
66import { fileURLToPath } from 'node:url' ;
77
@@ -34,6 +34,7 @@ const USAGE = `iso-harness — one source directory, every agent harness
3434Usage:
3535 iso-harness --version
3636 iso-harness build [--source <dir>] [--out <dir>] [--target claude,cursor,codex,opencode]
37+ [--dry-run] [--watch]
3738 iso-harness validate [--source <dir>] [--format text|json]
3839
3940Commands:
4748 --source <dir> Path to iso source directory (default: iso)
4849 --out <dir> Output root directory (default: .)
4950 --target <list> Comma-separated targets (default: all four)
51+ --dry-run Print what would be written, with byte sizes. No disk writes.
52+ --watch Rebuild on changes to the source directory. Ctrl-C to exit.
5053 --format <fmt> validate-only: text (default) | json
5154` ;
5255
@@ -70,21 +73,58 @@ if (!cmd || cmd === '-h' || cmd === '--help' || cmd === 'help') {
7073 process . exit ( cmd ? 0 : 0 ) ;
7174}
7275
76+ async function runBuildOnce ( { source, out, targets, dryRun } ) {
77+ try {
78+ const summary = await build ( { source, out, targets, dryRun } ) ;
79+ for ( const line of summary ) console . log ( line ) ;
80+ return 0 ;
81+ } catch ( err ) {
82+ console . error ( err . message ) ;
83+ return 1 ;
84+ }
85+ }
86+
87+ function watchBuild ( { source, out, targets, dryRun } ) {
88+ const sourceAbs = resolve ( source ) ;
89+ let scheduled = null ;
90+ let building = false ;
91+ const trigger = ( ) => {
92+ if ( scheduled ) clearTimeout ( scheduled ) ;
93+ scheduled = setTimeout ( async ( ) => {
94+ scheduled = null ;
95+ if ( building ) return ;
96+ building = true ;
97+ console . log ( `\n--- change detected — rebuilding ---` ) ;
98+ await runBuildOnce ( { source, out, targets, dryRun } ) ;
99+ building = false ;
100+ } , 150 ) ;
101+ } ;
102+ try {
103+ fsWatch ( sourceAbs , { recursive : true } , trigger ) ;
104+ } catch ( err ) {
105+ console . error ( `watch failed for ${ sourceAbs } : ${ err . message } ` ) ;
106+ process . exit ( 1 ) ;
107+ }
108+ console . log ( `watching ${ sourceAbs } — press ^C to exit` ) ;
109+ }
110+
73111if ( cmd === 'build' ) {
74112 const source = flag ( 'source' , 'iso' ) ;
75113 const out = flag ( 'out' , '.' ) ;
76114 const targets = list ( 'target' ) ?? ALL_TARGETS ;
115+ const dryRun = boolFlag ( 'dry-run' ) ;
116+ const watchMode = boolFlag ( 'watch' ) ;
77117 const unknown = targets . filter ( t => ! ALL_TARGETS . includes ( t ) ) ;
78118 if ( unknown . length ) {
79119 console . error ( `Unknown target(s): ${ unknown . join ( ', ' ) } . Valid: ${ ALL_TARGETS . join ( ', ' ) } ` ) ;
80120 process . exit ( 2 ) ;
81121 }
82- try {
83- const summary = await build ( { source , out , targets } ) ;
84- for ( const line of summary ) console . log ( line ) ;
85- } catch ( err ) {
86- console . error ( err . message ) ;
87- process . exit ( 1 ) ;
122+ const initial = await runBuildOnce ( { source , out , targets , dryRun } ) ;
123+ if ( watchMode ) {
124+ watchBuild ( { source , out , targets , dryRun } ) ;
125+ // Keep the process alive; ^C exits.
126+ } else {
127+ process . exit ( initial ) ;
88128 }
89129} else if ( cmd === 'validate' ) {
90130 const source = flag ( 'source' , 'iso' ) ;
0 commit comments