@bli-cockpit/cli 0.2.26 → 0.2.27
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.js +99 -13
- package/dist/commands/public-root.js +1 -1
- package/package.json +1 -1
package/dist/autostart.js
CHANGED
|
@@ -226,6 +226,22 @@ async function installWindowsTask(options) {
|
|
|
226
226
|
"-File",
|
|
227
227
|
registrationPath,
|
|
228
228
|
]);
|
|
229
|
+
// BLI-2996: before this, a nonzero exit here (schtasks rejected the
|
|
230
|
+
// registration script, or Set-ScheduledTask threw) skipped straight to the
|
|
231
|
+
// return with `verified: null` and never logged anything — the doctor row
|
|
232
|
+
// said "needs repair" every run with no way to tell "the rewrite itself
|
|
233
|
+
// never ran" apart from "it ran and Windows still disagrees" (BLI-2996,
|
|
234
|
+
// Brandon's machine: same silent non-convergence under two different
|
|
235
|
+
// validator messages across CLI versions). Metadata only: exit code and
|
|
236
|
+
// whether the process produced any output, never the stderr/stdout text
|
|
237
|
+
// (it can carry the state directory or a workspace path).
|
|
238
|
+
if (created.code !== 0) {
|
|
239
|
+
console.error("[autostart] windows task repair rewrite failed", JSON.stringify({
|
|
240
|
+
task_name: WINDOWS_AUTOSTART_TASK_NAME,
|
|
241
|
+
exit_code: created.code,
|
|
242
|
+
had_output: Boolean(created.stderr.trim() || created.stdout.trim()),
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
229
245
|
const verified = created.code === 0
|
|
230
246
|
? await windowsTaskStatus({
|
|
231
247
|
...options,
|
|
@@ -238,6 +254,14 @@ async function installWindowsTask(options) {
|
|
|
238
254
|
})
|
|
239
255
|
: null;
|
|
240
256
|
const loaded = verified?.status === "loaded";
|
|
257
|
+
// windowsTaskStatus() above already logs the needs-repair case (with the
|
|
258
|
+
// problem list) when the rewrite ran but the read-back still disagrees; log
|
|
259
|
+
// the converged branch here too, tagged as a repair outcome and by name, so
|
|
260
|
+
// "did the rewrite actually fix it this run" never depends on inferring it
|
|
261
|
+
// from a routine status log emitted for an unrelated reason.
|
|
262
|
+
if (created.code === 0 && loaded) {
|
|
263
|
+
console.error("[autostart] windows task repair converged", JSON.stringify({ task_name: WINDOWS_AUTOSTART_TASK_NAME }));
|
|
264
|
+
}
|
|
241
265
|
return {
|
|
242
266
|
status: "installed",
|
|
243
267
|
label: AUTOSTART_LABEL,
|
|
@@ -549,10 +573,17 @@ function windowsTaskRegistrationProblems(taskXml, expected) {
|
|
|
549
573
|
.replace(/<Exec(?:\s[^>]*)?>[\s\S]*?<\/Exec>/giu, "")
|
|
550
574
|
.trim();
|
|
551
575
|
const exactExecBlock = execBlocks.length === 1 && actionRemainder === "" ? execBlocks[0] : "";
|
|
552
|
-
|
|
576
|
+
// Strip a wrapping quote pair defensively: every real capture we have shows
|
|
577
|
+
// Task Scheduler storing <Command> unquoted, but the writer builds the /TR
|
|
578
|
+
// executable segment with the same \"-escaped quoting it uses for the
|
|
579
|
+
// launcher argument (BLI-2598), and there is no verified capture proving
|
|
580
|
+
// schtasks' own TR-splitting heuristic always discards those quotes rather
|
|
581
|
+
// than leaving them as literal text (BLI-2996 canary-gated gap). A stray
|
|
582
|
+
// pair of quotes should never be the reason a correctly-registered task
|
|
583
|
+
// reads as broken.
|
|
584
|
+
const command = stripSurroundingQuotes(exactExecBlock
|
|
553
585
|
?.match(/<Command>\s*([^<]*?)\s*<\/Command>/iu)?.[1]
|
|
554
|
-
?.trim() ??
|
|
555
|
-
"";
|
|
586
|
+
?.trim() ?? "");
|
|
556
587
|
const actionArguments = exactExecBlock
|
|
557
588
|
.match(/<Arguments>\s*([^<]*?)\s*<\/Arguments>/iu)?.[1]
|
|
558
589
|
?.trim() ?? "";
|
|
@@ -654,16 +685,35 @@ function windowsWScriptPath(env = process.env) {
|
|
|
654
685
|
* DESKTOP-G2UO1GK (CLI 0.2.13, 2026-08-12), where the stored value differed
|
|
655
686
|
* from the expected one only by those quotes (BLI-2541).
|
|
656
687
|
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
688
|
+
* BLI-2996: that lesson was only ever applied to the pre-BLI-2677 `-File`
|
|
689
|
+
* action. The wscript launcher pair (`//B "<launcher>"`) has NO verified
|
|
690
|
+
* real-Windows capture — `windowlessWindowsTaskXml()` in autostart.test.ts is
|
|
691
|
+
* SYNTHETIC, built by hand-editing the one real capture we have rather than
|
|
692
|
+
* exported from a machine actually running this action. A repair that rewrites
|
|
693
|
+
* the task correctly but is judged against a guessed shape can fail forever
|
|
694
|
+
* without ever being wrong (Brandon's machine: same non-convergence under two
|
|
695
|
+
* different validator messages across two CLI versions, neither of which ever
|
|
696
|
+
* repaired anything). So this comparison is deliberately structural, not
|
|
697
|
+
* positional: tokenize the way CommandLineToArgvW groups quoted/unquoted
|
|
698
|
+
* arguments, then check flag-token-then-path-token semantically (a real path
|
|
699
|
+
* comparison, case- and quote-insensitive) instead of demanding the exact
|
|
700
|
+
* byte layout we happened to send. Whatever the wscript action's real
|
|
701
|
+
* normalization turns out to be once a canary captures it, this only fails on
|
|
702
|
+
* an actual different flag or different script — not on Windows' own
|
|
703
|
+
* reformatting.
|
|
661
704
|
*/
|
|
662
705
|
function windowsActionArgumentProblem(actionArguments, expectedFlags, expectedLauncherPath) {
|
|
663
|
-
|
|
706
|
+
const tokens = splitWindowsArgumentTokens(actionArguments);
|
|
707
|
+
if (tokens.length === 0) {
|
|
708
|
+
return "task action names no sync launcher to run";
|
|
709
|
+
}
|
|
710
|
+
if ((tokens[0] ?? "").toUpperCase() !== expectedFlags.toUpperCase()) {
|
|
664
711
|
return "task action does not run the launcher with the expected batch-mode flag";
|
|
665
712
|
}
|
|
666
|
-
|
|
713
|
+
if (tokens.length > 2) {
|
|
714
|
+
return "task action passes unexpected extra arguments to the sync launcher";
|
|
715
|
+
}
|
|
716
|
+
const launcherArgument = tokens[1];
|
|
667
717
|
if (!launcherArgument) {
|
|
668
718
|
return "task action names no sync launcher to run";
|
|
669
719
|
}
|
|
@@ -672,10 +722,46 @@ function windowsActionArgumentProblem(actionArguments, expectedFlags, expectedLa
|
|
|
672
722
|
}
|
|
673
723
|
return null;
|
|
674
724
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
725
|
+
/**
|
|
726
|
+
* Quote-aware whitespace tokenizer for an already-XML-decoded `<Arguments>`
|
|
727
|
+
* value: a `"..."` run is one token (quotes stripped, whitespace inside kept),
|
|
728
|
+
* everything else splits on whitespace. Windows paths never contain a quote
|
|
729
|
+
* character, so this does not need CommandLineToArgvW's backslash-escaping
|
|
730
|
+
* rules — only its quoting rule — to tell "one argument with a space in it"
|
|
731
|
+
* apart from "two arguments".
|
|
732
|
+
*/
|
|
733
|
+
function splitWindowsArgumentTokens(value) {
|
|
734
|
+
const tokens = [];
|
|
735
|
+
let current = "";
|
|
736
|
+
let inQuotes = false;
|
|
737
|
+
let hasToken = false;
|
|
738
|
+
for (const char of value) {
|
|
739
|
+
if (char === '"') {
|
|
740
|
+
inQuotes = !inQuotes;
|
|
741
|
+
hasToken = true;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (!inQuotes && /\s/u.test(char)) {
|
|
745
|
+
if (hasToken) {
|
|
746
|
+
tokens.push(current);
|
|
747
|
+
current = "";
|
|
748
|
+
hasToken = false;
|
|
749
|
+
}
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
current += char;
|
|
753
|
+
hasToken = true;
|
|
754
|
+
}
|
|
755
|
+
if (hasToken)
|
|
756
|
+
tokens.push(current);
|
|
757
|
+
return tokens;
|
|
758
|
+
}
|
|
759
|
+
/** Removes one wrapping pair of double quotes, if present. Used only to
|
|
760
|
+
* normalize before a path comparison — never to reconstruct a shell-safe
|
|
761
|
+
* value. */
|
|
762
|
+
function stripSurroundingQuotes(value) {
|
|
763
|
+
const match = value.match(/^"([\s\S]*)"$/u);
|
|
764
|
+
return match ? match[1] : value;
|
|
679
765
|
}
|
|
680
766
|
function sameWindowsPath(left, right) {
|
|
681
767
|
if (!left || !right)
|
|
@@ -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.27");
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
21
|
|