@mycadet/cli 0.12.10 → 0.13.0-dev.202610171617.9ea363e

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.
Files changed (111) hide show
  1. package/README.md +13 -0
  2. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  3. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
  4. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  5. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  6. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
  7. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  8. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  9. package/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
  10. package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  11. package/package.json +8 -3
  12. package/src/__tests__/assistant-client-cloud-auth.test.ts +2 -2
  13. package/src/__tests__/backup-ops-matching.test.ts +15 -13
  14. package/src/__tests__/backup-ops.test.ts +105 -0
  15. package/src/__tests__/backup.test.ts +114 -673
  16. package/src/__tests__/cloud-login-poll.test.ts +69 -0
  17. package/src/__tests__/cloud-releases.test.ts +82 -0
  18. package/src/__tests__/commands-mock.ts +101 -0
  19. package/src/__tests__/debug.test.ts +17 -91
  20. package/src/__tests__/devices.test.ts +64 -2
  21. package/src/__tests__/ensure-binary.test.ts +52 -0
  22. package/src/__tests__/gateway-token.test.ts +267 -0
  23. package/src/__tests__/hatch-next-steps.test.ts +38 -0
  24. package/src/__tests__/host-teleport-docker.test.ts +371 -0
  25. package/src/__tests__/host-teleport.test.ts +405 -0
  26. package/src/__tests__/multi-local.test.ts +3 -3
  27. package/src/__tests__/nginx-ingress.test.ts +587 -36
  28. package/src/__tests__/orphan-detection.test.ts +1 -1
  29. package/src/__tests__/paired-lifecycle.test.ts +2 -2
  30. package/src/__tests__/ps-cloud-status.test.ts +8 -7
  31. package/src/__tests__/root-backup-docker.test.ts +294 -0
  32. package/src/__tests__/root-backup.test.ts +527 -0
  33. package/src/__tests__/root-restore-command.test.ts +147 -0
  34. package/src/__tests__/root-signing-key.test.ts +50 -0
  35. package/src/__tests__/setup.test.ts +17 -7
  36. package/src/__tests__/shell-mode.test.ts +83 -0
  37. package/src/__tests__/sleep.test.ts +11 -0
  38. package/src/__tests__/teleport.test.ts +68 -2996
  39. package/src/__tests__/tunnel-startup-summary.test.ts +110 -0
  40. package/src/__tests__/tunnel-stop.test.ts +226 -0
  41. package/src/__tests__/tunnel.test.ts +22 -4
  42. package/src/__tests__/upgrade-local.test.ts +21 -11
  43. package/src/__tests__/upgrade-replay-env.test.ts +2 -2
  44. package/src/__tests__/upgrade-root-recovery.test.ts +185 -0
  45. package/src/__tests__/wake.test.ts +4 -1
  46. package/src/__tests__/windows-lifecycle.test.ts +25 -0
  47. package/src/commands/backup.ts +44 -399
  48. package/src/commands/client.ts +3 -3
  49. package/src/commands/completions.ts +0 -2
  50. package/src/commands/debug.ts +75 -131
  51. package/src/commands/devices.ts +117 -47
  52. package/src/commands/gateway/token.ts +77 -23
  53. package/src/commands/hatch.ts +10 -52
  54. package/src/commands/pair.ts +9 -3
  55. package/src/commands/ps.ts +4 -2
  56. package/src/commands/restore.ts +77 -444
  57. package/src/commands/retire.ts +2 -3
  58. package/src/commands/sleep.ts +15 -2
  59. package/src/commands/teleport.ts +47 -1559
  60. package/src/commands/tunnel.ts +91 -11
  61. package/src/commands/upgrade.ts +84 -157
  62. package/src/commands/wake.ts +1 -1
  63. package/src/index.ts +0 -4
  64. package/src/lib/__tests__/http-client-version.test.ts +29 -0
  65. package/src/lib/__tests__/runtime-url.test.ts +3 -75
  66. package/src/lib/__tests__/web-dist.test.ts +27 -1
  67. package/src/lib/assistant-client.ts +1 -1
  68. package/src/lib/assistant-config.ts +43 -23
  69. package/src/lib/backup-ops.ts +37 -251
  70. package/src/lib/cloud-client.ts +10 -13
  71. package/src/lib/cloud-login-poll.ts +75 -0
  72. package/src/lib/cloud-login.ts +5 -2
  73. package/src/lib/cloud-releases.ts +54 -10
  74. package/src/lib/cloud-values.ts +1 -1
  75. package/src/lib/confirm-action.ts +2 -2
  76. package/src/lib/docker.ts +65 -34
  77. package/src/lib/ensure-binary.ts +65 -6
  78. package/src/lib/github-repository.ts +2 -0
  79. package/src/lib/guardian-token.ts +1 -23
  80. package/src/lib/hatch-local.ts +18 -52
  81. package/src/lib/hatch-next-steps.ts +15 -2
  82. package/src/lib/host-teleport.ts +506 -0
  83. package/src/lib/http-client.ts +25 -0
  84. package/src/lib/ingress-config.ts +31 -0
  85. package/src/lib/live-voice/tunnel.test.ts +1 -1
  86. package/src/lib/live-voice/tunnel.ts +3 -3
  87. package/src/lib/local.ts +10 -8
  88. package/src/lib/nginx-ingress.ts +572 -95
  89. package/src/lib/process.ts +13 -0
  90. package/src/lib/root-backup.ts +917 -0
  91. package/src/lib/root-signing-key.ts +31 -0
  92. package/src/lib/runtime-url.ts +0 -29
  93. package/src/lib/teleport-backup.ts +3 -16
  94. package/src/lib/tunnel.ts +87 -34
  95. package/src/lib/tunnels/cadet.ts +12 -1
  96. package/src/lib/tunnels/cloudflare.ts +36 -1
  97. package/src/lib/tunnels/ngrok.ts +16 -2
  98. package/src/lib/tunnels/process-lifecycle.ts +26 -0
  99. package/src/lib/tunnels/tailscale.ts +79 -1
  100. package/src/lib/upgrade-lifecycle.ts +63 -141
  101. package/src/lib/web-dist.ts +79 -1
  102. package/src/lib/xdg-log.ts +11 -0
  103. package/src/__tests__/bundle-staging.test.ts +0 -268
  104. package/src/__tests__/unpair.test.ts +0 -160
  105. package/src/commands/rollback.ts +0 -479
  106. package/src/commands/unpair.ts +0 -122
  107. package/src/lib/__tests__/job-polling.test.ts +0 -278
  108. package/src/lib/__tests__/local-runtime-client.test.ts +0 -666
  109. package/src/lib/bundle-staging.ts +0 -220
  110. package/src/lib/job-polling.ts +0 -195
  111. package/src/lib/local-runtime-client.ts +0 -374
