Describe the feature you'd like to request
When I save a file, the dev server should automatically restart. Right now I have to manually restart it everytime I change a file.
Describe the solution you'd like
I think there should be a separate dev server script that looks like the following.
//node
import path from 'node:path';
import { ChildProcess, spawn } from 'node:child_process';
//stackpress
import { Terminal } from 'stackpress/terminal';
//config
import bootstrap from '../config/develop.js';
export type Control = {
brand: string;
error(message: string, variables?: string[]): void;
info(message: string, variables?: string[]): void;
input(question: string, answer?: string): Promise<string>;
output(message: string, variables?: string[], color?: string): void;
success(message: string, variables?: string[]): void;
system(message: string, variables?: string[]): void;
warning(message: string, variables?: string[]): void;
};
const project = path.dirname(import.meta.dirname);
let child: ChildProcess|null = null;
let restarting = false;
export async function develop() {
const server = await bootstrap();
const cli = new Terminal([], server);
// Handle graceful shutdown
process.on('SIGINT', () => {
if (child) {
cli.control.system('Shutting down server...');
child.kill('SIGTERM');
child = null;
}
cli.control.success('Server shutdown complete.');
process.exit(0);
});
process.on('SIGTERM', () => {
if (child) {
cli.control.system('Shutting down server...');
child.kill('SIGTERM');
child = null;
}
cli.control.success('Server shutdown complete.');
process.exit(0);
});
//start the server
child = start(cli.control);
//start the watcher
watch(cli.control);
}
export async function watch(control: Control) {
//lazy load chokidar to avoid unnecessary dependencies in production
const chokidar = await import('chokidar').then(m => m.default || m);
//watch the current directory for changes
const watcher = chokidar.watch('.', {
ignored: [ /node_modules/, /\.build/ ],
cwd: project,
ignoreInitial: true,
followSymlinks: true
});
watcher.on('change', async filepath => {
if (!filepath.endsWith('.ts')) return;
control.system(`📁 changed: ${filepath}`);
await restart(control);
control.success('✅ Server restarted...');
});
watcher.on('add', async filepath => {
if (!filepath.endsWith('.ts')) return;
control.system(`📁 added: ${filepath}`);
await restart(control);
control.success('✅ Server restarted...');
});
watcher.on('unlink', async filepath => {
if (!filepath.endsWith('.ts')) return;
control.system(`📁 removed: ${filepath}`);
await restart(control);
control.success('✅ Server restarted...');
});
};
export function start(control: Control) {
const command = path.join(project, 'node_modules', '.bin', 'tsx');
const script = path.join(project, 'scripts', 'serve.ts');
const child = spawn(command, [ script ], {
stdio: 'inherit',
shell: true,
env: { ...process.env }
});
child.on('error', (error) => {
console.error(error);
});
child.on('exit', (code) => {
if (!restarting) {
control.success(`Exited with code ${code}`);
}
});
return child;
};
export function restart(control: Control) {
return new Promise<ChildProcess>((resolve, reject) => {
if (restarting) return child;
restarting = true;
if (child) {
shutdown(child, control).then(() => {
child = start(control);
restarting = false;
resolve(child);
}).catch(reject);
} else {
child = start(control);
restarting = false;
resolve(child);
}
});
};
export function shutdown(current: ChildProcess, control: Control) {
const kill = () => {
control.warning(`⚠️ Process didn't exit gracefully, force killing...`);
current.kill('SIGKILL');
};
return new Promise<void>((resolve, reject) => {
const timeout = setTimeout(kill, 5000);
current.once('exit', () => {
clearTimeout(timeout);
resolve();
});
current.once('error', error => {
clearTimeout(timeout);
kill();
reject(error);
});
current.kill('SIGTERM');
});
};
develop().catch(e => {
console.error(e);
process.exit(1);
});
Describe alternatives you've considered
There is most likely better solutions than the one I prescribed above.
Describe the feature you'd like to request
When I save a file, the dev server should automatically restart. Right now I have to manually restart it everytime I change a file.
Describe the solution you'd like
I think there should be a separate dev server script that looks like the following.
Describe alternatives you've considered
There is most likely better solutions than the one I prescribed above.