@voltro/cli 0.36.0 → 0.38.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 (126) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/dist/{apiBuild-DiE9zcj6.js → apiBuild-CvacV4zA.js} +2 -2
  3. package/dist/apiBuild-ttSDDOGp.js +2 -0
  4. package/dist/{baselineCommand-DIttzO8A.js → baselineCommand-C6NMt-oa.js} +1 -4
  5. package/dist/baselineCommand-Cfw2Afwm.js +2 -0
  6. package/dist/bin.js +1 -1
  7. package/dist/bootRefusal-pgvDxlrU.js +7 -0
  8. package/dist/{build-DBe6c7qC.js → build-_LoTC1s0.js} +31 -31
  9. package/dist/{checkCommand-BhG0gmUD.js → checkCommand-Pjk5sBl2.js} +3 -3
  10. package/dist/{checkCommand-BTA6P3Yr.js → checkCommand-wibtQx-N.js} +1 -1
  11. package/dist/{cloudCmd-Cvv5HGaZ.js → cloudCmd-NQSwe_Qk.js} +1 -1
  12. package/dist/{codegen-CYM3Zqrf.js → codegen-GYYcdpCg.js} +2 -2
  13. package/dist/codegen-uYrrDzQv.js +2 -0
  14. package/dist/{codegenCommand-DH-8ZXzR.js → codegenCommand-C60m_LCp.js} +2 -2
  15. package/dist/{codemodRunner-DjQGrdmg.js → codemodRunner-CXgQ-ecJ.js} +339 -167
  16. package/dist/{commands-Bp7utV-7.js → commands-DaZVi9wi.js} +74 -73
  17. package/dist/{dashboardCommand-D5Jwjmr1.js → dashboardCommand-Dxy_kmoW.js} +3 -3
  18. package/dist/{dataCommand-DnCm0B2C.js → dataCommand-D6-Ha2lf.js} +2 -2
  19. package/dist/{dbCommand-O8HA63s2.js → dbCommand-C4UW8cVN.js} +353 -351
  20. package/dist/dbCommand-CC-qNzpz.js +2 -0
  21. package/dist/{dev-BSFTgCQ5.js → dev-CAOoXOkc.js} +1 -1
  22. package/dist/{dev-Dc4cpmRl.js → dev-E7Eo5GHc.js} +262 -261
  23. package/dist/doctorCommand-Cjl8Yii8.js +2 -0
  24. package/dist/{doctorCommand-CNFtt3nR.js → doctorCommand-gmDyALFL.js} +10 -10
  25. package/dist/{dormancyCommand-6F6NsJKf.js → dormancyCommand-CE6wG8lj.js} +1 -1
  26. package/dist/{embeddingsCommand-DRRdneJD.js → embeddingsCommand-C3uAuf6N.js} +1 -1
  27. package/dist/{envCommand-TZzcyqZ-.js → envCommand-DzuWh8ya.js} +19 -19
  28. package/dist/{evolveCommand-iU35TPjT.js → evolveCommand-DPLqcYb2.js} +2 -2
  29. package/dist/index.js +2 -2
  30. package/dist/{infoCommand-unqisJGp.js → infoCommand-CmczGj_l.js} +1 -1
  31. package/dist/inspectGateHint-BF6608UT.js +4 -0
  32. package/dist/{metaCommands-7MJfZ5cf.js → metaCommands-CIoQNlaQ.js} +1 -1
  33. package/dist/{migrate-BV7I-ZHZ.js → migrate-RwgUWXfc.js} +1 -1
  34. package/dist/{privacyCommand-C-Df56U_.js → privacyCommand-XejDMvmu.js} +4 -3
  35. package/dist/{probeCommand-CZfaaUOZ.js → probeCommand-BPEpwT32.js} +40 -39
  36. package/dist/{projectScaffold-DmzEKHib.js → projectScaffold-CxgtJvlb.js} +1 -1
  37. package/dist/{projectScaffold-LMMtaavR.js → projectScaffold-DDgWbNLe.js} +2 -2
  38. package/dist/{runtimeTrace-D5LJ_N55.js → runtimeTrace-6jfznSzr.js} +1 -1
  39. package/dist/{sdkgen-82xVuvbt.js → sdkgen-CEJZZQUA.js} +1 -1
  40. package/dist/serveCommand-DNRF2nDI.js +2 -0
  41. package/dist/{serveCommand-VVGcIMIw.js → serveCommand-DnY4VS3D.js} +541 -540
  42. package/dist/serveEntry.js +1 -1
  43. package/dist/{start-BgN62boB.js → start-CidwCcjl.js} +1 -1
  44. package/dist/{start-T4VesWiM.js → start-RCh6qKFe.js} +24 -24
  45. package/dist/startEntry.js +1 -1
  46. package/dist/startup.d.ts +40 -1
  47. package/dist/startup.js +2 -2
  48. package/dist/startupRunner-CEqQq7ax.js +108 -0
  49. package/dist/subcommandNames-CKG5Dz3a.js +27 -0
  50. package/dist/{templates-De8IR5-c.js → templates-BR-fb4SP.js} +1 -4
  51. package/dist/{test-CI6iDsYc.js → test-DIQ0jlkQ.js} +1 -1
  52. package/dist/updateCommand-B0J_T9Vs.js +2 -0
  53. package/dist/{updateCommand-CwnUK_mf.js → updateCommand-CmksX7m_.js} +1 -1
  54. package/dist/{webDev-Dybxew86.js → webDev-BKdD7c_a.js} +689 -691
  55. package/dist/webDev-CcJfdSjl2.js +2 -0
  56. package/dist/{webhooksCommand-di1CJVzu.js → webhooksCommand-BHeaF1ZJ.js} +1 -1
  57. package/package.json +35 -17
  58. package/templates/AGENTS.md +1 -1
  59. package/templates/agent-docs/_index.md +1 -1
  60. package/templates/agent-docs/cli.md +16 -0
  61. package/templates/agent-docs/data.md +30 -0
  62. package/templates/agent-docs/plugins/audit.md +50 -0
  63. package/templates/agent-docs/plugins/auth.md +4 -6
  64. package/templates/agent-docs/reference.md +3 -2
  65. package/templates/agent-docs/whats-new.md +59 -35
  66. package/templates/apps/api-ai/package.json +7 -7
  67. package/templates/apps/api-auth/package.json +8 -8
  68. package/templates/apps/api-backend/package.json +7 -7
  69. package/templates/apps/api-backend-deactivation/package.json +7 -7
  70. package/templates/apps/api-backend-mail/package.json +8 -8
  71. package/templates/apps/api-backend-mariadb/package.json +9 -9
  72. package/templates/apps/api-backend-sqlite/package.json +8 -8
  73. package/templates/apps/api-backend-storage/package.json +8 -8
  74. package/templates/apps/api-cms/package.json +10 -10
  75. package/templates/apps/api-cms/tests/content.write.test.ts +16 -2
  76. package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +11 -5
  77. package/templates/apps/api-collab/package.json +8 -8
  78. package/templates/apps/api-collab/tests/documents.setBody.test.ts +8 -2
  79. package/templates/apps/api-data-advanced/package.json +8 -8
  80. package/templates/apps/api-durable/package.json +8 -8
  81. package/templates/apps/api-feature-flags/package.json +9 -9
  82. package/templates/apps/api-governance/package.json +8 -8
  83. package/templates/apps/api-kv/package.json +8 -8
  84. package/templates/apps/api-moderation/package.json +8 -8
  85. package/templates/apps/api-observability/package.json +8 -8
  86. package/templates/apps/api-ratelimit/package.json +8 -8
  87. package/templates/apps/api-rbac/package.json +8 -8
  88. package/templates/apps/api-rest/package.json +7 -7
  89. package/templates/apps/api-saas/package.json +11 -11
  90. package/templates/apps/api-saas-starter/package.json +10 -10
  91. package/templates/apps/api-search/package.json +8 -8
  92. package/templates/apps/api-status/package.json +8 -8
  93. package/templates/apps/api-versioning/mutations/documents.update.mutation.server.ts +7 -5
  94. package/templates/apps/api-versioning/package.json +8 -8
  95. package/templates/apps/api-webhooks/package.json +9 -9
  96. package/templates/apps/changelog/package.json +6 -6
  97. package/templates/apps/edge-functions/package.json +2 -2
  98. package/templates/apps/frontend-admin/package.json +8 -8
  99. package/templates/apps/frontend-app/package.json +9 -9
  100. package/templates/apps/frontend-auth/package.json +8 -8
  101. package/templates/apps/frontend-blank/package.json +7 -7
  102. package/templates/apps/frontend-cms/package.json +9 -9
  103. package/templates/apps/frontend-collab/package.json +10 -10
  104. package/templates/apps/frontend-contact/package.json +7 -7
  105. package/templates/apps/frontend-dashboard/package.json +7 -7
  106. package/templates/apps/frontend-docs/package.json +7 -7
  107. package/templates/apps/frontend-i18n/package.json +6 -6
  108. package/templates/apps/frontend-landing/package.json +7 -7
  109. package/templates/apps/frontend-portal/package.json +8 -8
  110. package/templates/apps/frontend-saas/package.json +8 -8
  111. package/templates/apps/frontend-spa/package.json +7 -7
  112. package/templates/apps/frontend-ssr/package.json +7 -7
  113. package/templates/apps/frontend-ssr-api/package.json +8 -8
  114. package/templates/apps/frontend-static-blog/package.json +6 -6
  115. package/templates/apps/frontend-status/package.json +8 -8
  116. package/templates/apps/mobile-app/package.json +3 -3
  117. package/templates/patches/@effect__cluster@0.60.0.patch +51 -3
  118. package/dist/apiBuild-ZEZnCXn2.js +0 -2
  119. package/dist/baselineCommand-C2ClWZN3.js +0 -2
  120. package/dist/codegen-ChBi_hVa.js +0 -2
  121. package/dist/dbCommand-DS4b97Is.js +0 -2
  122. package/dist/doctorCommand-BYffQc0s.js +0 -2
  123. package/dist/serveCommand-CEBVP36A.js +0 -2
  124. package/dist/startupRunner-DPGFchOa.js +0 -71
  125. package/dist/updateCommand-CSBXVsYq.js +0 -2
  126. package/dist/webDev-BcykISYQ2.js +0 -2
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./loadEnv-D9nEOClM.js";
2
2
  import { r as t } from "./appModuleLoader-C9r9mxZt.js";
