489 lines
21 KiB
TypeScript
489 lines
21 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import { extname } from "node:path";
|
|
import { runCommandObserved, type CommandProgress } from "./command";
|
|
import { type UniDeskConfig, repoRoot, rootPath } from "./config";
|
|
import { composeConfig } from "./docker";
|
|
import { compactD601RecoveryGuardrails, runD601RecoveryGuardrails } from "./recovery-guardrails";
|
|
|
|
interface CheckItem {
|
|
name: string;
|
|
ok: boolean;
|
|
detail: unknown;
|
|
}
|
|
|
|
const syntaxFiles = [
|
|
"scripts/cli.ts",
|
|
"scripts/playwright-cli.ts",
|
|
"scripts/src/playwright-cli.ts",
|
|
"scripts/src/check.ts",
|
|
"scripts/src/artifact-registry.ts",
|
|
"scripts/src/auth-broker.ts",
|
|
"scripts/src/code-queue.ts",
|
|
"scripts/src/code-queue-execution-plane.ts",
|
|
"scripts/src/command.ts",
|
|
"scripts/src/d601-k3s-guard.ts",
|
|
"scripts/src/hwlab-cd.ts",
|
|
"scripts/src/decision-center.ts",
|
|
"scripts/src/dev-env.ts",
|
|
"scripts/src/deploy.ts",
|
|
"scripts/src/docker.ts",
|
|
"scripts/src/e2e.ts",
|
|
"scripts/src/help.ts",
|
|
"scripts/src/health.ts",
|
|
"scripts/src/commander.ts",
|
|
"scripts/src/platform-db.ts",
|
|
"scripts/src/recovery-guardrails.ts",
|
|
"scripts/src/server-cleanup.ts",
|
|
"scripts/src/remote.ts",
|
|
"scripts/code-queue-cli-steer-test.ts",
|
|
"src/components/frontend/src/index.ts",
|
|
"src/components/frontend/src/app.tsx",
|
|
"src/components/frontend/src/decision-center.tsx",
|
|
"src/components/provider-gateway/src/index.ts",
|
|
"src/components/microservices/oa-event-flow/src/index.ts",
|
|
"src/components/microservices/baidu-netdisk/src/index.ts",
|
|
"src/components/microservices/k3sctl-adapter/src/index.ts",
|
|
"src/components/microservices/mdtodo/src/index.ts",
|
|
"src/components/microservices/decision-center/src/index.ts",
|
|
"src/components/microservices/code-queue-mgr/src/index.ts",
|
|
"src/components/microservices/code-agent-sandbox/src/index.ts",
|
|
"src/components/microservices/host-codex-commander/src/index.ts",
|
|
"src/components/microservices/host-codex-commander/src/contract.ts",
|
|
"src/components/microservices/host-codex-commander/src/redaction.ts",
|
|
"src/components/microservices/host-codex-commander/src/state.ts",
|
|
];
|
|
|
|
export interface CheckOptions {
|
|
full: boolean;
|
|
files: boolean;
|
|
scriptsTypecheck: boolean;
|
|
components: boolean;
|
|
compose: boolean;
|
|
logs: boolean;
|
|
recoveryGuardrails: boolean;
|
|
rust: boolean;
|
|
scriptsTypecheckTimeoutMs: number;
|
|
checkHeartbeatMs: number;
|
|
}
|
|
|
|
const defaultScriptsTypecheckTimeoutMs = 120_000;
|
|
const defaultCheckHeartbeatMs = 15_000;
|
|
|
|
const defaultCheckOptions: CheckOptions = {
|
|
full: false,
|
|
files: false,
|
|
scriptsTypecheck: false,
|
|
components: false,
|
|
compose: false,
|
|
logs: false,
|
|
recoveryGuardrails: false,
|
|
rust: false,
|
|
scriptsTypecheckTimeoutMs: defaultScriptsTypecheckTimeoutMs,
|
|
checkHeartbeatMs: defaultCheckHeartbeatMs,
|
|
};
|
|
|
|
interface CheckRunResult {
|
|
ok: boolean;
|
|
mode: string;
|
|
options: CheckOptions;
|
|
summary: { total: number; passed: number; failed: number; failedItems: string[] };
|
|
items: CheckItem[];
|
|
}
|
|
|
|
export function checkHelp(): Record<string, unknown> {
|
|
return {
|
|
ok: true,
|
|
command: "check",
|
|
usage: [
|
|
"bun scripts/cli.ts check [--syntax-only|--full|--files|--scripts-typecheck|--scripts-typecheck-timeout-ms N|--check-heartbeat-ms N|--components|--compose|--logs|--recovery-guardrails|--rust]",
|
|
"bun scripts/cli.ts check recovery-guardrails",
|
|
],
|
|
defaultMode: "syntax/config only; Rust is never compiled on the master server by default",
|
|
options: [
|
|
{ name: "--syntax-only|--basic", description: "Run only config validation, Bun version and TypeScript syntax transpile." },
|
|
{ name: "--full", description: "Enable all non-Rust checks." },
|
|
{ name: "--files", description: "Verify required entrypoint files, including backend-core Cargo files." },
|
|
{ name: "--scripts-typecheck", description: "Run scripts TypeScript typecheck through the observed checker." },
|
|
{ name: "--scripts-typecheck-timeout-ms N", description: `Bound scripts TypeScript typecheck duration; default ${defaultScriptsTypecheckTimeoutMs}.` },
|
|
{ name: "--check-heartbeat-ms N", description: `Emit unidesk.check.progress JSON lines for running command checks; default ${defaultCheckHeartbeatMs}.` },
|
|
{ name: "--components", description: "Run component TypeScript typecheck." },
|
|
{ name: "--compose", description: "Render Docker Compose config." },
|
|
{ name: "--logs", description: "Check unified log rotation policy." },
|
|
{ name: "--recovery-guardrails", description: "Run D601 k3s/Code Queue reboot recovery diagnostics in read-only mode." },
|
|
{ name: "--rust", description: "Run cargo check only when UNIDESK_D601_RUST_CHECK=1 or UNIDESK_NATIVE_K3S_RUST_CHECK=1 is set inside an approved native k3s CI/dev execution." },
|
|
],
|
|
rustBoundary: {
|
|
masterServer: "do not run cargo check/build here",
|
|
nativeK3sCi: "use deploy apply --env dev --service backend-core and CI with UNIDESK_NATIVE_K3S_RUST_CHECK=1",
|
|
},
|
|
recoveryGuardrailsBoundary: {
|
|
command: "bun scripts/cli.ts check recovery-guardrails",
|
|
mutation: false,
|
|
forbidden: ["restart k3s", "delete CRI sandboxes or pods", "modify hostPath directories", "deploy/rollout", "destructive prune/reset"],
|
|
},
|
|
};
|
|
}
|
|
|
|
export function parseCheckOptions(args: string[]): CheckOptions {
|
|
const options = { ...defaultCheckOptions };
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const arg = args[index];
|
|
if (arg === "--full") {
|
|
options.full = true;
|
|
options.files = true;
|
|
options.scriptsTypecheck = true;
|
|
options.components = true;
|
|
options.compose = true;
|
|
options.logs = true;
|
|
options.recoveryGuardrails = true;
|
|
} else if (arg === "--scripts-typecheck-timeout-ms") {
|
|
options.scriptsTypecheckTimeoutMs = positiveIntegerOption(arg, args[index + 1], 600_000);
|
|
index += 1;
|
|
} else if (arg === "--check-heartbeat-ms") {
|
|
options.checkHeartbeatMs = positiveIntegerOption(arg, args[index + 1], 60_000);
|
|
index += 1;
|
|
} else if (arg === "--files") {
|
|
options.files = true;
|
|
} else if (arg === "--scripts-typecheck") {
|
|
options.scriptsTypecheck = true;
|
|
} else if (arg === "--components") {
|
|
options.components = true;
|
|
} else if (arg === "--compose") {
|
|
options.compose = true;
|
|
} else if (arg === "--logs") {
|
|
options.logs = true;
|
|
} else if (arg === "--recovery-guardrails") {
|
|
options.recoveryGuardrails = true;
|
|
} else if (arg === "--rust") {
|
|
options.rust = true;
|
|
} else if (arg === "--basic" || arg === "--syntax-only") {
|
|
Object.assign(options, defaultCheckOptions);
|
|
} else {
|
|
throw new Error(`unknown check option: ${arg}`);
|
|
}
|
|
}
|
|
return options;
|
|
}
|
|
|
|
function positiveIntegerOption(name: string, raw: string | undefined, max: number): number {
|
|
const value = Number(raw);
|
|
if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
|
|
return Math.min(value, max);
|
|
}
|
|
|
|
function fileItem(path: string): CheckItem {
|
|
const absolute = rootPath(path);
|
|
return { name: `file:${path}`, ok: existsSync(absolute), detail: absolute };
|
|
}
|
|
|
|
function emitCheckProgress(detail: Record<string, unknown>): void {
|
|
console.error(JSON.stringify({ event: "unidesk.check.progress", ...detail }));
|
|
}
|
|
|
|
async function commandItem(name: string, command: string[], timeoutMs = 30_000, env: NodeJS.ProcessEnv = process.env, heartbeatMs = defaultCheckHeartbeatMs): Promise<CheckItem> {
|
|
const startedAt = Date.now();
|
|
emitCheckProgress({ phase: "started", name, command, timeoutMs, heartbeatMs });
|
|
const result = await runCommandObserved(command, repoRoot, {
|
|
timeoutMs,
|
|
env,
|
|
heartbeatMs,
|
|
onProgress: (progress) => emitCommandHeartbeat(name, command, progress),
|
|
});
|
|
const durationMs = result.durationMs ?? Date.now() - startedAt;
|
|
emitCheckProgress({
|
|
phase: result.timedOut ? "timed-out" : result.exitCode === 0 ? "succeeded" : "failed",
|
|
name,
|
|
durationMs,
|
|
exitCode: result.exitCode,
|
|
signal: result.signal,
|
|
timedOut: result.timedOut,
|
|
});
|
|
return {
|
|
name,
|
|
ok: result.exitCode === 0,
|
|
detail: {
|
|
command,
|
|
durationMs,
|
|
exitCode: result.exitCode,
|
|
signal: result.signal,
|
|
timedOut: result.timedOut,
|
|
timeoutMs,
|
|
heartbeatMs,
|
|
stdoutBytes: result.stdoutBytes,
|
|
stderrBytes: result.stderrBytes,
|
|
stdoutTruncated: result.stdoutTruncated,
|
|
stderrTruncated: result.stderrTruncated,
|
|
stdoutTail: result.stdout.slice(-4000),
|
|
stderrTail: result.stderr.slice(-4000),
|
|
},
|
|
};
|
|
}
|
|
|
|
function emitCommandHeartbeat(name: string, command: string[], progress: CommandProgress): void {
|
|
emitCheckProgress({
|
|
phase: "running",
|
|
name,
|
|
command,
|
|
elapsedMs: progress.elapsedMs,
|
|
timeoutMs: progress.timeoutMs,
|
|
pid: progress.pid,
|
|
stdoutBytes: progress.stdoutBytes,
|
|
stderrBytes: progress.stderrBytes,
|
|
lastOutputAgeMs: progress.lastOutputAgeMs,
|
|
});
|
|
}
|
|
|
|
function syntaxItem(): CheckItem {
|
|
const failures: Array<{ path: string; error: string }> = [];
|
|
const checked: string[] = [];
|
|
const ts = new Bun.Transpiler({ loader: "ts" });
|
|
const tsx = new Bun.Transpiler({ loader: "tsx" });
|
|
for (const path of syntaxFiles) {
|
|
const absolute = rootPath(path);
|
|
try {
|
|
const source = readFileSync(absolute, "utf8");
|
|
const loader = extname(path) === ".tsx" ? tsx : ts;
|
|
loader.transformSync(source);
|
|
checked.push(path);
|
|
} catch (error) {
|
|
failures.push({ path, error: error instanceof Error ? error.message : String(error) });
|
|
}
|
|
}
|
|
return {
|
|
name: "syntax:transpile",
|
|
ok: failures.length === 0,
|
|
detail: { checked, failures },
|
|
};
|
|
}
|
|
|
|
function unifiedLogRotationItem(): CheckItem {
|
|
const serviceFiles = [
|
|
"src/components/frontend/src/index.ts",
|
|
"src/components/provider-gateway/src/index.ts",
|
|
"src/components/microservices/code-queue-mgr/src/index.ts",
|
|
"src/components/microservices/code-queue/src/index.ts",
|
|
"src/components/microservices/k3sctl-adapter/src/index.ts",
|
|
"src/components/microservices/mdtodo/src/index.ts",
|
|
"src/components/microservices/project-manager/src/index.ts",
|
|
"src/components/microservices/baidu-netdisk/src/index.ts",
|
|
"src/components/microservices/oa-event-flow/src/index.ts",
|
|
"src/components/microservices/decision-center/src/index.ts",
|
|
"src/components/microservices/code-agent-sandbox/src/index.ts",
|
|
"src/components/microservices/host-codex-commander/src/index.ts",
|
|
];
|
|
const offenders = serviceFiles.flatMap((path) => {
|
|
const text = readFileSync(rootPath(path), "utf8");
|
|
const directLogAppend = /appendFileSync\(\s*(?:config\.)?logFile\b/u.test(text) || /appendFileSync\(\s*process\.env\.LOG_FILE\b/u.test(text);
|
|
const missingWriter = !text.includes("createHourlyJsonlWriter");
|
|
return directLogAppend || missingWriter ? [{ path, directLogAppend, missingWriter }] : [];
|
|
});
|
|
const backendLogger = readFileSync(rootPath("src/components/backend-core/src/logger.rs"), "utf8");
|
|
const backendMissingRotation = !backendLogger.includes("current_path") || !backendLogger.includes("prune");
|
|
const backendDirectUnboundedAppend = backendLogger.includes("appendFileSync");
|
|
if (backendMissingRotation || backendDirectUnboundedAppend) {
|
|
offenders.push({ path: "src/components/backend-core/src/logger.rs", directLogAppend: backendDirectUnboundedAppend, missingWriter: backendMissingRotation });
|
|
}
|
|
return {
|
|
name: "logs:unified-hourly-rotation",
|
|
ok: offenders.length === 0,
|
|
detail: {
|
|
sharedWriter: "src/components/shared/src/rotating-jsonl.ts",
|
|
checkedFiles: ["src/components/backend-core/src/logger.rs", ...serviceFiles],
|
|
offenders,
|
|
},
|
|
};
|
|
}
|
|
|
|
function extractComposeServiceBlock(composeText: string, serviceName: string): string {
|
|
const lines = composeText.split("\n");
|
|
const startLine = lines.findIndex((line) => line === ` ${serviceName}:`);
|
|
if (startLine < 0) return "";
|
|
let endLine = lines.length;
|
|
for (let index = startLine + 1; index < lines.length; index += 1) {
|
|
if (/^ [A-Za-z0-9][A-Za-z0-9_-]*:$/u.test(lines[index])) {
|
|
endLine = index;
|
|
break;
|
|
}
|
|
}
|
|
return lines.slice(startLine, endLine).join("\n");
|
|
}
|
|
|
|
function codeQueueMgrHealthcheckItem(): CheckItem {
|
|
const composeText = readFileSync(rootPath("docker-compose.yml"), "utf8");
|
|
const serviceBlock = extractComposeServiceBlock(composeText, "code-queue-mgr");
|
|
const dockerfileText = readFileSync(rootPath("src/components/microservices/code-queue-mgr/Dockerfile"), "utf8");
|
|
const sourceText = readFileSync(rootPath("src/components/microservices/code-queue-mgr/src-rs/main.rs"), "utf8");
|
|
const healthcheckUsesRustProbe = serviceBlock.includes("code-queue-mgr") && serviceBlock.includes("--healthcheck");
|
|
const healthcheckReferencesBun = /\bhealthcheck:[\s\S]*?\bbun\b/u.test(serviceBlock);
|
|
const binaryCopiedIntoRuntime = dockerfileText.includes("/usr/local/bin/code-queue-mgr");
|
|
const binaryImplementsProbe = sourceText.includes("--healthcheck") && sourceText.includes("run_healthcheck");
|
|
return {
|
|
name: "docker-compose:code-queue-mgr-rust-healthcheck",
|
|
ok: healthcheckUsesRustProbe && !healthcheckReferencesBun && binaryCopiedIntoRuntime && binaryImplementsProbe,
|
|
detail: {
|
|
healthcheckUsesRustProbe,
|
|
healthcheckReferencesBun,
|
|
binaryCopiedIntoRuntime,
|
|
binaryImplementsProbe,
|
|
expected: "code-queue-mgr Rust runtime healthcheck must use code-queue-mgr --healthcheck and must not depend on bun/curl/wget being present.",
|
|
},
|
|
};
|
|
}
|
|
|
|
function skippedRustCheckItem(): CheckItem {
|
|
return {
|
|
name: "rust:backend-core",
|
|
ok: false,
|
|
detail: {
|
|
skipped: true,
|
|
reason: "Rust compilation is intentionally not allowed on the master server; run it from an approved native k3s CI/dev execution plane.",
|
|
enableOnNativeK3s: "UNIDESK_NATIVE_K3S_RUST_CHECK=1 bun scripts/cli.ts check --rust",
|
|
legacyEnableOnD601: "UNIDESK_D601_RUST_CHECK=1 bun scripts/cli.ts check --rust",
|
|
deployPath: "bun scripts/cli.ts deploy apply --env dev --service backend-core",
|
|
},
|
|
};
|
|
}
|
|
|
|
async function runRustCheckItem(heartbeatMs: number): Promise<CheckItem> {
|
|
const rustCheckAllowed = process.env.UNIDESK_D601_RUST_CHECK === "1" || process.env.UNIDESK_NATIVE_K3S_RUST_CHECK === "1";
|
|
if (!rustCheckAllowed) return skippedRustCheckItem();
|
|
const envPath = process.env.HOME ? `${process.env.HOME}/.cargo/bin:${process.env.PATH ?? ""}` : process.env.PATH;
|
|
const env = envPath ? { ...process.env, PATH: envPath } : process.env;
|
|
return commandItem("rust:backend-core", ["cargo", "check", "--manifest-path", "src/components/backend-core/Cargo.toml"], 180_000, env, heartbeatMs);
|
|
}
|
|
|
|
function skippedItem(name: string, reason: string, enableWith: string): CheckItem {
|
|
return { name, ok: true, detail: { skipped: true, reason, enableWith } };
|
|
}
|
|
|
|
export function runRecoveryGuardrailsCheck(config: UniDeskConfig): ReturnType<typeof compactD601RecoveryGuardrails> {
|
|
return compactD601RecoveryGuardrails(runD601RecoveryGuardrails(config));
|
|
}
|
|
|
|
export async function runChecks(config: UniDeskConfig, options: CheckOptions = defaultCheckOptions): Promise<CheckRunResult> {
|
|
const items: CheckItem[] = [
|
|
{ name: "config:validated", ok: true, detail: { project: config.project.name, runtime: config.runtime } },
|
|
];
|
|
items.push(await commandItem("bun:version", ["bun", "--version"], 30_000, process.env, options.checkHeartbeatMs));
|
|
items.push(syntaxItem());
|
|
items.push(codeQueueMgrHealthcheckItem());
|
|
if (options.files) {
|
|
items.push(
|
|
fileItem("scripts/cli.ts"),
|
|
fileItem("scripts/playwright-cli.ts"),
|
|
fileItem("scripts/src/playwright-cli.ts"),
|
|
fileItem("AGENTS.md"),
|
|
fileItem("TEST.md"),
|
|
fileItem("docs/reference/artifact-registry.md"),
|
|
fileItem("docs/reference/auth-broker.md"),
|
|
fileItem("docker-compose.yml"),
|
|
fileItem("src/components/backend-core/Cargo.toml"),
|
|
fileItem("src/components/backend-core/Cargo.lock"),
|
|
fileItem("src/components/backend-core/src/main.rs"),
|
|
fileItem("src/components/backend-core/src/http.rs"),
|
|
fileItem("src/components/frontend/src/index.ts"),
|
|
fileItem("src/components/provider-gateway/src/index.ts"),
|
|
fileItem("src/components/microservices/oa-event-flow/src/index.ts"),
|
|
fileItem("src/components/microservices/k3sctl-adapter/src/index.ts"),
|
|
fileItem("src/components/microservices/mdtodo/src/index.ts"),
|
|
fileItem("src/components/microservices/decision-center/src/index.ts"),
|
|
fileItem("src/components/microservices/code-queue-mgr/src/index.ts"),
|
|
fileItem("src/components/microservices/code-agent-sandbox/src/index.ts"),
|
|
fileItem("src/components/microservices/host-codex-commander/package.json"),
|
|
fileItem("src/components/microservices/host-codex-commander/tsconfig.json"),
|
|
fileItem("src/components/microservices/host-codex-commander/Dockerfile"),
|
|
fileItem("src/components/microservices/host-codex-commander/src/index.ts"),
|
|
fileItem("src/components/microservices/host-codex-commander/src/contract.ts"),
|
|
fileItem("src/components/microservices/host-codex-commander/src/redaction.ts"),
|
|
fileItem("src/components/microservices/host-codex-commander/src/state.ts"),
|
|
fileItem("src/components/microservices/code-queue-mgr/src/prompt-observation.ts"),
|
|
fileItem("scripts/src/deploy.ts"),
|
|
fileItem("scripts/code-queue-issue3-regression-test.ts"),
|
|
fileItem("scripts/code-queue-liveness-diagnostics-test.ts"),
|
|
fileItem("scripts/src/code-queue-liveness-fixtures.ts"),
|
|
fileItem("scripts/code-queue-cli-steer-test.ts"),
|
|
fileItem("scripts/src/provider-triage.ts"),
|
|
fileItem("src/components/microservices/code-queue/src/runner-error-classifier.ts"),
|
|
fileItem("scripts/src/ci.ts"),
|
|
fileItem("scripts/src/e2e.ts"),
|
|
fileItem("scripts/code-queue-prompt-observation-test.ts"),
|
|
fileItem("scripts/code-queue-pr-preflight-example.ts"),
|
|
fileItem("scripts/src/artifact-registry.ts"),
|
|
fileItem("scripts/src/server-cleanup.ts"),
|
|
fileItem("scripts/src/recovery-guardrails.ts"),
|
|
fileItem("scripts/src/auth-broker.ts"),
|
|
fileItem("src/components/microservices/auth-broker/Cargo.toml"),
|
|
fileItem("src/components/microservices/auth-broker/Dockerfile"),
|
|
fileItem("src/components/microservices/auth-broker/src/main.rs"),
|
|
fileItem("scripts/artifact-consumer-dry-run-matrix-test.ts"),
|
|
fileItem("src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.yaml"),
|
|
fileItem("src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.g14.yaml"),
|
|
fileItem("src/components/microservices/k3sctl-adapter/k3s/code-queue.g14.k3s.json"),
|
|
fileItem("src/components/microservices/k3sctl-adapter/k3s/code-queue.g14.k8s.yaml"),
|
|
);
|
|
} else {
|
|
items.push(skippedItem("files:required-entrypoints", "required file presence scan is opt-in", "--files or --full"));
|
|
}
|
|
if (options.scriptsTypecheck) {
|
|
items.push(await commandItem("typescript:scripts", ["bun", "--bun", "tsc", "-p", "scripts/tsconfig.json", "--noEmit", "--pretty", "false"], options.scriptsTypecheckTimeoutMs, process.env, options.checkHeartbeatMs));
|
|
} else {
|
|
items.push(skippedItem("typescript:scripts", "scripts TypeScript typecheck is opt-in", "--scripts-typecheck or --full"));
|
|
}
|
|
if (options.logs) {
|
|
items.push(unifiedLogRotationItem());
|
|
} else {
|
|
items.push(skippedItem("logs:unified-hourly-rotation", "policy scan is opt-in", "--logs or --full"));
|
|
}
|
|
if (options.recoveryGuardrails) {
|
|
const recovery = runRecoveryGuardrailsCheck(config);
|
|
items.push({
|
|
name: "d601:recovery-guardrails",
|
|
ok: recovery.ok,
|
|
detail: recovery,
|
|
});
|
|
} else {
|
|
items.push(skippedItem("d601:recovery-guardrails", "D601 reboot recovery diagnostics are opt-in and read-only", "--recovery-guardrails or --full"));
|
|
}
|
|
if (options.components) {
|
|
items.push(await commandItem("typescript:components", ["bunx", "tsc", "-p", "src/tsconfig.check.json", "--pretty", "false"], 180_000, process.env, options.checkHeartbeatMs));
|
|
} else {
|
|
items.push(skippedItem("typescript:components", "component TypeScript check is opt-in", "--components or --full"));
|
|
}
|
|
if (options.compose) {
|
|
const compose = composeConfig(config);
|
|
items.push({
|
|
name: "docker-compose:config",
|
|
ok: compose.result.exitCode === 0,
|
|
detail: {
|
|
command: compose.command,
|
|
exitCode: compose.result.exitCode,
|
|
signal: compose.result.signal,
|
|
timedOut: compose.result.timedOut,
|
|
stdoutTail: compose.result.stdout.slice(-4000),
|
|
stderrTail: compose.result.stderr.slice(-4000),
|
|
runtimeEnv: compose.runtimeEnv,
|
|
},
|
|
});
|
|
} else {
|
|
items.push(skippedItem("docker-compose:config", "Docker Compose config rendering is opt-in", "--compose or --full"));
|
|
}
|
|
if (options.rust) {
|
|
items.push(await runRustCheckItem(options.checkHeartbeatMs));
|
|
} else {
|
|
items.push(skippedItem("rust:backend-core", "Rust check/build must run through an approved native k3s CI artifact publication, not on the master server or CD runtime target", "--rust inside native k3s CI with UNIDESK_NATIVE_K3S_RUST_CHECK=1"));
|
|
}
|
|
const failedItems = items.filter((item) => !item.ok).map((item) => item.name);
|
|
return {
|
|
ok: failedItems.length === 0,
|
|
mode: options.full ? "full" : "basic",
|
|
options,
|
|
summary: {
|
|
total: items.length,
|
|
passed: items.length - failedItems.length,
|
|
failed: failedItems.length,
|
|
failedItems,
|
|
},
|
|
items,
|
|
};
|
|
}
|