| 504 | }; |
| 505 | |
| 506 | const down = async (targetName: string) => { |
| 507 | const state = readState(targetName); |
| 508 | if (!state) return console.log(`${targetName}: nothing recorded`); |
| 509 | if (alive(state.runnerPid)) { |
| 510 | process.kill(state.runnerPid, "SIGTERM"); |
| 511 | const deadline = Date.now() + 10_000; |
| 512 | while (alive(state.runnerPid) && Date.now() < deadline) { |
| 513 | await new Promise((resolve) => setTimeout(resolve, 200)); |
| 514 | } |
| 515 | } |
| 516 | // Runner gone (or was already): make sure the children are too. |
| 517 | for (const pid of state.childPids ?? []) { |
| 518 | try { |
| 519 | process.kill(-pid, "SIGKILL"); |
| 520 | } catch { |
| 521 | try { |
| 522 | process.kill(pid, "SIGKILL"); |
| 523 | } catch { |
| 524 | // already gone |
| 525 | } |
| 526 | } |
| 527 | } |
| 528 | if (state.servePorts) { |
| 529 | const ts = await findTailscale(); |
| 530 | if (ts) for (const port of state.servePorts) await sh(ts, ["serve", `--https=${port}`, "off"]); |
| 531 | } |
| 532 | rmSync(statePath(targetName), { force: true }); |
| 533 | console.log(`${targetName}: down`); |
| 534 | }; |
| 535 | |
| 536 | const logs = (targetName: string) => { |
| 537 | const state = readState(targetName); |