fix: 修复 PaC 状态命令空输出
This commit is contained in:
@@ -47,6 +47,8 @@ import { compactAgentRunLaneStatusTarget, compactLaneSecretsStatus } from "./tri
|
||||
import { capture, captureJsonPayload, compactCapture, record, stringOrNull, timedStatusStage } from "./utils";
|
||||
import { yamlLaneK3sSourceStatusScript, yamlLaneRuntimeStatusScript, yamlLaneSourceStatusScript } from "./yaml-lane";
|
||||
|
||||
const agentRunStatusCaptureTimeoutMs = 20_000;
|
||||
|
||||
export function parseSecretSyncOptions(args: string[]): SecretSyncOptions {
|
||||
const base = parseConfirmOptions(args);
|
||||
let node: string | null = null;
|
||||
@@ -409,13 +411,15 @@ function giteaMirrorStatusCommand(spec: AgentRunLaneSpec): string {
|
||||
export async function statusPipelinesAsCodeLane(config: UniDeskConfig, options: StatusOptions, target: { configPath: string; spec: AgentRunLaneSpec }, authority: Extract<CicdDeliveryAuthority, { kind: "pac-pr-merge" }>): Promise<Record<string, unknown>> {
|
||||
const spec = target.spec;
|
||||
const consumerId = authority.consumer.consumerId;
|
||||
const pacStatus = record(await runPlatformInfraPipelinesAsCodeCommand(config, ["status", "--target", spec.nodeId, "--consumer", consumerId, "--full"]));
|
||||
const pacProbe = await timedStatusStage("pac", () => runPlatformInfraPipelinesAsCodeCommand(config, ["status", "--target", spec.nodeId, "--consumer", consumerId, "--full"]));
|
||||
const pacStatus = record(pacProbe.value);
|
||||
const pacSummary = record(pacStatus.summary);
|
||||
const pacReadObservation = record(pacStatus.observation);
|
||||
const latestPipelineRun = record(pacSummary.latestPipelineRun);
|
||||
const artifact = record(pacSummary.artifact);
|
||||
const pacArgo = record(pacSummary.argo);
|
||||
const pipelineRunName = options.pipelineRun ?? stringOrNull(latestPipelineRun.name);
|
||||
const runtimeProbe = await timedStatusStage("runtime", () => capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneRuntimeStatusScript(spec, pipelineRunName)]));
|
||||
const runtimeProbe = await timedStatusStage("runtime", () => capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneRuntimeStatusScript(spec, pipelineRunName)], { runtimeTimeoutMs: agentRunStatusCaptureTimeoutMs }));
|
||||
const runtimePayload = captureJsonPayload(runtimeProbe.value);
|
||||
const manager = record(runtimePayload.manager);
|
||||
const database = record(runtimePayload.database);
|
||||
@@ -479,6 +483,7 @@ export async function statusPipelinesAsCodeLane(config: UniDeskConfig, options:
|
||||
pac: {
|
||||
ready: pacSummary.ready ?? false,
|
||||
webhookCount: pacSummary.webhookCount ?? null,
|
||||
readObservation: pacReadObservation,
|
||||
latestPipelineRun: {
|
||||
name: pipelineRunName,
|
||||
status: latestPipelineRun.status ?? null,
|
||||
@@ -539,8 +544,9 @@ export async function statusPipelinesAsCodeLane(config: UniDeskConfig, options:
|
||||
migration,
|
||||
},
|
||||
timings: {
|
||||
pacMs: pacProbe.elapsedMs,
|
||||
runtimeMs: runtimeProbe.elapsedMs,
|
||||
totalMs: runtimeProbe.elapsedMs,
|
||||
totalMs: pacProbe.elapsedMs + runtimeProbe.elapsedMs,
|
||||
},
|
||||
disclosure: {
|
||||
output: options.full || options.raw ? "full" : "compact-summary",
|
||||
@@ -636,8 +642,8 @@ export async function statusYamlLane(config: UniDeskConfig, options: StatusOptio
|
||||
if (deliveryAuthority.kind === "pac-pr-merge") return await statusPipelinesAsCodeLane(config, options, target, deliveryAuthority);
|
||||
if (deliveryAuthority.kind === "unknown") return unknownDeliveryAuthorityStatus(options, target, deliveryAuthority);
|
||||
const sourceProbe = await timedStatusStage("source", () => spec.source.statusMode === "k3s-git-mirror"
|
||||
? capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneK3sSourceStatusScript(spec)])
|
||||
: capture(config, `${spec.nodeRoute}:${spec.source.workspace}`, ["sh", "--", yamlLaneSourceStatusScript(spec)]));
|
||||
? capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneK3sSourceStatusScript(spec)], { runtimeTimeoutMs: agentRunStatusCaptureTimeoutMs })
|
||||
: capture(config, `${spec.nodeRoute}:${spec.source.workspace}`, ["sh", "--", yamlLaneSourceStatusScript(spec)], { runtimeTimeoutMs: agentRunStatusCaptureTimeoutMs }));
|
||||
const sourcePayload = captureJsonPayload(sourceProbe.value);
|
||||
const branchTipCommit = stringOrNull(sourcePayload.remoteBranchCommit) ?? stringOrNull(sourcePayload.localHead);
|
||||
const initialSourceCommit = options.sourceCommit
|
||||
@@ -645,8 +651,8 @@ export async function statusYamlLane(config: UniDeskConfig, options: StatusOptio
|
||||
?? stringOrNull(sourcePayload.localHead);
|
||||
const pipelineRunName = options.pipelineRun ?? (initialSourceCommit ? agentRunPipelineRunName(spec, initialSourceCommit) : null);
|
||||
const [runtimeProbe, mirrorProbe] = await Promise.all([
|
||||
timedStatusStage("runtime", () => capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneRuntimeStatusScript(spec, pipelineRunName)])),
|
||||
timedStatusStage("git-mirror", () => capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneGitMirrorStatusScript(spec)])),
|
||||
timedStatusStage("runtime", () => capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneRuntimeStatusScript(spec, pipelineRunName)], { runtimeTimeoutMs: agentRunStatusCaptureTimeoutMs })),
|
||||
timedStatusStage("git-mirror", () => capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneGitMirrorStatusScript(spec)], { runtimeTimeoutMs: agentRunStatusCaptureTimeoutMs })),
|
||||
]);
|
||||
const runtimePayload = captureJsonPayload(runtimeProbe.value);
|
||||
const mirrorPayload = captureJsonPayload(mirrorProbe.value);
|
||||
|
||||
@@ -80,6 +80,8 @@ export function renderAgentRunControlPlaneStatusSummary(result: Record<string, u
|
||||
const migration = record(summary.migration);
|
||||
if (migration.migrated === true && migration.sourceAuthority === "gitea-pipelines-as-code") {
|
||||
const pac = record(summary.pac);
|
||||
const pacRead = record(pac.readObservation);
|
||||
const pacFirstBreak = record(pacRead.firstBreak);
|
||||
const latestPipelineRun = record(pac.latestPipelineRun);
|
||||
const artifact = record(pac.artifact);
|
||||
const taskRuns = Array.isArray(pac.taskRuns) ? pac.taskRuns.map(record) : [];
|
||||
@@ -119,6 +121,17 @@ export function renderAgentRunControlPlaneStatusSummary(result: Record<string, u
|
||||
"TASKRUN DURATIONS",
|
||||
taskRows.length === 0 ? "-" : renderTable(["TASKRUN", "STATUS", "REASON", "DURATION_S"], taskRows),
|
||||
"",
|
||||
"PAC READ OBSERVATION",
|
||||
renderTable(["STAGE", "STATUS", "ELAPSED_MS", "TIMEOUT_MS", "EXIT", "FIRST_BREAK"], [[
|
||||
displayValue(pacRead.stage),
|
||||
displayValue(pacRead.status),
|
||||
displayValue(pacRead.elapsedMs),
|
||||
displayValue(pacRead.timeoutMs),
|
||||
displayValue(pacRead.exitCode),
|
||||
displayValue(pacFirstBreak.code),
|
||||
]]),
|
||||
` next-read-only: ${displayValue(pacRead.next)}`,
|
||||
"",
|
||||
warnings.length === 0 ? "WARNINGS\n-" : ["WARNINGS", ...warnings.map((warning) => `- ${warning}`)].join("\n"),
|
||||
"",
|
||||
"NEXT",
|
||||
@@ -127,7 +140,7 @@ export function renderAgentRunControlPlaneStatusSummary(result: Record<string, u
|
||||
` pac: ${displayValue(next.pacStatus ?? disclosure.pacStatusCommand ?? "-")}`,
|
||||
` full: ${displayValue(next.statusFull ?? disclosure.fullCommand ?? "bun scripts/cli.ts agentrun control-plane status --full")}`,
|
||||
"",
|
||||
`TIMINGS runtime=${displayValue(timings.runtimeMs)}ms total=${displayValue(timings.totalMs)}ms`,
|
||||
`TIMINGS pac=${displayValue(timings.pacMs)}ms runtime=${displayValue(timings.runtimeMs)}ms total=${displayValue(timings.totalMs)}ms`,
|
||||
].filter((line): line is string => line !== null);
|
||||
return renderedCliResult(result.ok !== false, "agentrun control-plane status", `${lines.join("\n")}\n`);
|
||||
}
|
||||
|
||||
@@ -137,8 +137,8 @@ export type AgentRunBridgeCaptureResult = SshCaptureResult & { bridgeExecution?:
|
||||
|
||||
export let localBackendCoreStatusCache: LocalBackendCoreStatus | null = null;
|
||||
|
||||
export async function capture(config: UniDeskConfig, target: string, args: string[]): Promise<AgentRunBridgeCaptureResult> {
|
||||
const result = await runSshCommandCapture(config, target, args);
|
||||
export async function capture(config: UniDeskConfig, target: string, args: string[], options: { runtimeTimeoutMs?: number } = {}): Promise<AgentRunBridgeCaptureResult> {
|
||||
const result = await runSshCommandCapture(config, target, args, undefined, options);
|
||||
const plan = agentRunBridgeCapturePlan(config, target);
|
||||
return attachBridgeExecution(result, { ...plan, reason: `unified-ssh-capture:${plan.reason}` });
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { resolve } from "node:path";
|
||||
import { createRequire } from "node:module";
|
||||
import { sentinelPublishImageBuildShell } from "./hwlab-node-web-sentinel-cicd-jobs";
|
||||
import type { SentinelCicdState } from "./hwlab-node-web-sentinel-cicd-shared";
|
||||
import { compactHistoryJson, compactStatusSummary, renderDebugStep, renderHistory, renderStatus } from "./platform-infra-pipelines-as-code";
|
||||
import { compactHistoryJson, compactStatusSummary, pacReadOnlyCaptureObservation, renderDebugStep, renderHistory, renderStatus } from "./platform-infra-pipelines-as-code";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const { parsePacLogRecords } = require("../native/cicd/pac-status-evaluator.cjs") as {
|
||||
@@ -55,6 +55,35 @@ describe("PaC 失败证据合同", () => {
|
||||
expect(compactStatus).toMatchObject({ artifact: { traceId: row.traceId, firstBreak: row.firstBreak, error: row.error } });
|
||||
});
|
||||
|
||||
test("只读 capture timeout 保留阶段、预算、首断点与下一步", () => {
|
||||
const observation = pacReadOnlyCaptureObservation({
|
||||
action: "history",
|
||||
targetId: "NC01",
|
||||
consumerId: "agentrun-nc01-v02",
|
||||
result: { exitCode: 124, stdout: "", stderr: "UNIDESK_SSH_RUNTIME_TIMEOUT" },
|
||||
parsed: null,
|
||||
elapsedMs: 25001,
|
||||
timeoutMs: 25000,
|
||||
next: "bun scripts/cli.ts platform-infra pipelines-as-code history --target NC01 --consumer agentrun-nc01-v02 --limit 5",
|
||||
});
|
||||
expect(observation).toMatchObject({
|
||||
ok: false,
|
||||
status: "timeout",
|
||||
target: "NC01",
|
||||
consumer: "agentrun-nc01-v02",
|
||||
stage: "history-remote-observe",
|
||||
elapsedMs: 25001,
|
||||
timeoutMs: 25000,
|
||||
exitCode: 124,
|
||||
firstBreak: { code: "pac-read-timeout", phase: "remote-read" },
|
||||
});
|
||||
const output = renderHistory({ rows: [], config: {}, historyErrors: [], deliveryBudget: {}, observation, next: {} }).renderedText;
|
||||
expect(output).toContain("READ OBSERVATION");
|
||||
expect(output).toContain("history-remote-observe");
|
||||
expect(output).toContain("pac-read-timeout");
|
||||
expect(output).toContain("next-read-only:");
|
||||
});
|
||||
|
||||
test("feature-config warning 在 status、history、debug-step 同构有界投影", () => {
|
||||
const observation = extractPacSourceObservation([
|
||||
{
|
||||
|
||||
@@ -52,6 +52,7 @@ const remoteScriptFile = rootPath("scripts", "src", "platform-infra-pipelines-as
|
||||
const evaluatorFile = rootPath("scripts", "native", "cicd", "pac-status-evaluator.cjs");
|
||||
const sourceArtifactRuntimeObserverFile = rootPath("scripts", "native", "cicd", "pac-source-artifact-runtime.mjs");
|
||||
const fieldManager = "unidesk-platform-infra-pipelines-as-code";
|
||||
const pacReadOnlyCaptureTimeoutMs = 25_000;
|
||||
const y = createYamlFieldReader(configLabel);
|
||||
|
||||
function kubernetesLabelValue(value: string): string {
|
||||
@@ -293,6 +294,25 @@ interface SecretMaterial {
|
||||
webhookPath: string;
|
||||
}
|
||||
|
||||
type PacReadOnlyAction = "status" | "history" | "debug-step";
|
||||
|
||||
interface PacReadOnlyCaptureObservation {
|
||||
readonly ok: boolean;
|
||||
readonly status: "succeeded" | "timeout" | "remote-failure" | "empty-response" | "parse-failure";
|
||||
readonly target: string;
|
||||
readonly consumer: string;
|
||||
readonly stage: string;
|
||||
readonly elapsedMs: number;
|
||||
readonly timeoutMs: number;
|
||||
readonly exitCode: number;
|
||||
readonly stdoutBytes: number;
|
||||
readonly stderrBytes: number;
|
||||
readonly partial: boolean;
|
||||
readonly firstBreak: { readonly code: string; readonly phase: string };
|
||||
readonly next: string;
|
||||
readonly valuesPrinted: false;
|
||||
}
|
||||
|
||||
export async function runPlatformInfraPipelinesAsCodeCommand(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown> | RenderedCliResult> {
|
||||
const [action = "plan"] = args;
|
||||
if (action === "help" || action === "--help") return help(args[1] ?? null);
|
||||
@@ -1350,14 +1370,15 @@ async function status(config: UniDeskConfig, options: CommonOptions): Promise<Re
|
||||
const consumer = resolveConsumer(pac, options.consumerId);
|
||||
const repository = resolveRepository(pac, consumer.repositoryRef);
|
||||
const secrets = ensureSecrets(pac, false);
|
||||
const result = await capture(config, target.route, ["sh"], remoteScript("status", pac, target, repository, consumer, { ...options, confirm: false, dryRun: true, wait: false }, secrets, ""));
|
||||
const parsed = parseJsonOutput(result.stdout);
|
||||
const next = nextCommands(target.id, consumer.id, pac.defaults.consumerId);
|
||||
const observed = await capturePacReadOnly(config, "status", target, consumer, remoteScript("status", pac, target, repository, consumer, { ...options, confirm: false, dryRun: true, wait: false }, secrets, ""), `${stringValue(next.status)} --full`);
|
||||
const { result, parsed, observation } = observed;
|
||||
const summary = parsed === null ? null : statusSummary(parsed);
|
||||
const deliveryAuthority = resolveCicdDeliveryAuthority({ consumerId: consumer.id, node: consumer.node, lane: consumer.lane });
|
||||
const deliveryBudget = observePacStatusDeliveryBudget({ policy: pac.deliveryTiming, targetId: target.id, consumerId: consumer.id, summary });
|
||||
const deliveryWarning = record(deliveryBudget.warning);
|
||||
return {
|
||||
ok: result.exitCode === 0 && summary?.ready === true && deliveryAuthority.kind === "pac-pr-merge",
|
||||
ok: observation.ok && summary?.ready === true && deliveryAuthority.kind === "pac-pr-merge",
|
||||
action: "platform-infra-pipelines-as-code-status",
|
||||
mutation: false,
|
||||
target: targetSummary(target),
|
||||
@@ -1367,9 +1388,10 @@ async function status(config: UniDeskConfig, options: CommonOptions): Promise<Re
|
||||
coverage: consumerCoverage(pac, target.id),
|
||||
summary,
|
||||
deliveryBudget,
|
||||
observation,
|
||||
remote: parsed === null ? compactCapture(result, { full: true }) : options.raw ? parsed : undefined,
|
||||
warnings: [...pac.validationWarnings, ...(Object.keys(deliveryWarning).length === 0 ? [] : [deliveryWarning])],
|
||||
next: nextCommands(target.id, consumer.id, pac.defaults.consumerId),
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1602,8 +1624,11 @@ async function history(config: UniDeskConfig, options: HistoryOptions): Promise<
|
||||
if (firstConsumer === undefined) throw new Error("no Pipelines-as-Code consumers are configured");
|
||||
const firstRepository = resolveRepository(pac, firstConsumer.repositoryRef);
|
||||
const secrets = ensureSecrets(pac, false);
|
||||
const result = await capture(config, target.route, ["sh"], remoteScript("history", pac, target, firstRepository, firstConsumer, { ...options, confirm: false, dryRun: true, wait: false }, secrets, "", selectedConsumers));
|
||||
const parsed = parseJsonOutput(result.stdout);
|
||||
const next = selection.nextConsumerId === null
|
||||
? pacNodeReadOnlyNext(target.id)
|
||||
: pacReadOnlyNext(target.id, selection.nextConsumerId);
|
||||
const observed = await capturePacReadOnly(config, "history", target, firstConsumer, remoteScript("history", pac, target, firstRepository, firstConsumer, { ...options, confirm: false, dryRun: true, wait: false }, secrets, "", selectedConsumers), stringValue(record(next).history));
|
||||
const { result, parsed, observation } = observed;
|
||||
const remote = parsed ?? compactCapture(result, { full: true });
|
||||
const historyErrors = arrayRecords(record(remote).historyErrors);
|
||||
const rows = arrayRecords(record(remote).rows);
|
||||
@@ -1618,7 +1643,7 @@ async function history(config: UniDeskConfig, options: HistoryOptions): Promise<
|
||||
.map((observation) => record(observation.warning))
|
||||
.filter((warning) => Object.keys(warning).length > 0);
|
||||
return {
|
||||
ok: result.exitCode === 0 && parsed?.ok !== false && historyErrors.length === 0,
|
||||
ok: observation.ok && parsed?.ok !== false && historyErrors.length === 0,
|
||||
action: "platform-infra-pipelines-as-code-history",
|
||||
mutation: false,
|
||||
target: targetSummary(target),
|
||||
@@ -1633,6 +1658,7 @@ async function history(config: UniDeskConfig, options: HistoryOptions): Promise<
|
||||
detailId: options.detailId,
|
||||
rows,
|
||||
deliveryBudget,
|
||||
observation,
|
||||
historyErrors,
|
||||
warnings: [...pac.validationWarnings, ...deliveryWarnings],
|
||||
controlPlane: parsed === null ? undefined : {
|
||||
@@ -1646,9 +1672,7 @@ async function history(config: UniDeskConfig, options: HistoryOptions): Promise<
|
||||
valuesPrinted: false,
|
||||
},
|
||||
remote: parsed === null || options.raw ? remote : undefined,
|
||||
next: selection.nextConsumerId === null
|
||||
? pacNodeReadOnlyNext(target.id)
|
||||
: pacReadOnlyNext(target.id, selection.nextConsumerId),
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1698,12 +1722,13 @@ async function debugStep(config: UniDeskConfig, options: HistoryOptions): Promis
|
||||
}
|
||||
const consumer = resolveConsumer(pac, selection.selectedConsumerIds[0] ?? null);
|
||||
const repository = resolveRepository(pac, consumer.repositoryRef);
|
||||
const result = await capture(config, target.route, ["sh"], remoteScript("debug-step", pac, target, repository, consumer, { ...options, confirm: false, dryRun: true, wait: false }, emptySecretMaterial(), ""));
|
||||
const parsed = parseJsonOutput(result.stdout);
|
||||
const next = nextCommands(target.id, consumer.id, pac.defaults.consumerId);
|
||||
const observed = await capturePacReadOnly(config, "debug-step", target, consumer, remoteScript("debug-step", pac, target, repository, consumer, { ...options, confirm: false, dryRun: true, wait: false }, emptySecretMaterial(), ""), stringValue(next.status));
|
||||
const { result, parsed, observation } = observed;
|
||||
const fixtureChecks = parsed === null ? [] : arrayRecords(parsed.checks);
|
||||
const fixtureDisclosure = pacDebugFixtureDisclosure(fixtureChecks, options.detailId);
|
||||
return {
|
||||
ok: result.exitCode === 0 && parsed?.ok === true,
|
||||
ok: observation.ok && parsed?.ok === true,
|
||||
action: "platform-infra-pipelines-as-code-debug-step",
|
||||
mutation: false,
|
||||
target: targetSummary(target),
|
||||
@@ -1713,12 +1738,97 @@ async function debugStep(config: UniDeskConfig, options: HistoryOptions): Promis
|
||||
fixtureGate: { ...fixtureDisclosure.fixtureGate, ok: parsed !== null && fixtureDisclosure.fixtureGate.ok === true },
|
||||
checks: fixtureDisclosure.checks,
|
||||
realRun: parsed === null ? null : parsed.realRun ?? null,
|
||||
observation,
|
||||
remote: parsed === null || options.raw ? parsed ?? compactCapture(result, { full: true }) : undefined,
|
||||
next: nextCommands(target.id, consumer.id, pac.defaults.consumerId),
|
||||
next,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
async function capturePacReadOnly(
|
||||
config: UniDeskConfig,
|
||||
action: PacReadOnlyAction,
|
||||
target: PacTarget,
|
||||
consumer: PacConsumer,
|
||||
script: string,
|
||||
next: string,
|
||||
): Promise<{ readonly result: Awaited<ReturnType<typeof capture>>; readonly parsed: Record<string, unknown> | null; readonly observation: PacReadOnlyCaptureObservation }> {
|
||||
const stage = `${action}-remote-observe`;
|
||||
const startedAt = Date.now();
|
||||
printPacReadOnlyProgress(target, consumer, stage, "started", startedAt, { timeoutMs: pacReadOnlyCaptureTimeoutMs });
|
||||
const result = await capture(config, target.route, ["sh"], script, { runtimeTimeoutMs: pacReadOnlyCaptureTimeoutMs });
|
||||
const parsed = parseJsonOutput(result.stdout);
|
||||
const observation = pacReadOnlyCaptureObservation({
|
||||
action,
|
||||
targetId: target.id,
|
||||
consumerId: consumer.id,
|
||||
result,
|
||||
parsed,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
timeoutMs: pacReadOnlyCaptureTimeoutMs,
|
||||
next,
|
||||
});
|
||||
printPacReadOnlyProgress(target, consumer, stage, observation.status, startedAt, {
|
||||
exitCode: result.exitCode,
|
||||
firstBreak: observation.firstBreak.code,
|
||||
});
|
||||
return { result, parsed, observation };
|
||||
}
|
||||
|
||||
export function pacReadOnlyCaptureObservation(options: {
|
||||
readonly action: PacReadOnlyAction;
|
||||
readonly targetId: string;
|
||||
readonly consumerId: string;
|
||||
readonly result: { readonly exitCode: number; readonly stdout: string; readonly stderr: string };
|
||||
readonly parsed: Record<string, unknown> | null;
|
||||
readonly elapsedMs: number;
|
||||
readonly timeoutMs: number;
|
||||
readonly next: string;
|
||||
}): PacReadOnlyCaptureObservation {
|
||||
const stdoutBytes = Buffer.byteLength(options.result.stdout, "utf8");
|
||||
const stderrBytes = Buffer.byteLength(options.result.stderr, "utf8");
|
||||
const timedOut = options.result.exitCode === 124 || /ssh-runtime-timeout|top-level runtime limit/iu.test(options.result.stderr);
|
||||
const status = timedOut
|
||||
? "timeout"
|
||||
: options.result.exitCode !== 0
|
||||
? "remote-failure"
|
||||
: options.result.stdout.trim().length === 0
|
||||
? "empty-response"
|
||||
: options.parsed === null
|
||||
? "parse-failure"
|
||||
: "succeeded";
|
||||
const firstBreakCode = status === "succeeded" ? "none" : `pac-read-${status}`;
|
||||
return {
|
||||
ok: status === "succeeded",
|
||||
status,
|
||||
target: options.targetId,
|
||||
consumer: options.consumerId,
|
||||
stage: `${options.action}-remote-observe`,
|
||||
elapsedMs: options.elapsedMs,
|
||||
timeoutMs: options.timeoutMs,
|
||||
exitCode: options.result.exitCode,
|
||||
stdoutBytes,
|
||||
stderrBytes,
|
||||
partial: stdoutBytes > 0 || stderrBytes > 0,
|
||||
firstBreak: { code: firstBreakCode, phase: "remote-read" },
|
||||
next: options.next,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function printPacReadOnlyProgress(target: PacTarget, consumer: PacConsumer, stage: string, status: string, startedAt: number, detail: Record<string, unknown>): void {
|
||||
process.stderr.write(`${JSON.stringify({
|
||||
event: "platform-infra.pipelines-as-code.read.progress",
|
||||
target: target.id,
|
||||
consumer: consumer.id,
|
||||
stage,
|
||||
status,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
...detail,
|
||||
valuesPrinted: false,
|
||||
})}\n`);
|
||||
}
|
||||
|
||||
export function pacDebugFixtureDisclosure(fixtureChecks: readonly Record<string, unknown>[], detailId: string | null): {
|
||||
readonly fixtureGate: Record<string, unknown>;
|
||||
readonly checks: readonly Record<string, unknown>[];
|
||||
@@ -2718,6 +2828,7 @@ export function renderStatus(result: Record<string, unknown>): RenderedCliResult
|
||||
]]),
|
||||
` hint: ${compactLine(stringValue(diagnostics.hint))}`,
|
||||
` pipeline-run: ${latest.name === undefined ? "-" : `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${stringValue(record(result.target).id)} --id ${stringValue(latest.name)}`}`,
|
||||
...renderPacReadObservation(result),
|
||||
"",
|
||||
"NEXT",
|
||||
` full: ${stringValue(record(result.next).status)} --full`,
|
||||
@@ -2912,6 +3023,7 @@ export function renderHistory(result: Record<string, unknown>): RenderedCliResul
|
||||
];
|
||||
}))),
|
||||
...(detailId === "-" || rows.length !== 1 ? [] : renderHistoryDetail(rows[0] ?? {})),
|
||||
...renderPacReadObservation(result),
|
||||
"",
|
||||
"NEXT",
|
||||
rows.length > 0 && detailId === "-" ? ` detail: ${stringValue(record(result.next).history)} --id ${stringValue(rows[0]?.id ?? rows[0]?.pipelineRun)}` : null,
|
||||
@@ -2987,6 +3099,7 @@ export function renderDebugStep(result: Record<string, unknown>): RenderedCliRes
|
||||
`${stringValue(item.expectedOk)}/${stringValue(item.expectedCode)}`,
|
||||
`${stringValue(item.actualOk)}/${stringValue(item.actualCode)}`,
|
||||
]))),
|
||||
...renderPacReadObservation(result),
|
||||
"",
|
||||
"NEXT",
|
||||
` status: ${stringValue(record(result.next).status)}`,
|
||||
@@ -2994,6 +3107,28 @@ export function renderDebugStep(result: Record<string, unknown>): RenderedCliRes
|
||||
return rendered(result, "platform-infra pipelines-as-code debug-step", lines);
|
||||
}
|
||||
|
||||
function renderPacReadObservation(result: Record<string, unknown>): string[] {
|
||||
const observation = record(result.observation);
|
||||
if (Object.keys(observation).length === 0) return [];
|
||||
const firstBreak = record(observation.firstBreak);
|
||||
return [
|
||||
"",
|
||||
"READ OBSERVATION",
|
||||
...table(["TARGET", "CONSUMER", "STAGE", "STATUS", "ELAPSED_MS", "TIMEOUT_MS", "EXIT", "PARTIAL", "FIRST_BREAK"], [[
|
||||
stringValue(observation.target),
|
||||
stringValue(observation.consumer),
|
||||
stringValue(observation.stage),
|
||||
stringValue(observation.status),
|
||||
stringValue(observation.elapsedMs),
|
||||
stringValue(observation.timeoutMs),
|
||||
stringValue(observation.exitCode),
|
||||
boolText(observation.partial),
|
||||
stringValue(firstBreak.code),
|
||||
]]),
|
||||
` next-read-only: ${stringValue(observation.next)}`,
|
||||
];
|
||||
}
|
||||
|
||||
function parseApplyOptions(args: string[]): ApplyOptions {
|
||||
const commonArgs: string[] = [];
|
||||
let confirm = false;
|
||||
|
||||
Reference in New Issue
Block a user