@vellumai/cli 0.5.7 → 0.5.8
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/package.json +1 -1
- package/src/commands/backup.ts +124 -0
- package/src/commands/hatch.ts +24 -5
- package/src/commands/restore.ts +359 -16
- package/src/commands/rollback.ts +234 -103
- package/src/commands/upgrade.ts +231 -209
- package/src/index.ts +4 -4
- package/src/lib/aws.ts +14 -9
- package/src/lib/cli-error.ts +2 -0
- package/src/lib/docker.ts +54 -13
- package/src/lib/gcp.ts +15 -10
- package/src/lib/guardian-token.ts +4 -42
- package/src/lib/local.ts +1 -0
- package/src/lib/platform-client.ts +206 -18
- package/src/lib/upgrade-lifecycle.ts +612 -5
- package/src/lib/workspace-git.ts +0 -39
package/src/lib/docker.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { AssistantEntry } from "./assistant-config";
|
|
|
15
15
|
import { writeInitialConfig } from "./config-utils";
|
|
16
16
|
import { DEFAULT_GATEWAY_PORT, PROVIDER_ENV_VAR_NAMES } from "./constants";
|
|
17
17
|
import type { Species } from "./constants";
|
|
18
|
-
import { leaseGuardianToken
|
|
18
|
+
import { leaseGuardianToken } from "./guardian-token";
|
|
19
19
|
import { isVellumProcess, stopProcess } from "./process";
|
|
20
20
|
import { generateInstanceName } from "./random-name";
|
|
21
21
|
import { resolveImageRefs } from "./platform-releases.js";
|
|
@@ -271,11 +271,30 @@ async function ensureDockerInstalled(): Promise<void> {
|
|
|
271
271
|
console.log("🚀 Docker daemon not running. Starting Colima...");
|
|
272
272
|
try {
|
|
273
273
|
await exec("colima", ["start"]);
|
|
274
|
-
} catch
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
} catch {
|
|
275
|
+
// Colima may fail if a previous VM instance is in a corrupt state.
|
|
276
|
+
// Attempt to delete the stale instance and retry once.
|
|
277
|
+
console.log(
|
|
278
|
+
"⚠️ Colima start failed — attempting to reset stale VM state...",
|
|
278
279
|
);
|
|
280
|
+
try {
|
|
281
|
+
await exec("colima", ["stop", "--force"]).catch(() => {});
|
|
282
|
+
await exec("colima", ["delete", "--force"]);
|
|
283
|
+
} catch {
|
|
284
|
+
// If delete also fails, fall through to the retry which will
|
|
285
|
+
// produce a clear error message.
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
console.log("🔄 Retrying colima start...");
|
|
290
|
+
await exec("colima", ["start"]);
|
|
291
|
+
} catch (retryErr) {
|
|
292
|
+
const message =
|
|
293
|
+
retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Failed to start Colima after resetting stale VM state. Please run 'colima start' manually.\n${message}`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
279
298
|
}
|
|
280
299
|
}
|
|
281
300
|
}
|
|
@@ -505,7 +524,7 @@ export function serviceDockerRunArgs(opts: {
|
|
|
505
524
|
"-e",
|
|
506
525
|
"RUNTIME_HTTP_HOST=0.0.0.0",
|
|
507
526
|
"-e",
|
|
508
|
-
"
|
|
527
|
+
"VELLUM_WORKSPACE_DIR=/workspace",
|
|
509
528
|
"-e",
|
|
510
529
|
`CES_CREDENTIAL_URL=http://${res.cesContainer}:8090`,
|
|
511
530
|
"-e",
|
|
@@ -556,7 +575,7 @@ export function serviceDockerRunArgs(opts: {
|
|
|
556
575
|
"-v",
|
|
557
576
|
`${res.gatewaySecurityVolume}:/gateway-security`,
|
|
558
577
|
"-e",
|
|
559
|
-
"
|
|
578
|
+
"VELLUM_WORKSPACE_DIR=/workspace",
|
|
560
579
|
"-e",
|
|
561
580
|
"GATEWAY_SECURITY_DIR=/gateway-security",
|
|
562
581
|
"-e",
|
|
@@ -596,7 +615,7 @@ export function serviceDockerRunArgs(opts: {
|
|
|
596
615
|
"-e",
|
|
597
616
|
"CES_MODE=managed",
|
|
598
617
|
"-e",
|
|
599
|
-
"
|
|
618
|
+
"VELLUM_WORKSPACE_DIR=/workspace",
|
|
600
619
|
"-e",
|
|
601
620
|
"CES_BOOTSTRAP_SOCKET_DIR=/run/ces-bootstrap",
|
|
602
621
|
"-e",
|
|
@@ -789,7 +808,6 @@ export async function stopContainers(
|
|
|
789
808
|
await removeContainer(res.assistantContainer);
|
|
790
809
|
}
|
|
791
810
|
|
|
792
|
-
|
|
793
811
|
/** Stop containers without removing them (preserves state for `docker start`). */
|
|
794
812
|
export async function sleepContainers(
|
|
795
813
|
res: ReturnType<typeof dockerResourceNames>,
|
|
@@ -1128,8 +1146,18 @@ export async function hatchDocker(
|
|
|
1128
1146
|
|
|
1129
1147
|
const cesServiceToken = randomBytes(32).toString("hex");
|
|
1130
1148
|
const signingKey = randomBytes(32).toString("hex");
|
|
1131
|
-
|
|
1132
|
-
|
|
1149
|
+
|
|
1150
|
+
// When launched by a remote hatch startup script, the env var
|
|
1151
|
+
// GUARDIAN_BOOTSTRAP_SECRET is already set with the laptop's secret.
|
|
1152
|
+
// Generate a new secret for the local docker hatch caller and append
|
|
1153
|
+
// it so the gateway receives a comma-separated list of all expected
|
|
1154
|
+
// bootstrap secrets.
|
|
1155
|
+
const ownSecret = randomBytes(32).toString("hex");
|
|
1156
|
+
const preExisting = process.env.GUARDIAN_BOOTSTRAP_SECRET;
|
|
1157
|
+
const bootstrapSecret = preExisting
|
|
1158
|
+
? `${preExisting},${ownSecret}`
|
|
1159
|
+
: ownSecret;
|
|
1160
|
+
|
|
1133
1161
|
await startContainers(
|
|
1134
1162
|
{
|
|
1135
1163
|
signingKey,
|
|
@@ -1169,6 +1197,7 @@ export async function hatchDocker(
|
|
|
1169
1197
|
setActiveAssistant(instanceName);
|
|
1170
1198
|
|
|
1171
1199
|
const { ready } = await waitForGatewayAndLease({
|
|
1200
|
+
bootstrapSecret: ownSecret,
|
|
1172
1201
|
containerName: res.assistantContainer,
|
|
1173
1202
|
detached: watch ? false : detached,
|
|
1174
1203
|
instanceName,
|
|
@@ -1226,13 +1255,21 @@ export async function hatchDocker(
|
|
|
1226
1255
|
* lease a guardian token.
|
|
1227
1256
|
*/
|
|
1228
1257
|
async function waitForGatewayAndLease(opts: {
|
|
1258
|
+
bootstrapSecret: string;
|
|
1229
1259
|
containerName: string;
|
|
1230
1260
|
detached: boolean;
|
|
1231
1261
|
instanceName: string;
|
|
1232
1262
|
logFd: number | "ignore";
|
|
1233
1263
|
runtimeUrl: string;
|
|
1234
1264
|
}): Promise<{ ready: boolean }> {
|
|
1235
|
-
const {
|
|
1265
|
+
const {
|
|
1266
|
+
bootstrapSecret,
|
|
1267
|
+
containerName,
|
|
1268
|
+
detached,
|
|
1269
|
+
instanceName,
|
|
1270
|
+
logFd,
|
|
1271
|
+
runtimeUrl,
|
|
1272
|
+
} = opts;
|
|
1236
1273
|
|
|
1237
1274
|
const log = (msg: string): void => {
|
|
1238
1275
|
console.log(msg);
|
|
@@ -1306,7 +1343,11 @@ async function waitForGatewayAndLease(opts: {
|
|
|
1306
1343
|
|
|
1307
1344
|
while (Date.now() < leaseDeadline) {
|
|
1308
1345
|
try {
|
|
1309
|
-
const tokenData = await leaseGuardianToken(
|
|
1346
|
+
const tokenData = await leaseGuardianToken(
|
|
1347
|
+
runtimeUrl,
|
|
1348
|
+
instanceName,
|
|
1349
|
+
bootstrapSecret,
|
|
1350
|
+
);
|
|
1310
1351
|
const leaseElapsed = ((Date.now() - leaseStart) / 1000).toFixed(1);
|
|
1311
1352
|
log(
|
|
1312
1353
|
`Guardian token lease: success after ${leaseElapsed}s (principalId=${tokenData.guardianPrincipalId}, expiresAt=${tokenData.accessTokenExpiresAt})`,
|
package/src/lib/gcp.ts
CHANGED
|
@@ -457,7 +457,7 @@ export async function hatchGcp(
|
|
|
457
457
|
instanceName: string,
|
|
458
458
|
cloud: "gcp",
|
|
459
459
|
configValues?: Record<string, string>,
|
|
460
|
-
) => Promise<string>,
|
|
460
|
+
) => Promise<{ script: string; laptopBootstrapSecret: string }>,
|
|
461
461
|
watchHatching: (
|
|
462
462
|
pollFn: () => Promise<PollResult>,
|
|
463
463
|
instanceName: string,
|
|
@@ -522,14 +522,15 @@ export async function hatchGcp(
|
|
|
522
522
|
);
|
|
523
523
|
process.exit(1);
|
|
524
524
|
}
|
|
525
|
-
const startupScript
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
525
|
+
const { script: startupScript, laptopBootstrapSecret } =
|
|
526
|
+
await buildStartupScript(
|
|
527
|
+
species,
|
|
528
|
+
sshUser,
|
|
529
|
+
providerApiKeys,
|
|
530
|
+
instanceName,
|
|
531
|
+
"gcp",
|
|
532
|
+
configValues,
|
|
533
|
+
);
|
|
533
534
|
const startupScriptPath = join(tmpdir(), `${instanceName}-startup.sh`);
|
|
534
535
|
writeFileSync(startupScriptPath, startupScript);
|
|
535
536
|
|
|
@@ -662,7 +663,11 @@ export async function hatchGcp(
|
|
|
662
663
|
}
|
|
663
664
|
|
|
664
665
|
try {
|
|
665
|
-
await leaseGuardianToken(
|
|
666
|
+
await leaseGuardianToken(
|
|
667
|
+
runtimeUrl,
|
|
668
|
+
instanceName,
|
|
669
|
+
laptopBootstrapSecret,
|
|
670
|
+
);
|
|
666
671
|
} catch (err) {
|
|
667
672
|
console.warn(
|
|
668
673
|
`\u26a0\ufe0f Could not lease guardian token: ${err instanceof Error ? err.message : err}`,
|
|
@@ -42,46 +42,6 @@ function getPersistedDeviceIdPath(): string {
|
|
|
42
42
|
return join(getXdgConfigHome(), "vellum", "device-id");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function getBootstrapSecretPath(assistantId: string): string {
|
|
46
|
-
return join(
|
|
47
|
-
getXdgConfigHome(),
|
|
48
|
-
"vellum",
|
|
49
|
-
"assistants",
|
|
50
|
-
assistantId,
|
|
51
|
-
"bootstrap-secret",
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Load a previously saved bootstrap secret for the given assistant.
|
|
57
|
-
* Returns null if the file does not exist or is unreadable.
|
|
58
|
-
*/
|
|
59
|
-
export function loadBootstrapSecret(assistantId: string): string | null {
|
|
60
|
-
try {
|
|
61
|
-
const raw = readFileSync(getBootstrapSecretPath(assistantId), "utf-8").trim();
|
|
62
|
-
return raw.length > 0 ? raw : null;
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Persist a bootstrap secret for the given assistant so that the desktop
|
|
70
|
-
* client and upgrade/rollback paths can retrieve it later.
|
|
71
|
-
*/
|
|
72
|
-
export function saveBootstrapSecret(
|
|
73
|
-
assistantId: string,
|
|
74
|
-
secret: string,
|
|
75
|
-
): void {
|
|
76
|
-
const path = getBootstrapSecretPath(assistantId);
|
|
77
|
-
const dir = dirname(path);
|
|
78
|
-
if (!existsSync(dir)) {
|
|
79
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
80
|
-
}
|
|
81
|
-
writeFileSync(path, secret + "\n", { mode: 0o600 });
|
|
82
|
-
chmodSync(path, 0o600);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
45
|
function hashWithSalt(input: string): string {
|
|
86
46
|
return createHash("sha256")
|
|
87
47
|
.update(input + DEVICE_ID_SALT)
|
|
@@ -206,10 +166,12 @@ export function saveGuardianToken(
|
|
|
206
166
|
export async function leaseGuardianToken(
|
|
207
167
|
gatewayUrl: string,
|
|
208
168
|
assistantId: string,
|
|
169
|
+
bootstrapSecret?: string,
|
|
209
170
|
): Promise<GuardianTokenData> {
|
|
210
171
|
const deviceId = computeDeviceId();
|
|
211
|
-
const headers: Record<string, string> = {
|
|
212
|
-
|
|
172
|
+
const headers: Record<string, string> = {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
};
|
|
213
175
|
if (bootstrapSecret) {
|
|
214
176
|
headers["x-bootstrap-secret"] = bootstrapSecret;
|
|
215
177
|
}
|
package/src/lib/local.ts
CHANGED
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
import { homedir } from "os";
|
|
10
10
|
import { join, dirname } from "path";
|
|
11
11
|
|
|
12
|
-
const DEFAULT_PLATFORM_URL = "";
|
|
13
|
-
|
|
14
12
|
function getXdgConfigHome(): string {
|
|
15
13
|
return process.env.XDG_CONFIG_HOME?.trim() || join(homedir(), ".config");
|
|
16
14
|
}
|
|
@@ -20,21 +18,25 @@ function getPlatformTokenPath(): string {
|
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export function getPlatformUrl(): string {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
21
|
+
let configUrl: string | undefined;
|
|
22
|
+
try {
|
|
23
|
+
const base = process.env.BASE_DATA_DIR?.trim() || homedir();
|
|
24
|
+
const configPath = join(base, ".vellum", "workspace", "config.json");
|
|
25
|
+
if (existsSync(configPath)) {
|
|
26
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8")) as Record<
|
|
27
|
+
string,
|
|
28
|
+
unknown
|
|
29
|
+
>;
|
|
30
|
+
const val = (raw.platform as Record<string, unknown> | undefined)
|
|
31
|
+
?.baseUrl;
|
|
32
|
+
if (typeof val === "string" && val.trim()) configUrl = val.trim();
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Config not available — fall through
|
|
36
36
|
}
|
|
37
|
-
return
|
|
37
|
+
return (
|
|
38
|
+
configUrl || process.env.VELLUM_PLATFORM_URL || "https://platform.vellum.ai"
|
|
39
|
+
);
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export function readPlatformToken(): string | null {
|
|
@@ -74,7 +76,7 @@ interface OrganizationListResponse {
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
export async function fetchOrganizationId(token: string): Promise<string> {
|
|
77
|
-
const platformUrl =
|
|
79
|
+
const platformUrl = getPlatformUrl();
|
|
78
80
|
const url = `${platformUrl}/v1/organizations/`;
|
|
79
81
|
const response = await fetch(url, {
|
|
80
82
|
headers: { "X-Session-Token": token },
|
|
@@ -106,7 +108,7 @@ interface AllauthSessionResponse {
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
export async function fetchCurrentUser(token: string): Promise<PlatformUser> {
|
|
109
|
-
const url = `${
|
|
111
|
+
const url = `${getPlatformUrl()}/_allauth/app/v1/auth/session`;
|
|
110
112
|
const response = await fetch(url, {
|
|
111
113
|
headers: { "X-Session-Token": token },
|
|
112
114
|
});
|
|
@@ -127,3 +129,189 @@ export async function fetchCurrentUser(token: string): Promise<PlatformUser> {
|
|
|
127
129
|
const body = (await response.json()) as AllauthSessionResponse;
|
|
128
130
|
return body.data.user;
|
|
129
131
|
}
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Rollback
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
export async function rollbackPlatformAssistant(
|
|
138
|
+
token: string,
|
|
139
|
+
orgId: string,
|
|
140
|
+
version?: string,
|
|
141
|
+
): Promise<{ detail: string; version: string | null }> {
|
|
142
|
+
const platformUrl = getPlatformUrl();
|
|
143
|
+
const response = await fetch(`${platformUrl}/v1/assistants/rollback/`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
"X-Session-Token": token,
|
|
148
|
+
"Vellum-Organization-Id": orgId,
|
|
149
|
+
},
|
|
150
|
+
body: JSON.stringify(version ? { version } : {}),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const body = (await response.json().catch(() => ({}))) as {
|
|
154
|
+
detail?: string;
|
|
155
|
+
version?: string | null;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (response.status === 200) {
|
|
159
|
+
return { detail: body.detail ?? "", version: body.version ?? null };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (response.status === 400) {
|
|
163
|
+
throw new Error(body.detail ?? "Rollback failed: bad request");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (response.status === 404) {
|
|
167
|
+
throw new Error(body.detail ?? "Rollback target not found");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (response.status === 502) {
|
|
171
|
+
throw new Error(body.detail ?? "Rollback failed: transport error");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
throw new Error(`Rollback failed: ${response.status} ${response.statusText}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Migration export
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
export async function platformInitiateExport(
|
|
182
|
+
token: string,
|
|
183
|
+
orgId: string,
|
|
184
|
+
description?: string,
|
|
185
|
+
): Promise<{ jobId: string; status: string }> {
|
|
186
|
+
const platformUrl = getPlatformUrl();
|
|
187
|
+
const response = await fetch(`${platformUrl}/v1/migrations/export/`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: {
|
|
190
|
+
"Content-Type": "application/json",
|
|
191
|
+
"X-Session-Token": token,
|
|
192
|
+
"Vellum-Organization-Id": orgId,
|
|
193
|
+
},
|
|
194
|
+
body: JSON.stringify({ description: description ?? "CLI backup" }),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (response.status !== 201) {
|
|
198
|
+
const body = (await response.json().catch(() => ({}))) as {
|
|
199
|
+
detail?: string;
|
|
200
|
+
};
|
|
201
|
+
throw new Error(
|
|
202
|
+
body.detail ??
|
|
203
|
+
`Export initiation failed: ${response.status} ${response.statusText}`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const body = (await response.json()) as {
|
|
208
|
+
job_id: string;
|
|
209
|
+
status: string;
|
|
210
|
+
};
|
|
211
|
+
return { jobId: body.job_id, status: body.status };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function platformPollExportStatus(
|
|
215
|
+
jobId: string,
|
|
216
|
+
token: string,
|
|
217
|
+
orgId: string,
|
|
218
|
+
): Promise<{ status: string; downloadUrl?: string; error?: string }> {
|
|
219
|
+
const platformUrl = getPlatformUrl();
|
|
220
|
+
const response = await fetch(
|
|
221
|
+
`${platformUrl}/v1/migrations/export/${jobId}/status/`,
|
|
222
|
+
{
|
|
223
|
+
headers: {
|
|
224
|
+
"X-Session-Token": token,
|
|
225
|
+
"Vellum-Organization-Id": orgId,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (response.status === 404) {
|
|
231
|
+
throw new Error("Export job not found");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Export status check failed: ${response.status} ${response.statusText}`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const body = (await response.json()) as {
|
|
241
|
+
status: string;
|
|
242
|
+
download_url?: string;
|
|
243
|
+
error?: string;
|
|
244
|
+
};
|
|
245
|
+
return {
|
|
246
|
+
status: body.status,
|
|
247
|
+
downloadUrl: body.download_url,
|
|
248
|
+
error: body.error,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function platformDownloadExport(
|
|
253
|
+
downloadUrl: string,
|
|
254
|
+
): Promise<Response> {
|
|
255
|
+
const response = await fetch(downloadUrl);
|
|
256
|
+
if (!response.ok) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Download failed: ${response.status} ${response.statusText}`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return response;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Migration import
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
export async function platformImportPreflight(
|
|
269
|
+
bundleData: Uint8Array<ArrayBuffer>,
|
|
270
|
+
token: string,
|
|
271
|
+
orgId: string,
|
|
272
|
+
): Promise<{ statusCode: number; body: Record<string, unknown> }> {
|
|
273
|
+
const platformUrl = getPlatformUrl();
|
|
274
|
+
const response = await fetch(
|
|
275
|
+
`${platformUrl}/v1/migrations/import-preflight/`,
|
|
276
|
+
{
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: {
|
|
279
|
+
"Content-Type": "application/octet-stream",
|
|
280
|
+
"X-Session-Token": token,
|
|
281
|
+
"Vellum-Organization-Id": orgId,
|
|
282
|
+
},
|
|
283
|
+
body: new Blob([bundleData]),
|
|
284
|
+
signal: AbortSignal.timeout(120_000),
|
|
285
|
+
},
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const body = (await response.json().catch(() => ({}))) as Record<
|
|
289
|
+
string,
|
|
290
|
+
unknown
|
|
291
|
+
>;
|
|
292
|
+
return { statusCode: response.status, body };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export async function platformImportBundle(
|
|
296
|
+
bundleData: Uint8Array<ArrayBuffer>,
|
|
297
|
+
token: string,
|
|
298
|
+
orgId: string,
|
|
299
|
+
): Promise<{ statusCode: number; body: Record<string, unknown> }> {
|
|
300
|
+
const platformUrl = getPlatformUrl();
|
|
301
|
+
const response = await fetch(`${platformUrl}/v1/migrations/import/`, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: {
|
|
304
|
+
"Content-Type": "application/octet-stream",
|
|
305
|
+
"X-Session-Token": token,
|
|
306
|
+
"Vellum-Organization-Id": orgId,
|
|
307
|
+
},
|
|
308
|
+
body: new Blob([bundleData]),
|
|
309
|
+
signal: AbortSignal.timeout(120_000),
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const body = (await response.json().catch(() => ({}))) as Record<
|
|
313
|
+
string,
|
|
314
|
+
unknown
|
|
315
|
+
>;
|
|
316
|
+
return { statusCode: response.status, body };
|
|
317
|
+
}
|