@mycadet/cli 0.12.11 → 0.13.0-dev.202610171626.d97b8b8

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 (94) 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 +2 -2
  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 +2 -2
  5. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
  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 +2 -2
  8. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
  9. package/node_modules/@mycadet/service-contracts/src/channels.ts +0 -1
  10. package/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
  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-releases.test.ts +82 -0
  17. package/src/__tests__/commands-mock.ts +101 -0
  18. package/src/__tests__/debug.test.ts +17 -91
  19. package/src/__tests__/devices.test.ts +2 -2
  20. package/src/__tests__/gateway-token.test.ts +267 -0
  21. package/src/__tests__/host-teleport-docker.test.ts +371 -0
  22. package/src/__tests__/host-teleport.test.ts +405 -0
  23. package/src/__tests__/nginx-ingress.test.ts +536 -23
  24. package/src/__tests__/orphan-detection.test.ts +1 -1
  25. package/src/__tests__/paired-lifecycle.test.ts +2 -2
  26. package/src/__tests__/ps-cloud-status.test.ts +6 -6
  27. package/src/__tests__/root-backup-docker.test.ts +294 -0
  28. package/src/__tests__/root-backup.test.ts +527 -0
  29. package/src/__tests__/root-restore-command.test.ts +147 -0
  30. package/src/__tests__/root-signing-key.test.ts +50 -0
  31. package/src/__tests__/setup.test.ts +3 -3
  32. package/src/__tests__/shell-mode.test.ts +83 -0
  33. package/src/__tests__/sleep.test.ts +11 -0
  34. package/src/__tests__/teleport.test.ts +68 -2996
  35. package/src/__tests__/tunnel.test.ts +10 -2
  36. package/src/__tests__/upgrade-local.test.ts +21 -11
  37. package/src/__tests__/upgrade-replay-env.test.ts +2 -2
  38. package/src/__tests__/upgrade-root-recovery.test.ts +185 -0
  39. package/src/__tests__/wake.test.ts +4 -1
  40. package/src/__tests__/windows-lifecycle.test.ts +25 -0
  41. package/src/commands/backup.ts +44 -399
  42. package/src/commands/client.ts +3 -3
  43. package/src/commands/completions.ts +0 -2
  44. package/src/commands/debug.ts +75 -131
  45. package/src/commands/devices.ts +51 -17
  46. package/src/commands/gateway/token.ts +77 -23
  47. package/src/commands/pair.ts +2 -2
  48. package/src/commands/restore.ts +77 -444
  49. package/src/commands/retire.ts +2 -3
  50. package/src/commands/sleep.ts +15 -2
  51. package/src/commands/teleport.ts +47 -1559
  52. package/src/commands/tunnel.ts +48 -7
  53. package/src/commands/upgrade.ts +78 -156
  54. package/src/commands/wake.ts +1 -1
  55. package/src/index.ts +0 -4
  56. package/src/lib/__tests__/http-client-version.test.ts +29 -0
  57. package/src/lib/__tests__/runtime-url.test.ts +3 -75
  58. package/src/lib/__tests__/web-dist.test.ts +27 -1
  59. package/src/lib/assistant-client.ts +1 -1
  60. package/src/lib/assistant-config.ts +23 -18
  61. package/src/lib/backup-ops.ts +37 -251
  62. package/src/lib/cloud-client.ts +10 -13
  63. package/src/lib/cloud-login.ts +2 -1
  64. package/src/lib/cloud-releases.ts +54 -10
  65. package/src/lib/cloud-values.ts +1 -1
  66. package/src/lib/confirm-action.ts +2 -2
  67. package/src/lib/docker.ts +59 -22
  68. package/src/lib/github-repository.ts +2 -0
  69. package/src/lib/guardian-token.ts +1 -23
  70. package/src/lib/hatch-local.ts +17 -1
  71. package/src/lib/host-teleport.ts +506 -0
  72. package/src/lib/http-client.ts +25 -0
  73. package/src/lib/ingress-config.ts +31 -0
  74. package/src/lib/live-voice/tunnel.test.ts +1 -1
  75. package/src/lib/live-voice/tunnel.ts +1 -1
  76. package/src/lib/local.ts +4 -2
  77. package/src/lib/nginx-ingress.ts +454 -79
  78. package/src/lib/process.ts +13 -0
  79. package/src/lib/root-backup.ts +917 -0
  80. package/src/lib/root-signing-key.ts +31 -0
  81. package/src/lib/runtime-url.ts +0 -29
  82. package/src/lib/teleport-backup.ts +3 -16
  83. package/src/lib/tunnel.ts +1 -0
  84. package/src/lib/upgrade-lifecycle.ts +63 -141
  85. package/src/lib/web-dist.ts +79 -1
  86. package/src/__tests__/bundle-staging.test.ts +0 -268
  87. package/src/__tests__/unpair.test.ts +0 -160
  88. package/src/commands/rollback.ts +0 -479
  89. package/src/commands/unpair.ts +0 -122
  90. package/src/lib/__tests__/job-polling.test.ts +0 -278
  91. package/src/lib/__tests__/local-runtime-client.test.ts +0 -666
  92. package/src/lib/bundle-staging.ts +0 -220
  93. package/src/lib/job-polling.ts +0 -195
  94. package/src/lib/local-runtime-client.ts +0 -374
