@voltro/cli 0.41.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/dist/apiBuild-B7NgSMhX.js +2 -0
  3. package/dist/{apiBuild-B_iH24qf.js → apiBuild-DF0f69Gf.js} +2 -2
  4. package/dist/bin.js +1 -1
  5. package/dist/{build-DUdi6F_Q.js → build-LC_IJaHL.js} +7 -7
  6. package/dist/{checkCommand-B2RWrpWi.js → checkCommand-DBpTSK8d.js} +1 -1
  7. package/dist/{checkCommand-DWL2_PPR.js → checkCommand-DDxwJCja.js} +2 -2
  8. package/dist/{codegenCommand-DkjoJ30Q.js → codegenCommand-LjxfK4Am.js} +2 -2
  9. package/dist/{codemodRunner-sFuWDETH.js → codemodRunner-Cnnz4-I7.js} +19 -4
  10. package/dist/{commands-D0VR1yBB.js → commands-CbQcuWsE.js} +22 -22
  11. package/dist/{dashboardCommand-Bro7Jwts.js → dashboardCommand-CAHjCe_9.js} +3 -3
  12. package/dist/{dataCommand-OKC5FscP.js → dataCommand-CrqvbVOv.js} +298 -170
  13. package/dist/dataProfile-Cm0YVKSy.js +18 -0
  14. package/dist/{dbCommand-BHRamDsy.js → dbCommand-B237qI-j.js} +361 -361
  15. package/dist/dbCommand-BvQeKQOb.js +2 -0
  16. package/dist/{dev-D4l_YOzI.js → dev-B2rZfqxw.js} +2 -2
  17. package/dist/{dev-DuqBJC7g.js → dev-D0E5x2Ue.js} +1 -1
  18. package/dist/doctorCommand-CdsctSwg.js +2 -0
  19. package/dist/{doctorCommand-TmZTAepc.js → doctorCommand-Ct_3BHgd.js} +226 -216
  20. package/dist/{dormancyCommand-CUKhRmGG.js → dormancyCommand-D1DKnYce.js} +1 -1
  21. package/dist/{embeddingsCommand-Db-iQM3l.js → embeddingsCommand-1sZRBKlM.js} +1 -1
  22. package/dist/{envCommand-DQ_URZq9.js → envCommand-7qjlQgLD.js} +2 -2
  23. package/dist/{evolveCommand-CghRPrGO.js → evolveCommand-D5opB48I.js} +2 -2
  24. package/dist/index.js +1 -1
  25. package/dist/{infoCommand-BCs_ZFnl.js → infoCommand-Bs_8RU22.js} +1 -1
  26. package/dist/{migrate-DLtPv2_D.js → migrate-DN47p2PG.js} +1 -1
  27. package/dist/{runtimeTrace-jFKvOPBz.js → runtimeTrace-Wmi7V-wN.js} +1 -1
  28. package/dist/{sdkgen-COQqHB9V.js → sdkgen-DbTFGCjg.js} +1 -1
  29. package/dist/{serveCommand-Cba_M2Su.js → serveCommand-C8xzYCwa.js} +417 -411
  30. package/dist/serveCommand-CGNTXafv.js +2 -0
  31. package/dist/serveEntry.js +1 -1
  32. package/dist/{start-fifc0xpW.js → start-BAlGcjF5.js} +1 -1
  33. package/dist/{start-BjcuFME_.js → start-Ds3xyn8R.js} +1 -1
  34. package/dist/startEntry.js +1 -1
  35. package/dist/startup.d.ts +1 -1
  36. package/dist/updateCommand-BvkKlV7D.js +2 -0
  37. package/dist/{updateCommand-HZQVBU3T.js → updateCommand-CrI9B4d7.js} +1 -1
  38. package/dist/{webDev-Dk8lFUzf.js → webDev-101csg6Q.js} +2 -2
  39. package/dist/webDev-BLKBO34K.js +2 -0
  40. package/dist/{webhooksCommand-D7ZpH7w0.js → webhooksCommand-BXUP5YjW.js} +1 -1
  41. package/package.json +23 -17
  42. package/templates/AGENTS.md +1 -1
  43. package/templates/agent-docs/_index.md +1 -1
  44. package/templates/agent-docs/authentication.md +1 -1
  45. package/templates/agent-docs/cli.md +75 -8
  46. package/templates/agent-docs/data.md +46 -11
  47. package/templates/agent-docs/database/migrations.md +1 -1
  48. package/templates/agent-docs/internationalization.md +2 -2
  49. package/templates/agent-docs/observability.md +1 -1
  50. package/templates/agent-docs/plugins/openapi.md +1 -1
  51. package/templates/agent-docs/plugins/versioning.md +1 -1
  52. package/templates/agent-docs/plugins/webhooks.md +1 -1
  53. package/templates/agent-docs/plugins.md +1 -1
  54. package/templates/agent-docs/routing.md +1 -1
  55. package/templates/agent-docs/scheduling.md +1 -1
  56. package/templates/agent-docs/security.md +2 -2
  57. package/templates/agent-docs/templates/apibackends.md +1 -1
  58. package/templates/agent-docs/whats-new.md +54 -37
  59. package/templates/apps/api-ai/package.json +7 -7
  60. package/templates/apps/api-auth/package.json +8 -8
  61. package/templates/apps/api-backend/package.json +7 -7
  62. package/templates/apps/api-backend-deactivation/package.json +7 -7
  63. package/templates/apps/api-backend-mail/package.json +8 -8
  64. package/templates/apps/api-backend-mariadb/package.json +9 -9
  65. package/templates/apps/api-backend-sqlite/package.json +8 -8
  66. package/templates/apps/api-backend-storage/package.json +8 -8
  67. package/templates/apps/api-cms/package.json +10 -10
  68. package/templates/apps/api-collab/package.json +8 -8
  69. package/templates/apps/api-data-advanced/package.json +8 -8
  70. package/templates/apps/api-durable/package.json +8 -8
  71. package/templates/apps/api-feature-flags/package.json +9 -9
  72. package/templates/apps/api-governance/package.json +8 -8
  73. package/templates/apps/api-kv/package.json +8 -8
  74. package/templates/apps/api-moderation/package.json +8 -8
  75. package/templates/apps/api-observability/package.json +8 -8
  76. package/templates/apps/api-ratelimit/package.json +8 -8
  77. package/templates/apps/api-rbac/package.json +8 -8
  78. package/templates/apps/api-rest/package.json +7 -7
  79. package/templates/apps/api-saas/package.json +11 -11
  80. package/templates/apps/api-saas-starter/package.json +10 -10
  81. package/templates/apps/api-search/package.json +8 -8
  82. package/templates/apps/api-status/package.json +8 -8
  83. package/templates/apps/api-versioning/package.json +8 -8
  84. package/templates/apps/api-webhooks/package.json +9 -9
  85. package/templates/apps/changelog/package.json +6 -6
  86. package/templates/apps/edge-functions/package.json +2 -2
  87. package/templates/apps/frontend-admin/package.json +8 -8
  88. package/templates/apps/frontend-app/package.json +9 -9
  89. package/templates/apps/frontend-auth/package.json +8 -8
  90. package/templates/apps/frontend-blank/package.json +7 -7
  91. package/templates/apps/frontend-cms/package.json +9 -9
  92. package/templates/apps/frontend-collab/package.json +10 -10
  93. package/templates/apps/frontend-contact/package.json +7 -7
  94. package/templates/apps/frontend-dashboard/package.json +7 -7
  95. package/templates/apps/frontend-docs/package.json +7 -7
  96. package/templates/apps/frontend-i18n/package.json +6 -6
  97. package/templates/apps/frontend-landing/package.json +7 -7
  98. package/templates/apps/frontend-portal/package.json +8 -8
  99. package/templates/apps/frontend-saas/package.json +8 -8
  100. package/templates/apps/frontend-spa/package.json +7 -7
  101. package/templates/apps/frontend-ssr/package.json +7 -7
  102. package/templates/apps/frontend-ssr-api/package.json +8 -8
  103. package/templates/apps/frontend-static-blog/package.json +6 -6
  104. package/templates/apps/frontend-status/package.json +8 -8
  105. package/templates/apps/mobile-app/package.json +4 -4
  106. package/dist/apiBuild-RM5W5YXk.js +0 -2
  107. package/dist/dataProfile-dW-PsfLB.js +0 -15
  108. package/dist/dbCommand-C9iE1pd9.js +0 -2
  109. package/dist/doctorCommand-Bw3V4L1j.js +0 -2
  110. package/dist/serveCommand-DRbYK646.js +0 -2
  111. package/dist/updateCommand-B2alql9k.js +0 -2
  112. package/dist/webDev-D6QeXw3P.js +0 -2
