@voltro/cli 0.12.0 → 0.13.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 (85) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/dist/{apiBuild-GefjNI5d.js → apiBuild-B6_ehbem.js} +3 -3
  3. package/dist/apiBuild-Bzpb383i.js +2 -0
  4. package/dist/bin.js +3 -3
  5. package/dist/{commands-BmNi1RgZ.js → commands-DYOuGlEC.js} +1668 -1371
  6. package/dist/dbCommand-ADD4mrTI.js +2 -0
  7. package/dist/{dbCommand-DNrkb58g.js → dbCommand-DIOJQwsq.js} +71 -57
  8. package/dist/{dev-d0LjACxE.js → dev-Fb9BmavX.js} +1483 -1414
  9. package/dist/dev-xdijqojc.js +3 -0
  10. package/dist/{seedRunner-DZmhoqNt.js → frameworkTableAssembly-CYlrafoK.js} +110 -331
  11. package/dist/frameworkTableAssembly-XvKO7wQt.js +2 -0
  12. package/dist/index.js +1 -1
  13. package/dist/{inspectMetrics-uEfIS1vp.js → inspectMetrics-DOiiII34.js} +302 -293
  14. package/dist/seedRunner-D6eu-u5U.js +229 -0
  15. package/dist/{serveCommand-B2-YV9TU.js → serveCommand-CqYxzBZR.js} +368 -367
  16. package/dist/serveEntry.js +3 -3
  17. package/dist/{start-DKcQd52a.js → start-CjG_0xhR.js} +1 -1
  18. package/dist/startEntry.js +2 -2
  19. package/package.json +17 -17
  20. package/templates/AGENTS.md +1 -1
  21. package/templates/agent-docs/_index.md +1 -1
  22. package/templates/agent-docs/authentication.md +36 -0
  23. package/templates/agent-docs/cli.md +78 -4
  24. package/templates/agent-docs/configuration.md +48 -0
  25. package/templates/agent-docs/database/migrations.md +32 -0
  26. package/templates/agent-docs/database/transactions.md +31 -0
  27. package/templates/agent-docs/testing.md +24 -0
  28. package/templates/agent-docs/whats-new.md +41 -137
  29. package/templates/apps/api-ai/package.json +7 -7
  30. package/templates/apps/api-auth/package.json +8 -8
  31. package/templates/apps/api-backend/package.json +7 -7
  32. package/templates/apps/api-backend-deactivation/package.json +7 -7
  33. package/templates/apps/api-backend-deactivation/tests/users.deactivate.test.ts +1 -1
  34. package/templates/apps/api-backend-mail/package.json +8 -8
  35. package/templates/apps/api-backend-mariadb/package.json +9 -9
  36. package/templates/apps/api-backend-storage/package.json +8 -8
  37. package/templates/apps/api-data-advanced/package.json +8 -8
  38. package/templates/apps/api-data-advanced/tests/queries.test.ts +6 -1
  39. package/templates/apps/api-durable/package.json +8 -8
  40. package/templates/apps/api-feature-flags/package.json +9 -9
  41. package/templates/apps/api-governance/app.config.ts +0 -1
  42. package/templates/apps/api-governance/package.json +8 -8
  43. package/templates/apps/api-kv/package.json +8 -8
  44. package/templates/apps/api-kv/tests/sync.test.ts +2 -2
  45. package/templates/apps/api-moderation/package.json +8 -8
  46. package/templates/apps/api-observability/package.json +8 -8
  47. package/templates/apps/api-ratelimit/package.json +8 -8
  48. package/templates/apps/api-rbac/README.md +53 -14
  49. package/templates/apps/api-rbac/app.config.ts +24 -26
  50. package/templates/apps/api-rbac/authz.ts +76 -0
  51. package/templates/apps/api-rbac/database/schema.ts +23 -6
  52. package/templates/apps/api-rbac/mutations/notes.create.mutation.server.ts +7 -5
  53. package/templates/apps/api-rbac/mutations/notes.create.mutation.ts +15 -8
  54. package/templates/apps/api-rbac/mutations/notes.delete.mutation.server.ts +54 -0
  55. package/templates/apps/api-rbac/mutations/notes.delete.mutation.ts +43 -0
  56. package/templates/apps/api-rbac/mutations/teams.rename.mutation.server.ts +19 -0
  57. package/templates/apps/api-rbac/mutations/teams.rename.mutation.ts +37 -0
  58. package/templates/apps/api-rbac/package.json +8 -8
  59. package/templates/apps/api-rbac/queries/notes.list.query.server.ts +12 -0
  60. package/templates/apps/api-rbac/queries/notes.list.query.ts +26 -0
  61. package/templates/apps/api-rbac/template.json +2 -2
  62. package/templates/apps/api-rbac/tests/authz.test.ts +233 -0
  63. package/templates/apps/api-rest/package.json +7 -7
  64. package/templates/apps/api-saas/package.json +11 -11
  65. package/templates/apps/api-search/package.json +8 -8
  66. package/templates/apps/api-versioning/package.json +8 -8
  67. package/templates/apps/api-webhooks/package.json +9 -8
  68. package/templates/apps/changelog/package.json +6 -6
  69. package/templates/apps/edge-functions/package.json +2 -2
  70. package/templates/apps/frontend-admin/package.json +8 -8
  71. package/templates/apps/frontend-app/package.json +8 -8
  72. package/templates/apps/frontend-blank/package.json +7 -7
  73. package/templates/apps/frontend-contact/package.json +7 -7
  74. package/templates/apps/frontend-dashboard/package.json +7 -7
  75. package/templates/apps/frontend-docs/package.json +7 -7
  76. package/templates/apps/frontend-i18n/package.json +6 -6
  77. package/templates/apps/frontend-landing/package.json +7 -7
  78. package/templates/apps/frontend-spa/package.json +7 -7
  79. package/templates/apps/frontend-ssr/package.json +7 -7
  80. package/templates/apps/frontend-ssr-api/package.json +8 -8
  81. package/templates/apps/frontend-static-blog/package.json +6 -6
  82. package/dist/apiBuild-s7swuEU5.js +0 -2
  83. package/dist/dbCommand-Bt6BCG7g.js +0 -2
  84. package/dist/dev-YHbHmeXt.js +0 -3
  85. package/templates/apps/api-rbac/tests/notes.create.test.ts +0 -80
