1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
/**
* 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 <ms>', '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<void> {
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;
}
}
|