1// @effect-diagnostics nodeBuiltinImport:off - Exercises real Node IPC and process signals.
2import * as NodeChildProcess from "node:child_process";
3import * as NodeEvents from "node:events";
4import * as NodeFSP from "node:fs/promises";
5import * as NodeOS from "node:os";
6import * as NodePath from "node:path";
7import { expect, it } from "vite-plus/test";
8
9import { legacyCliLauncherScript } from "./legacyCliLauncher.ts";
10
11// oxlint-disable-next-line t3code/no-global-process-runtime -- This test launches a real host executable.
12const hostPlatform = NodeOS.platform();
13// oxlint-disable-next-line t3code/no-global-process-runtime -- Match the real executable used by the subprocess.
14const hostArch = NodeOS.arch();
15
16// The fixture executable uses a POSIX shebang. The wrapper itself also runs on Windows.
17it.skipIf(hostPlatform === "win32")(
18 "keeps service IPC, arguments, and termination connected",
19 async () => {
20 const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-legacy-launcher-"));
21 const entry = NodePath.join(root, "node_modules/t3/dist/bin.mjs");
22 const executable = NodePath.join(
23 root,
24 `node_modules/@t3code/t3-${hostPlatform}-${hostArch}/t3`,
25 );
26 await NodeFSP.mkdir(NodePath.dirname(entry), { recursive: true });
27 await NodeFSP.mkdir(NodePath.dirname(executable), { recursive: true });
28 await NodeFSP.writeFile(
29 NodePath.join(NodePath.dirname(executable), "package.json"),
30 '{"type":"commonjs"}',
31 );
32 await NodeFSP.writeFile(entry, legacyCliLauncherScript());
33 await NodeFSP.writeFile(
34 executable,
35 `#!${process.execPath}
36process.on("SIGTERM", () => process.exit(23));
37process.on("message", message => process.send({ reply: message }));
38process.send({ args: process.argv.slice(2) });
39`,
40 );
41 await NodeFSP.chmod(executable, 0o755);
42 const child = NodeChildProcess.fork(entry, ["serve", "a path with spaces"], { silent: true });
43 try {
44 expect((await NodeEvents.EventEmitter.once(child, "message"))[0]).toEqual({
45 args: ["serve", "a path with spaces"],
46 });
47 const reply = NodeEvents.EventEmitter.once(child, "message");
48 child.send({ type: "trial-accepted" });
49 expect((await reply)[0]).toEqual({ reply: { type: "trial-accepted" } });
50 const exit = NodeEvents.EventEmitter.once(child, "exit");
51 child.kill("SIGTERM");
52 expect(await exit).toEqual([23, null]);
53 } finally {
54 if (child.exitCode === null && child.signalCode === null) {
55 const exit = NodeEvents.EventEmitter.once(child, "exit");
56 child.kill("SIGTERM");
57 await exit;
58 }
59 await NodeFSP.rm(root, { recursive: true, force: true });
60 }
61 },
62);