@mycadet/cli 0.12.7 → 0.12.9-dev.202610131452.9acab64
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 +6 -4
- 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 +1 -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 +15 -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 +149 -85
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +80 -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 +180 -211
- package/src/__tests__/tunnel.test.ts +490 -270
- package/src/__tests__/uninstall.test.ts +150 -9
- 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 -78
- 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 +27 -24
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +8 -8
- package/src/commands/pair.ts +140 -37
- package/src/commands/ps.ts +232 -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 +110 -129
- package/src/commands/terminal.ts +158 -18
- package/src/commands/tunnel.ts +156 -175
- package/src/commands/uninstall.ts +137 -54
- 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-gateway-cli.test.ts +42 -0
- 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} +105 -99
- package/src/lib/cloud-login.ts +23 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/color.ts +4 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +300 -111
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +29 -3
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/paths.ts +16 -8
- package/src/lib/environments/resolve.ts +10 -12
- 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 +32 -29
- package/src/lib/hatch-next-steps.ts +5 -9
- 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 +46 -34
- package/src/lib/nginx-ingress.ts +40 -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/step-runner.ts +6 -1
- 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
|
@@ -20,10 +20,11 @@ import * as httpClient from "../lib/http-client.js";
|
|
|
20
20
|
import * as ingressConfig from "../lib/ingress-config.js";
|
|
21
21
|
import * as local from "../lib/local.js";
|
|
22
22
|
import * as nginxIngress from "../lib/nginx-ingress.js";
|
|
23
|
-
import * as ngrok from "../lib/ngrok.js";
|
|
23
|
+
import * as ngrok from "../lib/tunnels/ngrok.js";
|
|
24
24
|
import * as processLib from "../lib/process.js";
|
|
25
25
|
import * as portAllocator from "../lib/port-allocator.js";
|
|
26
26
|
import type { AssistantEntry } from "../lib/assistant-config.js";
|
|
27
|
+
import { makeGuardianTokenData } from "./helpers/guardian-token.js";
|
|
27
28
|
|
|
28
29
|
const realAssistantConfig = { ...assistantConfig };
|
|
29
30
|
const realDocker = { ...docker };
|
|
@@ -61,11 +62,15 @@ const dockerResourceNamesMock = mock<typeof docker.dockerResourceNames>(
|
|
|
61
62
|
realDocker.dockerResourceNames,
|
|
62
63
|
);
|
|
63
64
|
const wakeContainersMock = mock<typeof docker.wakeContainers>(async () => {});
|
|
65
|
+
const recreateDockerContainersMock = mock<typeof docker.recreateDockerContainers>(
|
|
66
|
+
async () => {},
|
|
67
|
+
);
|
|
64
68
|
|
|
65
69
|
mock.module("../lib/docker.js", () => ({
|
|
66
70
|
...realDocker,
|
|
67
71
|
dockerResourceNames: dockerResourceNamesMock,
|
|
68
72
|
wakeContainers: wakeContainersMock,
|
|
73
|
+
recreateDockerContainers: recreateDockerContainersMock,
|
|
69
74
|
}));
|
|
70
75
|
|
|
71
76
|
const seedGuardianTokenFromSiblingEnvMock = mock<
|
|
@@ -73,18 +78,14 @@ const seedGuardianTokenFromSiblingEnvMock = mock<
|
|
|
73
78
|
>(() => false);
|
|
74
79
|
// Default: a token exists, so the re-provision recovery path is skipped. Tests
|
|
75
80
|
// that exercise recovery override loadGuardianToken to return null.
|
|
76
|
-
const loadGuardianTokenMock = mock<typeof guardianToken.loadGuardianToken>(
|
|
77
|
-
()
|
|
78
|
-
({ accessToken: "existing" }) as ReturnType<
|
|
79
|
-
typeof guardianToken.loadGuardianToken
|
|
80
|
-
>,
|
|
81
|
+
const loadGuardianTokenMock = mock<typeof guardianToken.loadGuardianToken>(() =>
|
|
82
|
+
makeGuardianTokenData({ accessToken: "existing" }),
|
|
81
83
|
);
|
|
82
84
|
const resetGuardianBootstrapMock = mock<
|
|
83
85
|
typeof guardianToken.resetGuardianBootstrap
|
|
84
86
|
>(async () => {});
|
|
85
87
|
const leaseGuardianTokenMock = mock<typeof guardianToken.leaseGuardianToken>(
|
|
86
|
-
async () =>
|
|
87
|
-
({}) as Awaited<ReturnType<typeof guardianToken.leaseGuardianToken>>,
|
|
88
|
+
async () => makeGuardianTokenData(),
|
|
88
89
|
);
|
|
89
90
|
|
|
90
91
|
mock.module("../lib/guardian-token.js", () => ({
|
|
@@ -156,7 +157,7 @@ const maybeStartNgrokTunnelMock = mock<typeof ngrok.maybeStartNgrokTunnel>(
|
|
|
156
157
|
async () => null,
|
|
157
158
|
);
|
|
158
159
|
|
|
159
|
-
mock.module("../lib/ngrok", () => ({
|
|
160
|
+
mock.module("../lib/tunnels/ngrok", () => ({
|
|
160
161
|
...realNgrok,
|
|
161
162
|
maybeStartNgrokTunnel: maybeStartNgrokTunnelMock,
|
|
162
163
|
}));
|
|
@@ -288,15 +289,13 @@ beforeEach(() => {
|
|
|
288
289
|
seedGuardianTokenFromSiblingEnvMock.mockReset();
|
|
289
290
|
seedGuardianTokenFromSiblingEnvMock.mockReturnValue(false);
|
|
290
291
|
loadGuardianTokenMock.mockReset();
|
|
291
|
-
loadGuardianTokenMock.mockReturnValue(
|
|
292
|
-
accessToken: "existing",
|
|
293
|
-
|
|
292
|
+
loadGuardianTokenMock.mockReturnValue(
|
|
293
|
+
makeGuardianTokenData({ accessToken: "existing" }),
|
|
294
|
+
);
|
|
294
295
|
resetGuardianBootstrapMock.mockReset();
|
|
295
296
|
resetGuardianBootstrapMock.mockResolvedValue(undefined);
|
|
296
297
|
leaseGuardianTokenMock.mockReset();
|
|
297
|
-
leaseGuardianTokenMock.mockResolvedValue(
|
|
298
|
-
{} as Awaited<ReturnType<typeof guardianToken.leaseGuardianToken>>,
|
|
299
|
-
);
|
|
298
|
+
leaseGuardianTokenMock.mockResolvedValue(makeGuardianTokenData());
|
|
300
299
|
maybeStartNgrokTunnelMock.mockReset();
|
|
301
300
|
maybeStartNgrokTunnelMock.mockResolvedValue(null);
|
|
302
301
|
loadRawConfigMock.mockReset();
|
|
@@ -330,7 +329,7 @@ afterAll(() => {
|
|
|
330
329
|
mock.module("../lib/process", () => realProcessLib);
|
|
331
330
|
mock.module("../lib/port-allocator.js", () => realPortAllocator);
|
|
332
331
|
mock.module("../lib/local", () => realLocal);
|
|
333
|
-
mock.module("../lib/ngrok", () => realNgrok);
|
|
332
|
+
mock.module("../lib/tunnels/ngrok", () => realNgrok);
|
|
334
333
|
mock.module("../lib/ingress-config.js", () => realIngressConfig);
|
|
335
334
|
mock.module("../lib/nginx-ingress.js", () => realNginxIngress);
|
|
336
335
|
mock.module("../lib/http-client.js", () => realHttpClient);
|
|
@@ -819,11 +818,11 @@ describe("cadet wake: daemon startup failure", () => {
|
|
|
819
818
|
// AND a foreign listener on the runtime HTTP port answers readiness in
|
|
820
819
|
// the dead daemon's place
|
|
821
820
|
probeDaemonReadinessWithRetryMock.mockResolvedValue("ready");
|
|
822
|
-
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
823
|
-
code?: number
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
821
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
822
|
+
(code?: number | string | undefined): never => {
|
|
823
|
+
throw new Error(`process.exit:${code}`);
|
|
824
|
+
},
|
|
825
|
+
);
|
|
827
826
|
|
|
828
827
|
// WHEN wake runs
|
|
829
828
|
const result = wake();
|
|
@@ -945,7 +944,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
945
944
|
runtimeUrl: "http://localhost:7930",
|
|
946
945
|
cloud: "docker",
|
|
947
946
|
ingressUrl: "https://assistant.example.com",
|
|
948
|
-
}
|
|
947
|
+
});
|
|
949
948
|
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
950
949
|
process.argv = ["bun", "cadet", "wake", "docker-1"];
|
|
951
950
|
|
|
@@ -957,9 +956,13 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
957
956
|
workspaceDir: defaultWorkspace,
|
|
958
957
|
gatewayPort: 7930,
|
|
959
958
|
});
|
|
960
|
-
// Container wakes
|
|
961
|
-
//
|
|
962
|
-
expect(maybeStartNgrokTunnelMock).
|
|
959
|
+
// Container wakes run the same auto-tunnel as local wakes (docker
|
|
960
|
+
// parity); the spawned agent records itself in the root lockfile.
|
|
961
|
+
expect(maybeStartNgrokTunnelMock).toHaveBeenCalledWith(
|
|
962
|
+
7840,
|
|
963
|
+
defaultWorkspace,
|
|
964
|
+
"docker-1",
|
|
965
|
+
);
|
|
963
966
|
expect(logSpy).toHaveBeenCalledWith("Wake complete.");
|
|
964
967
|
} finally {
|
|
965
968
|
if (originalWorkspaceDir === undefined) {
|
|
@@ -971,6 +974,48 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
971
974
|
}
|
|
972
975
|
});
|
|
973
976
|
|
|
977
|
+
test("a migrated docker assistant (containers never created) is recreated from the entry", async () => {
|
|
978
|
+
const defaultWorkspace = mkdtempSync(join(tmpdir(), "cadet-wake-default-"));
|
|
979
|
+
const originalWorkspaceDir = process.env.CADET_WORKSPACE_DIR;
|
|
980
|
+
process.env.CADET_WORKSPACE_DIR = defaultWorkspace;
|
|
981
|
+
const migratedEntry = {
|
|
982
|
+
assistantId: "docker-1",
|
|
983
|
+
runtimeUrl: "http://localhost:7930",
|
|
984
|
+
cloud: "docker" as const,
|
|
985
|
+
containerInfo: {
|
|
986
|
+
assistantImage: "mycadet/mycadet-assistant:v0.12.7",
|
|
987
|
+
gatewayImage: "mycadet/mycadet-gateway:v0.12.7",
|
|
988
|
+
cesImage: "mycadet/mycadet-credential-executor:v0.12.7",
|
|
989
|
+
},
|
|
990
|
+
};
|
|
991
|
+
resolveTargetAssistantMock.mockReturnValue(migratedEntry);
|
|
992
|
+
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
993
|
+
// The v0.0.10 run feedback: the containers were never created —
|
|
994
|
+
// `docker start` answers "No such container".
|
|
995
|
+
wakeContainersMock.mockImplementation(async () => {
|
|
996
|
+
throw new Error(
|
|
997
|
+
"Error response from daemon: No such container: docker-1-assistant",
|
|
998
|
+
);
|
|
999
|
+
});
|
|
1000
|
+
process.argv = ["bun", "cadet", "wake", "docker-1"];
|
|
1001
|
+
|
|
1002
|
+
try {
|
|
1003
|
+
await wake();
|
|
1004
|
+
|
|
1005
|
+
expect(recreateDockerContainersMock).toHaveBeenCalledWith(migratedEntry);
|
|
1006
|
+
expect(logSpy).toHaveBeenCalledWith("Docker containers started.");
|
|
1007
|
+
expect(logSpy).toHaveBeenCalledWith("Wake complete.");
|
|
1008
|
+
} finally {
|
|
1009
|
+
wakeContainersMock.mockImplementation(async () => {});
|
|
1010
|
+
if (originalWorkspaceDir === undefined) {
|
|
1011
|
+
delete process.env.CADET_WORKSPACE_DIR;
|
|
1012
|
+
} else {
|
|
1013
|
+
process.env.CADET_WORKSPACE_DIR = originalWorkspaceDir;
|
|
1014
|
+
}
|
|
1015
|
+
rmSync(defaultWorkspace, { recursive: true, force: true });
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
|
|
974
1019
|
test("a docker wake leaves an edge fronting another container alone", async () => {
|
|
975
1020
|
const defaultWorkspace = mkdtempSync(join(tmpdir(), "cadet-wake-default-"));
|
|
976
1021
|
const originalWorkspaceDir = process.env.CADET_WORKSPACE_DIR;
|
|
@@ -980,7 +1025,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
980
1025
|
runtimeUrl: "http://localhost:7930",
|
|
981
1026
|
cloud: "docker",
|
|
982
1027
|
ingressUrl: "https://assistant.example.com",
|
|
983
|
-
}
|
|
1028
|
+
});
|
|
984
1029
|
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
985
1030
|
isIngressRunningMock.mockReturnValue(true);
|
|
986
1031
|
readIngressStateMock.mockReturnValue({
|
|
@@ -1014,7 +1059,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1014
1059
|
runtimeUrl: "http://localhost:7930",
|
|
1015
1060
|
cloud: "docker",
|
|
1016
1061
|
ingressUrl: "https://assistant.example.com",
|
|
1017
|
-
}
|
|
1062
|
+
});
|
|
1018
1063
|
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
1019
1064
|
isIngressRunningMock.mockReturnValue(true);
|
|
1020
1065
|
readIngressStateMock.mockReturnValue({
|
|
@@ -1052,7 +1097,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1052
1097
|
}
|
|
1053
1098
|
});
|
|
1054
1099
|
|
|
1055
|
-
test("a failed docker edge restore
|
|
1100
|
+
test("a failed docker edge restore falls back to the gateway tunnel", async () => {
|
|
1056
1101
|
const defaultWorkspace = mkdtempSync(join(tmpdir(), "cadet-wake-default-"));
|
|
1057
1102
|
const originalWorkspaceDir = process.env.CADET_WORKSPACE_DIR;
|
|
1058
1103
|
process.env.CADET_WORKSPACE_DIR = defaultWorkspace;
|
|
@@ -1061,7 +1106,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1061
1106
|
runtimeUrl: "http://localhost:7930",
|
|
1062
1107
|
cloud: "docker",
|
|
1063
1108
|
ingressUrl: "https://assistant.example.com",
|
|
1064
|
-
}
|
|
1109
|
+
});
|
|
1065
1110
|
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
1066
1111
|
ensureTunnelEdgeMock.mockRejectedValue(
|
|
1067
1112
|
new Error("nginx is not installed."),
|
|
@@ -1071,11 +1116,14 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1071
1116
|
try {
|
|
1072
1117
|
await wake();
|
|
1073
1118
|
|
|
1119
|
+
// Docker wakes get the same tunnel logic as local ones: when the edge
|
|
1120
|
+
// cannot be rebuilt, the auto-tunnel still fronts the gateway port so
|
|
1121
|
+
// webhook delivery survives.
|
|
1074
1122
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
1075
|
-
expect.stringContaining("
|
|
1123
|
+
expect.stringContaining("Webhooks still work"),
|
|
1076
1124
|
);
|
|
1077
1125
|
expect(warnSpy).not.toHaveBeenCalledWith(
|
|
1078
|
-
expect.stringContaining("
|
|
1126
|
+
expect.stringContaining("webhook delivery are unavailable"),
|
|
1079
1127
|
);
|
|
1080
1128
|
} finally {
|
|
1081
1129
|
if (originalWorkspaceDir === undefined) {
|
|
@@ -1098,14 +1146,14 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1098
1146
|
runtimeUrl: "http://localhost:7930",
|
|
1099
1147
|
cloud: "docker",
|
|
1100
1148
|
ingressUrl: "https://other.example.com",
|
|
1101
|
-
}
|
|
1149
|
+
});
|
|
1102
1150
|
loadAllAssistantsMock.mockReturnValue([
|
|
1103
1151
|
{
|
|
1104
1152
|
assistantId: "docker-2",
|
|
1105
1153
|
runtimeUrl: "http://localhost:8030",
|
|
1106
1154
|
cloud: "docker",
|
|
1107
1155
|
ingressUrl: "https://assistant.example.com",
|
|
1108
|
-
}
|
|
1156
|
+
},
|
|
1109
1157
|
]);
|
|
1110
1158
|
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
1111
1159
|
isIngressRunningMock.mockReturnValue(false);
|
|
@@ -1135,7 +1183,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1135
1183
|
runtimeUrl: "http://localhost:7930",
|
|
1136
1184
|
cloud: "docker",
|
|
1137
1185
|
ingressUrl: "https://assistant.example.com",
|
|
1138
|
-
}
|
|
1186
|
+
});
|
|
1139
1187
|
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
1140
1188
|
process.argv = ["bun", "cadet", "wake", "docker-1"];
|
|
1141
1189
|
|
|
@@ -1163,7 +1211,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1163
1211
|
runtimeUrl: "http://localhost:7930",
|
|
1164
1212
|
cloud: "docker",
|
|
1165
1213
|
ingressUrl: "https://assistant.example.com",
|
|
1166
|
-
}
|
|
1214
|
+
});
|
|
1167
1215
|
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
1168
1216
|
waitForDaemonReadyMock.mockImplementation(async () => false);
|
|
1169
1217
|
process.argv = ["bun", "cadet", "wake", "docker-1"];
|
|
@@ -1194,7 +1242,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1194
1242
|
assistantId: "docker-1",
|
|
1195
1243
|
runtimeUrl: "http://localhost:7930",
|
|
1196
1244
|
cloud: "docker",
|
|
1197
|
-
}
|
|
1245
|
+
});
|
|
1198
1246
|
process.argv = ["bun", "cadet", "wake", "docker-1"];
|
|
1199
1247
|
|
|
1200
1248
|
try {
|
|
@@ -1222,13 +1270,13 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1222
1270
|
assistantId: "docker-1",
|
|
1223
1271
|
runtimeUrl: "http://localhost:7930",
|
|
1224
1272
|
cloud: "docker",
|
|
1225
|
-
}
|
|
1273
|
+
});
|
|
1226
1274
|
loadAllAssistantsMock.mockReturnValue([
|
|
1227
1275
|
{
|
|
1228
1276
|
assistantId: "docker-1",
|
|
1229
1277
|
runtimeUrl: "http://localhost:7930",
|
|
1230
1278
|
cloud: "docker",
|
|
1231
|
-
}
|
|
1279
|
+
},
|
|
1232
1280
|
]);
|
|
1233
1281
|
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
1234
1282
|
isIngressRunningMock.mockReturnValue(false);
|
|
@@ -1260,14 +1308,14 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1260
1308
|
assistantId: "docker-1",
|
|
1261
1309
|
runtimeUrl: "http://localhost:7930",
|
|
1262
1310
|
cloud: "docker",
|
|
1263
|
-
}
|
|
1311
|
+
});
|
|
1264
1312
|
loadAllAssistantsAcrossEnvsMock.mockReturnValue([
|
|
1265
1313
|
{
|
|
1266
1314
|
assistantId: "docker-2",
|
|
1267
1315
|
runtimeUrl: "http://localhost:8030",
|
|
1268
1316
|
cloud: "docker",
|
|
1269
1317
|
ingressUrl: "https://assistant.example.com",
|
|
1270
|
-
}
|
|
1318
|
+
},
|
|
1271
1319
|
]);
|
|
1272
1320
|
loadRawConfigMock.mockReturnValue(enabledConfig);
|
|
1273
1321
|
isIngressRunningMock.mockReturnValue(false);
|
|
@@ -1296,7 +1344,7 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1296
1344
|
runtimeUrl: "http://localhost:7930",
|
|
1297
1345
|
cloud: "docker",
|
|
1298
1346
|
ingressUrl: "https://assistant.example.com",
|
|
1299
|
-
}
|
|
1347
|
+
});
|
|
1300
1348
|
process.argv = ["bun", "cadet", "wake", "docker-1"];
|
|
1301
1349
|
|
|
1302
1350
|
try {
|
|
@@ -1455,14 +1503,14 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1455
1503
|
loadRawConfigMock.mockReturnValue(webhookConfig);
|
|
1456
1504
|
ensureTunnelEdgeMock.mockRejectedValue(
|
|
1457
1505
|
new Error(
|
|
1458
|
-
"nginx is not installed, so the tunnel edge cannot start.
|
|
1506
|
+
"nginx is not installed, so the tunnel edge cannot start. See https://nginx.org/en/docs/install.html for how to install, or point NGINX_BIN at an existing binary.",
|
|
1459
1507
|
),
|
|
1460
1508
|
);
|
|
1461
1509
|
|
|
1462
1510
|
await wake();
|
|
1463
1511
|
|
|
1464
1512
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
1465
|
-
expect.stringContaining("
|
|
1513
|
+
expect.stringContaining("https://nginx.org/en/docs/install.html"),
|
|
1466
1514
|
);
|
|
1467
1515
|
// No tunnel is recorded here, so no provider can be named and the advice
|
|
1468
1516
|
// points at the help that lists them.
|
|
@@ -1541,4 +1589,173 @@ describe("cadet wake — tunnel edge restore", () => {
|
|
|
1541
1589
|
"local-assistant",
|
|
1542
1590
|
);
|
|
1543
1591
|
});
|
|
1592
|
+
|
|
1593
|
+
describe("--all", () => {
|
|
1594
|
+
function makeAllEntry(
|
|
1595
|
+
assistantId: string,
|
|
1596
|
+
instanceDir: string,
|
|
1597
|
+
): AssistantEntry {
|
|
1598
|
+
return {
|
|
1599
|
+
assistantId,
|
|
1600
|
+
runtimeUrl: "http://127.0.0.1:7830",
|
|
1601
|
+
cloud: "local",
|
|
1602
|
+
resources: {
|
|
1603
|
+
instanceDir,
|
|
1604
|
+
daemonPort: 7821,
|
|
1605
|
+
gatewayPort: 7830,
|
|
1606
|
+
qdrantPort: 6333,
|
|
1607
|
+
cesPort: 7822,
|
|
1608
|
+
signingKey: "ab".repeat(32),
|
|
1609
|
+
},
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function makeAllEntries(): AssistantEntry[] {
|
|
1614
|
+
mkdirSync(join(tempDir, "instance-1"), { recursive: true });
|
|
1615
|
+
mkdirSync(join(tempDir, "instance-2"), { recursive: true });
|
|
1616
|
+
return [
|
|
1617
|
+
makeAllEntry("wake-all-1", join(tempDir, "instance-1")),
|
|
1618
|
+
makeAllEntry("wake-all-2", join(tempDir, "instance-2")),
|
|
1619
|
+
];
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// Cold-start both assistants so each wake's starts are observable.
|
|
1623
|
+
function mockColdStart(): void {
|
|
1624
|
+
resolveProcessStateMock.mockImplementation(async () => ({
|
|
1625
|
+
status: "needs_start",
|
|
1626
|
+
pid: null,
|
|
1627
|
+
}));
|
|
1628
|
+
isProcessAliveMock.mockReturnValue({ alive: true, pid: 123 });
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
test("starts every assistant in the lockfile", async () => {
|
|
1632
|
+
mockColdStart();
|
|
1633
|
+
loadAllAssistantsMock.mockReturnValue(makeAllEntries());
|
|
1634
|
+
process.argv = ["bun", "cadet", "wake", "--all"];
|
|
1635
|
+
|
|
1636
|
+
await wake();
|
|
1637
|
+
|
|
1638
|
+
expect(startCesMock).toHaveBeenCalledTimes(2);
|
|
1639
|
+
expect(startLocalDaemonMock).toHaveBeenCalledTimes(2);
|
|
1640
|
+
expect(startGatewayMock).toHaveBeenCalledTimes(2);
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
test("skips assistants whose lifecycle is managed elsewhere", async () => {
|
|
1644
|
+
mockColdStart();
|
|
1645
|
+
const entries = makeAllEntries();
|
|
1646
|
+
entries[1] = {
|
|
1647
|
+
assistantId: "paired-one",
|
|
1648
|
+
runtimeUrl: "http://127.0.0.1:1",
|
|
1649
|
+
cloud: "paired",
|
|
1650
|
+
};
|
|
1651
|
+
loadAllAssistantsMock.mockReturnValue(entries);
|
|
1652
|
+
process.argv = ["bun", "cadet", "wake", "--all"];
|
|
1653
|
+
|
|
1654
|
+
await wake();
|
|
1655
|
+
|
|
1656
|
+
expect(startCesMock).toHaveBeenCalledTimes(1);
|
|
1657
|
+
expect(startLocalDaemonMock).toHaveBeenCalledTimes(1);
|
|
1658
|
+
expect(startGatewayMock).toHaveBeenCalledTimes(1);
|
|
1659
|
+
expect(logSpy.mock.calls.flat().join("\n")).toContain(
|
|
1660
|
+
"Skipping 'paired-one' (paired) — its lifecycle is managed elsewhere.",
|
|
1661
|
+
);
|
|
1662
|
+
});
|
|
1663
|
+
|
|
1664
|
+
test("continues past a failing assistant and exits 1", async () => {
|
|
1665
|
+
mockColdStart();
|
|
1666
|
+
loadAllAssistantsMock.mockReturnValue([
|
|
1667
|
+
// Missing resources is a deliberate refusal for this entry.
|
|
1668
|
+
{
|
|
1669
|
+
assistantId: "wake-all-1",
|
|
1670
|
+
runtimeUrl: "http://127.0.0.1:7830",
|
|
1671
|
+
cloud: "local",
|
|
1672
|
+
},
|
|
1673
|
+
makeAllEntry("wake-all-2", join(tempDir, "instance-2")),
|
|
1674
|
+
]);
|
|
1675
|
+
mkdirSync(join(tempDir, "instance-2"), { recursive: true });
|
|
1676
|
+
process.argv = ["bun", "cadet", "wake", "--all"];
|
|
1677
|
+
|
|
1678
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1679
|
+
(code?: number | string | undefined): never => {
|
|
1680
|
+
throw new Error(`process.exit:${code}`);
|
|
1681
|
+
},
|
|
1682
|
+
);
|
|
1683
|
+
|
|
1684
|
+
try {
|
|
1685
|
+
await expect(wake()).rejects.toThrow("process.exit:1");
|
|
1686
|
+
expect(errorSpy.mock.calls.flat().join("\n")).toContain(
|
|
1687
|
+
"failed to wake 'wake-all-1'",
|
|
1688
|
+
);
|
|
1689
|
+
} finally {
|
|
1690
|
+
exitSpy.mockRestore();
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// The healthy assistant was still started.
|
|
1694
|
+
expect(startCesMock).toHaveBeenCalledTimes(1);
|
|
1695
|
+
expect(startLocalDaemonMock).toHaveBeenCalledTimes(1);
|
|
1696
|
+
expect(startGatewayMock).toHaveBeenCalledTimes(1);
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1699
|
+
test("exits 1 when there is nothing to start", async () => {
|
|
1700
|
+
loadAllAssistantsMock.mockReturnValue([]);
|
|
1701
|
+
process.argv = ["bun", "cadet", "wake", "--all"];
|
|
1702
|
+
|
|
1703
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1704
|
+
(code?: number | string | undefined): never => {
|
|
1705
|
+
throw new Error(`process.exit:${code}`);
|
|
1706
|
+
},
|
|
1707
|
+
);
|
|
1708
|
+
|
|
1709
|
+
try {
|
|
1710
|
+
await expect(wake()).rejects.toThrow("process.exit:1");
|
|
1711
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
1712
|
+
expect.stringContaining("no assistants found"),
|
|
1713
|
+
);
|
|
1714
|
+
} finally {
|
|
1715
|
+
exitSpy.mockRestore();
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
test("refuses --all combined with a name", async () => {
|
|
1720
|
+
process.argv = ["bun", "cadet", "wake", "local-assistant", "--all"];
|
|
1721
|
+
|
|
1722
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1723
|
+
(code?: number | string | undefined): never => {
|
|
1724
|
+
throw new Error(`process.exit:${code}`);
|
|
1725
|
+
},
|
|
1726
|
+
);
|
|
1727
|
+
|
|
1728
|
+
try {
|
|
1729
|
+
await expect(wake()).rejects.toThrow("process.exit:1");
|
|
1730
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
1731
|
+
expect.stringContaining("--all cannot be combined"),
|
|
1732
|
+
);
|
|
1733
|
+
} finally {
|
|
1734
|
+
exitSpy.mockRestore();
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1738
|
+
test("refuses --all with --foreground and --repair-guardian", async () => {
|
|
1739
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(
|
|
1740
|
+
(code?: number | string | undefined): never => {
|
|
1741
|
+
throw new Error(`process.exit:${code}`);
|
|
1742
|
+
},
|
|
1743
|
+
);
|
|
1744
|
+
|
|
1745
|
+
try {
|
|
1746
|
+
for (const flag of ["--foreground", "--repair-guardian"]) {
|
|
1747
|
+
process.argv = ["bun", "cadet", "wake", "--all", flag];
|
|
1748
|
+
await expect(wake()).rejects.toThrow("process.exit:1");
|
|
1749
|
+
}
|
|
1750
|
+
} finally {
|
|
1751
|
+
exitSpy.mockRestore();
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
1755
|
+
expect.stringContaining(
|
|
1756
|
+
"--all cannot be combined with --foreground or --repair-guardian",
|
|
1757
|
+
),
|
|
1758
|
+
);
|
|
1759
|
+
});
|
|
1760
|
+
});
|
|
1544
1761
|
});
|
|
@@ -88,7 +88,7 @@ describe("buildAuthorizeUrl", () => {
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
describe("selectWorkosClientId", () => {
|
|
91
|
-
// During coexistence the
|
|
91
|
+
// During coexistence the cloud lists two providers that share the
|
|
92
92
|
// "workos-oidc" id; only the OAuth2 one (no discovery URL) is usable.
|
|
93
93
|
const legacy = {
|
|
94
94
|
id: "workos-oidc",
|
|
@@ -110,7 +110,7 @@ describe("selectWorkosClientId", () => {
|
|
|
110
110
|
expect(selectWorkosClientId([modern, legacy])).toBe("client_um");
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
test("returns null when the
|
|
113
|
+
test("returns null when the cloud lacks token auth", () => {
|
|
114
114
|
const preTokenAuth = { ...modern, flows: ["provider_redirect"] };
|
|
115
115
|
expect(selectWorkosClientId([legacy, preTokenAuth])).toBeNull();
|
|
116
116
|
expect(selectWorkosClientId([])).toBeNull();
|
package/src/commands/backup.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCloudHome } from "../lib/cloud-values.js";
|
|
2
2
|
import { mkdirSync, writeFileSync } from "fs";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
|
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
localRuntimePollJobStatus,
|
|
15
15
|
} from "../lib/local-runtime-client.js";
|
|
16
16
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from "../lib/
|
|
17
|
+
cloudRequestSignedUrl,
|
|
18
|
+
readCloudToken,
|
|
19
|
+
} from "../lib/cloud-client.js";
|
|
20
20
|
import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
|
|
21
21
|
|
|
22
22
|
// Default timeout for the runtime-direct export request. Overridable via
|
|
@@ -42,10 +42,10 @@ export const backup = Object.assign(
|
|
|
42
42
|
console.log("Options:");
|
|
43
43
|
console.log(" --output <path> Path to save the .vbundle file");
|
|
44
44
|
console.log(
|
|
45
|
-
"
|
|
45
|
+
" defaults to ~/.local/share/cadet/backups/<name>-<timestamp>.vbundle",
|
|
46
46
|
);
|
|
47
47
|
console.log(
|
|
48
|
-
` --export-timeout <secs> Export request timeout in seconds
|
|
48
|
+
` --export-timeout <secs> Export request timeout in seconds, defaults to ${DEFAULT_EXPORT_TIMEOUT_MS / 1000}`,
|
|
49
49
|
);
|
|
50
50
|
console.log("");
|
|
51
51
|
console.log("Examples:");
|
|
@@ -102,7 +102,7 @@ export const backup = Object.assign(
|
|
|
102
102
|
process.exit(1);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
if (
|
|
105
|
+
if (isCloudHome(cloud)) {
|
|
106
106
|
await backupPlatform(entry, name, outputArg);
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
@@ -233,9 +233,9 @@ export const backup = Object.assign(
|
|
|
233
233
|
// ---------------------------------------------------------------------------
|
|
234
234
|
// Platform-managed (cloud="cadet") backup over GCS.
|
|
235
235
|
//
|
|
236
|
-
// The runtime exports the bundle straight to a
|
|
236
|
+
// The runtime exports the bundle straight to a cloud-issued signed GCS
|
|
237
237
|
// URL; the CLI then downloads from GCS to local disk. Bytes never flow
|
|
238
|
-
// through Django. Same architectural shape as the
|
|
238
|
+
// through Django. Same architectural shape as the cloud-source half of
|
|
239
239
|
// `cadet teleport`. Output format and success log lines match mode 1
|
|
240
240
|
// (runtime-direct local backup) so users see one consistent UX.
|
|
241
241
|
//
|
|
@@ -251,24 +251,24 @@ async function backupPlatform(
|
|
|
251
251
|
name: string,
|
|
252
252
|
outputArg?: string,
|
|
253
253
|
): Promise<void> {
|
|
254
|
-
const
|
|
255
|
-
if (!
|
|
254
|
+
const cloudToken = readCloudToken();
|
|
255
|
+
if (!cloudToken) {
|
|
256
256
|
console.error(
|
|
257
|
-
"Not logged in. Run 'cadet login' first
|
|
257
|
+
"Not logged in. Run 'cadet login' first.",
|
|
258
258
|
);
|
|
259
259
|
process.exit(1);
|
|
260
260
|
}
|
|
261
261
|
// Pin upload, download, and runtime requests to the same platform instance
|
|
262
|
-
// the assistant lives on. Using `
|
|
262
|
+
// the assistant lives on. Using `getCloudUrl()` instead would target
|
|
263
263
|
// whatever the lockfile / env-var resolves to, which may differ from
|
|
264
264
|
// `entry.runtimeUrl` for staging/dev assistants and end up signing URLs
|
|
265
|
-
// for the wrong GCS bucket. Mirrors the teleport
|
|
265
|
+
// for the wrong GCS bucket. Mirrors the teleport bundleCloudUrl
|
|
266
266
|
// threading at `cli/src/commands/teleport.ts:1311-1312`.
|
|
267
|
-
const
|
|
267
|
+
const cloudUrl = entry.runtimeUrl;
|
|
268
268
|
// Track the working platform token across kickoff/poll/download so a
|
|
269
269
|
// 401-driven refresh during polling stays consistent through the final
|
|
270
270
|
// signed-download request.
|
|
271
|
-
let exportPlatformToken =
|
|
271
|
+
let exportPlatformToken = cloudToken;
|
|
272
272
|
|
|
273
273
|
// Step 0 — Ask the source runtime which version it's running. The bundle
|
|
274
274
|
// is produced by the daemon (not the CLI), and the CLI version can drift
|
|
@@ -288,17 +288,17 @@ async function backupPlatform(
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
// Step 1 — Request a signed upload URL.
|
|
291
|
-
const { url: uploadUrl, bundleKey } = await
|
|
291
|
+
const { url: uploadUrl, bundleKey } = await cloudRequestSignedUrl(
|
|
292
292
|
{
|
|
293
293
|
operation: "upload",
|
|
294
294
|
minRuntimeVersion: runtimeIdentity.version,
|
|
295
295
|
maxRuntimeVersion: null,
|
|
296
296
|
},
|
|
297
297
|
exportPlatformToken,
|
|
298
|
-
|
|
298
|
+
cloudUrl,
|
|
299
299
|
);
|
|
300
300
|
|
|
301
|
-
// Step 2 — Kick off runtime export-to-GCS through the
|
|
301
|
+
// Step 2 — Kick off runtime export-to-GCS through the cloud's
|
|
302
302
|
// wildcard runtime proxy. `localRuntimeExportToGcs` builds the
|
|
303
303
|
// `/v1/assistants/<id>/migrations/export-to-gcs` URL for cloud="cadet"
|
|
304
304
|
// and uses platform-token auth (no guardian-token bootstrap).
|
|
@@ -327,7 +327,7 @@ async function backupPlatform(
|
|
|
327
327
|
label: "platform export",
|
|
328
328
|
poll: () => localRuntimePollJobStatus(entry, exportPlatformToken, jobId),
|
|
329
329
|
refreshOn401: async () => {
|
|
330
|
-
const refreshed =
|
|
330
|
+
const refreshed = readCloudToken();
|
|
331
331
|
if (!refreshed) {
|
|
332
332
|
throw new Error(
|
|
333
333
|
"Platform auth expired during export and no credential was found on disk. Run 'cadet login' and retry.",
|
|
@@ -344,7 +344,7 @@ async function backupPlatform(
|
|
|
344
344
|
|
|
345
345
|
// Step 4 — Request a signed download URL for the same bundle and fetch
|
|
346
346
|
// it from GCS directly. No auth on signed URLs.
|
|
347
|
-
// Use `exportPlatformToken` (not the original `
|
|
347
|
+
// Use `exportPlatformToken` (not the original `cloudToken`) so a
|
|
348
348
|
// poll-loop 401 refresh doesn't get clobbered here — otherwise a long
|
|
349
349
|
// export that recovered mid-poll via re-auth would still 401 on the
|
|
350
350
|
// download-URL request and abort an otherwise successful run.
|
|
@@ -354,15 +354,15 @@ async function backupPlatform(
|
|
|
354
354
|
// runtime to gate against, and the user can later restore the file
|
|
355
355
|
// into any compatible runtime. Sending the CLI's version would
|
|
356
356
|
// incorrectly block older CLIs from backing up newer assistants.
|
|
357
|
-
// The
|
|
357
|
+
// The cloud treats `target_runtime_version` as optional and skips
|
|
358
358
|
// the version check when it's omitted.
|
|
359
|
-
const { url: bundleUrl } = await
|
|
359
|
+
const { url: bundleUrl } = await cloudRequestSignedUrl(
|
|
360
360
|
{
|
|
361
361
|
operation: "download",
|
|
362
362
|
bundleKey,
|
|
363
363
|
},
|
|
364
364
|
exportPlatformToken,
|
|
365
|
-
|
|
365
|
+
cloudUrl,
|
|
366
366
|
);
|
|
367
367
|
|
|
368
368
|
let downloadResponse: Response;
|