@@ -1,25 +1,25 @@
1
1
  import { t as e } from "./cliError-BmdYnghb.js";
2
- import { c as t, i as n, o as r } from "./dev-D4l_YOzI.js";
2
+ import { c as t, i as n, o as r } from "./dev-B2rZfqxw.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
- import { t as s } from "./dataProfile-dW-PsfLB.js";
5
+ import { t as s } from "./dataProfile-Cm0YVKSy.js";
6
6
  import { o as c, t as l } from "./inspectFetch-EMuhTG_9.js";
7
- import { r as u } from "./dbCommand-BHRamDsy.js";
7
+ import { r as u } from "./dbCommand-B237qI-j.js";
8
8
  import { createLogger as d } from "@voltro/logger";
9
9
  import { Effect as f, Stream as p } from "effect";
10
10
  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 { assessLiveImport as te, backupArtifactName as C, backupCommand as ne, classificationFromSnapshot as re, decryptStream as w, encryptStream as ie, packBundle as T, peekBundle as E, previewMasking as ae, restoreCommand as D, runExport as O, runImport as k, runNativeStep as A, storageAssetSink as j, storageAssetSource as oe, storageBlobSink as se, storageBlobSource as ce, unpackBundle as le, unpackBundleToDir as M } from "@voltro/data-transfer";
14
+ import { assessLiveImport as te, backupArtifactName as C, backupCommand as ne, classificationFromSnapshot as re, decryptStream as w, encryptStream as ie, packBundle as T, peekBundle as E, previewMasking as ae, restoreCommand as D, runExport as O, runImport as k, runNativeStep as A, storageAssetSink as oe, storageAssetSource as se, storageBlobSink as ce, storageBlobSource as le, unpackBundle as ue, unpackBundleToDir as de } from "@voltro/data-transfer";
15
15
  //#region src/backupStamp.ts
