Skip to content

Commit 8075bff

Browse files
ochafikclaude
andauthored
refactor(examples): use concurrently API for proper process cleanup (#128)
Replace custom spawn-based orchestration with concurrently's API. This fixes zombie processes on Ctrl+C - concurrently uses tree-kill internally to properly terminate entire process trees. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent 366c5d5 commit 8075bff

File tree

1 file changed

+27
-66
lines changed

1 file changed

+27
-66
lines changed

examples/run-all.ts

Lines changed: 27 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010

1111
import { readdirSync, statSync, existsSync } from "fs";
12-
import { spawn, type ChildProcess } from "child_process";
12+
import concurrently from "concurrently";
1313

1414
const BASE_PORT = 3101;
1515
const BASIC_HOST = "basic-host";
@@ -36,57 +36,9 @@ const command = process.argv[2];
3636

3737
if (!command || !COMMANDS.includes(command)) {
3838
console.error(`Usage: bun examples/run-all.ts <${COMMANDS.join("|")}>`);
39-
4039
process.exit(1);
4140
}
4241

43-
const processes: ChildProcess[] = [];
44-
45-
// Handle cleanup on exit
46-
function cleanup() {
47-
for (const proc of processes) {
48-
proc.kill();
49-
}
50-
}
51-
process.on("SIGINT", cleanup);
52-
process.on("SIGTERM", cleanup);
53-
54-
// Spawn a process and track it
55-
function spawnProcess(
56-
cmd: string,
57-
args: string[],
58-
env: Record<string, string> = {},
59-
prefix: string,
60-
): ChildProcess {
61-
const proc = spawn(cmd, args, {
62-
env: { ...process.env, ...env },
63-
stdio: ["ignore", "pipe", "pipe"],
64-
});
65-
66-
proc.stdout?.on("data", (data) => {
67-
const lines = data.toString().trim().split("\n");
68-
for (const line of lines) {
69-
console.log(`[${prefix}] ${line}`);
70-
}
71-
});
72-
73-
proc.stderr?.on("data", (data) => {
74-
const lines = data.toString().trim().split("\n");
75-
for (const line of lines) {
76-
console.error(`[${prefix}] ${line}`);
77-
}
78-
});
79-
80-
proc.on("exit", (code) => {
81-
if (code !== 0 && code !== null) {
82-
console.error(`[${prefix}] exited with code ${code}`);
83-
}
84-
});
85-
86-
processes.push(proc);
87-
return proc;
88-
}
89-
9042
// Build the SERVERS environment variable (JSON array of URLs)
9143
const serversEnv = JSON.stringify(servers.map((s) => s.url));
9244

@@ -96,25 +48,34 @@ console.log(
9648
);
9749
console.log("");
9850

51+
// Build command list for concurrently
52+
const commands: Parameters<typeof concurrently>[0] = [
53+
// Server examples
54+
...servers.map(({ dir, port }) => ({
55+
command: `npm run --workspace examples/${dir} ${command}`,
56+
name: dir,
57+
env: { PORT: String(port) },
58+
})),
59+
// Basic host with SERVERS env
60+
{
61+
command: `npm run --workspace examples/${BASIC_HOST} ${command}`,
62+
name: BASIC_HOST,
63+
env: { SERVERS: serversEnv },
64+
},
65+
];
66+
9967
// If dev mode, also run the main library watcher
10068
if (command === "dev") {
101-
spawnProcess("npm", ["run", "watch"], {}, "lib");
69+
commands.unshift({
70+
command: "npm run watch",
71+
name: "lib",
72+
});
10273
}
10374

104-
// Run each server example
105-
for (const { dir, port } of servers) {
106-
spawnProcess(
107-
"npm",
108-
["run", "--workspace", `examples/${dir}`, command],
109-
{ PORT: String(port) },
110-
dir,
111-
);
112-
}
75+
const { result } = concurrently(commands, {
76+
prefix: "name",
77+
// For build command, we want all to complete; for start/dev, kill all on failure
78+
killOthersOnFail: command !== "build",
79+
});
11380

114-
// Run basic-host with the SERVERS env var
115-
spawnProcess(
116-
"npm",
117-
["run", "--workspace", `examples/${BASIC_HOST}`, command],
118-
{ SERVERS: serversEnv },
119-
BASIC_HOST,
120-
);
81+
result.catch(() => process.exit(1));

0 commit comments

Comments
 (0)