fix: aggregate HWPOD native service lifecycle
This commit is contained in:
@@ -27,10 +27,10 @@ if (argv[0] === "tasktree") {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} else if (argv[0] === "hwpod" && argv[1] === "service") {
|
||||
const { hwpodNativeServiceCommand, hwpodNativeServicesStatus } = await import("../../src/hwpod-native-service.ts");
|
||||
const { hwpodNativeServiceCommand, hwpodNativeServicesCommand } = await import("../../src/hwpod-native-service.ts");
|
||||
try {
|
||||
const result = argv[2] === "status" && argv[3] === undefined
|
||||
? await hwpodNativeServicesStatus({ cwd: process.cwd(), env: process.env })
|
||||
const result = ["start", "stop", "restart", "status", "logs"].includes(argv[2] ?? "") && argv[3] === undefined
|
||||
? await hwpodNativeServicesCommand({ action: argv[2], cwd: process.cwd(), env: process.env })
|
||||
: await hwpodNativeServiceCommand({ service: argv[2], action: argv[3] ?? "status", cwd: process.cwd(), env: process.env });
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
process.exitCode = result?.ok === false ? 1 : 0;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { hwpodNativeServicesStatus } from "./hwpod-native-service.ts";
|
||||
import { hwpodNativeServicesCommand, hwpodNativeServicesStatus } from "./hwpod-native-service.ts";
|
||||
|
||||
test("HWPOD aggregate status reports API, worker, and Web together", async () => {
|
||||
const cwd = await mkdtemp(path.join(tmpdir(), "hwpod-native-status-"));
|
||||
@@ -23,3 +23,15 @@ test("HWPOD aggregate status reports API, worker, and Web together", async () =>
|
||||
await rm(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("aggregate stop owns all HWPOD native services", async () => {
|
||||
const cwd = await mkdtemp(path.join(tmpdir(), "hwpod-native-stop-"));
|
||||
try {
|
||||
const result = await hwpodNativeServicesCommand({ action: "stop", cwd, env: { HWPOD_NATIVE_SERVICE_STATE_DIR: ".state/services" } });
|
||||
expect(result.ok).toBe(true);
|
||||
expect(Object.keys(result.services).sort()).toEqual(["api", "web", "worker"]);
|
||||
expect(result.summary.completed).toBe(3);
|
||||
} finally {
|
||||
await rm(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -24,6 +24,52 @@ export async function hwpodNativeServicesStatus(input: { cwd: string; env: Recor
|
||||
};
|
||||
}
|
||||
|
||||
export async function hwpodNativeServicesCommand(input: { action: string; cwd: string; env: Record<string, string | undefined> }) {
|
||||
if (input.action === "status") return hwpodNativeServicesStatus(input);
|
||||
if (input.action === "logs") {
|
||||
const results = await Promise.all(SERVICE_NAMES.map((service) => hwpodNativeServiceCommand({ service, action: "logs", ...input })));
|
||||
return aggregateResult("logs", results);
|
||||
}
|
||||
if (input.action === "stop") {
|
||||
const results = [];
|
||||
for (const service of [...SERVICE_NAMES].reverse()) results.push(await hwpodNativeServiceCommand({ service, action: "stop", ...input }));
|
||||
return aggregateResult("stop", results);
|
||||
}
|
||||
if (input.action === "start" || input.action === "restart") {
|
||||
const stopped = [];
|
||||
if (input.action === "restart") {
|
||||
for (const service of [...SERVICE_NAMES].reverse()) stopped.push(await hwpodNativeServiceCommand({ service, action: "stop", ...input }));
|
||||
}
|
||||
const started = [];
|
||||
for (const service of SERVICE_NAMES) started.push(await hwpodNativeServiceCommand({ service, action: "start", ...input }));
|
||||
const readiness = await waitForServicesReady(input);
|
||||
return { ...aggregateResult(input.action, started), stopped, readiness, ok: readiness.ok, status: readiness.status };
|
||||
}
|
||||
throw codedError("unsupported_service_action", `unsupported aggregate HWPOD service action: ${input.action}`);
|
||||
}
|
||||
|
||||
function aggregateResult(action: string, results: any[]) {
|
||||
const ok = results.every((result) => result.ok !== false);
|
||||
return {
|
||||
ok,
|
||||
operation: `hwpod.service.${action}`,
|
||||
status: ok ? "completed" : "failed",
|
||||
summary: { total: SERVICE_NAMES.length, completed: results.filter((result) => result.ok !== false).length, failed: results.filter((result) => result.ok === false).length },
|
||||
services: Object.fromEntries(results.map((result) => [result.service, result])),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForServicesReady(input: { cwd: string; env: Record<string, string | undefined> }) {
|
||||
const deadline = Date.now() + 30_000;
|
||||
let current = await hwpodNativeServicesStatus(input);
|
||||
while (!current.ok && Date.now() < deadline) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
current = await hwpodNativeServicesStatus(input);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
export async function hwpodNativeServiceCommand(input: { service: string; action: string; cwd: string; env: Record<string, string | undefined> }) {
|
||||
const service = validService(input.service);
|
||||
const stateDir = path.resolve(input.cwd, requiredEnv(input.env, "HWPOD_NATIVE_SERVICE_STATE_DIR"));
|
||||
|
||||
Reference in New Issue
Block a user