@voltro/cli 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +190 -0
- package/dist/{apiBuild-370ITNRk.js → apiBuild-BJtiqoOu.js} +2 -2
- package/dist/apiBuild-BOczx9-m.js +2 -0
- package/dist/bin.js +3 -3
- package/dist/{commands-D-teL_TM.js → commands-8A5RU3MW.js} +1785 -1564
- package/dist/dbCommand-DNQ6l-su.js +2 -0
- package/dist/{dbCommand-Dmi-vHfA.js → dbCommand-OPMtO0sX.js} +225 -211
- package/dist/{dev-CKyW_LAv.js → dev-DGjEvvkV.js} +1321 -1300
- package/dist/{dev-CPKbckaN.js → dev-HdG7JdSk.js} +1 -1
- package/dist/{fileConventions-COg0ZytD.js → fileConventions-Cu0tM-Bl.js} +22 -2
- package/dist/{frameworkTableAssembly-DCLhpHsF.js → frameworkTableAssembly-BA2bAmYN.js} +1 -1
- package/dist/frameworkTableAssembly-D1zJSgOU.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-CyVMeofr.js → inspectMetrics-CYDSO3za.js} +1 -1
- package/dist/{serveCommand-CU3Sw4ZM.js → serveCommand-C3lq3mFd.js} +7 -5
- package/dist/serveEntry.js +2 -2
- package/dist/{start-D8ATvTzo.js → start-BtgatJN3.js} +1 -1
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/authentication.md +29 -0
- package/templates/agent-docs/cli.md +80 -0
- package/templates/agent-docs/data.md +11 -3
- package/templates/agent-docs/database/migrations.md +21 -0
- package/templates/agent-docs/plugins.md +1 -1
- package/templates/agent-docs/schema-driven-ui.md +2 -1
- package/templates/agent-docs/whats-new.md +84 -50
- 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-storage/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-search/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 +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- 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-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/dist/apiBuild-DatWP5hh.js +0 -2
- package/dist/dbCommand-B3CeMU3u.js +0 -2
- package/dist/frameworkTableAssembly-DYzb2elg.js +0 -2
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { i as e, n as t, r as n, s as r, t as i } from "./seedRunner-D6eu-u5U.js";
|
|
2
|
-
import { t as a } from "./frameworkTableAssembly-
|
|
3
|
-
import { g as o } from "./fileConventions-
|
|
2
|
+
import { t as a } from "./frameworkTableAssembly-BA2bAmYN.js";
|
|
3
|
+
import { g as o } from "./fileConventions-Cu0tM-Bl.js";
|
|
4
4
|
import { a as s, n as c, t as l } from "./cliArgs-qdZSElM3.js";
|
|
5
5
|
import { basename as u, join as d, relative as f, resolve as p } from "node:path";
|
|
6
6
|
import { pathToFileURL as m } from "node:url";
|
|
7
7
|
import { Effect as h } from "effect";
|
|
8
|
-
import { createLogger as
|
|
9
|
-
import { promises as
|
|
10
|
-
import { InMemoryDataStore as
|
|
11
|
-
import { SqlClient as
|
|
12
|
-
import { actorsTable as
|
|
13
|
-
import { createHash as
|
|
14
|
-
import { applyPlan as
|
|
8
|
+
import { createLogger as g } from "@voltro/logger";
|
|
9
|
+
import { promises as _, writeSync as v } from "node:fs";
|
|
10
|
+
import { InMemoryDataStore as ee } from "@voltro/runtime";
|
|
11
|
+
import { SqlClient as y } from "@effect/sql";
|
|
12
|
+
import { actorsTable as te, frameworkTables as b, isMigrationDefinition as x, registerCoreTables as ne } from "@voltro/database";
|
|
13
|
+
import { createHash as re } from "node:crypto";
|
|
14
|
+
import { applyPlan as S, applySchema as C, declaredSnapshot as ie, destructiveScope as ae, fingerprintSchema as w, ignoreTablesFromEnv as T, introspectSchema as oe, planMigrations as E, rollbackFileBasedMigration as se, runFileBasedMigrations as ce, shortFingerprint as D, squashMigrationPlans as le, unblockLossy as ue } from "@voltro/database/sql";
|
|
15
15
|
//#region src/cliError.ts
|
|
16
|
-
var
|
|
16
|
+
var O = class extends Error {
|
|
17
17
|
isCliError = !0;
|
|
18
18
|
code;
|
|
19
19
|
constructor(e, t = 1) {
|
|
20
20
|
super(e), this.name = "CliError", this.code = t;
|
|
21
21
|
}
|
|
22
|
-
},
|
|
22
|
+
}, de = (e) => typeof e == "object" && !!e && e.isCliError === !0, k = /\.migration\.ts$/, A = async (e, t) => {
|
|
23
23
|
let n = [];
|
|
24
24
|
for (let r of e) {
|
|
25
25
|
let e;
|
|
@@ -29,7 +29,7 @@ var k = class extends Error {
|
|
|
29
29
|
t.warn("migration: failed to import", { file: r }, e);
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
|
-
let i = (e.default &&
|
|
32
|
+
let i = (e.default && x(e.default) ? e.default : void 0) ?? Object.values(e).find(x);
|
|
33
33
|
if (!i) {
|
|
34
34
|
t.warn("migration: no defineMigration export", { file: r });
|
|
35
35
|
continue;
|
|
@@ -39,7 +39,7 @@ var k = class extends Error {
|
|
|
39
39
|
idInFile: i.id,
|
|
40
40
|
idFromFile: u(r).replace(/\.migration\.ts$/, "")
|
|
41
41
|
});
|
|
42
|
-
let a = await
|
|
42
|
+
let a = await _.readFile(r, "utf8").catch(() => ""), o = re("sha256").update(a).digest("hex").slice(0, 16);
|
|
43
43
|
n.push({
|
|
44
44
|
file: r,
|
|
45
45
|
definition: i,
|
|
@@ -47,14 +47,14 @@ var k = class extends Error {
|
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
return [...n].sort((e, t) => e.definition.id < t.definition.id ? -1 : 1);
|
|
50
|
-
},
|
|
50
|
+
}, j = (e) => {
|
|
51
51
|
let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase(), n = t === "memory" ? "postgres" : t;
|
|
52
|
-
return h.runPromise(
|
|
53
|
-
},
|
|
54
|
-
let e = yield* (yield*
|
|
52
|
+
return h.runPromise(C(b, n).pipe(h.provide(e)));
|
|
53
|
+
}, M = (e) => h.runPromise(h.gen(function* () {
|
|
54
|
+
let e = yield* (yield* y.SqlClient)`SELECT id, status, hash, "appliedAt" FROM "_voltro_migrations"`.pipe(h.orElseSucceed(() => []));
|
|
55
55
|
return new Map(e.map((e) => [e.id, e]));
|
|
56
|
-
}).pipe(h.provide(e))),
|
|
57
|
-
yield* (yield*
|
|
56
|
+
}).pipe(h.provide(e))), N = (e, t) => h.runPromise(h.gen(function* () {
|
|
57
|
+
yield* (yield* y.SqlClient)`
|
|
58
58
|
INSERT INTO "_voltro_migrations"
|
|
59
59
|
(id, name, hash, status, "appliedAt", "durationMs", "errorMessage", "errorStack")
|
|
60
60
|
VALUES
|
|
@@ -69,14 +69,14 @@ var k = class extends Error {
|
|
|
69
69
|
"errorStack" = EXCLUDED."errorStack",
|
|
70
70
|
"updatedAt" = now()
|
|
71
71
|
`;
|
|
72
|
-
}).pipe(h.provide(e))),
|
|
72
|
+
}).pipe(h.provide(e))), fe = async (e, t, n) => {
|
|
73
73
|
if (e.length === 0) return {
|
|
74
74
|
applied: [],
|
|
75
75
|
failed: null,
|
|
76
76
|
skipped: []
|
|
77
77
|
};
|
|
78
|
-
await
|
|
79
|
-
let r = await
|
|
78
|
+
await j(t);
|
|
79
|
+
let r = await M(t), i = [], a = [], o = null;
|
|
80
80
|
for (let s of e) {
|
|
81
81
|
let e = r.get(s.definition.id);
|
|
82
82
|
if (e && e.status === "applied") {
|
|
@@ -97,7 +97,7 @@ var k = class extends Error {
|
|
|
97
97
|
name: s.definition.name
|
|
98
98
|
});
|
|
99
99
|
let c = Date.now(), l = [];
|
|
100
|
-
await
|
|
100
|
+
await N(t, {
|
|
101
101
|
id: s.definition.id,
|
|
102
102
|
name: s.definition.name,
|
|
103
103
|
hash: s.hash,
|
|
@@ -138,7 +138,7 @@ var k = class extends Error {
|
|
|
138
138
|
...a.stack ? { stack: a.stack } : {}
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
|
-
o = f, i.push(f), await
|
|
141
|
+
o = f, i.push(f), await N(t, {
|
|
142
142
|
id: s.definition.id,
|
|
143
143
|
name: s.definition.name,
|
|
144
144
|
hash: s.hash,
|
|
@@ -157,7 +157,7 @@ var k = class extends Error {
|
|
|
157
157
|
status: "applied",
|
|
158
158
|
durationMs: Date.now() - c,
|
|
159
159
|
steps: l
|
|
160
|
-
}), await
|
|
160
|
+
}), await N(t, {
|
|
161
161
|
id: s.definition.id,
|
|
162
162
|
name: s.definition.name,
|
|
163
163
|
hash: s.hash,
|
|
@@ -177,9 +177,9 @@ var k = class extends Error {
|
|
|
177
177
|
failed: o,
|
|
178
178
|
skipped: a
|
|
179
179
|
};
|
|
180
|
-
},
|
|
181
|
-
await
|
|
182
|
-
let i = await
|
|
180
|
+
}, pe = async (e, t, n, r = {}) => {
|
|
181
|
+
await j(t);
|
|
182
|
+
let i = await M(t), a = e.filter((e) => i.get(e.definition.id)?.status === "applied");
|
|
183
183
|
if (a.length === 0) return n.info("migration: nothing to roll back"), {
|
|
184
184
|
applied: [],
|
|
185
185
|
failed: null,
|
|
@@ -232,7 +232,7 @@ var k = class extends Error {
|
|
|
232
232
|
status: "applied",
|
|
233
233
|
durationMs: Date.now() - r,
|
|
234
234
|
steps: i
|
|
235
|
-
}), await
|
|
235
|
+
}), await N(t, {
|
|
236
236
|
id: e.definition.id,
|
|
237
237
|
name: e.definition.name,
|
|
238
238
|
hash: e.hash,
|
|
@@ -248,9 +248,9 @@ var k = class extends Error {
|
|
|
248
248
|
failed: c,
|
|
249
249
|
skipped: []
|
|
250
250
|
};
|
|
251
|
-
},
|
|
252
|
-
await
|
|
253
|
-
let n = await
|
|
251
|
+
}, P = async (e, t) => {
|
|
252
|
+
await j(t);
|
|
253
|
+
let n = await M(t);
|
|
254
254
|
return e.map((e) => {
|
|
255
255
|
let t = n.get(e.definition.id);
|
|
256
256
|
return t ? {
|
|
@@ -267,7 +267,7 @@ var k = class extends Error {
|
|
|
267
267
|
hashMatches: !0
|
|
268
268
|
};
|
|
269
269
|
});
|
|
270
|
-
},
|
|
270
|
+
}, F = (e) => {
|
|
271
271
|
if (!e.failed) return "";
|
|
272
272
|
let t = e.failed, n = [
|
|
273
273
|
`✗ Migration failed: ${t.migrationId} / step '${t.error?.stepName ?? "?"}'`,
|
|
@@ -280,7 +280,7 @@ var k = class extends Error {
|
|
|
280
280
|
""
|
|
281
281
|
];
|
|
282
282
|
return e.applied.length > 0 && n.unshift("", `(${e.applied.length} migration(s) applied successfully before the failure.)`), n.join("\n");
|
|
283
|
-
},
|
|
283
|
+
}, I = g({ scope: "voltro:db" }), me = /* @__PURE__ */ new Set([
|
|
284
284
|
"node_modules",
|
|
285
285
|
"dist",
|
|
286
286
|
"build",
|
|
@@ -288,17 +288,17 @@ var k = class extends Error {
|
|
|
288
288
|
".vite",
|
|
289
289
|
".next",
|
|
290
290
|
".git"
|
|
291
|
-
]),
|
|
291
|
+
]), L = async (e, t) => {
|
|
292
292
|
let n = [], r = async (e) => {
|
|
293
|
-
let i = await
|
|
293
|
+
let i = await _.readdir(e, { withFileTypes: !0 }).catch(() => []);
|
|
294
294
|
for (let a of i) {
|
|
295
|
-
if (
|
|
295
|
+
if (me.has(a.name)) continue;
|
|
296
296
|
let i = d(e, a.name);
|
|
297
297
|
a.isDirectory() ? await r(i) : a.isFile() && t.test(a.name) && n.push(i);
|
|
298
298
|
}
|
|
299
299
|
};
|
|
300
300
|
return await r(e), n;
|
|
301
|
-
},
|
|
301
|
+
}, R = (e) => p(e.find((e) => !e.startsWith("-")) ?? process.cwd()), z = () => {
|
|
302
302
|
let e = process.env.DB_MAX_CONNECTIONS ?? process.env.PG_MAX_CONNECTIONS, t = e !== void 0 && e !== "" ? Number(e) : NaN, n = Number.isInteger(t) && t > 0 ? { maxConnections: t } : {}, r = process.env.DB_SCHEMA, i = r && r.length > 0 ? { schema: r } : {}, a = process.env.DB_DIRECT_URL ?? process.env.DB_MIGRATE_URL ?? process.env.DB_URL ?? process.env.DB_PRIMARY_URL;
|
|
303
303
|
return a ? {
|
|
304
304
|
url: a,
|
|
@@ -313,63 +313,63 @@ var k = class extends Error {
|
|
|
313
313
|
...n,
|
|
314
314
|
...i
|
|
315
315
|
};
|
|
316
|
-
},
|
|
316
|
+
}, B = (e) => {
|
|
317
317
|
let t = e;
|
|
318
318
|
if (typeof t != "object" || !t) return !1;
|
|
319
319
|
if (t.code === "ERR_OUT_OF_RANGE" || t.cause?.code === "ERR_OUT_OF_RANGE") return !0;
|
|
320
320
|
let n = `${t.message ?? ""} ${t.cause?.message ?? ""} ${t.stack ?? ""}`;
|
|
321
321
|
return /ERR_OUT_OF_RANGE|out of range/i.test(n) && /pg-protocol|parseDataRow|buffer-reader|BufferReader/i.test(n);
|
|
322
|
-
},
|
|
322
|
+
}, V = "Live-DB introspection got a corrupt / misaligned response from the database. This happens when `voltro db plan` / `apply` / `drift` / `migrate` runs through a connection POOLER (Supabase Supavisor / PgBouncer) on a LARGE schema — the pooler mis-frames a big catalog response. The introspection already batches by table to keep responses small; if a batch still trips it:\n 1. Lower the batch: set `VOLTRO_INTROSPECT_BATCH` to a smaller number of tables (default 20 — try 5 or 1). This keeps `db plan` on the normal pooler, no direct connection needed.\n 2. Or use a DIRECT (non-pooler) connection: set `DB_DIRECT_URL` (or `DB_MIGRATE_URL`) to the direct database URL — the runtime keeps `DB_URL`, so only the migration path is affected. (Supabase: direct = `db.<project-ref>.supabase.co` [IPv6-only on newer projects]; pooler = `aws-0-<region>.pooler.supabase.com`.)", H = () => {
|
|
323
323
|
let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
324
|
-
if (e === "memory") throw new
|
|
324
|
+
if (e === "memory") throw new O("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
|
|
325
325
|
return e;
|
|
326
|
-
},
|
|
327
|
-
let t =
|
|
328
|
-
if (r.length === 0) return
|
|
326
|
+
}, U = async () => (await r(H())).makeSqlLayer(z()), he = async (e) => {
|
|
327
|
+
let t = R(e), n = e.includes("--dry-run"), r = await A(await L(t, k), I);
|
|
328
|
+
if (r.length === 0) return I.info("no migrations found", { root: t }), 0;
|
|
329
329
|
if (n) {
|
|
330
330
|
let e = /* @__PURE__ */ new Set();
|
|
331
331
|
try {
|
|
332
|
-
let t = await
|
|
332
|
+
let t = await P(r, await U());
|
|
333
333
|
e = new Set(t.filter((e) => e.status === "applied").map((e) => e.id));
|
|
334
334
|
} catch {
|
|
335
|
-
|
|
335
|
+
I.warn("migrate --dry-run: could not read the migration ledger — showing ALL as pending");
|
|
336
336
|
}
|
|
337
337
|
let t = r.filter((t) => !e.has(t.definition.id));
|
|
338
|
-
if (t.length === 0) return
|
|
339
|
-
|
|
338
|
+
if (t.length === 0) return I.info("migrate --dry-run: nothing pending — every migration is applied"), 0;
|
|
339
|
+
I.info(`migrate --dry-run: ${t.length} pending (nothing applied)`);
|
|
340
340
|
for (let e of t) console.log(` pending ${e.definition.id}`);
|
|
341
341
|
return 0;
|
|
342
342
|
}
|
|
343
|
-
let i = await
|
|
344
|
-
return i.failed ? (process.stderr.write(
|
|
343
|
+
let i = await fe(r, await U(), I);
|
|
344
|
+
return i.failed ? (process.stderr.write(F(i) + "\n"), 1) : (I.info("migrate complete", {
|
|
345
345
|
applied: i.applied.length,
|
|
346
346
|
skipped: i.skipped.length
|
|
347
347
|
}), 0);
|
|
348
|
-
},
|
|
349
|
-
let t =
|
|
350
|
-
if (r.length === 0) return
|
|
351
|
-
let i = await
|
|
352
|
-
return i.failed ? (process.stderr.write(
|
|
353
|
-
},
|
|
354
|
-
let t = await
|
|
348
|
+
}, ge = async (e) => {
|
|
349
|
+
let t = R(e), n = c(e, "--to"), r = await A(await L(t, k), I);
|
|
350
|
+
if (r.length === 0) return I.info("no migrations found", { root: t }), 0;
|
|
351
|
+
let i = await pe(r, await U(), I, n ? { to: n } : {});
|
|
352
|
+
return i.failed ? (process.stderr.write(F(i) + "\n"), 1) : (I.info("rollback complete", { reverted: i.applied.length }), 0);
|
|
353
|
+
}, _e = async (e) => {
|
|
354
|
+
let t = await A(await L(R(e), k), I);
|
|
355
355
|
if (t.length === 0) return console.log("no migrations found"), 0;
|
|
356
|
-
let n = await
|
|
356
|
+
let n = await P(t, await U());
|
|
357
357
|
for (let e of n) {
|
|
358
358
|
let t = e.status === "applied" ? "✓" : e.status === "failed" ? "✗" : e.status === "reverted" ? "↩" : "·", n = e.hashMatches ? "" : " (hash drift!)", r = e.appliedAt ? ` @ ${e.appliedAt.toISOString()}` : "";
|
|
359
359
|
console.log(` ${t} ${e.id} [${e.status}] ${e.name}${r}${n}`);
|
|
360
360
|
}
|
|
361
361
|
return 0;
|
|
362
|
-
},
|
|
362
|
+
}, ve = async (e) => {
|
|
363
363
|
let t = (c(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
364
|
-
if (t === "memory") return
|
|
365
|
-
store: new
|
|
364
|
+
if (t === "memory") return I.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), I.warn(" rows written by this run will NOT persist beyond the process. set DB_DIALECT=postgres or unset to seed the real DB."), {
|
|
365
|
+
store: new ee(),
|
|
366
366
|
dispose: async () => {},
|
|
367
367
|
dialectId: "memory"
|
|
368
368
|
};
|
|
369
369
|
let n = await r(t);
|
|
370
|
-
|
|
370
|
+
I.info(`voltro db seed: target store=${n.id} — rows will persist`);
|
|
371
371
|
let i = await n.makeStore({
|
|
372
|
-
sqlLayer: n.makeSqlLayer(
|
|
372
|
+
sqlLayer: n.makeSqlLayer(z()),
|
|
373
373
|
changeStrategy: "inline"
|
|
374
374
|
}), a = i;
|
|
375
375
|
return {
|
|
@@ -379,66 +379,77 @@ var k = class extends Error {
|
|
|
379
379
|
},
|
|
380
380
|
dialectId: n.id
|
|
381
381
|
};
|
|
382
|
-
},
|
|
383
|
-
let a =
|
|
384
|
-
if (l.length === 0) return
|
|
385
|
-
let { store: u, dispose: d, dialectId: f } = await
|
|
382
|
+
}, ye = async (r) => {
|
|
383
|
+
let a = R(r), o = c(r, "--id"), s = c(r, "--lifecycle"), l = await t(await L(a, i), I);
|
|
384
|
+
if (l.length === 0) return I.info("no seeds found", { root: a }), 0;
|
|
385
|
+
let { store: u, dispose: d, dialectId: f } = await ve(r);
|
|
386
386
|
try {
|
|
387
|
-
return o ? +((await e(l, o, u,
|
|
387
|
+
return o ? +((await e(l, o, u, I)).status === "failed") : +((await n((s ? l.filter((e) => e.definition.lifecycle === s) : l.filter((e) => e.definition.lifecycle === "boot")).map((e) => ({
|
|
388
388
|
...e,
|
|
389
389
|
definition: {
|
|
390
390
|
...e.definition,
|
|
391
391
|
lifecycle: "boot"
|
|
392
392
|
}
|
|
393
|
-
})), u,
|
|
393
|
+
})), u, I, { force: !0 })).filter((e) => e.status === "failed").length > 0);
|
|
394
394
|
} finally {
|
|
395
395
|
await d().catch((e) => {
|
|
396
|
-
|
|
396
|
+
I.warn("voltro db seed: store dispose failed", { error: String(e) });
|
|
397
397
|
});
|
|
398
398
|
}
|
|
399
|
-
},
|
|
400
|
-
let t = await
|
|
399
|
+
}, be = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", W = async (e) => {
|
|
400
|
+
let t = await L(e, o), n = [];
|
|
401
401
|
for (let r of t) {
|
|
402
402
|
let t;
|
|
403
403
|
try {
|
|
404
404
|
t = await import(m(r).href);
|
|
405
405
|
} catch (t) {
|
|
406
|
-
|
|
406
|
+
I.warn("db: failed to import schema file", {
|
|
407
407
|
file: f(e, r),
|
|
408
408
|
error: String(t)
|
|
409
409
|
});
|
|
410
410
|
continue;
|
|
411
411
|
}
|
|
412
|
-
for (let e of Object.values(t))
|
|
412
|
+
for (let e of Object.values(t)) be(e) && n.push(e);
|
|
413
413
|
}
|
|
414
414
|
return n;
|
|
415
|
-
},
|
|
415
|
+
}, G = (e) => {
|
|
416
416
|
let t = new Map(e.map((e) => [e.tableName, e]));
|
|
417
|
-
t.has("actors") || t.set("actors",
|
|
417
|
+
t.has("actors") || t.set("actors", te);
|
|
418
418
|
let n = t.get("actors"), r = t.get("tenants");
|
|
419
|
-
return
|
|
419
|
+
return ne({
|
|
420
420
|
actors: () => n,
|
|
421
421
|
...r === void 0 ? {} : { tenants: () => r }
|
|
422
422
|
}), [...t.values()];
|
|
423
|
-
},
|
|
423
|
+
}, K = (e) => {
|
|
424
424
|
let t = /* @__PURE__ */ new Map();
|
|
425
425
|
for (let n of e) {
|
|
426
426
|
let e = n.tableName;
|
|
427
427
|
e !== void 0 && !t.has(e) && t.set(e, n);
|
|
428
428
|
}
|
|
429
429
|
return [...t.values()];
|
|
430
|
+
}, q = (e) => {
|
|
431
|
+
let t = ae(process.env.VOLTRO_DESTRUCTIVE_OK);
|
|
432
|
+
if (t === void 0 || !e.operations.filter((e) => e.blocked).every((e) => e.classification === "lossy")) return {
|
|
433
|
+
plan: e,
|
|
434
|
+
acknowledged: !1
|
|
435
|
+
};
|
|
436
|
+
let n = ue(e, t);
|
|
437
|
+
return {
|
|
438
|
+
plan: n,
|
|
439
|
+
acknowledged: !n.operations.some((e) => e.blocked)
|
|
440
|
+
};
|
|
430
441
|
}, J = async (e, t) => h.runPromise(t.pipe(h.provide(e))), Y = async (e) => {
|
|
431
442
|
try {
|
|
432
|
-
return await J(e,
|
|
443
|
+
return await J(e, y.SqlClient.pipe(h.flatMap(oe)));
|
|
433
444
|
} catch (e) {
|
|
434
445
|
let t = String(e?.message ?? e);
|
|
435
|
-
if (
|
|
436
|
-
let n = /* @__PURE__ */ Error(`${
|
|
446
|
+
if (B(e) || /connection error/i.test(t)) {
|
|
447
|
+
let n = /* @__PURE__ */ Error(`${V}\n\nOriginal error: ${t}`);
|
|
437
448
|
throw n.cause = e, n;
|
|
438
449
|
}
|
|
439
450
|
throw e;
|
|
440
451
|
}
|
|
441
|
-
},
|
|
452
|
+
}, xe = {
|
|
442
453
|
safe: "\x1B[32m✓\x1B[0m",
|
|
443
454
|
"needs-default": "\x1B[32m⊕\x1B[0m",
|
|
444
455
|
"needs-backfill": "\x1B[33m⊕\x1B[0m",
|
|
@@ -446,7 +457,7 @@ var k = class extends Error {
|
|
|
446
457
|
lossy: "\x1B[31m✗\x1B[0m",
|
|
447
458
|
"online-required": "\x1B[36m◷\x1B[0m",
|
|
448
459
|
"multi-step": "\x1B[35m⇉\x1B[0m"
|
|
449
|
-
},
|
|
460
|
+
}, Se = (e) => {
|
|
450
461
|
let t = e.op, n;
|
|
451
462
|
switch (t.kind) {
|
|
452
463
|
case "create-table":
|
|
@@ -504,7 +515,7 @@ var k = class extends Error {
|
|
|
504
515
|
n = `ALTER TABLE ${t.table} DROP CHECK ${t.column}`;
|
|
505
516
|
break;
|
|
506
517
|
}
|
|
507
|
-
let r =
|
|
518
|
+
let r = xe[e.classification], i = e.reason ? ` [2m# ${e.reason}[0m` : "", a = e.blocked ? `\n [31m! ${e.blocked.fix}[0m` : "";
|
|
508
519
|
return ` ${r} ${n}${i}${a}`;
|
|
509
520
|
}, X = (e) => {
|
|
510
521
|
let t = e.summary, n = e.operations.length;
|
|
@@ -512,7 +523,7 @@ var k = class extends Error {
|
|
|
512
523
|
console.log(" (schema is up to date)\n");
|
|
513
524
|
return;
|
|
514
525
|
}
|
|
515
|
-
for (let t of e.operations) console.log(
|
|
526
|
+
for (let t of e.operations) console.log(Se(t));
|
|
516
527
|
console.log(), console.log(` safe: ${t.safe} needs-default: ${t.needsDefault} needs-backfill: ${t.needsBackfill}`), console.log(` rename: ${t.needsRenameAnnotation} lossy: ${t.lossy} blocked: ${t.blocked}`), console.log(` fingerprint: ${e.fromFingerprint.slice(0, 16)} → ${e.toFingerprint.slice(0, 16)}\n`);
|
|
517
528
|
}, Z = (e) => {
|
|
518
529
|
let t = c(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
|
|
@@ -520,15 +531,15 @@ var k = class extends Error {
|
|
|
520
531
|
let n = Number(t);
|
|
521
532
|
if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
|
|
522
533
|
return n;
|
|
523
|
-
},
|
|
524
|
-
let t = Z(e), n =
|
|
525
|
-
if (r.length === 0) return
|
|
534
|
+
}, Ce = async (e) => {
|
|
535
|
+
let t = Z(e), n = R(e), r = await W(n);
|
|
536
|
+
if (r.length === 0) return I.warn("db plan: no schema files found", {
|
|
526
537
|
root: n,
|
|
527
538
|
hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
|
|
528
539
|
}), 1;
|
|
529
|
-
let i = await a({ root: n }), o = K(
|
|
540
|
+
let i = await a({ root: n }), o = G(K([
|
|
530
541
|
...r,
|
|
531
|
-
...
|
|
542
|
+
...b,
|
|
532
543
|
...i
|
|
533
544
|
])), s = c(e, "--against");
|
|
534
545
|
if (s) {
|
|
@@ -536,115 +547,118 @@ var k = class extends Error {
|
|
|
536
547
|
r && (i.authorization = `Bearer ${r}`);
|
|
537
548
|
try {
|
|
538
549
|
let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`, r = await fetch(e, { headers: i });
|
|
539
|
-
if (!r.ok) return
|
|
550
|
+
if (!r.ok) return I.error(`db plan --against: remote returned ${r.status}`, { url: e }), 1;
|
|
540
551
|
let a = (await r.json()).drift?.liveSnapshot;
|
|
541
|
-
if (!a) return
|
|
542
|
-
let s =
|
|
552
|
+
if (!a) return I.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
|
|
553
|
+
let s = E({
|
|
543
554
|
declared: o,
|
|
544
555
|
live: a,
|
|
545
|
-
dialect:
|
|
546
|
-
...
|
|
556
|
+
dialect: H(),
|
|
557
|
+
...T(),
|
|
547
558
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
548
559
|
});
|
|
549
560
|
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), X(s), s.summary.blocked > 0 ? 2 : 0;
|
|
550
561
|
} catch (e) {
|
|
551
|
-
return
|
|
562
|
+
return I.error(`db plan --against: ${e.message}`), 1;
|
|
552
563
|
}
|
|
553
564
|
}
|
|
554
|
-
let l =
|
|
565
|
+
let l = E({
|
|
555
566
|
declared: o,
|
|
556
|
-
live: await Y(await
|
|
557
|
-
dialect:
|
|
558
|
-
...
|
|
567
|
+
live: await Y(await U()),
|
|
568
|
+
dialect: H(),
|
|
569
|
+
...T(),
|
|
559
570
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
560
571
|
});
|
|
561
|
-
return e.includes("--json") ? (
|
|
562
|
-
},
|
|
572
|
+
return e.includes("--json") ? (v(1, `${JSON.stringify(l, null, 2)}\n`), l.summary.blocked > 0 ? 2 : 0) : (X(l), l.summary.blocked > 0 ? 2 : 0);
|
|
573
|
+
}, we = async (e, t, n) => {
|
|
563
574
|
let r;
|
|
564
575
|
try {
|
|
565
|
-
let t = await
|
|
576
|
+
let t = await _.readFile(p(e), "utf8");
|
|
566
577
|
r = JSON.parse(t);
|
|
567
578
|
} catch (t) {
|
|
568
|
-
return
|
|
579
|
+
return I.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
|
|
569
580
|
}
|
|
570
|
-
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return
|
|
571
|
-
let i = await
|
|
572
|
-
if (i.length === 0) return
|
|
573
|
-
let o = await a({ root: n }), s = K(
|
|
581
|
+
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return I.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
|
|
582
|
+
let i = await W(n);
|
|
583
|
+
if (i.length === 0) return I.warn("db apply --plan: no schema files found", { root: n }), 1;
|
|
584
|
+
let o = await a({ root: n }), s = G(K([
|
|
574
585
|
...i,
|
|
575
|
-
...
|
|
586
|
+
...b,
|
|
576
587
|
...o
|
|
577
|
-
])), l = await
|
|
578
|
-
await J(l,
|
|
579
|
-
let u =
|
|
588
|
+
])), l = await U();
|
|
589
|
+
await J(l, C(b, H()));
|
|
590
|
+
let u = E({
|
|
580
591
|
declared: s,
|
|
581
592
|
live: await Y(l),
|
|
582
|
-
dialect:
|
|
583
|
-
...
|
|
593
|
+
dialect: H(),
|
|
594
|
+
...T()
|
|
584
595
|
});
|
|
585
|
-
if (u.fromFingerprint !== r.fromFingerprint) return
|
|
586
|
-
if (u.toFingerprint !== r.toFingerprint) return
|
|
587
|
-
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
596
|
+
if (u.fromFingerprint !== r.fromFingerprint) return I.error("db apply --plan: refusing — the live schema has drifted since this plan was generated."), console.error(` plan.from=${r.fromFingerprint.slice(0, 16)} live=${u.fromFingerprint.slice(0, 16)}`), console.error(" Regenerate against the current live schema: `voltro db plan --json > plan.json`"), 2;
|
|
597
|
+
if (u.toFingerprint !== r.toFingerprint) return I.error("db apply --plan: refusing — the declared schema differs from what this plan targets."), console.error(` plan.to=${r.toFingerprint.slice(0, 16)} declared=${u.toFingerprint.slice(0, 16)}`), console.error(" The schema files changed since the plan was generated. Regenerate the plan."), 2;
|
|
598
|
+
let d = q(u), f = d.plan;
|
|
599
|
+
if (f.summary.blocked > 0 && !d.acknowledged) return X(f), I.error(`db apply --plan: refusing — ${u.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (I.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), I.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
600
|
+
if (d.acknowledged && I.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), f.operations.length === 0) return I.info("db apply --plan: schema is up to date — nothing to apply"), 0;
|
|
601
|
+
X(f);
|
|
602
|
+
let m = (process.env.NODE_ENV ?? "dev").toLowerCase(), g = c(t, "--note"), v = await J(l, y.SqlClient.pipe(h.flatMap((e) => S(e, f, {
|
|
591
603
|
declared: s,
|
|
592
604
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
|
|
593
|
-
environment:
|
|
605
|
+
environment: m === "production" ? "prod" : m === "staging" ? "staging" : "dev",
|
|
594
606
|
source: "file",
|
|
595
|
-
...
|
|
596
|
-
replan: () => h.promise(() => Y(l)).pipe(h.map((e) =>
|
|
607
|
+
...g ? { notes: g } : {},
|
|
608
|
+
replan: () => h.promise(() => Y(l)).pipe(h.map((e) => E({
|
|
597
609
|
declared: s,
|
|
598
610
|
live: e,
|
|
599
|
-
dialect:
|
|
600
|
-
...
|
|
611
|
+
dialect: H(),
|
|
612
|
+
...T()
|
|
601
613
|
})))
|
|
602
614
|
}))));
|
|
603
|
-
return
|
|
604
|
-
id:
|
|
605
|
-
fingerprint:
|
|
615
|
+
return I.info(`db apply --plan: applied ${v.appliedOps} op(s) in ${v.durationMs}ms`, {
|
|
616
|
+
id: v.id,
|
|
617
|
+
fingerprint: v.fingerprint.slice(0, 16)
|
|
606
618
|
}), 0;
|
|
607
|
-
},
|
|
608
|
-
let t =
|
|
609
|
-
if (r) return
|
|
610
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
611
|
-
let i = await
|
|
612
|
-
if (i.length === 0) return
|
|
613
|
-
let o = await a({ root: t }), s = K(
|
|
619
|
+
}, Te = async (e) => {
|
|
620
|
+
let t = R(e), n = Z(e), r = c(e, "--plan");
|
|
621
|
+
if (r) return we(r, e, t);
|
|
622
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return I.error("voltro db apply: refusing — auto-apply on prod is not allowed."), console.error("In production, schema changes go through `voltro db plan --json > plan.json`"), console.error("then `voltro db apply --plan plan.json` as an explicit step in your deploy pipeline."), 3;
|
|
623
|
+
let i = await W(t);
|
|
624
|
+
if (i.length === 0) return I.warn("db apply: no schema files found", { root: t }), 1;
|
|
625
|
+
let o = await a({ root: t }), s = G(K([
|
|
614
626
|
...i,
|
|
615
|
-
...
|
|
627
|
+
...b,
|
|
616
628
|
...o
|
|
617
|
-
])), l = await
|
|
618
|
-
await J(l,
|
|
619
|
-
let u =
|
|
629
|
+
])), l = await U();
|
|
630
|
+
await J(l, C(b, H()));
|
|
631
|
+
let u = E({
|
|
620
632
|
declared: s,
|
|
621
633
|
live: await Y(l),
|
|
622
|
-
dialect:
|
|
623
|
-
...
|
|
634
|
+
dialect: H(),
|
|
635
|
+
...T(),
|
|
624
636
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
625
637
|
});
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
638
|
+
X(u);
|
|
639
|
+
let d = q(u), f = d.plan;
|
|
640
|
+
if (f.summary.blocked > 0 && !d.acknowledged) return I.error(`db apply: refusing — ${f.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (I.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), I.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
641
|
+
if (d.acknowledged && I.warn(`db apply: proceeding with ${u.summary.blocked} acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)`), f.operations.length === 0) return I.info("db apply: schema is up to date — nothing to apply"), 0;
|
|
642
|
+
let p = c(e, "--note"), m = await J(l, y.SqlClient.pipe(h.flatMap((e) => S(e, f, {
|
|
629
643
|
declared: s,
|
|
630
644
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
|
|
631
645
|
environment: (process.env.NODE_ENV ?? "dev").toLowerCase() === "staging" ? "staging" : "dev",
|
|
632
646
|
source: "auto-diff",
|
|
633
|
-
...
|
|
634
|
-
replan: () => h.promise(() => Y(l)).pipe(h.map((e) =>
|
|
647
|
+
...p ? { notes: p } : {},
|
|
648
|
+
replan: () => h.promise(() => Y(l)).pipe(h.map((e) => E({
|
|
635
649
|
declared: s,
|
|
636
650
|
live: e,
|
|
637
|
-
dialect:
|
|
638
|
-
...
|
|
651
|
+
dialect: H(),
|
|
652
|
+
...T(),
|
|
639
653
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
640
654
|
})))
|
|
641
655
|
}))));
|
|
642
|
-
return
|
|
643
|
-
id:
|
|
644
|
-
fingerprint:
|
|
656
|
+
return I.info(`db apply: applied ${m.appliedOps} op(s) in ${m.durationMs}ms`, {
|
|
657
|
+
id: m.id,
|
|
658
|
+
fingerprint: m.fingerprint.slice(0, 16)
|
|
645
659
|
}), 0;
|
|
646
|
-
},
|
|
647
|
-
let t = Math.max(1, Number(c(e, "--limit") ?? "20")), n = await J(await
|
|
660
|
+
}, Ee = async (e) => {
|
|
661
|
+
let t = Math.max(1, Number(c(e, "--limit") ?? "20")), n = await J(await U(), y.SqlClient.pipe(h.flatMap((e) => e`
|
|
648
662
|
SELECT ${e("id")}, ${e("fingerprint")}, ${e("operations")}::text AS ${e("operations")},
|
|
649
663
|
${e("appliedBy")}, ${e("environment")}, ${e("source")},
|
|
650
664
|
${e("durationMs")}, ${e("notes")}, ${e("appliedAt")}::text AS ${e("appliedAt")}
|
|
@@ -652,20 +666,20 @@ var k = class extends Error {
|
|
|
652
666
|
ORDER BY ${e("appliedAt")} DESC
|
|
653
667
|
LIMIT ${t}
|
|
654
668
|
`)));
|
|
655
|
-
if (n.length === 0) return
|
|
669
|
+
if (n.length === 0) return I.info("db plans: no planner-based migrations applied yet"), console.log("Run `voltro db apply` after a schema change to start a plan history."), 0;
|
|
656
670
|
console.log(`\n${n.length} plan${n.length === 1 ? "" : "s"} (newest first):\n`);
|
|
657
671
|
for (let e of n) {
|
|
658
672
|
let t = 0;
|
|
659
673
|
try {
|
|
660
674
|
t = JSON.parse(e.operations).length;
|
|
661
675
|
} catch {}
|
|
662
|
-
console.log(` ${e.id} fp=${
|
|
676
|
+
console.log(` ${e.id} fp=${D(e.fingerprint)} env=${e.environment} src=${e.source} ${t} op(s) ${e.durationMs}ms ${e.appliedAt} by=${e.appliedBy}`), e.notes && console.log(` [2mnote: ${e.notes}[0m`);
|
|
663
677
|
}
|
|
664
678
|
return console.log(), 0;
|
|
665
|
-
},
|
|
679
|
+
}, De = async (e) => {
|
|
666
680
|
let t = e[0];
|
|
667
681
|
if (!t) return console.error("voltro db restore-snapshot <plan-id>"), console.error(" Renames soft-dropped columns/tables from a plan back to their original names."), console.error(" Requires the plan to have been applied with VOLTRO_SOFT_DROP=1."), 2;
|
|
668
|
-
let n = await
|
|
682
|
+
let n = await U(), r = await J(n, y.SqlClient.pipe(h.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}::text AS ${e("operations")}
|
|
669
683
|
FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t}`)));
|
|
670
684
|
if (r.length === 0) return console.error(`restore-snapshot: plan '${t}' not found`), 1;
|
|
671
685
|
let i;
|
|
@@ -684,7 +698,7 @@ var k = class extends Error {
|
|
|
684
698
|
if (a.length === 0 && o.length === 0) return console.log(`restore-snapshot: plan '${t}' has no drop-column / drop-table ops to restore`), 0;
|
|
685
699
|
let s = 0;
|
|
686
700
|
for (let e of a) {
|
|
687
|
-
let t = await J(n,
|
|
701
|
+
let t = await J(n, y.SqlClient.pipe(h.flatMap((t) => t`
|
|
688
702
|
SELECT column_name FROM information_schema.columns
|
|
689
703
|
WHERE table_schema = 'public'
|
|
690
704
|
AND table_name = ${e.table}
|
|
@@ -696,11 +710,11 @@ var k = class extends Error {
|
|
|
696
710
|
continue;
|
|
697
711
|
}
|
|
698
712
|
let r = t[0].column_name;
|
|
699
|
-
await J(n,
|
|
713
|
+
await J(n, y.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table}" RENAME COLUMN "${r}" TO "${e.column}"`)))), s++, console.log(` ↩ restored ${e.table}.${e.column} (from ${r})`);
|
|
700
714
|
}
|
|
701
715
|
let c = 0;
|
|
702
716
|
for (let e of o) {
|
|
703
|
-
let t = await J(n,
|
|
717
|
+
let t = await J(n, y.SqlClient.pipe(h.flatMap((t) => t`
|
|
704
718
|
SELECT table_name FROM information_schema.tables
|
|
705
719
|
WHERE table_schema = 'public'
|
|
706
720
|
AND table_name LIKE ${e.table + "__dropped_%"}
|
|
@@ -711,18 +725,18 @@ var k = class extends Error {
|
|
|
711
725
|
continue;
|
|
712
726
|
}
|
|
713
727
|
let r = t[0].table_name;
|
|
714
|
-
await J(n,
|
|
728
|
+
await J(n, y.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${r}" RENAME TO "${e.table}"`)))), c++, console.log(` ↩ restored table ${e.table} (from ${r})`);
|
|
715
729
|
}
|
|
716
730
|
return console.log(`\n✓ restored ${s} column(s) + ${c} table(s) from plan '${t}'\n`), 0;
|
|
717
|
-
},
|
|
731
|
+
}, Q = async (e) => {
|
|
718
732
|
let t = c(e, "--before");
|
|
719
733
|
if (!t) return console.error("voltro db gc-snapshots --before <date> [--dry-run]"), console.error(" Permanently drops soft-dropped columns/tables (<name>__dropped_<ts>) older than <date>."), console.error(" <date> is ISO — e.g. 2026-01-01 or 2026-01-01T00:00:00Z. NOT reversible."), 2;
|
|
720
734
|
let n = t.replace(/[^0-9]/g, "");
|
|
721
735
|
if (n.length < 8) return console.error(`gc-snapshots: --before must be at least a full date (YYYY-MM-DD), got "${t}"`), 2;
|
|
722
|
-
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await
|
|
736
|
+
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await U(), o = await J(a, y.SqlClient.pipe(h.flatMap((e) => e`
|
|
723
737
|
SELECT table_name, column_name FROM information_schema.columns
|
|
724
738
|
WHERE table_schema = 'public' AND column_name LIKE ${"%__dropped_%"}
|
|
725
|
-
`))), s = await J(a,
|
|
739
|
+
`))), s = await J(a, y.SqlClient.pipe(h.flatMap((e) => e`
|
|
726
740
|
SELECT table_name FROM information_schema.tables
|
|
727
741
|
WHERE table_schema = 'public' AND table_name LIKE ${"%__dropped_%"}
|
|
728
742
|
`))), l = (e) => {
|
|
@@ -731,69 +745,69 @@ var k = class extends Error {
|
|
|
731
745
|
}, u = 0;
|
|
732
746
|
for (let e of o) {
|
|
733
747
|
let t = l(e.column_name);
|
|
734
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await J(a,
|
|
748
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await J(a, y.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table_name}" DROP COLUMN "${e.column_name}"`)))), u++);
|
|
735
749
|
}
|
|
736
750
|
for (let e of s) {
|
|
737
751
|
let t = l(e.table_name);
|
|
738
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await J(a,
|
|
752
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await J(a, y.SqlClient.pipe(h.flatMap((t) => t.unsafe(`DROP TABLE "${e.table_name}"`)))), u++);
|
|
739
753
|
}
|
|
740
754
|
return console.log(`\n${i ? `[dry-run] ${u} snapshot(s) would be dropped` : `✓ dropped ${u} snapshot(s)`} (older than ${r}).\n`), 0;
|
|
741
|
-
},
|
|
755
|
+
}, Oe = async (e) => {
|
|
742
756
|
let t = c(e, "--before");
|
|
743
757
|
if (!t) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
|
|
744
758
|
let n = t;
|
|
745
759
|
if (Number.isNaN(new Date(n).getTime())) return console.error(`voltro db squash: --before '${n}' is not a valid ISO date`), 2;
|
|
746
|
-
let r = c(e, "--note"), i =
|
|
747
|
-
if (o.length === 0) return
|
|
748
|
-
let s = await a({ root: i }), l =
|
|
760
|
+
let r = c(e, "--note"), i = R(e), o = await W(i);
|
|
761
|
+
if (o.length === 0) return I.warn("db squash: no schema files found", { root: i }), 1;
|
|
762
|
+
let s = await a({ root: i }), l = w(ie(G(K([
|
|
749
763
|
...o,
|
|
750
|
-
...
|
|
764
|
+
...b,
|
|
751
765
|
...s
|
|
752
|
-
])))), u = await
|
|
766
|
+
])))), u = await U();
|
|
753
767
|
try {
|
|
754
|
-
let e = await J(u,
|
|
768
|
+
let e = await J(u, le({
|
|
755
769
|
before: n,
|
|
756
770
|
fingerprint: l,
|
|
757
771
|
appliedBy: process.env.USER ?? "cli",
|
|
758
772
|
environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
|
|
759
773
|
...r === void 0 ? {} : { note: r }
|
|
760
774
|
}));
|
|
761
|
-
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${
|
|
775
|
+
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${D(e.snapshotFingerprint)}\n`), 0;
|
|
762
776
|
} catch (e) {
|
|
763
777
|
return console.error(`squash refused: ${e.message}`), 1;
|
|
764
778
|
}
|
|
765
|
-
},
|
|
766
|
-
let t = await
|
|
779
|
+
}, ke = async (e) => {
|
|
780
|
+
let t = await U(), n = await J(t, y.SqlClient.pipe(h.flatMap((e) => e`
|
|
767
781
|
SELECT ${e("id")}, ${e("fingerprint")}, ${e("appliedAt")}::text AS ${e("appliedAt")}
|
|
768
782
|
FROM ${e("_voltro_migration_plans")}
|
|
769
783
|
ORDER BY ${e("appliedAt")} DESC
|
|
770
784
|
LIMIT 1
|
|
771
|
-
`))), r =
|
|
772
|
-
if (n.length === 0) return
|
|
785
|
+
`))), r = w({ tables: (await Y(t)).tables.filter((e) => !e.name.startsWith("_voltro_")) });
|
|
786
|
+
if (n.length === 0) return I.info("db drift: no _voltro_migration_plans rows yet"), console.log(`live user-schema fingerprint: ${D(r)}`), console.log("No baseline to compare against. Run `voltro db apply` to lock in the current state."), 0;
|
|
773
787
|
let i = n[0];
|
|
774
|
-
return i.fingerprint === r ? (
|
|
775
|
-
},
|
|
776
|
-
let t =
|
|
777
|
-
await J(n,
|
|
778
|
-
let i = await J(n,
|
|
788
|
+
return i.fingerprint === r ? (I.info(`db drift: live matches last applied (${D(r)})`), 0) : (I.error("db drift: live schema DIVERGED from last applied state"), console.log(), console.log(` last applied: ${D(i.fingerprint)} at ${i.appliedAt} (${i.id})`), console.log(` live now: ${D(r)}`), console.log(), console.log("Probable causes:"), console.log(" - manual DDL ran out-of-band (psql session, another tool)"), console.log(" - someone applied a plan but the row never made it into _voltro_migration_plans"), console.log(), console.log("To reconcile, run `voltro db plan` to see what your code expects vs the live DB."), 4);
|
|
789
|
+
}, Ae = async (e) => {
|
|
790
|
+
let t = R(e), n = await U(), r = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
|
|
791
|
+
await J(n, C(b, H()));
|
|
792
|
+
let i = await J(n, y.SqlClient.pipe(h.flatMap((e) => ce(e, {
|
|
779
793
|
projectRoot: t,
|
|
780
794
|
env: r,
|
|
781
795
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
782
796
|
}))));
|
|
783
|
-
return i.applied.length === 0 ? (
|
|
797
|
+
return i.applied.length === 0 ? (I.info(`db files: no pending file-based migrations (${i.skipped.length} already applied)`), 0) : (I.info(`db files: applied ${i.applied.length} migration(s)`, {
|
|
784
798
|
applied: i.applied.map((e) => `${e.id} (${e.durationMs}ms)`),
|
|
785
799
|
skipped: i.skipped.length
|
|
786
800
|
}), 0);
|
|
787
|
-
},
|
|
788
|
-
let t =
|
|
789
|
-
if (!n) return
|
|
790
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
791
|
-
let r = await J(await
|
|
801
|
+
}, je = async (e) => {
|
|
802
|
+
let t = R(e), n = e.find((e) => !e.startsWith("--"));
|
|
803
|
+
if (!n) return I.error("voltro db rollback-file <migration-id>"), 2;
|
|
804
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return I.error("voltro db rollback-file: refusing on prod — schema rollback runs as an explicit deploy step."), 3;
|
|
805
|
+
let r = await J(await U(), y.SqlClient.pipe(h.flatMap((e) => se(e, {
|
|
792
806
|
projectRoot: t,
|
|
793
807
|
id: n
|
|
794
808
|
}))));
|
|
795
|
-
return
|
|
796
|
-
},
|
|
809
|
+
return I.info(`db rollback-file: rolled back ${r.id} in ${r.durationMs}ms`), 0;
|
|
810
|
+
}, Me = () => {
|
|
797
811
|
let e = /* @__PURE__ */ new Date();
|
|
798
812
|
return [
|
|
799
813
|
e.getUTCFullYear(),
|
|
@@ -803,7 +817,7 @@ var k = class extends Error {
|
|
|
803
817
|
String(e.getUTCMinutes()).padStart(2, "0"),
|
|
804
818
|
String(e.getUTCSeconds()).padStart(2, "0")
|
|
805
819
|
].join("");
|
|
806
|
-
},
|
|
820
|
+
}, Ne = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", Pe = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
|
|
807
821
|
import { Effect } from 'effect'
|
|
808
822
|
|
|
809
823
|
// A hand-written migration. Reach for this when the DECLARATIVE differ can't
|
|
@@ -836,24 +850,24 @@ export default defineMigration({
|
|
|
836
850
|
],
|
|
837
851
|
})
|
|
838
852
|
`, $ = {
|
|
839
|
-
plan:
|
|
840
|
-
apply:
|
|
841
|
-
plans:
|
|
842
|
-
drift:
|
|
843
|
-
files:
|
|
844
|
-
"rollback-file":
|
|
845
|
-
migrate:
|
|
846
|
-
rollback:
|
|
847
|
-
status:
|
|
848
|
-
seed:
|
|
853
|
+
plan: Ce,
|
|
854
|
+
apply: Te,
|
|
855
|
+
plans: Ee,
|
|
856
|
+
drift: ke,
|
|
857
|
+
files: Ae,
|
|
858
|
+
"rollback-file": je,
|
|
859
|
+
migrate: he,
|
|
860
|
+
rollback: ge,
|
|
861
|
+
status: _e,
|
|
862
|
+
seed: ye,
|
|
849
863
|
generate: async (e) => {
|
|
850
|
-
let { value: t, rest: n } = s(e, "--root"), { value: r, rest: i } = s(n, "--name"), a = t ? p(t) : process.cwd(), o =
|
|
851
|
-
return await
|
|
864
|
+
let { value: t, rest: n } = s(e, "--root"), { value: r, rest: i } = s(n, "--name"), a = t ? p(t) : process.cwd(), o = Ne(r ?? l(i) ?? "migration"), c = `${Me()}_${o}`, u = d(a, "migrations"), m = d(u, `${c}.migration.ts`);
|
|
865
|
+
return await _.mkdir(u, { recursive: !0 }), await _.writeFile(m, Pe(c, o), "utf8"), I.info(`created ${f(a, m)}`), console.error("Fill in the apply/undo steps, then run `voltro db migrate` (rollback with `voltro db rollback`)."), console.error("For a plain schema shape change you may not need this — `voltro db apply` diffs declared vs live."), 0;
|
|
852
866
|
},
|
|
853
|
-
squash:
|
|
854
|
-
"restore-snapshot":
|
|
855
|
-
"gc-snapshots":
|
|
856
|
-
},
|
|
867
|
+
squash: Oe,
|
|
868
|
+
"restore-snapshot": De,
|
|
869
|
+
"gc-snapshots": Q
|
|
870
|
+
}, Fe = async (e) => {
|
|
857
871
|
let [t, ...n] = e;
|
|
858
872
|
if (!t) {
|
|
859
873
|
console.error("voltro db <subcommand> [args]"), console.error("subcommands:");
|
|
@@ -862,9 +876,9 @@ export default defineMigration({
|
|
|
862
876
|
}
|
|
863
877
|
let r = $[t];
|
|
864
878
|
if (!r) return console.error(`voltro db: unknown subcommand '${t}'`), 2;
|
|
865
|
-
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) &&
|
|
879
|
+
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) && I.info("using a DIRECT (non-pooler) connection for the db-command path (DB_DIRECT_URL/DB_MIGRATE_URL set)");
|
|
866
880
|
let i = (e) => {
|
|
867
|
-
|
|
881
|
+
B(e) ? I.error(V) : console.error(e), process.exit(1);
|
|
868
882
|
};
|
|
869
883
|
process.on("uncaughtException", i);
|
|
870
884
|
try {
|
|
@@ -874,4 +888,4 @@ export default defineMigration({
|
|
|
874
888
|
}
|
|
875
889
|
};
|
|
876
890
|
//#endregion
|
|
877
|
-
export {
|
|
891
|
+
export { B as a, de as c, G as i, z as n, Fe as o, W as r, O as s, V as t };
|