@mycadet/cli 0.12.6-dev.202610091338.ee7f3e3 → 0.12.7
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 +4 -3
- package/src/__tests__/hatch-provider-secrets.test.ts +3 -3
- package/src/__tests__/nginx-ingress.test.ts +66 -74
- package/src/__tests__/pair.test.ts +135 -121
- package/src/__tests__/uninstall.test.ts +171 -0
- package/src/commands/client.ts +76 -34
- package/src/commands/ps.ts +1 -0
- package/src/commands/uninstall.ts +228 -0
- package/src/index.ts +2 -0
- package/src/lib/assistant-config.ts +9 -13
- package/src/lib/backup-ops.ts +3 -4
- package/src/lib/client-identity.ts +8 -10
- package/src/lib/guardian-token.ts +72 -18
- package/src/lib/health-check.ts +24 -15
- package/src/lib/local-hatch-lock.ts +19 -27
- package/src/lib/local-runtime-client.ts +25 -14
- package/src/lib/ngrok.ts +48 -17
- package/src/lib/platform-client.ts +141 -72
- package/src/lib/retire-archive.ts +4 -4
- package/src/lib/retire-local.ts +2 -25
- package/src/lib/teleport-backup.ts +22 -13
- package/src/lib/terminal-client.ts +19 -12
- package/src/lib/tunnel-edge.ts +11 -13
- package/src/lib/upgrade-lifecycle.ts +16 -4
- package/src/lib/workos-pkce.ts +51 -25
- package/src/lib/json.ts +0 -6
|
@@ -21,6 +21,14 @@ process.env.CADET_ENVIRONMENT = "production";
|
|
|
21
21
|
|
|
22
22
|
import { buildAppConnectUrl, pair } from "../commands/pair.js";
|
|
23
23
|
|
|
24
|
+
// bun-types' global fetch carries a `preconnect` member; these mocks only need
|
|
25
|
+
// the call shape, so hand the assignment a member-complete callable.
|
|
26
|
+
function withPreconnect(
|
|
27
|
+
impl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>,
|
|
28
|
+
): typeof fetch {
|
|
29
|
+
return Object.assign(impl, { preconnect: () => {} });
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
// Distinct loopback (mint) vs reachable (advertised) URLs to verify the split.
|
|
25
33
|
const LOCAL_URL = "http://127.0.0.1:7830";
|
|
26
34
|
const RUNTIME_URL = "http://192.168.1.50:7830";
|
|
@@ -55,7 +63,8 @@ function stubPairingGateway(
|
|
|
55
63
|
calls: Array<[string, RequestInit | undefined]>,
|
|
56
64
|
baseUrl = PUBLIC_URL,
|
|
57
65
|
): void {
|
|
58
|
-
globalThis.fetch = (async (
|
|
66
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
67
|
+
const url = String(input);
|
|
59
68
|
calls.push([url, init]);
|
|
60
69
|
if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
|
|
61
70
|
return challengeResponse(baseUrl);
|
|
@@ -64,12 +73,12 @@ function stubPairingGateway(
|
|
|
64
73
|
return approvalResponse(baseUrl);
|
|
65
74
|
}
|
|
66
75
|
return new Response("not found", { status: 404 });
|
|
67
|
-
})
|
|
76
|
+
});
|
|
68
77
|
}
|
|
69
78
|
|
|
70
79
|
function writeLockfile(): void {
|
|
71
80
|
writeFileSync(
|
|
72
|
-
join(testDir, ".
|
|
81
|
+
join(testDir, "lockfile.json"),
|
|
73
82
|
JSON.stringify({
|
|
74
83
|
assistants: [
|
|
75
84
|
{
|
|
@@ -134,10 +143,10 @@ describe("pair command", () => {
|
|
|
134
143
|
`${LOCAL_URL}/v1/remote-web/pairing-challenge`,
|
|
135
144
|
`${LOCAL_URL}/v1/remote-web/pairing-verification`,
|
|
136
145
|
]);
|
|
137
|
-
expect(JSON.parse(calls[0][1]?.body
|
|
146
|
+
expect(JSON.parse(String(calls[0][1]?.body))).toEqual({
|
|
138
147
|
publicBaseUrl: PUBLIC_URL,
|
|
139
148
|
});
|
|
140
|
-
expect(JSON.parse(calls[1][1]?.body
|
|
149
|
+
expect(JSON.parse(String(calls[1][1]?.body))).toEqual({
|
|
141
150
|
userCode: "ABCD-EFGH",
|
|
142
151
|
});
|
|
143
152
|
|
|
@@ -266,21 +275,21 @@ describe("pair command", () => {
|
|
|
266
275
|
// https QR and drop the scheme silently.
|
|
267
276
|
let fetchCalled = false;
|
|
268
277
|
const origFetch = globalThis.fetch;
|
|
269
|
-
globalThis.fetch = (async () => {
|
|
278
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
270
279
|
fetchCalled = true;
|
|
271
280
|
return new Response("{}", { status: 200 });
|
|
272
|
-
})
|
|
281
|
+
});
|
|
273
282
|
const errors: string[] = [];
|
|
274
283
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
275
284
|
(...a: unknown[]) => {
|
|
276
285
|
errors.push(a.join(" "));
|
|
277
286
|
},
|
|
278
287
|
);
|
|
279
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
280
|
-
code?: number
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
288
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
289
|
+
(code?: number | string | undefined): never => {
|
|
290
|
+
throw new Error(`exit:${code}`);
|
|
291
|
+
},
|
|
292
|
+
);
|
|
284
293
|
|
|
285
294
|
process.argv = [
|
|
286
295
|
"bun",
|
|
@@ -295,7 +304,7 @@ describe("pair command", () => {
|
|
|
295
304
|
try {
|
|
296
305
|
await pair();
|
|
297
306
|
} catch (e) {
|
|
298
|
-
exited =
|
|
307
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
299
308
|
} finally {
|
|
300
309
|
errSpy.mockRestore();
|
|
301
310
|
exitSpy.mockRestore();
|
|
@@ -312,28 +321,28 @@ describe("pair command", () => {
|
|
|
312
321
|
test("--web is refused, pointing at connect import on the other device", async () => {
|
|
313
322
|
let fetchCalled = false;
|
|
314
323
|
const origFetch = globalThis.fetch;
|
|
315
|
-
globalThis.fetch = (async () => {
|
|
324
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
316
325
|
fetchCalled = true;
|
|
317
326
|
return new Response("{}", { status: 200 });
|
|
318
|
-
})
|
|
327
|
+
});
|
|
319
328
|
const errors: string[] = [];
|
|
320
329
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
321
330
|
(...a: unknown[]) => {
|
|
322
331
|
errors.push(a.join(" "));
|
|
323
332
|
},
|
|
324
333
|
);
|
|
325
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
326
|
-
code?: number
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
334
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
335
|
+
(code?: number | string | undefined): never => {
|
|
336
|
+
throw new Error(`exit:${code}`);
|
|
337
|
+
},
|
|
338
|
+
);
|
|
330
339
|
|
|
331
340
|
process.argv = ["bun", "cadet", "pair", "--web", "--url", PUBLIC_URL];
|
|
332
341
|
let exited = false;
|
|
333
342
|
try {
|
|
334
343
|
await pair();
|
|
335
344
|
} catch (e) {
|
|
336
|
-
exited =
|
|
345
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
337
346
|
} finally {
|
|
338
347
|
errSpy.mockRestore();
|
|
339
348
|
exitSpy.mockRestore();
|
|
@@ -351,7 +360,7 @@ describe("pair command", () => {
|
|
|
351
360
|
test("resolves an unquoted multi-word display name", async () => {
|
|
352
361
|
// Assistant whose display name has a space; passed as separate argv tokens.
|
|
353
362
|
writeFileSync(
|
|
354
|
-
join(testDir, ".
|
|
363
|
+
join(testDir, "lockfile.json"),
|
|
355
364
|
JSON.stringify({
|
|
356
365
|
assistants: [
|
|
357
366
|
{
|
|
@@ -398,28 +407,28 @@ describe("pair command", () => {
|
|
|
398
407
|
test("rejects an unknown --flag before any network call", async () => {
|
|
399
408
|
let fetchCalled = false;
|
|
400
409
|
const origFetch = globalThis.fetch;
|
|
401
|
-
globalThis.fetch = (async () => {
|
|
410
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
402
411
|
fetchCalled = true;
|
|
403
412
|
return new Response("{}", { status: 200 });
|
|
404
|
-
})
|
|
413
|
+
});
|
|
405
414
|
const errors: string[] = [];
|
|
406
415
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
407
416
|
(...a: unknown[]) => {
|
|
408
417
|
errors.push(a.join(" "));
|
|
409
418
|
},
|
|
410
419
|
);
|
|
411
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
412
|
-
code?: number
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
420
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
421
|
+
(code?: number | string | undefined): never => {
|
|
422
|
+
throw new Error(`exit:${code}`);
|
|
423
|
+
},
|
|
424
|
+
);
|
|
416
425
|
|
|
417
426
|
process.argv = ["bun", "cadet", "pair", "--frobnicate"];
|
|
418
427
|
let exited = false;
|
|
419
428
|
try {
|
|
420
429
|
await pair();
|
|
421
430
|
} catch (e) {
|
|
422
|
-
exited =
|
|
431
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
423
432
|
} finally {
|
|
424
433
|
errSpy.mockRestore();
|
|
425
434
|
exitSpy.mockRestore();
|
|
@@ -438,7 +447,7 @@ describe("pair command", () => {
|
|
|
438
447
|
|
|
439
448
|
test("rejects an unknown --flag even alongside a multi-word positional name", async () => {
|
|
440
449
|
writeFileSync(
|
|
441
|
-
join(testDir, ".
|
|
450
|
+
join(testDir, "lockfile.json"),
|
|
442
451
|
JSON.stringify({
|
|
443
452
|
assistants: [
|
|
444
453
|
{
|
|
@@ -455,21 +464,21 @@ describe("pair command", () => {
|
|
|
455
464
|
|
|
456
465
|
let fetchCalled = false;
|
|
457
466
|
const origFetch = globalThis.fetch;
|
|
458
|
-
globalThis.fetch = (async () => {
|
|
467
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
459
468
|
fetchCalled = true;
|
|
460
469
|
return new Response("{}", { status: 200 });
|
|
461
|
-
})
|
|
470
|
+
});
|
|
462
471
|
const errors: string[] = [];
|
|
463
472
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
464
473
|
(...a: unknown[]) => {
|
|
465
474
|
errors.push(a.join(" "));
|
|
466
475
|
},
|
|
467
476
|
);
|
|
468
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
469
|
-
code?: number
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
477
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
478
|
+
(code?: number | string | undefined): never => {
|
|
479
|
+
throw new Error(`exit:${code}`);
|
|
480
|
+
},
|
|
481
|
+
);
|
|
473
482
|
|
|
474
483
|
// Positional name tokens must not rescue an unknown flag from rejection.
|
|
475
484
|
process.argv = ["bun", "cadet", "pair", "My", "Assistant", "--frobnicate"];
|
|
@@ -477,7 +486,7 @@ describe("pair command", () => {
|
|
|
477
486
|
try {
|
|
478
487
|
await pair();
|
|
479
488
|
} catch (e) {
|
|
480
|
-
exited =
|
|
489
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
481
490
|
} finally {
|
|
482
491
|
errSpy.mockRestore();
|
|
483
492
|
exitSpy.mockRestore();
|
|
@@ -493,7 +502,7 @@ describe("pair command", () => {
|
|
|
493
502
|
// Local hatch on a NON-default gateway port (e.g. a 2nd instance).
|
|
494
503
|
const LOOPBACK_CUSTOM = "http://127.0.0.1:7842";
|
|
495
504
|
writeFileSync(
|
|
496
|
-
join(testDir, ".
|
|
505
|
+
join(testDir, "lockfile.json"),
|
|
497
506
|
JSON.stringify({
|
|
498
507
|
assistants: [
|
|
499
508
|
{
|
|
@@ -509,28 +518,28 @@ describe("pair command", () => {
|
|
|
509
518
|
|
|
510
519
|
let fetchCalled = false;
|
|
511
520
|
const origFetch = globalThis.fetch;
|
|
512
|
-
globalThis.fetch = (async () => {
|
|
521
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
513
522
|
fetchCalled = true;
|
|
514
523
|
return new Response("{}", { status: 200 });
|
|
515
|
-
})
|
|
524
|
+
});
|
|
516
525
|
const errors: string[] = [];
|
|
517
526
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
518
527
|
(...a: unknown[]) => {
|
|
519
528
|
errors.push(a.join(" "));
|
|
520
529
|
},
|
|
521
530
|
);
|
|
522
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
523
|
-
code?: number
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
531
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
532
|
+
(code?: number | string | undefined): never => {
|
|
533
|
+
throw new Error(`exit:${code}`);
|
|
534
|
+
},
|
|
535
|
+
);
|
|
527
536
|
|
|
528
537
|
process.argv = ["bun", "cadet", "pair"];
|
|
529
538
|
let exited = false;
|
|
530
539
|
try {
|
|
531
540
|
await pair();
|
|
532
541
|
} catch (e) {
|
|
533
|
-
exited =
|
|
542
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
534
543
|
} finally {
|
|
535
544
|
errSpy.mockRestore();
|
|
536
545
|
exitSpy.mockRestore();
|
|
@@ -552,7 +561,7 @@ describe("pair command", () => {
|
|
|
552
561
|
|
|
553
562
|
test("--url override wins over a loopback runtime URL", async () => {
|
|
554
563
|
writeFileSync(
|
|
555
|
-
join(testDir, ".
|
|
564
|
+
join(testDir, "lockfile.json"),
|
|
556
565
|
JSON.stringify({
|
|
557
566
|
assistants: [
|
|
558
567
|
{
|
|
@@ -587,7 +596,7 @@ describe("pair command", () => {
|
|
|
587
596
|
|
|
588
597
|
// Mint still over loopback; the link advertises the override.
|
|
589
598
|
expect(calls[0][0]).toBe(`${LOCAL_URL}/v1/remote-web/pairing-challenge`);
|
|
590
|
-
expect(JSON.parse(calls[0][1]?.body
|
|
599
|
+
expect(JSON.parse(String(calls[0][1]?.body))).toEqual({
|
|
591
600
|
publicBaseUrl: OVERRIDE,
|
|
592
601
|
});
|
|
593
602
|
expect(JSON.parse(logs.join("\n")).pairUrl).toBe(
|
|
@@ -597,7 +606,7 @@ describe("pair command", () => {
|
|
|
597
606
|
|
|
598
607
|
test("--web-approve approves a browser pairing code over loopback", async () => {
|
|
599
608
|
writeFileSync(
|
|
600
|
-
join(testDir, ".
|
|
609
|
+
join(testDir, "lockfile.json"),
|
|
601
610
|
JSON.stringify({
|
|
602
611
|
assistants: [
|
|
603
612
|
{
|
|
@@ -613,7 +622,8 @@ describe("pair command", () => {
|
|
|
613
622
|
|
|
614
623
|
const calls: Array<[string, RequestInit | undefined]> = [];
|
|
615
624
|
const origFetch = globalThis.fetch;
|
|
616
|
-
globalThis.fetch = (async (
|
|
625
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
626
|
+
const url = String(input);
|
|
617
627
|
calls.push([url, init]);
|
|
618
628
|
if (url === `${LOCAL_URL}/v1/remote-web/pairing-verification`) {
|
|
619
629
|
return new Response(
|
|
@@ -626,7 +636,7 @@ describe("pair command", () => {
|
|
|
626
636
|
);
|
|
627
637
|
}
|
|
628
638
|
return new Response("not found", { status: 404 });
|
|
629
|
-
})
|
|
639
|
+
});
|
|
630
640
|
|
|
631
641
|
const logs: string[] = [];
|
|
632
642
|
const logSpy = spyOn(console, "log").mockImplementation(
|
|
@@ -652,7 +662,7 @@ describe("pair command", () => {
|
|
|
652
662
|
|
|
653
663
|
expect(calls).toHaveLength(1);
|
|
654
664
|
expect(calls[0][0]).toBe(`${LOCAL_URL}/v1/remote-web/pairing-verification`);
|
|
655
|
-
expect(JSON.parse(calls[0][1]?.body
|
|
665
|
+
expect(JSON.parse(String(calls[0][1]?.body))).toEqual({
|
|
656
666
|
userCode: "ABCD-EFGH",
|
|
657
667
|
});
|
|
658
668
|
expect(JSON.parse(logs.join("\n"))).toEqual({
|
|
@@ -665,21 +675,21 @@ describe("pair command", () => {
|
|
|
665
675
|
test("refuses a non-https --url without minting", async () => {
|
|
666
676
|
let fetchCalled = false;
|
|
667
677
|
const origFetch = globalThis.fetch;
|
|
668
|
-
globalThis.fetch = (async () => {
|
|
678
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
669
679
|
fetchCalled = true;
|
|
670
680
|
return new Response("{}", { status: 200 });
|
|
671
|
-
})
|
|
681
|
+
});
|
|
672
682
|
const errors: string[] = [];
|
|
673
683
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
674
684
|
(...a: unknown[]) => {
|
|
675
685
|
errors.push(a.join(" "));
|
|
676
686
|
},
|
|
677
687
|
);
|
|
678
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
679
|
-
code?: number
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
688
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
689
|
+
(code?: number | string | undefined): never => {
|
|
690
|
+
throw new Error(`exit:${code}`);
|
|
691
|
+
},
|
|
692
|
+
);
|
|
683
693
|
|
|
684
694
|
process.argv = [
|
|
685
695
|
"bun",
|
|
@@ -692,7 +702,7 @@ describe("pair command", () => {
|
|
|
692
702
|
try {
|
|
693
703
|
await pair();
|
|
694
704
|
} catch (e) {
|
|
695
|
-
exited =
|
|
705
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
696
706
|
} finally {
|
|
697
707
|
errSpy.mockRestore();
|
|
698
708
|
exitSpy.mockRestore();
|
|
@@ -708,28 +718,28 @@ describe("pair command", () => {
|
|
|
708
718
|
test("refuses a loopback --url without minting", async () => {
|
|
709
719
|
let fetchCalled = false;
|
|
710
720
|
const origFetch = globalThis.fetch;
|
|
711
|
-
globalThis.fetch = (async () => {
|
|
721
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
712
722
|
fetchCalled = true;
|
|
713
723
|
return new Response("{}", { status: 200 });
|
|
714
|
-
})
|
|
724
|
+
});
|
|
715
725
|
const errors: string[] = [];
|
|
716
726
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
717
727
|
(...a: unknown[]) => {
|
|
718
728
|
errors.push(a.join(" "));
|
|
719
729
|
},
|
|
720
730
|
);
|
|
721
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
722
|
-
code?: number
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
731
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
732
|
+
(code?: number | string | undefined): never => {
|
|
733
|
+
throw new Error(`exit:${code}`);
|
|
734
|
+
},
|
|
735
|
+
);
|
|
726
736
|
|
|
727
737
|
process.argv = ["bun", "cadet", "pair", "--url", "http://127.0.0.1:7830"];
|
|
728
738
|
let exited = false;
|
|
729
739
|
try {
|
|
730
740
|
await pair();
|
|
731
741
|
} catch (e) {
|
|
732
|
-
exited =
|
|
742
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
733
743
|
} finally {
|
|
734
744
|
errSpy.mockRestore();
|
|
735
745
|
exitSpy.mockRestore();
|
|
@@ -744,28 +754,28 @@ describe("pair command", () => {
|
|
|
744
754
|
test("refuses an unparseable --url with an accurate error, not a non-https mislabel", async () => {
|
|
745
755
|
let fetchCalled = false;
|
|
746
756
|
const origFetch = globalThis.fetch;
|
|
747
|
-
globalThis.fetch = (async () => {
|
|
757
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
748
758
|
fetchCalled = true;
|
|
749
759
|
return new Response("{}", { status: 200 });
|
|
750
|
-
})
|
|
760
|
+
});
|
|
751
761
|
const errors: string[] = [];
|
|
752
762
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
753
763
|
(...a: unknown[]) => {
|
|
754
764
|
errors.push(a.join(" "));
|
|
755
765
|
},
|
|
756
766
|
);
|
|
757
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
758
|
-
code?: number
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
767
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
768
|
+
(code?: number | string | undefined): never => {
|
|
769
|
+
throw new Error(`exit:${code}`);
|
|
770
|
+
},
|
|
771
|
+
);
|
|
762
772
|
|
|
763
773
|
process.argv = ["bun", "cadet", "pair", "--url", "not-a-url"];
|
|
764
774
|
let exited = false;
|
|
765
775
|
try {
|
|
766
776
|
await pair();
|
|
767
777
|
} catch (e) {
|
|
768
|
-
exited =
|
|
778
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
769
779
|
} finally {
|
|
770
780
|
errSpy.mockRestore();
|
|
771
781
|
exitSpy.mockRestore();
|
|
@@ -785,21 +795,21 @@ describe("pair command", () => {
|
|
|
785
795
|
test("refuses a tunnel-provider website URL (Tailscale admin invite) without minting", async () => {
|
|
786
796
|
let fetchCalled = false;
|
|
787
797
|
const origFetch = globalThis.fetch;
|
|
788
|
-
globalThis.fetch = (async () => {
|
|
798
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
789
799
|
fetchCalled = true;
|
|
790
800
|
return new Response("{}", { status: 200 });
|
|
791
|
-
})
|
|
801
|
+
});
|
|
792
802
|
const errors: string[] = [];
|
|
793
803
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
794
804
|
(...a: unknown[]) => {
|
|
795
805
|
errors.push(a.join(" "));
|
|
796
806
|
},
|
|
797
807
|
);
|
|
798
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
799
|
-
code?: number
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
808
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
809
|
+
(code?: number | string | undefined): never => {
|
|
810
|
+
throw new Error(`exit:${code}`);
|
|
811
|
+
},
|
|
812
|
+
);
|
|
803
813
|
|
|
804
814
|
process.argv = [
|
|
805
815
|
"bun",
|
|
@@ -812,7 +822,7 @@ describe("pair command", () => {
|
|
|
812
822
|
try {
|
|
813
823
|
await pair();
|
|
814
824
|
} catch (e) {
|
|
815
|
-
exited =
|
|
825
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
816
826
|
} finally {
|
|
817
827
|
errSpy.mockRestore();
|
|
818
828
|
exitSpy.mockRestore();
|
|
@@ -875,7 +885,8 @@ describe("pair command", () => {
|
|
|
875
885
|
|
|
876
886
|
test("--app emits an app connect URL alongside the browser URL", async () => {
|
|
877
887
|
const origFetch = globalThis.fetch;
|
|
878
|
-
globalThis.fetch = (async (
|
|
888
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
|
|
889
|
+
const url = String(input);
|
|
879
890
|
if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
|
|
880
891
|
return new Response(
|
|
881
892
|
JSON.stringify({
|
|
@@ -899,7 +910,7 @@ describe("pair command", () => {
|
|
|
899
910
|
);
|
|
900
911
|
}
|
|
901
912
|
return new Response("not found", { status: 404 });
|
|
902
|
-
})
|
|
913
|
+
});
|
|
903
914
|
|
|
904
915
|
const logs: string[] = [];
|
|
905
916
|
const logSpy = spyOn(console, "log").mockImplementation(
|
|
@@ -941,7 +952,8 @@ describe("pair command", () => {
|
|
|
941
952
|
|
|
942
953
|
test("--app --label overrides the assistant name in the connect link", async () => {
|
|
943
954
|
const origFetch = globalThis.fetch;
|
|
944
|
-
globalThis.fetch = (async (
|
|
955
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
|
|
956
|
+
const url = String(input);
|
|
945
957
|
if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
|
|
946
958
|
return new Response(
|
|
947
959
|
JSON.stringify({
|
|
@@ -965,7 +977,7 @@ describe("pair command", () => {
|
|
|
965
977
|
);
|
|
966
978
|
}
|
|
967
979
|
return new Response("not found", { status: 404 });
|
|
968
|
-
})
|
|
980
|
+
});
|
|
969
981
|
|
|
970
982
|
const logs: string[] = [];
|
|
971
983
|
const logSpy = spyOn(console, "log").mockImplementation(
|
|
@@ -1001,21 +1013,21 @@ describe("pair command", () => {
|
|
|
1001
1013
|
test("--app is refused alongside --web-approve", async () => {
|
|
1002
1014
|
let fetchCalled = false;
|
|
1003
1015
|
const origFetch = globalThis.fetch;
|
|
1004
|
-
globalThis.fetch = (async () => {
|
|
1016
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
1005
1017
|
fetchCalled = true;
|
|
1006
1018
|
return new Response("{}", { status: 200 });
|
|
1007
|
-
})
|
|
1019
|
+
});
|
|
1008
1020
|
const errors: string[] = [];
|
|
1009
1021
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
1010
1022
|
(...a: unknown[]) => {
|
|
1011
1023
|
errors.push(a.join(" "));
|
|
1012
1024
|
},
|
|
1013
1025
|
);
|
|
1014
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1015
|
-
code?: number
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1026
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1027
|
+
(code?: number | string | undefined): never => {
|
|
1028
|
+
throw new Error(`exit:${code}`);
|
|
1029
|
+
},
|
|
1030
|
+
);
|
|
1019
1031
|
|
|
1020
1032
|
process.argv = [
|
|
1021
1033
|
"bun",
|
|
@@ -1029,7 +1041,7 @@ describe("pair command", () => {
|
|
|
1029
1041
|
try {
|
|
1030
1042
|
await pair();
|
|
1031
1043
|
} catch (e) {
|
|
1032
|
-
exited =
|
|
1044
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
1033
1045
|
} finally {
|
|
1034
1046
|
errSpy.mockRestore();
|
|
1035
1047
|
exitSpy.mockRestore();
|
|
@@ -1043,7 +1055,7 @@ describe("pair command", () => {
|
|
|
1043
1055
|
|
|
1044
1056
|
function writeLockfileWithIngress(ingressUrl: string): void {
|
|
1045
1057
|
writeFileSync(
|
|
1046
|
-
join(testDir, ".
|
|
1058
|
+
join(testDir, "lockfile.json"),
|
|
1047
1059
|
JSON.stringify({
|
|
1048
1060
|
assistants: [
|
|
1049
1061
|
{
|
|
@@ -1064,11 +1076,12 @@ describe("pair command", () => {
|
|
|
1064
1076
|
|
|
1065
1077
|
const calls: string[] = [];
|
|
1066
1078
|
const origFetch = globalThis.fetch;
|
|
1067
|
-
globalThis.fetch = (async (
|
|
1079
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
1080
|
+
const url = String(input);
|
|
1068
1081
|
calls.push(url);
|
|
1069
1082
|
if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
|
|
1070
1083
|
// The minted challenge must advertise the entry's recorded URL.
|
|
1071
|
-
expect(JSON.parse(init?.body
|
|
1084
|
+
expect(JSON.parse(String(init?.body))).toEqual({
|
|
1072
1085
|
publicBaseUrl: "https://saved.example.ts.net",
|
|
1073
1086
|
});
|
|
1074
1087
|
return new Response(
|
|
@@ -1093,7 +1106,7 @@ describe("pair command", () => {
|
|
|
1093
1106
|
);
|
|
1094
1107
|
}
|
|
1095
1108
|
return new Response("not found", { status: 404 });
|
|
1096
|
-
})
|
|
1109
|
+
});
|
|
1097
1110
|
|
|
1098
1111
|
const logs: string[] = [];
|
|
1099
1112
|
const logSpy = spyOn(console, "log").mockImplementation(
|
|
@@ -1124,9 +1137,10 @@ describe("pair command", () => {
|
|
|
1124
1137
|
writeLockfileWithIngress("https://saved.example.ts.net");
|
|
1125
1138
|
|
|
1126
1139
|
const origFetch = globalThis.fetch;
|
|
1127
|
-
globalThis.fetch = (async (
|
|
1140
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
1141
|
+
const url = String(input);
|
|
1128
1142
|
if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
|
|
1129
|
-
expect(JSON.parse(init?.body
|
|
1143
|
+
expect(JSON.parse(String(init?.body))).toEqual({
|
|
1130
1144
|
publicBaseUrl: "https://explicit.example.ts.net",
|
|
1131
1145
|
});
|
|
1132
1146
|
return new Response(
|
|
@@ -1151,7 +1165,7 @@ describe("pair command", () => {
|
|
|
1151
1165
|
);
|
|
1152
1166
|
}
|
|
1153
1167
|
return new Response("not found", { status: 404 });
|
|
1154
|
-
})
|
|
1168
|
+
});
|
|
1155
1169
|
|
|
1156
1170
|
const logs: string[] = [];
|
|
1157
1171
|
const logSpy = spyOn(console, "log").mockImplementation(
|
|
@@ -1182,10 +1196,10 @@ describe("pair command", () => {
|
|
|
1182
1196
|
|
|
1183
1197
|
const origFetch = globalThis.fetch;
|
|
1184
1198
|
let minted = false;
|
|
1185
|
-
globalThis.fetch = (async () => {
|
|
1199
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
1186
1200
|
minted = true;
|
|
1187
1201
|
return new Response("{}", { status: 200 });
|
|
1188
|
-
})
|
|
1202
|
+
});
|
|
1189
1203
|
|
|
1190
1204
|
const errors: string[] = [];
|
|
1191
1205
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
@@ -1193,18 +1207,18 @@ describe("pair command", () => {
|
|
|
1193
1207
|
errors.push(a.join(" "));
|
|
1194
1208
|
},
|
|
1195
1209
|
);
|
|
1196
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1197
|
-
code?: number
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1210
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1211
|
+
(code?: number | string | undefined): never => {
|
|
1212
|
+
throw new Error(`exit:${code}`);
|
|
1213
|
+
},
|
|
1214
|
+
);
|
|
1201
1215
|
|
|
1202
1216
|
process.argv = ["bun", "cadet", "pair"];
|
|
1203
1217
|
let exited = false;
|
|
1204
1218
|
try {
|
|
1205
1219
|
await pair();
|
|
1206
1220
|
} catch (e) {
|
|
1207
|
-
exited =
|
|
1221
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
1208
1222
|
} finally {
|
|
1209
1223
|
errSpy.mockRestore();
|
|
1210
1224
|
exitSpy.mockRestore();
|
|
@@ -1223,10 +1237,10 @@ describe("pair command", () => {
|
|
|
1223
1237
|
|
|
1224
1238
|
const origFetch = globalThis.fetch;
|
|
1225
1239
|
let minted = false;
|
|
1226
|
-
globalThis.fetch = (async () => {
|
|
1240
|
+
globalThis.fetch = withPreconnect(async () => {
|
|
1227
1241
|
minted = true;
|
|
1228
1242
|
return new Response("{}", { status: 200 });
|
|
1229
|
-
})
|
|
1243
|
+
});
|
|
1230
1244
|
|
|
1231
1245
|
const errors: string[] = [];
|
|
1232
1246
|
const errSpy = spyOn(console, "error").mockImplementation(
|
|
@@ -1234,18 +1248,18 @@ describe("pair command", () => {
|
|
|
1234
1248
|
errors.push(a.join(" "));
|
|
1235
1249
|
},
|
|
1236
1250
|
);
|
|
1237
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1238
|
-
code?: number
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1251
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1252
|
+
(code?: number | string | undefined): never => {
|
|
1253
|
+
throw new Error(`exit:${code}`);
|
|
1254
|
+
},
|
|
1255
|
+
);
|
|
1242
1256
|
|
|
1243
1257
|
process.argv = ["bun", "cadet", "pair"];
|
|
1244
1258
|
let exited = false;
|
|
1245
1259
|
try {
|
|
1246
1260
|
await pair();
|
|
1247
1261
|
} catch (e) {
|
|
1248
|
-
exited =
|
|
1262
|
+
exited = e instanceof Error && e.message === "exit:1";
|
|
1249
1263
|
} finally {
|
|
1250
1264
|
errSpy.mockRestore();
|
|
1251
1265
|
exitSpy.mockRestore();
|