forked from weroperking/Betterbase
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprocess-manager.ts
More file actions
89 lines (76 loc) · 2.26 KB
/
Copy pathprocess-manager.ts
File metadata and controls
89 lines (76 loc) · 2.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import { join } from "path";
import { type Subprocess, spawn } from "bun";
import chalk from "chalk";
import { error, info, success, warn } from "../../utils/logger";
export class ProcessManager {
private _proc: Subprocess | null = null;
private _projectRoot: string;
private _restartCount = 0;
private _restartCooldown = false;
constructor(projectRoot: string) {
this._projectRoot = projectRoot;
}
async start(): Promise<void> {
if (this._proc) await this.stop();
const entryPoint = join(this._projectRoot, "src", "index.ts");
this._proc = spawn({
cmd: ["bun", "run", entryPoint],
cwd: this._projectRoot,
env: { ...process.env, NODE_ENV: "development" },
stdout: "pipe",
stderr: "pipe",
onExit: (proc, code, signal) => {
if (code !== 0 && code !== null && !this._restartCooldown) {
error(`[server] Process exited with code ${code}. Restarting...`);
this._scheduleRestart(500);
}
},
});
// Pipe stdout with [server] prefix
this._pipeStream(
this._proc.stdout as ReadableStream<Uint8Array> | null,
chalk.cyan("[server]"),
);
this._pipeStream(
this._proc.stderr as ReadableStream<Uint8Array> | null,
chalk.red("[server:err]"),
);
success(`[dev] Server started (restart #${this._restartCount})`);
}
async stop(): Promise<void> {
if (!this._proc) return;
this._proc.kill("SIGTERM");
await this._proc.exited.catch(() => {});
this._proc = null;
}
async restart(reason?: string): Promise<void> {
if (this._restartCooldown) return;
this._restartCooldown = true;
setTimeout(() => {
this._restartCooldown = false;
}, 300);
if (reason) info(`[dev] Restarting — ${reason}`);
this._restartCount++;
await this.start();
}
private _scheduleRestart(delayMs: number) {
setTimeout(() => this.restart("process exited"), delayMs);
}
private _pipeStream(stream: ReadableStream<Uint8Array> | null, prefix: string) {
if (!stream) return;
const reader = stream.getReader();
const decoder = new TextDecoder();
const pump = () => {
reader
.read()
.then(({ done, value }) => {
if (done) return;
const lines = decoder.decode(value).split("\n").filter(Boolean);
lines.forEach((line) => console.log(`${prefix} ${line}`));
pump();
})
.catch(() => {});
};
pump();
}
}