@bli-cockpit/cli 0.2.265 → 0.2.266
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/autostart-darwin-plist.js +11 -5
- package/dist/autostart-self-heal.js +11 -2
- package/dist/autostart-windows-scripts.js +14 -3
- package/dist/autostart.js +14 -1
- package/dist/cli.js +2 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/image-description-session.js +6 -1
- package/dist/optimize-for-size-entry.js +3 -0
- package/dist/optimize-for-size.js +25 -0
- package/package.json +1 -1
|
@@ -126,7 +126,8 @@ export async function readRegisteredDarwinRuntimePaths(homeDir) {
|
|
|
126
126
|
const command = decodeXmlEntities(plist);
|
|
127
127
|
return {
|
|
128
128
|
node_executable: /node_bin='([^']*)'/u.exec(command)?.[1] ?? null,
|
|
129
|
-
cli_entry_point: /"\$node_bin" '([^']*)' sync /u.exec(command)?.[1] ?? null,
|
|
129
|
+
cli_entry_point: /"\$node_bin"(?: --optimize-for-size)? '([^']*)' sync /u.exec(command)?.[1] ?? null,
|
|
130
|
+
optimize_for_size: /"\$node_bin" '[^']*' sync /u.test(command) ? false : /"\$node_bin" --optimize-for-size '[^']*' sync /u.test(command) ? true : null,
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
133
|
/**
|
|
@@ -222,12 +223,15 @@ function watchPathsBlock(watchPaths) {
|
|
|
222
223
|
" </array>",
|
|
223
224
|
];
|
|
224
225
|
}
|
|
226
|
+
/** The V8 flag the scheduled sync runs node with (BLI-23317). */
|
|
227
|
+
export const NODE_OPTIMIZE_FOR_SIZE_FLAG = "--optimize-for-size";
|
|
225
228
|
function renderDarwinSyncCommand(options) {
|
|
229
|
+
const nodeFlag = options.optimizeForSize === false ? "" : ` ${NODE_OPTIMIZE_FOR_SIZE_FLAG}`;
|
|
226
230
|
const dashboardArg = isDefaultDashboardUrl(options.dashboardUrl)
|
|
227
231
|
? ""
|
|
228
232
|
: ` --dashboard-url ${shellQuote(options.dashboardUrl)}`;
|
|
229
233
|
const discoveryArg = options.discoveryArgs.length > 0 ? ` ${options.discoveryArgs.join(" ")}` : "";
|
|
230
|
-
const commands = options.workDirs.map((root) => `"$node_bin" ${shellQuote(options.cliEntryPoint)} sync --workspace ${shellQuote(root)}${dashboardArg}${discoveryArg} --json || exit_code=1`);
|
|
234
|
+
const commands = options.workDirs.map((root) => `"$node_bin"${nodeFlag} ${shellQuote(options.cliEntryPoint)} sync --workspace ${shellQuote(root)}${dashboardArg}${discoveryArg} --json || exit_code=1`);
|
|
231
235
|
return [
|
|
232
236
|
"exit_code=0",
|
|
233
237
|
...darwinNodeResolutionPreamble(options.nodeExecutable),
|
|
@@ -278,14 +282,16 @@ function darwinAgentRegistrationProblems(plist, expected) {
|
|
|
278
282
|
if (!plist.includes(` <key>ThrottleInterval</key>\n <integer>${DARWIN_MIN_SECONDS_BETWEEN_TICKS}</integer>`)) {
|
|
279
283
|
problems.push(`minimum gap between ticks is not ${DARWIN_MIN_SECONDS_BETWEEN_TICKS} seconds`);
|
|
280
284
|
}
|
|
281
|
-
|
|
285
|
+
// BLI-23317: the form with --optimize-for-size is what is written; the form without it is still accepted.
|
|
286
|
+
const matchesCurrentRuntime = [true, false].some((optimizeForSize) => plist.includes(programArgumentsBlock(renderDarwinSyncCommand({
|
|
282
287
|
discoveryArgs: expected.discoveryArgs,
|
|
283
288
|
workDirs: expected.workDirs,
|
|
284
289
|
dashboardUrl: expected.dashboardUrl,
|
|
285
290
|
nodeExecutable: expected.nodeExecutable,
|
|
286
291
|
cliEntryPoint: expected.cliEntryPoint,
|
|
287
|
-
|
|
288
|
-
|
|
292
|
+
optimizeForSize,
|
|
293
|
+
})).join("\n")));
|
|
294
|
+
if (!matchesCurrentRuntime) {
|
|
289
295
|
problems.push("command does not match the current roots, dashboard URL, or Tower runtime");
|
|
290
296
|
}
|
|
291
297
|
return problems;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { autostartStatus, installAutostartAgent, registeredRuntimePathProblems, } from "./autostart.js";
|
|
4
|
+
import { autostartStatus, installAutostartAgent, registeredLauncherUpgradeProblems, registeredRuntimePathProblems, } from "./autostart.js";
|
|
5
5
|
import { detectSecondCockpitInstall, secondInstallReason, } from "./second-install.js";
|
|
6
6
|
import { isAutostartOptedOut } from "./autostart-opt-out.js";
|
|
7
7
|
export const AUTOSTART_REPAIR_THROTTLE_MARKER = ".last-autostart-repair";
|
|
@@ -52,6 +52,14 @@ async function readAutostartProblem(paths, options, platform) {
|
|
|
52
52
|
platform,
|
|
53
53
|
})
|
|
54
54
|
: [];
|
|
55
|
+
// BLI-23317: an install that still runs node without --optimize-for-size works, so it is not "broken";
|
|
56
|
+
// it is rewritten once here, on the normal cadence, and logged as its own reason.
|
|
57
|
+
const upgradeProblems = status.status === "loaded"
|
|
58
|
+
? await registeredLauncherUpgradeProblems({ homeDir: options.homeDir, exec: options.exec, platform })
|
|
59
|
+
: [];
|
|
60
|
+
if (upgradeProblems.length > 0) {
|
|
61
|
+
console.error("[autostart-self-heal] registration runs node without --optimize-for-size; rewriting it", JSON.stringify({ reason: "registration_predates_optimize_for_size", platform }));
|
|
62
|
+
}
|
|
55
63
|
const marker = path.join(paths.state_dir, AUTOSTART_REPAIR_THROTTLE_MARKER);
|
|
56
64
|
// BLI-21564: the reminder job is repaired with the sync agent. A missing or broken reminder job is a
|
|
57
65
|
// problem of its own (an existing install that predates it gets it created here), and the opt-out
|
|
@@ -59,7 +67,7 @@ async function readAutostartProblem(paths, options, platform) {
|
|
|
59
67
|
const remindProblem = status.status === "loaded" && status.remind && status.remind.status !== "loaded" && status.remind.status !== "unsupported"
|
|
60
68
|
? [`reminder job ${status.remind.status === "absent" ? "absent" : status.remind.message ?? "not loaded"}`]
|
|
61
69
|
: [];
|
|
62
|
-
if (!absent && status.status !== "not_loaded" && runtimeProblems.length === 0 && remindProblem.length === 0) {
|
|
70
|
+
if (!absent && status.status !== "not_loaded" && runtimeProblems.length === 0 && upgradeProblems.length === 0 && remindProblem.length === 0) {
|
|
63
71
|
// Healthy is the steady state and stays silent. A marker left by an
|
|
64
72
|
// earlier attempt is spent: that repair worked, so the next break starts
|
|
65
73
|
// fresh instead of waiting out a retry window.
|
|
@@ -70,6 +78,7 @@ async function readAutostartProblem(paths, options, platform) {
|
|
|
70
78
|
...(absent ? ["registration absent"] : []),
|
|
71
79
|
...(status.message ? [status.message] : []),
|
|
72
80
|
...runtimeProblems,
|
|
81
|
+
...upgradeProblems,
|
|
73
82
|
...remindProblem,
|
|
74
83
|
]
|
|
75
84
|
.join("; ")
|
|
@@ -110,6 +110,9 @@ export async function readRegisteredWindowsRuntimePaths(homeDir) {
|
|
|
110
110
|
return {
|
|
111
111
|
node_executable: /\$nodeExecutable = '([^']*)'/u.exec(script)?.[1] ?? null,
|
|
112
112
|
cli_entry_point: /\$cliEntryPoint = '([^']*)'/u.exec(script)?.[1] ?? null,
|
|
113
|
+
optimize_for_size: /& \$nodeExecutable --optimize-for-size \$cliEntryPoint sync /u.test(script)
|
|
114
|
+
? true
|
|
115
|
+
: /& \$nodeExecutable \$cliEntryPoint sync /u.test(script) ? false : null,
|
|
113
116
|
};
|
|
114
117
|
}
|
|
115
118
|
/**
|
|
@@ -125,15 +128,22 @@ export async function syncScriptProblem(options) {
|
|
|
125
128
|
if (!options.repoRoots || options.repoRoots.length === 0)
|
|
126
129
|
return null;
|
|
127
130
|
const settings = resolveAutostartSettings(options, "win32");
|
|
128
|
-
const
|
|
131
|
+
const onDisk = await readFile(scriptPath, "utf8").catch(() => null);
|
|
132
|
+
const expectedFor = async (optimizeForSize) => `${UTF8_BOM}${renderWindowsSyncScript({
|
|
129
133
|
discoveryArgs: await savedDiscoveryLimitArgs(options.homeDir),
|
|
130
134
|
workDirs: settings.work_dirs,
|
|
131
135
|
dashboardUrl: settings.dashboard_url,
|
|
132
136
|
nodeExecutable: (await schedulerNodeExecutable(options, "win32")).path,
|
|
133
137
|
cliEntryPoint: settings.cli_entry_point,
|
|
134
138
|
syncLogPath: windowsSyncLogPath(options.homeDir),
|
|
139
|
+
optimizeForSize,
|
|
135
140
|
})}`;
|
|
136
|
-
|
|
141
|
+
// BLI-23317: the script with --optimize-for-size is what is written; the older one without it is still accepted.
|
|
142
|
+
const difference = renderedFileDifference(onDisk, await expectedFor(true)) === null
|
|
143
|
+
? null
|
|
144
|
+
: renderedFileDifference(onDisk, await expectedFor(false)) === null
|
|
145
|
+
? null
|
|
146
|
+
: renderedFileDifference(onDisk, await expectedFor(true));
|
|
137
147
|
return difference
|
|
138
148
|
? `sync script does not match the current roots or Tower runtime (${describeDifference(difference)})`
|
|
139
149
|
: null;
|
|
@@ -271,13 +281,14 @@ export function renderWindowsLauncherScript(options) {
|
|
|
271
281
|
].join("\r\n");
|
|
272
282
|
}
|
|
273
283
|
function renderWindowsSyncScript(options) {
|
|
284
|
+
const nodeFlag = options.optimizeForSize === false ? "" : " --optimize-for-size";
|
|
274
285
|
const dashboardArgs = isDefaultDashboardUrl(options.dashboardUrl)
|
|
275
286
|
? ""
|
|
276
287
|
: ` --dashboard-url ${powershellLiteral(options.dashboardUrl)}`;
|
|
277
288
|
const discoveryArgs = options.discoveryArgs.length > 0 ? ` ${options.discoveryArgs.join(" ")}` : "";
|
|
278
289
|
const commands = options.workDirs.flatMap((root) => [
|
|
279
290
|
"try {",
|
|
280
|
-
` & $nodeExecutable $cliEntryPoint sync --workspace ${powershellLiteral(root)}${dashboardArgs}${discoveryArgs} --json 2>&1 | ForEach-Object { "$_" } | Add-Content -LiteralPath $syncLogPath -Encoding UTF8`,
|
|
291
|
+
` & $nodeExecutable${nodeFlag} $cliEntryPoint sync --workspace ${powershellLiteral(root)}${dashboardArgs}${discoveryArgs} --json 2>&1 | ForEach-Object { "$_" } | Add-Content -LiteralPath $syncLogPath -Encoding UTF8`,
|
|
281
292
|
" if ($LASTEXITCODE -ne 0) { $exitCode = $LASTEXITCODE }",
|
|
282
293
|
"} catch {",
|
|
283
294
|
" Add-Content -LiteralPath $syncLogPath -Encoding UTF8 -Value $_.Exception.Message",
|
package/dist/autostart.js
CHANGED
|
@@ -75,7 +75,20 @@ export async function readRegisteredRuntimePaths(options) {
|
|
|
75
75
|
if (platform === "win32") {
|
|
76
76
|
return readRegisteredWindowsRuntimePaths(options.homeDir);
|
|
77
77
|
}
|
|
78
|
-
return { node_executable: null, cli_entry_point: null };
|
|
78
|
+
return { node_executable: null, cli_entry_point: null, optimize_for_size: null };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* BLI-23317: a registration written before `--optimize-for-size` still RUNS, so it is not a broken
|
|
82
|
+
* registration and `registeredRuntimePathProblems` stays silent about it. The scheduled tick's
|
|
83
|
+
* self-heal asks this separately so the existing fleet is rewritten to the new form on its normal
|
|
84
|
+
* cadence instead of waiting for a hand-run `cockpit doctor`.
|
|
85
|
+
*/
|
|
86
|
+
export async function registeredLauncherUpgradeProblems(options) {
|
|
87
|
+
const platform = options.platform ?? process.platform;
|
|
88
|
+
if (platform !== (options.hostPlatform ?? process.platform))
|
|
89
|
+
return [];
|
|
90
|
+
const registered = await readRegisteredRuntimePaths(options);
|
|
91
|
+
return registered.optimize_for_size === false ? ["registration predates --optimize-for-size"] : [];
|
|
79
92
|
}
|
|
80
93
|
/**
|
|
81
94
|
* Named problems with the registered runtime, or an empty list. Path-free
|
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
24
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
24
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.266");
|
|
25
25
|
return 0;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -207,7 +207,12 @@ async function describeAndReplace(run, input, finds) {
|
|
|
207
207
|
const deferredOnly = counts.over_budget + counts.backing_off > 0
|
|
208
208
|
&& counts.described_now + counts.known + counts.failed + counts.too_large + counts.settled_earlier === 0;
|
|
209
209
|
const replaced = mode === "describe_and_drop" ? finds.swapped(replace) : { replacedCount: 0, charsSaved: 0 };
|
|
210
|
-
|
|
210
|
+
// BLI-23317: an image over the describe limit is never sent, so it can never gain a description and
|
|
211
|
+
// never changes the output. Listing it made the describer look like it still had room, and the
|
|
212
|
+
// transcript was rebuilt on every tick (rebuilt_describer_has_room). Counted in `too_large` above.
|
|
213
|
+
const pendingImages = [...unique.values()]
|
|
214
|
+
.filter((image) => !described.has(imageStateKey(image.sha256)) && image.byteSize <= IMAGE_DESCRIBE_MAX_BYTES)
|
|
215
|
+
.map((image) => image.sha256);
|
|
211
216
|
console.error(TAG + " transcript prepared", JSON.stringify({
|
|
212
217
|
mode,
|
|
213
218
|
source: input.source,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import v8 from "node:v8";
|
|
2
|
+
/**
|
|
3
|
+
* BLI-23317: run V8 with `--optimize-for-size`. Measured 15-30% lower peak memory with no slowdown on
|
|
4
|
+
* Node 20-26 (one 200 MB transcript 848 -> 721 MB; CLI floor 113 -> 94 MB). `NODE_OPTIONS` rejects the
|
|
5
|
+
* flag, so the scheduled launchers pass it on the node command line and a hand-run `cockpit` applies it
|
|
6
|
+
* here, as the first statement of the entry (`optimize-for-size-entry.ts`, imported before anything heavy).
|
|
7
|
+
*
|
|
8
|
+
* Only a refusal is logged: this runs at the start of every command a person types, and a stderr line
|
|
9
|
+
* on each success would be noise in their terminal. A refusal is logged and ignored, never fatal: the flag is a memory saving, not a requirement.
|
|
10
|
+
*/
|
|
11
|
+
export const OPTIMIZE_FOR_SIZE_FLAG = "--optimize-for-size";
|
|
12
|
+
export function applyOptimizeForSize(options = {}) {
|
|
13
|
+
const execArgv = options.execArgv ?? process.execArgv;
|
|
14
|
+
if (execArgv.includes(OPTIMIZE_FOR_SIZE_FLAG)) {
|
|
15
|
+
return "already_set_by_launcher";
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
(options.setFlags ?? v8.setFlagsFromString)(OPTIMIZE_FOR_SIZE_FLAG);
|
|
19
|
+
return "applied";
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error("[optimize-for-size] could not apply the flag; continuing without it", JSON.stringify({ reason: "set_flags_refused", error: error instanceof Error ? error.name : "unknown" }));
|
|
23
|
+
return "refused";
|
|
24
|
+
}
|
|
25
|
+
}
|