Monorepo for Aesthetic.Computer aesthetic.computer
at main 136 lines 3.5 kB view raw
1#!/usr/bin/env node 2/** 3 * 🩸 Artery Dev Runner - Hot-reloading wrapper for artery-tui.mjs 4 * Watches for changes and automatically restarts the TUI. 5 */ 6 7import { spawn } from 'child_process'; 8import { watch } from 'fs'; 9import path from 'path'; 10import { fileURLToPath } from 'url'; 11 12const __dirname = path.dirname(fileURLToPath(import.meta.url)); 13 14const ARTERY_TUI_PATH = path.join(__dirname, 'artery-tui.mjs'); 15const ARTERY_PATH = path.join(__dirname, 'artery.mjs'); 16 17let child = null; 18let restarting = false; 19let debounceTimer = null; 20 21// ANSI codes 22const RESET = '\x1b[0m'; 23const BOLD = '\x1b[1m'; 24const FG_MAGENTA = '\x1b[35m'; 25const FG_YELLOW = '\x1b[33m'; 26const FG_GREEN = '\x1b[32m'; 27const FG_DIM = '\x1b[2m'; 28 29function log(msg) { 30 console.log(`${FG_MAGENTA}${BOLD}🩸 Artery Dev:${RESET} ${msg}`); 31} 32 33function startArtery() { 34 if (child) { 35 return; 36 } 37 38 child = spawn('node', [ARTERY_TUI_PATH], { 39 stdio: 'inherit', 40 env: { ...process.env, ARTERY_DEV_MODE: 'true' } 41 }); 42 43 child.on('exit', (code, signal) => { 44 child = null; 45 if (restarting) { 46 restarting = false; 47 // Small delay before restart to let file system settle 48 setTimeout(startArtery, 100); 49 } else if (signal !== 'SIGTERM' && signal !== 'SIGINT' && code !== 42) { 50 // Unexpected exit - restart after delay (code 42 = intentional quit) 51 log(`${FG_YELLOW}Process exited (${code}), restarting in 1s...${RESET}`); 52 setTimeout(startArtery, 1000); 53 } else if (code === 42) { 54 // Intentional quit - restart immediately 55 log(`${FG_GREEN}Restarting TUI...${RESET}`); 56 setTimeout(startArtery, 100); 57 } 58 }); 59 60 child.on('error', (err) => { 61 log(`${FG_YELLOW}Error: ${err.message}${RESET}`); 62 child = null; 63 }); 64} 65 66function restartArtery() { 67 if (child) { 68 restarting = true; 69 log(`${FG_GREEN}File changed, restarting...${RESET}`); 70 child.kill('SIGTERM'); 71 72 // Force kill if process doesn't exit within 2 seconds 73 const forceKillTimeout = setTimeout(() => { 74 if (child && restarting) { 75 log(`${FG_YELLOW}Process not responding to SIGTERM, force killing...${RESET}`); 76 child.kill('SIGKILL'); 77 } 78 }, 2000); 79 80 // Clear the force kill timeout when child exits 81 child.once('exit', () => { 82 clearTimeout(forceKillTimeout); 83 }); 84 } else { 85 startArtery(); 86 } 87} 88 89function debounceRestart() { 90 if (debounceTimer) { 91 clearTimeout(debounceTimer); 92 } 93 debounceTimer = setTimeout(restartArtery, 200); 94} 95 96// Watch for file changes 97function setupWatchers() { 98 const filesToWatch = [ARTERY_TUI_PATH, ARTERY_PATH]; 99 100 log(`${FG_DIM}Watching for changes...${RESET}`); 101 log(`${FG_DIM} - artery-tui.mjs${RESET}`); 102 log(`${FG_DIM} - artery.mjs${RESET}`); 103 console.log(''); // Blank line before TUI starts 104 105 for (const file of filesToWatch) { 106 try { 107 watch(file, { persistent: true }, (eventType, filename) => { 108 if (eventType === 'change') { 109 debounceRestart(); 110 } 111 }); 112 } catch (err) { 113 log(`${FG_YELLOW}Warning: Could not watch ${path.basename(file)}: ${err.message}${RESET}`); 114 } 115 } 116} 117 118// Handle graceful shutdown 119process.on('SIGINT', () => { 120 if (child) { 121 child.kill('SIGTERM'); 122 } 123 process.exit(0); 124}); 125 126process.on('SIGTERM', () => { 127 if (child) { 128 child.kill('SIGTERM'); 129 } 130 process.exit(0); 131}); 132 133// Main 134log(`${FG_GREEN}Starting in dev mode with hot reload${RESET}`); 135setupWatchers(); 136startArtery();