| 1 | import { randomUUID } from "node:crypto"; |
| 2 | import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs"; |
| 3 | import { dirname, join } from "node:path"; |
| 4 | import type { HelloResult } from "./handshake.js"; |
| 5 | |
| 6 | type ShellBuild = { version: string; channel: string; commit: string }; |
| 7 | |
| 8 | export class ShellLifecycle { |
| 9 | private path = ""; |
| 10 | private state: Record<string, unknown> | null = null; |
| 11 | |
| 12 | constructor( |
| 13 | private readonly dataHome: string, |
| 14 | private readonly build: ShellBuild, |
| 15 | private readonly now = () => new Date(), |
| 16 | ) {} |
| 17 | |
| 18 | start(hello: HelloResult): void { |
| 19 | if (!hello.diagnosticsEnabled || this.path) return; |
| 20 | const runId = randomUUID().replaceAll("-", ""); |
| 21 | const at = this.now().toISOString(); |
| 22 | this.path = join(this.dataHome, "diagnostics", "lifecycle", `shell-${process.pid}-${runId}.json`); |
| 23 | this.state = { |
| 24 | schemaVersion: 3, |
| 25 | pid: process.pid, |
| 26 | runId, |
| 27 | incidentId: hello.incidentId, |
| 28 | version: this.build.version, |
| 29 | buildCommit: this.build.commit, |
| 30 | channel: this.build.channel, |
| 31 | processRole: "shell", |
| 32 | phase: "healthy", |
| 33 | startedAt: at, |
| 34 | updatedAt: at, |
| 35 | }; |
| 36 | this.write(); |
| 37 | } |
| 38 | |
| 39 | mark(phase: string): void { |
| 40 | if (!this.state || !phase) return; |
| 41 | this.state.phase = phase; |
| 42 | this.state.updatedAt = this.now().toISOString(); |
| 43 | this.write(); |
| 44 | } |
| 45 | |
| 46 | complete(): void { |
| 47 | if (!this.path) return; |
| 48 | rmSync(this.path, { force: true }); |
| 49 | this.path = ""; |
| 50 | this.state = null; |
| 51 | } |
| 52 | |
| 53 | private write(): void { |
| 54 | if (!this.path || !this.state) return; |
| 55 | mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 }); |
| 56 | const temporary = `${this.path}.tmp-${process.pid}`; |
| 57 | writeFileSync(temporary, `${JSON.stringify(this.state)}\n`, { |
| 58 | mode: 0o600, |
| 59 | }); |
| 60 | renameSync(temporary, this.path); |
| 61 | } |
| 62 | } |
| 63 |