@@ -0,0 +1,2 @@
1
+ import { o as e } from "./dbCommand-DIOJQwsq.js";
2
+ export { e as runDbCommand };
@@ -1,4 +1,5 @@
1
- import { G as e, a as t, i as n, m as r, n as i, r as a, t as o } from "./seedRunner-DZmhoqNt.js";
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, u as o } from "./frameworkTableAssembly-CYlrafoK.js";
2
3
  import { a as s, n as c, t as l } from "./cliArgs-qdZSElM3.js";
3
4
  import { basename as u, join as d, relative as f, resolve as p } from "node:path";
4
5
  import { pathToFileURL as m } from "node:url";
@@ -321,7 +322,7 @@ var k = class extends Error {
321
322
  let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
322
323
  if (e === "memory") throw new k("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
323
324
  return e;
324
- }, W = async () => (await e(U())).makeSqlLayer(B()), fe = async (e) => {
325
+ }, W = async () => (await r(U())).makeSqlLayer(B()), fe = async (e) => {
325
326
  let t = z(e), n = e.includes("--dry-run"), r = await j(await R(t, A), L);
326
327
  if (r.length === 0) return L.info("no migrations found", { root: t }), 0;
327
328
  if (n) {
@@ -357,17 +358,17 @@ var k = class extends Error {
357
358
  console.log(` ${t} ${e.id} [${e.status}] ${e.name}${r}${n}`);
358
359
  }
359
360
  return 0;
360
- }, he = async (t) => {
361
- let n = (c(t, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
362
- if (n === "memory") return L.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), L.warn(" rows written by this run will NOT persist beyond the process. set DB_DIALECT=postgres or unset to seed the real DB."), {
361
+ }, he = async (e) => {
362
+ let t = (c(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
363
+ if (t === "memory") return L.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), L.warn(" rows written by this run will NOT persist beyond the process. set DB_DIALECT=postgres or unset to seed the real DB."), {
363
364
  store: new ne(),
364
365
  dispose: async () => {},
365
366
  dialectId: "memory"
366
367
  };
367
- let r = await e(n);
368
- L.info(`voltro db seed: target store=${r.id} — rows will persist`);
369
- let i = await r.makeStore({
370
- sqlLayer: r.makeSqlLayer(B()),
368
+ let n = await r(t);
369
+ L.info(`voltro db seed: target store=${n.id} — rows will persist`);
370
+ let i = await n.makeStore({
371
+ sqlLayer: n.makeSqlLayer(B()),
371
372
  changeStrategy: "inline"
372
373
  }), a = i;
373
374
  return {
@@ -375,14 +376,14 @@ var k = class extends Error {
375
376
  dispose: async () => {
376
377
  typeof a.close == "function" && await a.close(5e3);
377
378
  },
378
- dialectId: r.id
379
+ dialectId: n.id
379
380
  };
380
- }, ge = async (e) => {
381
- let t = z(e), r = c(e, "--id"), s = c(e, "--lifecycle"), l = await i(await R(t, o), L);
382
- if (l.length === 0) return L.info("no seeds found", { root: t }), 0;
383
- let { store: u, dispose: d, dialectId: f } = await he(e);
381
+ }, ge = async (r) => {
382
+ let a = z(r), o = c(r, "--id"), s = c(r, "--lifecycle"), l = await t(await R(a, i), L);
383
+ if (l.length === 0) return L.info("no seeds found", { root: a }), 0;
384
+ let { store: u, dispose: d, dialectId: f } = await he(r);
384
385
  try {
385
- return r ? +((await n(l, r, u, L)).status === "failed") : +((await a((s ? l.filter((e) => e.definition.lifecycle === s) : l.filter((e) => e.definition.lifecycle === "boot")).map((e) => ({
386
+ return o ? +((await e(l, o, u, L)).status === "failed") : +((await n((s ? l.filter((e) => e.definition.lifecycle === s) : l.filter((e) => e.definition.lifecycle === "boot")).map((e) => ({
386
387
  ...e,
387
388
  definition: {
388
389
  ...e.definition,
@@ -395,7 +396,7 @@ var k = class extends Error {
395
396
  });
396
397
  }
397
398
  }, _e = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", G = async (e) => {
398
- let t = await R(e, r), n = [];
399
+ let t = await R(e, o), n = [];
399
400
  for (let r of t) {
400
401
  let t;
401
402
  try {
@@ -519,21 +520,21 @@ var k = class extends Error {
519
520
  if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
520
521
  return n;
521
522
  }, be = async (e) => {
522
- let n = Z(e), r = z(e), i = await G(r);
523
- if (i.length === 0) return L.warn("db plan: no schema files found", {
524
- root: r,
523
+ let t = Z(e), n = z(e), r = await G(n);
524
+ if (r.length === 0) return L.warn("db plan: no schema files found", {
525
+ root: n,
525
526
  hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
526
527
  }), 1;
527
- let a = await t({ root: r }), o = K(q([
528
- ...i,
528
+ let i = await a({ root: n }), o = K(q([
529
+ ...r,
529
530
  ...y,
530
- ...a
531
+ ...i
531
532
  ])), s = c(e, "--against");
532
533
  if (s) {
533
- let t = s, r = c(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, i = { accept: "application/json" };
534
+ let n = s, r = c(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, i = { accept: "application/json" };
534
535
  r && (i.authorization = `Bearer ${r}`);
535
536
  try {
536
- let e = t.endsWith("/_voltro/inspect/migrations") ? t : `${t.replace(/\/+$/, "")}/_voltro/inspect/migrations`, r = await fetch(e, { headers: i });
537
+ let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`, r = await fetch(e, { headers: i });
537
538
  if (!r.ok) return L.error(`db plan --against: remote returned ${r.status}`, { url: e }), 1;
538
539
  let a = (await r.json()).drift?.liveSnapshot;
539
540
  if (!a) return L.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
@@ -542,7 +543,7 @@ var k = class extends Error {
542
543
  live: a,
543
544
  dialect: U(),
544
545
  ...w(),
545
- ...n === void 0 ? {} : { onlineAfter: n }
546
+ ...t === void 0 ? {} : { onlineAfter: t }
546
547
  });
547
548
  return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), X(s), s.summary.blocked > 0 ? 2 : 0;
548
549
  } catch (e) {
@@ -554,22 +555,22 @@ var k = class extends Error {
554
555
  live: await Y(await W()),
555
556
  dialect: U(),
556
557
  ...w(),
557
- ...n === void 0 ? {} : { onlineAfter: n }
558
+ ...t === void 0 ? {} : { onlineAfter: t }
558
559
  });
559
560
  return e.includes("--json") ? (te(1, `${JSON.stringify(l, null, 2)}\n`), l.summary.blocked > 0 ? 2 : 0) : (X(l), l.summary.blocked > 0 ? 2 : 0);
560
- }, xe = async (e, n, r) => {
561
- let i;
561
+ }, xe = async (e, t, n) => {
562
+ let r;
562
563
  try {
563
564
  let t = await g.readFile(p(e), "utf8");
564
- i = JSON.parse(t);
565
+ r = JSON.parse(t);
565
566
  } catch (t) {
566
567
  return L.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
567
568
  }
568
- if (!Array.isArray(i?.operations) || typeof i?.fromFingerprint != "string" || typeof i?.toFingerprint != "string") return L.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
569
- let a = await G(r);
570
- if (a.length === 0) return L.warn("db apply --plan: no schema files found", { root: r }), 1;
571
- let o = await t({ root: r }), s = K(q([
572
- ...a,
569
+ if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return L.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
570
+ let i = await G(n);
571
+ if (i.length === 0) return L.warn("db apply --plan: no schema files found", { root: n }), 1;
572
+ let o = await a({ root: n }), s = K(q([
573
+ ...i,
573
574
  ...y,
574
575
  ...o
575
576
  ])), l = await W();
@@ -580,30 +581,36 @@ var k = class extends Error {
580
581
  dialect: U(),
581
582
  ...w()
582
583
  });
583
- if (u.fromFingerprint !== i.fromFingerprint) return L.error("db apply --plan: refusing — the live schema has drifted since this plan was generated."), console.error(` plan.from=${i.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;
584
- if (u.toFingerprint !== i.toFingerprint) return L.error("db apply --plan: refusing — the declared schema differs from what this plan targets."), console.error(` plan.to=${i.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;
584
+ if (u.fromFingerprint !== r.fromFingerprint) return L.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;
585
+ if (u.toFingerprint !== r.toFingerprint) return L.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;
585
586
  if (u.summary.blocked > 0) return X(u), L.error(`db apply --plan: refusing — ${u.summary.blocked} blocked operation(s). See the fix hints above.`), 2;
586
587
  if (u.operations.length === 0) return L.info("db apply --plan: schema is up to date — nothing to apply"), 0;
587
588
  X(u);
588
- let d = (process.env.NODE_ENV ?? "dev").toLowerCase(), f = c(n, "--note"), m = await J(l, _.SqlClient.pipe(h.flatMap((e) => x(e, u, {
589
+ let d = (process.env.NODE_ENV ?? "dev").toLowerCase(), f = c(t, "--note"), m = await J(l, _.SqlClient.pipe(h.flatMap((e) => x(e, u, {
589
590
  declared: s,
590
591
  appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
591
592
  environment: d === "production" ? "prod" : d === "staging" ? "staging" : "dev",
592
593
  source: "file",
593
- ...f ? { notes: f } : {}
594
+ ...f ? { notes: f } : {},
595
+ replan: () => h.promise(() => Y(l)).pipe(h.map((e) => T({
596
+ declared: s,
597
+ live: e,
598
+ dialect: U(),
599
+ ...w()
600
+ })))
594
601
  }))));
595
- return L.info(`db apply --plan: applied ${u.operations.length} op(s) in ${m.durationMs}ms`, {
602
+ return L.info(`db apply --plan: applied ${m.appliedOps} op(s) in ${m.durationMs}ms`, {
596
603
  id: m.id,
597
604
  fingerprint: m.fingerprint.slice(0, 16)
598
605
  }), 0;
599
606
  }, Se = async (e) => {
600
- let n = z(e), r = Z(e), i = c(e, "--plan");
601
- if (i) return xe(i, e, n);
607
+ let t = z(e), n = Z(e), r = c(e, "--plan");
608
+ if (r) return xe(r, e, t);
602
609
  if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return L.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;
603
- let a = await G(n);
604
- if (a.length === 0) return L.warn("db apply: no schema files found", { root: n }), 1;
605
- let o = await t({ root: n }), s = K(q([
606
- ...a,
610
+ let i = await G(t);
611
+ if (i.length === 0) return L.warn("db apply: no schema files found", { root: t }), 1;
612
+ let o = await a({ root: t }), s = K(q([
613
+ ...i,
607
614
  ...y,
608
615
  ...o
609
616
  ])), l = await W();
@@ -613,7 +620,7 @@ var k = class extends Error {
613
620
  live: await Y(l),
614
621
  dialect: U(),
615
622
  ...w(),
616
- ...r === void 0 ? {} : { onlineAfter: r }
623
+ ...n === void 0 ? {} : { onlineAfter: n }
617
624
  });
618
625
  if (X(u), u.summary.blocked > 0) return L.error(`db apply: refusing — ${u.summary.blocked} blocked operation(s). See the fix hints above.`), 2;
619
626
  if (u.operations.length === 0) return L.info("db apply: schema is up to date — nothing to apply"), 0;
@@ -622,9 +629,16 @@ var k = class extends Error {
622
629
  appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
623
630
  environment: (process.env.NODE_ENV ?? "dev").toLowerCase() === "staging" ? "staging" : "dev",
624
631
  source: "auto-diff",
625
- ...d ? { notes: d } : {}
632
+ ...d ? { notes: d } : {},
633
+ replan: () => h.promise(() => Y(l)).pipe(h.map((e) => T({
634
+ declared: s,
635
+ live: e,
636
+ dialect: U(),
637
+ ...w(),
638
+ ...n === void 0 ? {} : { onlineAfter: n }
639
+ })))
626
640
  }))));
627
- return L.info(`db apply: applied ${u.operations.length} op(s) in ${f.durationMs}ms`, {
641
+ return L.info(`db apply: applied ${f.appliedOps} op(s) in ${f.durationMs}ms`, {
628
642
  id: f.id,
629
643
  fingerprint: f.fingerprint.slice(0, 16)
630
644
  }), 0;
@@ -724,24 +738,24 @@ var k = class extends Error {
724
738
  }
725
739
  return console.log(`\n${i ? `[dry-run] ${u} snapshot(s) would be dropped` : `✓ dropped ${u} snapshot(s)`} (older than ${r}).\n`), 0;
726
740
  }, Q = async (e) => {
727
- let n = c(e, "--before");
728
- if (!n) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
729
- let r = n;
730
- if (Number.isNaN(new Date(r).getTime())) return console.error(`voltro db squash: --before '${r}' is not a valid ISO date`), 2;
731
- let i = c(e, "--note"), a = z(e), o = await G(a);
732
- if (o.length === 0) return L.warn("db squash: no schema files found", { root: a }), 1;
733
- let s = await t({ root: a }), l = C(ae(K(q([
741
+ let t = c(e, "--before");
742
+ if (!t) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
743
+ let n = t;
744
+ if (Number.isNaN(new Date(n).getTime())) return console.error(`voltro db squash: --before '${n}' is not a valid ISO date`), 2;
745
+ let r = c(e, "--note"), i = z(e), o = await G(i);
746
+ if (o.length === 0) return L.warn("db squash: no schema files found", { root: i }), 1;
747
+ let s = await a({ root: i }), l = C(ae(K(q([
734
748
  ...o,
735
749
  ...y,
736
750
  ...s
737
751
  ])))), u = await W();
738
752
  try {
739
753
  let e = await J(u, se({
740
- before: r,
754
+ before: n,
741
755
  fingerprint: l,
742
756
  appliedBy: process.env.USER ?? "cli",
743
757
  environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
744
- ...i === void 0 ? {} : { note: i }
758
+ ...r === void 0 ? {} : { note: r }
745
759
  }));
746
760
  return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${O(e.snapshotFingerprint)}\n`), 0;
747
761
  } catch (e) {