Monorepo for Aesthetic.Computer
aesthetic.computer
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();