@bli-cockpit/cli 0.2.265 → 0.2.267
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/image-description-state.js +17 -72
- package/dist/optimize-for-size-entry.js +3 -0
- package/dist/optimize-for-size.js +25 -0
- package/package.json +4 -4
|
@@ -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.267");
|
|
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,
|
|
@@ -2,87 +2,32 @@
|
|
|
2
2
|
* Which images this machine has already sent to the describe door (BLI-16977).
|
|
3
3
|
*
|
|
4
4
|
* One small file in the collector state directory. It is how "each distinct image is sent once"
|
|
5
|
-
* survives from one 15-minute sync to the next.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* survives from one 15-minute sync to the next. The file holds keys only, never a description, never
|
|
6
|
+
* a path, never a byte. If it is lost or corrupt the worst case is the images are sent again and the
|
|
7
|
+
* server answers "known" without calling a model.
|
|
8
|
+
*
|
|
9
|
+
* BLI-23328: the BLI Memory daemon writes the same file when it describes an image at turn end, so
|
|
10
|
+
* the format, the keys, the backoff steps and the locked read-merge-write live in telemetry-core
|
|
11
|
+
* (`image-description-store.ts`), shared by both writers. This module keeps the collector's names.
|
|
9
12
|
*/
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
import { readJsonFile, writeJsonFileAtomic } from "./local-state-files.js";
|
|
13
|
-
export const IMAGE_DESCRIPTION_STATE_FILENAME = "image-descriptions.json";
|
|
14
|
-
/** Oldest keys leave first past this many (about 5 MB on disk). */
|
|
15
|
-
export const IMAGE_DESCRIPTION_STATE_MAX_KEYS = 150_000;
|
|
16
|
-
const KEY_CHARS = 32;
|
|
17
|
-
export function imageStateKey(sha256) {
|
|
18
|
-
return sha256.slice(0, KEY_CHARS);
|
|
19
|
-
}
|
|
20
|
-
export function imageDescriptionStatePath(stateDir) {
|
|
21
|
-
return path.join(stateDir, IMAGE_DESCRIPTION_STATE_FILENAME);
|
|
22
|
-
}
|
|
13
|
+
import { IMAGE_DESCRIPTION_STATE_FILENAME, IMAGE_DESCRIPTION_STATE_MAX_KEYS, IMAGE_FAILED_STATE_MAX_KEYS, imageDescriptionStatePath, imageRetryDelayMs, imageStateKey, mergeImageDescriptionStates, readImageDescriptionState, updateImageDescriptionState, } from "@bli-cockpit/telemetry-core";
|
|
14
|
+
export { IMAGE_DESCRIPTION_STATE_FILENAME, IMAGE_DESCRIPTION_STATE_MAX_KEYS, IMAGE_FAILED_STATE_MAX_KEYS, imageDescriptionStatePath, imageRetryDelayMs, imageStateKey, };
|
|
23
15
|
/** The keys already described, oldest first. A missing or unreadable file is an empty list, said out loud when unreadable. */
|
|
24
16
|
export async function readDescribedImageKeys(stateDir) {
|
|
25
|
-
|
|
26
|
-
const raw = (await readJsonFile(imageDescriptionStatePath(stateDir)));
|
|
27
|
-
if (!Array.isArray(raw?.described))
|
|
28
|
-
return [];
|
|
29
|
-
return raw.described.filter((key) => typeof key === "string" && key.length === KEY_CHARS);
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
if (!isMissingFileFailure(error)) {
|
|
33
|
-
console.error("[image-descriptions] state file unreadable, starting from an empty list", JSON.stringify({ reason: "image_state_unreadable" }));
|
|
34
|
-
}
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
/** Oldest failures leave first past this many. */
|
|
39
|
-
export const IMAGE_FAILED_STATE_MAX_KEYS = 5_000;
|
|
40
|
-
const HOUR_MS = 3_600_000;
|
|
41
|
-
/** A refusal that repeats for the same bytes (a 4xx): a week, then a month. */
|
|
42
|
-
const PERMANENT_BACKOFF_MS = [7 * 24 * HOUR_MS, 30 * 24 * HOUR_MS];
|
|
43
|
-
/** A failure that may pass (the door or the model was down): 1 h, 6 h, 24 h, then 72 h. */
|
|
44
|
-
const TRANSIENT_BACKOFF_MS = [HOUR_MS, 6 * HOUR_MS, 24 * HOUR_MS, 72 * HOUR_MS];
|
|
45
|
-
/** How long to leave an image alone after its Nth failed attempt. */
|
|
46
|
-
export function imageRetryDelayMs(attempts, permanent) {
|
|
47
|
-
const steps = permanent ? PERMANENT_BACKOFF_MS : TRANSIENT_BACKOFF_MS;
|
|
48
|
-
return steps[Math.min(Math.max(attempts, 1), steps.length) - 1];
|
|
17
|
+
return (await readImageDescriptionState(stateDir)).described;
|
|
49
18
|
}
|
|
50
19
|
/** The failures on record, key to record. A missing or unreadable file is an empty map, said out loud when unreadable. */
|
|
51
20
|
export async function readFailedImages(stateDir) {
|
|
52
|
-
|
|
53
|
-
const raw = (await readJsonFile(imageDescriptionStatePath(stateDir)));
|
|
54
|
-
if (!raw?.failed || typeof raw.failed !== "object" || Array.isArray(raw.failed))
|
|
55
|
-
return {};
|
|
56
|
-
const kept = {};
|
|
57
|
-
for (const [key, value] of Object.entries(raw.failed)) {
|
|
58
|
-
const record = value;
|
|
59
|
-
if (key.length !== KEY_CHARS || !record)
|
|
60
|
-
continue;
|
|
61
|
-
if (typeof record.reason !== "string" || typeof record.attempts !== "number" || typeof record.retry_after !== "number")
|
|
62
|
-
continue;
|
|
63
|
-
kept[key] = { reason: record.reason, attempts: record.attempts, retry_after: record.retry_after };
|
|
64
|
-
}
|
|
65
|
-
return kept;
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
if (!isMissingFileFailure(error)) {
|
|
69
|
-
console.error("[image-descriptions] failed-image list unreadable, starting from an empty list", JSON.stringify({ reason: "image_failed_state_unreadable" }));
|
|
70
|
-
}
|
|
71
|
-
return {};
|
|
72
|
-
}
|
|
21
|
+
return (await readImageDescriptionState(stateDir)).failed;
|
|
73
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Write this sync's view, merged with whatever the file holds now: the memory daemon may have
|
|
25
|
+
* described images since the tick opened its copy, and those entries must survive.
|
|
26
|
+
*/
|
|
74
27
|
export async function writeImageDescriptionState(stateDir, state) {
|
|
75
|
-
|
|
76
|
-
const kept = keys.length > IMAGE_DESCRIPTION_STATE_MAX_KEYS ? keys.slice(keys.length - IMAGE_DESCRIPTION_STATE_MAX_KEYS) : keys;
|
|
77
|
-
const failedEntries = Object.entries(state.failed).sort((a, b) => a[1].retry_after - b[1].retry_after);
|
|
78
|
-
const failed = Object.fromEntries(failedEntries.slice(Math.max(0, failedEntries.length - IMAGE_FAILED_STATE_MAX_KEYS)));
|
|
79
|
-
await writeJsonFileAtomic(imageDescriptionStatePath(stateDir), {
|
|
80
|
-
schema_version: "cockpit-image-descriptions.v1",
|
|
81
|
-
described: kept,
|
|
82
|
-
failed,
|
|
83
|
-
});
|
|
28
|
+
await updateImageDescriptionState(stateDir, (disk) => mergeImageDescriptionStates({ described: [...state.described], failed: { ...state.failed } }, disk));
|
|
84
29
|
}
|
|
85
30
|
/** Writes the described list and keeps whatever failures are on record. */
|
|
86
31
|
export async function writeDescribedImageKeys(stateDir, keys) {
|
|
87
|
-
await writeImageDescriptionState(stateDir, { described: keys, failed:
|
|
32
|
+
await writeImageDescriptionState(stateDir, { described: keys, failed: {} });
|
|
88
33
|
}
|
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.267",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-surface-doors.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-cli-doctor-json.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@bli-cockpit/memory-mcp": "0.1.
|
|
31
|
-
"@bli-cockpit/mcp": "0.1.
|
|
32
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
30
|
+
"@bli-cockpit/memory-mcp": "0.1.79",
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.163",
|
|
32
|
+
"@bli-cockpit/telemetry-core": "0.1.86"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|