@indigoai-us/hq-cli 5.345.33 → 5.345.35
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/CHANGELOG.md +11 -0
- package/dist/commands/cloud-provision.d.ts +2 -0
- package/dist/commands/core-resolve-company-native.d.ts +2 -0
- package/dist/commands/core-resolve-company-native.js +54 -17
- package/dist/commands/install-global.d.ts +1 -1
- package/dist/commands/install-global.js +5 -4
- package/dist/commands/lanes.js +18 -14
- package/dist/commands/pkg-install.js +5 -3
- package/dist/commands/reindex.js +11 -8
- package/dist/lib/daemon/remote-control.js +62 -3
- package/dist/lib/index-render/shared.d.ts +3 -1
- package/dist/lib/index-render/shared.js +5 -2
- package/dist/lib/lanes/depth-cap.d.ts +7 -0
- package/dist/lib/lanes/depth-cap.js +41 -0
- package/dist/lib/lanes/reconcile.d.ts +1 -1
- package/dist/lib/lanes/reconcile.js +16 -5
- package/dist/lib/mesh/live/daemon/install.d.ts +7 -4
- package/dist/lib/mesh/live/daemon/install.js +144 -47
- package/dist/utils/self-update.d.ts +4 -3
- package/dist/utils/self-update.js +12 -5
- package/dist/utils/version-gate.d.ts +20 -4
- package/dist/utils/version-gate.js +112 -11
- package/package.json +1 -1
|
@@ -55,6 +55,8 @@ export interface InstallPaths {
|
|
|
55
55
|
* host has no conhost (the task then runs node directly).
|
|
56
56
|
*/
|
|
57
57
|
windowsHeadlessLauncher?: string;
|
|
58
|
+
/** Windows command processor used to execute the stable npm .cmd shim. */
|
|
59
|
+
windowsCommandProcessor?: string;
|
|
58
60
|
/** When true, render the root/ec2-user system unit instead of user unit. */
|
|
59
61
|
systemUser?: string;
|
|
60
62
|
/** Verified HQ root to persist and pass to the installed service. */
|
|
@@ -102,7 +104,6 @@ export declare function restartDaemonServiceIfInstalled(opts?: {
|
|
|
102
104
|
env?: NodeJS.ProcessEnv;
|
|
103
105
|
existsSync?: typeof fs.existsSync;
|
|
104
106
|
readFileSync?: typeof fs.readFileSync;
|
|
105
|
-
writeFileSync?: typeof fs.writeFileSync;
|
|
106
107
|
run?: (command: string, args: string[]) => {
|
|
107
108
|
status: number | null;
|
|
108
109
|
stdout?: string;
|
|
@@ -119,7 +120,7 @@ export declare function detectPlatform(platform?: NodeJS.Platform): DaemonInstal
|
|
|
119
120
|
* dies with MODULE_NOT_FOUND on the next restart. Prefer the live npm-global
|
|
120
121
|
* binary when both exist.
|
|
121
122
|
*/
|
|
122
|
-
export declare function preferStableHqBinary(entry: string): string;
|
|
123
|
+
export declare function preferStableHqBinary(entry: string, platform?: DaemonInstallPlatform, existsSync?: typeof fs.existsSync): string;
|
|
123
124
|
/**
|
|
124
125
|
* Absolute path of the CLI entry that is executing this process.
|
|
125
126
|
* realpath of argv[1] only — never a PATH lookup of `hq`.
|
|
@@ -153,7 +154,6 @@ export declare function installDaemonIfStale(opts: {
|
|
|
153
154
|
run?: RunCommand;
|
|
154
155
|
uid?: number;
|
|
155
156
|
sleep?: SleepFn;
|
|
156
|
-
writeFileSync?: typeof fs.writeFileSync;
|
|
157
157
|
mkdirSync?: typeof fs.mkdirSync;
|
|
158
158
|
reloadSystemd?: () => {
|
|
159
159
|
status: number | null;
|
|
@@ -193,6 +193,10 @@ export declare function buildInstallPaths(opts: {
|
|
|
193
193
|
windowsUser?: string;
|
|
194
194
|
/** Windows headless launcher override (tests); defaults to %SystemRoot%\System32\conhost.exe when present. */
|
|
195
195
|
windowsHeadlessLauncher?: string | null;
|
|
196
|
+
/** Platform override for unit path and command rendering tests. */
|
|
197
|
+
platform?: DaemonInstallPlatform;
|
|
198
|
+
/** Filesystem seam for stable hq shim resolution tests. */
|
|
199
|
+
existsSync?: typeof fs.existsSync;
|
|
196
200
|
}): InstallPaths;
|
|
197
201
|
/** `DOMAIN\user` for the current account, as Task Scheduler expects. */
|
|
198
202
|
export declare function resolveWindowsUser(env?: NodeJS.ProcessEnv): string;
|
|
@@ -277,7 +281,6 @@ export declare function installDaemonService(opts: {
|
|
|
277
281
|
paths?: InstallPaths;
|
|
278
282
|
/** When set, write the system unit template (does not sudo-install). */
|
|
279
283
|
systemUser?: string;
|
|
280
|
-
writeFileSync?: typeof fs.writeFileSync;
|
|
281
284
|
mkdirSync?: typeof fs.mkdirSync;
|
|
282
285
|
/** Optional: actually load the service (disabled in tests). */
|
|
283
286
|
activate?: (platform: DaemonInstallPlatform, dest: string) => void;
|
|
@@ -16,6 +16,7 @@ import * as os from "node:os";
|
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import { spawnSync } from "node:child_process";
|
|
18
18
|
import semver from "semver";
|
|
19
|
+
import { atomicWrite } from "../../../index-render/shared.js";
|
|
19
20
|
import { resolveDaemonAssetDir } from "./log.js";
|
|
20
21
|
import { LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, WINDOWS_TASK_NAME, daemonDir, daemonLogPath, daemonPidPath, } from "./paths.js";
|
|
21
22
|
import { isInstallableHqRoot, readDaemonConfig, registerTrustedHqRoot, verifyHqRoot } from "./config.js";
|
|
@@ -123,17 +124,25 @@ function migrateLegacyDaemonUnit(opts) {
|
|
|
123
124
|
// The rendered command only has to name a binary; install already resolved one.
|
|
124
125
|
}
|
|
125
126
|
if (platform === "darwin") {
|
|
126
|
-
|
|
127
|
+
replaceUnitFile(unitPath, renderLaunchdPlist(paths), 0o600, () => {
|
|
128
|
+
activateLaunchd(unitPath, asRunCommand(opts.run), opts.uid, { sleep: opts.sleep });
|
|
129
|
+
});
|
|
127
130
|
return;
|
|
128
131
|
}
|
|
129
132
|
if (platform === "linux") {
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
replaceUnitFile(unitPath, renderSystemdUserUnit(paths), 0o600, () => {
|
|
134
|
+
const result = opts.run("systemctl", ["--user", "daemon-reload"]);
|
|
135
|
+
if (result.status !== 0)
|
|
136
|
+
throw new Error("systemctl --user daemon-reload failed while repairing the mesh unit");
|
|
137
|
+
});
|
|
132
138
|
return;
|
|
133
139
|
}
|
|
134
140
|
const rendered = renderWindowsTaskXml(paths);
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
replaceUnitFile(unitPath, encodeWindowsTaskXml(rendered), 0o600, () => {
|
|
142
|
+
const result = opts.run("schtasks.exe", ["/Create", "/TN", WINDOWS_TASK_NAME, "/XML", unitPath, "/F"]);
|
|
143
|
+
if (result.status !== 0)
|
|
144
|
+
throw new Error("schtasks /Create failed while repairing the mesh unit");
|
|
145
|
+
});
|
|
137
146
|
}
|
|
138
147
|
/**
|
|
139
148
|
* Restart the installed user daemon after a CLI upgrade. This is deliberately
|
|
@@ -155,7 +164,6 @@ export function restartDaemonServiceIfInstalled(opts = {}) {
|
|
|
155
164
|
const home = opts.home ?? os.homedir();
|
|
156
165
|
const existsSync = opts.existsSync ?? fs.existsSync;
|
|
157
166
|
const readFileSync = opts.readFileSync ?? fs.readFileSync;
|
|
158
|
-
const writeFileSync = opts.writeFileSync ?? fs.writeFileSync;
|
|
159
167
|
const run = opts.run ?? ((command, args) => {
|
|
160
168
|
const result = spawnSync(command, args, { encoding: "utf8" });
|
|
161
169
|
return {
|
|
@@ -165,7 +173,17 @@ export function restartDaemonServiceIfInstalled(opts = {}) {
|
|
|
165
173
|
};
|
|
166
174
|
});
|
|
167
175
|
try {
|
|
168
|
-
migrateLegacyDaemonUnit({
|
|
176
|
+
migrateLegacyDaemonUnit({
|
|
177
|
+
platform,
|
|
178
|
+
home,
|
|
179
|
+
hqRoot: opts.hqRoot,
|
|
180
|
+
env: opts.env,
|
|
181
|
+
existsSync,
|
|
182
|
+
readFileSync,
|
|
183
|
+
run,
|
|
184
|
+
uid: opts.uid,
|
|
185
|
+
sleep: opts.sleep,
|
|
186
|
+
});
|
|
169
187
|
if (platform === "darwin") {
|
|
170
188
|
const plist = path.join(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
171
189
|
if (!existsSync(plist))
|
|
@@ -239,14 +257,46 @@ export function detectPlatform(platform = process.platform) {
|
|
|
239
257
|
* dies with MODULE_NOT_FOUND on the next restart. Prefer the live npm-global
|
|
240
258
|
* binary when both exist.
|
|
241
259
|
*/
|
|
242
|
-
export function preferStableHqBinary(entry) {
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
260
|
+
export function preferStableHqBinary(entry, platform = detectPlatform(), existsSync = fs.existsSync) {
|
|
261
|
+
const api = platform === "win32" ? path.win32 : path;
|
|
262
|
+
const sep = api.sep;
|
|
263
|
+
const stale = `${sep}toolchain${sep}node${sep}bin${sep}`;
|
|
264
|
+
const stable = `${sep}toolchain${sep}npm-global${sep}bin${sep}`;
|
|
265
|
+
if (entry.includes(stale)) {
|
|
266
|
+
const candidate = entry.replace(stale, stable);
|
|
267
|
+
if (candidate !== entry && existsSync(candidate))
|
|
268
|
+
return api.resolve(candidate);
|
|
269
|
+
}
|
|
270
|
+
const normalized = entry.replace(/\\/g, "/");
|
|
271
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
272
|
+
let packageAt = -1;
|
|
273
|
+
for (let i = 0; i < parts.length - 2; i += 1) {
|
|
274
|
+
if (parts[i]?.toLowerCase() === "node_modules"
|
|
275
|
+
&& parts[i + 1]?.toLowerCase() === "@indigoai-us"
|
|
276
|
+
&& parts[i + 2]?.toLowerCase() === "hq-cli") {
|
|
277
|
+
packageAt = i;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (packageAt >= 0) {
|
|
282
|
+
const prefixParts = parts.slice(0, packageAt);
|
|
283
|
+
let prefix;
|
|
284
|
+
if (platform === "win32") {
|
|
285
|
+
const root = path.win32.parse(entry).root;
|
|
286
|
+
const rootParts = root.replace(/\\+$/, "").split(/[\\/]+/).filter(Boolean);
|
|
287
|
+
prefix = path.win32.resolve(root, ...prefixParts.slice(rootParts.length));
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
if (prefixParts.at(-1) === "lib")
|
|
291
|
+
prefixParts.pop();
|
|
292
|
+
prefix = path.posix.resolve(`/${prefixParts.join("/")}`);
|
|
293
|
+
}
|
|
294
|
+
const shim = platform === "win32"
|
|
295
|
+
? path.win32.join(prefix, "hq.cmd")
|
|
296
|
+
: path.posix.join(prefix, "bin", "hq");
|
|
297
|
+
if (existsSync(shim))
|
|
298
|
+
return api.resolve(shim);
|
|
299
|
+
}
|
|
250
300
|
return entry;
|
|
251
301
|
}
|
|
252
302
|
/**
|
|
@@ -346,7 +396,6 @@ export function installDaemonIfStale(opts) {
|
|
|
346
396
|
platform,
|
|
347
397
|
paths: opts.paths,
|
|
348
398
|
systemUser: opts.systemUser,
|
|
349
|
-
writeFileSync: opts.writeFileSync,
|
|
350
399
|
mkdirSync: opts.mkdirSync,
|
|
351
400
|
reloadSystemd: opts.reloadSystemd,
|
|
352
401
|
schtasks: opts.schtasks,
|
|
@@ -357,9 +406,6 @@ export function installDaemonIfStale(opts) {
|
|
|
357
406
|
uid: opts.uid,
|
|
358
407
|
sleep: opts.sleep,
|
|
359
408
|
});
|
|
360
|
-
if (platform === "linux" && !opts.systemUser && !opts.paths.systemUser) {
|
|
361
|
-
activateSystemd(SYSTEMD_UNIT_NAME, run);
|
|
362
|
-
}
|
|
363
409
|
return {
|
|
364
410
|
reinstalled: true,
|
|
365
411
|
message: `${installed.message} Restarted with ${opts.paths.nodeBinary} ${opts.paths.hqBinary}.`,
|
|
@@ -413,7 +459,8 @@ export function buildInstallPaths(opts) {
|
|
|
413
459
|
const home = opts.home ?? os.homedir();
|
|
414
460
|
const env = opts.env ?? process.env;
|
|
415
461
|
const ddir = daemonDir(opts.meshRoot, home, env);
|
|
416
|
-
const
|
|
462
|
+
const platform = opts.platform ?? detectPlatform();
|
|
463
|
+
const hqBinary = preferStableHqBinary(opts.hqBinary ?? resolveHqBinary(opts.argv, env), platform, opts.existsSync ?? fs.existsSync);
|
|
417
464
|
const nodeBinary = opts.nodeBinary ?? resolveNodeBinary(opts.execPath, env);
|
|
418
465
|
return {
|
|
419
466
|
home,
|
|
@@ -430,6 +477,7 @@ export function buildInstallPaths(opts) {
|
|
|
430
477
|
windowsHeadlessLauncher: opts.windowsHeadlessLauncher === undefined
|
|
431
478
|
? resolveWindowsHeadlessLauncher(env)
|
|
432
479
|
: (opts.windowsHeadlessLauncher ?? undefined),
|
|
480
|
+
windowsCommandProcessor: path.win32.join(env.SystemRoot ?? "C:\\Windows", "System32", "cmd.exe"),
|
|
433
481
|
systemUser: opts.systemUser,
|
|
434
482
|
hqRoot: opts.hqRoot,
|
|
435
483
|
hqRootSource: opts.hqRootSource,
|
|
@@ -480,7 +528,12 @@ export function windowsLogonNotice(paths) {
|
|
|
480
528
|
function describeWindowsLaunchMode(paths) {
|
|
481
529
|
return windowsLaunchMode(paths) === "conhost-headless"
|
|
482
530
|
? `mode: conhost --headless via ${paths.windowsHeadlessLauncher}`
|
|
483
|
-
:
|
|
531
|
+
: isWindowsCommandShim(paths.hqBinary)
|
|
532
|
+
? "mode: direct command processor (conhost --headless unavailable on this host; a console window may appear at logon)"
|
|
533
|
+
: "mode: direct node (conhost --headless unavailable on this host; a console window may appear at logon)";
|
|
534
|
+
}
|
|
535
|
+
function isWindowsCommandShim(file) {
|
|
536
|
+
return path.win32.extname(file).toLowerCase() === ".cmd";
|
|
484
537
|
}
|
|
485
538
|
function safeUsername() {
|
|
486
539
|
try {
|
|
@@ -601,13 +654,19 @@ WantedBy=multi-user.target
|
|
|
601
654
|
*/
|
|
602
655
|
export function renderWindowsTaskXml(paths) {
|
|
603
656
|
const user = escapeXml(paths.windowsUser);
|
|
604
|
-
const
|
|
657
|
+
const stableShim = isWindowsCommandShim(paths.hqBinary);
|
|
605
658
|
const hqArg = windowsArgQuote(paths.hqBinary);
|
|
606
|
-
const
|
|
659
|
+
const commandProcessor = paths.windowsCommandProcessor ?? path.win32.join("C:\\Windows", "System32", "cmd.exe");
|
|
660
|
+
const command = stableShim ? (paths.windowsHeadlessLauncher ?? commandProcessor) : (paths.windowsHeadlessLauncher ?? paths.nodeBinary);
|
|
607
661
|
const rootArgs = paths.hqRoot ? ` --hq-root ${windowsArgQuote(paths.hqRoot)}` : "";
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
662
|
+
const commandLine = `${hqArg} mesh daemon run${rootArgs}`;
|
|
663
|
+
const args = stableShim
|
|
664
|
+
? paths.windowsHeadlessLauncher
|
|
665
|
+
? `--headless ${windowsArgQuote(commandProcessor)} /d /s /c "${commandLine}"`
|
|
666
|
+
: `/d /s /c "${commandLine}"`
|
|
667
|
+
: paths.windowsHeadlessLauncher
|
|
668
|
+
? `--headless ${windowsArgQuote(paths.nodeBinary)} ${hqArg} mesh daemon run${rootArgs}`
|
|
669
|
+
: `${hqArg} mesh daemon run${rootArgs}`;
|
|
611
670
|
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
612
671
|
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
613
672
|
<RegistrationInfo>
|
|
@@ -769,7 +828,7 @@ export function parseWindowsTaskHqRoot(xml) {
|
|
|
769
828
|
if (quoted)
|
|
770
829
|
return quoted[1].replace(/\\"/g, '"');
|
|
771
830
|
const bare = /--hq-root (\S+)/.exec(args);
|
|
772
|
-
return bare ? bare[1] : null;
|
|
831
|
+
return bare ? bare[1].replace(/"+$/, "") : null;
|
|
773
832
|
}
|
|
774
833
|
/**
|
|
775
834
|
* The HQ root the installed Windows task actually carries: the task XML on
|
|
@@ -812,11 +871,40 @@ function shellQuote(value) {
|
|
|
812
871
|
return value;
|
|
813
872
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
814
873
|
}
|
|
874
|
+
function replaceUnitFile(file, content, mode, reload) {
|
|
875
|
+
let previous = null;
|
|
876
|
+
let previousMode = mode;
|
|
877
|
+
try {
|
|
878
|
+
previous = fs.readFileSync(file);
|
|
879
|
+
previousMode = fs.statSync(file).mode & 0o777;
|
|
880
|
+
}
|
|
881
|
+
catch (error) {
|
|
882
|
+
if (error.code !== "ENOENT")
|
|
883
|
+
throw error;
|
|
884
|
+
}
|
|
885
|
+
try {
|
|
886
|
+
atomicWrite(file, content, { mode });
|
|
887
|
+
reload?.();
|
|
888
|
+
}
|
|
889
|
+
catch (error) {
|
|
890
|
+
try {
|
|
891
|
+
if (previous) {
|
|
892
|
+
atomicWrite(file, previous, { mode: previousMode });
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
fs.rmSync(file, { force: true });
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
catch (rollbackError) {
|
|
899
|
+
throw new AggregateError([error, rollbackError], `Unit replacement and rollback failed: ${file}`);
|
|
900
|
+
}
|
|
901
|
+
throw error;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
815
904
|
export function installDaemonService(opts) {
|
|
816
905
|
const platform = opts.platform ?? detectPlatform();
|
|
817
906
|
const paths = opts.paths ??
|
|
818
907
|
buildInstallPaths({ systemUser: opts.systemUser });
|
|
819
|
-
const writeFileSync = opts.writeFileSync ?? fs.writeFileSync;
|
|
820
908
|
const mkdirSync = opts.mkdirSync ?? fs.mkdirSync;
|
|
821
909
|
mkdirSync(paths.daemonDir, { recursive: true, mode: 0o700 });
|
|
822
910
|
registerTrustedHqRoot(paths.daemonDir, paths.hqRoot);
|
|
@@ -832,13 +920,12 @@ export function installDaemonService(opts) {
|
|
|
832
920
|
if (platform === "darwin") {
|
|
833
921
|
const rendered = renderLaunchdPlist(paths);
|
|
834
922
|
mkdirSync(paths.launchAgentsDir, { recursive: true });
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
}
|
|
923
|
+
replaceUnitFile(paths.plistPath, rendered, 0o600, () => {
|
|
924
|
+
if (opts.run)
|
|
925
|
+
activateLaunchd(paths.plistPath, opts.run, opts.uid, { sleep: opts.sleep });
|
|
926
|
+
else
|
|
927
|
+
opts.activate?.(platform, paths.plistPath);
|
|
928
|
+
});
|
|
842
929
|
return {
|
|
843
930
|
platform,
|
|
844
931
|
action: "install",
|
|
@@ -856,7 +943,7 @@ export function installDaemonService(opts) {
|
|
|
856
943
|
});
|
|
857
944
|
// Write under daemon dir as a template the operator can sudo-install.
|
|
858
945
|
const dest = path.join(paths.daemonDir, SYSTEMD_UNIT_NAME);
|
|
859
|
-
|
|
946
|
+
replaceUnitFile(dest, rendered, 0o644);
|
|
860
947
|
return {
|
|
861
948
|
platform,
|
|
862
949
|
action: "install",
|
|
@@ -870,9 +957,18 @@ export function installDaemonService(opts) {
|
|
|
870
957
|
}
|
|
871
958
|
const rendered = renderSystemdUserUnit(paths);
|
|
872
959
|
mkdirSync(paths.systemdUserDir, { recursive: true });
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
960
|
+
replaceUnitFile(paths.systemdUnitPath, rendered, 0o600, () => {
|
|
961
|
+
const result = (opts.reloadSystemd ?? (() => {
|
|
962
|
+
const reload = spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
963
|
+
return { status: reload.status };
|
|
964
|
+
}))();
|
|
965
|
+
if (result.status !== 0)
|
|
966
|
+
throw new Error("systemctl --user daemon-reload failed while installing the mesh unit");
|
|
967
|
+
if (opts.run)
|
|
968
|
+
activateSystemd(SYSTEMD_UNIT_NAME, opts.run);
|
|
969
|
+
else
|
|
970
|
+
opts.activate?.(platform, paths.systemdUnitPath);
|
|
971
|
+
});
|
|
876
972
|
return {
|
|
877
973
|
platform,
|
|
878
974
|
action: "install",
|
|
@@ -885,14 +981,15 @@ export function installDaemonService(opts) {
|
|
|
885
981
|
if (platform === "win32") {
|
|
886
982
|
const schtasks = opts.schtasks ?? defaultSchtasks;
|
|
887
983
|
const rendered = renderWindowsTaskXml(paths);
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
984
|
+
replaceUnitFile(paths.windowsTaskXmlPath, encodeWindowsTaskXml(rendered), 0o600, () => {
|
|
985
|
+
const created = schtasks([
|
|
986
|
+
"/Create", "/TN", WINDOWS_TASK_NAME, "/XML", paths.windowsTaskXmlPath, "/F",
|
|
987
|
+
]);
|
|
988
|
+
if (created.status !== 0) {
|
|
989
|
+
throw new Error(`schtasks /Create failed for ${WINDOWS_TASK_NAME} (exit ${created.status ?? "signal"}): ` +
|
|
990
|
+
`${(created.stderr || created.stdout).trim()}`);
|
|
991
|
+
}
|
|
992
|
+
});
|
|
896
993
|
// /F replaced any previous registration without ending its instance; stop
|
|
897
994
|
// the recorded daemon so the fresh task instance can take the pid lock.
|
|
898
995
|
schtasks(["/End", "/TN", WINDOWS_TASK_NAME]);
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
*/
|
|
59
59
|
import { spawn } from "node:child_process";
|
|
60
60
|
import * as fs from "node:fs";
|
|
61
|
-
import { type RunningInstall, type UpdateResult } from "./version-gate.js";
|
|
61
|
+
import { probePnpmVersion, type RunningInstall, type UpdateResult } from "./version-gate.js";
|
|
62
62
|
import { type RestartDaemonResult } from "../lib/mesh/live/daemon/install.js";
|
|
63
63
|
/**
|
|
64
64
|
* Set on the re-exec'd child so it can never self-update (and re-exec) again.
|
|
@@ -116,7 +116,7 @@ export declare function buildSelfUpdatePlan(install: RunningInstall): {
|
|
|
116
116
|
* to stdout. Progress is summarised on stderr by the caller instead; captured
|
|
117
117
|
* stderr is kept only to explain a failure.
|
|
118
118
|
*/
|
|
119
|
-
export declare function runUpdateQuiet(cmd: string, args: string[], env?: NodeJS.ProcessEnv): UpdateResult;
|
|
119
|
+
export declare function runUpdateQuiet(cmd: string, args: string[], env?: NodeJS.ProcessEnv, cwd?: string): UpdateResult;
|
|
120
120
|
/**
|
|
121
121
|
* Serialize self-updates across concurrent `hq` processes AND the hq-sync
|
|
122
122
|
* menubar app. Without this, a machine running several HQ agents can fire many
|
|
@@ -144,7 +144,8 @@ export interface SelfUpdateDeps {
|
|
|
144
144
|
homeDir?: string;
|
|
145
145
|
/** Same-filesystem install rename, injectable for rollback tests. */
|
|
146
146
|
renamePath?: typeof fs.renameSync;
|
|
147
|
-
runner?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => UpdateResult;
|
|
147
|
+
runner?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv, cwd?: string) => UpdateResult;
|
|
148
|
+
probePnpmVersion?: typeof probePnpmVersion;
|
|
148
149
|
/** Delay between bounded npm retries when the just-published target is not visible yet. */
|
|
149
150
|
sleep?: (ms: number) => Promise<void>;
|
|
150
151
|
reexec?: (argv: string[], env: NodeJS.ProcessEnv) => number | null;
|
|
@@ -65,7 +65,7 @@ import chalk from "chalk";
|
|
|
65
65
|
import { Sentry } from "../sentry.js";
|
|
66
66
|
import { isCliTelemetryDisabled } from "./cli-telemetry.js";
|
|
67
67
|
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
68
|
-
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, canWriteNpmInstall, captureNonWritableNpmPrefixNotice, claimNonWritableNpmPrefixNotice, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, UPDATE_RECURSION_GUARD_ENV, } from "./version-gate.js";
|
|
68
|
+
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, canWriteNpmInstall, captureNonWritableNpmPrefixNotice, claimNonWritableNpmPrefixNotice, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, pnpmInstalledVersion, performPnpmUpdate, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, UPDATE_RECURSION_GUARD_ENV, } from "./version-gate.js";
|
|
69
69
|
import { canStageInstall, stagedNpmInstall } from "./staged-install.js";
|
|
70
70
|
import { acquireUpdateLock as acquireSharedUpdateLock } from "./update-lock.js";
|
|
71
71
|
import { markLatestIneffective } from "./version-check.js";
|
|
@@ -548,8 +548,12 @@ async function fetchLatestVersion() {
|
|
|
548
548
|
* through the resolved prefix so the copy that is actually running is replaced.
|
|
549
549
|
*/
|
|
550
550
|
export function buildSelfUpdatePlan(install) {
|
|
551
|
-
if (install.manager === "pnpm")
|
|
552
|
-
|
|
551
|
+
if (install.manager === "pnpm") {
|
|
552
|
+
const version = pnpmInstalledVersion(install);
|
|
553
|
+
return version
|
|
554
|
+
? { cmd: "corepack", args: [`pnpm@${version}`, ...buildPnpmInstallArgv(install)] }
|
|
555
|
+
: { cmd: "pnpm", args: buildPnpmInstallArgv(install) };
|
|
556
|
+
}
|
|
553
557
|
if (install.manager === "bun")
|
|
554
558
|
return { cmd: "bun", args: buildBunInstallArgv() };
|
|
555
559
|
if (install.prefix)
|
|
@@ -563,12 +567,12 @@ export function buildSelfUpdatePlan(install) {
|
|
|
563
567
|
* to stdout. Progress is summarised on stderr by the caller instead; captured
|
|
564
568
|
* stderr is kept only to explain a failure.
|
|
565
569
|
*/
|
|
566
|
-
export function runUpdateQuiet(cmd, args, env) {
|
|
570
|
+
export function runUpdateQuiet(cmd, args, env, cwd) {
|
|
567
571
|
// File sinks rather than pipes: a pipe dies with this process and would take
|
|
568
572
|
// the installer down with it mid-write. See `openInstallOutput`.
|
|
569
573
|
const output = openInstallOutput(false);
|
|
570
574
|
try {
|
|
571
|
-
return runSupervisedUpdateCommand(cmd, args, output, env);
|
|
575
|
+
return runSupervisedUpdateCommand(cmd, args, output, env, cwd);
|
|
572
576
|
}
|
|
573
577
|
finally {
|
|
574
578
|
output.dispose();
|
|
@@ -826,6 +830,9 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
826
830
|
if (staged) {
|
|
827
831
|
return stagedNpmInstall(install, latestValid, { run: runner, env, rename: renamePath });
|
|
828
832
|
}
|
|
833
|
+
if (install.manager === "pnpm") {
|
|
834
|
+
return performPnpmUpdate(install, runner, updateEnv ?? env, deps.probePnpmVersion);
|
|
835
|
+
}
|
|
829
836
|
return updateEnv === undefined
|
|
830
837
|
? runner(plan.cmd, plan.args)
|
|
831
838
|
: runner(plan.cmd, plan.args, updateEnv);
|
|
@@ -229,9 +229,19 @@ export declare function buildPnpmInstallArgv(install?: RunningInstall): string[]
|
|
|
229
229
|
* forward the paths recorded for the install it is replacing.
|
|
230
230
|
*/
|
|
231
231
|
export declare function pnpmInstallDirs(install: RunningInstall): {
|
|
232
|
+
globalDir: string;
|
|
232
233
|
storeDir: string;
|
|
233
234
|
virtualStoreDir: string;
|
|
234
235
|
} | null;
|
|
236
|
+
/** Version recorded by pnpm for the global install that owns this package. */
|
|
237
|
+
export declare function pnpmInstalledVersion(install: RunningInstall): string | null;
|
|
238
|
+
/**
|
|
239
|
+
* Resolve a bounded pnpm executable probe. pnpm 10 and older reject
|
|
240
|
+
* --virtual-store-dir for global installs; pnpm 11+ accepts the flag.
|
|
241
|
+
*/
|
|
242
|
+
export declare function probePnpmVersion(cmd: string, args: string[], env: NodeJS.ProcessEnv, cwd: string): string | null;
|
|
243
|
+
/** Run the install-owned pnpm first; use PATH only if that version cannot run. */
|
|
244
|
+
export declare function performPnpmUpdate(install: RunningInstall, runner?: UpdateRunner, env?: NodeJS.ProcessEnv, probe?: typeof probePnpmVersion): UpdateResult;
|
|
235
245
|
/** Argv for updating a Bun-managed global install. */
|
|
236
246
|
export declare function buildBunInstallArgv(): string[];
|
|
237
247
|
/**
|
|
@@ -278,11 +288,12 @@ export type UpdateResult = {
|
|
|
278
288
|
ok: boolean;
|
|
279
289
|
detail?: string;
|
|
280
290
|
code?: string;
|
|
291
|
+
stdout?: string;
|
|
281
292
|
/** Sanitized installer error code for diagnostics; never drives retry or exit behavior. */
|
|
282
293
|
diagnosticCode?: string;
|
|
283
294
|
failureKind?: "package_manager_exit";
|
|
284
295
|
};
|
|
285
|
-
type UpdateRunner = (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => UpdateResult;
|
|
296
|
+
type UpdateRunner = (cmd: string, args: string[], env?: NodeJS.ProcessEnv, cwd?: string) => UpdateResult;
|
|
286
297
|
export { buildSpawnPlan, quoteForWindowsShell };
|
|
287
298
|
/**
|
|
288
299
|
* Add `detached` to a **synchronous** spawn's options.
|
|
@@ -366,9 +377,9 @@ export interface InstallOutput {
|
|
|
366
377
|
dispose: () => void;
|
|
367
378
|
}
|
|
368
379
|
export declare function openInstallOutput(verbose: boolean, isTty?: boolean): InstallOutput;
|
|
369
|
-
export declare function runSupervisedUpdateCommand(cmd: string, args: string[], output: InstallOutput, env?: NodeJS.ProcessEnv): UpdateResult;
|
|
370
|
-
export declare function runUpdateCommand(cmd: string, args: string[], env?: NodeJS.ProcessEnv): UpdateResult;
|
|
371
|
-
declare function performUpdateCommand(cmd: string, args: string[], runner?: UpdateRunner, env?: NodeJS.ProcessEnv): UpdateResult;
|
|
380
|
+
export declare function runSupervisedUpdateCommand(cmd: string, args: string[], output: InstallOutput, env?: NodeJS.ProcessEnv, cwd?: string): UpdateResult;
|
|
381
|
+
export declare function runUpdateCommand(cmd: string, args: string[], env?: NodeJS.ProcessEnv, cwd?: string): UpdateResult;
|
|
382
|
+
declare function performUpdateCommand(cmd: string, args: string[], runner?: UpdateRunner, env?: NodeJS.ProcessEnv, cwd?: string): UpdateResult;
|
|
372
383
|
declare function performUpdate(command: string, runner?: UpdateRunner): UpdateResult;
|
|
373
384
|
/**
|
|
374
385
|
* Soft notify when the server says we're below `latestVersion` but still ≥
|
|
@@ -434,6 +445,7 @@ export declare function checkUpdateConvergence(targetVersion: string, deps?: {
|
|
|
434
445
|
/** Injectable surface for {@link enforceUpdateRequired} (unit tests). */
|
|
435
446
|
interface EnforceUpdateDeps {
|
|
436
447
|
performUpdateString?: (command: string) => UpdateResult;
|
|
448
|
+
probePnpmVersion?: typeof probePnpmVersion;
|
|
437
449
|
resolveInstall?: () => RunningInstall;
|
|
438
450
|
canWriteNpmInstall?: (install: RunningInstall) => boolean;
|
|
439
451
|
homeDir?: string;
|
|
@@ -518,6 +530,10 @@ export declare const __test__: {
|
|
|
518
530
|
enforceUpdateRequired: typeof enforceUpdateRequired;
|
|
519
531
|
isBunManagedPackageDir: typeof isBunManagedPackageDir;
|
|
520
532
|
pnpmUpdateEnv: typeof pnpmUpdateEnv;
|
|
533
|
+
pnpmInstallDirs: typeof pnpmInstallDirs;
|
|
534
|
+
pnpmInstalledVersion: typeof pnpmInstalledVersion;
|
|
535
|
+
performPnpmUpdate: typeof performPnpmUpdate;
|
|
536
|
+
probePnpmVersion: typeof probePnpmVersion;
|
|
521
537
|
isLocalDependencyInstall: typeof isLocalDependencyInstall;
|
|
522
538
|
isLocalDependencyPackageDir: typeof isLocalDependencyPackageDir;
|
|
523
539
|
isNewerVersion: typeof isNewerVersion;
|