@mycadet/cli 0.12.9 → 0.12.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.
@@ -245,7 +245,14 @@ async function runTunnelCapturingLogs(): Promise<string> {
245
245
 
246
246
  describe("tunnel edge targeting", () => {
247
247
  beforeEach(() => {
248
- process.argv = ["bun", "cadet", "tunnel", "--provider", "tailscale"];
248
+ process.argv = [
249
+ "bun",
250
+ "cadet",
251
+ "tunnel",
252
+ "--provider",
253
+ "tailscale",
254
+ "--foreground",
255
+ ];
249
256
  writeLockfile(makeLocalEntry());
250
257
  globalThis.fetch = withPreconnect(async () => {
251
258
  throw new Error("gateway unavailable");
@@ -311,7 +318,14 @@ describe("tunnel edge targeting", () => {
311
318
  const entry = makeLocalEntry();
312
319
  entry.runtimeUrl = "https://stale-tunnel.ngrok-free.dev";
313
320
  writeLockfile(entry);
314
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
321
+ process.argv = [
322
+ "bun",
323
+ "cadet",
324
+ "tunnel",
325
+ "--provider",
326
+ "ngrok",
327
+ "--foreground",
328
+ ];
315
329
 
316
330
  const logs = await runTunnelCapturingLogs();
317
331
 
@@ -332,7 +346,14 @@ describe("tunnel edge targeting", () => {
332
346
 
333
347
  test("the tunnel level notifies the gateway of the provider's public URL", async () => {
334
348
  writeLockfile(makeLocalEntry());
335
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
349
+ process.argv = [
350
+ "bun",
351
+ "cadet",
352
+ "tunnel",
353
+ "--provider",
354
+ "ngrok",
355
+ "--foreground",
356
+ ];
336
357
 
337
358
  await runTunnelCapturingLogs();
338
359
 
@@ -348,7 +369,14 @@ describe("tunnel edge targeting", () => {
348
369
  const cloud = makeCloudEntry();
349
370
  const local = makeLocalEntry();
350
371
  writeLockfile([cloud, local], cloud.assistantId);
351
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
372
+ process.argv = [
373
+ "bun",
374
+ "cadet",
375
+ "tunnel",
376
+ "--provider",
377
+ "ngrok",
378
+ "--foreground",
379
+ ];
352
380
 
353
381
  const logs = await runTunnelCapturingLogs();
354
382
 
@@ -380,6 +408,7 @@ describe("tunnel edge targeting", () => {
380
408
  "docker-1",
381
409
  "--provider",
382
410
  "ngrok",
411
+ "--foreground",
383
412
  ];
384
413
 
385
414
  await runTunnelCapturingLogs();
@@ -400,7 +429,14 @@ describe("tunnel edge targeting", () => {
400
429
  const workspaceDir = useTempDefaultWorkspaceDir();
401
430
  const docker = makeDockerEntry();
402
431
  writeLockfile(docker, docker.assistantId);
403
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
432
+ process.argv = [
433
+ "bun",
434
+ "cadet",
435
+ "tunnel",
436
+ "--provider",
437
+ "ngrok",
438
+ "--foreground",
439
+ ];
404
440
 
405
441
  await runTunnelCapturingLogs();
406
442
 
@@ -420,7 +456,14 @@ describe("tunnel edge targeting", () => {
420
456
  const workspaceDir = useTempDefaultWorkspaceDir();
421
457
  const cloud = makeCloudEntry();
422
458
  writeLockfile([cloud, makeDockerEntry()], cloud.assistantId);
423
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
459
+ process.argv = [
460
+ "bun",
461
+ "cadet",
462
+ "tunnel",
463
+ "--provider",
464
+ "ngrok",
465
+ "--foreground",
466
+ ];
424
467
 
425
468
  const logs = await runTunnelCapturingLogs();
426
469
 
@@ -437,7 +480,14 @@ describe("tunnel edge targeting", () => {
437
480
 
438
481
  test("an active cloud assistant with no local entries exits with an error", async () => {
439
482
  writeLockfile(makeCloudEntry());
440
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
483
+ process.argv = [
484
+ "bun",
485
+ "cadet",
486
+ "tunnel",
487
+ "--provider",
488
+ "ngrok",
489
+ "--foreground",
490
+ ];
441
491
 
442
492
  const { exited, errors } = await runTunnelExpectingExit1();
443
493
 
@@ -456,7 +506,14 @@ describe("tunnel edge targeting", () => {
456
506
  [cloud, makeLocalEntry("assistant-a"), makeLocalEntry("assistant-b")],
457
507
  cloud.assistantId,
458
508
  );
459
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
509
+ process.argv = [
510
+ "bun",
511
+ "cadet",
512
+ "tunnel",
513
+ "--provider",
514
+ "ngrok",
515
+ "--foreground",
516
+ ];
460
517
 
461
518
  const { exited, errors } = await runTunnelExpectingExit1();
462
519
 
@@ -474,7 +531,15 @@ describe("tunnel edge targeting", () => {
474
531
  test("a positional cloud assistant name errors without auto-fallback", async () => {
475
532
  const local = makeLocalEntry();
476
533
  writeLockfile([makeCloudEntry(), local], local.assistantId);
477
- process.argv = ["bun", "cadet", "tunnel", "cloud-1", "--provider", "ngrok"];
534
+ process.argv = [
535
+ "bun",
536
+ "cadet",
537
+ "tunnel",
538
+ "cloud-1",
539
+ "--provider",
540
+ "ngrok",
541
+ "--foreground",
542
+ ];
478
543
 
479
544
  const { exited, errors } = await runTunnelExpectingExit1();
480
545
 
@@ -493,7 +558,15 @@ describe("tunnel edge targeting", () => {
493
558
  const local = makeLocalEntry();
494
559
  local.name = "Ada";
495
560
  writeLockfile([makeCloudEntry(), local], "cloud-1");
496
- process.argv = ["bun", "cadet", "tunnel", "Ada", "--provider", "ngrok"];
561
+ process.argv = [
562
+ "bun",
563
+ "cadet",
564
+ "tunnel",
565
+ "Ada",
566
+ "--provider",
567
+ "ngrok",
568
+ "--foreground",
569
+ ];
497
570
 
498
571
  await runTunnelCapturingLogs();
499
572
 
@@ -517,6 +590,7 @@ describe("tunnel edge targeting", () => {
517
590
  "Lovelace",
518
591
  "--provider",
519
592
  "ngrok",
593
+ "--foreground",
520
594
  ];
521
595
 
522
596
  await runTunnelCapturingLogs();
@@ -533,7 +607,15 @@ describe("tunnel edge targeting", () => {
533
607
  const workspaceDir = useTempDefaultWorkspaceDir();
534
608
  const local = makeLocalEntry();
535
609
  writeLockfile([local, makeAppleContainerEntry()], local.assistantId);
536
- process.argv = ["bun", "cadet", "tunnel", "apple-1", "--provider", "ngrok"];
610
+ process.argv = [
611
+ "bun",
612
+ "cadet",
613
+ "tunnel",
614
+ "apple-1",
615
+ "--provider",
616
+ "ngrok",
617
+ "--foreground",
618
+ ];
537
619
 
538
620
  await runTunnelCapturingLogs();
539
621
 
@@ -553,7 +635,14 @@ describe("tunnel edge targeting", () => {
553
635
  const workspaceDir = useTempDefaultWorkspaceDir();
554
636
  const cloud = makeCloudEntry();
555
637
  writeLockfile([cloud, makeAppleContainerEntry()], cloud.assistantId);
556
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
638
+ process.argv = [
639
+ "bun",
640
+ "cadet",
641
+ "tunnel",
642
+ "--provider",
643
+ "ngrok",
644
+ "--foreground",
645
+ ];
557
646
 
558
647
  const logs = await runTunnelCapturingLogs();
559
648
 
@@ -585,6 +674,7 @@ describe("tunnel edge targeting", () => {
585
674
  "assistant-b",
586
675
  "--provider",
587
676
  "ngrok",
677
+ "--foreground",
588
678
  ];
589
679
 
590
680
  await runTunnelCapturingLogs();
@@ -599,7 +689,15 @@ describe("tunnel edge targeting", () => {
599
689
  cloud.name = "Cloudy";
600
690
  const local = makeLocalEntry();
601
691
  writeLockfile([cloud, local], local.assistantId);
602
- process.argv = ["bun", "cadet", "tunnel", "Cloudy", "--provider", "ngrok"];
692
+ process.argv = [
693
+ "bun",
694
+ "cadet",
695
+ "tunnel",
696
+ "Cloudy",
697
+ "--provider",
698
+ "ngrok",
699
+ "--foreground",
700
+ ];
603
701
 
604
702
  const { exited, errors } = await runTunnelExpectingExit1();
605
703
 
@@ -620,7 +718,15 @@ describe("tunnel edge targeting", () => {
620
718
  const second = makeLocalEntry("assistant-b");
621
719
  second.name = "Ada";
622
720
  writeLockfile([first, second], "assistant-a");
623
- process.argv = ["bun", "cadet", "tunnel", "Ada", "--provider", "ngrok"];
721
+ process.argv = [
722
+ "bun",
723
+ "cadet",
724
+ "tunnel",
725
+ "Ada",
726
+ "--provider",
727
+ "ngrok",
728
+ "--foreground",
729
+ ];
624
730
 
625
731
  const { exited, errors } = await runTunnelExpectingExit1();
626
732
 
@@ -633,7 +739,15 @@ describe("tunnel edge targeting", () => {
633
739
  });
634
740
 
635
741
  test("an unknown positional name errors", async () => {
636
- process.argv = ["bun", "cadet", "tunnel", "nope", "--provider", "ngrok"];
742
+ process.argv = [
743
+ "bun",
744
+ "cadet",
745
+ "tunnel",
746
+ "nope",
747
+ "--provider",
748
+ "ngrok",
749
+ "--foreground",
750
+ ];
637
751
 
638
752
  const { exited, errors } = await runTunnelExpectingExit1();
639
753
 
@@ -646,7 +760,14 @@ describe("tunnel edge targeting", () => {
646
760
  test("targets the edge port for cloudflare", async () => {
647
761
  const entry = makeLocalEntry();
648
762
  writeLockfile(entry);
649
- process.argv = ["bun", "cadet", "tunnel", "--provider", "cloudflare"];
763
+ process.argv = [
764
+ "bun",
765
+ "cadet",
766
+ "tunnel",
767
+ "--provider",
768
+ "cloudflare",
769
+ "--foreground",
770
+ ];
650
771
 
651
772
  await runTunnelCapturingLogs();
652
773
 
@@ -661,7 +782,14 @@ describe("tunnel edge targeting", () => {
661
782
  test("targets the edge port for tailscale and notes a reused webhooks-only edge", async () => {
662
783
  const entry = makeLocalEntry();
663
784
  writeLockfile(entry);
664
- process.argv = ["bun", "cadet", "tunnel", "--provider", "tailscale"];
785
+ process.argv = [
786
+ "bun",
787
+ "cadet",
788
+ "tunnel",
789
+ "--provider",
790
+ "tailscale",
791
+ "--foreground",
792
+ ];
665
793
  ensureTunnelEdgeMock.mockResolvedValue({
666
794
  port: EDGE_PORT,
667
795
  started: false,
@@ -681,7 +809,7 @@ describe("tunnel edge targeting", () => {
681
809
  test("a bare invocation tunnels the default provider", async () => {
682
810
  const entry = makeLocalEntry();
683
811
  writeLockfile(entry);
684
- process.argv = ["bun", "cadet", "tunnel"];
812
+ process.argv = ["bun", "cadet", "tunnel", "--foreground"];
685
813
 
686
814
  const logs = await runTunnelCapturingLogs();
687
815
 
@@ -700,7 +828,7 @@ describe("tunnel edge targeting", () => {
700
828
  const entry = makeLocalEntry();
701
829
  entry.name = "Ada";
702
830
  writeLockfile(entry);
703
- process.argv = ["bun", "cadet", "tunnel", "Ada"];
831
+ process.argv = ["bun", "cadet", "tunnel", "Ada", "--foreground"];
704
832
 
705
833
  const logs = await runTunnelCapturingLogs();
706
834
 
@@ -720,7 +848,14 @@ describe("tunnel edge targeting", () => {
720
848
  const workspaceDir = writeEntryWorkspaceConfig(entry, {
721
849
  telegram: { botUsername: "example_bot" },
722
850
  });
723
- process.argv = ["bun", "cadet", "tunnel", "--provider", "tailscale"];
851
+ process.argv = [
852
+ "bun",
853
+ "cadet",
854
+ "tunnel",
855
+ "--provider",
856
+ "tailscale",
857
+ "--foreground",
858
+ ];
724
859
 
725
860
  const warnings: string[] = [];
726
861
  const warnSpy = spyOn(console, "warn").mockImplementation(
@@ -751,7 +886,14 @@ describe("tunnel edge targeting", () => {
751
886
  const entry = makeLocalEntry();
752
887
  writeLockfile(entry);
753
888
  const workspaceDir = writeEntryWorkspaceConfig(entry, {});
754
- process.argv = ["bun", "cadet", "tunnel", "--provider", "tailscale"];
889
+ process.argv = [
890
+ "bun",
891
+ "cadet",
892
+ "tunnel",
893
+ "--provider",
894
+ "tailscale",
895
+ "--foreground",
896
+ ];
755
897
 
756
898
  await runTunnelCapturingLogs();
757
899
 
@@ -768,7 +910,14 @@ describe("tunnel edge targeting", () => {
768
910
  const workspaceDir = writeEntryWorkspaceConfig(entry, {
769
911
  telegram: { botUsername: "example_bot" },
770
912
  });
771
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
913
+ process.argv = [
914
+ "bun",
915
+ "cadet",
916
+ "tunnel",
917
+ "--provider",
918
+ "ngrok",
919
+ "--foreground",
920
+ ];
772
921
 
773
922
  await runTunnelCapturingLogs();
774
923
 
@@ -780,7 +929,14 @@ describe("tunnel edge targeting", () => {
780
929
  });
781
930
 
782
931
  test("missing nginx aborts before any provider spawn", async () => {
783
- process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
932
+ process.argv = [
933
+ "bun",
934
+ "cadet",
935
+ "tunnel",
936
+ "--provider",
937
+ "ngrok",
938
+ "--foreground",
939
+ ];
784
940
  ensureTunnelEdgeMock.mockRejectedValue(
785
941
  new Error(
786
942
  "nginx is not installed, so the tunnel edge cannot start. " +
@@ -801,7 +957,14 @@ describe("tunnel edge targeting", () => {
801
957
  });
802
958
 
803
959
  test("rejects an unknown --provider with a stale-CLI hint", async () => {
804
- process.argv = ["bun", "cadet", "tunnel", "--provider", "bogus"];
960
+ process.argv = [
961
+ "bun",
962
+ "cadet",
963
+ "tunnel",
964
+ "--provider",
965
+ "bogus",
966
+ "--foreground",
967
+ ];
805
968
 
806
969
  const { exited, errors } = await runTunnelExpectingExit1();
807
970
 
@@ -818,7 +981,14 @@ describe("tunnel edge targeting", () => {
818
981
  try {
819
982
  for (const provider of TUNNEL_PROVIDERS) {
820
983
  writeLockfile(makeLocalEntry());
821
- process.argv = ["bun", "cadet", "tunnel", "--provider", provider];
984
+ process.argv = [
985
+ "bun",
986
+ "cadet",
987
+ "tunnel",
988
+ "--provider",
989
+ provider,
990
+ "--foreground",
991
+ ];
822
992
 
823
993
  const { exited, errors } = await runTunnelExpectingExit1();
824
994
 
@@ -841,6 +1011,7 @@ describe("tunnel edge targeting", () => {
841
1011
  "ngrok",
842
1012
  "--domain",
843
1013
  "foo.ngrok.app",
1014
+ "--foreground",
844
1015
  ];
845
1016
 
846
1017
  await runTunnelCapturingLogs();
@@ -862,6 +1033,7 @@ describe("tunnel edge targeting", () => {
862
1033
  "cloudflare",
863
1034
  "--domain",
864
1035
  "foo.ngrok.app",
1036
+ "--foreground",
865
1037
  ];
866
1038
 
867
1039
  const { exited, errors } = await runTunnelExpectingExit1();
@@ -887,6 +1059,7 @@ describe("tunnel edge targeting", () => {
887
1059
  "--provider",
888
1060
  "ngrok",
889
1061
  "--clear-domain",
1062
+ "--foreground",
890
1063
  ];
891
1064
 
892
1065
  const logs = await runTunnelCapturingLogs();
@@ -912,6 +1085,7 @@ describe("tunnel edge targeting", () => {
912
1085
  "--domain",
913
1086
  "foo.ngrok.app",
914
1087
  "--clear-domain",
1088
+ "--foreground",
915
1089
  ];
916
1090
 
917
1091
  const { exited, errors } = await runTunnelExpectingExit1();
@@ -929,6 +1103,7 @@ describe("tunnel edge targeting", () => {
929
1103
  "--provider",
930
1104
  "cloudflare",
931
1105
  "--clear-domain",
1106
+ "--foreground",
932
1107
  ];
933
1108
 
934
1109
  const { exited, errors } = await runTunnelExpectingExit1();
@@ -949,6 +1124,7 @@ describe("tunnel edge targeting", () => {
949
1124
  "--provider",
950
1125
  "ngrok",
951
1126
  "--domain",
1127
+ "--foreground",
952
1128
  ];
953
1129
 
954
1130
  const { exited, errors } = await runTunnelExpectingExit1();
@@ -961,7 +1137,14 @@ describe("tunnel edge targeting", () => {
961
1137
  test("a not-yet-implemented provider error carries the stale-CLI hint", async () => {
962
1138
  // `cadet` stays valid-but-unimplemented, so it hits this path rather
963
1139
  // than the unknown-provider one.
964
- process.argv = ["bun", "cadet", "tunnel", "--provider", "cadet"];
1140
+ process.argv = [
1141
+ "bun",
1142
+ "cadet",
1143
+ "tunnel",
1144
+ "--provider",
1145
+ "cadet",
1146
+ "--foreground",
1147
+ ];
965
1148
 
966
1149
  let err: Error | undefined;
967
1150
  try {
@@ -1490,7 +1673,7 @@ describe("ngrok --domain spawn args", () => {
1490
1673
  });
1491
1674
  });
1492
1675
 
1493
- describe("tunnel --detach", () => {
1676
+ describe("tunnel background mode", () => {
1494
1677
  const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
1495
1678
 
1496
1679
  function makeFakeChild(pid: number): ChildProcess {
@@ -1512,13 +1695,22 @@ describe("tunnel --detach", () => {
1512
1695
  ): ChildProcess => makeFakeChild(9999),
1513
1696
  );
1514
1697
 
1515
- // spawnDetachedTunnel() scopes the log filename to its own (parent) pid,
1516
- // which in-process is this test runner's pid.
1517
1698
  function detachedLogPath(): string {
1518
- return join(getLogDir(), `tunnel-${process.pid}.log`);
1699
+ return join(getLogDir(), "tunnel.log");
1519
1700
  }
1520
1701
 
1521
1702
  beforeAll(() => {
1703
+ // The edge-targeting describe's afterAll restores the REAL provider
1704
+ // modules; re-establish the fakes the foreground test here needs.
1705
+ mock.module("../lib/tunnels/ngrok", () => ({
1706
+ ...realNgrok,
1707
+ runNgrokTunnel: runNgrokTunnelMock,
1708
+ }));
1709
+ mock.module("../lib/nginx-ingress.js", () => ({
1710
+ ...realNginxIngress,
1711
+ ensureTunnelEdge: ensureTunnelEdgeMock,
1712
+ getNginxVersion: getNginxVersionMock,
1713
+ }));
1522
1714
  mock.module("node:child_process", () => ({
1523
1715
  ...realChildProcess,
1524
1716
  spawn: spawnMock,
@@ -1527,10 +1719,22 @@ describe("tunnel --detach", () => {
1527
1719
 
1528
1720
  afterAll(() => {
1529
1721
  mock.module("node:child_process", () => realChildProcess);
1722
+ mock.module("../lib/tunnels/ngrok", () => realNgrok);
1723
+ mock.module("../lib/nginx-ingress.js", () => realNginxIngress);
1530
1724
  });
1531
1725
 
1532
1726
  beforeEach(() => {
1533
1727
  spawnMock.mockClear();
1728
+ runNgrokTunnelMock.mockReset();
1729
+ runNgrokTunnelMock.mockResolvedValue(
1730
+ fakeTunnelResult("https://test.example.ngrok.dev"),
1731
+ );
1732
+ ensureTunnelEdgeMock.mockReset();
1733
+ ensureTunnelEdgeMock.mockResolvedValue({
1734
+ port: EDGE_PORT,
1735
+ started: true,
1736
+ includesWebApp: true,
1737
+ });
1534
1738
  const configHome = mkdtempSync(join(tmpdir(), "cadet-tunnel-xdg-"));
1535
1739
  tempDirs.push(configHome);
1536
1740
  process.env.XDG_CONFIG_HOME = configHome;
@@ -1548,15 +1752,8 @@ describe("tunnel --detach", () => {
1548
1752
  }
1549
1753
  });
1550
1754
 
1551
- test("--detach re-invokes without the flag as a detached child and reports readiness", async () => {
1552
- process.argv = [
1553
- "bun",
1554
- "cadet",
1555
- "tunnel",
1556
- "--provider",
1557
- "ngrok",
1558
- "--detach",
1559
- ];
1755
+ test("runs detached by default: re-invokes itself as a detached child and reports readiness", async () => {
1756
+ process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
1560
1757
 
1561
1758
  // Simulate the detached child writing its readiness line to the log file
1562
1759
  // the moment it is spawned.
@@ -1575,8 +1772,10 @@ describe("tunnel --detach", () => {
1575
1772
  expect(args).toContain("tunnel");
1576
1773
  expect(args).toContain("--provider");
1577
1774
  expect(args).toContain("ngrok");
1578
- expect(args).not.toContain("--detach");
1579
- expect(args).not.toContain("-d");
1775
+ // The child runs the foreground path (it IS the background daemon); the
1776
+ // parent must not hand the default through or the child would daemonize
1777
+ // again.
1778
+ expect(args).toContain("--foreground");
1580
1779
  expect(opts?.detached).toBe(true);
1581
1780
 
1582
1781
  expect(logs).toContain("Tunnel established: https://foo.ngrok.app");
@@ -1589,32 +1788,25 @@ describe("tunnel --detach", () => {
1589
1788
  expect(ensureTunnelEdgeMock).not.toHaveBeenCalled();
1590
1789
  });
1591
1790
 
1592
- test("-d is equivalent to --detach", async () => {
1593
- process.argv = ["bun", "cadet", "tunnel", "--provider", "tailscale", "-d"];
1594
- spawnMock.mockImplementationOnce(() => {
1595
- appendFileSync(
1596
- detachedLogPath(),
1597
- "Tunnel established: https://tailnet.example.ts.net\n",
1598
- );
1599
- return makeFakeChild(1234);
1600
- });
1601
-
1602
- await runTunnelCapturingLogs();
1603
-
1604
- const [, args] = spawnMock.mock.calls[0];
1605
- expect(args).not.toContain("-d");
1606
- expect(args).not.toContain("--detach");
1607
- });
1608
-
1609
- test("reports an early child exit instead of hanging", async () => {
1791
+ test("--foreground runs the tunnel in this process instead of a detached child", async () => {
1610
1792
  process.argv = [
1611
1793
  "bun",
1612
1794
  "cadet",
1613
1795
  "tunnel",
1614
1796
  "--provider",
1615
1797
  "ngrok",
1616
- "--detach",
1798
+ "--foreground",
1617
1799
  ];
1800
+
1801
+ await runTunnelCapturingLogs();
1802
+
1803
+ expect(spawnMock).not.toHaveBeenCalled();
1804
+ expect(ensureTunnelEdgeMock).toHaveBeenCalled();
1805
+ expect(runNgrokTunnelMock).toHaveBeenCalled();
1806
+ });
1807
+
1808
+ test("reports an early child exit instead of hanging", async () => {
1809
+ process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
1618
1810
  spawnMock.mockImplementationOnce(() => {
1619
1811
  const child = makeFakeChild(4242);
1620
1812
  // Listeners attach synchronously right after spawn() returns; defer the
@@ -1631,14 +1823,7 @@ describe("tunnel --detach", () => {
1631
1823
  });
1632
1824
 
1633
1825
  test("an adopted ngrok tunnel gets an accurate stop message, not a false kill claim", async () => {
1634
- process.argv = [
1635
- "bun",
1636
- "cadet",
1637
- "tunnel",
1638
- "--provider",
1639
- "ngrok",
1640
- "--detach",
1641
- ];
1826
+ process.argv = ["bun", "cadet", "tunnel", "--provider", "ngrok"];
1642
1827
  spawnMock.mockImplementationOnce(() => {
1643
1828
  appendFileSync(
1644
1829
  detachedLogPath(),