16
- var N = "voltro-backup-stamp.json", ue = (e) => ({
16
+ var j = "voltro-backup-stamp.json", fe = (e) => ({
17
17
  stampVersion: 1,
18
18
  dialect: e.dialect,
19
19
  schemaFingerprint: e.schemaFingerprint,
20
20
  voltroVersion: e.voltroVersion,
21
21
  createdAt: e.createdAt
22
- }), P = (e) => {
22
+ }), M = (e) => {
23
23
  let t;
24
24
  try {
25
25
  t = JSON.parse(e);
@@ -37,10 +37,10 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
37
37
  voltroVersion: n.voltroVersion,
38
38
  createdAt: n.createdAt
39
39
  };
40
- }, de = (e) => {
40
+ }, pe = (e) => {
41
41
  let { stamp: t, currentDialect: n, currentFingerprint: r } = e;
42
42
  if (t === null) return {
43
- warn: `no ${N} beside the dump — an older/handmade backup. Cannot verify dialect or schema version; make sure this dump is a ${n} dump.`,
43
+ warn: `no ${j} beside the dump — an older/handmade backup. Cannot verify dialect or schema version; make sure this dump is a ${n} dump.`,
44
44
  info: []
45
45
  };
46
46
  let i = [`backup produced by @voltro/cli ${t.voltroVersion} at ${t.createdAt}`, `schema fingerprint: ${t.schemaFingerprint ?? "<none recorded>"}`];
@@ -51,20 +51,20 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
51
51
  refuse: `dialect mismatch — backup is a ${t.dialect} dump, but DB_DIALECT is ${n}. A cross-dialect restore is never valid.`,
52
52
  info: i
53
53
  };
54
- }, F = (e) => {
54
+ }, N = (e) => {
55
55
  try {
56
56
  let t = new URL(e), n = t.pathname.replace(/^\//, "");
57
57
  return `${t.hostname.toLowerCase()}:${t.port || "(default)"}/${n}`;
58
58
  } catch {
59
59
  return e.trim();
60
60
  }
61
- }, fe = (e) => {
61
+ }, me = (e) => {
62
62
  let t = e.flagUrl ?? e.envUrl;
63
63
  if (t === void 0 || t.trim() === "") return {
64
64
  ok: !1,
65
65
  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
66
  };
67
- let n = F(t);
67
+ let n = N(t);
68
68
  return e.liveKeys.some((e) => e === n) ? {
69
69
  ok: !1,
70
70
  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 +72,7 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
72
72
  ok: !0,
73
73
  url: t
74
74
  };
75
- }, pe = (e) => {
75
+ }, he = (e) => {
76
76
  let { stampFingerprint: t, restoredFingerprint: n, restoredTableCount: r } = e;
77
77
  return r === 0 ? {
78
78
  pass: !1,
@@ -87,24 +87,24 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
87
87
  pass: !0,
88
88
  lines: [`PASS — restored ${r} table(s); schema fingerprint matches the backup stamp (${n.slice(0, 12)}).`]
89
89
  };
90
- }, I = d({ scope: "voltro:data" }), me = async () => {
90
+ }, P = d({ scope: "voltro:data" }), ge = async () => {
91
91
  try {
92
92
  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
93
  return JSON.parse(n(r, "utf8")).version ?? "unknown";
94
94
  } catch {
95
95
  return "unknown";
96
96
  }
97
- }, L = async (e) => {
97
+ }, F = async (e) => {
98
98
  try {
99
- return g(await G(e));
99
+ return g(await H(e));
100
100
  } catch {
101
101
  return null;
102
102
  }
103
- }, R = (e) => {
103
+ }, I = (e) => {
104
104
  if (e.event !== "done") return;
105
105
  let t = e.total === void 0 ? "" : ` / ${e.total}`;
106
- I.info(`${e.phase} [${e.index + 1}/${e.tableCount}] ${e.table}`, { rows: `${e.rowsDone}${t}` });
107
- }, z = async (e) => {
106
+ P.info(`${e.phase} [${e.index + 1}/${e.tableCount}] ${e.table}`, { rows: `${e.rowsDone}${t}` });
107
+ }, _e = async (e) => {
108
108
  let t = (e) => e?.meta?.databaseId;
109
109
  if (e) {
110
110
  let n = await l({
@@ -124,12 +124,12 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
124
124
  };
125
125
  }
126
126
  return null;
127
- }, B = async (e) => {
128
- let t = e.includes("--allow-live"), n = t ? null : await z(V(e, "--api-url")), i = v({
129
- dialect: U(),
127
+ }, L = async (e) => {
128
+ let t = e.includes("--allow-live"), n = t ? null : await _e(R(e, "--api-url")), i = v({
129
+ dialect: B(),
130
130
  ...r()
131
131
  }), a = y({
132
- dialect: U(),
132
+ dialect: B(),
133
133
  ...r()
134
134
  }), { refuse: o, message: s } = te({
135
135
  liveInstance: n?.label ?? null,
@@ -137,8 +137,8 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
137
137
  sameTarget: b(i, n?.databaseId),
138
138
  ...a === void 0 ? {} : { targetLabel: `${a.host}:${a.port}/${a.database}` }
139
139
  });
140
- return !o || (I.error(s ?? "refusing: live instance detected"), !1);
141
- }, V = i, he = [
140
+ return !o || (P.error(s ?? "refusing: live instance detected"), !1);
141
+ }, R = i, ve = [
142
142
  "--api-url",
143
143
  "--bundle-key",
144
144
  "--compression",
@@ -153,44 +153,44 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
153
153
  "--target",
154
154
  "--tenant",
155
155
  "--token"
156
- ], H = (e) => a(e, he), U = () => {
156
+ ], z = (e) => a(e, ve), B = () => {
157
157
  let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
158
158
  if (t === "memory") throw new e("voltro data: requires a real dialect, not DB_DIALECT=memory.");
159
159
  return t;
160
- }, W = async (e, t) => {
160
+ }, V = async (e, t) => {
161
161
  let n = (await o(e)).makeSqlLayer(t);
162
162
  return f.runPromise(m.SqlClient.pipe(f.flatMap(_), f.provide(n)));
163
- }, G = (e) => W(e, r()), ge = () => {
163
+ }, H = (e) => V(e, r()), ye = () => {
164
164
  let e = r(), t = [];
165
- return e.url && t.push(F(e.url)), (e.host || e.database) && t.push(`${(e.host ?? "localhost").toLowerCase()}:${e.port ?? "(default)"}/${e.database ?? ""}`), t;
166
- }, K = async () => {
165
+ return e.url && t.push(N(e.url)), (e.host || e.database) && t.push(`${(e.host ?? "localhost").toLowerCase()}:${e.port ?? "(default)"}/${e.database ?? ""}`), t;
166
+ }, U = async () => {
167
167
  let r = process.cwd(), i = await t(r);
168
168
  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 = U();
169
+ let a = await n(i, void 0), o = B();
170
170
  return {
171
171
  store: a,
172
- snapshot: await G(o),
172
+ snapshot: await H(o),
173
173
  dialect: o
174
174
  };
175
- }, q = (e) => x(e, {
175
+ }, W = (e) => x(e, {
176
176
  table: ee,
177
177
  chunkSize: 500
178
178
  }).pipe(p.map((e) => ({
179
179
  key: String(e.key),
180
180
  contentType: String(e.contentType ?? "application/octet-stream"),
181
181
  size: Number(e.size ?? 0)
182
- }))), J = (e) => !!e && e.endsWith(".vbundle"), Y = (e) => V(e, "--passphrase") ?? process.env.VOLTRO_BUNDLE_KEY, _e = (e) => e.includes("--encrypt") || V(e, "--passphrase") !== void 0 || process.env.VOLTRO_BUNDLE_KEY !== void 0, ve = async (e, t) => {
182
+ }))), G = (e) => !!e && e.endsWith(".vbundle"), K = (e) => R(e, "--passphrase") ?? process.env.VOLTRO_BUNDLE_KEY, be = (e) => e.includes("--encrypt") || R(e, "--passphrase") !== void 0 || process.env.VOLTRO_BUNDLE_KEY !== void 0, xe = async (e, t) => {
183
183
  let { createWriteStream: n } = await import("node:fs"), { pipeline: r } = await import("node:stream/promises"), { Readable: i } = await import("node:stream");
184
184
  await r(i.from(e), n(t));
185
- }, X = (e) => ({ async *[Symbol.asyncIterator]() {
185
+ }, q = (e) => ({ async *[Symbol.asyncIterator]() {
186
186
  let { createReadStream: t } = await import("node:fs");
187
187
  for await (let n of t(e)) yield n instanceof Uint8Array ? n : new Uint8Array(n);
188
- } }), Z = (e) => (e ?? "").split(",").map((e) => e.trim()).filter(Boolean), ye = (t, n) => {
189
- let r = V(t, "--tenant"), i = V(t, "--tables"), a = V(t, "--exclude");
188
+ } }), J = (e) => (e ?? "").split(",").map((e) => e.trim()).filter(Boolean), Se = (t, n) => {
189
+ let r = R(t, "--tenant"), i = R(t, "--tables"), a = R(t, "--exclude");
190
190
  if (a !== void 0) {
191
191
  if (i !== void 0) throw new e("voltro data export: --exclude and --tables are two ways to say the same thing; use one.");
192
192
  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 = Z(a), o = new Set(n), s = t.filter((e) => !o.has(e));
193
+ let t = J(a), o = new Set(n), s = t.filter((e) => !o.has(e));
194
194
  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
195
  let c = new Set(t);
196
196
  return {
@@ -203,15 +203,15 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
203
203
  tenantId: r
204
204
  } : i ? {
205
205
  kind: "tables",
206
- tables: Z(i)
206
+ tables: J(i)
207
207
  } : { kind: "all" };
208
- }, be = async () => {
208
+ }, Ce = async () => {
209
209
  let e = await u(process.cwd());
210
210
  return re(h(e));
211
- }, xe = "voltro/tenant", Se = async () => {
212
- let e = (await u(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id === xe)).map((e) => e.tableName);
211
+ }, we = "voltro/tenant", Te = async () => {
212
+ let e = (await u(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id === we)).map((e) => e.tableName);
213
213
  return e.length > 0 ? e : void 0;
214
- }, Ce = (e) => {
214
+ }, Ee = (e) => {
215
215
  for (let t of e.tables) if (t.samples.length !== 0) {
216
216
  console.log(`\n ${t.table}`);
217
217
  for (let e of t.samples) console.log(` ${e.column} [${e.action}] ${JSON.stringify(e.before)} → ${JSON.stringify(e.after)}`);
@@ -224,21 +224,21 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
224
224
  console.log("\n ✗ unclassified (would BLOCK a real export — add .sensitive()/.safe() or a policy override):");
225
225
  for (let t of e.unclassified) console.log(` ${t}`);
226
226
  } else console.log("\n ✓ every exported column is classified — safe to run.");
227
- }, we = async (e) => {
228
- let t = V(e, "--target") === "api", n = V(e, "--bundle-key"), r = H(e);
229
- if (!r && !(t && n)) return I.error("usage: voltro data export <outDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--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;
227
+ }, De = async (e) => {
228
+ let t = R(e, "--target") === "api", n = R(e, "--bundle-key"), r = z(e);
229
+ if (!r && !(t && n)) return P.error("usage: voltro data export <outDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--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
230
  if (t) {
231
- let t = V(e, "--api-url");
232
- if (!t) return I.error("--target api requires --api-url <url> (the running instance)"), 1;
233
- let i = V(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
234
- if (!i) return I.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
235
- if (V(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;
236
- let a = V(e, "--tables"), o = {
237
- ...V(e, "--profile") ? { profile: V(e, "--profile") } : {},
238
- ...V(e, "--tenant") ? { tenant: V(e, "--tenant") } : {},
231
+ let t = R(e, "--api-url");
232
+ if (!t) return P.error("--target api requires --api-url <url> (the running instance)"), 1;
233
+ let i = R(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
234
+ if (!i) return P.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
235
+ if (R(e, "--exclude") !== void 0) return P.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;
236
+ let a = R(e, "--tables"), o = {
237
+ ...R(e, "--profile") ? { profile: R(e, "--profile") } : {},
238
+ ...R(e, "--tenant") ? { tenant: R(e, "--tenant") } : {},
239
239
  ...a ? { tables: a.split(",").map((e) => e.trim()).filter(Boolean) } : {},
240
- ...V(e, "--consistency") === "snapshot" ? { consistency: "snapshot" } : {},
241
- ...V(e, "--compression") ? { compression: V(e, "--compression") } : {},
240
+ ...R(e, "--consistency") === "snapshot" ? { consistency: "snapshot" } : {},
241
+ ...R(e, "--compression") ? { compression: R(e, "--compression") } : {},
242
242
  ...e.includes("--assets") ? { assets: !0 } : {},
243
243
  ...n ? { bundleKey: n } : {}
244
244
  }, s = await fetch(`${t.replace(/\/+$/, "")}/_voltro/admin/export`, {
@@ -249,26 +249,35 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
249
249
  },
250
250
  body: JSON.stringify(o)
251
251
  });
252
- if (!s.ok) return I.error(`api export failed: HTTP ${s.status} — ${await s.text()}`), 1;
253
- if (n) return I.info("api export complete (pushed to storage)", {
252
+ if (!s.ok) return P.error(`api export failed: HTTP ${s.status} — ${await s.text()}`), 1;
253
+ if (n) return P.info("api export complete (pushed to storage)", {
254
254
  via: t,
255
255
  ...await s.json()
256
256
  }), 0;
257
257
  let { resolve: c } = await import("node:path"), l = new Uint8Array(await s.arrayBuffer());
258
- return await M((async function* () {
258
+ return await de((async function* () {
259
259
  yield l;
260
- })(), c(process.cwd(), r)), I.info("api export complete (downloaded from the running instance)", {
260
+ })(), c(process.cwd(), r)), P.info("api export complete (downloaded from the running instance)", {
261
261
  outDir: r,
262
262
  tables: s.headers.get("x-export-tables") ?? void 0,
263
263
  rows: s.headers.get("x-export-rows") ?? void 0,
264
264
  masked: s.headers.get("x-export-masked") ?? void 0
265
265
  }), 0;
266
266
  }
267
- if (!r) return I.error("a <outDir> is required for a direct export"), 1;
268
- let i = V(e, "--profile"), a = i ? await s(i) : {}, { store: o, snapshot: c, dialect: l } = await K(), u = e.includes("--assets") || a.assets === !0, d = u ? S({}) : void 0, p = V(e, "--compression") ?? a.compression, m = V(e, "--consistency") === "snapshot" ? "snapshot" : a.consistency ?? "live", h = a.masking, g = a.subset, _ = a.scope ?? ye(e, c.tables.map((e) => e.name)), v = h ? await be() : void 0, y = _.kind === "tenant" ? await Se() : void 0, b = y ? { tenantTables: y } : {};
267
+ if (!r) return P.error("a <outDir> is required for a direct export"), 1;
268
+ let i = R(e, "--profile"), a = i ? await s(i, { allowPath: !0 }) : {}, { store: o, snapshot: c, dialect: l } = await U(), u = e.includes("--assets") || a.assets === !0, d = u ? S({}) : void 0, p = R(e, "--compression") ?? a.compression, m = R(e, "--consistency") === "snapshot" ? "snapshot" : a.consistency ?? "live", h = a.masking, g = a.subset, _ = a.scope ?? Se(e, c.tables.map((e) => e.name)), v = h ? await Ce() : void 0, y = _.kind === "tenant" ? await Te() : void 0, b = y ? { tenantTables: y } : {};
269
269
  if (e.includes("--dry-run")) {
270
- if (!h) return I.error("--dry-run previews masking, but the profile sets no masking policy"), 1;
271
- let e = await f.runPromise(ae({
270
+ let e = _.kind === "tables" && _.tables ? [..._.tables] : c.tables.map((e) => e.name);
271
+ if (P.info("export DRY RUN — nothing was written", {
272
+ outDir: r,
273
+ tables: e.length,
274
+ scope: _.kind,
275
+ consistency: m,
276
+ compression: p ?? "zstd",
277
+ assets: u,
278
+ masking: h ? "profile policy applies" : "none"
279
+ }), P.info(`would export: ${e.join(", ")}`), !h) return 0;
280
+ let t = await f.runPromise(ae({
272
281
  store: o,
273
282
  snapshot: c,
274
283
  masking: h,
@@ -278,9 +287,9 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
278
287
  ...b
279
288
  }
280
289
  }));
281
- return Ce(e), +(e.unclassified.length > 0);
290
+ return Ee(t), +(t.unclassified.length > 0);
282
291
  }
283
- if (J(r)) {
292
+ if (G(r)) {
284
293
  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-"));
285
294
  try {
286
295
  let t = await f.runPromise(O({
@@ -296,17 +305,17 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
296
305
  ...p ? { compression: p } : {},
297
306
  ...h ? { masking: h } : {},
298
307
  ...v ? { classification: v } : {},
299
- onProgress: R
308
+ onProgress: I
300
309
  })), n = T({
301
310
  dir: s,
302
- ...d ? { blobs: ce(d, () => q(o)) } : {}
303
- }), a = _e(e);
311
+ ...d ? { blobs: le(d, () => W(o)) } : {}
312
+ }), a = be(e);
304
313
  if (a) {
305
- let t = Y(e);
306
- if (!t) return I.error("--encrypt requires --passphrase <p> or VOLTRO_BUNDLE_KEY (a dedicated key, NOT the transfer secret)"), 1;
314
+ let t = K(e);
315
+ if (!t) return P.error("--encrypt requires --passphrase <p> or VOLTRO_BUNDLE_KEY (a dedicated key, NOT the transfer secret)"), 1;
307
316
  n = ie(n, t);
308
317
  }
309
- return await ve(n, i(process.cwd(), r)), I.info("export complete (single file)", {
318
+ return await xe(n, i(process.cwd(), r)), P.info("export complete (single file)", {
310
319
  file: r,
311
320
  encrypted: a,
312
321
  assets: u,
@@ -333,10 +342,10 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
333
342
  ...p ? { compression: p } : {},
334
343
  ...h ? { masking: h } : {},
335
344
  ...v ? { classification: v } : {},
336
- ...d ? { assets: oe(d, () => q(o)) } : {},
337
- onProgress: R
345
+ ...d ? { assets: se(d, () => W(o)) } : {},
346
+ onProgress: I
338
347
  }));
339
- return I.info("export complete", {
348
+ return P.info("export complete", {
340
349
  outDir: r,
341
350
  tables: x.tables.length,
342
351
  rows: x.tables.reduce((e, t) => e + t.rowCount, 0),
@@ -344,55 +353,82 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
344
353
  compression: x.compression,
345
354
  ...x.masking ? { maskedColumns: x.masking.transformed.length } : {}
346
355
  }), 0;
347
- }, Q = async (e) => {
348
- let t = V(e, "--target") === "api", n = V(e, "--bundle-key"), i = H(e);
349
- if (!i && !(t && n)) return I.error("usage: voltro data import <bundleDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--bundle-key <storage-key>] [--mode upsert|append|replace] [--on-conflict skip|fail] [--atomic] [--assets] [--no-verify] [--force] [--allow-live] (--force: import despite schema drift AND cross-dialect warnings)"), 1;
350
- let a = V(e, "--mode") ?? "upsert", o = V(e, "--on-conflict") ?? "skip";
356
+ }, Y = () => "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.", X = (e) => {
357
+ let t = R(e, "--tables");
358
+ return {
359
+ ...t ? { tables: t.split(",").map((e) => e.trim()).filter(Boolean) } : {},
360
+ ...e.includes("--dry-run") ? { dryRun: !0 } : {}
361
+ };
362
+ }, Z = (e, t) => {
363
+ if (e.includes("--dry-run")) {
364
+ P.info("import DRY RUN — nothing was written", t), P.info(Y());
365
+ return;
366
+ }
367
+ P.info("import complete", t);
368
+ }, Oe = (e) => {
369
+ let t = e;
370
+ if (t?._tag !== "RowsRefusedError" || !Array.isArray(t.rows)) return;
371
+ let n = t.rows, r = n.filter((e) => !e.derived), i = Number(t.totalCount ?? n.length), a = Number(t.primaryCount ?? r.length);
372
+ P.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.`);
373
+ for (let e of r) P.error(` ${e.table} ${e.id} ${e.reason}`);
374
+ let o = i - a;
375
+ o > 0 && P.error(` … and ${o} row(s) behind them. Fix the ${a} above and re-run; they resolve with their parents.`);
376
+ }, ke = async (e) => {
377
+ let t = R(e, "--target") === "api", n = R(e, "--bundle-key"), i = z(e);
378
+ if (!i && !(t && n)) return P.error("usage: voltro data import <bundleDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--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;
379
+ let a = R(e, "--mode") ?? "upsert", o = R(e, "--on-conflict") ?? "skip";
351
380
  if (t) {
352
- let t = V(e, "--api-url");
353
- if (!t) return I.error("--target api requires --api-url <url> (the running instance)"), 1;
354
- let r = V(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
355
- if (!r) return I.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
356
- let s = {
381
+ let t = R(e, "--api-url");
382
+ if (!t) return P.error("--target api requires --api-url <url> (the running instance)"), 1;
383
+ let r = R(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
384
+ if (!r) return P.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
385
+ let s = R(e, "--tables"), c = {
357
386
  authorization: `Bearer ${r}`,
358
387
  "x-import-mode": a,
359
388
  "x-import-on-conflict": o,
360
389
  ...e.includes("--atomic") ? { "x-import-atomic": "1" } : {},
361
- ...e.includes("--force") ? { "x-import-force": "1" } : {}
362
- }, { resolve: c } = await import("node:path"), l;
363
- if (n) s["content-type"] = "application/json", l = JSON.stringify({ bundleKey: n });
364
- else if (J(i)) {
365
- let t = X(c(process.cwd(), i)), n = Y(e);
390
+ ...e.includes("--force") ? { "x-import-force": "1" } : {},
391
+ ...e.includes("--dry-run") ? { "x-import-dry-run": "1" } : {},
392
+ ...s ? { "x-import-tables": s } : {}
393
+ }, { resolve: l } = await import("node:path"), u;
394
+ if (n) c["content-type"] = "application/json", u = JSON.stringify({ bundleKey: n });
395
+ else if (G(i)) {
396
+ let t = q(l(process.cwd(), i)), n = K(e);
366
397
  n && (t = w(t, n));
367
398
  let r = [];
368
399
  for await (let e of t) r.push(Buffer.from(e));
369
- s["content-type"] = "application/octet-stream", l = Buffer.concat(r);
400
+ c["content-type"] = "application/octet-stream", u = Buffer.concat(r);
370
401
  } else {
371
- await (await import("node:fs/promises")).access(c(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.");
402
+ await (await import("node:fs/promises")).access(l(process.cwd(), i, "assets", "index.ndjson")).then(() => !0, () => !1) && P.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.");
372
403
  let e = [];
373
- for await (let t of T({ dir: c(process.cwd(), i) })) e.push(Buffer.from(t));
374
- s["content-type"] = "application/octet-stream", l = Buffer.concat(e);
404
+ for await (let t of T({ dir: l(process.cwd(), i) })) e.push(Buffer.from(t));
405
+ c["content-type"] = "application/octet-stream", u = Buffer.concat(e);
375
406
  }
376
- let u = await fetch(`${t.replace(/\/+$/, "")}/_voltro/admin/import`, {
407
+ let d = await fetch(`${t.replace(/\/+$/, "")}/_voltro/admin/import`, {
377
408
  method: "POST",
378
- body: l,
379
- headers: s
409
+ body: u,
410
+ headers: c
380
411
  });
381
- return u.ok ? (I.info("api import complete (imported by the running instance)", {
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", {
415
+ via: t,
416
+ ...f
417
+ }), P.info(Y()), 0) : (P.info("api import complete (imported by the running instance)", {
382
418
  via: t,
383
- ...await u.json()
384
- }), 0) : (I.error(`api import failed: HTTP ${u.status} — ${await u.text()}`), 1);
419
+ ...f
420
+ }), 0);
385
421
  }
386
- if (!i) return I.error("a <bundleDir> is required for a direct import"), 1;
387
- if (!await B(e)) return 1;
388
- let { store: s, snapshot: c } = await K(), l = e.includes("--force"), u = (e) => I.warn(e), d = e.includes("--assets") ? S({}) : void 0, p = U() === "postgres" ? (await import("@voltro/sql-postgres")).makePgCopySession(r()) : void 0;
389
- if (J(i)) {
422
+ if (!i) return P.error("a <bundleDir> is required for a direct import"), 1;
423
+ if (!await L(e)) return 1;
424
+ let { store: s, snapshot: c } = await U(), l = e.includes("--force"), u = (e) => P.warn(e), d = e.includes("--assets") ? S({}) : void 0, p = B() === "postgres" ? (await import("@voltro/sql-postgres")).makePgCopySession(r()) : void 0;
425
+ if (G(i)) {
390
426
  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-"));
391
427
  try {
392
- let t = X(r(process.cwd(), i)), n = Y(e);
393
- n && (t = w(t, n)), await le(t, {
428
+ let t = q(r(process.cwd(), i)), n = K(e);
429
+ n && (t = w(t, n)), await ue(t, {
394
430
  dir: h,
395
- blobs: se(d ?? S({}))
431
+ blobs: ce(d ?? S({}))
396
432
  });
397
433
  let m = await f.runPromise(k({
398
434
  store: s,
@@ -401,16 +437,17 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
401
437
  onConflict: o,
402
438
  atomic: e.includes("--atomic"),
403
439
  verify: !e.includes("--no-verify"),
404
- targetDialect: U(),
440
+ targetDialect: B(),
405
441
  allowIncompatible: l,
406
442
  targetSnapshot: c,
407
443
  force: l,
408
444
  onWarn: u,
409
- onProgress: R,
445
+ onProgress: I,
410
446
  assetsAlreadyRestored: !0,
447
+ ...X(e),
411
448
  ...p ? { copyLoader: p } : {}
412
449
  }));
413
- return I.info("import complete (single file)", {
450
+ return Z(e, {
414
451
  file: i,
415
452
  tables: m.tables.length,
416
453
  rows: m.tables.reduce((e, t) => e + t.rowCount, 0)
@@ -429,22 +466,25 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
429
466
  onConflict: o,
430
467
  atomic: e.includes("--atomic"),
431
468
  verify: !e.includes("--no-verify"),
432
- targetDialect: U(),
469
+ targetDialect: B(),
433
470
  allowIncompatible: l,
434
471
  targetSnapshot: c,
435
472
  force: l,
436
473
  onWarn: u,
437
- onProgress: R,
438
- ...d ? { assets: j(d) } : {},
474
+ onProgress: I,
475
+ ...X(e),
476
+ ...d ? { assets: oe(d) } : {},
439
477
  ...p ? { copyLoader: p } : {}
440
- })).finally(() => p?.close());
441
- return I.info("import complete", {
478
+ })).catch((e) => {
479
+ throw Oe(e), e;
480
+ }).finally(() => p?.close());
481
+ return Z(e, {
442
482
  bundleDir: i,
443
483
  tables: m.tables.length,
444
484
  rows: m.tables.reduce((e, t) => e + t.rowCount, 0),
445
485
  assets: m.assets.count
446
486
  }), 0;
447
- }, $ = () => {
487
+ }, Q = () => {
448
488
  let e = r();
449
489
  return {
450
490
  ...e.url ? { url: e.url } : {},
@@ -454,98 +494,186 @@ var N = "voltro-backup-stamp.json", ue = (e) => ({
454
494
  ...e.password ? { password: e.password } : {},
455
495
  ...e.database ? { database: e.database } : {}
456
496
  };
457
- }, Te = async (e) => {
458
- let t = H(e);
459
- if (!t) return I.error("usage: voltro data backup <outDir> (native pg_dump/mysqldump/… + consistent artifact)"), 1;
460
- let n = U(), { mkdir: r, writeFile: i } = await import("node:fs/promises"), { join: a } = await import("node:path");
497
+ }, Ae = async (e) => {
498
+ let t = z(e);
499
+ if (!t) return P.error("usage: voltro data backup <outDir> (native pg_dump/mysqldump/… + consistent artifact)"), 1;
500
+ let n = B(), { mkdir: r, writeFile: i } = await import("node:fs/promises"), { join: a } = await import("node:path");
461
501
  await r(t, { recursive: !0 });
462
502
  let o = a(t, C(n));
463
- await f.runPromise(A(ne(n, $(), o)));
464
- let s = ue({
503
+ await f.runPromise(A(ne(n, Q(), o)));
504
+ let s = fe({
465
505
  dialect: n,
466
- schemaFingerprint: await L(n),
467
- voltroVersion: await me(),
506
+ schemaFingerprint: await F(n),
507
+ voltroVersion: await ge(),
468
508
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
469
509
  });
470
- return await i(a(t, N), `${JSON.stringify(s, null, 2)}\n`, "utf8"), I.info("native backup complete", {
510
+ return await i(a(t, j), `${JSON.stringify(s, null, 2)}\n`, "utf8"), P.info("native backup complete", {
471
511
  dialect: n,
472
512
  artifact: o,
473
- stamp: N,
513
+ stamp: j,
474
514
  schemaFingerprint: s.schemaFingerprint ?? "<none>",
475
515
  assets: "NOT included — use `voltro data export --assets` for rows + blobs"
476
516
  }), 0;
477
- }, Ee = async (e, t) => {
478
- let n = U(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(e, C(n)), o = fe({
479
- flagUrl: V(t, "--drill-url"),
517
+ }, je = async (e, t) => {
518
+ let n = B(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(e, C(n)), o = me({
519
+ flagUrl: R(t, "--drill-url"),
480
520
  envUrl: process.env.DRILL_DB_URL,
481
- liveKeys: ge()
521
+ liveKeys: ye()
482
522
  });
483
- if (!o.ok) return I.error(`restore --drill: ${o.error}`), 1;
484
- let s = await i(r(e, N), "utf8").then(P, () => null);
485
- 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;
486
- I.info("restore drill: restoring into throwaway DB", {
523
+ if (!o.ok) return P.error(`restore --drill: ${o.error}`), 1;
524
+ let s = await i(r(e, j), "utf8").then(M, () => null);
525
+ if (s && s.dialect !== n) return P.error(`restore --drill: backup is a ${s.dialect} dump but DB_DIALECT is ${n} — cross-dialect restore is never valid.`), 1;
526
+ P.info("restore drill: restoring into throwaway DB", {
487
527
  dialect: n,
488
- target: F(o.url)
528
+ target: N(o.url)
489
529
  }), await f.runPromise(A(D(n, { url: o.url }, a)));
490
530
  let c = null, l = 0;
491
531
  try {
492
- let e = await W(n, { url: o.url });
532
+ let e = await V(n, { url: o.url });
493
533
  c = g(e), l = e.tables.length;
494
534
  } catch (e) {
495
- return I.error(`restore --drill: could not introspect the restored DB — ${String(e)}`), 1;
535
+ return P.error(`restore --drill: could not introspect the restored DB — ${String(e)}`), 1;
496
536
  }
497
- let u = pe({
537
+ let u = he({
498
538
  stampFingerprint: s?.schemaFingerprint ?? null,
499
539
  restoredFingerprint: c,
500
540
  restoredTableCount: l
501
541
  });
502
- for (let e of u.lines) u.pass ? I.info(`restore drill: ${e}`) : I.error(`restore drill: ${e}`);
542
+ for (let e of u.lines) u.pass ? P.info(`restore drill: ${e}`) : P.error(`restore drill: ${e}`);
503
543
  return +!u.pass;
504
- }, De = async (e) => {
505
- let t = H(e);
506
- 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;
507
- if (e.includes("--drill")) return Ee(t, e);
508
- if (!await B(e)) return 1;
509
- let n = U(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t, C(n)), o = de({
510
- stamp: await i(r(t, N), "utf8").then(P, () => null),
544
+ }, Me = async (e) => {
545
+ let t = z(e);
546
+ if (!t) return P.error("usage: voltro data restore <backupDir> [--allow-live] [--api-url <url>] [--force] [--drill [--drill-url <url>]] (native pg_restore/mysql/…)"), 1;
547
+ if (e.includes("--drill")) return je(t, e);
548
+ if (!await L(e)) return 1;
549
+ let n = B(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t, C(n)), o = pe({
550
+ stamp: await i(r(t, j), "utf8").then(M, () => null),
511
551
  currentDialect: n,
512
- currentFingerprint: await L(n)
552
+ currentFingerprint: await F(n)
513
553
  });
514
- for (let e of o.info) I.info(`backup stamp: ${e}`);
515
- if (o.warn && I.warn(o.warn), o.refuse) {
516
- if (!e.includes("--force")) return I.error(`restore refused: ${o.refuse} (pass --force to override)`), 1;
517
- I.warn(`restore proceeding despite: ${o.refuse} (--force)`);
554
+ for (let e of o.info) P.info(`backup stamp: ${e}`);
555
+ if (o.warn && P.warn(o.warn), o.refuse) {
556
+ if (!e.includes("--force")) return P.error(`restore refused: ${o.refuse} (pass --force to override)`), 1;
557
+ P.warn(`restore proceeding despite: ${o.refuse} (--force)`);
518
558
  }
519
- return await f.runPromise(A(D(n, $(), a))), I.info("native restore complete", {
559
+ return await f.runPromise(A(D(n, Q(), a))), P.info("native restore complete", {
520
560
  dialect: n,
521
561
  artifact: a
522
562
  }), 0;
523
- }, Oe = async (e) => {
524
- let t = H(e);
525
- if (!t) return I.error("usage: voltro data inspect <file.vbundle> [--json] [--passphrase <p>]"), 1;
526
- let { resolve: n } = await import("node:path"), r = n(process.cwd(), t), i = Y(e), a = () => {
527
- let e = X(r);
528
- return i ? w(e, i) : e;
563
+ }, Ne = async (e) => {
564
+ let t = z(e);
565
+ if (!t) return P.error("usage: voltro data inspect <file.vbundle|dir> [--json] [--passphrase <p>]"), 1;
566
+ let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = n(process.cwd(), t);
567
+ if ((await i.stat(a).catch(() => void 0))?.isDirectory()) {
568
+ let n = r(a, "manifest.json"), o = r(a, "README.md");
569
+ if (!e.includes("--json")) {
570
+ let e = await i.readFile(o, "utf8").catch(() => void 0);
571
+ if (e !== void 0) return console.log(e), 0;
572
+ }
573
+ let s = await i.readFile(n, "utf8").catch(() => void 0);
574
+ return s === void 0 ? (P.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
+ }
576
+ let o = K(e), s = () => {
577
+ let e = q(a);
578
+ return o ? w(e, o) : e;
529
579
  };
530
580
  if (e.includes("--json")) {
531
- let e = (await E(a(), ["manifest.json"])).get("manifest.json");
532
- return e ? (console.log(e.toString("utf8")), 0) : (I.error("no manifest.json found in the bundle"), 1);
581
+ let e = (await E(s(), ["manifest.json"])).get("manifest.json");
582
+ return e ? (console.log(e.toString("utf8")), 0) : (P.error("no manifest.json found in the bundle"), 1);
533
583
  }
534
- let o = (await E(a(), ["README.md"])).get("README.md");
535
- if (o) return console.log(o.toString("utf8")), 0;
536
- let s = (await E(a(), ["manifest.json"])).get("manifest.json");
537
- return s ? (console.log(s.toString("utf8")), 0) : (I.error("bundle has no README.md or manifest.json"), 1);
538
- }, ke = async (e) => {
539
- let t = H(e), n = H(e.filter((e) => e !== t));
540
- if (!t || !n) return I.error("usage: voltro data unpack <file.vbundle> <dir> [--passphrase <p>]"), 1;
541
- let { resolve: r } = await import("node:path"), i = X(r(process.cwd(), t)), a = Y(e);
542
- return a && (i = w(i, a)), await M(i, r(process.cwd(), n)), I.info("unpack complete", {
584
+ let c = (await E(s(), ["README.md"])).get("README.md");
585
+ if (c) return console.log(c.toString("utf8")), 0;
586
+ let l = (await E(s(), ["manifest.json"])).get("manifest.json");
587
+ return l ? (console.log(l.toString("utf8")), 0) : (P.error("bundle has no README.md or manifest.json"), 1);
588
+ }, Pe = async (e) => {
589
+ let t = z(e), n = z(e.filter((e) => e !== t));
590
+ if (!t || !n) return P.error("usage: voltro data unpack <file.vbundle> <dir> [--passphrase <p>]"), 1;
591
+ let { resolve: r } = await import("node:path"), i = q(r(process.cwd(), t)), a = K(e);
592
+ return a && (i = w(i, a)), await de(i, r(process.cwd(), n)), P.info("unpack complete", {
543
593
  file: t,
544
594
  dir: n
545
595
  }), 0;
546
- }, Ae = async (e) => {
596
+ }, $ = {
597
+ export: {
598
+ common: [
599
+ "--target",
600
+ "--tables",
601
+ "--profile",
602
+ "--tenant",
603
+ "--assets",
604
+ "--compression",
605
+ "--consistency",
606
+ "--json"
607
+ ],
608
+ directOnly: [
609
+ "--exclude",
610
+ "--passphrase",
611
+ "--dry-run",
612
+ "--encrypt"
613
+ ],
614
+ apiOnly: [
615
+ "--api-url",
616
+ "--token",
617
+ "--bundle-key"
618
+ ]
619
+ },
620
+ import: {
621
+ common: [
622
+ "--target",
623
+ "--tables",
624
+ "--mode",
625
+ "--on-conflict",
626
+ "--atomic",
627
+ "--assets",
628
+ "--force",
629
+ "--dry-run",
630
+ "--json"
631
+ ],
632
+ directOnly: [
633
+ "--no-verify",
634
+ "--allow-live",
635
+ "--passphrase"
636
+ ],
637
+ apiOnly: [
638
+ "--api-url",
639
+ "--token",
640
+ "--bundle-key"
641
+ ]
642
+ },
643
+ unpack: { common: ["--passphrase", "--json"] },
644
+ inspect: { common: ["--json", "--passphrase"] },
645
+ backup: { common: ["--json", "--assets"] },
646
+ restore: { common: [
647
+ "--json",
648
+ "--allow-live",
649
+ "--force",
650
+ "--drill",
651
+ "--drill-url",
652
+ "--api-url"
653
+ ] }
654
+ }, Fe = (e) => e.split("=")[0], Ie = (e, t) => {
655
+ let n = $[e];
656
+ if (!n) return;
657
+ let r = R(t, "--target") === "api" ? "api" : "direct", i = /* @__PURE__ */ new Set([...n.common, ...r === "api" ? n.apiOnly ?? [] : n.directOnly ?? []]), a = new Set(r === "api" ? n.directOnly ?? [] : n.apiOnly ?? []);
658
+ for (let n of t) {
659
+ if (!n.startsWith("--")) continue;
660
+ let t = Fe(n);
661
+ if (i.has(t)) continue;
662
+ 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
+ let o = Object.entries($).filter(([n, r]) => n !== e && [
664
+ ...r.common,
665
+ ...r.directOnly ?? [],
666
+ ...r.apiOnly ?? []
667
+ ].includes(t)).map(([e]) => e);
668
+ 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
+ }
670
+ }, Le = async (e) => {
547
671
  let [t, ...n] = e;
548
- return t === "export" ? we(n) : t === "import" ? Q(n) : t === "unpack" ? ke(n) : t === "inspect" ? Oe(n) : t === "backup" ? Te(n) : t === "restore" ? De(n) : (I.error("usage: voltro data <export|import|unpack|inspect|backup|restore> … (logical: export/import; native: backup/restore)"), t ? 2 : 1);
672
+ if (t !== void 0 && $[t] !== void 0) {
673
+ let e = Ie(t, n);
674
+ if (e) return P.error(e), 2;
675
+ }
676
+ return t === "export" ? De(n) : t === "import" ? ke(n) : t === "unpack" ? Pe(n) : t === "inspect" ? Ne(n) : t === "backup" ? Ae(n) : t === "restore" ? Me(n) : (P.error("usage: voltro data <export|import|unpack|inspect|backup|restore> … (logical: export/import; native: backup/restore)"), t ? 2 : 1);
549
677
  };
550
678
  //#endregion
551
- export { Ae as runDataCommand };
679
+ export { Le as runDataCommand };