feat(cli): migrate child json recovery callers
This commit is contained in:
@@ -10,6 +10,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
||||
import { dirname, join } from "node:path";
|
||||
import { repoRoot, rootPath, type Config } from "../config";
|
||||
import { runCommand, type CommandResult } from "../command";
|
||||
import { resolveCliChildJsonCommandResult } from "../cli-child-json-recovery";
|
||||
import { startJob } from "../jobs";
|
||||
import { classifySshTcpPoolFailure } from "../ssh";
|
||||
import { HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, hwlabNodeControlPlaneInfraHelp, runHwlabNodeControlPlaneInfra } from "../hwlab-node-control-plane";
|
||||
@@ -31,7 +32,7 @@ import type { RenderedCliResult } from "../output";
|
||||
import type { BootstrapAdminPasswordMaterial, NodeWebProbeScriptOptions, RuntimeSecretSpec } from "./entry";
|
||||
import type { NodeWebProbeHostProxyEnv } from "./web-probe-observe";
|
||||
import { isSafeWebProbeScriptArtifactPath, isSafeWebProbeScriptReportPath, isSafeWebProbeScriptRunDir, runTransWorkspaceStdinScript } from "./public-exposure";
|
||||
import { compactCommandResultRedacted, nullableRecord, parseJsonObject, redactKnownSecrets, shellQuote } from "./utils";
|
||||
import { compactCommandResultRedacted, nullableRecord, redactKnownSecrets, shellQuote } from "./utils";
|
||||
import { renderWebProbeScriptResult } from "./web-observe-render";
|
||||
import { nodeWebProbeHostProxyEnv } from "./web-probe-observe";
|
||||
|
||||
@@ -481,11 +482,26 @@ export function runNodeWebProbeScript(
|
||||
const script = nodeWebProbeScriptRemoteShell(options, secretSpec, material.username ?? secretSpec.bootstrapAdminUsername, material.password ?? "", webProbeProxy, spec.webProbe?.playwrightBrowsersPath);
|
||||
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
|
||||
const commandTimedOut = result.timedOut || result.exitCode === 124;
|
||||
const stdoutReport = parseJsonObject(result.stdout);
|
||||
const runPaths = webProbeScriptRunPathsFromStderr(result.stderr);
|
||||
const recoveredReport = stdoutReport === null ? readNodeWebProbeScriptReport(options, spec, runPaths.reportPath) : null;
|
||||
const recoveredArtifacts = stdoutReport === null || commandTimedOut ? readNodeWebProbeScriptArtifacts(options, spec, runPaths.runDir) : null;
|
||||
const parsedReport = stdoutReport ?? recoveredReport?.report ?? null;
|
||||
let recoveredReport: WebProbeScriptReportReadResult | null = null;
|
||||
const stdoutResolution = resolveCliChildJsonCommandResult({
|
||||
result,
|
||||
requestedStdoutType: "web-probe script report JSON",
|
||||
acceptParsed: isWebProbeScriptReportContract,
|
||||
forceArtifactFallbackReason: commandTimedOut ? "remote-command-timeout" : null,
|
||||
artifactFallback: {
|
||||
path: runPaths.reportPath,
|
||||
nextCommand: runPaths.reportPath === null ? null : `trans ${options.node}:${spec.workspace} cat ${shellQuote(runPaths.reportPath)}`,
|
||||
read: () => {
|
||||
recoveredReport = readNodeWebProbeScriptReport(options, spec, runPaths.reportPath);
|
||||
if (recoveredReport?.report) return { ok: true, value: recoveredReport.report, path: recoveredReport.path, reason: recoveredReport.source };
|
||||
return { ok: false, reason: recoveredReport?.degradedReason ?? "web-probe-report-artifact-missing", path: runPaths.reportPath };
|
||||
},
|
||||
},
|
||||
});
|
||||
const stdoutReport = stdoutResolution.source === "stdout" || stdoutResolution.source === "dump" ? stdoutResolution.parsed : null;
|
||||
const recoveredArtifacts = stdoutResolution.parsed === null || commandTimedOut ? readNodeWebProbeScriptArtifacts(options, spec, runPaths.runDir) : null;
|
||||
const parsedReport = stdoutResolution.parsed;
|
||||
const report = compactWebProbeScriptResult(parsedReport);
|
||||
const passed = result.exitCode === 0 && report?.ok === true;
|
||||
const summary = nullableRecord(report?.summary);
|
||||
@@ -493,9 +509,10 @@ export function runNodeWebProbeScript(
|
||||
const outputFailureKind = parsedReport === null
|
||||
? commandTimedOut
|
||||
? "web-probe-command-timeout"
|
||||
: stdoutBytes > 64 * 1024
|
||||
? "web-probe-output-too-large"
|
||||
: "web-probe-report-parse-failed"
|
||||
: stringOrNullValue(stdoutResolution.diagnostics.fallbackReason)
|
||||
?? (stdoutBytes > 64 * 1024
|
||||
? "web-probe-output-too-large"
|
||||
: "web-probe-report-parse-failed")
|
||||
: null;
|
||||
const degradedReason = commandTimedOut
|
||||
? "web-probe-command-timeout"
|
||||
@@ -518,7 +535,8 @@ export function runNodeWebProbeScript(
|
||||
const effectiveSummary = summary !== null ? {
|
||||
...summary,
|
||||
transportTimedOut: commandTimedOut,
|
||||
recoveredFrom: stdoutReport !== null ? "stdout" : recoveredReport?.source ?? null,
|
||||
recoveredFrom: stdoutReport !== null ? stdoutResolution.source : recoveredReport?.source ?? null,
|
||||
stdoutRecovery: stdoutResolution.diagnostics,
|
||||
} : (outputFailureKind === null ? null : {
|
||||
ok: false,
|
||||
status: "blocked",
|
||||
@@ -539,8 +557,9 @@ export function runNodeWebProbeScript(
|
||||
},
|
||||
screenshots: recoveredArtifacts?.screenshots ?? [],
|
||||
artifacts: recoveredArtifacts?.artifacts ?? null,
|
||||
stdoutBytes,
|
||||
exitCode: result.exitCode,
|
||||
stdoutBytes,
|
||||
stdoutRecovery: stdoutResolution.diagnostics,
|
||||
exitCode: result.exitCode,
|
||||
stderrTail: result.stderr.trim().slice(-2000),
|
||||
valuesRedacted: true,
|
||||
});
|
||||
@@ -566,12 +585,14 @@ export function runNodeWebProbeScript(
|
||||
summary: effectiveSummary,
|
||||
issueEvidence,
|
||||
probe: report,
|
||||
reportLoad: stdoutReport !== null ? { source: "stdout", path: report?.reportPath ?? null, degradedReason: null } : recoveredReport === null ? null : {
|
||||
reportLoad: stdoutReport !== null ? { source: stdoutResolution.source, path: report?.reportPath ?? null, degradedReason: null } : recoveredReport === null ? null : {
|
||||
source: recoveredReport.source,
|
||||
path: recoveredReport.path,
|
||||
degradedReason: recoveredReport.degradedReason,
|
||||
result: recoveredReport.result === null ? null : compactCommandResultRedacted(recoveredReport.result, [material.password ?? ""]),
|
||||
stdoutRecovery: recoveredReport.stdoutRecovery ?? null,
|
||||
},
|
||||
stdoutRecovery: stdoutResolution.diagnostics,
|
||||
warnings: webProbeScriptGovernanceWarnings(options),
|
||||
hints: webProbeScriptGovernanceHints(options),
|
||||
preferredCommands: webProbeScriptPreferredCommands(options),
|
||||
@@ -667,11 +688,20 @@ export function lineValueFromText(text: string, name: string): string | null {
|
||||
return match ? match[1].trim() : null;
|
||||
}
|
||||
|
||||
interface WebProbeScriptReportReadResult {
|
||||
source: string;
|
||||
report: Record<string, unknown> | null;
|
||||
result: CommandResult | null;
|
||||
degradedReason: string | null;
|
||||
path: string | null;
|
||||
stdoutRecovery?: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export function readNodeWebProbeScriptReport(
|
||||
options: NodeWebProbeScriptOptions,
|
||||
spec: HwlabRuntimeLaneSpec,
|
||||
reportPath: string | null,
|
||||
): { source: string; report: Record<string, unknown> | null; result: CommandResult | null; degradedReason: string | null; path: string | null } | null {
|
||||
): WebProbeScriptReportReadResult | null {
|
||||
if (!reportPath) return null;
|
||||
if (!isSafeWebProbeScriptReportPath(reportPath)) return { source: "unsafe-path", report: null, result: null, degradedReason: "web-probe-report-path-invalid", path: reportPath };
|
||||
const script = [
|
||||
@@ -753,16 +783,38 @@ export function readNodeWebProbeScriptReport(
|
||||
if (result.exitCode !== 0 || result.timedOut) {
|
||||
return { source: "report-file", report: null, result, degradedReason: result.timedOut ? "web-probe-command-timeout" : "web-probe-report-read-failed", path: reportPath };
|
||||
}
|
||||
const report = parseJsonObject(result.stdout);
|
||||
const reportResolution = resolveCliChildJsonCommandResult({
|
||||
result,
|
||||
requestedStdoutType: "web-probe script report artifact compact JSON",
|
||||
acceptParsed: isWebProbeScriptReportContract,
|
||||
});
|
||||
const report = reportResolution.parsed;
|
||||
return {
|
||||
source: "report-file",
|
||||
report,
|
||||
result,
|
||||
degradedReason: report === null ? "web-probe-report-parse-failed" : null,
|
||||
degradedReason: report === null ? stringOrNullValue(reportResolution.diagnostics.fallbackReason) ?? "web-probe-report-parse-failed" : null,
|
||||
path: reportPath,
|
||||
stdoutRecovery: reportResolution.diagnostics,
|
||||
};
|
||||
}
|
||||
|
||||
function isWebProbeScriptReportContract(value: Record<string, unknown>): boolean {
|
||||
return value.ok === true
|
||||
|| value.ok === false
|
||||
|| typeof value.status === "string"
|
||||
|| typeof value.reportPath === "string"
|
||||
|| typeof value.error === "string"
|
||||
|| typeof value.failureKind === "string"
|
||||
|| Object.keys(nullableRecord(value.summary) ?? {}).length > 0
|
||||
|| Object.keys(nullableRecord(value.script) ?? {}).length > 0
|
||||
|| Array.isArray(value.steps);
|
||||
}
|
||||
|
||||
function stringOrNullValue(value: unknown): string | null {
|
||||
return typeof value === "string" && value.length > 0 ? value : null;
|
||||
}
|
||||
|
||||
export function readNodeWebProbeScriptArtifacts(
|
||||
options: NodeWebProbeScriptOptions,
|
||||
spec: HwlabRuntimeLaneSpec,
|
||||
|
||||
Reference in New Issue
Block a user