Files
blog/write/src/lib/hugo.ts
T
2026-06-02 00:23:04 +08:00

115 lines
3.1 KiB
TypeScript

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<boolean> {
if (this.process && this.ready) return true;
const result = await this.start();
return result.ok;
}
private async waitForReady(timeoutMs: number): Promise<boolean> {
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<boolean> {
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();