/** * Dev Command - Watch grammar.js and rebuild/test on change */ import { Command } from 'commander'; import chokidar from 'chokidar'; import chalk from 'chalk'; import { existsSync } from 'fs'; import { execa } from 'execa'; /** * Create the dev command */ export function createDevCommand(): Command { const devCommand = new Command('dev'); devCommand .description('Watch grammar.js and run build + test on changes') .option('-v, --verbose', 'Show detailed build output') .option('--quiet', 'Suppress non-error logs') .option('--debounce ', 'Debounce delay in milliseconds', '150') .action(async (options) => { if (!existsSync('grammar.js')) { console.error(chalk.red('โŒ No grammar.js found. Are you in a DSL project directory?')); process.exit(1); } const verbose: boolean = Boolean(options.verbose); const quiet: boolean = Boolean(options.quiet); const debounceMs: number = Number.parseInt(options.debounce, 10) || 150; if (!quiet) { console.log(chalk.blue('๐Ÿ‘€ Watching grammar.js for changes...')); } // Initial build + test await runBuildAndTest({ verbose, quiet }); // Watcher with debounced rebuilds const watcher = chokidar.watch('grammar.js', { ignoreInitial: true }); let isRunning = false; let rerunRequested = false; let debounceTimer: NodeJS.Timeout | null = null; const runOnce = async () => { if (isRunning) { rerunRequested = true; return; } isRunning = true; if (!quiet) { console.log(chalk.yellow('โ†ป Change detected. Rebuilding...')); } try { await runBuildAndTest({ verbose, quiet }); if (!quiet) { console.log(chalk.green('โœ… Rebuild and tests completed.')); } } catch (e) { // Errors already printed by build/test } finally { isRunning = false; if (rerunRequested) { rerunRequested = false; runOnce(); } } }; const debouncedTrigger = () => { if (debounceTimer) clearTimeout(debounceTimer); debounceTimer = setTimeout(runOnce, debounceMs); }; watcher.on('change', debouncedTrigger); }); return devCommand; } async function runBuildAndTest(opts: { verbose?: boolean; quiet?: boolean }): Promise { const { verbose, quiet } = opts; if (!quiet) { console.log(chalk.blue('๐Ÿ—๏ธ Building...')); } try { const buildArgs = ['build', ...(verbose ? ['--verbose'] : [])]; await execa('dsk', buildArgs, { stdio: 'inherit' }); } catch (error: any) { console.error(chalk.red('โŒ Build failed. Fix errors and save again.')); throw error; } if (!quiet) { console.log(chalk.blue('๐Ÿงช Testing...')); } try { await execa('dsk', ['test'], { stdio: 'inherit' }); } catch (error: any) { console.error(chalk.red('โŒ Tests failed. Fix tests and save again.')); throw error; } }