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