about summary refs log tree commit diff stats
path: root/tree-sitter/dsk/dsk-cli/src/commands/dev.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tree-sitter/dsk/dsk-cli/src/commands/dev.ts')
-rw-r--r--tree-sitter/dsk/dsk-cli/src/commands/dev.ts105
1 files changed, 105 insertions, 0 deletions
diff --git a/tree-sitter/dsk/dsk-cli/src/commands/dev.ts b/tree-sitter/dsk/dsk-cli/src/commands/dev.ts
new file mode 100644
index 0000000..92b2867
--- /dev/null
+++ b/tree-sitter/dsk/dsk-cli/src/commands/dev.ts
@@ -0,0 +1,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;
+  }
+}
+
+