@@ -1,278 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
-
3
- import { pollJobUntilDone } from "../job-polling.js";
4
- import type { UnifiedJobStatus } from "../cloud-client.js";
5
-
6
- describe("pollJobUntilDone", () => {
7
- test("returns terminal 'complete' after N processing polls", async () => {
8
- const statuses: UnifiedJobStatus[] = [
9
- { jobId: "j1", type: "export", status: "processing" },
10
- { jobId: "j1", type: "export", status: "processing" },
11
- {
12
- jobId: "j1",
13
- type: "export",
14
- status: "complete",
15
- bundleKey: "bundles/j1.tar.gz",
16
- },
17
- ];
18
- let i = 0;
19
- const result = await pollJobUntilDone({
20
- poll: async () => statuses[i++]!,
21
- intervalMs: 1,
22
- timeoutMs: 1_000,
23
- label: "test export",
24
- });
25
-
26
- expect(result.status).toBe("complete");
27
- if (result.status === "complete") {
28
- expect(result.bundleKey).toBe("bundles/j1.tar.gz");
29
- }
30
- expect(i).toBe(3);
31
- });
32
-
33
- test("propagates terminal 'failed' status to caller without throwing", async () => {
34
- const result = await pollJobUntilDone({
35
- poll: async () => ({
36
- jobId: "j2",
37
- type: "import",
38
- status: "failed",
39
- error: "bad bundle",
40
- }),
41
- intervalMs: 1,
42
- timeoutMs: 1_000,
43
- label: "test import",
44
- });
45
-
46
- expect(result.status).toBe("failed");
47
- if (result.status === "failed") {
48
- expect(result.error).toBe("bad bundle");
49
- }
50
- });
51
-
52
- test("throws with label when polling exceeds timeoutMs", async () => {
53
- let calls = 0;
54
- await expect(
55
- pollJobUntilDone({
56
- poll: async () => {
57
- calls += 1;
58
- return { jobId: "j3", type: "export", status: "processing" };
59
- },
60
- intervalMs: 20,
61
- timeoutMs: 10,
62
- label: "slow export",
63
- }),
64
- ).rejects.toThrow(/slow export/);
65
-
66
- // The loop does one poll before checking the deadline, so calls ≥ 1.
67
- expect(calls).toBeGreaterThanOrEqual(1);
68
- });
69
-
70
- test("uses defaults when intervalMs/timeoutMs are omitted (fast path)", async () => {
71
- // Fast path: first poll is already terminal so neither default matters.
72
- const result = await pollJobUntilDone({
73
- poll: async () => ({ jobId: "j4", type: "export", status: "complete" }),
74
- label: "defaults test",
75
- });
76
- expect(result.status).toBe("complete");
77
- });
78
-
79
- describe("transient-error retry", () => {
80
- let warnSpy: ReturnType<typeof spyOn>;
81
-
82
- beforeEach(() => {
83
- warnSpy = spyOn(console, "warn").mockImplementation(() => {});
84
- });
85
-
86
- afterEach(() => {
87
- warnSpy.mockRestore();
88
- });
89
-
90
- test("retries N-1 transient errors then returns terminal status", async () => {
91
- const maxTransientErrors = 3;
92
- let calls = 0;
93
- const result = await pollJobUntilDone({
94
- label: "flaky export",
95
- intervalMs: 1,
96
- timeoutMs: 1_000,
97
- maxTransientErrors,
98
- poll: async () => {
99
- calls += 1;
100
- if (calls < maxTransientErrors) {
101
- throw new Error(
102
- `Local job status check failed: 503 Service Unavailable`,
103
- );
104
- }
105
- return {
106
- jobId: "j5",
107
- type: "export",
108
- status: "complete",
109
- } as UnifiedJobStatus;
110
- },
111
- });
112
- expect(result.status).toBe("complete");
113
- expect(calls).toBe(maxTransientErrors);
114
- // One warning per retried transient error (first two attempts).
115
- expect(warnSpy).toHaveBeenCalledTimes(maxTransientErrors - 1);
116
- });
117
-
118
- test("propagates the last error once maxTransientErrors is exceeded", async () => {
119
- const maxTransientErrors = 2;
120
- let calls = 0;
121
- await expect(
122
- pollJobUntilDone({
123
- label: "always broken",
124
- intervalMs: 1,
125
- timeoutMs: 1_000,
126
- maxTransientErrors,
127
- poll: async () => {
128
- calls += 1;
129
- throw new Error(`Local job status check failed: 502 Bad Gateway`);
130
- },
131
- }),
132
- ).rejects.toThrow(/502 Bad Gateway/);
133
- // Helper makes `maxTransientErrors + 1` attempts before giving up: the
134
- // first attempt plus N retries, counted against the budget.
135
- expect(calls).toBe(maxTransientErrors + 1);
136
- });
137
-
138
- test("permanent 4xx errors (except 429) propagate immediately", async () => {
139
- let calls = 0;
140
- await expect(
141
- pollJobUntilDone({
142
- label: "auth broken",
143
- intervalMs: 1,
144
- timeoutMs: 1_000,
145
- maxTransientErrors: 5,
146
- poll: async () => {
147
- calls += 1;
148
- throw new Error(`Local job status check failed: 403 Forbidden`);
149
- },
150
- }),
151
- ).rejects.toThrow(/403 Forbidden/);
152
- expect(calls).toBe(1);
153
- expect(warnSpy).not.toHaveBeenCalled();
154
- });
155
-
156
- test("429 rate-limit is retried as transient", async () => {
157
- let calls = 0;
158
- const result = await pollJobUntilDone({
159
- label: "rate limited",
160
- intervalMs: 1,
161
- timeoutMs: 1_000,
162
- maxTransientErrors: 3,
163
- poll: async () => {
164
- calls += 1;
165
- if (calls === 1) {
166
- throw new Error(`Local job status check failed: 429 Too Many`);
167
- }
168
- return {
169
- jobId: "j6",
170
- type: "export",
171
- status: "complete",
172
- } as UnifiedJobStatus;
173
- },
174
- });
175
- expect(result.status).toBe("complete");
176
- expect(calls).toBe(2);
177
- });
178
-
179
- test("refreshOn401 is invoked on 401 and polling continues after refresh", async () => {
180
- let calls = 0;
181
- let refreshes = 0;
182
- const result = await pollJobUntilDone({
183
- label: "expiring auth",
184
- intervalMs: 1,
185
- timeoutMs: 1_000,
186
- maxTransientErrors: 0,
187
- refreshOn401: async () => {
188
- refreshes += 1;
189
- },
190
- poll: async () => {
191
- calls += 1;
192
- if (calls === 1) {
193
- throw new Error("Local job status check failed: 401 Unauthorized");
194
- }
195
- return {
196
- jobId: "j401",
197
- type: "export",
198
- status: "complete",
199
- } as UnifiedJobStatus;
200
- },
201
- });
202
- expect(result.status).toBe("complete");
203
- expect(refreshes).toBe(1);
204
- expect(calls).toBe(2);
205
- // The 401 branch logs its own distinct warning (not the generic
206
- // "polling failed:" one) so operators can distinguish an
207
- // auth refresh from a transient-error retry in the output.
208
- expect(warnSpy).toHaveBeenCalledWith(
209
- expect.stringContaining("refreshing auth"),
210
- );
211
- });
212
-
213
- test("propagates 401 once maxAuthRefreshes is exceeded", async () => {
214
- const maxAuthRefreshes = 2;
215
- let calls = 0;
216
- let refreshes = 0;
217
- await expect(
218
- pollJobUntilDone({
219
- label: "persistently unauthorized",
220
- intervalMs: 1,
221
- timeoutMs: 1_000,
222
- maxAuthRefreshes,
223
- refreshOn401: async () => {
224
- refreshes += 1;
225
- },
226
- poll: async () => {
227
- calls += 1;
228
- throw new Error("Local job status check failed: 401 Unauthorized");
229
- },
230
- }),
231
- ).rejects.toThrow(/401 Unauthorized/);
232
- // Helper allows `maxAuthRefreshes` successful refresh-and-retry cycles
233
- // (each counted against the budget after the poll fails), plus one
234
- // final attempt on the refreshed token that exceeds the budget.
235
- expect(calls).toBe(maxAuthRefreshes + 1);
236
- expect(refreshes).toBe(maxAuthRefreshes);
237
- });
238
-
239
- test("without refreshOn401, 401 still propagates as a permanent 4xx", async () => {
240
- let calls = 0;
241
- await expect(
242
- pollJobUntilDone({
243
- label: "no refresh hook",
244
- intervalMs: 1,
245
- timeoutMs: 1_000,
246
- poll: async () => {
247
- calls += 1;
248
- throw new Error("Local job status check failed: 401 Unauthorized");
249
- },
250
- }),
251
- ).rejects.toThrow(/401 Unauthorized/);
252
- expect(calls).toBe(1);
253
- });
254
-
255
- test("unclassified network-style errors are treated as transient", async () => {
256
- let calls = 0;
257
- const result = await pollJobUntilDone({
258
- label: "network blip",
259
- intervalMs: 1,
260
- timeoutMs: 1_000,
261
- maxTransientErrors: 3,
262
- poll: async () => {
263
- calls += 1;
264
- if (calls === 1) {
265
- throw new Error("fetch failed");
266
- }
267
- return {
268
- jobId: "j7",
269
- type: "export",
270
- status: "complete",
271
- } as UnifiedJobStatus;
272
- },
273
- });
274
- expect(result.status).toBe("complete");
275
- expect(calls).toBe(2);
276
- });
277
- });
278
- });