import { spawn, execSync, type ChildProcess } from "child_process"; import { BLOG_ROOT } from "./config"; class HugoManager { private process: ChildProcess | null = null; private ready = false; private starting = false; private healthTimer: NodeJS.Timeout | null = null; async start(): Promise<{ ok: boolean; alreadyRunning?: boolean }> { if (this.process && this.ready) { return { ok: true, alreadyRunning: true }; } if (this.starting) { return { ok: false }; } this.starting = true; this.ready = false; this.process = spawn("hugo", ["server", "--bind", "0.0.0.0", "--port", "1313", "--disableFastRender", "--noHTTPCache"], { cwd: BLOG_ROOT, shell: true, stdio: "pipe", windowsHide: true, }); this.process.stdout?.on("data", (d: Buffer) => process.stdout.write(`[hugo] ${d}`)); this.process.stderr?.on("data", (d: Buffer) => process.stderr.write(`[hugo] ${d}`)); this.process.on("exit", (code) => { console.log(`[hugo] exited ${code}`); this.process = null; this.ready = false; this.starting = false; }); // 等待 Hugo 就绪(最多 15 秒) const ok = await this.waitForReady(15000); this.starting = false; if (ok) { this.ready = true; this.startHealthCheck(); console.log("[hugo] ready on 0.0.0.0:1313"); } return { ok }; } stop(): void { this.stopHealthCheck(); if (!this.process) return; try { const pid = this.process.pid; if (process.platform === "win32" && pid) { execSync(`taskkill /PID ${pid} /T /F`, { stdio: "ignore" }); } else { this.process.kill(); } } catch (_e) { /* nothing to kill */ } this.process = null; this.ready = false; this.starting = false; } status(): { running: boolean; starting: boolean } { return { running: !!(this.process && this.ready), starting: this.starting }; } async ensureRunning(): Promise { if (this.process && this.ready) return true; const result = await this.start(); return result.ok; } private async waitForReady(timeoutMs: number): Promise { const start = Date.now(); while (Date.now() - start < timeoutMs) { if (await this.ping()) return true; await new Promise((r) => setTimeout(r, 1500)); } return false; } private async ping(): Promise { try { const res = await fetch("http://127.0.0.1:1313", { signal: AbortSignal.timeout(1500) }); return res.ok || res.status < 500; } catch (_e) { return false; } } private startHealthCheck(): void { this.stopHealthCheck(); this.healthTimer = setInterval(async () => { if (!this.process) return; const alive = await this.ping(); if (!alive) { console.warn("[hugo] health check failed, marking as not ready"); this.ready = false; this.stopHealthCheck(); } }, 30_000); } private stopHealthCheck(): void { if (this.healthTimer) { clearInterval(this.healthTimer); this.healthTimer = null; } } } export const hugo = new HugoManager();