3
3
  import { i as n } from "./dialectDriver-CgXnDfec.js";
4
- import { t as r } from "./serveCommand-VVGcIMIw.js";
4
+ import { t as r } from "./serveCommand-DnY4VS3D.js";
5
5
  export { e as loadDotEnv, t as registerAppModules, n as registerDriver, r as runServe };
@@ -1,3 +1,3 @@
1
1
  import "./renderModeScan-D7J1B7Kw.js";
2
- import { t as e } from "./start-T4VesWiM.js";
2
+ import { t as e } from "./start-RCh6qKFe.js";
3
3
  export { e as runStartCommand };
@@ -1,6 +1,6 @@
1
1
  import { i as e, t } from "./nodeEnvironment-cGFAj1J8.js";
2
2
  import { r as n } from "./appPort-B_HpJ_ck.js";
3
- import { A as r, B as i, C as a, D as o, E as s, H as c, I as l, J as u, N as d, O as f, P as p, S as m, T as ee, U as h, V as g, a as _, b as v, d as y, f as b, i as x, j as S, k as te, n as ne, q as C, s as re, st as ie, ut as w, w as T, x as E, y as ae, z as D } from "./webDev-Dybxew86.js";
3
+ import { A as r, B as i, C as a, D as o, E as s, H as c, I as l, J as u, N as d, O as f, P as p, S as m, T as ee, U as h, V as g, a as _, b as v, d as y, f as b, i as x, j as S, k as te, lt as C, n as w, ot as ne, q as T, s as re, w as E, x as ie, y as ae, z as D } from "./webDev-BKdD7c_a.js";
4
4
  import { o as O, t as k } from "./connectionConfig-UFlIEiys.js";
