@voltro/cli 0.44.1 → 0.45.0
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/CHANGELOG.md +104 -1
- package/dist/{apiBuild-DvWreIQP.js → apiBuild-CDQBNRDx.js} +1 -1
- package/dist/apiBuild-yJCqJYQ8.js +2 -0
- package/dist/bin.d.ts +16 -0
- package/dist/bin.js +46 -11
- package/dist/{build-Ckc-FA7d.js → build-yvsLG77U.js} +7 -7
- package/dist/{checkCommand-BNfEs4KG.js → checkCommand-Boxov8lr.js} +11 -11
- package/dist/{checkCommand-AlbawIal.js → checkCommand-lrzwOzV1.js} +1 -1
- package/dist/codegen-DcE2li1i.js +2 -0
- package/dist/{codegen-CbpGCWLG.js → codegen-sbN1Y8PB.js} +37 -37
- package/dist/codegenCommand-CQOLytL-.js +144 -0
- package/dist/{codemodRunner-BKTnaGOz.js → codemodRunner-5ZhDH3rB.js} +41 -0
- package/dist/{commands-CYkHTox2.js → commands-CEkVoFM5.js} +19 -19
- package/dist/{dashboardCommand-DHDmlo9A.js → dashboardCommand-0ahyp-zA.js} +1 -1
- package/dist/{dataCommand-WFn8CM11.js → dataCommand-D4IlZcih.js} +275 -211
- package/dist/{dev-D-JZ6CGQ.js → dev-DAM5CnAa.js} +1856 -1801
- package/dist/{dev-BZO5aLdr.js → dev-P4HyeY9W.js} +1 -1
- package/dist/devActivity.d.ts +16 -0
- package/dist/doctorCommand-2K1dIzYk.js +2 -0
- package/dist/{doctorCommand-BlUyESpD.js → doctorCommand-DW2cwAvC.js} +244 -223
- package/dist/doctorSourceRelations-CPjSwYuZ.js +83 -0
- package/dist/{dormancyCommand-lQxdrU8C.js → dormancyCommand-BqQz7lbM.js} +1 -1
- package/dist/{embeddingsCommand-ByBTju6X.js → embeddingsCommand-DJyArpU4.js} +1 -1
- package/dist/{envCommand-CpCbYAgR.js → envCommand-CviRZx1L.js} +1 -1
- package/dist/{evolveCommand-CBV3-TtK.js → evolveCommand-qdlP-R9G.js} +2 -2
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1 -1
- package/dist/{infoCommand-DJoA9cKD.js → infoCommand-CNWPSUcW.js} +1 -1
- package/dist/inspectState.d.ts +16 -0
- package/dist/{runtimeTrace-DJ9tPhrc.js → runtimeTrace-BDZSt9W_.js} +1 -1
- package/dist/{sdkgen-7dr8-Onw.js → sdkgen-sNaD3oZB.js} +1 -1
- package/dist/{serveCommand-BlnAlNQg.js → serveCommand-BBhT2U94.js} +731 -737
- package/dist/serveCommand-DY2jLy0o.js +2 -0
- package/dist/serveEntry.d.ts +16 -0
- package/dist/serveEntry.js +1 -1
- package/dist/startEntry.d.ts +16 -0
- package/dist/startup.d.ts +16 -0
- package/dist/{test-CLYXrZ2F.js → test-CHb7UwvE.js} +1 -1
- package/dist/updateCommand-BH6EGuCc.js +2 -0
- package/dist/{updateCommand-BvRidHp6.js → updateCommand-rCnhaj84.js} +1 -1
- package/dist/{webhooksCommand-BfO_JF2b.js → webhooksCommand-C0iZZfaI.js} +1 -1
- package/package.json +23 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/cli.md +41 -1
- package/templates/agent-docs/data.md +33 -3
- package/templates/agent-docs/plugins/versioning.md +2 -0
- package/templates/agent-docs/whats-new.md +93 -6
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +10 -10
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/package.json +10 -10
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +4 -4
- package/dist/apiBuild-XaW40W8x.js +0 -2
- package/dist/codegen-i8QGcHsi.js +0 -2
- package/dist/codegenCommand-iClXoRCW.js +0 -129
- package/dist/doctorCommand-BeLOwq7S.js +0 -2
- package/dist/serveCommand-Dg_E1ev4.js +0 -2
- package/dist/updateCommand-DcKVmLVN.js +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as e } from "./cliError-BmdYnghb.js";
|
|
2
|
-
import { c as t, i as n, o as r } from "./dev-
|
|
2
|
+
import { c as t, i as n, o as r } from "./dev-DAM5CnAa.js";
|
|
3
3
|
import { n as i, t as a } from "./cliArgs-D4p8n7EE.js";
|
|
4
4
|
import { r as o } from "./dialectDriver-czCHYpeH.js";
|
|
5
5
|
import { t as s } from "./dataProfile-Cm0YVKSy.js";
|
|
@@ -12,18 +12,39 @@ import { declaredSnapshot as h, fingerprintSchema as g, introspectSchema as _ }
|
|
|
12
12
|
import { connectionIdentity as v, normaliseTarget as y, sameDatabase as b, streamTable as x } from "@voltro/database";
|
|
13
13
|
import { STORAGE_REFS_TABLE as ee, resolveStorageProvider as S } from "@voltro/plugin-storage";
|
|
14
14
|
import { createHash as te } from "node:crypto";
|
|
15
|
-
import {
|
|
16
|
-
import { request as
|
|
17
|
-
import { request as
|
|
15
|
+
import { asImportError as ne, assessLiveImport as re, backupArtifactName as C, backupCommand as ie, classificationFromSnapshot as ae, decryptStream as w, encryptStream as oe, packBundle as T, peekBundle as E, previewMasking as se, restoreCommand as D, runExport as ce, runImport as le, runNativeStep as O, storageAssetSink as ue, storageAssetSource as de, storageBlobSink as fe, storageBlobSource as pe, unpackBundle as me, unpackBundleToDir as k } from "@voltro/data-transfer";
|
|
16
|
+
import { request as he } from "node:http";
|
|
17
|
+
import { request as ge } from "node:https";
|
|
18
18
|
//#region src/longRequest.ts
|
|
19
|
-
var
|
|
19
|
+
var _e = /* @__PURE__ */ new Set([
|
|
20
|
+
"ECONNREFUSED",
|
|
21
|
+
"ETIMEDOUT",
|
|
22
|
+
"EHOSTUNREACH",
|
|
23
|
+
"ENETUNREACH",
|
|
24
|
+
"ENOTFOUND",
|
|
25
|
+
"ECONNRESET",
|
|
26
|
+
"EAI_AGAIN"
|
|
27
|
+
]), A = class extends Error {
|
|
28
|
+
url;
|
|
29
|
+
cause;
|
|
30
|
+
constructor(e, t) {
|
|
31
|
+
let n = (() => {
|
|
32
|
+
try {
|
|
33
|
+
return new URL(e).host;
|
|
34
|
+
} catch {
|
|
35
|
+
return e;
|
|
36
|
+
}
|
|
37
|
+
})();
|
|
38
|
+
super(`the instance at ${n} is not answering (${t.code ?? "connect failed"}).`), this.url = e, this.cause = t, this.name = "InstanceUnreachable";
|
|
39
|
+
}
|
|
40
|
+
}, j = class extends Error {
|
|
20
41
|
elapsedMs;
|
|
21
42
|
url;
|
|
22
43
|
constructor(e, t) {
|
|
23
44
|
super(`no response after ${Math.round(e / 1e3)}s`), this.elapsedMs = e, this.url = t, this.name = "LongRequestTimeout";
|
|
24
45
|
}
|
|
25
|
-
},
|
|
26
|
-
let i = new URL(e), a = i.protocol === "https:" ?
|
|
46
|
+
}, M = async (e, t, n, r) => {
|
|
47
|
+
let i = new URL(e), a = i.protocol === "https:" ? ge : he, o = Date.now();
|
|
27
48
|
return await new Promise((s, c) => {
|
|
28
49
|
let l = a({
|
|
29
50
|
protocol: i.protocol,
|
|
@@ -41,8 +62,8 @@ var A = class extends Error {
|
|
|
41
62
|
})), e.on("error", c);
|
|
42
63
|
});
|
|
43
64
|
if (r?.timeoutMs !== void 0 && r.timeoutMs > 0 && l.setTimeout(r.timeoutMs, () => {
|
|
44
|
-
l.destroy(new
|
|
45
|
-
}), l.on("error", c), typeof t == "string" || Buffer.isBuffer(t)) {
|
|
65
|
+
l.destroy(new j(Date.now() - o, e));
|
|
66
|
+
}), l.on("error", (t) => c(_e.has(t.code ?? "") ? new A(e, t) : t)), typeof t == "string" || Buffer.isBuffer(t)) {
|
|
46
67
|
l.end(t);
|
|
47
68
|
return;
|
|
48
69
|
}
|
|
@@ -55,7 +76,7 @@ var A = class extends Error {
|
|
|
55
76
|
}
|
|
56
77
|
})();
|
|
57
78
|
});
|
|
58
|
-
},
|
|
79
|
+
}, ve = (e) => `${e.message}\n That address came from --api-url (or VOLTRO_API_URL) — it is the running instance, not the database.\n Nothing was sent, so nothing was imported. Check the instance is up and reachable from here\n (a VPN, an ingress or a stopped deployment are the usual three), e.g. curl -sS ${e.url}/_voltro/inspect/app`, ye = (e, t) => `the instance did not answer within ${Math.round(e.elapsedMs / 1e3)}s. This is a client-side deadline, not a failure on the server: the upload itself finished long before, and the response arrives only when the import does. The instance is very probably STILL IMPORTING and will finish.\n` + (t === "replace" ? " Do NOT simply re-run: --mode replace empties the target first, so a second run started while the first is mid-flight would empty it under the first one. Check the instance's log for the import's completion, and check the target's row counts, before deciding." : ` Re-running the same bundle is safe once the first run has finished (--mode ${t} is idempotent), but check the instance's log first — the work may already be done.`) + "\n To keep a deadline anyway, pass --timeout <seconds>; the default is to wait.", be = 16777216, xe = async function* (e, t) {
|
|
59
80
|
let n = [], r = 0, i = 0, a = function* (e) {
|
|
60
81
|
let t = Buffer.concat(n);
|
|
61
82
|
n = [], r = 0;
|
|
@@ -77,12 +98,12 @@ var A = class extends Error {
|
|
|
77
98
|
i += a.byteLength, yield s;
|
|
78
99
|
}
|
|
79
100
|
yield* a(!0);
|
|
80
|
-
},
|
|
101
|
+
}, Se = (e) => te("sha256").update(Buffer.from(e)).digest("hex"), Ce = async (e) => {
|
|
81
102
|
let t = 0, n = 0, r = {
|
|
82
103
|
status: 0,
|
|
83
104
|
body: {}
|
|
84
105
|
};
|
|
85
|
-
for await (let i of
|
|
106
|
+
for await (let i of xe(e.stream, e.chunkBytes)) {
|
|
86
107
|
if (i.offset + i.bytes.byteLength <= e.alreadyHeld && !i.final) {
|
|
87
108
|
n++;
|
|
88
109
|
continue;
|
|
@@ -108,13 +129,13 @@ var A = class extends Error {
|
|
|
108
129
|
bytes: t,
|
|
109
130
|
resumedFrom: e.alreadyHeld
|
|
110
131
|
};
|
|
111
|
-
},
|
|
132
|
+
}, N = "voltro-backup-stamp.json", we = (e) => ({
|
|
112
133
|
stampVersion: 1,
|
|
113
134
|
dialect: e.dialect,
|
|
114
135
|
schemaFingerprint: e.schemaFingerprint,
|
|
115
136
|
voltroVersion: e.voltroVersion,
|
|
116
137
|
createdAt: e.createdAt
|
|
117
|
-
}),
|
|
138
|
+
}), P = (e) => {
|
|
118
139
|
let t;
|
|
119
140
|
try {
|
|
120
141
|
t = JSON.parse(e);
|
|
@@ -132,10 +153,10 @@ var A = class extends Error {
|
|
|
132
153
|
voltroVersion: n.voltroVersion,
|
|
133
154
|
createdAt: n.createdAt
|
|
134
155
|
};
|
|
135
|
-
},
|
|
156
|
+
}, Te = (e) => {
|
|
136
157
|
let { stamp: t, currentDialect: n, currentFingerprint: r } = e;
|
|
137
158
|
if (t === null) return {
|
|
138
|
-
warn: `no ${
|
|
159
|
+
warn: `no ${N} beside the dump — an older/handmade backup. Cannot verify dialect or schema version; make sure this dump is a ${n} dump.`,
|
|
139
160
|
info: []
|
|
140
161
|
};
|
|
141
162
|
let i = [`backup produced by @voltro/cli ${t.voltroVersion} at ${t.createdAt}`, `schema fingerprint: ${t.schemaFingerprint ?? "<none recorded>"}`];
|
|
@@ -146,20 +167,20 @@ var A = class extends Error {
|
|
|
146
167
|
refuse: `dialect mismatch — backup is a ${t.dialect} dump, but DB_DIALECT is ${n}. A cross-dialect restore is never valid.`,
|
|
147
168
|
info: i
|
|
148
169
|
};
|
|
149
|
-
},
|
|
170
|
+
}, F = (e) => {
|
|
150
171
|
try {
|
|
151
172
|
let t = new URL(e), n = t.pathname.replace(/^\//, "");
|
|
152
173
|
return `${t.hostname.toLowerCase()}:${t.port || "(default)"}/${n}`;
|
|
153
174
|
} catch {
|
|
154
175
|
return e.trim();
|
|
155
176
|
}
|
|
156
|
-
},
|
|
177
|
+
}, Ee = (e) => {
|
|
157
178
|
let t = e.flagUrl ?? e.envUrl;
|
|
158
179
|
if (t === void 0 || t.trim() === "") return {
|
|
159
180
|
ok: !1,
|
|
160
181
|
error: "no drill target — pass `--drill-url <url>` or set `DRILL_DB_URL`. The drill restores into a THROWAWAY database (never the live one), so it needs its own connection."
|
|
161
182
|
};
|
|
162
|
-
let n =
|
|
183
|
+
let n = F(t);
|
|
163
184
|
return e.liveKeys.some((e) => e === n) ? {
|
|
164
185
|
ok: !1,
|
|
165
186
|
error: `drill target (${n}) is the LIVE database — a restore drill must target a throwaway DB, never production. It would --clean and overwrite it.`
|
|
@@ -167,7 +188,7 @@ var A = class extends Error {
|
|
|
167
188
|
ok: !0,
|
|
168
189
|
url: t
|
|
169
190
|
};
|
|
170
|
-
},
|
|
191
|
+
}, De = (e) => {
|
|
171
192
|
let { stampFingerprint: t, restoredFingerprint: n, restoredTableCount: r } = e;
|
|
172
193
|
return r === 0 ? {
|
|
173
194
|
pass: !1,
|
|
@@ -182,24 +203,24 @@ var A = class extends Error {
|
|
|
182
203
|
pass: !0,
|
|
183
204
|
lines: [`PASS — restored ${r} table(s); schema fingerprint matches the backup stamp (${n.slice(0, 12)}).`]
|
|
184
205
|
};
|
|
185
|
-
},
|
|
206
|
+
}, I = d({ scope: "voltro:data" }), Oe = async () => {
|
|
186
207
|
try {
|
|
187
208
|
let { createRequire: e } = await import("node:module"), { join: t } = await import("node:path"), { readFileSync: n } = await import("node:fs"), r = e(t(process.cwd(), "package.json")).resolve("@voltro/cli/package.json");
|
|
188
209
|
return JSON.parse(n(r, "utf8")).version ?? "unknown";
|
|
189
210
|
} catch {
|
|
190
211
|
return "unknown";
|
|
191
212
|
}
|
|
192
|
-
},
|
|
213
|
+
}, ke = async (e) => {
|
|
193
214
|
try {
|
|
194
|
-
return g(await
|
|
215
|
+
return g(await H(e));
|
|
195
216
|
} catch {
|
|
196
217
|
return null;
|
|
197
218
|
}
|
|
198
219
|
}, L = (e) => {
|
|
199
220
|
if (e.event !== "done") return;
|
|
200
221
|
let t = e.total === void 0 ? "" : ` / ${e.total}`;
|
|
201
|
-
|
|
202
|
-
},
|
|
222
|
+
I.info(`${e.phase} [${e.index + 1}/${e.tableCount}] ${e.table}`, { rows: `${e.rowsDone}${t}` });
|
|
223
|
+
}, Ae = async (e) => {
|
|
203
224
|
let t = (e) => e?.meta?.databaseId;
|
|
204
225
|
if (e) {
|
|
205
226
|
let n = await l({
|
|
@@ -219,21 +240,21 @@ var A = class extends Error {
|
|
|
219
240
|
};
|
|
220
241
|
}
|
|
221
242
|
return null;
|
|
222
|
-
},
|
|
223
|
-
let t = e.includes("--allow-live"), n = t ? null : await
|
|
224
|
-
dialect:
|
|
243
|
+
}, je = async (e) => {
|
|
244
|
+
let t = e.includes("--allow-live"), n = t ? null : await Ae(R(e, "--api-url")), i = v({
|
|
245
|
+
dialect: B(),
|
|
225
246
|
...r()
|
|
226
247
|
}), a = y({
|
|
227
|
-
dialect:
|
|
248
|
+
dialect: B(),
|
|
228
249
|
...r()
|
|
229
|
-
}), { refuse: o, message: s } =
|
|
250
|
+
}), { refuse: o, message: s } = re({
|
|
230
251
|
liveInstance: n?.label ?? null,
|
|
231
252
|
allowLive: t,
|
|
232
253
|
sameTarget: b(i, n?.databaseId),
|
|
233
254
|
...a === void 0 ? {} : { targetLabel: `${a.host}:${a.port}/${a.database}` }
|
|
234
255
|
});
|
|
235
|
-
return !o || (
|
|
236
|
-
},
|
|
256
|
+
return !o || (I.error(s ?? "refusing: live instance detected"), !1);
|
|
257
|
+
}, R = i, Me = [
|
|
237
258
|
"--api-url",
|
|
238
259
|
"--bundle-key",
|
|
239
260
|
"--compression",
|
|
@@ -251,44 +272,44 @@ var A = class extends Error {
|
|
|
251
272
|
"--timeout",
|
|
252
273
|
"--tenant",
|
|
253
274
|
"--token"
|
|
254
|
-
],
|
|
275
|
+
], z = (e) => a(e, Me), B = () => {
|
|
255
276
|
let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
256
277
|
if (t === "memory") throw new e("voltro data: requires a real dialect, not DB_DIALECT=memory.");
|
|
257
278
|
return t;
|
|
258
|
-
},
|
|
279
|
+
}, V = async (e, t) => {
|
|
259
280
|
let n = (await o(e)).makeSqlLayer(t);
|
|
260
281
|
return f.runPromise(m.SqlClient.pipe(f.flatMap(_), f.provide(n)));
|
|
261
|
-
},
|
|
282
|
+
}, H = (e) => V(e, r()), Ne = () => {
|
|
262
283
|
let e = r(), t = [];
|
|
263
|
-
return e.url && t.push(
|
|
264
|
-
},
|
|
284
|
+
return e.url && t.push(F(e.url)), (e.host || e.database) && t.push(`${(e.host ?? "localhost").toLowerCase()}:${e.port ?? "(default)"}/${e.database ?? ""}`), t;
|
|
285
|
+
}, U = async () => {
|
|
265
286
|
let r = process.cwd(), i = await t(r);
|
|
266
287
|
if (!i) throw new e("voltro data: no app.config.ts found in the current directory.");
|
|
267
|
-
let a = await n(i, void 0), o =
|
|
288
|
+
let a = await n(i, void 0), o = B();
|
|
268
289
|
return {
|
|
269
290
|
store: a,
|
|
270
|
-
snapshot: await
|
|
291
|
+
snapshot: await H(o),
|
|
271
292
|
dialect: o
|
|
272
293
|
};
|
|
273
|
-
},
|
|
294
|
+
}, W = (e) => x(e, {
|
|
274
295
|
table: ee,
|
|
275
296
|
chunkSize: 500
|
|
276
297
|
}).pipe(p.map((e) => ({
|
|
277
298
|
key: String(e.key),
|
|
278
299
|
contentType: String(e.contentType ?? "application/octet-stream"),
|
|
279
300
|
size: Number(e.size ?? 0)
|
|
280
|
-
}))),
|
|
301
|
+
}))), G = (e) => !!e && e.endsWith(".vbundle"), K = (e) => R(e, "--passphrase") ?? process.env.VOLTRO_BUNDLE_KEY, Pe = (e) => e.includes("--encrypt") || R(e, "--passphrase") !== void 0 || process.env.VOLTRO_BUNDLE_KEY !== void 0, Fe = async (e, t) => {
|
|
281
302
|
let { createWriteStream: n } = await import("node:fs"), { pipeline: r } = await import("node:stream/promises"), { Readable: i } = await import("node:stream");
|
|
282
303
|
await r(i.from(e), n(t));
|
|
283
|
-
},
|
|
304
|
+
}, q = (e) => ({ async *[Symbol.asyncIterator]() {
|
|
284
305
|
let { createReadStream: t } = await import("node:fs");
|
|
285
306
|
for await (let n of t(e)) yield n instanceof Uint8Array ? n : new Uint8Array(n);
|
|
286
|
-
} }),
|
|
287
|
-
let r =
|
|
307
|
+
} }), J = (e) => (e ?? "").split(",").map((e) => e.trim()).filter(Boolean), Ie = (t, n) => {
|
|
308
|
+
let r = R(t, "--tenant"), i = R(t, "--tables"), a = R(t, "--exclude");
|
|
288
309
|
if (a !== void 0) {
|
|
289
310
|
if (i !== void 0) throw new e("voltro data export: --exclude and --tables are two ways to say the same thing; use one.");
|
|
290
311
|
if (r !== void 0) throw new e("voltro data export: --exclude does not apply to --tenant, whose table set is derived from the tenant closure.");
|
|
291
|
-
let t =
|
|
312
|
+
let t = J(a), o = new Set(n), s = t.filter((e) => !o.has(e));
|
|
292
313
|
if (s.length > 0) throw new e(`voltro data export: --exclude names ${s.length} table(s) that do not exist: ${s.join(", ")}. An exclusion that excludes nothing would leave the run looking like it worked.`);
|
|
293
314
|
let c = new Set(t);
|
|
294
315
|
return {
|
|
@@ -301,15 +322,15 @@ var A = class extends Error {
|
|
|
301
322
|
tenantId: r
|
|
302
323
|
} : i ? {
|
|
303
324
|
kind: "tables",
|
|
304
|
-
tables:
|
|
325
|
+
tables: J(i)
|
|
305
326
|
} : { kind: "all" };
|
|
306
|
-
},
|
|
327
|
+
}, Le = async () => {
|
|
307
328
|
let e = await u(process.cwd());
|
|
308
|
-
return
|
|
309
|
-
},
|
|
310
|
-
let e = (await u(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id ===
|
|
329
|
+
return ae(h(e));
|
|
330
|
+
}, Re = "voltro/tenant", ze = async () => {
|
|
331
|
+
let e = (await u(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id === Re)).map((e) => e.tableName);
|
|
311
332
|
return e.length > 0 ? e : void 0;
|
|
312
|
-
},
|
|
333
|
+
}, Be = (e) => {
|
|
313
334
|
for (let t of e.tables) if (t.samples.length !== 0) {
|
|
314
335
|
console.log(`\n ${t.table}`);
|
|
315
336
|
for (let e of t.samples) console.log(` ${e.column} [${e.action}] ${JSON.stringify(e.before)} → ${JSON.stringify(e.after)}`);
|
|
@@ -322,8 +343,8 @@ var A = class extends Error {
|
|
|
322
343
|
console.log("\n ✗ unclassified (would BLOCK a real export — add .sensitive()/.safe() or a policy override):");
|
|
323
344
|
for (let t of e.unclassified) console.log(` ${t}`);
|
|
324
345
|
} else console.log("\n ✓ every exported column is classified — safe to run.");
|
|
325
|
-
},
|
|
326
|
-
let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a =
|
|
346
|
+
}, Ve = async (t) => {
|
|
347
|
+
let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = R(t.args, "--chunk-size"), o = a === void 0 ? be : Math.round(Number(a) * 1024 * 1024);
|
|
327
348
|
if (!Number.isFinite(o) || o <= 0) throw new e(`--chunk-size expects a positive number of megabytes, got '${String(a)}'`);
|
|
328
349
|
let s = t.stream[Symbol.asyncIterator](), c = [], l = 0, u = !1;
|
|
329
350
|
for (; l <= o;) {
|
|
@@ -338,11 +359,11 @@ var A = class extends Error {
|
|
|
338
359
|
kind: "small",
|
|
339
360
|
body: Buffer.concat(c)
|
|
340
361
|
};
|
|
341
|
-
let d = n(process.cwd(), t.bundleDir), f =
|
|
362
|
+
let d = n(process.cwd(), t.bundleDir), f = Se(G(t.bundleDir) ? await i.readFile(d) : await i.readFile(r(d, "manifest.json"))), p = `up-${f.slice(0, 32)}`, m = await M(t.url, Buffer.alloc(0), {
|
|
342
363
|
...t.headers,
|
|
343
364
|
"x-import-upload-id": p,
|
|
344
365
|
"x-import-probe": "1"
|
|
345
|
-
},
|
|
366
|
+
}, Z(t.args)), h = m.status === 200 ? Number(JSON.parse(m.body.toString("utf8")).bytes ?? 0) : 0;
|
|
346
367
|
h > 0 && t.log.info("api import: resuming a chunked upload the instance already holds", { bytes: h });
|
|
347
368
|
let g = (async function* () {
|
|
348
369
|
for (let e of c) yield e;
|
|
@@ -351,7 +372,7 @@ var A = class extends Error {
|
|
|
351
372
|
if (e.done === !0) return;
|
|
352
373
|
yield e.value;
|
|
353
374
|
}
|
|
354
|
-
})(), _ = 0, v = await
|
|
375
|
+
})(), _ = 0, v = await Ce({
|
|
355
376
|
stream: g,
|
|
356
377
|
chunkBytes: o,
|
|
357
378
|
uploadId: p,
|
|
@@ -365,7 +386,7 @@ var A = class extends Error {
|
|
|
365
386
|
t.log.info("api import: upload done — the instance is importing now. This request stays open until it finishes, which on a full bundle is minutes, and nothing more is printed until then. It is not a hang; the instance logs the import itself.");
|
|
366
387
|
},
|
|
367
388
|
target: { send: async (e, n) => {
|
|
368
|
-
let r = await
|
|
389
|
+
let r = await M(t.url, e, n, Z(t.args)), i = {};
|
|
369
390
|
try {
|
|
370
391
|
i = JSON.parse(r.body.toString("utf8") || "{}");
|
|
371
392
|
} catch {}
|
|
@@ -387,59 +408,60 @@ var A = class extends Error {
|
|
|
387
408
|
body: Buffer.from(JSON.stringify(v.body))
|
|
388
409
|
}
|
|
389
410
|
};
|
|
390
|
-
},
|
|
391
|
-
let n =
|
|
411
|
+
}, Y = (e) => `--mode replace with --no-atomic against a ${e ? "RUNNING instance" : "live database"}: the target is empty between the delete and the load, and anything the application writes in that window can collide with a row from the bundle — or survive the import as a row the bundle never carried. Drop --no-atomic to run the whole load in one transaction (concurrent writers then wait instead of racing), or stop the traffic for the duration.`, X = (e, t) => e.includes("--no-atomic") ? !1 : e.includes("--atomic") || t === "replace", Z = (t) => {
|
|
412
|
+
let n = R(t, "--timeout");
|
|
392
413
|
if (n === void 0) return {};
|
|
393
414
|
let r = Number(n);
|
|
394
415
|
if (!Number.isFinite(r) || r <= 0) throw new e(`--timeout expects a positive number of seconds, got '${n}'`);
|
|
395
416
|
return { timeoutMs: Math.round(r * 1e3) };
|
|
396
|
-
},
|
|
397
|
-
let t =
|
|
398
|
-
if (!r && !(t && n)) return
|
|
417
|
+
}, He = async (e) => {
|
|
418
|
+
let t = R(e, "--target") === "api", n = R(e, "--bundle-key"), r = z(e);
|
|
419
|
+
if (!r && !(t && n)) return I.error("usage: voltro data export <outDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--timeout <seconds>] [--profile <name>] [--tenant <id>] [--tables a,b] [--exclude a,b] [--bundle-key <storage-key>] [--assets] [--compression zstd|gzip|none] [--consistency live|snapshot] [--dry-run]"), 1;
|
|
399
420
|
if (t) {
|
|
400
|
-
let t =
|
|
401
|
-
if (!t) return
|
|
402
|
-
let i =
|
|
403
|
-
if (!i) return
|
|
404
|
-
if (
|
|
405
|
-
let a =
|
|
406
|
-
...
|
|
407
|
-
...
|
|
421
|
+
let t = R(e, "--api-url");
|
|
422
|
+
if (!t) return I.error("--target api requires --api-url <url> (the running instance)"), 1;
|
|
423
|
+
let i = R(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
|
|
424
|
+
if (!i) return I.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
|
|
425
|
+
if (R(e, "--exclude") !== void 0) return I.error("--exclude is a direct-target flag: it expands against the live table list, which --target api does not expose. Use --tables with the explicit set."), 1;
|
|
426
|
+
let a = R(e, "--tables"), o = {
|
|
427
|
+
...R(e, "--profile") ? { profile: R(e, "--profile") } : {},
|
|
428
|
+
...R(e, "--tenant") ? { tenant: R(e, "--tenant") } : {},
|
|
408
429
|
...a ? { tables: a.split(",").map((e) => e.trim()).filter(Boolean) } : {},
|
|
409
|
-
...
|
|
410
|
-
...
|
|
430
|
+
...R(e, "--consistency") === "snapshot" ? { consistency: "snapshot" } : {},
|
|
431
|
+
...R(e, "--compression") ? { compression: R(e, "--compression") } : {},
|
|
411
432
|
...e.includes("--assets") ? { assets: !0 } : {},
|
|
412
433
|
...n ? { bundleKey: n } : {}
|
|
413
434
|
}, s;
|
|
414
435
|
try {
|
|
415
|
-
s = await
|
|
436
|
+
s = await M(`${t.replace(/\/+$/, "")}/_voltro/admin/export`, JSON.stringify(o), {
|
|
416
437
|
"content-type": "application/json",
|
|
417
438
|
authorization: `Bearer ${i}`
|
|
418
|
-
},
|
|
439
|
+
}, Z(e));
|
|
419
440
|
} catch (e) {
|
|
420
|
-
if (e instanceof
|
|
441
|
+
if (e instanceof j) return I.error(`api export: the instance did not answer within ${Math.round(e.elapsedMs / 1e3)}s. That is a client-side deadline, not a server failure — the response arrives only when the export does. Nothing was written locally; re-run, or pass --timeout <seconds> (the default is to wait).`), 1;
|
|
442
|
+
if (e instanceof A) return I.error(`api export: ${ve(e)}`), 1;
|
|
421
443
|
throw e;
|
|
422
444
|
}
|
|
423
|
-
if (s.status < 200 || s.status >= 300) return
|
|
424
|
-
if (n) return
|
|
445
|
+
if (s.status < 200 || s.status >= 300) return I.error(`api export failed: HTTP ${s.status} — ${s.body.toString("utf8")}`), 1;
|
|
446
|
+
if (n) return I.info("api export complete (pushed to storage)", {
|
|
425
447
|
via: t,
|
|
426
448
|
...JSON.parse(s.body.toString("utf8"))
|
|
427
449
|
}), 0;
|
|
428
450
|
let { resolve: c } = await import("node:path"), l = new Uint8Array(s.body);
|
|
429
451
|
return await k((async function* () {
|
|
430
452
|
yield l;
|
|
431
|
-
})(), c(process.cwd(), r)),
|
|
453
|
+
})(), c(process.cwd(), r)), I.info("api export complete (downloaded from the running instance)", {
|
|
432
454
|
outDir: r,
|
|
433
455
|
tables: s.headers["x-export-tables"],
|
|
434
456
|
rows: s.headers["x-export-rows"],
|
|
435
457
|
masked: s.headers["x-export-masked"]
|
|
436
458
|
}), 0;
|
|
437
459
|
}
|
|
438
|
-
if (!r) return
|
|
439
|
-
let i =
|
|
460
|
+
if (!r) return I.error("a <outDir> is required for a direct export"), 1;
|
|
461
|
+
let i = R(e, "--profile"), a = i ? await s(i, { allowPath: !0 }) : {}, { store: o, snapshot: c, dialect: l } = await U(), u = e.includes("--assets") || a.assets === !0, d = u ? S({}) : void 0, p = R(e, "--compression") ?? a.compression, m = R(e, "--consistency") === "snapshot" ? "snapshot" : a.consistency ?? "live", h = a.masking, g = a.subset, _ = a.scope ?? Ie(e, c.tables.map((e) => e.name)), v = h ? await Le() : void 0, y = _.kind === "tenant" ? await ze() : void 0, b = y ? { tenantTables: y } : {};
|
|
440
462
|
if (e.includes("--dry-run")) {
|
|
441
463
|
let e = _.kind === "tables" && _.tables ? [..._.tables] : c.tables.map((e) => e.name);
|
|
442
|
-
if (
|
|
464
|
+
if (I.info("export DRY RUN — nothing was written", {
|
|
443
465
|
outDir: r,
|
|
444
466
|
tables: e.length,
|
|
445
467
|
scope: _.kind,
|
|
@@ -447,8 +469,8 @@ var A = class extends Error {
|
|
|
447
469
|
compression: p ?? "zstd",
|
|
448
470
|
assets: u,
|
|
449
471
|
masking: h ? "profile policy applies" : "none"
|
|
450
|
-
}),
|
|
451
|
-
let t = await f.runPromise(
|
|
472
|
+
}), I.info(`would export: ${e.join(", ")}`), !h) return 0;
|
|
473
|
+
let t = await f.runPromise(se({
|
|
452
474
|
store: o,
|
|
453
475
|
snapshot: c,
|
|
454
476
|
masking: h,
|
|
@@ -458,12 +480,12 @@ var A = class extends Error {
|
|
|
458
480
|
...b
|
|
459
481
|
}
|
|
460
482
|
}));
|
|
461
|
-
return
|
|
483
|
+
return Be(t), +(t.unclassified.length > 0);
|
|
462
484
|
}
|
|
463
|
-
if (
|
|
485
|
+
if (G(r)) {
|
|
464
486
|
let t = await import("node:os"), n = await import("node:fs/promises"), { resolve: i, join: a } = await import("node:path"), s = await n.mkdtemp(a(t.tmpdir(), "voltro-export-"));
|
|
465
487
|
try {
|
|
466
|
-
let t = await f.runPromise(
|
|
488
|
+
let t = await f.runPromise(ce({
|
|
467
489
|
store: o,
|
|
468
490
|
snapshot: c,
|
|
469
491
|
dialect: l,
|
|
@@ -479,14 +501,14 @@ var A = class extends Error {
|
|
|
479
501
|
onProgress: L
|
|
480
502
|
})), n = T({
|
|
481
503
|
dir: s,
|
|
482
|
-
...d ? { blobs:
|
|
483
|
-
}), a =
|
|
504
|
+
...d ? { blobs: pe(d, () => W(o)) } : {}
|
|
505
|
+
}), a = Pe(e);
|
|
484
506
|
if (a) {
|
|
485
|
-
let t =
|
|
486
|
-
if (!t) return
|
|
487
|
-
n =
|
|
507
|
+
let t = K(e);
|
|
508
|
+
if (!t) return I.error("--encrypt requires --passphrase <p> or VOLTRO_BUNDLE_KEY (a dedicated key, NOT the transfer secret)"), 1;
|
|
509
|
+
n = oe(n, t);
|
|
488
510
|
}
|
|
489
|
-
return await
|
|
511
|
+
return await Fe(n, i(process.cwd(), r)), I.info("export complete (single file)", {
|
|
490
512
|
file: r,
|
|
491
513
|
encrypted: a,
|
|
492
514
|
assets: u,
|
|
@@ -500,7 +522,7 @@ var A = class extends Error {
|
|
|
500
522
|
});
|
|
501
523
|
}
|
|
502
524
|
}
|
|
503
|
-
let x = await f.runPromise(
|
|
525
|
+
let x = await f.runPromise(ce({
|
|
504
526
|
store: o,
|
|
505
527
|
snapshot: c,
|
|
506
528
|
dialect: l,
|
|
@@ -513,10 +535,10 @@ var A = class extends Error {
|
|
|
513
535
|
...p ? { compression: p } : {},
|
|
514
536
|
...h ? { masking: h } : {},
|
|
515
537
|
...v ? { classification: v } : {},
|
|
516
|
-
...d ? { assets:
|
|
538
|
+
...d ? { assets: de(d, () => W(o)) } : {},
|
|
517
539
|
onProgress: L
|
|
518
540
|
}));
|
|
519
|
-
return
|
|
541
|
+
return I.info("export complete", {
|
|
520
542
|
outDir: r,
|
|
521
543
|
tables: x.tables.length,
|
|
522
544
|
rows: x.tables.reduce((e, t) => e + t.rowCount, 0),
|
|
@@ -524,13 +546,13 @@ var A = class extends Error {
|
|
|
524
546
|
compression: x.compression,
|
|
525
547
|
...x.masking ? { maskedColumns: x.masking.transformed.length } : {}
|
|
526
548
|
}), 0;
|
|
527
|
-
},
|
|
528
|
-
let t =
|
|
549
|
+
}, Ue = () => "dry run checked: schema fit, cross-dialect portability, mode legality, the table selection. It did NOT read per-table checksums (those stream during the load) and cannot see conflicts that depend on the target's current rows.", We = (e) => {
|
|
550
|
+
let t = R(e, "--tables");
|
|
529
551
|
return {
|
|
530
552
|
...t ? { tables: t.split(",").map((e) => e.trim()).filter(Boolean) } : {},
|
|
531
553
|
...e.includes("--dry-run") ? { dryRun: !0 } : {}
|
|
532
554
|
};
|
|
533
|
-
},
|
|
555
|
+
}, Ge = (e) => ({
|
|
534
556
|
tables: e.tablesWritten,
|
|
535
557
|
rows: e.rowsWritten,
|
|
536
558
|
carried: e.manifest.tables.reduce((e, t) => e + t.rowCount, 0),
|
|
@@ -538,105 +560,146 @@ var A = class extends Error {
|
|
|
538
560
|
skippedTables: e.tablesSkipped,
|
|
539
561
|
skippedRows: e.rowsSkipped
|
|
540
562
|
} : {}
|
|
541
|
-
}),
|
|
563
|
+
}), Ke = (e, t) => {
|
|
542
564
|
if (e.includes("--dry-run")) {
|
|
543
|
-
|
|
565
|
+
I.info("import DRY RUN — nothing was written", t), I.info(Ue());
|
|
544
566
|
return;
|
|
545
567
|
}
|
|
546
568
|
if (t.rows === 0 && typeof t.skippedRows == "number" && t.skippedRows !== 0) {
|
|
547
|
-
|
|
569
|
+
I.info("import complete — 0 rows written, every table was already applied by an earlier run of this bundle directory", t);
|
|
548
570
|
return;
|
|
549
571
|
}
|
|
550
|
-
|
|
551
|
-
},
|
|
552
|
-
let t = e;
|
|
553
|
-
if (t?._tag !== "RowsRefusedError" || !Array.isArray(t.rows)) return;
|
|
572
|
+
I.info("import complete", t);
|
|
573
|
+
}, qe = Symbol("refused"), Q = (e) => {
|
|
574
|
+
let t = ne(e) ?? e;
|
|
575
|
+
if (t?._tag !== "RowsRefusedError" || !Array.isArray(t.rows)) return !1;
|
|
554
576
|
let n = t.rows, r = n.filter((e) => !e.derived), i = Number(t.totalCount ?? n.length), a = Number(t.primaryCount ?? r.length);
|
|
555
|
-
|
|
577
|
+
I.error(`import refused ${i} row(s), of which ${a} are the actual failures — the rest could not land because a row they reference did not.`);
|
|
556
578
|
let o = Array.isArray(t.byTable) ? t.byTable : [];
|
|
557
|
-
o.length > 1 &&
|
|
558
|
-
for (let e of r)
|
|
559
|
-
n.length < i &&
|
|
579
|
+
o.length > 1 && I.error(` by table: ${o.map((e) => `${e.table} ${e.refused}`).join(", ")}`);
|
|
580
|
+
for (let e of r) I.error(` ${e.table} ${e.id} ${e.reason}`);
|
|
581
|
+
n.length < i && I.error(` (the list above is capped at ${n.length} of ${i} — the counts by table are complete)`);
|
|
560
582
|
let s = i - a;
|
|
561
|
-
s > 0 &&
|
|
562
|
-
},
|
|
563
|
-
let t =
|
|
564
|
-
|
|
565
|
-
|
|
583
|
+
return s > 0 && I.error(` … and ${s} row(s) behind them. Fix the ${a} above and re-run; they resolve with their parents.`), !0;
|
|
584
|
+
}, Je = (e) => {
|
|
585
|
+
let t = typeof e == "string" ? e : e.toString("utf8"), n = t;
|
|
586
|
+
try {
|
|
587
|
+
let e = JSON.parse(t), r = e.error ?? e.message;
|
|
588
|
+
typeof r == "string" && (n = r);
|
|
589
|
+
} catch {}
|
|
590
|
+
let r = n.indexOf("RowsRefusedError");
|
|
591
|
+
if (r < 0) return;
|
|
592
|
+
let i = n.indexOf("{", r);
|
|
593
|
+
if (i < 0) return;
|
|
594
|
+
let a = 0, o = !1, s = !1;
|
|
595
|
+
for (let e = i; e < n.length; e++) {
|
|
596
|
+
let t = n[e];
|
|
597
|
+
if (o) {
|
|
598
|
+
if (s) {
|
|
599
|
+
s = !1;
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (t === "\\") {
|
|
603
|
+
s = !0;
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
t === "\"" && (o = !1);
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (t === "\"") {
|
|
610
|
+
o = !0;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (t === "{") a++;
|
|
614
|
+
else if (t === "}" && --a === 0) try {
|
|
615
|
+
let t = JSON.parse(n.slice(i, e + 1));
|
|
616
|
+
return Array.isArray(t.rows) ? {
|
|
617
|
+
_tag: "RowsRefusedError",
|
|
618
|
+
...t
|
|
619
|
+
} : void 0;
|
|
620
|
+
} catch {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}, Ye = async (e) => {
|
|
625
|
+
let t = R(e, "--target") === "api", n = R(e, "--bundle-key"), i = z(e);
|
|
626
|
+
if (!i && !(t && n)) return I.error("usage: voltro data import <bundleDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--timeout <seconds>] [--bundle-key <storage-key>] [--tables a,b] [--mode upsert|append|replace] [--on-conflict skip|fail] [--atomic] [--assets] [--no-verify] [--force] [--allow-live] [--dry-run] (--force: import despite schema drift AND cross-dialect warnings; --dry-run: report what would move, write nothing)"), 1;
|
|
627
|
+
let a = R(e, "--mode") ?? "upsert", o = R(e, "--on-conflict") ?? "skip";
|
|
566
628
|
if (t) {
|
|
567
|
-
let t =
|
|
568
|
-
if (!t) return
|
|
569
|
-
let r =
|
|
570
|
-
if (!r) return
|
|
571
|
-
let s =
|
|
629
|
+
let t = R(e, "--api-url");
|
|
630
|
+
if (!t) return I.error("--target api requires --api-url <url> (the running instance)"), 1;
|
|
631
|
+
let r = R(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
|
|
632
|
+
if (!r) return I.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
|
|
633
|
+
let s = R(e, "--tables"), c = {
|
|
572
634
|
authorization: `Bearer ${r}`,
|
|
573
635
|
"x-import-mode": a,
|
|
574
636
|
"x-import-on-conflict": o,
|
|
575
|
-
...
|
|
637
|
+
...X(e, a) ? { "x-import-atomic": "1" } : {},
|
|
576
638
|
...e.includes("--no-atomic") ? { "x-import-atomic": "0" } : {},
|
|
577
639
|
...e.includes("--force") ? { "x-import-force": "1" } : {},
|
|
578
640
|
...e.includes("--dry-run") ? { "x-import-dry-run": "1" } : {},
|
|
579
641
|
...s ? { "x-import-tables": s } : {}
|
|
580
642
|
}, { resolve: l } = await import("node:path"), u;
|
|
581
643
|
if (n) c["content-type"] = "application/json", u = JSON.stringify({ bundleKey: n });
|
|
582
|
-
else if (
|
|
583
|
-
let t =
|
|
644
|
+
else if (G(i)) {
|
|
645
|
+
let t = q(l(process.cwd(), i)), n = K(e);
|
|
584
646
|
n && (t = w(t, n)), c["content-type"] = "application/octet-stream", u = t;
|
|
585
|
-
} else await (await import("node:fs/promises")).access(l(process.cwd(), i, "assets", "index.ndjson")).then(() => !0, () => !1) &&
|
|
586
|
-
a === "replace" && !
|
|
647
|
+
} else await (await import("node:fs/promises")).access(l(process.cwd(), i, "assets", "index.ndjson")).then(() => !0, () => !1) && I.warn("this directory bundle has assets; --target api uploads DATA ONLY. Pack a single-file `.vbundle` (voltro data export <name>.vbundle --assets) to transfer assets over the API."), c["content-type"] = "application/octet-stream", u = T({ dir: l(process.cwd(), i) });
|
|
648
|
+
a === "replace" && !X(e, a) && I.warn(Y(!0));
|
|
587
649
|
let d = `${t.replace(/\/+$/, "")}/_voltro/admin/import`, f;
|
|
588
650
|
try {
|
|
589
|
-
let t = typeof u == "string" ? null : await
|
|
651
|
+
let t = typeof u == "string" ? null : await Ve({
|
|
590
652
|
stream: u,
|
|
591
653
|
url: d,
|
|
592
654
|
headers: c,
|
|
593
655
|
bundleDir: i,
|
|
594
656
|
args: e,
|
|
595
|
-
log:
|
|
657
|
+
log: I
|
|
596
658
|
});
|
|
597
|
-
(t === null || t.kind === "small") &&
|
|
659
|
+
(t === null || t.kind === "small") && I.info("api import: uploading the bundle — this request stays open until the instance has imported it"), f = t === null ? await M(d, u, c, Z(e)) : t.kind === "sent" ? t.res : await M(d, t.body, c, Z(e));
|
|
598
660
|
} catch (e) {
|
|
599
|
-
if (e instanceof
|
|
661
|
+
if (e instanceof j) return I.error(`api import: ${ye(e, a)}`), 1;
|
|
662
|
+
if (e instanceof A) return I.error(`api import: ${ve(e)}`), 1;
|
|
600
663
|
throw e;
|
|
601
664
|
}
|
|
602
|
-
if (f.status < 200 || f.status >= 300) return
|
|
665
|
+
if (f.status < 200 || f.status >= 300) return Q(Je(f.body)) || I.error(`api import failed: HTTP ${f.status} — ${f.body.toString("utf8")}`), 1;
|
|
603
666
|
let p = JSON.parse(f.body.toString("utf8"));
|
|
604
|
-
return e.includes("--dry-run") ? (
|
|
667
|
+
return e.includes("--dry-run") ? (I.info("api import DRY RUN — nothing was written", {
|
|
605
668
|
via: t,
|
|
606
669
|
...p
|
|
607
|
-
}),
|
|
670
|
+
}), I.info(Ue()), 0) : (I.info("api import complete (imported by the running instance)", {
|
|
608
671
|
via: t,
|
|
609
672
|
...p
|
|
610
673
|
}), 0);
|
|
611
674
|
}
|
|
612
|
-
if (!i) return
|
|
613
|
-
if (!await
|
|
614
|
-
a === "replace" && e.includes("--allow-live") && !
|
|
615
|
-
let { store: s, snapshot: c } = await
|
|
616
|
-
if (
|
|
675
|
+
if (!i) return I.error("a <bundleDir> is required for a direct import"), 1;
|
|
676
|
+
if (!await je(e)) return 1;
|
|
677
|
+
a === "replace" && e.includes("--allow-live") && !X(e, a) && I.warn(Y(!1));
|
|
678
|
+
let { store: s, snapshot: c } = await U(), l = e.includes("--force"), u = (e) => I.warn(e), d = e.includes("--assets") ? S({}) : void 0, p = B() === "postgres" ? (await import("@voltro/sql-postgres")).makePgCopySession(r()) : void 0;
|
|
679
|
+
if (G(i)) {
|
|
617
680
|
let t = await import("node:os"), n = await import("node:fs/promises"), { resolve: r, join: m } = await import("node:path"), h = await n.mkdtemp(m(t.tmpdir(), "voltro-import-"));
|
|
618
681
|
try {
|
|
619
|
-
let t =
|
|
620
|
-
return n && (t = w(t, n)), await
|
|
682
|
+
let t = q(r(process.cwd(), i)), n = K(e);
|
|
683
|
+
return n && (t = w(t, n)), await me(t, {
|
|
621
684
|
dir: h,
|
|
622
|
-
blobs:
|
|
623
|
-
}),
|
|
685
|
+
blobs: fe(d ?? S({}))
|
|
686
|
+
}), Ke(e, {
|
|
624
687
|
file: i,
|
|
625
|
-
...
|
|
688
|
+
...Ge(await f.runPromise(le({
|
|
626
689
|
store: s,
|
|
627
690
|
bundleDir: h,
|
|
628
691
|
mode: a,
|
|
629
692
|
onConflict: o,
|
|
630
|
-
atomic:
|
|
693
|
+
atomic: X(e, a),
|
|
631
694
|
verify: !e.includes("--no-verify"),
|
|
632
|
-
targetDialect:
|
|
695
|
+
targetDialect: B(),
|
|
633
696
|
allowIncompatible: l,
|
|
634
697
|
targetSnapshot: c,
|
|
635
698
|
force: l,
|
|
636
699
|
onWarn: u,
|
|
637
700
|
onProgress: L,
|
|
638
701
|
assetsAlreadyRestored: !0,
|
|
639
|
-
...
|
|
702
|
+
...We(e),
|
|
640
703
|
...p ? { copyLoader: p } : {}
|
|
641
704
|
})))
|
|
642
705
|
}), 0;
|
|
@@ -647,31 +710,32 @@ var A = class extends Error {
|
|
|
647
710
|
});
|
|
648
711
|
}
|
|
649
712
|
}
|
|
650
|
-
let m = await f.runPromise(
|
|
713
|
+
let m = await f.runPromise(le({
|
|
651
714
|
store: s,
|
|
652
715
|
bundleDir: i,
|
|
653
716
|
mode: a,
|
|
654
717
|
onConflict: o,
|
|
655
|
-
atomic:
|
|
718
|
+
atomic: X(e, a),
|
|
656
719
|
verify: !e.includes("--no-verify"),
|
|
657
|
-
targetDialect:
|
|
720
|
+
targetDialect: B(),
|
|
658
721
|
allowIncompatible: l,
|
|
659
722
|
targetSnapshot: c,
|
|
660
723
|
force: l,
|
|
661
724
|
onWarn: u,
|
|
662
725
|
onProgress: L,
|
|
663
|
-
...
|
|
664
|
-
...d ? { assets:
|
|
726
|
+
...We(e),
|
|
727
|
+
...d ? { assets: ue(d) } : {},
|
|
665
728
|
...p ? { copyLoader: p } : {}
|
|
666
729
|
})).catch((e) => {
|
|
667
|
-
|
|
730
|
+
if (Q(e)) return qe;
|
|
731
|
+
throw e;
|
|
668
732
|
}).finally(() => p?.close());
|
|
669
|
-
return
|
|
733
|
+
return typeof m == "symbol" ? 1 : (Ke(e, {
|
|
670
734
|
bundleDir: i,
|
|
671
|
-
...
|
|
735
|
+
...Ge(m),
|
|
672
736
|
assets: m.manifest.assets.count
|
|
673
|
-
}), 0;
|
|
674
|
-
},
|
|
737
|
+
}), 0);
|
|
738
|
+
}, Xe = () => {
|
|
675
739
|
let e = r();
|
|
676
740
|
return {
|
|
677
741
|
...e.url ? { url: e.url } : {},
|
|
@@ -681,75 +745,75 @@ var A = class extends Error {
|
|
|
681
745
|
...e.password ? { password: e.password } : {},
|
|
682
746
|
...e.database ? { database: e.database } : {}
|
|
683
747
|
};
|
|
684
|
-
},
|
|
685
|
-
let t =
|
|
686
|
-
if (!t) return
|
|
687
|
-
let n =
|
|
748
|
+
}, Ze = async (e) => {
|
|
749
|
+
let t = z(e);
|
|
750
|
+
if (!t) return I.error("usage: voltro data backup <outDir> (native pg_dump/mysqldump/… + consistent artifact)"), 1;
|
|
751
|
+
let n = B(), { mkdir: r, writeFile: i } = await import("node:fs/promises"), { join: a } = await import("node:path");
|
|
688
752
|
await r(t, { recursive: !0 });
|
|
689
753
|
let o = a(t, C(n));
|
|
690
|
-
await f.runPromise(O(
|
|
691
|
-
let s =
|
|
754
|
+
await f.runPromise(O(ie(n, Xe(), o)));
|
|
755
|
+
let s = we({
|
|
692
756
|
dialect: n,
|
|
693
|
-
schemaFingerprint: await
|
|
694
|
-
voltroVersion: await
|
|
757
|
+
schemaFingerprint: await ke(n),
|
|
758
|
+
voltroVersion: await Oe(),
|
|
695
759
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
696
760
|
});
|
|
697
|
-
return await i(a(t,
|
|
761
|
+
return await i(a(t, N), `${JSON.stringify(s, null, 2)}\n`, "utf8"), I.info("native backup complete", {
|
|
698
762
|
dialect: n,
|
|
699
763
|
artifact: o,
|
|
700
|
-
stamp:
|
|
764
|
+
stamp: N,
|
|
701
765
|
schemaFingerprint: s.schemaFingerprint ?? "<none>",
|
|
702
766
|
assets: "NOT included — use `voltro data export --assets` for rows + blobs"
|
|
703
767
|
}), 0;
|
|
704
|
-
},
|
|
705
|
-
let n =
|
|
706
|
-
flagUrl:
|
|
768
|
+
}, Qe = async (e, t) => {
|
|
769
|
+
let n = B(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(e, C(n)), o = Ee({
|
|
770
|
+
flagUrl: R(t, "--drill-url"),
|
|
707
771
|
envUrl: process.env.DRILL_DB_URL,
|
|
708
|
-
liveKeys:
|
|
772
|
+
liveKeys: Ne()
|
|
709
773
|
});
|
|
710
|
-
if (!o.ok) return
|
|
711
|
-
let s = await i(r(e,
|
|
712
|
-
if (s && s.dialect !== n) return
|
|
713
|
-
|
|
774
|
+
if (!o.ok) return I.error(`restore --drill: ${o.error}`), 1;
|
|
775
|
+
let s = await i(r(e, N), "utf8").then(P, () => null);
|
|
776
|
+
if (s && s.dialect !== n) return I.error(`restore --drill: backup is a ${s.dialect} dump but DB_DIALECT is ${n} — cross-dialect restore is never valid.`), 1;
|
|
777
|
+
I.info("restore drill: restoring into throwaway DB", {
|
|
714
778
|
dialect: n,
|
|
715
|
-
target:
|
|
779
|
+
target: F(o.url)
|
|
716
780
|
}), await f.runPromise(O(D(n, { url: o.url }, a)));
|
|
717
781
|
let c = null, l = 0;
|
|
718
782
|
try {
|
|
719
|
-
let e = await
|
|
783
|
+
let e = await V(n, { url: o.url });
|
|
720
784
|
c = g(e), l = e.tables.length;
|
|
721
785
|
} catch (e) {
|
|
722
|
-
return
|
|
786
|
+
return I.error(`restore --drill: could not introspect the restored DB — ${String(e)}`), 1;
|
|
723
787
|
}
|
|
724
|
-
let u =
|
|
788
|
+
let u = De({
|
|
725
789
|
stampFingerprint: s?.schemaFingerprint ?? null,
|
|
726
790
|
restoredFingerprint: c,
|
|
727
791
|
restoredTableCount: l
|
|
728
792
|
});
|
|
729
|
-
for (let e of u.lines) u.pass ?
|
|
793
|
+
for (let e of u.lines) u.pass ? I.info(`restore drill: ${e}`) : I.error(`restore drill: ${e}`);
|
|
730
794
|
return +!u.pass;
|
|
731
|
-
},
|
|
732
|
-
let t =
|
|
733
|
-
if (!t) return
|
|
734
|
-
if (e.includes("--drill")) return
|
|
735
|
-
if (!await
|
|
736
|
-
let n =
|
|
737
|
-
stamp: await i(r(t,
|
|
795
|
+
}, $e = async (e) => {
|
|
796
|
+
let t = z(e);
|
|
797
|
+
if (!t) return I.error("usage: voltro data restore <backupDir> [--allow-live] [--api-url <url>] [--force] [--drill [--drill-url <url>]] (native pg_restore/mysql/…)"), 1;
|
|
798
|
+
if (e.includes("--drill")) return Qe(t, e);
|
|
799
|
+
if (!await je(e)) return 1;
|
|
800
|
+
let n = B(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t, C(n)), o = Te({
|
|
801
|
+
stamp: await i(r(t, N), "utf8").then(P, () => null),
|
|
738
802
|
currentDialect: n,
|
|
739
|
-
currentFingerprint: await
|
|
803
|
+
currentFingerprint: await ke(n)
|
|
740
804
|
});
|
|
741
|
-
for (let e of o.info)
|
|
742
|
-
if (o.warn &&
|
|
743
|
-
if (!e.includes("--force")) return
|
|
744
|
-
|
|
805
|
+
for (let e of o.info) I.info(`backup stamp: ${e}`);
|
|
806
|
+
if (o.warn && I.warn(o.warn), o.refuse) {
|
|
807
|
+
if (!e.includes("--force")) return I.error(`restore refused: ${o.refuse} (pass --force to override)`), 1;
|
|
808
|
+
I.warn(`restore proceeding despite: ${o.refuse} (--force)`);
|
|
745
809
|
}
|
|
746
|
-
return await f.runPromise(O(D(n,
|
|
810
|
+
return await f.runPromise(O(D(n, Xe(), a))), I.info("native restore complete", {
|
|
747
811
|
dialect: n,
|
|
748
812
|
artifact: a
|
|
749
813
|
}), 0;
|
|
750
|
-
},
|
|
751
|
-
let t =
|
|
752
|
-
if (!t) return
|
|
814
|
+
}, et = async (e) => {
|
|
815
|
+
let t = z(e);
|
|
816
|
+
if (!t) return I.error("usage: voltro data inspect <file.vbundle|dir> [--json] [--passphrase <p>]"), 1;
|
|
753
817
|
let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = n(process.cwd(), t);
|
|
754
818
|
if ((await i.stat(a).catch(() => void 0))?.isDirectory()) {
|
|
755
819
|
let n = r(a, "manifest.json"), o = r(a, "README.md");
|
|
@@ -758,25 +822,25 @@ var A = class extends Error {
|
|
|
758
822
|
if (e !== void 0) return console.log(e), 0;
|
|
759
823
|
}
|
|
760
824
|
let s = await i.readFile(n, "utf8").catch(() => void 0);
|
|
761
|
-
return s === void 0 ? (
|
|
825
|
+
return s === void 0 ? (I.error(`'${t}' is a directory but holds no manifest.json — is it an unpacked bundle? (a bundle exported with --target api is always unpacked into a directory, even when the path ends in .vbundle)`), 1) : (console.log(s), 0);
|
|
762
826
|
}
|
|
763
|
-
let o =
|
|
764
|
-
let e =
|
|
827
|
+
let o = K(e), s = () => {
|
|
828
|
+
let e = q(a);
|
|
765
829
|
return o ? w(e, o) : e;
|
|
766
830
|
};
|
|
767
831
|
if (e.includes("--json")) {
|
|
768
832
|
let e = (await E(s(), ["manifest.json"])).get("manifest.json");
|
|
769
|
-
return e ? (console.log(e.toString("utf8")), 0) : (
|
|
833
|
+
return e ? (console.log(e.toString("utf8")), 0) : (I.error("no manifest.json found in the bundle"), 1);
|
|
770
834
|
}
|
|
771
835
|
let c = (await E(s(), ["README.md"])).get("README.md");
|
|
772
836
|
if (c) return console.log(c.toString("utf8")), 0;
|
|
773
837
|
let l = (await E(s(), ["manifest.json"])).get("manifest.json");
|
|
774
|
-
return l ? (console.log(l.toString("utf8")), 0) : (
|
|
775
|
-
},
|
|
776
|
-
let t =
|
|
777
|
-
if (!t || !n) return
|
|
778
|
-
let { resolve: r } = await import("node:path"), i =
|
|
779
|
-
return a && (i = w(i, a)), await k(i, r(process.cwd(), n)),
|
|
838
|
+
return l ? (console.log(l.toString("utf8")), 0) : (I.error("bundle has no README.md or manifest.json"), 1);
|
|
839
|
+
}, tt = async (e) => {
|
|
840
|
+
let t = z(e), n = z(e.filter((e) => e !== t));
|
|
841
|
+
if (!t || !n) return I.error("usage: voltro data unpack <file.vbundle> <dir> [--passphrase <p>]"), 1;
|
|
842
|
+
let { resolve: r } = await import("node:path"), i = q(r(process.cwd(), t)), a = K(e);
|
|
843
|
+
return a && (i = w(i, a)), await k(i, r(process.cwd(), n)), I.info("unpack complete", {
|
|
780
844
|
file: t,
|
|
781
845
|
dir: n
|
|
782
846
|
}), 0;
|
|
@@ -842,13 +906,13 @@ var A = class extends Error {
|
|
|
842
906
|
"--drill-url",
|
|
843
907
|
"--api-url"
|
|
844
908
|
] }
|
|
845
|
-
},
|
|
909
|
+
}, nt = (e) => e.split("=")[0], rt = (e, t) => {
|
|
846
910
|
let n = $[e];
|
|
847
911
|
if (!n) return;
|
|
848
|
-
let r =
|
|
912
|
+
let r = R(t, "--target") === "api" ? "api" : "direct", i = /* @__PURE__ */ new Set([...n.common, ...r === "api" ? n.apiOnly ?? [] : n.directOnly ?? []]), a = new Set(r === "api" ? n.directOnly ?? [] : n.apiOnly ?? []);
|
|
849
913
|
for (let n of t) {
|
|
850
914
|
if (!n.startsWith("--")) continue;
|
|
851
|
-
let t =
|
|
915
|
+
let t = nt(n);
|
|
852
916
|
if (i.has(t)) continue;
|
|
853
917
|
if (a.has(t)) return `voltro data ${e}: ${t} is not read with --target ${r} (it is a --target ${r === "api" ? "direct" : "api"} flag). Remove it, or change --target.`;
|
|
854
918
|
let o = Object.entries($).filter(([n, r]) => n !== e && [
|
|
@@ -858,13 +922,13 @@ var A = class extends Error {
|
|
|
858
922
|
].includes(t)).map(([e]) => e);
|
|
859
923
|
return o.length > 0 ? `voltro data ${e}: ${t} is not a \`${e}\` flag (it belongs to \`voltro data ${o.join("` / `")}\`). Remove it.` : `voltro data ${e}: unknown flag ${t}. Run \`voltro data --help\` for the flags this command reads.`;
|
|
860
924
|
}
|
|
861
|
-
},
|
|
925
|
+
}, it = async (e) => {
|
|
862
926
|
let [t, ...n] = e;
|
|
863
927
|
if (t !== void 0 && $[t] !== void 0) {
|
|
864
|
-
let e =
|
|
865
|
-
if (e) return
|
|
928
|
+
let e = rt(t, n);
|
|
929
|
+
if (e) return I.error(e), 2;
|
|
866
930
|
}
|
|
867
|
-
return t === "export" ?
|
|
931
|
+
return t === "export" ? He(n) : t === "import" ? Ye(n) : t === "unpack" ? tt(n) : t === "inspect" ? et(n) : t === "backup" ? Ze(n) : t === "restore" ? $e(n) : (I.error("usage: voltro data <export|import|unpack|inspect|backup|restore> … (logical: export/import; native: backup/restore)"), t ? 2 : 1);
|
|
868
932
|
};
|
|
869
933
|
//#endregion
|
|
870
|
-
export {
|
|
934
|
+
export { it as runDataCommand };
|