@openparachute/hub 0.6.3 → 0.6.4-rc.10
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/package.json +1 -2
- package/src/__tests__/account-home-ui.test.ts +344 -110
- package/src/__tests__/account-mirror.test.ts +156 -0
- package/src/__tests__/account-setup.test.ts +880 -0
- package/src/__tests__/account-usage.test.ts +137 -0
- package/src/__tests__/account-vault-admin-token.test.ts +301 -0
- package/src/__tests__/account-vault-token.test.ts +53 -1
- package/src/__tests__/admin-vault-admin-token.test.ts +17 -0
- package/src/__tests__/admin-vaults.test.ts +20 -0
- package/src/__tests__/api-account.test.ts +236 -4
- package/src/__tests__/api-invites.test.ts +217 -0
- package/src/__tests__/api-mint-token.test.ts +259 -10
- package/src/__tests__/api-modules-ops.test.ts +195 -3
- package/src/__tests__/api-modules.test.ts +40 -4
- package/src/__tests__/api-settings-hub-origin.test.ts +13 -8
- package/src/__tests__/auto-wire.test.ts +101 -1
- package/src/__tests__/cli.test.ts +188 -2
- package/src/__tests__/cloudflare-state.test.ts +104 -0
- package/src/__tests__/expose-2fa-warning.test.ts +11 -8
- package/src/__tests__/expose-cloudflare.test.ts +135 -9
- package/src/__tests__/expose-interactive.test.ts +234 -7
- package/src/__tests__/expose-supervisor-version.test.ts +104 -0
- package/src/__tests__/expose.test.ts +10 -5
- package/src/__tests__/grants.test.ts +197 -8
- package/src/__tests__/hub-origin-resolution.test.ts +179 -25
- package/src/__tests__/hub-server.test.ts +761 -13
- package/src/__tests__/hub-unit.test.ts +185 -0
- package/src/__tests__/init.test.ts +579 -3
- package/src/__tests__/install.test.ts +448 -2
- package/src/__tests__/invites.test.ts +220 -0
- package/src/__tests__/launchctl-guard.test.ts +185 -0
- package/src/__tests__/migrate-cutover.test.ts +33 -0
- package/src/__tests__/module-ops-client.test.ts +68 -0
- package/src/__tests__/scope-explanations.test.ts +16 -0
- package/src/__tests__/serve-boot.test.ts +74 -1
- package/src/__tests__/serve.test.ts +121 -7
- package/src/__tests__/setup-wizard.test.ts +110 -0
- package/src/__tests__/spawn-path.test.ts +191 -0
- package/src/__tests__/status.test.ts +64 -0
- package/src/__tests__/supervisor.test.ts +374 -0
- package/src/__tests__/users.test.ts +66 -0
- package/src/__tests__/well-known.test.ts +25 -0
- package/src/__tests__/wizard.test.ts +72 -1
- package/src/account-home-ui.ts +481 -235
- package/src/account-mirror.ts +126 -0
- package/src/account-setup.ts +381 -0
- package/src/account-usage.ts +118 -0
- package/src/account-vault-admin-token.ts +242 -0
- package/src/account-vault-token.ts +36 -2
- package/src/admin-login-ui.ts +121 -0
- package/src/admin-vault-admin-token.ts +8 -2
- package/src/admin-vaults.ts +137 -29
- package/src/api-account.ts +118 -1
- package/src/api-invites.ts +345 -0
- package/src/api-mint-token.ts +81 -0
- package/src/api-modules-ops.ts +168 -53
- package/src/api-modules.ts +36 -0
- package/src/auto-wire.ts +87 -0
- package/src/cli.ts +128 -34
- package/src/cloudflare/detect.ts +1 -1
- package/src/cloudflare/state.ts +104 -8
- package/src/commands/expose-2fa-warning.ts +17 -13
- package/src/commands/expose-cloudflare.ts +103 -36
- package/src/commands/expose-interactive.ts +163 -17
- package/src/commands/expose-supervisor.ts +45 -0
- package/src/commands/init.ts +183 -4
- package/src/commands/install.ts +321 -3
- package/src/commands/migrate-cutover.ts +12 -5
- package/src/commands/serve-boot.ts +33 -3
- package/src/commands/serve.ts +158 -37
- package/src/commands/status.ts +9 -1
- package/src/commands/wizard.ts +36 -2
- package/src/grants.ts +113 -0
- package/src/help.ts +18 -5
- package/src/hub-db.ts +70 -2
- package/src/hub-server.ts +438 -41
- package/src/hub-settings.ts +3 -3
- package/src/hub-unit.ts +259 -9
- package/src/invites.ts +291 -0
- package/src/launchctl-guard.ts +131 -0
- package/src/managed-unit.ts +13 -3
- package/src/migrate-offer.ts +15 -6
- package/src/module-ops-client.ts +47 -22
- package/src/scope-attenuation.ts +19 -0
- package/src/scope-explanations.ts +9 -1
- package/src/service-spec.ts +17 -4
- package/src/setup-wizard.ts +34 -2
- package/src/spawn-path.ts +148 -0
- package/src/supervisor.ts +232 -7
- package/src/users.ts +54 -8
- package/src/vault-hub-origin-env.ts +28 -0
- package/src/vault-name.ts +13 -1
- package/src/well-known.ts +13 -0
- package/web/ui/dist/assets/{index-mz8XcVPP.css → index-BYYUeLGA.css} +1 -1
- package/web/ui/dist/assets/index-D3cDUOOj.js +61 -0
- package/web/ui/dist/index.html +2 -2
- package/web/ui/dist/assets/index-D_0TRjeo.js +0 -61
|
@@ -86,6 +86,13 @@ describe("init", () => {
|
|
|
86
86
|
const logs: string[] = [];
|
|
87
87
|
const code = await init({
|
|
88
88
|
configDir: h.configDir,
|
|
89
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
90
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
91
|
+
ensureHubVersion: async () => ({
|
|
92
|
+
outcome: "match" as const,
|
|
93
|
+
installedVersion: "test",
|
|
94
|
+
messages: [],
|
|
95
|
+
}),
|
|
89
96
|
manifestPath: h.manifestPath,
|
|
90
97
|
log: (l) => logs.push(l),
|
|
91
98
|
alive: () => false,
|
|
@@ -127,6 +134,13 @@ describe("init", () => {
|
|
|
127
134
|
const logs: string[] = [];
|
|
128
135
|
const code = await init({
|
|
129
136
|
configDir: h.configDir,
|
|
137
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
138
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
139
|
+
ensureHubVersion: async () => ({
|
|
140
|
+
outcome: "match" as const,
|
|
141
|
+
installedVersion: "test",
|
|
142
|
+
messages: [],
|
|
143
|
+
}),
|
|
130
144
|
manifestPath: h.manifestPath,
|
|
131
145
|
log: (l) => logs.push(l),
|
|
132
146
|
// No pidfile (unit-managed) → processState reports not-running.
|
|
@@ -168,6 +182,13 @@ describe("init", () => {
|
|
|
168
182
|
const logs: string[] = [];
|
|
169
183
|
const code = await init({
|
|
170
184
|
configDir: h.configDir,
|
|
185
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
186
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
187
|
+
ensureHubVersion: async () => ({
|
|
188
|
+
outcome: "match" as const,
|
|
189
|
+
installedVersion: "test",
|
|
190
|
+
messages: [],
|
|
191
|
+
}),
|
|
171
192
|
manifestPath: h.manifestPath,
|
|
172
193
|
log: (l) => logs.push(l),
|
|
173
194
|
alive: () => true,
|
|
@@ -213,6 +234,13 @@ describe("init", () => {
|
|
|
213
234
|
const logs: string[] = [];
|
|
214
235
|
const code = await init({
|
|
215
236
|
configDir: h.configDir,
|
|
237
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
238
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
239
|
+
ensureHubVersion: async () => ({
|
|
240
|
+
outcome: "match" as const,
|
|
241
|
+
installedVersion: "test",
|
|
242
|
+
messages: [],
|
|
243
|
+
}),
|
|
216
244
|
manifestPath: h.manifestPath,
|
|
217
245
|
log: (l) => logs.push(l),
|
|
218
246
|
alive: () => true,
|
|
@@ -237,6 +265,13 @@ describe("init", () => {
|
|
|
237
265
|
const opened: string[] = [];
|
|
238
266
|
const code = await init({
|
|
239
267
|
configDir: h.configDir,
|
|
268
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
269
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
270
|
+
ensureHubVersion: async () => ({
|
|
271
|
+
outcome: "match" as const,
|
|
272
|
+
installedVersion: "test",
|
|
273
|
+
messages: [],
|
|
274
|
+
}),
|
|
240
275
|
manifestPath: h.manifestPath,
|
|
241
276
|
log: () => {},
|
|
242
277
|
alive: () => false,
|
|
@@ -270,6 +305,13 @@ describe("init", () => {
|
|
|
270
305
|
const opened: string[] = [];
|
|
271
306
|
const code = await init({
|
|
272
307
|
configDir: h.configDir,
|
|
308
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
309
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
310
|
+
ensureHubVersion: async () => ({
|
|
311
|
+
outcome: "match" as const,
|
|
312
|
+
installedVersion: "test",
|
|
313
|
+
messages: [],
|
|
314
|
+
}),
|
|
273
315
|
manifestPath: h.manifestPath,
|
|
274
316
|
log: () => {},
|
|
275
317
|
alive: () => false,
|
|
@@ -306,6 +348,13 @@ describe("init", () => {
|
|
|
306
348
|
let prompted = false;
|
|
307
349
|
const code = await init({
|
|
308
350
|
configDir: h.configDir,
|
|
351
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
352
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
353
|
+
ensureHubVersion: async () => ({
|
|
354
|
+
outcome: "match" as const,
|
|
355
|
+
installedVersion: "test",
|
|
356
|
+
messages: [],
|
|
357
|
+
}),
|
|
309
358
|
manifestPath: h.manifestPath,
|
|
310
359
|
log: () => {},
|
|
311
360
|
alive: () => false,
|
|
@@ -339,6 +388,13 @@ describe("init", () => {
|
|
|
339
388
|
let prompted = false;
|
|
340
389
|
const code = await init({
|
|
341
390
|
configDir: h.configDir,
|
|
391
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
392
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
393
|
+
ensureHubVersion: async () => ({
|
|
394
|
+
outcome: "match" as const,
|
|
395
|
+
installedVersion: "test",
|
|
396
|
+
messages: [],
|
|
397
|
+
}),
|
|
342
398
|
manifestPath: h.manifestPath,
|
|
343
399
|
log: () => {},
|
|
344
400
|
alive: () => false,
|
|
@@ -366,6 +422,13 @@ describe("init", () => {
|
|
|
366
422
|
const opened: string[] = [];
|
|
367
423
|
const code = await init({
|
|
368
424
|
configDir: h.configDir,
|
|
425
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
426
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
427
|
+
ensureHubVersion: async () => ({
|
|
428
|
+
outcome: "match" as const,
|
|
429
|
+
installedVersion: "test",
|
|
430
|
+
messages: [],
|
|
431
|
+
}),
|
|
369
432
|
manifestPath: h.manifestPath,
|
|
370
433
|
log: () => {},
|
|
371
434
|
alive: () => false,
|
|
@@ -400,6 +463,13 @@ describe("init", () => {
|
|
|
400
463
|
const logs: string[] = [];
|
|
401
464
|
const code = await init({
|
|
402
465
|
configDir: h.configDir,
|
|
466
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
467
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
468
|
+
ensureHubVersion: async () => ({
|
|
469
|
+
outcome: "match" as const,
|
|
470
|
+
installedVersion: "test",
|
|
471
|
+
messages: [],
|
|
472
|
+
}),
|
|
403
473
|
manifestPath: h.manifestPath,
|
|
404
474
|
log: (l) => logs.push(l),
|
|
405
475
|
alive: () => false,
|
|
@@ -434,6 +504,13 @@ describe("init", () => {
|
|
|
434
504
|
const opened: string[] = [];
|
|
435
505
|
const code = await init({
|
|
436
506
|
configDir: h.configDir,
|
|
507
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
508
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
509
|
+
ensureHubVersion: async () => ({
|
|
510
|
+
outcome: "match" as const,
|
|
511
|
+
installedVersion: "test",
|
|
512
|
+
messages: [],
|
|
513
|
+
}),
|
|
437
514
|
manifestPath: h.manifestPath,
|
|
438
515
|
log: () => {},
|
|
439
516
|
alive: () => false,
|
|
@@ -464,6 +541,13 @@ describe("init", () => {
|
|
|
464
541
|
const logs: string[] = [];
|
|
465
542
|
const code = await init({
|
|
466
543
|
configDir: h.configDir,
|
|
544
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
545
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
546
|
+
ensureHubVersion: async () => ({
|
|
547
|
+
outcome: "match" as const,
|
|
548
|
+
installedVersion: "test",
|
|
549
|
+
messages: [],
|
|
550
|
+
}),
|
|
467
551
|
manifestPath: h.manifestPath,
|
|
468
552
|
log: (l) => logs.push(l),
|
|
469
553
|
alive: () => false,
|
|
@@ -485,6 +569,340 @@ describe("init", () => {
|
|
|
485
569
|
});
|
|
486
570
|
});
|
|
487
571
|
|
|
572
|
+
describe("init — version-check-and-restart at the hub adoption point (#590)", () => {
|
|
573
|
+
test("invokes the version check with the resolved hub port after the hub is up", async () => {
|
|
574
|
+
const h = makeHarness();
|
|
575
|
+
try {
|
|
576
|
+
const logs: string[] = [];
|
|
577
|
+
let seenPort: number | undefined;
|
|
578
|
+
const code = await init({
|
|
579
|
+
configDir: h.configDir,
|
|
580
|
+
manifestPath: h.manifestPath,
|
|
581
|
+
log: (l) => logs.push(l),
|
|
582
|
+
alive: () => false,
|
|
583
|
+
ensureHub: async () => {
|
|
584
|
+
writeHubPort(1939, h.configDir);
|
|
585
|
+
return { pid: 0, port: 1939, started: false };
|
|
586
|
+
},
|
|
587
|
+
ensureHubVersion: async ({ port, log }) => {
|
|
588
|
+
seenPort = port;
|
|
589
|
+
log(
|
|
590
|
+
"⚠ the running hub is 0.5.14-rc.4 but 0.6.4-rc.9 is installed — restarting the hub unit to pick up the new code.",
|
|
591
|
+
);
|
|
592
|
+
return {
|
|
593
|
+
outcome: "restarted" as const,
|
|
594
|
+
runningVersion: "0.6.4-rc.9",
|
|
595
|
+
installedVersion: "0.6.4-rc.9",
|
|
596
|
+
messages: ["✓ hub unit restarted; now running 0.6.4-rc.9."],
|
|
597
|
+
};
|
|
598
|
+
},
|
|
599
|
+
readExposeStateFn: () => undefined,
|
|
600
|
+
isTty: false,
|
|
601
|
+
platform: "linux",
|
|
602
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
603
|
+
});
|
|
604
|
+
expect(code).toBe(0);
|
|
605
|
+
expect(seenPort).toBe(1939);
|
|
606
|
+
const joined = logs.join("\n");
|
|
607
|
+
// The mismatch notice + the restart confirmation both surface.
|
|
608
|
+
expect(joined).toContain("restarting the hub unit to pick up the new code");
|
|
609
|
+
expect(joined).toContain("now running 0.6.4-rc.9");
|
|
610
|
+
} finally {
|
|
611
|
+
h.cleanup();
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
test("a not-unit-managed mismatch bails init with exit 1 (don't wire a tunnel to a zombie)", async () => {
|
|
616
|
+
const h = makeHarness();
|
|
617
|
+
try {
|
|
618
|
+
const logs: string[] = [];
|
|
619
|
+
let exposeRan = false;
|
|
620
|
+
const code = await init({
|
|
621
|
+
configDir: h.configDir,
|
|
622
|
+
manifestPath: h.manifestPath,
|
|
623
|
+
log: (l) => logs.push(l),
|
|
624
|
+
alive: () => false,
|
|
625
|
+
ensureHub: async () => {
|
|
626
|
+
writeHubPort(1939, h.configDir);
|
|
627
|
+
return { pid: 0, port: 1939, started: false };
|
|
628
|
+
},
|
|
629
|
+
ensureHubVersion: async () => ({
|
|
630
|
+
outcome: "not-unit-managed" as const,
|
|
631
|
+
runningVersion: "0.5.14-rc.4",
|
|
632
|
+
installedVersion: "0.6.4-rc.9",
|
|
633
|
+
messages: [
|
|
634
|
+
"⚠ the running hub is 0.5.14-rc.4 but 0.6.4-rc.9 is installed.",
|
|
635
|
+
" The running hub is NOT managed by a Parachute service unit (a detached process or a foreground `parachute serve`), so it won't be restarted automatically.",
|
|
636
|
+
],
|
|
637
|
+
}),
|
|
638
|
+
// If init wrongly continued, this would run — assert it does NOT.
|
|
639
|
+
exposeChoice: "tailnet",
|
|
640
|
+
exposeTailnetImpl: async () => {
|
|
641
|
+
exposeRan = true;
|
|
642
|
+
return 0;
|
|
643
|
+
},
|
|
644
|
+
readExposeStateFn: () => undefined,
|
|
645
|
+
isTty: false,
|
|
646
|
+
platform: "linux",
|
|
647
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
648
|
+
});
|
|
649
|
+
expect(code).toBe(1);
|
|
650
|
+
expect(exposeRan).toBe(false);
|
|
651
|
+
const joined = logs.join("\n");
|
|
652
|
+
expect(joined).toContain("NOT managed by a Parachute service unit");
|
|
653
|
+
expect(joined).toContain("re-run `parachute init`");
|
|
654
|
+
} finally {
|
|
655
|
+
h.cleanup();
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test("a restart-failed outcome bails init with exit 1 + an orienting line (don't continue past a failed restart)", async () => {
|
|
660
|
+
const h = makeHarness();
|
|
661
|
+
try {
|
|
662
|
+
const logs: string[] = [];
|
|
663
|
+
let exposeRan = false;
|
|
664
|
+
const code = await init({
|
|
665
|
+
configDir: h.configDir,
|
|
666
|
+
manifestPath: h.manifestPath,
|
|
667
|
+
log: (l) => logs.push(l),
|
|
668
|
+
alive: () => false,
|
|
669
|
+
ensureHub: async () => {
|
|
670
|
+
writeHubPort(1939, h.configDir);
|
|
671
|
+
return { pid: 0, port: 1939, started: false };
|
|
672
|
+
},
|
|
673
|
+
ensureHubVersion: async () => ({
|
|
674
|
+
outcome: "restart-failed" as const,
|
|
675
|
+
runningVersion: "0.5.14-rc.4",
|
|
676
|
+
installedVersion: "0.6.4-rc.9",
|
|
677
|
+
messages: [
|
|
678
|
+
"⚠ the running hub is 0.5.14-rc.4 but 0.6.4-rc.9 is installed, and the hub unit restart failed.",
|
|
679
|
+
"failed to restart the hub unit via the service manager (Unit parachute-hub.service not found.)",
|
|
680
|
+
],
|
|
681
|
+
}),
|
|
682
|
+
// If init wrongly continued past the failed restart, this would run.
|
|
683
|
+
exposeChoice: "tailnet",
|
|
684
|
+
exposeTailnetImpl: async () => {
|
|
685
|
+
exposeRan = true;
|
|
686
|
+
return 0;
|
|
687
|
+
},
|
|
688
|
+
readExposeStateFn: () => undefined,
|
|
689
|
+
isTty: false,
|
|
690
|
+
platform: "linux",
|
|
691
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
692
|
+
});
|
|
693
|
+
expect(code).toBe(1);
|
|
694
|
+
expect(exposeRan).toBe(false);
|
|
695
|
+
const joined = logs.join("\n");
|
|
696
|
+
// The version-check messages + the orienting line + the logs hint surface.
|
|
697
|
+
expect(joined).toContain("the hub unit restart failed");
|
|
698
|
+
expect(joined).toContain("The hub service manager rejected the restart command.");
|
|
699
|
+
expect(joined).toContain("parachute logs hub");
|
|
700
|
+
} finally {
|
|
701
|
+
h.cleanup();
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
test("a still-mismatched outcome warns but continues (bun-linked branch)", async () => {
|
|
706
|
+
const h = makeHarness();
|
|
707
|
+
try {
|
|
708
|
+
const logs: string[] = [];
|
|
709
|
+
const code = await init({
|
|
710
|
+
configDir: h.configDir,
|
|
711
|
+
manifestPath: h.manifestPath,
|
|
712
|
+
log: (l) => logs.push(l),
|
|
713
|
+
alive: () => false,
|
|
714
|
+
ensureHub: async () => {
|
|
715
|
+
writeHubPort(1939, h.configDir);
|
|
716
|
+
return { pid: 0, port: 1939, started: false };
|
|
717
|
+
},
|
|
718
|
+
ensureHubVersion: async () => ({
|
|
719
|
+
outcome: "still-mismatched" as const,
|
|
720
|
+
runningVersion: "0.6.4-rc.8",
|
|
721
|
+
installedVersion: "0.6.4-rc.9",
|
|
722
|
+
messages: [
|
|
723
|
+
"⚠ restarted the hub unit, but it is still not reporting 0.6.4-rc.9 (reports 0.6.4-rc.8).",
|
|
724
|
+
],
|
|
725
|
+
}),
|
|
726
|
+
readExposeStateFn: () => undefined,
|
|
727
|
+
isTty: false,
|
|
728
|
+
platform: "linux",
|
|
729
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
730
|
+
});
|
|
731
|
+
// Continues (does NOT bail) — the warning surfaces but init reaches the URL.
|
|
732
|
+
expect(code).toBe(0);
|
|
733
|
+
const joined = logs.join("\n");
|
|
734
|
+
expect(joined).toContain("still not reporting 0.6.4-rc.9");
|
|
735
|
+
expect(joined).toContain("http://127.0.0.1:1939/admin/");
|
|
736
|
+
} finally {
|
|
737
|
+
h.cleanup();
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
describe("init bootstrap-token first-claim (hub#576)", () => {
|
|
743
|
+
function publicState(): ExposeState {
|
|
744
|
+
return {
|
|
745
|
+
version: 1,
|
|
746
|
+
layer: "public",
|
|
747
|
+
mode: "path",
|
|
748
|
+
canonicalFqdn: "demo.parachute.computer",
|
|
749
|
+
port: 443,
|
|
750
|
+
funnel: false,
|
|
751
|
+
entries: [],
|
|
752
|
+
hubOrigin: "https://demo.parachute.computer",
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
test("publicly-exposed + wizard mode: prints the bootstrap token in the terminal", async () => {
|
|
757
|
+
const h = makeHarness();
|
|
758
|
+
try {
|
|
759
|
+
writeHubPort(1939, h.configDir);
|
|
760
|
+
const probed: string[] = [];
|
|
761
|
+
const logs: string[] = [];
|
|
762
|
+
const code = await init({
|
|
763
|
+
configDir: h.configDir,
|
|
764
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
765
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
766
|
+
ensureHubVersion: async () => ({
|
|
767
|
+
outcome: "match" as const,
|
|
768
|
+
installedVersion: "test",
|
|
769
|
+
messages: [],
|
|
770
|
+
}),
|
|
771
|
+
manifestPath: h.manifestPath,
|
|
772
|
+
log: (l) => logs.push(l),
|
|
773
|
+
alive: () => true,
|
|
774
|
+
ensureHub: async () => ({ pid: 4321, port: 1939, started: false }),
|
|
775
|
+
readExposeStateFn: () => publicState(),
|
|
776
|
+
isTty: false,
|
|
777
|
+
platform: "linux",
|
|
778
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
779
|
+
fetchBootstrapTokenImpl: async (loopbackUrl) => {
|
|
780
|
+
probed.push(loopbackUrl);
|
|
781
|
+
return "parachute-bootstrap-XYZ";
|
|
782
|
+
},
|
|
783
|
+
});
|
|
784
|
+
expect(code).toBe(0);
|
|
785
|
+
// Probed the LOOPBACK hub, not the public FQDN.
|
|
786
|
+
expect(probed).toEqual(["http://127.0.0.1:1939"]);
|
|
787
|
+
const joined = logs.join("\n");
|
|
788
|
+
expect(joined).toContain("parachute-bootstrap-XYZ");
|
|
789
|
+
expect(joined).toContain("bootstrap token");
|
|
790
|
+
// Still prints the public admin URL.
|
|
791
|
+
expect(joined).toContain("https://demo.parachute.computer/admin/");
|
|
792
|
+
} finally {
|
|
793
|
+
h.cleanup();
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
test("loopback-only install: does NOT probe or print a token", async () => {
|
|
798
|
+
const h = makeHarness();
|
|
799
|
+
try {
|
|
800
|
+
writeHubPort(1939, h.configDir);
|
|
801
|
+
let probedCount = 0;
|
|
802
|
+
const logs: string[] = [];
|
|
803
|
+
const code = await init({
|
|
804
|
+
configDir: h.configDir,
|
|
805
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
806
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
807
|
+
ensureHubVersion: async () => ({
|
|
808
|
+
outcome: "match" as const,
|
|
809
|
+
installedVersion: "test",
|
|
810
|
+
messages: [],
|
|
811
|
+
}),
|
|
812
|
+
manifestPath: h.manifestPath,
|
|
813
|
+
log: (l) => logs.push(l),
|
|
814
|
+
alive: () => true,
|
|
815
|
+
ensureHub: async () => ({ pid: 4321, port: 1939, started: false }),
|
|
816
|
+
readExposeStateFn: () => undefined, // no public exposure
|
|
817
|
+
isTty: false,
|
|
818
|
+
platform: "linux",
|
|
819
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
820
|
+
fetchBootstrapTokenImpl: async () => {
|
|
821
|
+
probedCount++;
|
|
822
|
+
return "parachute-bootstrap-XYZ";
|
|
823
|
+
},
|
|
824
|
+
});
|
|
825
|
+
expect(code).toBe(0);
|
|
826
|
+
expect(probedCount).toBe(0);
|
|
827
|
+
expect(logs.join("\n")).not.toContain("parachute-bootstrap-");
|
|
828
|
+
} finally {
|
|
829
|
+
h.cleanup();
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
test("admin already exists (no token): prints the URL without a token block", async () => {
|
|
834
|
+
const h = makeHarness();
|
|
835
|
+
try {
|
|
836
|
+
writeHubPort(1939, h.configDir);
|
|
837
|
+
const logs: string[] = [];
|
|
838
|
+
const code = await init({
|
|
839
|
+
configDir: h.configDir,
|
|
840
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
841
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
842
|
+
ensureHubVersion: async () => ({
|
|
843
|
+
outcome: "match" as const,
|
|
844
|
+
installedVersion: "test",
|
|
845
|
+
messages: [],
|
|
846
|
+
}),
|
|
847
|
+
manifestPath: h.manifestPath,
|
|
848
|
+
log: (l) => logs.push(l),
|
|
849
|
+
alive: () => true,
|
|
850
|
+
ensureHub: async () => ({ pid: 4321, port: 1939, started: false }),
|
|
851
|
+
readExposeStateFn: () => publicState(),
|
|
852
|
+
isTty: false,
|
|
853
|
+
platform: "linux",
|
|
854
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
855
|
+
// Hub returns undefined → already-claimed / no token to surface.
|
|
856
|
+
fetchBootstrapTokenImpl: async () => undefined,
|
|
857
|
+
});
|
|
858
|
+
expect(code).toBe(0);
|
|
859
|
+
const joined = logs.join("\n");
|
|
860
|
+
expect(joined).toContain("https://demo.parachute.computer/admin/");
|
|
861
|
+
expect(joined).not.toContain("bootstrap token");
|
|
862
|
+
} finally {
|
|
863
|
+
h.cleanup();
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
test("CLI wizard is driven against the LOOPBACK hub, not the public FQDN", async () => {
|
|
868
|
+
const h = makeHarness();
|
|
869
|
+
try {
|
|
870
|
+
writeHubPort(1939, h.configDir);
|
|
871
|
+
const wizardUrls: string[] = [];
|
|
872
|
+
const code = await init({
|
|
873
|
+
configDir: h.configDir,
|
|
874
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
875
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
876
|
+
ensureHubVersion: async () => ({
|
|
877
|
+
outcome: "match" as const,
|
|
878
|
+
installedVersion: "test",
|
|
879
|
+
messages: [],
|
|
880
|
+
}),
|
|
881
|
+
manifestPath: h.manifestPath,
|
|
882
|
+
log: () => {},
|
|
883
|
+
alive: () => true,
|
|
884
|
+
ensureHub: async () => ({ pid: 4321, port: 1939, started: false }),
|
|
885
|
+
readExposeStateFn: () => publicState(),
|
|
886
|
+
isTty: false,
|
|
887
|
+
platform: "linux",
|
|
888
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
889
|
+
wizardChoice: "cli",
|
|
890
|
+
fetchBootstrapTokenImpl: async () => "parachute-bootstrap-XYZ",
|
|
891
|
+
runCliWizardImpl: async ({ hubUrl }) => {
|
|
892
|
+
wizardUrls.push(hubUrl);
|
|
893
|
+
return 0;
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
expect(code).toBe(0);
|
|
897
|
+
// The CLI wizard runs on-box → must use loopback (where the hub hands it
|
|
898
|
+
// the token transparently), never the public FQDN.
|
|
899
|
+
expect(wizardUrls).toEqual(["http://127.0.0.1:1939"]);
|
|
900
|
+
} finally {
|
|
901
|
+
h.cleanup();
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
|
|
488
906
|
describe("looksLikeServer heuristic", () => {
|
|
489
907
|
test("macOS is never a server", () => {
|
|
490
908
|
expect(looksLikeServer("darwin", { SSH_CONNECTION: "1.2.3.4 22 5.6.7.8 22" })).toBe(false);
|
|
@@ -550,6 +968,13 @@ describe("init exposure chain", () => {
|
|
|
550
968
|
const promptCalls: string[] = [];
|
|
551
969
|
const code = await init({
|
|
552
970
|
configDir: h.configDir,
|
|
971
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
972
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
973
|
+
ensureHubVersion: async () => ({
|
|
974
|
+
outcome: "match" as const,
|
|
975
|
+
installedVersion: "test",
|
|
976
|
+
messages: [],
|
|
977
|
+
}),
|
|
553
978
|
manifestPath: h.manifestPath,
|
|
554
979
|
log: () => {},
|
|
555
980
|
alive: () => false,
|
|
@@ -583,6 +1008,13 @@ describe("init exposure chain", () => {
|
|
|
583
1008
|
const promptCalls: string[] = [];
|
|
584
1009
|
const code = await init({
|
|
585
1010
|
configDir: h.configDir,
|
|
1011
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1012
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1013
|
+
ensureHubVersion: async () => ({
|
|
1014
|
+
outcome: "match" as const,
|
|
1015
|
+
installedVersion: "test",
|
|
1016
|
+
messages: [],
|
|
1017
|
+
}),
|
|
586
1018
|
manifestPath: h.manifestPath,
|
|
587
1019
|
log: () => {},
|
|
588
1020
|
alive: () => false,
|
|
@@ -629,6 +1061,13 @@ describe("init exposure chain", () => {
|
|
|
629
1061
|
const promptCalls: string[] = [];
|
|
630
1062
|
const code = await init({
|
|
631
1063
|
configDir: h.configDir,
|
|
1064
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1065
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1066
|
+
ensureHubVersion: async () => ({
|
|
1067
|
+
outcome: "match" as const,
|
|
1068
|
+
installedVersion: "test",
|
|
1069
|
+
messages: [],
|
|
1070
|
+
}),
|
|
632
1071
|
manifestPath: h.manifestPath,
|
|
633
1072
|
log: () => {},
|
|
634
1073
|
alive: () => false,
|
|
@@ -672,6 +1111,13 @@ describe("init exposure chain", () => {
|
|
|
672
1111
|
let cloudflareCalls = 0;
|
|
673
1112
|
const code = await init({
|
|
674
1113
|
configDir: h.configDir,
|
|
1114
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1115
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1116
|
+
ensureHubVersion: async () => ({
|
|
1117
|
+
outcome: "match" as const,
|
|
1118
|
+
installedVersion: "test",
|
|
1119
|
+
messages: [],
|
|
1120
|
+
}),
|
|
675
1121
|
manifestPath: h.manifestPath,
|
|
676
1122
|
log: () => {},
|
|
677
1123
|
alive: () => false,
|
|
@@ -706,6 +1152,13 @@ describe("init exposure chain", () => {
|
|
|
706
1152
|
let cloudflareCalls = 0;
|
|
707
1153
|
const code = await init({
|
|
708
1154
|
configDir: h.configDir,
|
|
1155
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1156
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1157
|
+
ensureHubVersion: async () => ({
|
|
1158
|
+
outcome: "match" as const,
|
|
1159
|
+
installedVersion: "test",
|
|
1160
|
+
messages: [],
|
|
1161
|
+
}),
|
|
709
1162
|
manifestPath: h.manifestPath,
|
|
710
1163
|
log: () => {},
|
|
711
1164
|
alive: () => false,
|
|
@@ -748,6 +1201,13 @@ describe("init exposure chain", () => {
|
|
|
748
1201
|
};
|
|
749
1202
|
await init({
|
|
750
1203
|
configDir: h.configDir,
|
|
1204
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1205
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1206
|
+
ensureHubVersion: async () => ({
|
|
1207
|
+
outcome: "match" as const,
|
|
1208
|
+
installedVersion: "test",
|
|
1209
|
+
messages: [],
|
|
1210
|
+
}),
|
|
751
1211
|
manifestPath: h.manifestPath,
|
|
752
1212
|
log: (l) => promptLog.push(l),
|
|
753
1213
|
alive: () => false,
|
|
@@ -782,6 +1242,13 @@ describe("init exposure chain", () => {
|
|
|
782
1242
|
setChained("none");
|
|
783
1243
|
await init({
|
|
784
1244
|
configDir: h.configDir,
|
|
1245
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1246
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1247
|
+
ensureHubVersion: async () => ({
|
|
1248
|
+
outcome: "match" as const,
|
|
1249
|
+
installedVersion: "test",
|
|
1250
|
+
messages: [],
|
|
1251
|
+
}),
|
|
785
1252
|
manifestPath: h.manifestPath,
|
|
786
1253
|
log: (l) => promptLog.push(l),
|
|
787
1254
|
alive: () => true,
|
|
@@ -831,6 +1298,13 @@ describe("init exposure chain", () => {
|
|
|
831
1298
|
const logs: string[] = [];
|
|
832
1299
|
const code = await init({
|
|
833
1300
|
configDir: h.configDir,
|
|
1301
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1302
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1303
|
+
ensureHubVersion: async () => ({
|
|
1304
|
+
outcome: "match" as const,
|
|
1305
|
+
installedVersion: "test",
|
|
1306
|
+
messages: [],
|
|
1307
|
+
}),
|
|
834
1308
|
manifestPath: h.manifestPath,
|
|
835
1309
|
log: (l) => logs.push(l),
|
|
836
1310
|
alive: () => false,
|
|
@@ -874,6 +1348,13 @@ describe("init exposure chain", () => {
|
|
|
874
1348
|
let chained = false;
|
|
875
1349
|
const code = await init({
|
|
876
1350
|
configDir: h.configDir,
|
|
1351
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1352
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1353
|
+
ensureHubVersion: async () => ({
|
|
1354
|
+
outcome: "match" as const,
|
|
1355
|
+
installedVersion: "test",
|
|
1356
|
+
messages: [],
|
|
1357
|
+
}),
|
|
877
1358
|
manifestPath: h.manifestPath,
|
|
878
1359
|
log: () => {},
|
|
879
1360
|
alive: () => false,
|
|
@@ -898,14 +1379,22 @@ describe("init exposure chain", () => {
|
|
|
898
1379
|
}
|
|
899
1380
|
});
|
|
900
1381
|
|
|
901
|
-
test("exposure chain
|
|
1382
|
+
test("hub#565: a failed exposure chain does NOT abort init — warns, continues, exits 0", async () => {
|
|
902
1383
|
const h = makeHarness();
|
|
903
1384
|
try {
|
|
904
1385
|
writeHubPort(1939, h.configDir);
|
|
1386
|
+
const logs: string[] = [];
|
|
905
1387
|
const code = await init({
|
|
906
1388
|
configDir: h.configDir,
|
|
1389
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1390
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1391
|
+
ensureHubVersion: async () => ({
|
|
1392
|
+
outcome: "match" as const,
|
|
1393
|
+
installedVersion: "test",
|
|
1394
|
+
messages: [],
|
|
1395
|
+
}),
|
|
907
1396
|
manifestPath: h.manifestPath,
|
|
908
|
-
log: () =>
|
|
1397
|
+
log: (l) => logs.push(l),
|
|
909
1398
|
alive: () => false,
|
|
910
1399
|
ensureHub: async () => ({ pid: 7, port: 1939, started: true }),
|
|
911
1400
|
readExposeStateFn: () => undefined,
|
|
@@ -915,8 +1404,53 @@ describe("init exposure chain", () => {
|
|
|
915
1404
|
exposeTailnetImpl: async () => 0,
|
|
916
1405
|
exposeCloudflareImpl: async () => 2,
|
|
917
1406
|
exposeChoice: "cloudflare",
|
|
1407
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
918
1408
|
});
|
|
919
|
-
|
|
1409
|
+
// Init reaches the admin-URL/wizard handoff regardless of the expose
|
|
1410
|
+
// failure (exposure is an enhancement, not a prerequisite).
|
|
1411
|
+
expect(code).toBe(0);
|
|
1412
|
+
const joined = logs.join("\n");
|
|
1413
|
+
// Warned about the failure + printed the exact retry command (the
|
|
1414
|
+
// `--cloudflare` flag matters — bare `expose public` defaults to
|
|
1415
|
+
// Tailscale, hub#566).
|
|
1416
|
+
expect(joined).toContain("Couldn't finish setting up public access");
|
|
1417
|
+
expect(joined).toContain("parachute expose public --cloudflare");
|
|
1418
|
+
// Fell through to the loopback admin URL.
|
|
1419
|
+
expect(joined).toContain("http://127.0.0.1:1939/admin/");
|
|
1420
|
+
} finally {
|
|
1421
|
+
h.cleanup();
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
test("hub#565: tailnet expose failure prints the --tailnet retry command", async () => {
|
|
1426
|
+
const h = makeHarness();
|
|
1427
|
+
try {
|
|
1428
|
+
writeHubPort(1939, h.configDir);
|
|
1429
|
+
const logs: string[] = [];
|
|
1430
|
+
const code = await init({
|
|
1431
|
+
configDir: h.configDir,
|
|
1432
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1433
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1434
|
+
ensureHubVersion: async () => ({
|
|
1435
|
+
outcome: "match" as const,
|
|
1436
|
+
installedVersion: "test",
|
|
1437
|
+
messages: [],
|
|
1438
|
+
}),
|
|
1439
|
+
manifestPath: h.manifestPath,
|
|
1440
|
+
log: (l) => logs.push(l),
|
|
1441
|
+
alive: () => false,
|
|
1442
|
+
ensureHub: async () => ({ pid: 7, port: 1939, started: true }),
|
|
1443
|
+
readExposeStateFn: () => undefined,
|
|
1444
|
+
isTty: false,
|
|
1445
|
+
platform: "linux",
|
|
1446
|
+
env: {},
|
|
1447
|
+
exposeTailnetImpl: async () => 3,
|
|
1448
|
+
exposeCloudflareImpl: async () => 0,
|
|
1449
|
+
exposeChoice: "tailnet",
|
|
1450
|
+
installVaultModuleImpl: noopVaultInstall,
|
|
1451
|
+
});
|
|
1452
|
+
expect(code).toBe(0);
|
|
1453
|
+
expect(logs.join("\n")).toContain("parachute expose public --tailnet");
|
|
920
1454
|
} finally {
|
|
921
1455
|
h.cleanup();
|
|
922
1456
|
}
|
|
@@ -936,6 +1470,13 @@ describe("init exposure chain", () => {
|
|
|
936
1470
|
const order: string[] = [];
|
|
937
1471
|
const code = await init({
|
|
938
1472
|
configDir: h.configDir,
|
|
1473
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1474
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1475
|
+
ensureHubVersion: async () => ({
|
|
1476
|
+
outcome: "match" as const,
|
|
1477
|
+
installedVersion: "test",
|
|
1478
|
+
messages: [],
|
|
1479
|
+
}),
|
|
939
1480
|
manifestPath: h.manifestPath,
|
|
940
1481
|
log: () => {},
|
|
941
1482
|
alive: () => false,
|
|
@@ -981,6 +1522,13 @@ describe("init exposure chain", () => {
|
|
|
981
1522
|
|
|
982
1523
|
const code = await init({
|
|
983
1524
|
configDir: h.configDir,
|
|
1525
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1526
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1527
|
+
ensureHubVersion: async () => ({
|
|
1528
|
+
outcome: "match" as const,
|
|
1529
|
+
installedVersion: "test",
|
|
1530
|
+
messages: [],
|
|
1531
|
+
}),
|
|
984
1532
|
manifestPath: h.manifestPath,
|
|
985
1533
|
log: () => {},
|
|
986
1534
|
alive: () => false,
|
|
@@ -1018,6 +1566,13 @@ describe("init exposure chain", () => {
|
|
|
1018
1566
|
|
|
1019
1567
|
const code = await init({
|
|
1020
1568
|
configDir: h.configDir,
|
|
1569
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1570
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1571
|
+
ensureHubVersion: async () => ({
|
|
1572
|
+
outcome: "match" as const,
|
|
1573
|
+
installedVersion: "test",
|
|
1574
|
+
messages: [],
|
|
1575
|
+
}),
|
|
1021
1576
|
manifestPath: h.manifestPath,
|
|
1022
1577
|
log: () => {},
|
|
1023
1578
|
alive: () => false,
|
|
@@ -1046,6 +1601,13 @@ describe("init exposure chain", () => {
|
|
|
1046
1601
|
// wizard creates first-admin.
|
|
1047
1602
|
const code = await init({
|
|
1048
1603
|
configDir: h.configDir,
|
|
1604
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1605
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1606
|
+
ensureHubVersion: async () => ({
|
|
1607
|
+
outcome: "match" as const,
|
|
1608
|
+
installedVersion: "test",
|
|
1609
|
+
messages: [],
|
|
1610
|
+
}),
|
|
1049
1611
|
manifestPath: h.manifestPath,
|
|
1050
1612
|
log: () => {},
|
|
1051
1613
|
alive: () => false,
|
|
@@ -1072,6 +1634,13 @@ describe("init exposure chain", () => {
|
|
|
1072
1634
|
const logs: string[] = [];
|
|
1073
1635
|
const code = await init({
|
|
1074
1636
|
configDir: h.configDir,
|
|
1637
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1638
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1639
|
+
ensureHubVersion: async () => ({
|
|
1640
|
+
outcome: "match" as const,
|
|
1641
|
+
installedVersion: "test",
|
|
1642
|
+
messages: [],
|
|
1643
|
+
}),
|
|
1075
1644
|
manifestPath: h.manifestPath,
|
|
1076
1645
|
log: (l) => logs.push(l),
|
|
1077
1646
|
alive: () => false,
|
|
@@ -1114,6 +1683,13 @@ describe("init exposure chain", () => {
|
|
|
1114
1683
|
const logs: string[] = [];
|
|
1115
1684
|
const code = await init({
|
|
1116
1685
|
configDir: h.configDir,
|
|
1686
|
+
// #590: keep the version-check a no-op in init tests — NEVER let the
|
|
1687
|
+
// production default fire a real /health fetch + restart the live unit.
|
|
1688
|
+
ensureHubVersion: async () => ({
|
|
1689
|
+
outcome: "match" as const,
|
|
1690
|
+
installedVersion: "test",
|
|
1691
|
+
messages: [],
|
|
1692
|
+
}),
|
|
1117
1693
|
manifestPath: h.manifestPath,
|
|
1118
1694
|
log: (l) => logs.push(l),
|
|
1119
1695
|
alive: () => false,
|