@@ -0,0 +1,185 @@
1
+ import { afterAll, beforeEach, expect, spyOn, test } from "bun:test";
2
+ import * as backups from "../lib/backup-ops.js";
3
+ import * as docker from "../lib/docker.js";
4
+ import * as lifecycle from "../lib/upgrade-lifecycle.js";
5
+ import * as config from "../lib/assistant-config.js";
6
+ import { mockCommands } from "./commands-mock.js";
7
+ import * as loopback from "../lib/loopback-fetch.js";
8
+
9
+ const capture = spyOn(backups, "createBackup");
10
+ const images = spyOn(docker, "captureImageRefs");
11
+ const pull = spyOn(docker, "pullImage");
12
+ const wake = spyOn(docker, "wakeContainers");
13
+ const save = spyOn(config, "saveAssistantEntry");
14
+ const restore = spyOn(backups, "restoreBackup");
15
+ const sleep = spyOn(docker, "sleepContainers");
16
+ const stop = spyOn(docker, "stopContainers");
17
+ const start = spyOn(docker, "startContainers");
18
+ const fetch = spyOn(loopback, "loopbackSafeFetch");
19
+ const res = docker.dockerResourceNames("recovery");
20
+ const containerOptions = {
21
+ gatewayPort: 7830,
22
+ assistantPort: 3001,
23
+ imageTags: {
24
+ assistant: "assistant:old",
25
+ gateway: "gateway:old",
26
+ "credential-executor": "ces:old",
27
+ },
28
+ instanceName: "recovery",
29
+ res,
30
+ };
31
+ const events: string[] = [];
32
+ beforeEach(() => {
33
+ events.length = 0;
34
+ capture.mockReset();
35
+ images.mockReset();
36
+ pull.mockReset();
37
+ wake.mockReset();
38
+ save.mockReset();
39
+ save.mockImplementation(() => {});
40
+ restore.mockReset();
41
+ sleep.mockReset();
42
+ stop.mockReset();
43
+ start.mockReset();
44
+ fetch.mockReset();
45
+ restore.mockImplementation(async () => {
46
+ events.push("restore-roots");
47
+ return true;
48
+ });
49
+ sleep.mockImplementation(async () => {
50
+ events.push("sleep");
51
+ });
52
+ stop.mockImplementation(async () => {
53
+ events.push("remove");
54
+ });
55
+ start.mockImplementation(async () => {
56
+ events.push("start-old-version");
57
+ });
58
+ fetch.mockImplementation(async () => {
59
+ events.push("check-ready");
60
+ return Response.json({ ready: true });
61
+ });
62
+ });
63
+ afterAll(() => {
64
+ for (const spy of [
65
+ restore,
66
+ sleep,
67
+ stop,
68
+ start,
69
+ fetch,
70
+ capture,
71
+ images,
72
+ pull,
73
+ wake,
74
+ save,
75
+ ])
76
+ spy.mockRestore();
77
+ });
78
+
79
+ test("failed-state recovery restores tar roots while stopped before restarting old services", async () => {
80
+ expect(
81
+ await lifecycle.attemptFailedStateRestore({
82
+ runtimeUrl: "http://127.0.0.1:1",
83
+ assistantId: "recovery",
84
+ backupPath: "pre-upgrade.tar.gz",
85
+ backupLabel: "pre-upgrade",
86
+ res,
87
+ containerOptions,
88
+ }),
89
+ ).toEqual({ restored: true, ready: true });
90
+ expect(restore).toHaveBeenCalledWith(
91
+ "http://127.0.0.1:1",
92
+ "recovery",
93
+ "pre-upgrade.tar.gz",
94
+ { resume: false },
95
+ );
96
+ expect(events).toEqual([
97
+ "restore-roots",
98
+ "sleep",
99
+ "remove",
100
+ "start-old-version",
101
+ "check-ready",
102
+ ]);
103
+ });
104
+
105
+ test("failed root restore does not restart a partial installation", async () => {
106
+ restore.mockResolvedValue(false);
107
+ expect(
108
+ await lifecycle.attemptFailedStateRestore({
109
+ runtimeUrl: "http://127.0.0.1:1",
110
+ assistantId: "recovery",
111
+ backupPath: "pre-upgrade.tar.gz",
112
+ backupLabel: "pre-upgrade",
113
+ res,
114
+ containerOptions,
115
+ }),
116
+ ).toEqual({ restored: false, ready: false });
117
+ expect(start).not.toHaveBeenCalled();
118
+ expect(stop).not.toHaveBeenCalled();
119
+ expect(fetch).not.toHaveBeenCalled();
120
+ });
121
+
122
+ test("versioned restore installs roots into stopped target containers before first startup", async () => {
123
+ const commands = mockCommands();
124
+ commands.on(
125
+ "docker",
126
+ (args) => args[0] === "inspect",
127
+ () => ({
128
+ stdout: JSON.stringify(["ACTOR_TOKEN_SIGNING_KEY=" + "01".repeat(32)]),
129
+ }),
130
+ );
131
+ const entry: config.AssistantEntry = {
132
+ assistantId: "recovery",
133
+ cloud: "docker",
134
+ runtimeUrl: "http://127.0.0.1:1",
135
+ };
136
+ capture.mockImplementation(async () => {
137
+ events.push("capture-safety");
138
+ return "/tmp/pre-rollback.tar.gz";
139
+ });
140
+ images.mockResolvedValue({
141
+ assistant: "assistant:current",
142
+ gateway: "gateway:current",
143
+ "credential-executor": "ces:current",
144
+ });
145
+ pull.mockResolvedValue(undefined);
146
+ start.mockImplementation(async (options) => {
147
+ expect(options.createOnly).toBe(true);
148
+ events.push("create-stopped-target");
149
+ });
150
+ wake.mockImplementation(async () => {
151
+ events.push("wake-restored-target");
152
+ });
153
+ fetch.mockImplementation(async (url) => {
154
+ if (String(url).includes("/healthz"))
155
+ return Response.json({ version: "0.12.11" });
156
+ if (String(url).includes("/readyz")) {
157
+ events.push("check-ready");
158
+ return Response.json({ ready: true });
159
+ }
160
+ return Response.json({});
161
+ });
162
+ try {
163
+ await lifecycle.performDockerRollback(entry, {
164
+ targetVersion: "0.12.10",
165
+ backupPath: "/tmp/source.tar.gz",
166
+ });
167
+ expect(restore).toHaveBeenCalledWith(
168
+ entry.runtimeUrl,
169
+ entry.assistantId,
170
+ "/tmp/source.tar.gz",
171
+ { resume: false },
172
+ );
173
+ expect(events).toEqual([
174
+ "capture-safety",
175
+ "sleep",
176
+ "remove",
177
+ "create-stopped-target",
178
+ "restore-roots",
179
+ "wake-restored-target",
180
+ "check-ready",
181
+ ]);
182
+ } finally {
183
+ commands.restore();
184
+ }
185
+ });
@@ -173,7 +173,7 @@ mock.module("../lib/ingress-config.js", () => ({
173
173
  }));
