-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathdetach.ts
More file actions
87 lines (79 loc) · 2.8 KB
/
Copy pathdetach.ts
File metadata and controls
87 lines (79 loc) · 2.8 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
import { spawn, type ChildProcess } from "node:child_process";
import { mkdirSync, openSync } from "node:fs";
import { dirname } from "node:path";
export type DetachedChild = Pick<ChildProcess, "pid" | "unref">;
export type DetachSpawner = (
command: string,
args: readonly string[],
options: {
detached: boolean;
stdio: readonly ["ignore", number, number];
windowsHide: boolean;
}
) => DetachedChild;
export type DetachOptions = {
/** Absolute path to the log file. stdout + stderr of the child are appended here. */
logPath: string;
/** The full original argv (from process.argv.slice(2)). --detach and --log are stripped before re-spawn. */
argv: string[];
/** Path to the bin script the child should run (typically process.argv[1]). */
binEntry: string;
// Test seams ----------------------------------------------------------
execPath?: string;
spawnFn?: DetachSpawner;
openFd?: (path: string) => number;
ensureDir?: (path: string) => void;
stderr?: { write: (s: string) => void };
exit?: (code?: number) => never;
};
/**
* Strip `--detach` and `--log <value>` from an argv array so the re-spawned
* child does not fork again and does not re-interpret the log target.
*/
export function stripDetachFlags(argv: readonly string[]): string[] {
const out: string[] = [];
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === "--detach") continue;
if (a === "--log") {
i++; // also skip the value
continue;
}
out.push(a);
}
return out;
}
/**
* Fork the current bin into the background, redirect its stdio to `logPath`,
* print `detached pid <pid>, log <path>` on stderr, and exit the parent with
* code 0. The re-spawned child receives the original argv minus --detach /
* --log so it cannot fork again.
*/
export function detachAndExit(opts: DetachOptions): never {
const execPath = opts.execPath ?? process.execPath;
const spawnFn: DetachSpawner =
opts.spawnFn ??
((cmd, args, options) =>
spawn(cmd, args as string[], {
detached: options.detached,
stdio: options.stdio as ["ignore", number, number],
windowsHide: options.windowsHide,
}));
const openFd = opts.openFd ?? ((p) => openSync(p, "a"));
const ensureDir =
opts.ensureDir ?? ((p) => mkdirSync(p, { recursive: true }));
const stderr = opts.stderr ?? process.stderr;
const exit: (code?: number) => never =
opts.exit ?? ((code) => process.exit(code));
ensureDir(dirname(opts.logPath));
const logFd = openFd(opts.logPath);
const childArgv = stripDetachFlags(opts.argv);
const child = spawnFn(execPath, [opts.binEntry, ...childArgv], {
detached: true,
stdio: ["ignore", logFd, logFd] as const,
windowsHide: true,
});
child.unref();
stderr.write(`detached pid ${child.pid}, log ${opts.logPath}\n`);
exit(0);
}