fix: summarize job status output
This commit is contained in:
+7
-2
@@ -2,7 +2,7 @@ import { readConfig } from "./src/config";
|
||||
import { debugDispatch, debugHealth, debugSshPool, debugTask, isDebugDispatchCommand, type DebugDispatchCommand } from "./src/debug";
|
||||
import { isRebuildableService, rebuildService, restartService, stackLogs, stackStatus, startStack, stopStack, unsupportedRebuildService, unsupportedRestartService } from "./src/docker";
|
||||
import { emitError, emitJson, emitText, isRenderedCliResult } from "./src/output";
|
||||
import { cancelJob, jobWithTail, listJobs, listJobsSummary, readJob, runJob } from "./src/jobs";
|
||||
import { cancelJob, jobWithTail, listJobs, listJobsSummary, readJob, renderJobStatusSummary, runJob } from "./src/jobs";
|
||||
import { checkHelp, parseCheckOptions, runChecks, runRecoveryGuardrailsCheck } from "./src/check";
|
||||
import { runSsh } from "./src/ssh";
|
||||
import { autoRemoteCiPublishUserServiceDryRunPlan, extractRemoteCliOptions, runRemoteCli } from "./src/remote";
|
||||
@@ -551,7 +551,12 @@ async function main(): Promise<void> {
|
||||
}
|
||||
if (sub === "status") {
|
||||
const id = third === "latest" || third === undefined ? latestJobId() : third;
|
||||
emitJson(commandName, { job: jobWithTail(readJob(id), boundedNumberOption("--tail-bytes", 12000, 500_000)) });
|
||||
const job = jobWithTail(readJob(id), boundedNumberOption("--tail-bytes", 12000, 500_000));
|
||||
if (args.includes("--full") || args.includes("--raw")) {
|
||||
emitJson(commandName, { job });
|
||||
return;
|
||||
}
|
||||
emitText(renderJobStatusSummary(job).renderedText, commandName);
|
||||
return;
|
||||
}
|
||||
if (sub === "cancel") {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
||||
import { repoRoot, rootPath } from "./config";
|
||||
import { runCommandToFiles, tailFile } from "./command";
|
||||
import { hwlabDefaultRuntimeTarget } from "./hwlab-node-lanes";
|
||||
import type { RenderedCliResult } from "./output";
|
||||
|
||||
export type JobStatus = "queued" | "running" | "succeeded" | "failed" | "canceled";
|
||||
|
||||
@@ -228,6 +229,47 @@ export function jobWithTail(job: JobRecord, maxBytes = 12000): JobRecord & {
|
||||
};
|
||||
}
|
||||
|
||||
export function renderJobStatusSummary(job: ReturnType<typeof jobWithTail>): RenderedCliResult {
|
||||
const progress = job.progress;
|
||||
const warnings = Array.isArray(progress.warnings) ? progress.warnings : [];
|
||||
const lines = [
|
||||
jobStatusTable(
|
||||
["JOB", "STATUS", "EXIT", "KIND", "STAGE", "STAGE_STATUS", "ELAPSED", "LAST_EVENT_AGE"],
|
||||
[[job.id, job.status, job.exitCode ?? "-", progress.kind, progress.stage ?? "-", progress.stageStatus ?? "-", secondsText(progress.elapsedSeconds), secondsText(progress.lastEventAgeSeconds)]],
|
||||
),
|
||||
"",
|
||||
jobStatusTable(
|
||||
["SOURCE", "PIPELINE", "CREATED", "EVENTS", "SLOW", "SUMMARY"],
|
||||
[[progress.sourceCommit ? String(progress.sourceCommit).slice(0, 12) : "-", progress.pipelineRun ?? "-", progress.pipelineCreated ?? "-", progress.eventsObserved, progress.slow, progress.summary]],
|
||||
),
|
||||
"",
|
||||
warnings.length === 0 ? "WARNINGS\n-" : ["WARNINGS", ...warnings.slice(0, 6).map((item) => `- ${jobStatusCell(item, 220)}`)].join("\n"),
|
||||
"",
|
||||
jobStatusTable(
|
||||
["TAIL", "BYTES", "TRUNCATED", "PATH"],
|
||||
[
|
||||
["stdout", job.tailPolicy.stdoutBytes, job.tailPolicy.stdoutTruncated, job.tailPolicy.fullLogPaths.stdoutFile],
|
||||
["stderr", job.tailPolicy.stderrBytes, job.tailPolicy.stderrTruncated, job.tailPolicy.fullLogPaths.stderrFile],
|
||||
],
|
||||
),
|
||||
"",
|
||||
jobTailSection("STDOUT_TAIL", job.stdoutTail),
|
||||
"",
|
||||
jobTailSection("STDERR_TAIL", job.stderrTail),
|
||||
"",
|
||||
"NEXT",
|
||||
` status: bun scripts/cli.ts job status ${job.id} --tail-bytes ${job.tailPolicy.requestedTailBytes}`,
|
||||
progress.nextCommand ? ` progress: ${progress.nextCommand}` : null,
|
||||
` full: bun scripts/cli.ts job status ${job.id} --tail-bytes ${job.tailPolicy.requestedTailBytes} --full`,
|
||||
].filter((line): line is string => line !== null);
|
||||
return {
|
||||
ok: job.status !== "failed",
|
||||
command: "job status",
|
||||
contentType: "text/plain",
|
||||
renderedText: `${lines.join("\n")}\n`,
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeJobProgress(job: JobRecord, maxBytes = 96_000, tails?: { stdoutTail: string; stderrTail: string }): JobProgressSummary {
|
||||
const nowMs = Date.now();
|
||||
const knownWorkflow = job.name === "hwlab_g14_v02_trigger_current";
|
||||
@@ -898,6 +940,31 @@ function tailTextByBytes(text: string, maxBytes: number): string {
|
||||
return buffer.subarray(buffer.length - safeMaxBytes).toString("utf8");
|
||||
}
|
||||
|
||||
function secondsText(value: number | null): string {
|
||||
return value === null ? "-" : `${value}s`;
|
||||
}
|
||||
|
||||
function jobStatusCell(value: unknown, maxLength = 96): string {
|
||||
if (value === null || value === undefined) return "-";
|
||||
const text = typeof value === "string" ? value : String(value);
|
||||
const compact = text.replace(/\s+/gu, " ").trim();
|
||||
if (compact.length === 0) return "-";
|
||||
if (compact.length <= maxLength) return compact;
|
||||
return `${compact.slice(0, Math.max(1, maxLength - 1))}...`;
|
||||
}
|
||||
|
||||
function jobStatusTable(headers: string[], rows: unknown[][]): string {
|
||||
const stringRows = rows.map((row) => row.map((value) => jobStatusCell(value)));
|
||||
const widths = headers.map((header, index) => Math.max(header.length, ...stringRows.map((row) => row[index]?.length ?? 0)));
|
||||
const renderRow = (row: string[]) => row.map((cell, index) => cell.padEnd(widths[index] ?? cell.length)).join(" ").trimEnd();
|
||||
return [renderRow(headers), ...stringRows.map(renderRow)].join("\n");
|
||||
}
|
||||
|
||||
function jobTailSection(title: string, text: string): string {
|
||||
const lines = text.trim().split(/\r?\n/u).filter((line) => line.trim().length > 0).slice(-6).map((line) => ` ${jobStatusCell(line, 220)}`);
|
||||
return lines.length === 0 ? `${title}\n-` : [title, ...lines].join("\n");
|
||||
}
|
||||
|
||||
function compactJobStdoutTail(job: JobRecord, progress: JobProgressSummary, rawTail: string, requestedTailBytes: number): string {
|
||||
if (progress.kind !== "hwlab-runtime-lane-trigger") return rawTail;
|
||||
if (Buffer.byteLength(rawTail, "utf8") <= 4096 && !rawTail.includes("\"expected\"") && !rawTail.includes("stdoutTail")) return rawTail;
|
||||
|
||||
Reference in New Issue
Block a user