@vellumai/cli 0.7.1 → 0.7.3
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/AGENTS.md +3 -11
- package/bun.lock +0 -15
- package/package.json +1 -6
- package/src/__tests__/backup.test.ts +121 -5
- package/src/__tests__/teleport.test.ts +515 -10
- package/src/commands/backup.ts +35 -2
- package/src/commands/client.ts +90 -7
- package/src/commands/exec.ts +13 -4
- package/src/commands/hatch.ts +1 -1
- package/src/commands/login.ts +11 -0
- package/src/commands/restore.ts +7 -1
- package/src/commands/rollback.ts +1 -1
- package/src/commands/setup.ts +38 -73
- package/src/commands/teleport.ts +122 -12
- package/src/commands/upgrade.ts +8 -2
- package/src/commands/wake.ts +5 -16
- package/src/components/DefaultMainScreen.tsx +42 -130
- package/src/index.ts +1 -7
- package/src/lib/__tests__/docker.test.ts +53 -35
- package/src/lib/__tests__/local-runtime-client.test.ts +186 -0
- package/src/lib/__tests__/platform-client-signed-url.test.ts +235 -0
- package/src/lib/__tests__/runtime-url.test.ts +39 -1
- package/src/lib/assistant-client.ts +13 -5
- package/src/lib/assistant-config.ts +0 -25
- package/src/lib/backup-ops.ts +43 -17
- package/src/lib/client-identity.ts +9 -5
- package/src/lib/docker.ts +6 -267
- package/src/lib/environments/paths.ts +20 -0
- package/src/lib/guardian-token.ts +56 -6
- package/src/lib/hatch-local.ts +3 -26
- package/src/lib/local-runtime-client.ts +82 -1
- package/src/lib/local.ts +9 -7
- package/src/lib/ngrok.ts +36 -26
- package/src/lib/platform-client.ts +100 -1
- package/src/lib/retire-local.ts +2 -2
- package/src/lib/runtime-url.ts +22 -0
- package/src/lib/statefulset.ts +375 -0
- package/src/lib/upgrade-lifecycle.ts +97 -1
- package/src/commands/pair.ts +0 -212
package/AGENTS.md
CHANGED
|
@@ -57,20 +57,12 @@ For example, the signing key used for JWT auth between the daemon and gateway is
|
|
|
57
57
|
|
|
58
58
|
The CLI creates and manages Docker volumes for containerized instances. See the root `AGENTS.md` § Docker Volume Architecture for the full volume layout.
|
|
59
59
|
|
|
60
|
-
**Volume creation** (`hatch`): Creates
|
|
60
|
+
**Volume creation** (`hatch`): Creates six volumes per instance — workspace, gateway-security, ces-security, socket, assistant-ipc, and gateway-ipc. The legacy data volume is no longer created.
|
|
61
61
|
|
|
62
62
|
**Volume migration** (`wake`/`hatch`): On startup, existing instances that still have a legacy data volume are migrated. `migrateGatewaySecurityFiles()` and `migrateCesSecurityFiles()` in `lib/docker.ts` copy security files from the data volume to their respective security volumes. Migrations are idempotent and non-fatal.
|
|
63
63
|
|
|
64
64
|
**Volume cleanup** (`retire`): All volumes (including the legacy data volume if it exists) are removed when an instance is retired.
|
|
65
65
|
|
|
66
|
-
**Volume mount rules**: Each service container receives only the volumes it needs. The assistant never mounts `gateway-security` or `ces-security`. The gateway never mounts `ces-security`. The CES mounts the workspace volume as read-only.
|
|
66
|
+
**Volume mount rules**: Each service container receives only the volumes it needs. The assistant never mounts `gateway-security` or `ces-security`. The gateway never mounts `ces-security`. The CES mounts the workspace volume as read-only.
|
|
67
67
|
|
|
68
|
-
**
|
|
69
|
-
|
|
70
|
-
- Creating a dedicated `<name>-dockerd-data` volume mounted at `/var/lib/docker` so pulled images and container state persist across assistant restarts.
|
|
71
|
-
- Running the assistant container with `CAP_SYS_ADMIN` + `CAP_NET_ADMIN` plus `--security-opt seccomp=unconfined` + `--security-opt apparmor=unconfined` so the inner dockerd can configure cgroups, overlay mounts, and container networking without the default seccomp profile blocking clone/unshare/pivot_root syscalls or the default AppArmor profile denying its mount operations. `--privileged` is deliberately avoided — dropping it shrinks the escape surface by withholding the rest of the host capability set and access to host device nodes.
|
|
72
|
-
- No longer bind-mounting the host's `/var/run/docker.sock`; Meet-bot spawning happens entirely inside the assistant container.
|
|
73
|
-
|
|
74
|
-
Both are wired in `serviceDockerRunArgs()` in `lib/docker.ts`.
|
|
75
|
-
|
|
76
|
-
This capability + security-opt set is acceptable for single-user local deployments. Managed/multi-tenant mode needs a different spawn model (e.g. a Kubernetes job runner) and is out of scope for this CLI.
|
|
68
|
+
**Container security posture**: The assistant container runs as a non-root user (UID 1001) with no elevated capabilities. `--privileged`, `--cap-add`, and `--security-opt` overrides are NOT used. The host Docker socket is NOT bind-mounted. Do NOT re-add elevated capabilities without a concrete runtime requirement — the Docker Engine packages and inner `dockerd` supervisor were reverted (PR #26028) and the capabilities they required are no longer needed.
|
package/bun.lock
CHANGED
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"chalk": "5.6.2",
|
|
9
9
|
"ink": "6.8.0",
|
|
10
|
-
"jsqr": "1.4.0",
|
|
11
10
|
"nanoid": "5.1.7",
|
|
12
|
-
"pngjs": "7.0.0",
|
|
13
|
-
"qrcode-terminal": "0.12.0",
|
|
14
11
|
"react": "19.2.4",
|
|
15
12
|
"react-devtools-core": "6.1.5",
|
|
16
13
|
},
|
|
17
14
|
"devDependencies": {
|
|
18
15
|
"@types/bun": "1.3.11",
|
|
19
|
-
"@types/pngjs": "6.0.5",
|
|
20
|
-
"@types/qrcode-terminal": "0.12.2",
|
|
21
16
|
"@types/react": "19.2.14",
|
|
22
17
|
"eslint": "10.1.0",
|
|
23
18
|
"knip": "5.88.1",
|
|
@@ -118,10 +113,6 @@
|
|
|
118
113
|
|
|
119
114
|
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
|
120
115
|
|
|
121
|
-
"@types/pngjs": ["@types/pngjs@6.0.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ=="],
|
|
122
|
-
|
|
123
|
-
"@types/qrcode-terminal": ["@types/qrcode-terminal@0.12.2", "", {}, "sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q=="],
|
|
124
|
-
|
|
125
116
|
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
|
126
117
|
|
|
127
118
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="],
|
|
@@ -268,8 +259,6 @@
|
|
|
268
259
|
|
|
269
260
|
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
|
270
261
|
|
|
271
|
-
"jsqr": ["jsqr@1.4.0", "", {}, "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A=="],
|
|
272
|
-
|
|
273
262
|
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
|
274
263
|
|
|
275
264
|
"knip": ["knip@5.88.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "minimist": "^1.2.8", "oxc-resolver": "^11.19.1", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "unbash": "^2.2.0", "yaml": "^2.8.2", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-tpy5o7zu1MjawVkLPuahymVJekYY3kYjvzcoInhIchgePxTlo+api90tBv2KfhAIe5uXh+mez1tAfmbv8/TiZg=="],
|
|
@@ -314,16 +303,12 @@
|
|
|
314
303
|
|
|
315
304
|
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
|
316
305
|
|
|
317
|
-
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
|
|
318
|
-
|
|
319
306
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
|
320
307
|
|
|
321
308
|
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
|
|
322
309
|
|
|
323
310
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
|
324
311
|
|
|
325
|
-
"qrcode-terminal": ["qrcode-terminal@0.12.0", "", { "bin": { "qrcode-terminal": "./bin/qrcode-terminal.js" } }, "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="],
|
|
326
|
-
|
|
327
312
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
|
328
313
|
|
|
329
314
|
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vellumai/cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "CLI tools for vellum-assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -26,17 +26,12 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"chalk": "5.6.2",
|
|
28
28
|
"ink": "6.8.0",
|
|
29
|
-
"jsqr": "1.4.0",
|
|
30
29
|
"nanoid": "5.1.7",
|
|
31
|
-
"pngjs": "7.0.0",
|
|
32
|
-
"qrcode-terminal": "0.12.0",
|
|
33
30
|
"react": "19.2.4",
|
|
34
31
|
"react-devtools-core": "6.1.5"
|
|
35
32
|
},
|
|
36
33
|
"devDependencies": {
|
|
37
34
|
"@types/bun": "1.3.11",
|
|
38
|
-
"@types/pngjs": "6.0.5",
|
|
39
|
-
"@types/qrcode-terminal": "0.12.2",
|
|
40
35
|
"@types/react": "19.2.14",
|
|
41
36
|
"eslint": "10.1.0",
|
|
42
37
|
"knip": "5.88.1",
|
|
@@ -64,6 +64,11 @@ const localRuntimeExportToGcsMock = spyOn(
|
|
|
64
64
|
"localRuntimeExportToGcs",
|
|
65
65
|
).mockResolvedValue({ jobId: "platform-export-job-1" });
|
|
66
66
|
|
|
67
|
+
const localRuntimeIdentityMock = spyOn(
|
|
68
|
+
localRuntimeClient,
|
|
69
|
+
"localRuntimeIdentity",
|
|
70
|
+
).mockResolvedValue({ version: "0.6.5" });
|
|
71
|
+
|
|
67
72
|
const localRuntimePollJobStatusMock = spyOn(
|
|
68
73
|
localRuntimeClient,
|
|
69
74
|
"localRuntimePollJobStatus",
|
|
@@ -138,6 +143,8 @@ beforeEach(() => {
|
|
|
138
143
|
localRuntimeExportToGcsMock.mockResolvedValue({
|
|
139
144
|
jobId: "platform-export-job-1",
|
|
140
145
|
});
|
|
146
|
+
localRuntimeIdentityMock.mockReset();
|
|
147
|
+
localRuntimeIdentityMock.mockResolvedValue({ version: "0.6.5" });
|
|
141
148
|
localRuntimePollJobStatusMock.mockReset();
|
|
142
149
|
localRuntimePollJobStatusMock.mockResolvedValue({
|
|
143
150
|
jobId: "platform-export-job-1",
|
|
@@ -165,6 +172,7 @@ afterAll(() => {
|
|
|
165
172
|
getPlatformUrlMock.mockRestore();
|
|
166
173
|
platformRequestSignedUrlMock.mockRestore();
|
|
167
174
|
localRuntimeExportToGcsMock.mockRestore();
|
|
175
|
+
localRuntimeIdentityMock.mockRestore();
|
|
168
176
|
localRuntimePollJobStatusMock.mockRestore();
|
|
169
177
|
getBackupsDirMock.mockRestore();
|
|
170
178
|
mkdirSyncMock.mockRestore();
|
|
@@ -201,7 +209,11 @@ describe("vellum backup <platform-managed>: GCS happy path", () => {
|
|
|
201
209
|
|
|
202
210
|
// Upload-URL request to the platform.
|
|
203
211
|
expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
|
|
204
|
-
expect.objectContaining({
|
|
212
|
+
expect.objectContaining({
|
|
213
|
+
operation: "upload",
|
|
214
|
+
minRuntimeVersion: "0.6.5",
|
|
215
|
+
maxRuntimeVersion: null,
|
|
216
|
+
}),
|
|
205
217
|
"platform-token",
|
|
206
218
|
"https://platform.vellum.ai",
|
|
207
219
|
);
|
|
@@ -230,15 +242,24 @@ describe("vellum backup <platform-managed>: GCS happy path", () => {
|
|
|
230
242
|
"platform-export-job-1",
|
|
231
243
|
);
|
|
232
244
|
|
|
233
|
-
// Download URL keyed off the upload's bundleKey.
|
|
245
|
+
// Download URL keyed off the upload's bundleKey. We deliberately do
|
|
246
|
+
// NOT send `targetRuntimeVersion` here — this backup downloads the
|
|
247
|
+
// bundle to disk for offline storage; there is no target runtime to
|
|
248
|
+
// gate against, and an older CLI must be able to download newer
|
|
249
|
+
// assistant backups.
|
|
234
250
|
expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
|
|
235
|
-
{
|
|
251
|
+
expect.objectContaining({
|
|
236
252
|
operation: "download",
|
|
237
253
|
bundleKey: "uploads/org-1/bundle-abc.vbundle",
|
|
238
|
-
},
|
|
254
|
+
}),
|
|
239
255
|
"platform-token",
|
|
240
256
|
"https://platform.vellum.ai",
|
|
241
257
|
);
|
|
258
|
+
const downloadCall = platformRequestSignedUrlMock.mock.calls.find(
|
|
259
|
+
(c) => (c[0] as { operation: string }).operation === "download",
|
|
260
|
+
);
|
|
261
|
+
expect(downloadCall).toBeDefined();
|
|
262
|
+
expect(downloadCall![0]).not.toHaveProperty("targetRuntimeVersion");
|
|
242
263
|
|
|
243
264
|
// GCS fetch went directly to the signed download URL with no auth.
|
|
244
265
|
const gcsFetch = globalThis.fetch as unknown as ReturnType<typeof mock>;
|
|
@@ -306,7 +327,11 @@ describe("vellum backup <platform-managed>: GCS happy path", () => {
|
|
|
306
327
|
// runtimeUrl. The signed URLs returned by the platform target the
|
|
307
328
|
// GCS bucket the runtime can reach, not the default platform's.
|
|
308
329
|
expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
|
|
309
|
-
expect.objectContaining({
|
|
330
|
+
expect.objectContaining({
|
|
331
|
+
operation: "upload",
|
|
332
|
+
minRuntimeVersion: "0.6.5",
|
|
333
|
+
maxRuntimeVersion: null,
|
|
334
|
+
}),
|
|
310
335
|
"platform-token",
|
|
311
336
|
"https://staging-platform.vellum.ai",
|
|
312
337
|
);
|
|
@@ -473,3 +498,94 @@ describe("vellum backup <platform-managed>: failure cases", () => {
|
|
|
473
498
|
}
|
|
474
499
|
});
|
|
475
500
|
});
|
|
501
|
+
|
|
502
|
+
// NOTE: The `VersionMismatchError handling` describe block was removed when
|
|
503
|
+
// backup stopped sending `targetRuntimeVersion` on the download signed-URL
|
|
504
|
+
// request — without that field the platform doesn't run the version gate,
|
|
505
|
+
// so 422 `version_mismatch` is no longer reachable from this code path.
|
|
506
|
+
|
|
507
|
+
// ---------------------------------------------------------------------------
|
|
508
|
+
// Source-runtime version is sourced from the daemon, not the CLI
|
|
509
|
+
// (Codex P1 regression guard for PR #29436)
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
describe("upload signed-URL records source runtime version (not CLI version)", () => {
|
|
512
|
+
test("identity is fetched BEFORE the upload signed-URL request", async () => {
|
|
513
|
+
findAssistantByNameMock.mockReturnValue(VELLUM_ENTRY);
|
|
514
|
+
setArgv("my-platform");
|
|
515
|
+
|
|
516
|
+
const callOrder: string[] = [];
|
|
517
|
+
localRuntimeIdentityMock.mockImplementationOnce(async () => {
|
|
518
|
+
callOrder.push("identity");
|
|
519
|
+
return { version: "0.5.9" };
|
|
520
|
+
});
|
|
521
|
+
platformRequestSignedUrlMock.mockImplementationOnce(async (params) => {
|
|
522
|
+
callOrder.push("signed-url");
|
|
523
|
+
return {
|
|
524
|
+
url: "https://storage.googleapis.com/bucket/signed-upload",
|
|
525
|
+
bundleKey: params.bundleKey ?? "uploads/org-1/bundle-abc.vbundle",
|
|
526
|
+
expiresAt: new Date(Date.now() + 3600_000).toISOString(),
|
|
527
|
+
};
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
mockGcsDownload(new Uint8Array([1]));
|
|
531
|
+
|
|
532
|
+
await backup();
|
|
533
|
+
|
|
534
|
+
expect(callOrder[0]).toBe("identity");
|
|
535
|
+
expect(callOrder[1]).toBe("signed-url");
|
|
536
|
+
|
|
537
|
+
expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
|
|
538
|
+
expect.objectContaining({
|
|
539
|
+
operation: "upload",
|
|
540
|
+
minRuntimeVersion: "0.5.9",
|
|
541
|
+
maxRuntimeVersion: null,
|
|
542
|
+
}),
|
|
543
|
+
"platform-token",
|
|
544
|
+
"https://platform.vellum.ai",
|
|
545
|
+
);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test("identity is fetched against the platform-managed runtime entry with the platform token", async () => {
|
|
549
|
+
findAssistantByNameMock.mockReturnValue(VELLUM_ENTRY);
|
|
550
|
+
setArgv("my-platform");
|
|
551
|
+
|
|
552
|
+
mockGcsDownload(new Uint8Array([1]));
|
|
553
|
+
|
|
554
|
+
await backup();
|
|
555
|
+
|
|
556
|
+
expect(localRuntimeIdentityMock).toHaveBeenCalledWith(
|
|
557
|
+
expect.objectContaining({
|
|
558
|
+
cloud: "vellum",
|
|
559
|
+
runtimeUrl: "https://platform.vellum.ai",
|
|
560
|
+
assistantId: "11111111-2222-3333-4444-555555555555",
|
|
561
|
+
}),
|
|
562
|
+
"platform-token",
|
|
563
|
+
);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test("identity fetch failure aborts before signed-URL request", async () => {
|
|
567
|
+
findAssistantByNameMock.mockReturnValue(VELLUM_ENTRY);
|
|
568
|
+
setArgv("my-platform");
|
|
569
|
+
|
|
570
|
+
localRuntimeIdentityMock.mockRejectedValue(
|
|
571
|
+
new Error("Failed to fetch runtime identity: 503 Service Unavailable"),
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
const consoleErrorSpy = spyOn(console, "error").mockImplementation(
|
|
575
|
+
() => undefined,
|
|
576
|
+
);
|
|
577
|
+
try {
|
|
578
|
+
await expect(backup()).rejects.toThrow("process.exit:1");
|
|
579
|
+
|
|
580
|
+
// Signed-URL must NOT have been requested.
|
|
581
|
+
expect(platformRequestSignedUrlMock).not.toHaveBeenCalled();
|
|
582
|
+
expect(localRuntimeExportToGcsMock).not.toHaveBeenCalled();
|
|
583
|
+
|
|
584
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
585
|
+
expect.stringContaining("Could not fetch runtime identity"),
|
|
586
|
+
);
|
|
587
|
+
} finally {
|
|
588
|
+
consoleErrorSpy.mockRestore();
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
});
|