174
174
 
175
175
  const ensureTunnelEdgeMock = mock<typeof nginxIngress.ensureTunnelEdge>(
176
- async () => ({ port: 7840, started: true, includesWebApp: true }),
176
+ async () => ({ port: 7840, started: true, includesWebApp: true, shellMode: "web" }),
177
177
  );
178
178
  const isIngressRunningMock = mock<typeof nginxIngress.isIngressRunning>(
179
179
  () => false,
@@ -305,6 +305,7 @@ beforeEach(() => {
305
305
  port: 7840,
306
306
  started: true,
307
307
  includesWebApp: true,
308
+ shellMode: "web",
308
309
  });
309
310
  isIngressRunningMock.mockReset();
310
311
  isIngressRunningMock.mockReturnValue(false);
@@ -1073,6 +1074,7 @@ describe("cadet wake — tunnel edge restore", () => {
1073
1074
  port: 7840,
1074
1075
  started: false,
1075
1076
  includesWebApp: true,
1077
+ shellMode: "web",
1076
1078
  });
1077
1079
  process.argv = ["bun", "cadet", "wake", "docker-1"];
1078
1080
 
@@ -1579,6 +1581,7 @@ describe("cadet wake — tunnel edge restore", () => {
1579
1581
  port: 7841,
1580
1582
  started: false,
1581
1583
  includesWebApp: true,
1584
+ shellMode: "web",
1582
1585
  });
1583
1586
 
1584
1587
  await wake();
@@ -227,3 +227,28 @@ test("preserves PID tracking when process termination fails", async () => {
227
227
  expect(isProcessAlive(pidFile)).toEqual({ alive: true, pid: process.pid });
228
228
  rmSync(dir, { recursive: true, force: true });
229
229
  });
230
+
231
+ test("graceful-only stop times out without SIGKILL or losing PID identity", async () => {
232
+ const originalKill = process.kill;
233
+ const calls: Array<NodeJS.Signals | number | undefined> = [];
234
+ process.kill = ((_pid: number, signal?: NodeJS.Signals | number) => {
235
+ calls.push(signal);
236
+ return true;
237
+ }) as typeof process.kill;
238
+ try {
239
+ expect(
240
+ await stopProcess(
241
+ 4812,
242
+ "backup service",
243
+ 0,
244
+ "linux",
245
+ undefined,
246
+ () => "cadet-daemon",
247
+ true,
248
+ ),
249
+ ).toBe(false);
250
+ expect(calls).toEqual([0, "SIGTERM", 0]);
251
+ } finally {
252
+ process.kill = originalKill;
253
+ }
254
+ });