@voltro/cli 0.43.1 → 0.43.2
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 +31 -0
- package/dist/{apiBuild-DF0f69Gf.js → apiBuild-BWuw8ZXS.js} +1 -1
- package/dist/apiBuild-Cw2-TEXW.js +2 -0
- package/dist/bin.js +1 -1
- package/dist/{build-LC_IJaHL.js → build-CGBs4ZOv.js} +6 -6
- package/dist/{checkCommand-DDxwJCja.js → checkCommand-BFtFBsqL.js} +2 -2
- package/dist/{checkCommand-DBpTSK8d.js → checkCommand-lTPSN3EQ.js} +1 -1
- package/dist/{codegenCommand-LjxfK4Am.js → codegenCommand-BJeyFlip.js} +1 -1
- package/dist/{commands-CbQcuWsE.js → commands-pEDOcOo2.js} +15 -15
- package/dist/{dashboardCommand-CAHjCe_9.js → dashboardCommand-Bt2hHMro.js} +1 -1
- package/dist/{dataCommand-CrqvbVOv.js → dataCommand-DD2bRHCu.js} +377 -208
- package/dist/{dev-D0E5x2Ue.js → dev-DMx-MF2s.js} +1 -1
- package/dist/{dev-B2rZfqxw.js → dev-wumUfDm1.js} +922 -912
- package/dist/doctorCommand-FOXTTaGp.js +2 -0
- package/dist/{doctorCommand-Ct_3BHgd.js → doctorCommand-RFKpnLih.js} +9 -9
- package/dist/{dormancyCommand-D1DKnYce.js → dormancyCommand-EFDQWiUw.js} +1 -1
- package/dist/{embeddingsCommand-1sZRBKlM.js → embeddingsCommand-OeYp3qSq.js} +1 -1
- package/dist/{envCommand-7qjlQgLD.js → envCommand-CKgBZK7x.js} +1 -1
- package/dist/{evolveCommand-D5opB48I.js → evolveCommand-ClQlAH0j.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{runtimeTrace-Wmi7V-wN.js → runtimeTrace-CgpAD9wL.js} +1 -1
- package/dist/{sdkgen-DbTFGCjg.js → sdkgen-Ca2zj4cd.js} +1 -1
- package/dist/{serveCommand-C8xzYCwa.js → serveCommand-CgK7Kj6j.js} +472 -368
- package/dist/serveCommand-DUm2-ikx.js +2 -0
- package/dist/serveEntry.js +1 -1
- package/dist/{webhooksCommand-BXUP5YjW.js → webhooksCommand-BVNtNsI0.js} +1 -1
- package/package.json +17 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/cli.md +126 -2
- package/templates/agent-docs/database/seedsdialects.md +12 -0
- package/templates/agent-docs/whats-new.md +14 -52
- 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-B7NgSMhX.js +0 -2
- package/dist/doctorCommand-CdsctSwg.js +0 -2
- package/dist/serveCommand-CGNTXafv.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-wumUfDm1.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";
|
|
@@ -11,15 +11,110 @@ import { SqlClient as m } from "@effect/sql";
|
|
|
11
11
|
import { declaredSnapshot as h, fingerprintSchema as g, introspectSchema as _ } from "@voltro/database/sql";
|
|
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
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
import { createHash as te } from "node:crypto";
|
|
15
|
+
import { assessLiveImport as ne, backupArtifactName as C, backupCommand as re, classificationFromSnapshot as ie, decryptStream as w, encryptStream as ae, packBundle as T, peekBundle as E, previewMasking as oe, restoreCommand as D, runExport as O, runImport as se, runNativeStep as k, storageAssetSink as ce, storageAssetSource as le, storageBlobSink as ue, storageBlobSource as de, unpackBundle as fe, unpackBundleToDir as A } from "@voltro/data-transfer";
|
|
16
|
+
import { request as pe } from "node:http";
|
|
17
|
+
import { request as me } from "node:https";
|
|
18
|
+
//#region src/longRequest.ts
|
|
19
|
+
var j = class extends Error {
|
|
20
|
+
elapsedMs;
|
|
21
|
+
url;
|
|
22
|
+
constructor(e, t) {
|
|
23
|
+
super(`no response after ${Math.round(e / 1e3)}s`), this.elapsedMs = e, this.url = t, this.name = "LongRequestTimeout";
|
|
24
|
+
}
|
|
25
|
+
}, M = async (e, t, n, r) => {
|
|
26
|
+
let i = new URL(e), a = i.protocol === "https:" ? me : pe, o = Date.now();
|
|
27
|
+
return await new Promise((s, c) => {
|
|
28
|
+
let l = a({
|
|
29
|
+
protocol: i.protocol,
|
|
30
|
+
hostname: i.hostname,
|
|
31
|
+
port: i.port === "" ? void 0 : Number(i.port),
|
|
32
|
+
path: `${i.pathname}${i.search}`,
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: n
|
|
35
|
+
}, (e) => {
|
|
36
|
+
let t = [];
|
|
37
|
+
e.on("data", (e) => t.push(e)), e.on("end", () => s({
|
|
38
|
+
status: e.statusCode ?? 0,
|
|
39
|
+
headers: Object.fromEntries(Object.entries(e.headers).map(([e, t]) => [e, Array.isArray(t) ? t.join(", ") : String(t ?? "")])),
|
|
40
|
+
body: Buffer.concat(t)
|
|
41
|
+
})), e.on("error", c);
|
|
42
|
+
});
|
|
43
|
+
if (r?.timeoutMs !== void 0 && r.timeoutMs > 0 && l.setTimeout(r.timeoutMs, () => {
|
|
44
|
+
l.destroy(new j(Date.now() - o, e));
|
|
45
|
+
}), l.on("error", c), typeof t == "string" || Buffer.isBuffer(t)) {
|
|
46
|
+
l.end(t);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
(async () => {
|
|
50
|
+
try {
|
|
51
|
+
for await (let e of t) l.write(Buffer.from(e)) || await new Promise((e) => l.once("drain", () => e()));
|
|
52
|
+
l.end();
|
|
53
|
+
} catch (e) {
|
|
54
|
+
l.destroy(e);
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
});
|
|
58
|
+
}, he = (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.", ge = 16777216, _e = async function* (e, t) {
|
|
59
|
+
let n = [], r = 0, i = 0, a = function* (e) {
|
|
60
|
+
let t = Buffer.concat(n);
|
|
61
|
+
n = [], r = 0;
|
|
62
|
+
let a = {
|
|
63
|
+
bytes: t,
|
|
64
|
+
offset: i,
|
|
65
|
+
final: e
|
|
66
|
+
};
|
|
67
|
+
i += t.byteLength, yield a;
|
|
68
|
+
};
|
|
69
|
+
for await (let a of e) for (n.push(Buffer.from(a)), r += a.byteLength; r >= t;) {
|
|
70
|
+
let e = Buffer.concat(n), a = e.subarray(0, t), o = e.subarray(t);
|
|
71
|
+
n = o.byteLength > 0 ? [Buffer.from(o)] : [], r = o.byteLength;
|
|
72
|
+
let s = {
|
|
73
|
+
bytes: Buffer.from(a),
|
|
74
|
+
offset: i,
|
|
75
|
+
final: !1
|
|
76
|
+
};
|
|
77
|
+
i += a.byteLength, yield s;
|
|
78
|
+
}
|
|
79
|
+
yield* a(!0);
|
|
80
|
+
}, ve = (e) => te("sha256").update(Buffer.from(e)).digest("hex"), ye = async (e) => {
|
|
81
|
+
let t = 0, n = 0, r = {
|
|
82
|
+
status: 0,
|
|
83
|
+
body: {}
|
|
84
|
+
};
|
|
85
|
+
for await (let i of _e(e.stream, e.chunkBytes)) {
|
|
86
|
+
if (i.offset + i.bytes.byteLength <= e.alreadyHeld && !i.final) {
|
|
87
|
+
n++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
let a = Math.max(0, e.alreadyHeld - i.offset), o = a > 0 ? i.bytes.subarray(a) : i.bytes, s = i.offset + a;
|
|
91
|
+
if (o.byteLength === 0 && !i.final) {
|
|
92
|
+
n++;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (r = await e.target.send(Buffer.from(o), {
|
|
96
|
+
...e.baseHeaders,
|
|
97
|
+
"content-type": "application/octet-stream",
|
|
98
|
+
"x-import-upload-id": e.uploadId,
|
|
99
|
+
"x-import-upload-key": e.key,
|
|
100
|
+
"x-import-chunk-offset": String(s),
|
|
101
|
+
...i.final ? { "x-import-chunk-final": "1" } : {}
|
|
102
|
+
}), n++, t += o.byteLength, e.onProgress?.(t), r.status >= 400) break;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
status: r.status,
|
|
106
|
+
body: r.body,
|
|
107
|
+
chunks: n,
|
|
108
|
+
bytes: t,
|
|
109
|
+
resumedFrom: e.alreadyHeld
|
|
110
|
+
};
|
|
111
|
+
}, N = "voltro-backup-stamp.json", be = (e) => ({
|
|
17
112
|
stampVersion: 1,
|
|
18
113
|
dialect: e.dialect,
|
|
19
114
|
schemaFingerprint: e.schemaFingerprint,
|
|
20
115
|
voltroVersion: e.voltroVersion,
|
|
21
116
|
createdAt: e.createdAt
|
|
22
|
-
}),
|
|
117
|
+
}), P = (e) => {
|
|
23
118
|
let t;
|
|
24
119
|
try {
|
|
25
120
|
t = JSON.parse(e);
|
|
@@ -37,10 +132,10 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
37
132
|
voltroVersion: n.voltroVersion,
|
|
38
133
|
createdAt: n.createdAt
|
|
39
134
|
};
|
|
40
|
-
},
|
|
135
|
+
}, xe = (e) => {
|
|
41
136
|
let { stamp: t, currentDialect: n, currentFingerprint: r } = e;
|
|
42
137
|
if (t === null) return {
|
|
43
|
-
warn: `no ${
|
|
138
|
+
warn: `no ${N} beside the dump — an older/handmade backup. Cannot verify dialect or schema version; make sure this dump is a ${n} dump.`,
|
|
44
139
|
info: []
|
|
45
140
|
};
|
|
46
141
|
let i = [`backup produced by @voltro/cli ${t.voltroVersion} at ${t.createdAt}`, `schema fingerprint: ${t.schemaFingerprint ?? "<none recorded>"}`];
|
|
@@ -51,20 +146,20 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
51
146
|
refuse: `dialect mismatch — backup is a ${t.dialect} dump, but DB_DIALECT is ${n}. A cross-dialect restore is never valid.`,
|
|
52
147
|
info: i
|
|
53
148
|
};
|
|
54
|
-
},
|
|
149
|
+
}, F = (e) => {
|
|
55
150
|
try {
|
|
56
151
|
let t = new URL(e), n = t.pathname.replace(/^\//, "");
|
|
57
152
|
return `${t.hostname.toLowerCase()}:${t.port || "(default)"}/${n}`;
|
|
58
153
|
} catch {
|
|
59
154
|
return e.trim();
|
|
60
155
|
}
|
|
61
|
-
},
|
|
156
|
+
}, Se = (e) => {
|
|
62
157
|
let t = e.flagUrl ?? e.envUrl;
|
|
63
158
|
if (t === void 0 || t.trim() === "") return {
|
|
64
159
|
ok: !1,
|
|
65
160
|
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."
|
|
66
161
|
};
|
|
67
|
-
let n =
|
|
162
|
+
let n = F(t);
|
|
68
163
|
return e.liveKeys.some((e) => e === n) ? {
|
|
69
164
|
ok: !1,
|
|
70
165
|
error: `drill target (${n}) is the LIVE database — a restore drill must target a throwaway DB, never production. It would --clean and overwrite it.`
|
|
@@ -72,7 +167,7 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
72
167
|
ok: !0,
|
|
73
168
|
url: t
|
|
74
169
|
};
|
|
75
|
-
},
|
|
170
|
+
}, Ce = (e) => {
|
|
76
171
|
let { stampFingerprint: t, restoredFingerprint: n, restoredTableCount: r } = e;
|
|
77
172
|
return r === 0 ? {
|
|
78
173
|
pass: !1,
|
|
@@ -87,24 +182,24 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
87
182
|
pass: !0,
|
|
88
183
|
lines: [`PASS — restored ${r} table(s); schema fingerprint matches the backup stamp (${n.slice(0, 12)}).`]
|
|
89
184
|
};
|
|
90
|
-
},
|
|
185
|
+
}, I = d({ scope: "voltro:data" }), we = async () => {
|
|
91
186
|
try {
|
|
92
187
|
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");
|
|
93
188
|
return JSON.parse(n(r, "utf8")).version ?? "unknown";
|
|
94
189
|
} catch {
|
|
95
190
|
return "unknown";
|
|
96
191
|
}
|
|
97
|
-
},
|
|
192
|
+
}, L = async (e) => {
|
|
98
193
|
try {
|
|
99
|
-
return g(await
|
|
194
|
+
return g(await W(e));
|
|
100
195
|
} catch {
|
|
101
196
|
return null;
|
|
102
197
|
}
|
|
103
|
-
},
|
|
198
|
+
}, R = (e) => {
|
|
104
199
|
if (e.event !== "done") return;
|
|
105
200
|
let t = e.total === void 0 ? "" : ` / ${e.total}`;
|
|
106
|
-
|
|
107
|
-
},
|
|
201
|
+
I.info(`${e.phase} [${e.index + 1}/${e.tableCount}] ${e.table}`, { rows: `${e.rowsDone}${t}` });
|
|
202
|
+
}, Te = async (e) => {
|
|
108
203
|
let t = (e) => e?.meta?.databaseId;
|
|
109
204
|
if (e) {
|
|
110
205
|
let n = await l({
|
|
@@ -124,26 +219,27 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
124
219
|
};
|
|
125
220
|
}
|
|
126
221
|
return null;
|
|
127
|
-
},
|
|
128
|
-
let t = e.includes("--allow-live"), n = t ? null : await
|
|
129
|
-
dialect:
|
|
222
|
+
}, z = async (e) => {
|
|
223
|
+
let t = e.includes("--allow-live"), n = t ? null : await Te(B(e, "--api-url")), i = v({
|
|
224
|
+
dialect: H(),
|
|
130
225
|
...r()
|
|
131
226
|
}), a = y({
|
|
132
|
-
dialect:
|
|
227
|
+
dialect: H(),
|
|
133
228
|
...r()
|
|
134
|
-
}), { refuse: o, message: s } =
|
|
229
|
+
}), { refuse: o, message: s } = ne({
|
|
135
230
|
liveInstance: n?.label ?? null,
|
|
136
231
|
allowLive: t,
|
|
137
232
|
sameTarget: b(i, n?.databaseId),
|
|
138
233
|
...a === void 0 ? {} : { targetLabel: `${a.host}:${a.port}/${a.database}` }
|
|
139
234
|
});
|
|
140
|
-
return !o || (
|
|
141
|
-
},
|
|
235
|
+
return !o || (I.error(s ?? "refusing: live instance detected"), !1);
|
|
236
|
+
}, B = i, Ee = [
|
|
142
237
|
"--api-url",
|
|
143
238
|
"--bundle-key",
|
|
144
239
|
"--compression",
|
|
145
240
|
"--consistency",
|
|
146
241
|
"--drill-url",
|
|
242
|
+
"--chunk-size",
|
|
147
243
|
"--exclude",
|
|
148
244
|
"--mode",
|
|
149
245
|
"--on-conflict",
|
|
@@ -151,46 +247,47 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
151
247
|
"--profile",
|
|
152
248
|
"--tables",
|
|
153
249
|
"--target",
|
|
250
|
+
"--timeout",
|
|
154
251
|
"--tenant",
|
|
155
252
|
"--token"
|
|
156
|
-
],
|
|
253
|
+
], V = (e) => a(e, Ee), H = () => {
|
|
157
254
|
let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
158
255
|
if (t === "memory") throw new e("voltro data: requires a real dialect, not DB_DIALECT=memory.");
|
|
159
256
|
return t;
|
|
160
|
-
},
|
|
257
|
+
}, U = async (e, t) => {
|
|
161
258
|
let n = (await o(e)).makeSqlLayer(t);
|
|
162
259
|
return f.runPromise(m.SqlClient.pipe(f.flatMap(_), f.provide(n)));
|
|
163
|
-
},
|
|
260
|
+
}, W = (e) => U(e, r()), De = () => {
|
|
164
261
|
let e = r(), t = [];
|
|
165
|
-
return e.url && t.push(
|
|
166
|
-
},
|
|
262
|
+
return e.url && t.push(F(e.url)), (e.host || e.database) && t.push(`${(e.host ?? "localhost").toLowerCase()}:${e.port ?? "(default)"}/${e.database ?? ""}`), t;
|
|
263
|
+
}, G = async () => {
|
|
167
264
|
let r = process.cwd(), i = await t(r);
|
|
168
265
|
if (!i) throw new e("voltro data: no app.config.ts found in the current directory.");
|
|
169
|
-
let a = await n(i, void 0), o =
|
|
266
|
+
let a = await n(i, void 0), o = H();
|
|
170
267
|
return {
|
|
171
268
|
store: a,
|
|
172
|
-
snapshot: await
|
|
269
|
+
snapshot: await W(o),
|
|
173
270
|
dialect: o
|
|
174
271
|
};
|
|
175
|
-
},
|
|
272
|
+
}, K = (e) => x(e, {
|
|
176
273
|
table: ee,
|
|
177
274
|
chunkSize: 500
|
|
178
275
|
}).pipe(p.map((e) => ({
|
|
179
276
|
key: String(e.key),
|
|
180
277
|
contentType: String(e.contentType ?? "application/octet-stream"),
|
|
181
278
|
size: Number(e.size ?? 0)
|
|
182
|
-
}))),
|
|
279
|
+
}))), q = (e) => !!e && e.endsWith(".vbundle"), J = (e) => B(e, "--passphrase") ?? process.env.VOLTRO_BUNDLE_KEY, Oe = (e) => e.includes("--encrypt") || B(e, "--passphrase") !== void 0 || process.env.VOLTRO_BUNDLE_KEY !== void 0, ke = async (e, t) => {
|
|
183
280
|
let { createWriteStream: n } = await import("node:fs"), { pipeline: r } = await import("node:stream/promises"), { Readable: i } = await import("node:stream");
|
|
184
281
|
await r(i.from(e), n(t));
|
|
185
|
-
},
|
|
282
|
+
}, Y = (e) => ({ async *[Symbol.asyncIterator]() {
|
|
186
283
|
let { createReadStream: t } = await import("node:fs");
|
|
187
284
|
for await (let n of t(e)) yield n instanceof Uint8Array ? n : new Uint8Array(n);
|
|
188
|
-
} }),
|
|
189
|
-
let r =
|
|
285
|
+
} }), X = (e) => (e ?? "").split(",").map((e) => e.trim()).filter(Boolean), Ae = (t, n) => {
|
|
286
|
+
let r = B(t, "--tenant"), i = B(t, "--tables"), a = B(t, "--exclude");
|
|
190
287
|
if (a !== void 0) {
|
|
191
288
|
if (i !== void 0) throw new e("voltro data export: --exclude and --tables are two ways to say the same thing; use one.");
|
|
192
289
|
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.");
|
|
193
|
-
let t =
|
|
290
|
+
let t = X(a), o = new Set(n), s = t.filter((e) => !o.has(e));
|
|
194
291
|
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.`);
|
|
195
292
|
let c = new Set(t);
|
|
196
293
|
return {
|
|
@@ -203,15 +300,15 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
203
300
|
tenantId: r
|
|
204
301
|
} : i ? {
|
|
205
302
|
kind: "tables",
|
|
206
|
-
tables:
|
|
303
|
+
tables: X(i)
|
|
207
304
|
} : { kind: "all" };
|
|
208
|
-
},
|
|
305
|
+
}, je = async () => {
|
|
209
306
|
let e = await u(process.cwd());
|
|
210
|
-
return
|
|
211
|
-
},
|
|
212
|
-
let e = (await u(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id ===
|
|
307
|
+
return ie(h(e));
|
|
308
|
+
}, Me = "voltro/tenant", Ne = async () => {
|
|
309
|
+
let e = (await u(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id === Me)).map((e) => e.tableName);
|
|
213
310
|
return e.length > 0 ? e : void 0;
|
|
214
|
-
},
|
|
311
|
+
}, Pe = (e) => {
|
|
215
312
|
for (let t of e.tables) if (t.samples.length !== 0) {
|
|
216
313
|
console.log(`\n ${t.table}`);
|
|
217
314
|
for (let e of t.samples) console.log(` ${e.column} [${e.action}] ${JSON.stringify(e.before)} → ${JSON.stringify(e.after)}`);
|
|
@@ -224,51 +321,118 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
224
321
|
console.log("\n ✗ unclassified (would BLOCK a real export — add .sensitive()/.safe() or a policy override):");
|
|
225
322
|
for (let t of e.unclassified) console.log(` ${t}`);
|
|
226
323
|
} else console.log("\n ✓ every exported column is classified — safe to run.");
|
|
227
|
-
},
|
|
228
|
-
let
|
|
229
|
-
if (!
|
|
324
|
+
}, Fe = async (t) => {
|
|
325
|
+
let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = B(t.args, "--chunk-size"), o = a === void 0 ? ge : Math.round(Number(a) * 1024 * 1024);
|
|
326
|
+
if (!Number.isFinite(o) || o <= 0) throw new e(`--chunk-size expects a positive number of megabytes, got '${String(a)}'`);
|
|
327
|
+
let s = t.stream[Symbol.asyncIterator](), c = [], l = 0, u = !1;
|
|
328
|
+
for (; l <= o;) {
|
|
329
|
+
let e = await s.next();
|
|
330
|
+
if (e.done === !0) {
|
|
331
|
+
u = !0;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
c.push(Buffer.from(e.value)), l += e.value.byteLength;
|
|
335
|
+
}
|
|
336
|
+
if (u && l <= o) return {
|
|
337
|
+
kind: "small",
|
|
338
|
+
body: Buffer.concat(c)
|
|
339
|
+
};
|
|
340
|
+
let d = n(process.cwd(), t.bundleDir), f = ve(q(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), {
|
|
341
|
+
...t.headers,
|
|
342
|
+
"x-import-upload-id": p,
|
|
343
|
+
"x-import-probe": "1"
|
|
344
|
+
}, Z(t.args)), h = m.status === 200 ? Number(JSON.parse(m.body.toString("utf8")).bytes ?? 0) : 0;
|
|
345
|
+
h > 0 && t.log.info("api import: resuming a chunked upload the instance already holds", { bytes: h });
|
|
346
|
+
let g = await ye({
|
|
347
|
+
stream: (async function* () {
|
|
348
|
+
for (let e of c) yield e;
|
|
349
|
+
for (;;) {
|
|
350
|
+
let e = await s.next();
|
|
351
|
+
if (e.done === !0) return;
|
|
352
|
+
yield e.value;
|
|
353
|
+
}
|
|
354
|
+
})(),
|
|
355
|
+
chunkBytes: o,
|
|
356
|
+
uploadId: p,
|
|
357
|
+
key: f,
|
|
358
|
+
alreadyHeld: h,
|
|
359
|
+
baseHeaders: t.headers,
|
|
360
|
+
target: { send: async (e, n) => {
|
|
361
|
+
let r = await M(t.url, e, n, Z(t.args)), i = {};
|
|
362
|
+
try {
|
|
363
|
+
i = JSON.parse(r.body.toString("utf8") || "{}");
|
|
364
|
+
} catch {}
|
|
365
|
+
return {
|
|
366
|
+
status: r.status,
|
|
367
|
+
body: i
|
|
368
|
+
};
|
|
369
|
+
} }
|
|
370
|
+
});
|
|
371
|
+
return t.log.info("api import: uploaded in chunks", {
|
|
372
|
+
chunks: g.chunks,
|
|
373
|
+
bytes: g.bytes,
|
|
374
|
+
...g.resumedFrom > 0 ? { resumedFrom: g.resumedFrom } : {}
|
|
375
|
+
}), {
|
|
376
|
+
kind: "sent",
|
|
377
|
+
res: {
|
|
378
|
+
status: g.status,
|
|
379
|
+
headers: {},
|
|
380
|
+
body: Buffer.from(JSON.stringify(g.body))
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}, Z = (t) => {
|
|
384
|
+
let n = B(t, "--timeout");
|
|
385
|
+
if (n === void 0) return {};
|
|
386
|
+
let r = Number(n);
|
|
387
|
+
if (!Number.isFinite(r) || r <= 0) throw new e(`--timeout expects a positive number of seconds, got '${n}'`);
|
|
388
|
+
return { timeoutMs: Math.round(r * 1e3) };
|
|
389
|
+
}, Ie = async (e) => {
|
|
390
|
+
let t = B(e, "--target") === "api", n = B(e, "--bundle-key"), r = V(e);
|
|
391
|
+
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;
|
|
230
392
|
if (t) {
|
|
231
|
-
let t =
|
|
232
|
-
if (!t) return
|
|
233
|
-
let i =
|
|
234
|
-
if (!i) return
|
|
235
|
-
if (
|
|
236
|
-
let a =
|
|
237
|
-
...
|
|
238
|
-
...
|
|
393
|
+
let t = B(e, "--api-url");
|
|
394
|
+
if (!t) return I.error("--target api requires --api-url <url> (the running instance)"), 1;
|
|
395
|
+
let i = B(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
|
|
396
|
+
if (!i) return I.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
|
|
397
|
+
if (B(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;
|
|
398
|
+
let a = B(e, "--tables"), o = {
|
|
399
|
+
...B(e, "--profile") ? { profile: B(e, "--profile") } : {},
|
|
400
|
+
...B(e, "--tenant") ? { tenant: B(e, "--tenant") } : {},
|
|
239
401
|
...a ? { tables: a.split(",").map((e) => e.trim()).filter(Boolean) } : {},
|
|
240
|
-
...
|
|
241
|
-
...
|
|
402
|
+
...B(e, "--consistency") === "snapshot" ? { consistency: "snapshot" } : {},
|
|
403
|
+
...B(e, "--compression") ? { compression: B(e, "--compression") } : {},
|
|
242
404
|
...e.includes("--assets") ? { assets: !0 } : {},
|
|
243
405
|
...n ? { bundleKey: n } : {}
|
|
244
|
-
}, s
|
|
245
|
-
|
|
246
|
-
|
|
406
|
+
}, s;
|
|
407
|
+
try {
|
|
408
|
+
s = await M(`${t.replace(/\/+$/, "")}/_voltro/admin/export`, JSON.stringify(o), {
|
|
247
409
|
"content-type": "application/json",
|
|
248
410
|
authorization: `Bearer ${i}`
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
411
|
+
}, Z(e));
|
|
412
|
+
} catch (e) {
|
|
413
|
+
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;
|
|
414
|
+
throw e;
|
|
415
|
+
}
|
|
416
|
+
if (s.status < 200 || s.status >= 300) return I.error(`api export failed: HTTP ${s.status} — ${s.body.toString("utf8")}`), 1;
|
|
417
|
+
if (n) return I.info("api export complete (pushed to storage)", {
|
|
254
418
|
via: t,
|
|
255
|
-
...
|
|
419
|
+
...JSON.parse(s.body.toString("utf8"))
|
|
256
420
|
}), 0;
|
|
257
|
-
let { resolve: c } = await import("node:path"), l = new Uint8Array(
|
|
258
|
-
return await
|
|
421
|
+
let { resolve: c } = await import("node:path"), l = new Uint8Array(s.body);
|
|
422
|
+
return await A((async function* () {
|
|
259
423
|
yield l;
|
|
260
|
-
})(), c(process.cwd(), r)),
|
|
424
|
+
})(), c(process.cwd(), r)), I.info("api export complete (downloaded from the running instance)", {
|
|
261
425
|
outDir: r,
|
|
262
|
-
tables: s.headers
|
|
263
|
-
rows: s.headers
|
|
264
|
-
masked: s.headers
|
|
426
|
+
tables: s.headers["x-export-tables"],
|
|
427
|
+
rows: s.headers["x-export-rows"],
|
|
428
|
+
masked: s.headers["x-export-masked"]
|
|
265
429
|
}), 0;
|
|
266
430
|
}
|
|
267
|
-
if (!r) return
|
|
268
|
-
let i =
|
|
431
|
+
if (!r) return I.error("a <outDir> is required for a direct export"), 1;
|
|
432
|
+
let i = B(e, "--profile"), a = i ? await s(i, { allowPath: !0 }) : {}, { store: o, snapshot: c, dialect: l } = await G(), u = e.includes("--assets") || a.assets === !0, d = u ? S({}) : void 0, p = B(e, "--compression") ?? a.compression, m = B(e, "--consistency") === "snapshot" ? "snapshot" : a.consistency ?? "live", h = a.masking, g = a.subset, _ = a.scope ?? Ae(e, c.tables.map((e) => e.name)), v = h ? await je() : void 0, y = _.kind === "tenant" ? await Ne() : void 0, b = y ? { tenantTables: y } : {};
|
|
269
433
|
if (e.includes("--dry-run")) {
|
|
270
434
|
let e = _.kind === "tables" && _.tables ? [..._.tables] : c.tables.map((e) => e.name);
|
|
271
|
-
if (
|
|
435
|
+
if (I.info("export DRY RUN — nothing was written", {
|
|
272
436
|
outDir: r,
|
|
273
437
|
tables: e.length,
|
|
274
438
|
scope: _.kind,
|
|
@@ -276,8 +440,8 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
276
440
|
compression: p ?? "zstd",
|
|
277
441
|
assets: u,
|
|
278
442
|
masking: h ? "profile policy applies" : "none"
|
|
279
|
-
}),
|
|
280
|
-
let t = await f.runPromise(
|
|
443
|
+
}), I.info(`would export: ${e.join(", ")}`), !h) return 0;
|
|
444
|
+
let t = await f.runPromise(oe({
|
|
281
445
|
store: o,
|
|
282
446
|
snapshot: c,
|
|
283
447
|
masking: h,
|
|
@@ -287,9 +451,9 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
287
451
|
...b
|
|
288
452
|
}
|
|
289
453
|
}));
|
|
290
|
-
return
|
|
454
|
+
return Pe(t), +(t.unclassified.length > 0);
|
|
291
455
|
}
|
|
292
|
-
if (
|
|
456
|
+
if (q(r)) {
|
|
293
457
|
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-"));
|
|
294
458
|
try {
|
|
295
459
|
let t = await f.runPromise(O({
|
|
@@ -305,17 +469,17 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
305
469
|
...p ? { compression: p } : {},
|
|
306
470
|
...h ? { masking: h } : {},
|
|
307
471
|
...v ? { classification: v } : {},
|
|
308
|
-
onProgress:
|
|
472
|
+
onProgress: R
|
|
309
473
|
})), n = T({
|
|
310
474
|
dir: s,
|
|
311
|
-
...d ? { blobs:
|
|
312
|
-
}), a =
|
|
475
|
+
...d ? { blobs: de(d, () => K(o)) } : {}
|
|
476
|
+
}), a = Oe(e);
|
|
313
477
|
if (a) {
|
|
314
|
-
let t =
|
|
315
|
-
if (!t) return
|
|
316
|
-
n =
|
|
478
|
+
let t = J(e);
|
|
479
|
+
if (!t) return I.error("--encrypt requires --passphrase <p> or VOLTRO_BUNDLE_KEY (a dedicated key, NOT the transfer secret)"), 1;
|
|
480
|
+
n = ae(n, t);
|
|
317
481
|
}
|
|
318
|
-
return await
|
|
482
|
+
return await ke(n, i(process.cwd(), r)), I.info("export complete (single file)", {
|
|
319
483
|
file: r,
|
|
320
484
|
encrypted: a,
|
|
321
485
|
assets: u,
|
|
@@ -342,10 +506,10 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
342
506
|
...p ? { compression: p } : {},
|
|
343
507
|
...h ? { masking: h } : {},
|
|
344
508
|
...v ? { classification: v } : {},
|
|
345
|
-
...d ? { assets:
|
|
346
|
-
onProgress:
|
|
509
|
+
...d ? { assets: le(d, () => K(o)) } : {},
|
|
510
|
+
onProgress: R
|
|
347
511
|
}));
|
|
348
|
-
return
|
|
512
|
+
return I.info("export complete", {
|
|
349
513
|
outDir: r,
|
|
350
514
|
tables: x.tables.length,
|
|
351
515
|
rows: x.tables.reduce((e, t) => e + t.rowCount, 0),
|
|
@@ -353,36 +517,36 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
353
517
|
compression: x.compression,
|
|
354
518
|
...x.masking ? { maskedColumns: x.masking.transformed.length } : {}
|
|
355
519
|
}), 0;
|
|
356
|
-
},
|
|
357
|
-
let t =
|
|
520
|
+
}, Q = () => "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.", Le = (e) => {
|
|
521
|
+
let t = B(e, "--tables");
|
|
358
522
|
return {
|
|
359
523
|
...t ? { tables: t.split(",").map((e) => e.trim()).filter(Boolean) } : {},
|
|
360
524
|
...e.includes("--dry-run") ? { dryRun: !0 } : {}
|
|
361
525
|
};
|
|
362
|
-
},
|
|
526
|
+
}, Re = (e, t) => {
|
|
363
527
|
if (e.includes("--dry-run")) {
|
|
364
|
-
|
|
528
|
+
I.info("import DRY RUN — nothing was written", t), I.info(Q());
|
|
365
529
|
return;
|
|
366
530
|
}
|
|
367
|
-
|
|
368
|
-
},
|
|
531
|
+
I.info("import complete", t);
|
|
532
|
+
}, ze = (e) => {
|
|
369
533
|
let t = e;
|
|
370
534
|
if (t?._tag !== "RowsRefusedError" || !Array.isArray(t.rows)) return;
|
|
371
535
|
let n = t.rows, r = n.filter((e) => !e.derived), i = Number(t.totalCount ?? n.length), a = Number(t.primaryCount ?? r.length);
|
|
372
|
-
|
|
373
|
-
for (let e of r)
|
|
536
|
+
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.`);
|
|
537
|
+
for (let e of r) I.error(` ${e.table} ${e.id} ${e.reason}`);
|
|
374
538
|
let o = i - a;
|
|
375
|
-
o > 0 &&
|
|
376
|
-
},
|
|
377
|
-
let t =
|
|
378
|
-
if (!i && !(t && n)) return
|
|
379
|
-
let a =
|
|
539
|
+
o > 0 && I.error(` … and ${o} row(s) behind them. Fix the ${a} above and re-run; they resolve with their parents.`);
|
|
540
|
+
}, Be = async (e) => {
|
|
541
|
+
let t = B(e, "--target") === "api", n = B(e, "--bundle-key"), i = V(e);
|
|
542
|
+
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;
|
|
543
|
+
let a = B(e, "--mode") ?? "upsert", o = B(e, "--on-conflict") ?? "skip";
|
|
380
544
|
if (t) {
|
|
381
|
-
let t =
|
|
382
|
-
if (!t) return
|
|
383
|
-
let r =
|
|
384
|
-
if (!r) return
|
|
385
|
-
let s =
|
|
545
|
+
let t = B(e, "--api-url");
|
|
546
|
+
if (!t) return I.error("--target api requires --api-url <url> (the running instance)"), 1;
|
|
547
|
+
let r = B(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
|
|
548
|
+
if (!r) return I.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
|
|
549
|
+
let s = B(e, "--tables"), c = {
|
|
386
550
|
authorization: `Bearer ${r}`,
|
|
387
551
|
"x-import-mode": a,
|
|
388
552
|
"x-import-on-conflict": o,
|
|
@@ -392,62 +556,64 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
392
556
|
...s ? { "x-import-tables": s } : {}
|
|
393
557
|
}, { resolve: l } = await import("node:path"), u;
|
|
394
558
|
if (n) c["content-type"] = "application/json", u = JSON.stringify({ bundleKey: n });
|
|
395
|
-
else if (
|
|
396
|
-
let t =
|
|
397
|
-
n && (t = w(t, n));
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
559
|
+
else if (q(i)) {
|
|
560
|
+
let t = Y(l(process.cwd(), i)), n = J(e);
|
|
561
|
+
n && (t = w(t, n)), c["content-type"] = "application/octet-stream", u = t;
|
|
562
|
+
} 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) });
|
|
563
|
+
let d = `${t.replace(/\/+$/, "")}/_voltro/admin/import`, f;
|
|
564
|
+
try {
|
|
565
|
+
let t = typeof u == "string" ? null : await Fe({
|
|
566
|
+
stream: u,
|
|
567
|
+
url: d,
|
|
568
|
+
headers: c,
|
|
569
|
+
bundleDir: i,
|
|
570
|
+
args: e,
|
|
571
|
+
log: I
|
|
572
|
+
});
|
|
573
|
+
f = t === null ? await M(d, u, c, Z(e)) : t.kind === "sent" ? t.res : await M(d, t.body, c, Z(e));
|
|
574
|
+
} catch (e) {
|
|
575
|
+
if (e instanceof j) return I.error(`api import: ${he(e, a)}`), 1;
|
|
576
|
+
throw e;
|
|
406
577
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
headers: c
|
|
411
|
-
});
|
|
412
|
-
if (!d.ok) return P.error(`api import failed: HTTP ${d.status} — ${await d.text()}`), 1;
|
|
413
|
-
let f = await d.json();
|
|
414
|
-
return e.includes("--dry-run") ? (P.info("api import DRY RUN — nothing was written", {
|
|
578
|
+
if (f.status < 200 || f.status >= 300) return I.error(`api import failed: HTTP ${f.status} — ${f.body.toString("utf8")}`), 1;
|
|
579
|
+
let p = JSON.parse(f.body.toString("utf8"));
|
|
580
|
+
return e.includes("--dry-run") ? (I.info("api import DRY RUN — nothing was written", {
|
|
415
581
|
via: t,
|
|
416
|
-
...
|
|
417
|
-
}),
|
|
582
|
+
...p
|
|
583
|
+
}), I.info(Q()), 0) : (I.info("api import complete (imported by the running instance)", {
|
|
418
584
|
via: t,
|
|
419
|
-
...
|
|
585
|
+
...p
|
|
420
586
|
}), 0);
|
|
421
587
|
}
|
|
422
|
-
if (!i) return
|
|
423
|
-
if (!await
|
|
424
|
-
let { store: s, snapshot: c } = await
|
|
425
|
-
if (
|
|
588
|
+
if (!i) return I.error("a <bundleDir> is required for a direct import"), 1;
|
|
589
|
+
if (!await z(e)) return 1;
|
|
590
|
+
let { store: s, snapshot: c } = await G(), l = e.includes("--force"), u = (e) => I.warn(e), d = e.includes("--assets") ? S({}) : void 0, p = H() === "postgres" ? (await import("@voltro/sql-postgres")).makePgCopySession(r()) : void 0;
|
|
591
|
+
if (q(i)) {
|
|
426
592
|
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-"));
|
|
427
593
|
try {
|
|
428
|
-
let t =
|
|
429
|
-
n && (t = w(t, n)), await
|
|
594
|
+
let t = Y(r(process.cwd(), i)), n = J(e);
|
|
595
|
+
n && (t = w(t, n)), await fe(t, {
|
|
430
596
|
dir: h,
|
|
431
|
-
blobs:
|
|
597
|
+
blobs: ue(d ?? S({}))
|
|
432
598
|
});
|
|
433
|
-
let m = await f.runPromise(
|
|
599
|
+
let m = await f.runPromise(se({
|
|
434
600
|
store: s,
|
|
435
601
|
bundleDir: h,
|
|
436
602
|
mode: a,
|
|
437
603
|
onConflict: o,
|
|
438
604
|
atomic: e.includes("--atomic"),
|
|
439
605
|
verify: !e.includes("--no-verify"),
|
|
440
|
-
targetDialect:
|
|
606
|
+
targetDialect: H(),
|
|
441
607
|
allowIncompatible: l,
|
|
442
608
|
targetSnapshot: c,
|
|
443
609
|
force: l,
|
|
444
610
|
onWarn: u,
|
|
445
|
-
onProgress:
|
|
611
|
+
onProgress: R,
|
|
446
612
|
assetsAlreadyRestored: !0,
|
|
447
|
-
...
|
|
613
|
+
...Le(e),
|
|
448
614
|
...p ? { copyLoader: p } : {}
|
|
449
615
|
}));
|
|
450
|
-
return
|
|
616
|
+
return Re(e, {
|
|
451
617
|
file: i,
|
|
452
618
|
tables: m.tables.length,
|
|
453
619
|
rows: m.tables.reduce((e, t) => e + t.rowCount, 0)
|
|
@@ -459,32 +625,32 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
459
625
|
});
|
|
460
626
|
}
|
|
461
627
|
}
|
|
462
|
-
let m = await f.runPromise(
|
|
628
|
+
let m = await f.runPromise(se({
|
|
463
629
|
store: s,
|
|
464
630
|
bundleDir: i,
|
|
465
631
|
mode: a,
|
|
466
632
|
onConflict: o,
|
|
467
633
|
atomic: e.includes("--atomic"),
|
|
468
634
|
verify: !e.includes("--no-verify"),
|
|
469
|
-
targetDialect:
|
|
635
|
+
targetDialect: H(),
|
|
470
636
|
allowIncompatible: l,
|
|
471
637
|
targetSnapshot: c,
|
|
472
638
|
force: l,
|
|
473
639
|
onWarn: u,
|
|
474
|
-
onProgress:
|
|
475
|
-
...
|
|
476
|
-
...d ? { assets:
|
|
640
|
+
onProgress: R,
|
|
641
|
+
...Le(e),
|
|
642
|
+
...d ? { assets: ce(d) } : {},
|
|
477
643
|
...p ? { copyLoader: p } : {}
|
|
478
644
|
})).catch((e) => {
|
|
479
|
-
throw
|
|
645
|
+
throw ze(e), e;
|
|
480
646
|
}).finally(() => p?.close());
|
|
481
|
-
return
|
|
647
|
+
return Re(e, {
|
|
482
648
|
bundleDir: i,
|
|
483
649
|
tables: m.tables.length,
|
|
484
650
|
rows: m.tables.reduce((e, t) => e + t.rowCount, 0),
|
|
485
651
|
assets: m.assets.count
|
|
486
652
|
}), 0;
|
|
487
|
-
},
|
|
653
|
+
}, Ve = () => {
|
|
488
654
|
let e = r();
|
|
489
655
|
return {
|
|
490
656
|
...e.url ? { url: e.url } : {},
|
|
@@ -494,75 +660,75 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
494
660
|
...e.password ? { password: e.password } : {},
|
|
495
661
|
...e.database ? { database: e.database } : {}
|
|
496
662
|
};
|
|
497
|
-
},
|
|
498
|
-
let t =
|
|
499
|
-
if (!t) return
|
|
500
|
-
let n =
|
|
663
|
+
}, He = async (e) => {
|
|
664
|
+
let t = V(e);
|
|
665
|
+
if (!t) return I.error("usage: voltro data backup <outDir> (native pg_dump/mysqldump/… + consistent artifact)"), 1;
|
|
666
|
+
let n = H(), { mkdir: r, writeFile: i } = await import("node:fs/promises"), { join: a } = await import("node:path");
|
|
501
667
|
await r(t, { recursive: !0 });
|
|
502
668
|
let o = a(t, C(n));
|
|
503
|
-
await f.runPromise(
|
|
504
|
-
let s =
|
|
669
|
+
await f.runPromise(k(re(n, Ve(), o)));
|
|
670
|
+
let s = be({
|
|
505
671
|
dialect: n,
|
|
506
|
-
schemaFingerprint: await
|
|
507
|
-
voltroVersion: await
|
|
672
|
+
schemaFingerprint: await L(n),
|
|
673
|
+
voltroVersion: await we(),
|
|
508
674
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
509
675
|
});
|
|
510
|
-
return await i(a(t,
|
|
676
|
+
return await i(a(t, N), `${JSON.stringify(s, null, 2)}\n`, "utf8"), I.info("native backup complete", {
|
|
511
677
|
dialect: n,
|
|
512
678
|
artifact: o,
|
|
513
|
-
stamp:
|
|
679
|
+
stamp: N,
|
|
514
680
|
schemaFingerprint: s.schemaFingerprint ?? "<none>",
|
|
515
681
|
assets: "NOT included — use `voltro data export --assets` for rows + blobs"
|
|
516
682
|
}), 0;
|
|
517
|
-
},
|
|
518
|
-
let n =
|
|
519
|
-
flagUrl:
|
|
683
|
+
}, Ue = async (e, t) => {
|
|
684
|
+
let n = H(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(e, C(n)), o = Se({
|
|
685
|
+
flagUrl: B(t, "--drill-url"),
|
|
520
686
|
envUrl: process.env.DRILL_DB_URL,
|
|
521
|
-
liveKeys:
|
|
687
|
+
liveKeys: De()
|
|
522
688
|
});
|
|
523
|
-
if (!o.ok) return
|
|
524
|
-
let s = await i(r(e,
|
|
525
|
-
if (s && s.dialect !== n) return
|
|
526
|
-
|
|
689
|
+
if (!o.ok) return I.error(`restore --drill: ${o.error}`), 1;
|
|
690
|
+
let s = await i(r(e, N), "utf8").then(P, () => null);
|
|
691
|
+
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;
|
|
692
|
+
I.info("restore drill: restoring into throwaway DB", {
|
|
527
693
|
dialect: n,
|
|
528
|
-
target:
|
|
529
|
-
}), await f.runPromise(
|
|
694
|
+
target: F(o.url)
|
|
695
|
+
}), await f.runPromise(k(D(n, { url: o.url }, a)));
|
|
530
696
|
let c = null, l = 0;
|
|
531
697
|
try {
|
|
532
|
-
let e = await
|
|
698
|
+
let e = await U(n, { url: o.url });
|
|
533
699
|
c = g(e), l = e.tables.length;
|
|
534
700
|
} catch (e) {
|
|
535
|
-
return
|
|
701
|
+
return I.error(`restore --drill: could not introspect the restored DB — ${String(e)}`), 1;
|
|
536
702
|
}
|
|
537
|
-
let u =
|
|
703
|
+
let u = Ce({
|
|
538
704
|
stampFingerprint: s?.schemaFingerprint ?? null,
|
|
539
705
|
restoredFingerprint: c,
|
|
540
706
|
restoredTableCount: l
|
|
541
707
|
});
|
|
542
|
-
for (let e of u.lines) u.pass ?
|
|
708
|
+
for (let e of u.lines) u.pass ? I.info(`restore drill: ${e}`) : I.error(`restore drill: ${e}`);
|
|
543
709
|
return +!u.pass;
|
|
544
|
-
},
|
|
545
|
-
let t =
|
|
546
|
-
if (!t) return
|
|
547
|
-
if (e.includes("--drill")) return
|
|
548
|
-
if (!await
|
|
549
|
-
let n =
|
|
550
|
-
stamp: await i(r(t,
|
|
710
|
+
}, We = async (e) => {
|
|
711
|
+
let t = V(e);
|
|
712
|
+
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;
|
|
713
|
+
if (e.includes("--drill")) return Ue(t, e);
|
|
714
|
+
if (!await z(e)) return 1;
|
|
715
|
+
let n = H(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t, C(n)), o = xe({
|
|
716
|
+
stamp: await i(r(t, N), "utf8").then(P, () => null),
|
|
551
717
|
currentDialect: n,
|
|
552
|
-
currentFingerprint: await
|
|
718
|
+
currentFingerprint: await L(n)
|
|
553
719
|
});
|
|
554
|
-
for (let e of o.info)
|
|
555
|
-
if (o.warn &&
|
|
556
|
-
if (!e.includes("--force")) return
|
|
557
|
-
|
|
720
|
+
for (let e of o.info) I.info(`backup stamp: ${e}`);
|
|
721
|
+
if (o.warn && I.warn(o.warn), o.refuse) {
|
|
722
|
+
if (!e.includes("--force")) return I.error(`restore refused: ${o.refuse} (pass --force to override)`), 1;
|
|
723
|
+
I.warn(`restore proceeding despite: ${o.refuse} (--force)`);
|
|
558
724
|
}
|
|
559
|
-
return await f.runPromise(
|
|
725
|
+
return await f.runPromise(k(D(n, Ve(), a))), I.info("native restore complete", {
|
|
560
726
|
dialect: n,
|
|
561
727
|
artifact: a
|
|
562
728
|
}), 0;
|
|
563
|
-
},
|
|
564
|
-
let t =
|
|
565
|
-
if (!t) return
|
|
729
|
+
}, Ge = async (e) => {
|
|
730
|
+
let t = V(e);
|
|
731
|
+
if (!t) return I.error("usage: voltro data inspect <file.vbundle|dir> [--json] [--passphrase <p>]"), 1;
|
|
566
732
|
let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = n(process.cwd(), t);
|
|
567
733
|
if ((await i.stat(a).catch(() => void 0))?.isDirectory()) {
|
|
568
734
|
let n = r(a, "manifest.json"), o = r(a, "README.md");
|
|
@@ -571,25 +737,25 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
571
737
|
if (e !== void 0) return console.log(e), 0;
|
|
572
738
|
}
|
|
573
739
|
let s = await i.readFile(n, "utf8").catch(() => void 0);
|
|
574
|
-
return s === void 0 ? (
|
|
740
|
+
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);
|
|
575
741
|
}
|
|
576
|
-
let o =
|
|
577
|
-
let e =
|
|
742
|
+
let o = J(e), s = () => {
|
|
743
|
+
let e = Y(a);
|
|
578
744
|
return o ? w(e, o) : e;
|
|
579
745
|
};
|
|
580
746
|
if (e.includes("--json")) {
|
|
581
747
|
let e = (await E(s(), ["manifest.json"])).get("manifest.json");
|
|
582
|
-
return e ? (console.log(e.toString("utf8")), 0) : (
|
|
748
|
+
return e ? (console.log(e.toString("utf8")), 0) : (I.error("no manifest.json found in the bundle"), 1);
|
|
583
749
|
}
|
|
584
750
|
let c = (await E(s(), ["README.md"])).get("README.md");
|
|
585
751
|
if (c) return console.log(c.toString("utf8")), 0;
|
|
586
752
|
let l = (await E(s(), ["manifest.json"])).get("manifest.json");
|
|
587
|
-
return l ? (console.log(l.toString("utf8")), 0) : (
|
|
588
|
-
},
|
|
589
|
-
let t =
|
|
590
|
-
if (!t || !n) return
|
|
591
|
-
let { resolve: r } = await import("node:path"), i =
|
|
592
|
-
return a && (i = w(i, a)), await
|
|
753
|
+
return l ? (console.log(l.toString("utf8")), 0) : (I.error("bundle has no README.md or manifest.json"), 1);
|
|
754
|
+
}, Ke = async (e) => {
|
|
755
|
+
let t = V(e), n = V(e.filter((e) => e !== t));
|
|
756
|
+
if (!t || !n) return I.error("usage: voltro data unpack <file.vbundle> <dir> [--passphrase <p>]"), 1;
|
|
757
|
+
let { resolve: r } = await import("node:path"), i = Y(r(process.cwd(), t)), a = J(e);
|
|
758
|
+
return a && (i = w(i, a)), await A(i, r(process.cwd(), n)), I.info("unpack complete", {
|
|
593
759
|
file: t,
|
|
594
760
|
dir: n
|
|
595
761
|
}), 0;
|
|
@@ -614,7 +780,8 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
614
780
|
apiOnly: [
|
|
615
781
|
"--api-url",
|
|
616
782
|
"--token",
|
|
617
|
-
"--bundle-key"
|
|
783
|
+
"--bundle-key",
|
|
784
|
+
"--timeout"
|
|
618
785
|
]
|
|
619
786
|
},
|
|
620
787
|
import: {
|
|
@@ -637,7 +804,9 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
637
804
|
apiOnly: [
|
|
638
805
|
"--api-url",
|
|
639
806
|
"--token",
|
|
640
|
-
"--bundle-key"
|
|
807
|
+
"--bundle-key",
|
|
808
|
+
"--timeout",
|
|
809
|
+
"--chunk-size"
|
|
641
810
|
]
|
|
642
811
|
},
|
|
643
812
|
unpack: { common: ["--passphrase", "--json"] },
|
|
@@ -651,13 +820,13 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
651
820
|
"--drill-url",
|
|
652
821
|
"--api-url"
|
|
653
822
|
] }
|
|
654
|
-
},
|
|
823
|
+
}, qe = (e) => e.split("=")[0], Je = (e, t) => {
|
|
655
824
|
let n = $[e];
|
|
656
825
|
if (!n) return;
|
|
657
|
-
let r =
|
|
826
|
+
let r = B(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 ?? []);
|
|
658
827
|
for (let n of t) {
|
|
659
828
|
if (!n.startsWith("--")) continue;
|
|
660
|
-
let t =
|
|
829
|
+
let t = qe(n);
|
|
661
830
|
if (i.has(t)) continue;
|
|
662
831
|
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.`;
|
|
663
832
|
let o = Object.entries($).filter(([n, r]) => n !== e && [
|
|
@@ -667,13 +836,13 @@ var j = "voltro-backup-stamp.json", fe = (e) => ({
|
|
|
667
836
|
].includes(t)).map(([e]) => e);
|
|
668
837
|
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.`;
|
|
669
838
|
}
|
|
670
|
-
},
|
|
839
|
+
}, Ye = async (e) => {
|
|
671
840
|
let [t, ...n] = e;
|
|
672
841
|
if (t !== void 0 && $[t] !== void 0) {
|
|
673
|
-
let e =
|
|
674
|
-
if (e) return
|
|
842
|
+
let e = Je(t, n);
|
|
843
|
+
if (e) return I.error(e), 2;
|
|
675
844
|
}
|
|
676
|
-
return t === "export" ?
|
|
845
|
+
return t === "export" ? Ie(n) : t === "import" ? Be(n) : t === "unpack" ? Ke(n) : t === "inspect" ? Ge(n) : t === "backup" ? He(n) : t === "restore" ? We(n) : (I.error("usage: voltro data <export|import|unpack|inspect|backup|restore> … (logical: export/import; native: backup/restore)"), t ? 2 : 1);
|
|
677
846
|
};
|
|
678
847
|
//#endregion
|
|
679
|
-
export {
|
|
848
|
+
export { Ye as runDataCommand };
|