@mycadet/cli 0.12.7 → 0.12.9
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/README.md +2 -2
- package/knip.json +1 -1
- package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
- package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +18 -0
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
- package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
- package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
- package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
- package/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/package.json +3 -2
- package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
- package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
- package/src/__tests__/assistant-client-refresh.test.ts +2 -2
- package/src/__tests__/backup.test.ts +86 -91
- package/src/__tests__/client-token.test.ts +1 -1
- package/src/__tests__/client-tui-refresh.test.ts +1 -1
- package/src/__tests__/client-web-config.test.ts +2 -2
- package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
- package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +28 -18
- package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
- package/src/__tests__/connect-import.test.ts +55 -39
- package/src/__tests__/debug.test.ts +38 -38
- package/src/__tests__/ensure-binary.test.ts +182 -0
- package/src/__tests__/guardian-token.test.ts +149 -56
- package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
- package/src/__tests__/helpers/guardian-token.ts +22 -0
- package/src/__tests__/helpers/http.ts +10 -0
- package/src/__tests__/nginx-ingress.test.ts +21 -20
- package/src/__tests__/ngrok-spawn.test.ts +49 -0
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +1 -8
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
- package/src/__tests__/ps-table.test.ts +79 -0
- package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
- package/src/__tests__/retire.test.ts +58 -199
- package/src/__tests__/root-process-lockfile.test.ts +99 -0
- package/src/__tests__/sleep.test.ts +19 -19
- package/src/__tests__/statefulset.test.ts +8 -8
- package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +2 -2
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +181 -197
- package/src/__tests__/tunnel.test.ts +257 -222
- package/src/__tests__/uninstall.test.ts +13 -4
- package/src/__tests__/upgrade-local.test.ts +3 -3
- package/src/__tests__/upgrade-replay-env.test.ts +12 -12
- package/src/__tests__/wake.test.ts +260 -43
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +24 -24
- package/src/commands/client.ts +76 -76
- package/src/commands/completions.test.ts +104 -0
- package/src/commands/completions.ts +87 -0
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +12 -12
- package/src/commands/flags.ts +2 -2
- package/src/commands/hatch.ts +23 -23
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +8 -8
- package/src/commands/ps.ts +244 -86
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +140 -22
- package/src/commands/retire.ts +48 -150
- package/src/commands/rollback.ts +20 -26
- package/src/commands/setup.ts +2 -2
- package/src/commands/sleep.ts +2 -2
- package/src/commands/teleport.ts +109 -109
- package/src/commands/terminal.ts +158 -18
- package/src/commands/tunnel.ts +138 -157
- package/src/commands/uninstall.ts +57 -9
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +30 -54
- package/src/commands/use.ts +1 -1
- package/src/commands/wake.ts +166 -50
- package/src/index.ts +18 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/docker.test.ts +168 -20
- package/src/lib/__tests__/job-polling.test.ts +2 -2
- package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
- package/src/lib/__tests__/runtime-url.test.ts +1 -1
- package/src/lib/assistant-api-key-resolution.ts +5 -5
- package/src/lib/assistant-client.ts +8 -8
- package/src/lib/assistant-config.ts +58 -12
- package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
- package/src/lib/cloud-login.ts +24 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +260 -53
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +2 -2
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/resolve.ts +8 -8
- package/src/lib/flag-args.test.ts +1 -1
- package/src/lib/flag-args.ts +1 -1
- package/src/lib/guardian-token.ts +37 -5
- package/src/lib/hatch-local.ts +19 -23
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +2 -2
- package/src/lib/live-voice/connection.ts +12 -12
- package/src/lib/live-voice/tunnel.test.ts +11 -11
- package/src/lib/live-voice/tunnel.ts +15 -15
- package/src/lib/local-runtime-client.ts +20 -20
- package/src/lib/local.ts +10 -31
- package/src/lib/nginx-ingress.ts +42 -15
- package/src/lib/pair.ts +1 -1
- package/src/lib/process.ts +1 -1
- package/src/lib/provider-secrets.ts +3 -3
- package/src/lib/retire-apple-container.ts +1 -1
- package/src/lib/runtime-url.ts +8 -8
- package/src/lib/statefulset.ts +2 -2
- package/src/lib/sync-cloud-assistants.ts +19 -19
- package/src/lib/teleport-backup.ts +4 -4
- package/src/lib/terminal-client.ts +17 -17
- package/src/lib/terminal-session.ts +24 -24
- package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
- package/src/lib/tunnels/cadet.ts +29 -0
- package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
- package/src/lib/upgrade-lifecycle.ts +8 -21
- package/src/lib/upgrade-preflight.ts +2 -2
- package/src/lib/workos-pkce.ts +5 -5
- package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
- package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
- package/src/commands/recover.ts +0 -131
- package/src/commands/roadmap.ts +0 -459
- package/src/commands/ssh.ts +0 -144
- package/src/commands/workflows.ts +0 -305
- package/src/lib/managed-cloud.ts +0 -3
|
@@ -61,7 +61,7 @@ afterAll(() => {
|
|
|
61
61
|
mock.module("../lib/step-runner.js", () => ({ exec: realExec }));
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
import {
|
|
64
|
+
import { restore } from "../commands/restore.js";
|
|
65
65
|
|
|
66
66
|
// ── Test fixtures ─────────────────────────────────────────────────────────────
|
|
67
67
|
|
|
@@ -175,18 +175,19 @@ afterAll(() => {
|
|
|
175
175
|
|
|
176
176
|
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
177
177
|
|
|
178
|
-
describe("
|
|
178
|
+
describe("restore --help", () => {
|
|
179
179
|
test("prints usage, description, and examples then exits 0", async () => {
|
|
180
|
-
process.argv = ["bun", "cadet", "
|
|
181
|
-
await expect(
|
|
180
|
+
process.argv = ["bun", "cadet", "restore", "--help"];
|
|
181
|
+
await expect(restore()).rejects.toThrow("process.exit(0)");
|
|
182
182
|
const output = consoleLogSpy.mock.calls.flat().join("\n");
|
|
183
|
-
expect(output).toContain("Usage: cadet
|
|
183
|
+
expect(output).toContain("Usage: cadet restore <name>");
|
|
184
|
+
expect(output).toContain("recovers a retired assistant");
|
|
184
185
|
expect(output).toContain("Examples:");
|
|
185
|
-
expect(output).toContain("cadet
|
|
186
|
+
expect(output).toContain("cadet restore");
|
|
186
187
|
});
|
|
187
188
|
});
|
|
188
189
|
|
|
189
|
-
describe("
|
|
190
|
+
describe("restore error cases", () => {
|
|
190
191
|
for (const failure of ["missing", "conflict"] as const) {
|
|
191
192
|
test(`recovery ${failure} leaves destination and registry untouched and permits retry`, async () => {
|
|
192
193
|
const name = `retry-${failure}`;
|
|
@@ -211,8 +212,8 @@ describe("recover error cases", () => {
|
|
|
211
212
|
}
|
|
212
213
|
const registry = resolveLockfilePaths(process.env)[0]!;
|
|
213
214
|
const before = readFileSync(registry, "utf8");
|
|
214
|
-
process.argv = ["bun", "cadet", "
|
|
215
|
-
await expect(
|
|
215
|
+
process.argv = ["bun", "cadet", "restore", name];
|
|
216
|
+
await expect(restore()).rejects.toThrow(`signing key ${failure}`);
|
|
216
217
|
expect(existsSync(instanceDir)).toBe(false);
|
|
217
218
|
expect(readFileSync(registry, "utf8")).toBe(before);
|
|
218
219
|
expect(existsSync(archivePath)).toBe(true);
|
|
@@ -222,7 +223,7 @@ describe("recover error cases", () => {
|
|
|
222
223
|
// Restore the authoritative identity in the archive metadata and retry.
|
|
223
224
|
entry.resources!.signingKey = "ab".repeat(32);
|
|
224
225
|
writeFileSync(metadataPath, JSON.stringify(entry));
|
|
225
|
-
await
|
|
226
|
+
await restore();
|
|
226
227
|
expect(existsSync(instanceDir)).toBe(true);
|
|
227
228
|
expect(existsSync(archivePath)).toBe(false);
|
|
228
229
|
expect(startGatewayMock).toHaveBeenCalledTimes(1);
|
|
@@ -241,8 +242,8 @@ describe("recover error cases", () => {
|
|
|
241
242
|
join(dir, "actor-token-signing-key"),
|
|
242
243
|
Buffer.from("cd".repeat(32), "hex"),
|
|
243
244
|
);
|
|
244
|
-
process.argv = ["bun", "cadet", "
|
|
245
|
-
await
|
|
245
|
+
process.argv = ["bun", "cadet", "restore", name];
|
|
246
|
+
await restore();
|
|
246
247
|
expect(startGatewayMock).toHaveBeenCalledWith(
|
|
247
248
|
false,
|
|
248
249
|
expect.objectContaining({ signingKey: "cd".repeat(32) }),
|
|
@@ -256,8 +257,8 @@ describe("recover error cases", () => {
|
|
|
256
257
|
const entry = makeEntry(name, join(testDir, "cadet", "assistants", name));
|
|
257
258
|
delete entry.resources!.signingKey;
|
|
258
259
|
const { archivePath, metadataPath } = writeArchiveFixtures(name, entry);
|
|
259
|
-
process.argv = ["bun", "cadet", "
|
|
260
|
-
await expect(
|
|
260
|
+
process.argv = ["bun", "cadet", "restore", name];
|
|
261
|
+
await expect(restore()).rejects.toThrow("signing key missing");
|
|
261
262
|
expect(existsSync(archivePath)).toBe(true);
|
|
262
263
|
expect(existsSync(metadataPath)).toBe(true);
|
|
263
264
|
expect(startLocalDaemonMock).not.toHaveBeenCalled();
|
|
@@ -265,14 +266,14 @@ describe("recover error cases", () => {
|
|
|
265
266
|
});
|
|
266
267
|
|
|
267
268
|
test("exits 1 when no name is given", async () => {
|
|
268
|
-
process.argv = ["bun", "cadet", "
|
|
269
|
-
await expect(
|
|
270
|
-
expect(consoleErrorSpy.mock.calls[0][0]).toContain("
|
|
269
|
+
process.argv = ["bun", "cadet", "restore"];
|
|
270
|
+
await expect(restore()).rejects.toThrow("process.exit(1)");
|
|
271
|
+
expect(consoleErrorSpy.mock.calls[0][0]).toContain("<name> is required");
|
|
271
272
|
});
|
|
272
273
|
|
|
273
274
|
test("exits 1 when archive is missing", async () => {
|
|
274
|
-
process.argv = ["bun", "cadet", "
|
|
275
|
-
await expect(
|
|
275
|
+
process.argv = ["bun", "cadet", "restore", "ghost-assistant"];
|
|
276
|
+
await expect(restore()).rejects.toThrow("process.exit(1)");
|
|
276
277
|
expect(consoleErrorSpy.mock.calls[0][0]).toContain(
|
|
277
278
|
"No retired archive found for 'ghost-assistant'",
|
|
278
279
|
);
|
|
@@ -292,8 +293,8 @@ describe("recover error cases", () => {
|
|
|
292
293
|
join(retiredDir, `${name}.json`),
|
|
293
294
|
JSON.stringify(entry, null, 2) + "\n",
|
|
294
295
|
);
|
|
295
|
-
process.argv = ["bun", "cadet", "
|
|
296
|
-
await expect(
|
|
296
|
+
process.argv = ["bun", "cadet", "restore", name];
|
|
297
|
+
await expect(restore()).rejects.toThrow("missing resource configuration");
|
|
297
298
|
});
|
|
298
299
|
|
|
299
300
|
test("exits 1 when target .cadet/ already exists", async () => {
|
|
@@ -303,13 +304,13 @@ describe("recover error cases", () => {
|
|
|
303
304
|
writeArchiveFixtures(name, entry);
|
|
304
305
|
// Pre-create the collision path that recover checks
|
|
305
306
|
mkdirSync(join(instanceDir, ".cadet"), { recursive: true });
|
|
306
|
-
process.argv = ["bun", "cadet", "
|
|
307
|
-
await expect(
|
|
307
|
+
process.argv = ["bun", "cadet", "restore", name];
|
|
308
|
+
await expect(restore()).rejects.toThrow("process.exit(1)");
|
|
308
309
|
expect(consoleErrorSpy.mock.calls[0][0]).toContain("already exists");
|
|
309
310
|
});
|
|
310
311
|
});
|
|
311
312
|
|
|
312
|
-
describe("
|
|
313
|
+
describe("archive-recovery extraction path — named instance (instanceDir !== homedir())", () => {
|
|
313
314
|
test("extracts to retiredDir and renames staging dir to instanceDir directly", async () => {
|
|
314
315
|
const name = "named-instance";
|
|
315
316
|
const instanceDir = join(testDir, "custom-location", name);
|
|
@@ -321,8 +322,8 @@ describe("recover extraction path — named instance (instanceDir !== homedir())
|
|
|
321
322
|
// Parent dir must exist for renameSync
|
|
322
323
|
mkdirSync(join(testDir, "custom-location"), { recursive: true });
|
|
323
324
|
|
|
324
|
-
process.argv = ["bun", "cadet", "
|
|
325
|
-
await
|
|
325
|
+
process.argv = ["bun", "cadet", "restore", name];
|
|
326
|
+
await restore();
|
|
326
327
|
|
|
327
328
|
// exec must have been called with -C retiredDir
|
|
328
329
|
expect(execMock).toHaveBeenCalledTimes(1);
|
|
@@ -355,8 +356,8 @@ describe("recover extraction path — named instance (instanceDir !== homedir())
|
|
|
355
356
|
const entry = makeEntry(name, instanceDir);
|
|
356
357
|
const { archivePath, extractedPath } = writeArchiveFixtures(name, entry);
|
|
357
358
|
|
|
358
|
-
process.argv = ["bun", "cadet", "
|
|
359
|
-
await
|
|
359
|
+
process.argv = ["bun", "cadet", "restore", name];
|
|
360
|
+
await restore();
|
|
360
361
|
|
|
361
362
|
expect(existsSync(extractedPath)).toBe(false);
|
|
362
363
|
expect(existsSync(instanceDir)).toBe(true);
|
|
@@ -21,7 +21,7 @@ import { join } from "node:path";
|
|
|
21
21
|
import type { AssistantEntry } from "../lib/assistant-config.js";
|
|
22
22
|
import { loadAllAssistants } from "../lib/assistant-config.js";
|
|
23
23
|
import * as loopbackFetchModule from "../lib/loopback-fetch.js";
|
|
24
|
-
import * as
|
|
24
|
+
import * as cloudClientModule from "../lib/cloud-client.js";
|
|
25
25
|
import * as retireLocalModule from "../lib/retire-local.js";
|
|
26
26
|
|
|
27
27
|
const testDir = mkdtempSync(join(tmpdir(), "cli-retire-test-"));
|
|
@@ -35,11 +35,11 @@ const originalStdinIsRaw = process.stdin.isRaw;
|
|
|
35
35
|
const originalSetRawMode = process.stdin.setRawMode;
|
|
36
36
|
const originalStdoutWrite = process.stdout.write;
|
|
37
37
|
const realRetireLocalModule = { ...retireLocalModule };
|
|
38
|
-
const
|
|
38
|
+
const realCloudClientModule = { ...cloudClientModule };
|
|
39
39
|
const realLoopbackFetchModule = { ...loopbackFetchModule };
|
|
40
40
|
|
|
41
41
|
const retireLocalMock = mock(async () => {});
|
|
42
|
-
const
|
|
42
|
+
const readCloudTokenMock = mock((): string | null => null);
|
|
43
43
|
const authHeadersMock = mock(async () => ({
|
|
44
44
|
"Content-Type": "application/json",
|
|
45
45
|
"X-Session-Token": "session-token",
|
|
@@ -51,7 +51,7 @@ const authHeadersForKnownOrganizationMock = mock(
|
|
|
51
51
|
"Cadet-Organization-Id": organizationId,
|
|
52
52
|
}),
|
|
53
53
|
);
|
|
54
|
-
const
|
|
54
|
+
const getCloudUrlMock = mock(() => "https://platform.example.com");
|
|
55
55
|
const readGatewayCredentialMock = mock(
|
|
56
56
|
async (
|
|
57
57
|
_gatewayUrl: string,
|
|
@@ -71,12 +71,12 @@ mock.module("../lib/retire-local.js", () => ({
|
|
|
71
71
|
retireLocal: retireLocalMock,
|
|
72
72
|
}));
|
|
73
73
|
|
|
74
|
-
mock.module("../lib/
|
|
74
|
+
mock.module("../lib/cloud-client.js", () => ({
|
|
75
75
|
authHeaders: authHeadersMock,
|
|
76
76
|
authHeadersForKnownOrganization: authHeadersForKnownOrganizationMock,
|
|
77
|
-
|
|
77
|
+
getCloudUrl: getCloudUrlMock,
|
|
78
78
|
readGatewayCredential: readGatewayCredentialMock,
|
|
79
|
-
|
|
79
|
+
readCloudToken: readCloudTokenMock,
|
|
80
80
|
}));
|
|
81
81
|
|
|
82
82
|
mock.module("../lib/loopback-fetch.js", () => ({
|
|
@@ -168,7 +168,11 @@ describe("cadet retire", () => {
|
|
|
168
168
|
beforeEach(() => {
|
|
169
169
|
process.env.CADET_LOCKFILE_DIR = testDir;
|
|
170
170
|
delete process.env.CADET_PLATFORM_TOKEN;
|
|
171
|
-
for (const name of [
|
|
171
|
+
for (const name of [
|
|
172
|
+
"lockfile.json",
|
|
173
|
+
".cadet.lock.json",
|
|
174
|
+
".cadet.lockfile.json",
|
|
175
|
+
]) {
|
|
172
176
|
rmSync(join(testDir, name), { force: true });
|
|
173
177
|
}
|
|
174
178
|
process.argv = ["bun", "cadet", "retire"];
|
|
@@ -177,8 +181,8 @@ describe("cadet retire", () => {
|
|
|
177
181
|
}) as typeof process.exit;
|
|
178
182
|
retireLocalMock.mockReset();
|
|
179
183
|
retireLocalMock.mockResolvedValue(undefined);
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
readCloudTokenMock.mockReset();
|
|
185
|
+
readCloudTokenMock.mockReturnValue(null);
|
|
182
186
|
authHeadersMock.mockReset();
|
|
183
187
|
authHeadersMock.mockResolvedValue({
|
|
184
188
|
"Content-Type": "application/json",
|
|
@@ -192,8 +196,8 @@ describe("cadet retire", () => {
|
|
|
192
196
|
"Cadet-Organization-Id": organizationId,
|
|
193
197
|
}),
|
|
194
198
|
);
|
|
195
|
-
|
|
196
|
-
|
|
199
|
+
getCloudUrlMock.mockReset();
|
|
200
|
+
getCloudUrlMock.mockReturnValue("https://platform.example.com");
|
|
197
201
|
readGatewayCredentialMock.mockReset();
|
|
198
202
|
readGatewayCredentialMock.mockResolvedValue({
|
|
199
203
|
value: null,
|
|
@@ -220,7 +224,7 @@ describe("cadet retire", () => {
|
|
|
220
224
|
|
|
221
225
|
afterAll(() => {
|
|
222
226
|
mock.module("../lib/retire-local.js", () => realRetireLocalModule);
|
|
223
|
-
mock.module("../lib/
|
|
227
|
+
mock.module("../lib/cloud-client.js", () => realCloudClientModule);
|
|
224
228
|
mock.module("../lib/loopback-fetch.js", () => realLoopbackFetchModule);
|
|
225
229
|
if (originalLockfileDir === undefined) {
|
|
226
230
|
delete process.env.CADET_LOCKFILE_DIR;
|
|
@@ -252,35 +256,25 @@ describe("cadet retire", () => {
|
|
|
252
256
|
);
|
|
253
257
|
});
|
|
254
258
|
|
|
255
|
-
test("local retire unregisters the
|
|
256
|
-
const entry = makeEntry("assistant-1", {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (name === "cadet:platform_assistant_id") {
|
|
262
|
-
return { value: "platform-assistant-1", unreachable: false };
|
|
263
|
-
}
|
|
264
|
-
if (name === "cadet:platform_base_url") {
|
|
265
|
-
return { value: "https://platform.example.test", unreachable: false };
|
|
266
|
-
}
|
|
267
|
-
if (name === "cadet:platform_organization_id") {
|
|
268
|
-
return { value: "org-123", unreachable: false };
|
|
269
|
-
}
|
|
270
|
-
return { value: null, unreachable: false };
|
|
259
|
+
test("local retire unregisters the cloud record straight from the lockfile entry", async () => {
|
|
260
|
+
const entry = makeEntry("assistant-1", {
|
|
261
|
+
name: "Example Assistant",
|
|
262
|
+
localUrl: "http://127.0.0.1:7831",
|
|
263
|
+
cloudAssistantId: "cloud-asst-1",
|
|
264
|
+
cloudOrganizationId: "org-123",
|
|
271
265
|
});
|
|
266
|
+
writeLockfile([entry]);
|
|
267
|
+
readCloudTokenMock.mockReturnValue("session-token");
|
|
268
|
+
getCloudUrlMock.mockReturnValue("https://platform.example.test");
|
|
272
269
|
process.argv = ["bun", "cadet", "retire", "assistant-1", "--yes"];
|
|
273
270
|
|
|
274
271
|
await retire();
|
|
275
272
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
entry.bearerToken,
|
|
280
|
-
);
|
|
281
|
-
expect(authHeadersMock).not.toHaveBeenCalled();
|
|
273
|
+
// The cloud record resolves from the entry + env — the local gateway
|
|
274
|
+
// credential store is never consulted.
|
|
275
|
+
expect(readGatewayCredentialMock).not.toHaveBeenCalled();
|
|
282
276
|
expect(loopbackSafeFetchMock).toHaveBeenCalledWith(
|
|
283
|
-
"https://platform.example.test/
|
|
277
|
+
"https://platform.example.test/v1/assistants/cloud-asst-1/retire/",
|
|
284
278
|
{
|
|
285
279
|
method: "DELETE",
|
|
286
280
|
headers: {
|
|
@@ -294,194 +288,67 @@ describe("cadet retire", () => {
|
|
|
294
288
|
expect(loadAllAssistants()).toEqual([]);
|
|
295
289
|
});
|
|
296
290
|
|
|
297
|
-
test("local retire
|
|
298
|
-
const localGatewayUrl = "http://127.0.0.1:7831";
|
|
299
|
-
const entry = makeEntry("assistant-1", {
|
|
300
|
-
name: "Example Assistant",
|
|
301
|
-
runtimeUrl: "http://assistant-1.local:7831",
|
|
302
|
-
localUrl: localGatewayUrl,
|
|
303
|
-
});
|
|
304
|
-
writeLockfile([entry]);
|
|
305
|
-
readPlatformTokenMock.mockReturnValue("session-token");
|
|
306
|
-
getPlatformUrlMock.mockReturnValue("https://platform.example.test");
|
|
307
|
-
readGatewayCredentialMock.mockImplementation(async (gatewayUrl, name) => {
|
|
308
|
-
expect(gatewayUrl).toBe(localGatewayUrl);
|
|
309
|
-
if (name === "cadet:platform_assistant_id") {
|
|
310
|
-
return { value: "platform-assistant-1", unreachable: false };
|
|
311
|
-
}
|
|
312
|
-
if (name === "cadet:platform_base_url") {
|
|
313
|
-
return { value: "https://platform.example.test", unreachable: false };
|
|
314
|
-
}
|
|
315
|
-
if (name === "cadet:platform_organization_id") {
|
|
316
|
-
return { value: "org-123", unreachable: false };
|
|
317
|
-
}
|
|
318
|
-
return { value: null, unreachable: false };
|
|
319
|
-
});
|
|
320
|
-
process.argv = ["bun", "cadet", "retire", "assistant-1", "--yes"];
|
|
321
|
-
|
|
322
|
-
await retire();
|
|
323
|
-
|
|
324
|
-
expect(readGatewayCredentialMock).toHaveBeenCalledWith(
|
|
325
|
-
localGatewayUrl,
|
|
326
|
-
"cadet:platform_assistant_id",
|
|
327
|
-
entry.bearerToken,
|
|
328
|
-
);
|
|
329
|
-
expect(readGatewayCredentialMock).toHaveBeenCalledWith(
|
|
330
|
-
localGatewayUrl,
|
|
331
|
-
"cadet:platform_base_url",
|
|
332
|
-
entry.bearerToken,
|
|
333
|
-
);
|
|
334
|
-
expect(readGatewayCredentialMock).toHaveBeenCalledWith(
|
|
335
|
-
localGatewayUrl,
|
|
336
|
-
"cadet:platform_organization_id",
|
|
337
|
-
entry.bearerToken,
|
|
338
|
-
);
|
|
339
|
-
expect(loopbackSafeFetchMock).toHaveBeenCalledWith(
|
|
340
|
-
"https://platform.example.test/v1/assistants/platform-assistant-1/retire/",
|
|
341
|
-
expect.any(Object),
|
|
342
|
-
);
|
|
343
|
-
expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
test("local retire uses saved platform registration when local credentials are unreachable", async () => {
|
|
291
|
+
test("local retire resolves the organization via the cloud when the entry omits it", async () => {
|
|
347
292
|
const entry = makeEntry("assistant-1", {
|
|
348
293
|
name: "Example Assistant",
|
|
349
|
-
|
|
350
|
-
platformBaseUrl: "https://platform.example.test",
|
|
351
|
-
platformOrganizationId: "org-123",
|
|
294
|
+
cloudAssistantId: "cloud-asst-1",
|
|
352
295
|
});
|
|
353
296
|
writeLockfile([entry]);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
readGatewayCredentialMock.mockResolvedValue({
|
|
357
|
-
value: null,
|
|
358
|
-
unreachable: true,
|
|
359
|
-
});
|
|
297
|
+
readCloudTokenMock.mockReturnValue("session-token");
|
|
298
|
+
getCloudUrlMock.mockReturnValue("https://platform.example.test");
|
|
360
299
|
process.argv = ["bun", "cadet", "retire", "assistant-1", "--yes"];
|
|
361
300
|
|
|
362
301
|
await retire();
|
|
363
302
|
|
|
364
|
-
expect(
|
|
365
|
-
|
|
366
|
-
"
|
|
367
|
-
entry.bearerToken,
|
|
303
|
+
expect(authHeadersMock).toHaveBeenCalledWith(
|
|
304
|
+
"session-token",
|
|
305
|
+
"https://platform.example.test",
|
|
368
306
|
);
|
|
307
|
+
expect(authHeadersForKnownOrganizationMock).not.toHaveBeenCalled();
|
|
369
308
|
expect(loopbackSafeFetchMock).toHaveBeenCalledWith(
|
|
370
|
-
"https://platform.example.test/v1/assistants/
|
|
309
|
+
"https://platform.example.test/v1/assistants/cloud-asst-1/retire/",
|
|
371
310
|
{
|
|
372
311
|
method: "DELETE",
|
|
373
312
|
headers: {
|
|
374
313
|
"Content-Type": "application/json",
|
|
375
314
|
"X-Session-Token": "session-token",
|
|
376
|
-
"Cadet-Organization-Id": "org-123",
|
|
377
315
|
},
|
|
378
316
|
},
|
|
379
317
|
);
|
|
380
|
-
expect(consoleWarnSpy.mock.calls.flat().join("\n")).toContain(
|
|
381
|
-
"using saved platform registration",
|
|
382
|
-
);
|
|
383
318
|
expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
|
|
384
319
|
expect(loadAllAssistants()).toEqual([]);
|
|
385
320
|
});
|
|
386
321
|
|
|
387
|
-
test("local retire
|
|
322
|
+
test("local retire skips the cloud unregister when no assistant id is bound", async () => {
|
|
388
323
|
const entry = makeEntry("assistant-1", { name: "Example Assistant" });
|
|
389
324
|
writeLockfile([entry]);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
value: null,
|
|
393
|
-
unreachable: true,
|
|
394
|
-
});
|
|
325
|
+
readCloudTokenMock.mockReturnValue("session-token");
|
|
326
|
+
getCloudUrlMock.mockReturnValue("https://platform.example.test");
|
|
395
327
|
process.argv = ["bun", "cadet", "retire", "assistant-1", "--yes"];
|
|
396
328
|
|
|
397
329
|
await retire();
|
|
398
330
|
|
|
399
331
|
expect(loopbackSafeFetchMock).not.toHaveBeenCalled();
|
|
400
|
-
expect(consoleWarnSpy.mock.calls.flat().join("\n")).toContain(
|
|
401
|
-
"no saved platform registration is available",
|
|
402
|
-
);
|
|
403
|
-
expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
|
|
404
|
-
expect(loadAllAssistants()).toEqual([]);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
test("local retire skips platform unregister when stored platform URL origin mismatches", async () => {
|
|
408
|
-
const entry = makeEntry("assistant-1", { name: "Example Assistant" });
|
|
409
|
-
writeLockfile([entry]);
|
|
410
|
-
readPlatformTokenMock.mockReturnValue("session-token");
|
|
411
|
-
getPlatformUrlMock.mockReturnValue("https://platform.example.com");
|
|
412
|
-
readGatewayCredentialMock.mockImplementation(async (_gatewayUrl, name) => {
|
|
413
|
-
if (name === "cadet:platform_assistant_id") {
|
|
414
|
-
return { value: "platform-assistant-1", unreachable: false };
|
|
415
|
-
}
|
|
416
|
-
if (name === "cadet:platform_base_url") {
|
|
417
|
-
return { value: "https://malicious.example.test", unreachable: false };
|
|
418
|
-
}
|
|
419
|
-
if (name === "cadet:platform_organization_id") {
|
|
420
|
-
return { value: "org-123", unreachable: false };
|
|
421
|
-
}
|
|
422
|
-
return { value: null, unreachable: false };
|
|
423
|
-
});
|
|
424
|
-
process.argv = ["bun", "cadet", "retire", "assistant-1", "--yes"];
|
|
425
|
-
|
|
426
|
-
await retire();
|
|
427
|
-
|
|
428
|
-
expect(loopbackSafeFetchMock).not.toHaveBeenCalled();
|
|
429
|
-
expect(consoleWarnSpy.mock.calls.flat().join("\n")).toContain(
|
|
430
|
-
"Stored platform URL does not match the configured platform URL",
|
|
431
|
-
);
|
|
432
|
-
expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
test("local retire skips platform unregister when stored organization ID is missing", async () => {
|
|
436
|
-
const entry = makeEntry("assistant-1", { name: "Example Assistant" });
|
|
437
|
-
writeLockfile([entry]);
|
|
438
|
-
readPlatformTokenMock.mockReturnValue("session-token");
|
|
439
|
-
getPlatformUrlMock.mockReturnValue("https://platform.example.test");
|
|
440
|
-
readGatewayCredentialMock.mockImplementation(async (_gatewayUrl, name) => {
|
|
441
|
-
if (name === "cadet:platform_assistant_id") {
|
|
442
|
-
return { value: "platform-assistant-1", unreachable: false };
|
|
443
|
-
}
|
|
444
|
-
if (name === "cadet:platform_base_url") {
|
|
445
|
-
return { value: "https://platform.example.test", unreachable: false };
|
|
446
|
-
}
|
|
447
|
-
return { value: null, unreachable: false };
|
|
448
|
-
});
|
|
449
|
-
process.argv = ["bun", "cadet", "retire", "assistant-1", "--yes"];
|
|
450
|
-
|
|
451
|
-
await retire();
|
|
452
|
-
|
|
453
|
-
expect(loopbackSafeFetchMock).not.toHaveBeenCalled();
|
|
454
|
-
expect(consoleWarnSpy.mock.calls.flat().join("\n")).toContain(
|
|
455
|
-
"Could not read platform organization ID",
|
|
456
|
-
);
|
|
457
332
|
expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
|
|
458
333
|
expect(loadAllAssistants()).toEqual([]);
|
|
459
334
|
});
|
|
460
335
|
|
|
461
336
|
test("local retire uses injected platform token from the app host", async () => {
|
|
462
|
-
const entry = makeEntry("assistant-1", {
|
|
337
|
+
const entry = makeEntry("assistant-1", {
|
|
338
|
+
name: "Example Assistant",
|
|
339
|
+
cloudAssistantId: "cloud-asst-1",
|
|
340
|
+
cloudOrganizationId: "org-123",
|
|
341
|
+
});
|
|
463
342
|
writeLockfile([entry]);
|
|
464
343
|
process.env.CADET_PLATFORM_TOKEN = "host-session-token";
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
readGatewayCredentialMock.mockImplementation(async (_gatewayUrl, name) => {
|
|
468
|
-
if (name === "cadet:platform_assistant_id") {
|
|
469
|
-
return { value: "platform-assistant-1", unreachable: false };
|
|
470
|
-
}
|
|
471
|
-
if (name === "cadet:platform_base_url") {
|
|
472
|
-
return { value: "https://platform.example.test", unreachable: false };
|
|
473
|
-
}
|
|
474
|
-
if (name === "cadet:platform_organization_id") {
|
|
475
|
-
return { value: "org-123", unreachable: false };
|
|
476
|
-
}
|
|
477
|
-
return { value: null, unreachable: false };
|
|
478
|
-
});
|
|
344
|
+
readCloudTokenMock.mockReturnValue(null);
|
|
345
|
+
getCloudUrlMock.mockReturnValue("https://platform.example.test");
|
|
479
346
|
process.argv = ["bun", "cadet", "retire", "assistant-1", "--yes"];
|
|
480
347
|
|
|
481
348
|
await retire();
|
|
482
349
|
|
|
483
350
|
expect(loopbackSafeFetchMock).toHaveBeenCalledWith(
|
|
484
|
-
"https://platform.example.test/v1/assistants/
|
|
351
|
+
"https://platform.example.test/v1/assistants/cloud-asst-1/retire/",
|
|
485
352
|
{
|
|
486
353
|
method: "DELETE",
|
|
487
354
|
headers: {
|
|
@@ -494,23 +361,15 @@ describe("cadet retire", () => {
|
|
|
494
361
|
expect(retireLocalMock).toHaveBeenCalledWith("assistant-1", entry);
|
|
495
362
|
});
|
|
496
363
|
|
|
497
|
-
test("local retire continues when
|
|
498
|
-
const entry = makeEntry("assistant-1", {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
readGatewayCredentialMock.mockImplementation(async (_gatewayUrl, name) => {
|
|
503
|
-
if (name === "cadet:platform_assistant_id") {
|
|
504
|
-
return { value: "platform-assistant-1", unreachable: false };
|
|
505
|
-
}
|
|
506
|
-
if (name === "cadet:platform_base_url") {
|
|
507
|
-
return { value: "https://platform.example.test", unreachable: false };
|
|
508
|
-
}
|
|
509
|
-
if (name === "cadet:platform_organization_id") {
|
|
510
|
-
return { value: "org-123", unreachable: false };
|
|
511
|
-
}
|
|
512
|
-
return { value: null, unreachable: false };
|
|
364
|
+
test("local retire continues when the cloud unregister fails", async () => {
|
|
365
|
+
const entry = makeEntry("assistant-1", {
|
|
366
|
+
name: "Example Assistant",
|
|
367
|
+
cloudAssistantId: "cloud-asst-1",
|
|
368
|
+
cloudOrganizationId: "org-123",
|
|
513
369
|
});
|
|
370
|
+
writeLockfile([entry]);
|
|
371
|
+
readCloudTokenMock.mockReturnValue("session-token");
|
|
372
|
+
getCloudUrlMock.mockReturnValue("https://platform.example.test");
|
|
514
373
|
loopbackSafeFetchMock.mockResolvedValue(
|
|
515
374
|
new Response("platform unavailable", { status: 503 }),
|
|
516
375
|
);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
clearRootProcess,
|
|
9
|
+
readRootProcesses,
|
|
10
|
+
stampRootProcess,
|
|
11
|
+
} from "../lib/assistant-config.js";
|
|
12
|
+
|
|
13
|
+
// Create a temp directory and set CADET_LOCKFILE_DIR so the real lockfile
|
|
14
|
+
// helpers resolve inside it (the sleep.test.ts fixture pattern).
|
|
15
|
+
let testDir: string;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
testDir = mkdtempSync(join(tmpdir(), "cadet-root-process-test-"));
|
|
19
|
+
mkdirSync(join(testDir, "config"), { recursive: true });
|
|
20
|
+
process.env.CADET_LOCKFILE_DIR = join(testDir, "config");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
delete process.env.CADET_LOCKFILE_DIR;
|
|
25
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("root process lockfile entries", () => {
|
|
29
|
+
test("a tunnel record stamps at the root, reads back, and clears", () => {
|
|
30
|
+
stampRootProcess("tunnel", {
|
|
31
|
+
pid: 4242,
|
|
32
|
+
provider: "ngrok",
|
|
33
|
+
startedAt: "2026-10-11T00:00:00.000Z",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(readRootProcesses().tunnel).toMatchObject({
|
|
37
|
+
pid: 4242,
|
|
38
|
+
provider: "ngrok",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
clearRootProcess("tunnel");
|
|
42
|
+
expect(readRootProcesses().tunnel).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("tunnel and app entries stamp and clear independently", () => {
|
|
46
|
+
stampRootProcess("app", { pid: 77, startedAt: "2026-10-11T00:00:00.000Z" });
|
|
47
|
+
stampRootProcess("tunnel", {
|
|
48
|
+
pid: 78,
|
|
49
|
+
provider: "cloudflare",
|
|
50
|
+
startedAt: "2026-10-11T00:00:00.000Z",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(readRootProcesses().app?.pid).toBe(77);
|
|
54
|
+
expect(readRootProcesses().tunnel?.provider).toBe("cloudflare");
|
|
55
|
+
|
|
56
|
+
clearRootProcess("app");
|
|
57
|
+
expect(readRootProcesses().app).toBeUndefined();
|
|
58
|
+
// The sibling entry survives.
|
|
59
|
+
expect(readRootProcesses().tunnel?.pid).toBe(78);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("a record can name the assistant it fronts, for `cadet ps` nesting", () => {
|
|
63
|
+
stampRootProcess("tunnel", {
|
|
64
|
+
pid: 4242,
|
|
65
|
+
provider: "ngrok",
|
|
66
|
+
startedAt: "2026-10-11T00:00:00.000Z",
|
|
67
|
+
assistantId: "assistant-1",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(readRootProcesses().tunnel).toMatchObject({
|
|
71
|
+
pid: 4242,
|
|
72
|
+
provider: "ngrok",
|
|
73
|
+
assistantId: "assistant-1",
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("the records live at the lockfile root under their kind keys", () => {
|
|
78
|
+
stampRootProcess("app", { pid: 1, startedAt: "t" });
|
|
79
|
+
|
|
80
|
+
const raw = z
|
|
81
|
+
.object({
|
|
82
|
+
app: z.object({ pid: z.number(), startedAt: z.string() }),
|
|
83
|
+
})
|
|
84
|
+
.parse(
|
|
85
|
+
JSON.parse(
|
|
86
|
+
readFileSync(
|
|
87
|
+
join(process.env.CADET_LOCKFILE_DIR!, "lockfile.json"),
|
|
88
|
+
"utf-8",
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
expect(raw.app).toEqual({ pid: 1, startedAt: "t" });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("clearing an absent entry is a no-op", () => {
|
|
96
|
+
clearRootProcess("tunnel");
|
|
97
|
+
expect(readRootProcesses()).toEqual({});
|
|
98
|
+
});
|
|
99
|
+
});
|