Repository navigation
Expand file tree
/
Copy pathdev.mjs
More file actions
68 lines (62 loc) · 2.5 KB
/
Copy pathdev.mjs
File metadata and controls
68 lines (62 loc) · 2.5 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
// npm run dev: the web app, then the agent once the app answers. Each line prefixed; Ctrl-C stops both.
// The app takes the first free port from PORT (3000), and the agent is pointed at that port, so another program
// already on 3000 can't swallow the agent's requests and leave every job waiting.
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { firstFreePort } from "./lib/port.mjs";
const root = fileURLToPath(new URL("..", import.meta.url));
const children = [];
function start(name, cwd, color, { args = [], env = {} } = {}) {
// Windows finds npm as npm.cmd, which only a shell resolves.
const child = spawn("npm", ["run", "dev", ...args], {
cwd: `${root}${cwd}`,
env: { ...process.env, ...env },
stdio: ["ignore", "pipe", "pipe"],
shell: process.platform === "win32",
});
const prefix = `${color}${name.padEnd(5)}\x1b[0m │ `;
const pipe = (stream, out) => {
let rest = "";
stream.on("data", (buf) => {
const lines = (rest + buf.toString()).split("\n");
rest = lines.pop() ?? "";
for (const line of lines) out.write(prefix + line + "\n");
});
};
pipe(child.stdout, process.stdout);
pipe(child.stderr, process.stderr);
child.on("exit", (code) => {
stopAll();
process.exitCode = code ?? 0;
});
children.push(child);
}
function stopAll() {
for (const child of children) if (child.exitCode === null) child.kill("SIGINT");
}
process.on("SIGINT", stopAll);
process.on("SIGTERM", stopAll);
/** True once PostEcho itself answers: its login page, not just any server on the port. */
async function waitForWeb(url, tries = 120) {
for (let i = 0; i < tries; i++) {
try {
const response = await fetch(url);
if (response.status < 500 && (await response.text()).includes("PostEcho")) return true;
} catch {
// Not up yet.
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
return false;
}
const wanted = Number(process.env.PORT) || 3000;
const port = await firstFreePort(wanted);
const url = `http://localhost:${port}`;
if (port !== wanted) console.log(`Port ${wanted} is taken by another program, so PostEcho uses ${port}.`);
start("web", "web", "\x1b[36m", { args: ["--", "-p", String(port)] });
if (await waitForWeb(`${url}/login`)) {
console.log(`\x1b[32mPostEcho is running: ${url}\x1b[0m`);
start("agent", "agent", "\x1b[35m", { env: { POSTECHO_URL: url } });
} else {
console.log(`The web app didn't answer on ${url} in two minutes, so the agent wasn't started.`);
}