@bli-cockpit/cli 0.2.101 → 0.2.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/doctor-pipeline.js +63 -27
- package/dist/commands/doctor-update.js +2 -0
- package/dist/commands/local-args-collector-setup.js +3 -0
- package/dist/commands/local-help-commands.js +6 -3
- package/dist/commands/local-help.js +1 -1
- package/dist/commands/public-root.js +1 -1
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ import { runBackfillCommand } from "./backfill.js";
|
|
|
14
14
|
import { diskRowMessage, mib, redeliveryLine } from "./doctor-disk-words.js";
|
|
15
15
|
import { doctorRoots } from "./doctor-access.js";
|
|
16
16
|
import { backfillCompletionStepState, backfillFixVerdict, jsonField, parseDoctorBackfillJson, parseDoctorSyncJson, syncBacklogDrainingVerdict, syncStandAsideVerdict, } from "./doctor-pipeline-verdicts.js";
|
|
17
|
-
import { fail, needsFix, ok, skipped } from "./doctor-report.js";
|
|
17
|
+
import { asRecord, fail, needsFix, ok, skipped } from "./doctor-report.js";
|
|
18
18
|
/**
|
|
19
19
|
* The `backfill-complete`, `gc-checked`, `disk-bounded`, and `sync-fresh`
|
|
20
20
|
* check family: does the collection pipeline itself have everything it
|
|
@@ -47,33 +47,69 @@ export async function checkBackfillState(context) {
|
|
|
47
47
|
return needsFix("backfill-complete", cursor.updated_at ? "partial" : "never_run", "the catch-up over your old sessions has not finished");
|
|
48
48
|
}
|
|
49
49
|
export async function fixBackfillState(context) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
50
|
+
const started = Date.now();
|
|
51
|
+
const deadline = started + (context.command.backfillBudgetSeconds ?? 900) * 1000;
|
|
52
|
+
let caughtUp = 0;
|
|
53
|
+
let remaining = null;
|
|
54
|
+
const unfinished = (code, reason) => ({
|
|
55
|
+
...needsFix("backfill-complete", code, `caught up on ${caughtUp} old sessions this run, ${remaining ?? "unknown"} still to go; ${reason}; run \`cockpit doctor\` again to continue`),
|
|
56
|
+
nextAction: "cockpit doctor",
|
|
57
|
+
});
|
|
58
|
+
const progress = setInterval(() => {
|
|
59
|
+
const seconds = Math.floor((Date.now() - started) / 1000);
|
|
60
|
+
context.io.stderr.write(`backfill: ${caughtUp} caught up, ${remaining ?? "unknown"} to go, ${Math.floor(seconds / 60)}m${seconds % 60}s\n`);
|
|
61
|
+
console.error("[doctor] backfill-complete progress", JSON.stringify({ caught_up: caughtUp, remaining, elapsed_seconds: seconds }));
|
|
62
|
+
}, 30_000);
|
|
63
|
+
try {
|
|
64
|
+
while (Date.now() < deadline) {
|
|
65
|
+
// Every chunk reacquires the collector's locks. Bound lock waiting by
|
|
66
|
+
// the overall budget too; never abandon an in-flight cursor write.
|
|
67
|
+
const chunkContext = { ...context, command: { ...context.command,
|
|
68
|
+
lockWaitSeconds: Math.min(context.command.lockWaitSeconds ?? 600, (deadline - Date.now()) / 1000),
|
|
69
|
+
} };
|
|
70
|
+
const row = await withDoctorLockWait(chunkContext, async () => {
|
|
71
|
+
if (Date.now() >= deadline)
|
|
72
|
+
return unfinished("backfill_budget_timeout", "backfill time budget reached");
|
|
73
|
+
const capture = capturedIo(context.io, false);
|
|
74
|
+
const code = await runBackfillCommand({
|
|
75
|
+
homeDir: context.command.homeDir, repoRoot: context.command.repoRoot,
|
|
76
|
+
all: true, dryRun: false, yes: true, json: true,
|
|
77
|
+
}, capture.io);
|
|
78
|
+
const parsed = parseDoctorBackfillJson(capture.stdout());
|
|
79
|
+
const counts = asRecord(parsed?.counts);
|
|
80
|
+
const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
81
|
+
caughtUp += count(counts?.["backfilled"]) ?? 0;
|
|
82
|
+
remaining = count(counts?.["remaining"]) ?? remaining;
|
|
83
|
+
if (code === 0 && parsed?.status === "complete") {
|
|
84
|
+
// Reporting totals include permanent caps. Preserve their marker
|
|
85
|
+
// note instead of retrying history the collector says is complete.
|
|
86
|
+
const checked = await checkBackfillState(context);
|
|
87
|
+
return ok("backfill-complete", checked.status === "ok" ? checked.code : "complete", `caught up on ${caughtUp} old sessions this run` + (checked.status === "ok" ? `; ${checked.message}` : ""));
|
|
88
|
+
}
|
|
89
|
+
const verdict = backfillFixVerdict(parsed, jsonField(capture.stderr(), "failure_reason"));
|
|
90
|
+
if (parsed?.status === "partial" &&
|
|
91
|
+
(parsed.failure_reason === "deferred_budget_exhausted" || !parsed.failure_reason)) {
|
|
92
|
+
return needsFix("backfill-complete", "backfill_chunk_pending", "more history remains");
|
|
93
|
+
}
|
|
94
|
+
return { ...verdict, message: typeof parsed?.failure_reason === "string" ? parsed.failure_reason : "backfill failed without a valid completion receipt" };
|
|
95
|
+
});
|
|
96
|
+
if (row.status === "ok") {
|
|
97
|
+
console.error("[doctor] backfill-complete finished", JSON.stringify({ caught_up: caughtUp, remaining, elapsed_seconds: Math.floor((Date.now() - started) / 1000) }));
|
|
98
|
+
return row;
|
|
99
|
+
}
|
|
100
|
+
if (row.code !== "backfill_chunk_pending") {
|
|
101
|
+
return unfinished(row.code === "lock_wait_timeout" && Date.now() >= deadline ? "backfill_budget_timeout" : row.code, row.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return unfinished("backfill_budget_timeout", "backfill time budget reached");
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.error("[doctor] backfill-complete failed", JSON.stringify({ reason: "backfill_threw", error_name: error instanceof Error ? error.name : "unknown" }));
|
|
108
|
+
return unfinished("backfill_threw", "backfill failed");
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
clearInterval(progress);
|
|
73
112
|
}
|
|
74
|
-
const verdict = backfillFixVerdict(parseDoctorBackfillJson(stdout), jsonField(output, "failure_reason"));
|
|
75
|
-
console.error("[cockpit-doctor] catch-up run did not finish", JSON.stringify({ reason: verdict.code, row_status: verdict.status, exit_code: code }));
|
|
76
|
-
return verdict;
|
|
77
113
|
}
|
|
78
114
|
export async function checkGcState(context) {
|
|
79
115
|
if (context.io.env["COCKPIT_DISABLE_GC"] === "1") {
|
|
@@ -71,6 +71,8 @@ export function reexecDoctor(command, io) {
|
|
|
71
71
|
const args = ["doctor"];
|
|
72
72
|
if (command.homeDir)
|
|
73
73
|
args.push("--home", command.homeDir);
|
|
74
|
+
if (command.backfillBudgetSeconds)
|
|
75
|
+
args.push("--backfill-budget", String(command.backfillBudgetSeconds));
|
|
74
76
|
if (command.lockWaitSeconds)
|
|
75
77
|
args.push("--lock-wait", String(command.lockWaitSeconds));
|
|
76
78
|
if (command.allowHomeRoot)
|
|
@@ -101,6 +101,7 @@ export function parseDoctorArgs(alias, args) {
|
|
|
101
101
|
"--check",
|
|
102
102
|
"--no-repair",
|
|
103
103
|
"--lock-wait",
|
|
104
|
+
"--backfill-budget",
|
|
104
105
|
"--json",
|
|
105
106
|
"--allow-home-root",
|
|
106
107
|
"--max-depth",
|
|
@@ -117,6 +118,7 @@ export function parseDoctorArgs(alias, args) {
|
|
|
117
118
|
],
|
|
118
119
|
valueFlags: [
|
|
119
120
|
"--lock-wait",
|
|
121
|
+
"--backfill-budget",
|
|
120
122
|
"--home",
|
|
121
123
|
"--repo",
|
|
122
124
|
"--workspace",
|
|
@@ -140,6 +142,7 @@ export function parseDoctorArgs(alias, args) {
|
|
|
140
142
|
return {
|
|
141
143
|
kind: "doctor",
|
|
142
144
|
checkOnly: values.booleans.has("--check") || values.booleans.has("--no-repair"),
|
|
145
|
+
backfillBudgetSeconds: optionalPositiveInteger(values.flags.get("--backfill-budget"), "--backfill-budget") ?? 900,
|
|
143
146
|
lockWaitSeconds: optionalPositiveInteger(values.flags.get("--lock-wait"), "--lock-wait") ?? 600,
|
|
144
147
|
alias,
|
|
145
148
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
@@ -71,11 +71,12 @@ export function localSubcommandHelp(command) {
|
|
|
71
71
|
[
|
|
72
72
|
"do-everything",
|
|
73
73
|
[
|
|
74
|
-
"Usage: cockpit do-everything [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--json]",
|
|
74
|
+
"Usage: cockpit do-everything [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--backfill-budget <seconds>] [--json]",
|
|
75
75
|
"",
|
|
76
76
|
"Diagnose, repair, and verify this machine. doctor, do-everything, and fix run the same job.",
|
|
77
77
|
"Repairs CLI, background sync, sign-in, roots, memory and hooks, agent rules, catch-up, uploads, and cleanup.",
|
|
78
78
|
"--check or --no-repair diagnoses without repairs. --dry-run previews repairs.",
|
|
79
|
+
"--backfill-budget defaults to 900 seconds; catch-up repeats chunks until complete, with progress every 30 seconds.",
|
|
79
80
|
"--lock-wait defaults to 600 seconds; progress prints every 30 seconds.",
|
|
80
81
|
"--json includes steps, repairs, and needs_person. Exit 0 means nothing needs you.",
|
|
81
82
|
"Maintainers: --update-tag next keeps self-update on the prerelease candidate.",
|
|
@@ -84,11 +85,12 @@ export function localSubcommandHelp(command) {
|
|
|
84
85
|
[
|
|
85
86
|
"fix",
|
|
86
87
|
[
|
|
87
|
-
"Usage: cockpit fix [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--json]",
|
|
88
|
+
"Usage: cockpit fix [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--backfill-budget <seconds>] [--json]",
|
|
88
89
|
"",
|
|
89
90
|
"Diagnose, repair, and verify this machine. doctor, do-everything, and fix run the same job.",
|
|
90
91
|
"Repairs CLI, background sync, sign-in, roots, memory and hooks, agent rules, catch-up, uploads, and cleanup.",
|
|
91
92
|
"--check or --no-repair diagnoses without repairs. --dry-run previews repairs.",
|
|
93
|
+
"--backfill-budget defaults to 900 seconds; catch-up repeats chunks until complete, with progress every 30 seconds.",
|
|
92
94
|
"--lock-wait defaults to 600 seconds; progress prints every 30 seconds.",
|
|
93
95
|
"--json includes steps, repairs, and needs_person. Exit 0 means nothing needs you.",
|
|
94
96
|
"Maintainers: --update-tag next keeps self-update on the prerelease candidate.",
|
|
@@ -97,11 +99,12 @@ export function localSubcommandHelp(command) {
|
|
|
97
99
|
[
|
|
98
100
|
"doctor",
|
|
99
101
|
[
|
|
100
|
-
"Usage: cockpit doctor [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--json]",
|
|
102
|
+
"Usage: cockpit doctor [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--backfill-budget <seconds>] [--json]",
|
|
101
103
|
"",
|
|
102
104
|
"Diagnose, repair, and verify this machine. doctor, do-everything, and fix run the same job.",
|
|
103
105
|
"Repairs CLI, background sync, sign-in, roots, memory and hooks, agent rules, catch-up, uploads, and cleanup.",
|
|
104
106
|
"--check or --no-repair diagnoses without repairs. --dry-run previews repairs.",
|
|
107
|
+
"--backfill-budget defaults to 900 seconds; catch-up repeats chunks until complete, with progress every 30 seconds.",
|
|
105
108
|
"--lock-wait defaults to 600 seconds; progress prints every 30 seconds.",
|
|
106
109
|
"--json includes steps, repairs, and needs_person. Exit 0 means nothing needs you.",
|
|
107
110
|
"Maintainers: --update-tag next keeps self-update on the prerelease candidate.",
|
|
@@ -64,7 +64,7 @@ export function localCommandHelp(command) {
|
|
|
64
64
|
" cockpit upgrade [same flags as update]",
|
|
65
65
|
" cockpit do-everything [--workspace <path>] [--dashboard-url <url>] [--update-tag <tag>] [--dry-run] [--json]",
|
|
66
66
|
" cockpit fix [same flags as do-everything]",
|
|
67
|
-
" cockpit doctor [--check | --no-repair] [--lock-wait <seconds>] [--json]",
|
|
67
|
+
" cockpit doctor [--check | --no-repair] [--lock-wait <seconds>] [--backfill-budget <seconds>] [--json]",
|
|
68
68
|
" cockpit install [--dashboard-url <url>] [--workspace <path>] [--allow-home-root] [--json]",
|
|
69
69
|
" cockpit login [--pair <code>] [--no-browser] [--legacy-pair] [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--no-auth] [--json]",
|
|
70
70
|
" cockpit pair [--pair <code>] [--no-browser] [--legacy-pair] [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--no-auth] [--json]",
|
|
@@ -15,7 +15,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
18
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
18
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.102");
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
21
|
|