5
5
  import { A, D as j, a as M, h as oe, j as N, k as se } from "./inspect-CjTYzAs_.js";
6
6
  import { a as ce, c as P, d as le, f as ue, l as F, p as I, u as de } from "./renderModeScan-D7J1B7Kw.js";
@@ -210,10 +210,10 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
210
210
  }, Ne = async (e) => {
211
211
  let { provider: t, route: n, params: i, pathname: c, dirs: l, shellTemplate: u, requestHeaders: d, requestUrl: p = "", apiOrigin: ee, apiName: h } = e, { search: g } = r(p), v = x(ee, d.cookie, { apiName: h }), y = await t.getPage(n.pattern, n.file), b = P(y, n.file), S = b === "spa" && re(l, n.dirChain).some((e) => e.hasLayout);
212
212
  if (b !== "ssr" && b !== "isr" && !S) return null;
213
- let ne = re(l, n.dirChain), C = {}, ie = null, w, ae = new AbortController();
213
+ let C = re(l, n.dirChain), w = {}, ne = null, T, ae = new AbortController();
214
214
  try {
215
- (await t.getHelpers()).beginStoreSeeds(), {chain: w, segmentLoaderData: C} = await _({
216
- entries: ne,
215
+ (await t.getHelpers()).beginStoreSeeds(), {chain: T, segmentLoaderData: w} = await _({
216
+ entries: C,
217
217
  loadLayout: (e) => t.getLayout(e.dir),
218
218
  loadError: (e) => t.getError(e.dir),
219
219
  loadPending: (e) => t.getPending(e.dir),
@@ -225,7 +225,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
225
225
  headers: d,
226
226
  query: v
227
227
  }
228
- }), typeof y.loader == "function" && !S && (ie = await y.loader({
228
+ }), typeof y.loader == "function" && !S && (ne = await y.loader({
229
229
  params: i,
230
230
  pathname: c,
231
231
  search: g,
@@ -243,7 +243,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
243
243
  throw e;
244
244
  }
245
245
  let D = await t.getHelpers();
246
- S || await E({
246
+ S || await ie({
247
247
  pageMod: y,
248
248
  params: i,
249
249
  query: v,
@@ -263,7 +263,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
263
263
  ErrorBoundary: y.ErrorBoundary,
264
264
  Pending: y.Pending,
265
265
  renderMode: b,
266
- chain: w
266
+ chain: T
267
267
  }, k = {
268
268
  cookies: D.parseCookieHeader(d.cookie),
269
269
  headers: d,
@@ -280,7 +280,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
280
280
  params: i,
281
281
  pathname: c,
282
282
  search: g,
283
- segmentLoaderData: C,
283
+ segmentLoaderData: w,
284
284
  requestContext: k,
285
285
  ...A ? {
286
286
  outerWrap: A.outerWrap,
@@ -297,7 +297,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
297
297
  let n = a({ shell: u });
298
298
  return {
299
299
  renderMode: "spa",
300
- stream: (i) => T({
300
+ stream: (i) => E({
301
301
  res: i,
302
302
  shell: n,
303
303
  htmlLang: t,
@@ -332,8 +332,8 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
332
332
  pathname: c,
333
333
  renderMode: b,
334
334
  interactive: j,
335
- loaderData: ie,
336
- segmentLoaderData: C,
335
+ loaderData: ne,
336
+ segmentLoaderData: w,
337
337
  pageLoaderRan: typeof y.loader == "function"
338
338
  }), oe = {
339
339
  descriptor: O,
@@ -357,7 +357,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
357
357
  let e = A?.locale ?? k.cookies["voltro:locale"] ?? "en", t = a({ shell: u });
358
358
  return {
359
359
  renderMode: "ssr",
360
- stream: (n) => T({
360
+ stream: (n) => E({
361
361
  res: n,
362
362
  shell: t,
363
363
  htmlLang: e,
@@ -415,7 +415,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
415
415
  async close() {}
416
416
  };
417
417
  }, Fe = async (e, t) => {
418
- let n = await w("vite", () => import("vite")), r = (await import("@vitejs/plugin-react")).default, i = (await import("@tailwindcss/vite")).default, a = await n.createServer({
418
+ let n = await C("vite", () => import("vite")), r = (await import("@vitejs/plugin-react")).default, i = (await import("@tailwindcss/vite")).default, a = await n.createServer({
419
419
  root: t,
420
420
  envPrefix: "__VOLTRO_VITE_ENV_DISABLED__",
421
421
  plugins: [r(), i()],
@@ -432,7 +432,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
432
432
  },
433
433
  appType: "custom",
434
434
  logLevel: "warn"
435
- }), o = L(e, "src", "pages"), s = (e) => e === "" ? o : L(o, e), c = C({
435
+ }), o = L(e, "src", "pages"), s = (e) => e === "" ? o : L(o, e), c = T({
436
436
  limit: u(),
437
437
  onColdStart: (e) => q.debug("ssr cold-compile start", { id: e }),
438
438
  onColdEnd: (e, { durationMs: t, failed: n }) => q.debug(`ssr cold-compile ${n ? "FAILED" : "end"} ${t}ms`, {
@@ -472,7 +472,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
472
472
  ran: !0,
473
473
  exitCode: 1
474
474
  };
475
- let { resolved: o } = await ie({
475
+ let { resolved: o } = await ne({
476
476
  rawEnv: a.env,
477
477
  appType: "web",
478
478
  root: t,
@@ -494,17 +494,17 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
494
494
  ran: !0,
495
495
  exitCode: 1
496
496
  };
497
- let C = a.name ?? "unnamed", w = he("web", C, m), T = ge(C);
497
+ let C = a.name ?? "unnamed", T = he("web", C, m), E = ge(C);
498
498
  await fe({
499
- id: w,
499
+ id: T,
500
500
  name: C,
501
501
  kind: "web",
502
502
  url: `http://localhost:${m}`,
503
503
  pid: process.pid,
504
504
  status: "booting",
505
- logFile: T.path
505
+ logFile: E.path
506
506
  }).catch(() => {});
507
- let E = L(x, "_voltro", "shell.html"), P = await Z(E) ? await z.readFile(E, "utf8") : await z.readFile(te, "utf8"), I = await b(t, a), ye = ne(I[0]), be = I[0]?.name, R, B = null, xe = process.env.SSR_CACHE === "postgres";
507
+ let ie = L(x, "_voltro", "shell.html"), P = await Z(ie) ? await z.readFile(ie, "utf8") : await z.readFile(te, "utf8"), I = await b(t, a), ye = w(I[0]), be = I[0]?.name, R, B = null, xe = process.env.SSR_CACHE === "postgres";
508
508
  if (xe && k()) B = new (await (import("pg"))).default.Client(O()), await B.connect(), R = De({ client: B }), q.info("isr cache backend: postgres", { table: "voltro_isr_cache" });
509
509
  else if (xe) {
510
510
  let t = "SSR_CACHE=postgres, but nothing in the environment names a database (looked for DB_URL, DB_PRIMARY_URL, DB_DIRECT_URL, DB_MIGRATE_URL, DB_HOST, PG_HOST).";
@@ -1023,13 +1023,13 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
1023
1023
  }), q.debug("dynamic + ssr/isr routes resolve via SSR loader");
1024
1024
  try {
1025
1025
  await fe({
1026
- id: w,
1026
+ id: T,
1027
1027
  name: C,
1028
1028
  kind: "web",
1029
1029
  url: `http://localhost:${m}`,
1030
1030
  pid: process.pid,
1031
1031
  status: "live",
1032
- logFile: T.path
1032
+ logFile: E.path
1033
1033
  });
1034
1034
  let e = p(t);
1035
1035
  if (e) {
@@ -1038,13 +1038,13 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
1038
1038
  }
1039
1039
  setInterval(() => {
1040
1040
  fe({
1041
- id: w,
1041
+ id: T,
1042
1042
  name: C,
1043
1043
  kind: "web",
1044
1044
  url: `http://localhost:${m}`,
1045
1045
  pid: process.pid,
1046
1046
  status: "live",
1047
- logFile: T.path
1047
+ logFile: E.path
1048
1048
  }).catch(() => {});
1049
1049
  }, 1e4).unref();
1050
1050
  } catch (e) {
@@ -1066,7 +1066,7 @@ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
1066
1066
  if (Ge = !0, q.info(`received ${e}, shutting down`, { signal: e }), Ue.close(), await $.stop(), B) try {
1067
1067
  await B.end();
1068
1068
  } catch {}
1069
- await K.close(), await me(w), await T.dispose(), process.exit(0);
1069
+ await K.close(), await me(T), await E.dispose(), process.exit(0);
1070
1070
  }
1071
1071
  };
1072
1072
  process.on("SIGINT", Ke("SIGINT")), process.on("SIGTERM", Ke("SIGTERM"));
@@ -1,3 +1,3 @@
1
1
  import { t as e } from "./loadEnv-D9nEOClM.js";
2
- import { t } from "./start-T4VesWiM.js";
2
+ import { t } from "./start-RCh6qKFe.js";
3
3
  export { e as loadDotEnv, t as runStartCommand };
package/dist/startup.d.ts CHANGED
@@ -3,6 +3,35 @@ import { StartupContext } from '@voltro/runtime';
3
3
  import { StartupFn } from '@voltro/runtime';
4
4
  import { SyncLogger } from '@voltro/logger';
5
5
 
6
+ /**
7
+ * How long a startup may run before the boot refuses it.
8
+ *
9
+ * ── Why there is a ceiling at all, and why it used to be 2 seconds ─────────
10
+ *
11
+ * The old value was 2000 ms and it was not a ceiling — it was a RACE the boot
12
+ * always won. A startup slower than that was reported as fine and the boot
13
+ * moved on, so a rejection arriving afterwards had nothing left to refuse.
14
+ *
15
+ * That race existed to protect one shape: a startup that never returns because
16
+ * it holds a fiber until shutdown. Counted before changing it, that shape
17
+ * appears in ZERO of the four startups this framework ships — `warm.startup.tsx`
18
+ * (twice), `searchBackfill.startup.tsx` and the memory fixture's all return.
19
+ * `warm` is the one to copy: it starts an interval, hands the teardown back
20
+ * through `onShutdown`, and returns.
21
+ *
22
+ * So the race protected the shape we discourage and penalised the shape we
23
+ * teach — and the penalised one includes `searchBackfill`, our own example,
24
+ * which awaits a full-table query plus an index backfill and is the likeliest
25
+ * thing in the box to exceed two seconds. Slow AND failing put a consumer back
26
+ * in exactly the silent state this file was fixed for.
27
+ *
28
+ * 60 seconds because the number now has one job: turn "hangs forever" into
29
+ * something diagnosable. It must never fire for honest work, and a boot that
30
+ * refuses after a minute with a message naming the file beats one that waits
31
+ * for a fiber that is never coming.
32
+ */
33
+ export declare const DEFAULT_STARTUP_TIMEOUT_MS = 60000;
34
+
6
35
  export declare interface DiscoveredStartup {
7
36
  readonly file: string;
8
37
  readonly id: string;
@@ -13,7 +42,7 @@ export declare const loadStartupFiles: (files: ReadonlyArray<string>, log: SyncL
13
42
 
14
43
  /** Run every discovered startup. Returns a handle whose `shutdown()`
15
44
  * drains every registered teardown LIFO. */
16
- export declare const runStartups: (startups: ReadonlyArray<DiscoveredStartup>, store: DataStore, parentLog: SyncLogger) => Promise<StartupHandle>;
45
+ export declare const runStartups: (startups: ReadonlyArray<DiscoveredStartup>, store: DataStore, parentLog: SyncLogger, ceilingMs?: number) => Promise<StartupHandle>;
17
46
 
18
47
  export { StartupContext }
19
48
 
@@ -23,4 +52,14 @@ export declare interface StartupHandle {
23
52
  readonly shutdown: () => Promise<void>;
24
53
  }
25
54
 
55
+ /**
56
+ * The ceiling, from `VOLTRO_STARTUP_TIMEOUT_MS`.
57
+ *
58
+ * An app with a genuinely long backfill raises it; the env var is the whole
59
+ * knob, and the refusal names it so nobody has to find this constant. Not an
60
+ * `app.config.ts` field: this is an operational safety limit, not a design
61
+ * choice about the app, and the parameter below is the seam if that changes.
62
+ */
63
+ export declare const startupTimeoutMsFromEnv: (envValue: string | undefined) => number;
64
+
26
65
  export { }
package/dist/startup.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as e, t } from "./startupRunner-DPGFchOa.js";
2
- export { t as loadStartupFiles, e as runStartups };
1
+ import { i as e, n as t, r as n, t as r } from "./startupRunner-CEqQq7ax.js";
2
+ export { r as DEFAULT_STARTUP_TIMEOUT_MS, t as loadStartupFiles, n as runStartups, e as startupTimeoutMsFromEnv };
@@ -0,0 +1,108 @@
1
+ import { n as e } from "./appModuleLoader-C9r9mxZt.js";
2
+ import { t } from "./bootRefusal-pgvDxlrU.js";
3
+ import { basename as n } from "node:path";
4
+ import { publishServerError as r } from "@voltro/protocol";
5
+ //#region src/startupRunner.ts
6
+ var i = (e) => e.replace(/\.startup\.tsx?$/, ""), a = async (r, a) => {
7
+ let o = [];
8
+ for (let a of r) {
9
+ let r;
10
+ try {
11
+ r = await e(a);
12
+ } catch (e) {
13
+ throw t(`startup file \`${a}\` could not be imported — refusing to boot.\n cause: ${e instanceof Error ? e.message : String(e)}\n
14
+ It was discovered as a startup, so whatever it registers is missing from this process. Skipping it would leave a server that looks healthy.`);
15
+ }
16
+ let s = r.default;
17
+ if (typeof s != "function") throw t(`startup file \`${a}\` has no default-exported function — refusing to boot.\n
18
+ The convention is a DEFAULT-exported function, not a \`defineX\` wrapper:
19
+
20
+ export default async (ctx) => { /* register here */ }
21
+
22
+ A named export is discovered and never runs, which is indistinguishable from a startup that ran and did nothing.`);
23
+ o.push({
24
+ file: a,
25
+ id: i(n(a)),
26
+ fn: s
27
+ });
28
+ }
29
+ return o;
30
+ }, o = 6e4, s = (e) => {
31
+ let t = Number(e);
32
+ return Number.isFinite(t) && t > 0 ? t : o;
33
+ }, c = async (e, n, i, a = s(process.env.VOLTRO_STARTUP_TIMEOUT_MS)) => {
34
+ let o = [];
35
+ for (let s of e) {
36
+ let e = i.child(`startup:${s.id}`), c = {
37
+ store: n,
38
+ log: e,
39
+ id: s.id,
40
+ onShutdown: (e) => {
41
+ o.push({
42
+ id: s.id,
43
+ cb: e
44
+ });
45
+ }
46
+ }, l, u = !1;
47
+ try {
48
+ let e = s.fn(c);
49
+ if (e instanceof Promise) {
50
+ let t, n = new Promise((e) => {
51
+ t = setTimeout(() => e("timeout"), a);
52
+ });
53
+ try {
54
+ u = await Promise.race([e.then(() => "done", (e) => (l = e, r({
55
+ error: e,
56
+ source: "startup",
57
+ name: s.id
58
+ }), "done")), n]) === "timeout";
59
+ } finally {
60
+ t !== void 0 && clearTimeout(t);
61
+ }
62
+ }
63
+ } catch (e) {
64
+ l = e, r({
65
+ error: e,
66
+ source: "startup",
67
+ name: s.id
68
+ });
69
+ }
70
+ if (l !== void 0) throw e.error("startup: failed", { file: s.file }, l), t(`startup \`${s.id}\` failed — refusing to boot.\n file: ${s.file}\n cause: ${l instanceof Error ? l.message : String(l)}\n
71
+ A startup is where an app REGISTERS things the request path depends on (\`setRowFilter\` is the one that matters most). Continuing past a failed one produces a server that looks healthy and is missing whatever the startup was there to install — which is why this used to be a warning and is now a refusal.
72
+
73
+ If this failure IS acceptable, catch it inside the startup function itself, where the decision is visible to a reviewer.`);
74
+ if (u) throw e.error("startup: never returned", {
75
+ file: s.file,
76
+ ceilingMs: a
77
+ }), t(`startup \`${s.id}\` did not return within ${a}ms — refusing to boot.\n file: ${s.file}\n
78
+ A startup must START its work and RETURN. Hand the teardown back through
79
+ \`onShutdown\` instead of awaiting forever:
80
+
81
+ export default async ({ store, log, onShutdown }) => {
82
+ const timer = setInterval(() => sync(store), 60_000)
83
+ onShutdown(() => clearInterval(timer))
84
+ }
85
+
86
+ Awaiting a fiber that resolves only on shutdown used to be allowed, and it
87
+ is what made a slow startup that then FAILED unreportable — the boot had
88
+ already moved on, so nothing was left to refuse.
89
+
90
+ If this startup is legitimately slower than ${a}ms, raise\nVOLTRO_STARTUP_TIMEOUT_MS — or, better, do the registration first and the
91
+ long work after, so a failure in the long half cannot hide a missing
92
+ registration.`);
93
+ e.info("startup: registered");
94
+ }
95
+ return { shutdown: async () => {
96
+ for (let e = o.length - 1; e >= 0; e--) {
97
+ let { id: t, cb: n } = o[e], r = i.child(`startup:${t}`);
98
+ try {
99
+ let e = n();
100
+ e instanceof Promise && await Promise.race([e, new Promise((e) => setTimeout(e, 5e3))]);
101
+ } catch (e) {
102
+ r.warn("startup: teardown threw", {}, e);
103
+ }
104
+ }
105
+ } };
106
+ };
107
+ //#endregion
108
+ export { s as i, a as n, c as r, o as t };
@@ -0,0 +1,27 @@
1
+ //#region src/subcommandNames.ts
2
+ var e = [
3
+ "plan",
4
+ "apply",
5
+ "branch",
6
+ "plans",
7
+ "drift",
8
+ "squash",
9
+ "restore-snapshot",
10
+ "generate",
11
+ "migrate",
12
+ "rollback",
13
+ "status",
14
+ "files",
15
+ "rollback-file",
16
+ "seed",
17
+ "gc-snapshots",
18
+ "adopt",
19
+ "scan-credentials",
20
+ "encrypt-column"
21
+ ], t = [
22
+ "scope",
23
+ "export",
24
+ "erase"
25
+ ], n = (e) => `<${e.join("|")}>`;
26
+ //#endregion
27
+ export { t as n, n as r, e as t };
@@ -4,10 +4,7 @@ import { fileURLToPath as s } from "node:url";
4
4
  //#region src/templates.ts
5
5
  var c = e(s(import.meta.url)), l = (() => {
6
6
  let n = process.env.VOLTRO_TEMPLATES_DIR;
7
- if (n) {
8
- let e = t(n, "apps");
9
- if (i(e)) return e;
10
- }
7
+ if (n) return t(n, "apps");
11
8
  let a = r(c, "..", "templates", "apps"), o = c;
12
9
  for (let n = 0; n < 12; n++) {
13
10
  for (let e of ["voltro-templates", ".voltro-templates"]) {
@@ -79,7 +79,7 @@ var d = l({ scope: "voltro:test" }), f = (e) => {
79
79
  }, C = async (e) => {
80
80
  let t = m(e);
81
81
  if (!o(t.root)) return console.error(`voltro test: no such directory: ${t.root}`), 1;
82
- let { checkRpcGroupFreshness: n } = await import("./codegen-ChBi_hVa.js"), r = await n(t.root);
82
+ let { checkRpcGroupFreshness: n } = await import("./codegen-uYrrDzQv.js"), r = await n(t.root);
83
83
  if (r.kind === "stale") return console.error(`voltro test: ${r.file} is stale — ${r.reason}.`), console.error(" The tests would run against the previously generated procedure group, pass, and prove nothing about the descriptors you just edited.\n Run `voltro codegen` (or boot `voltro dev` once) and try again."), 1;
84
84
  d.info("running vitest", {
85
85
  root: t.root,
@@ -0,0 +1,2 @@
1
+ import { f as e, p as t } from "./updateCommand-CmksX7m_.js";
2
+ export { e as runApplyCodemods, t as runUpdateCommand };
@@ -1,4 +1,4 @@
1
- import { n as e, r as t, t as n } from "./codemodRunner-DjQGrdmg.js";
1
+ import { n as e, r as t, t as n } from "./codemodRunner-CXgQ-ecJ.js";
2
2
  import { basename as r, dirname as i, join as a, relative as o, resolve as s } from "node:path";
3
3
  import { existsSync as c, readFileSync as l, readdirSync as u, statSync as d, writeFileSync as f } from "node:fs";
4
4
  import { totalmem as p } from "node:os";