Files
clawdbot/src/commands/doctor-ui.ts
Peter Steinberger c379191f80 chore: migrate to oxlint and oxfmt
Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
2026-01-14 15:02:19 +00:00

143 lines
4.8 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import { resolveClawdbotPackageRoot } from "../infra/clawdbot-root.js";
import { runCommandWithTimeout } from "../process/exec.js";
import type { RuntimeEnv } from "../runtime.js";
import { note } from "../terminal/note.js";
import type { DoctorPrompter } from "./doctor-prompter.js";
export async function maybeRepairUiProtocolFreshness(
_runtime: RuntimeEnv,
prompter: DoctorPrompter,
) {
const root = await resolveClawdbotPackageRoot({
moduleUrl: import.meta.url,
argv1: process.argv[1],
cwd: process.cwd(),
});
if (!root) return;
const schemaPath = path.join(root, "src/gateway/protocol/schema.ts");
const uiIndexPath = path.join(root, "dist/control-ui/index.html");
try {
const [schemaStats, uiStats] = await Promise.all([
fs.stat(schemaPath).catch(() => null),
fs.stat(uiIndexPath).catch(() => null),
]);
if (schemaStats && !uiStats) {
note(["- Control UI assets are missing.", "- Run: pnpm ui:build"].join("\n"), "UI");
// In slim/docker environments we may not have the UI source tree. Trying
// to build would fail (and spam logs), so skip the interactive repair.
const uiSourcesPath = path.join(root, "ui/package.json");
const uiSourcesExist = await fs.stat(uiSourcesPath).catch(() => null);
if (!uiSourcesExist) {
note("Skipping UI build: ui/ sources not present.", "UI");
return;
}
const shouldRepair = await prompter.confirmRepair({
message: "Build Control UI assets now?",
initialValue: true,
});
if (shouldRepair) {
note("Building Control UI assets... (this may take a moment)", "UI");
const uiScriptPath = path.join(root, "scripts/ui.js");
const buildResult = await runCommandWithTimeout([process.execPath, uiScriptPath, "build"], {
cwd: root,
timeoutMs: 120_000,
env: { ...process.env, FORCE_COLOR: "1" },
});
if (buildResult.code === 0) {
note("UI build complete.", "UI");
} else {
const details = [
`UI build failed (exit ${buildResult.code ?? "unknown"}).`,
buildResult.stderr.trim() ? buildResult.stderr.trim() : null,
]
.filter(Boolean)
.join("\n");
note(details, "UI");
}
}
return;
}
if (!schemaStats || !uiStats) return;
if (schemaStats.mtime > uiStats.mtime) {
const uiMtimeIso = uiStats.mtime.toISOString();
// Find changes since the UI build
const gitLog = await runCommandWithTimeout(
[
"git",
"-C",
root,
"log",
`--since=${uiMtimeIso}`,
"--format=%h %s",
"src/gateway/protocol/schema.ts",
],
{ timeoutMs: 5000 },
).catch(() => null);
if (gitLog && gitLog.code === 0 && gitLog.stdout.trim()) {
note(
`UI assets are older than the protocol schema.\nFunctional changes since last build:\n${gitLog.stdout
.trim()
.split("\n")
.map((l) => `- ${l}`)
.join("\n")}`,
"UI Freshness",
);
const shouldRepair = await prompter.confirmAggressive({
message: "Rebuild UI now? (Detected protocol mismatch requiring update)",
initialValue: true,
});
if (shouldRepair) {
const uiSourcesPath = path.join(root, "ui/package.json");
const uiSourcesExist = await fs.stat(uiSourcesPath).catch(() => null);
if (!uiSourcesExist) {
note("Skipping UI rebuild: ui/ sources not present.", "UI");
return;
}
note("Rebuilding stale UI assets... (this may take a moment)", "UI");
// Use scripts/ui.js to build, assuming node is available as we are running in it.
// We use the same node executable to run the script.
const uiScriptPath = path.join(root, "scripts/ui.js");
const buildResult = await runCommandWithTimeout(
[process.execPath, uiScriptPath, "build"],
{
cwd: root,
timeoutMs: 120_000,
env: { ...process.env, FORCE_COLOR: "1" },
},
);
if (buildResult.code === 0) {
note("UI rebuild complete.", "UI");
} else {
const details = [
`UI rebuild failed (exit ${buildResult.code ?? "unknown"}).`,
buildResult.stderr.trim() ? buildResult.stderr.trim() : null,
]
.filter(Boolean)
.join("\n");
note(details, "UI");
}
}
}
}
} catch {
// If files don't exist, we can't check.
// If git fails, we silently skip.
// runtime.debug(`UI freshness check failed: ${String(err)}`);
}
}