@voltro/cli 0.6.0 → 0.8.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 (63) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/bin/voltro.mjs +15 -6
  3. package/dist/apiBuild-CmtI6Ygh.js +2 -0
  4. package/dist/{apiBuild-BxY44VGj.js → apiBuild-OrDkXGFX.js} +21 -20
  5. package/dist/bin.js +2 -2
  6. package/dist/{commands-My_YlUJV.js → commands-DT4PipBz.js} +2489 -2972
  7. package/dist/dev-Ca1jdwnt.js +2 -0
  8. package/dist/dev-Ga65K4jc.js +6492 -0
  9. package/dist/index.js +1 -1
  10. package/dist/inspectMetrics-EK0CcHyu.js +3929 -0
  11. package/dist/{serveCommand-C-XQ89NJ.js → serveCommand-CR5xnYZt.js} +209 -208
  12. package/dist/serveEntry.js +3 -2
  13. package/dist/start-DuyBQxW3.js +977 -0
  14. package/dist/startEntry.d.ts +5 -0
  15. package/dist/startEntry.js +3 -0
  16. package/package.json +17 -17
  17. package/templates/AGENTS.core.md +15 -4
  18. package/templates/AGENTS.md +15 -4
  19. package/templates/agent-docs/authentication.md +111 -13
  20. package/templates/agent-docs/cli.md +94 -6
  21. package/templates/agent-docs/data.md +99 -31
  22. package/templates/agent-docs/database/schema.md +1 -1
  23. package/templates/agent-docs/reference.md +20 -0
  24. package/templates/agent-docs/routing.md +158 -24
  25. package/templates/agent-docs/testing.md +82 -0
  26. package/templates/apps/api-ai/package.json +7 -7
  27. package/templates/apps/api-auth/package.json +8 -8
  28. package/templates/apps/api-backend/package.json +7 -7
  29. package/templates/apps/api-backend-deactivation/package.json +7 -7
  30. package/templates/apps/api-backend-mail/package.json +8 -8
  31. package/templates/apps/api-backend-mariadb/package.json +9 -9
  32. package/templates/apps/api-backend-storage/package.json +8 -8
  33. package/templates/apps/api-data-advanced/package.json +8 -8
  34. package/templates/apps/api-durable/package.json +8 -8
  35. package/templates/apps/api-feature-flags/package.json +9 -9
  36. package/templates/apps/api-governance/package.json +8 -8
  37. package/templates/apps/api-kv/package.json +8 -8
  38. package/templates/apps/api-moderation/package.json +8 -8
  39. package/templates/apps/api-observability/package.json +8 -8
  40. package/templates/apps/api-ratelimit/package.json +8 -8
  41. package/templates/apps/api-rbac/package.json +8 -8
  42. package/templates/apps/api-rest/package.json +7 -7
  43. package/templates/apps/api-saas/package.json +11 -11
  44. package/templates/apps/api-search/package.json +8 -8
  45. package/templates/apps/api-versioning/package.json +8 -8
  46. package/templates/apps/api-webhooks/package.json +8 -8
  47. package/templates/apps/changelog/package.json +6 -6
  48. package/templates/apps/edge-functions/package.json +2 -2
  49. package/templates/apps/frontend-admin/package.json +8 -8
  50. package/templates/apps/frontend-app/package.json +8 -8
  51. package/templates/apps/frontend-blank/package.json +7 -7
  52. package/templates/apps/frontend-contact/package.json +7 -7
  53. package/templates/apps/frontend-dashboard/package.json +7 -7
  54. package/templates/apps/frontend-docs/package.json +7 -7
  55. package/templates/apps/frontend-i18n/package.json +6 -6
  56. package/templates/apps/frontend-landing/package.json +7 -7
  57. package/templates/apps/frontend-spa/package.json +7 -7
  58. package/templates/apps/frontend-ssr/package.json +7 -7
  59. package/templates/apps/frontend-ssr-api/package.json +8 -8
  60. package/templates/apps/frontend-static-blog/package.json +6 -6
  61. package/dist/apiBuild-Bk4JBt-i.js +0 -2
  62. package/dist/dev-Bjq-nIvH.js +0 -10186
  63. package/dist/dev-D6b74iXI.js +0 -2
@@ -0,0 +1,977 @@
1
+ import { A as e, B as t, D as n, E as r, H as i, L as a, M as o, O as s, S as c, T as l, U as u, V as d, W as f, a as p, b as m, d as h, f as g, ft as _, g as v, h as y, i as b, j as x, k as S, m as ee, n as te, o as C, p as w, pt as T, q as E, r as D, s as O, t as k, tt as ne, vt as re, w as ie, xt as A, y as ae } from "./inspectMetrics-EK0CcHyu.js";
2
+ import { dirname as j, extname as M, join as N, resolve as P } from "node:path";
3
+ import { fileURLToPath as F, pathToFileURL as I } from "node:url";
4
+ import { createLogger as L, printReadyBlock as oe } from "@voltro/logger";
5
+ import { promises as R } from "node:fs";
6
+ import { snapshotMetricsSync as se } from "@voltro/runtime";
7
+ import { createServer as ce } from "node:http";
8
+ //#region src/routeMeta.ts
9
+ var z = (e) => typeof e.renderMode == "string" ? e.renderMode : e.dynamic === "force-dynamic" ? "ssr" : (e.dynamic, "static"), le = (e) => ({
10
+ renderMode: z(e),
11
+ tenantAware: e.tenantAware === !0,
12
+ revalidateMs: o(e.revalidate),
13
+ staleWhileRevalidateMs: o(e.staleWhileRevalidate) ?? 0,
14
+ cacheInvalidatesOn: Array.isArray(e.cacheInvalidatesOn) ? e.cacheInvalidatesOn : [],
15
+ interactive: e.interactive ?? "full",
16
+ hasLoader: typeof e.loader == "function",
17
+ hasMeta: e.meta != null,
18
+ hasGetStaticPaths: typeof e.getStaticPaths == "function"
19
+ }), B = L({ scope: "voltro:start:cdc" }), V = (e) => (t) => {
20
+ let n = 0;
21
+ for (let [r, i] of e.tableSubscriptionsByPattern) {
22
+ if (!i.includes(t)) continue;
23
+ let a = e.regexByPattern.get(r);
24
+ a && (n += e.cache.invalidateBy((e) => {
25
+ let t = e.split("|")[0] ?? e;
26
+ return a.test(t);
27
+ }));
28
+ }
29
+ return n;
30
+ }, H = () => !process.env.PG_HOST && !process.env.PG_DATABASE ? null : {
31
+ host: process.env.PG_HOST ?? "localhost",
32
+ port: Number(process.env.PG_PORT ?? 5432),
33
+ user: process.env.PG_USER ?? "app",
34
+ password: process.env.PG_PASSWORD ?? "app",
35
+ database: process.env.PG_DATABASE ?? "app"
36
+ }, ue = (e) => {
37
+ let t = e.channel ?? "framework_changes", n = V(e), r = null, i = null;
38
+ return {
39
+ start: async () => {
40
+ let a = H();
41
+ if (!a) {
42
+ B.debug("skipped — no PG_* env vars set");
43
+ return;
44
+ }
45
+ if (e.tableSubscriptionsByPattern.size === 0) {
46
+ B.debug("skipped — no pages declare cacheInvalidatesOn");
47
+ return;
48
+ }
49
+ r = new (await (import("pg"))).default.Client(a), await r.connect(), i = (e) => {
50
+ if (e.channel !== t || !e.payload) return;
51
+ let r;
52
+ try {
53
+ r = JSON.parse(e.payload);
54
+ } catch (e) {
55
+ B.warn("bad CDC payload", {}, e);
56
+ return;
57
+ }
58
+ let i = n(r.table);
59
+ i > 0 && B.info(`${r.op} on '${r.table}' → dropped ${i} cache entries`, {
60
+ op: r.op,
61
+ table: r.table,
62
+ dropped: i
63
+ });
64
+ }, r.on("notification", i), await r.query(`LISTEN ${t}`), B.info(`LISTEN ${t}`, {
65
+ channel: t,
66
+ host: a.host,
67
+ port: a.port,
68
+ database: a.database
69
+ });
70
+ },
71
+ stop: async () => {
72
+ if (r) {
73
+ try {
74
+ i && r.off("notification", i), await r.end();
75
+ } catch {}
76
+ r = null, i = null;
77
+ }
78
+ }
79
+ };
80
+ }, U = L({ scope: "voltro:start:isr-cache" }), W = "voltro_isr_cache", G = (e) => `
81
+ CREATE TABLE IF NOT EXISTS ${e} (
82
+ cache_key TEXT PRIMARY KEY,
83
+ html TEXT NOT NULL,
84
+ rendered_at BIGINT NOT NULL,
85
+ expires_at BIGINT NOT NULL,
86
+ servable_until BIGINT NOT NULL,
87
+ revalidate_ms BIGINT NOT NULL,
88
+ stale_while_revalidate_ms BIGINT NOT NULL DEFAULT 0
89
+ );
90
+ CREATE INDEX IF NOT EXISTS ${e}_servable_until_idx ON ${e} (servable_until);
91
+ `, de = (e) => {
92
+ let t = e.tableName ?? W, { client: n } = e, r = /* @__PURE__ */ new Map(), i = !1, a = (async () => {
93
+ i || (i = !0, await n.query(G(t)));
94
+ })().then(async () => {
95
+ let e = await n.query(`SELECT * FROM ${t}`);
96
+ for (let t of e.rows) r.set(t.cache_key, {
97
+ html: t.html,
98
+ renderedAt: Number(t.rendered_at),
99
+ expiresAt: Number(t.expires_at),
100
+ servableUntil: Number(t.servable_until),
101
+ revalidateMs: Number(t.revalidate_ms),
102
+ staleWhileRevalidateMs: Number(t.stale_while_revalidate_ms)
103
+ });
104
+ }).catch((e) => {
105
+ U.warn("postgres bootstrap failed", {}, e);
106
+ }), o = (e, t) => {
107
+ a.then(() => n.query(e, t)).catch((e) => {
108
+ U.warn("postgres write failed", {}, e);
109
+ });
110
+ };
111
+ return {
112
+ get(e) {
113
+ return r.get(e);
114
+ },
115
+ set(e, n) {
116
+ r.set(e, n), o(`INSERT INTO ${t}
117
+ (cache_key, html, rendered_at, expires_at, servable_until, revalidate_ms, stale_while_revalidate_ms)
118
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
119
+ ON CONFLICT (cache_key) DO UPDATE SET
120
+ html = EXCLUDED.html,
121
+ rendered_at = EXCLUDED.rendered_at,
122
+ expires_at = EXCLUDED.expires_at,
123
+ servable_until = EXCLUDED.servable_until,
124
+ revalidate_ms = EXCLUDED.revalidate_ms,
125
+ stale_while_revalidate_ms = EXCLUDED.stale_while_revalidate_ms`, [
126
+ e,
127
+ n.html,
128
+ n.renderedAt,
129
+ n.expiresAt,
130
+ n.servableUntil,
131
+ n.revalidateMs,
132
+ n.staleWhileRevalidateMs
133
+ ]);
134
+ },
135
+ invalidate(e) {
136
+ r.delete(e), o(`DELETE FROM ${t} WHERE cache_key = $1`, [e]);
137
+ },
138
+ invalidateBy(e) {
139
+ let n = 0, i = [];
140
+ for (let t of r.keys()) e(t) && (r.delete(t), i.push(t), n++);
141
+ return i.length > 0 && o(`DELETE FROM ${t} WHERE cache_key = ANY($1)`, [i]), n;
142
+ },
143
+ clear() {
144
+ r.clear(), o(`DELETE FROM ${t}`, []);
145
+ },
146
+ size() {
147
+ return r.size;
148
+ },
149
+ snapshot() {
150
+ return Array.from(r.entries());
151
+ }
152
+ };
153
+ }, K = L({ scope: "voltro:start" }), fe = await (async () => {
154
+ try {
155
+ let e = N(j(F(import.meta.url)), "..", "package.json"), t = await R.readFile(e, "utf8");
156
+ return JSON.parse(t).version ?? "0.0.0";
157
+ } catch {
158
+ return "0.0.0";
159
+ }
160
+ })(), q = (e) => e instanceof Error && e._voltroControl === "redirect" && typeof e.location == "string", J = (e) => e instanceof Error && e._voltroControl === "not-found", pe = {
161
+ ".html": "text/html; charset=utf-8",
162
+ ".js": "application/javascript",
163
+ ".mjs": "application/javascript",
164
+ ".css": "text/css",
165
+ ".json": "application/json",
166
+ ".xml": "application/xml; charset=utf-8",
167
+ ".rss": "application/rss+xml; charset=utf-8",
168
+ ".atom": "application/atom+xml; charset=utf-8",
169
+ ".svg": "image/svg+xml",
170
+ ".png": "image/png",
171
+ ".jpg": "image/jpeg",
172
+ ".jpeg": "image/jpeg",
173
+ ".webp": "image/webp",
174
+ ".ico": "image/x-icon",
175
+ ".woff": "font/woff",
176
+ ".woff2": "font/woff2",
177
+ ".txt": "text/plain; charset=utf-8"
178
+ }, me = (e) => pe[M(e).toLowerCase()] ?? "application/octet-stream", Y = (e) => e.replace(/<script\b(?=[^>]*\btype="module")(?:(?!<\/script>).)*<\/script>/gi, "").replace(/<script\b(?=[^>]*\bsrc=)(?:(?!<\/script>).)*<\/script>/gi, "").replace(/<link\b[^>]*\brel="modulepreload"[^>]*>/gi, ""), X = async (e) => {
179
+ try {
180
+ return await R.stat(e), !0;
181
+ } catch {
182
+ return !1;
183
+ }
184
+ }, he = (e) => {
185
+ let t = [], n = 0, r = e.split("/").filter(Boolean), i = [];
186
+ for (let e of r) e.startsWith("[[...") && e.endsWith("]]") ? (i.push("(.*)"), t.push(e.slice(5, -2)), n += 2) : e.startsWith("[...") && e.endsWith("]") ? (i.push("(.+)"), t.push(e.slice(4, -1)), n += 3) : e.startsWith("[") && e.endsWith("]") ? (i.push("([^/]+)"), t.push(e.slice(1, -1)), n += 1) : i.push(e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
187
+ let a = i.join("/");
188
+ return {
189
+ regex: a.length === 0 ? /^\/?$/ : RegExp(`^/${a}/?$`),
190
+ paramNames: t,
191
+ priority: n
192
+ };
193
+ }, ge = (e) => e.map((e) => {
194
+ let { regex: t, paramNames: n, priority: r } = he(e.pattern);
195
+ return {
196
+ pattern: e.pattern,
197
+ file: e.file,
198
+ dirChain: e.dirChain,
199
+ regex: t,
200
+ paramNames: n,
201
+ priority: r
202
+ };
203
+ }).slice().sort((e, t) => e.priority - t.priority), _e = (e, t) => {
204
+ for (let n of t) {
205
+ let t = n.regex.exec(e);
206
+ if (!t) continue;
207
+ let r = {};
208
+ for (let e = 0; e < n.paramNames.length; e++) r[n.paramNames[e]] = t[e + 1] ?? "";
209
+ return {
210
+ route: n,
211
+ params: r
212
+ };
213
+ }
214
+ return null;
215
+ }, ve = async (e) => {
216
+ let { provider: t, route: n, params: r, pathname: i, dirs: a, shellTemplate: s, requestHeaders: c, requestUrl: l = "", apiOrigin: u } = e, d = D(u, c.cookie), f = await t.getPage(n.pattern, n.file), m = z(f);
217
+ if (m !== "ssr" && m !== "isr") return null;
218
+ let h = p(a, n.dirChain), g = {}, _ = null, y, x = new AbortController();
219
+ try {
220
+ ({chain: y, segmentLoaderData: g} = await b({
221
+ entries: h,
222
+ loadLayout: (e) => t.getLayout(e.dir),
223
+ loadError: (e) => t.getError(e.dir),
224
+ loadPending: (e) => t.getPending(e.dir),
225
+ ctx: {
226
+ params: r,
227
+ pathname: i,
228
+ signal: x.signal,
229
+ headers: c,
230
+ query: d
231
+ }
232
+ })), typeof f.loader == "function" && (_ = await f.loader({
233
+ params: r,
234
+ pathname: i,
235
+ signal: x.signal,
236
+ headers: c,
237
+ query: d
238
+ }));
239
+ } catch (e) {
240
+ if (q(e)) return {
241
+ control: "redirect",
242
+ status: e.status,
243
+ location: e.location
244
+ };
245
+ if (J(e)) return { control: "not-found" };
246
+ throw e;
247
+ }
248
+ let S = await t.getHelpers(), te = {
249
+ pattern: n.pattern,
250
+ Component: f.default,
251
+ meta: f.meta,
252
+ loader: f.loader,
253
+ ErrorBoundary: f.ErrorBoundary,
254
+ Pending: f.Pending,
255
+ renderMode: m,
256
+ chain: y
257
+ }, C = {
258
+ cookies: S.parseCookieHeader(c.cookie),
259
+ headers: c,
260
+ url: l
261
+ }, T = S.i18n?.resolve(c.cookie, c["accept-language"]), E = f.interactive ?? "full", O = v({
262
+ helpers: S,
263
+ page: n.pattern,
264
+ renderMode: m,
265
+ interactive: E,
266
+ loaderData: _,
267
+ segmentLoaderData: g,
268
+ pageLoaderRan: typeof f.loader == "function"
269
+ }), k = {
270
+ descriptor: te,
271
+ params: r,
272
+ pathname: i,
273
+ loaderData: O.loaderData,
274
+ segmentLoaderData: O.segmentLoaderData,
275
+ requestContext: C,
276
+ ...T ? {
277
+ outerWrap: T.outerWrap,
278
+ locale: T.locale
279
+ } : {}
280
+ }, ne = (e) => [
281
+ S.renderMetaToHtml(e),
282
+ `<meta name="voltro-interactive" content="${E}" />`,
283
+ ...O.hasDeferred ? [S.renderDeferredRegistryScript()] : [],
284
+ S.renderRouterStateScript(O.stateInput)
285
+ ].filter((e) => e.length > 0).join("\n ");
286
+ if (m === "ssr" && E === "full" && e.buffered !== !0) {
287
+ let e = T?.locale ?? C.cookies["voltro:lang"] ?? "en", t = w({ shell: s });
288
+ return {
289
+ renderMode: "ssr",
290
+ stream: (n) => ee({
291
+ res: n,
292
+ shell: t,
293
+ htmlLang: e,
294
+ headers: {
295
+ "content-type": "text/html; charset=utf-8",
296
+ "x-voltro-rendered-by": "ssr"
297
+ },
298
+ headInjection: ne,
299
+ render: (e) => S.renderPageToStream({
300
+ ...k,
301
+ ...e
302
+ }),
303
+ onShellError: (e) => {
304
+ K.error("ssr shell render failed", { pathname: i }, e), n.writeHead(500, { "content-type": "text/plain; charset=utf-8" }), n.end("server error");
305
+ }
306
+ })
307
+ };
308
+ }
309
+ let { html: re, meta: ie } = S.renderPageToHtml(k), A = ne(ie), ae = T?.locale ?? C.cookies["voltro:lang"] ?? "en", j = s.replace("<div id=\"root\"></div>", `<div id="root">${re}</div>`).replace("</head>", ` ${A}\n </head>`).replace(/<html(\s+lang="[^"]*")?/i, `<html lang="${ae}"`);
310
+ E === "none" && (j = Y(j));
311
+ let M = m === "isr" ? o(f.revalidate) : null;
312
+ return {
313
+ html: j,
314
+ renderMode: m,
315
+ revalidateMs: M
316
+ };
317
+ }, Z = (e) => e === "" ? "/" : e, ye = async (e) => {
318
+ let t = await import(I(e).href);
319
+ return {
320
+ kind: "bundle",
321
+ getPageMeta: () => t.pageMeta,
322
+ async getPage(e, n) {
323
+ let r = t.pages[e];
324
+ if (!r) throw Error(`[voltro start] ssr bundle missing page for pattern ${e}`);
325
+ return typeof r == "function" ? await r() : r;
326
+ },
327
+ async getLayout(e) {
328
+ let n = t.layouts[Z(e)];
329
+ if (!n) throw Error(`[voltro start] ssr bundle missing layout for dir ${Z(e)}`);
330
+ return typeof n == "function" ? await n() : n;
331
+ },
332
+ async getError(e) {
333
+ let n = t.errors[Z(e)];
334
+ if (!n) throw Error(`[voltro start] ssr bundle missing error for dir ${Z(e)}`);
335
+ return typeof n == "function" ? await n() : n;
336
+ },
337
+ async getPending(e) {
338
+ let n = t.pendings[Z(e)];
339
+ if (!n) throw Error(`[voltro start] ssr bundle missing pending for dir ${Z(e)}`);
340
+ return typeof n == "function" ? await n() : n;
341
+ },
342
+ async getHelpers() {
343
+ return t.helpers;
344
+ },
345
+ async close() {}
346
+ };
347
+ }, be = async (e, t) => {
348
+ let n = await A("vite", () => import("vite")), r = (await import("@vitejs/plugin-react")).default, i = (await import("@tailwindcss/vite")).default, a = await n.createServer({
349
+ root: t,
350
+ envPrefix: "__VOLTRO_VITE_ENV_DISABLED__",
351
+ plugins: [r(), i()],
352
+ resolve: { dedupe: ["react", "react-dom"] },
353
+ ssr: { noExternal: ["react-intl"] },
354
+ server: {
355
+ middlewareMode: !0,
356
+ hmr: !1
357
+ },
358
+ appType: "custom",
359
+ logLevel: "warn"
360
+ }), o = N(e, "src", "pages"), s = (e) => e === "" ? o : N(o, e);
361
+ return {
362
+ kind: "middleware",
363
+ async getPage(e, t) {
364
+ return a.ssrLoadModule(N(o, t));
365
+ },
366
+ async getLayout(e) {
367
+ return a.ssrLoadModule(N(s(e), "layout.tsx"));
368
+ },
369
+ async getError(e) {
370
+ return a.ssrLoadModule(N(s(e), "error.tsx"));
371
+ },
372
+ async getPending(e) {
373
+ return a.ssrLoadModule(N(s(e), "loading.tsx"));
374
+ },
375
+ async getHelpers() {
376
+ return await a.ssrLoadModule("@voltro/web/ssr");
377
+ },
378
+ async close() {
379
+ await a.close();
380
+ }
381
+ };
382
+ }, Q = async (o) => {
383
+ let p = await C(o);
384
+ if (!p) return {
385
+ ran: !1,
386
+ exitCode: 0
387
+ };
388
+ if (p.type !== "web") return K.error("only web apps are supported by `voltro start`"), {
389
+ ran: !0,
390
+ exitCode: 1
391
+ };
392
+ let { resolved: v } = await re({
393
+ rawEnv: p.env,
394
+ appType: "web",
395
+ root: o,
396
+ log: K,
397
+ generateTypes: !1
398
+ }), b = v.publicValues, ee = Date.now(), w = u();
399
+ t(w), K.info("booting voltro start", { app: p.name ?? "(unnamed)" });
400
+ let D = (() => {
401
+ if (process.env.VOLTRO_IS_DASHBOARD === "1" && process.env.VOLTRO_DASHBOARD_PORT) return Number(process.env.VOLTRO_DASHBOARD_PORT);
402
+ if (process.env.PORT) {
403
+ let e = Number(process.env.PORT);
404
+ if (Number.isFinite(e) && e > 0) return e;
405
+ }
406
+ return p.port ?? 5173;
407
+ })(), A = N(o, ".framework"), j = N(A, "dist"), M = N(j, "index.html");
408
+ if (!await X(M)) return K.error("no built dist found — run `voltro build` first", { distDir: j }), {
409
+ ran: !0,
410
+ exitCode: 1
411
+ };
412
+ let P = p.name ?? "unnamed", F = ie("web", P, D), I = c(P);
413
+ await n({
414
+ id: F,
415
+ name: P,
416
+ kind: "web",
417
+ url: `http://localhost:${D}`,
418
+ pid: process.pid,
419
+ status: "booting",
420
+ logFile: I.path
421
+ }).catch(() => {});
422
+ let L = N(j, "_voltro", "shell.html"), z = await X(L) ? await R.readFile(L, "utf8") : await R.readFile(M, "utf8"), B = await O(o, p), V = te(B[0]), H, U = null;
423
+ process.env.SSR_CACHE === "postgres" && process.env.PG_HOST ? (U = new (await (import("pg"))).default.Client({
424
+ host: process.env.PG_HOST,
425
+ port: Number(process.env.PG_PORT ?? 5432),
426
+ user: process.env.PG_USER ?? "app",
427
+ password: process.env.PG_PASSWORD ?? "app",
428
+ database: process.env.PG_DATABASE ?? "app"
429
+ }), await U.connect(), H = de({ client: U }), K.info("isr cache backend: postgres", { table: "voltro_isr_cache" })) : (H = x(), K.info("isr cache backend: memory (per-process)", { hint: "set SSR_CACHE=postgres for persistent storage" })), await g(o, p, B, b);
430
+ let { pages: W, dirs: G } = await h(N(o, "src", "pages")), q = ge(W), J = N(j, "server", "ssrEntry.js"), pe = await X(J);
431
+ if (!pe && process.env.NODE_ENV === "production") throw K.fatal(`production \`voltro start\` requires a precompiled SSR bundle at ${J} but it is missing. Run \`voltro build\` before serving — production renders from the bundle and never compiles on demand.`), Error(`[voltro start] missing SSR bundle at ${J}`);
432
+ let Y = pe ? await ye(J) : await be(o, A);
433
+ K.info(`ssr loader: ${Y.kind}`, {
434
+ kind: Y.kind,
435
+ ...Y.kind === "middleware" ? { hint: "no pre-built SSR bundle found; run `voltro build` to enable bundle mode" } : {}
436
+ });
437
+ let he = Y.getPageMeta?.(), Z = /* @__PURE__ */ new Map(), Q = 0;
438
+ for (let e of W) {
439
+ let t = he?.[e.pattern];
440
+ if (t) {
441
+ Z.set(e.pattern, t);
442
+ continue;
443
+ }
444
+ try {
445
+ let t = await Y.getPage(e.pattern, e.file);
446
+ Z.set(e.pattern, le(t)), Q++;
447
+ } catch (t) {
448
+ K.warn("failed to load page metadata", { pattern: e.pattern }, t);
449
+ }
450
+ }
451
+ K.info(`registered ${Z.size} route(s)`, {
452
+ count: Z.size,
453
+ fromManifest: Z.size - Q,
454
+ fromModules: Q
455
+ });
456
+ let xe = /* @__PURE__ */ new Map();
457
+ for (let [e, t] of Z) t.cacheInvalidatesOn.length > 0 && xe.set(e, t.cacheInvalidatesOn);
458
+ let Se = /* @__PURE__ */ new Map();
459
+ for (let e of q) Se.set(e.pattern, e.regex);
460
+ let Ce = ue({
461
+ cache: H,
462
+ tableSubscriptionsByPattern: xe,
463
+ regexByPattern: Se
464
+ });
465
+ await Ce.start();
466
+ let we = k(), Te = W.map((e) => {
467
+ let t = Z.get(e.pattern);
468
+ return {
469
+ pattern: e.pattern,
470
+ file: e.file,
471
+ dirChain: e.dirChain,
472
+ renderMode: t?.renderMode ?? "static",
473
+ interactive: t?.interactive ?? "full",
474
+ tenantAware: t?.tenantAware ?? !1,
475
+ revalidateMs: t?.revalidateMs ?? null,
476
+ staleWhileRevalidateMs: t?.staleWhileRevalidateMs ?? 0,
477
+ cacheInvalidatesOn: t?.cacheInvalidatesOn ?? [],
478
+ hasLoader: t?.hasLoader ?? !1,
479
+ hasMeta: t?.hasMeta ?? !1,
480
+ hasGetStaticPaths: t?.hasGetStaticPaths ?? !1
481
+ };
482
+ }), Ee = G.map((e) => ({
483
+ dir: e.dir,
484
+ hasLayout: e.hasLayout,
485
+ hasError: e.hasError,
486
+ hasPending: e.hasPending,
487
+ hasNotFound: e.hasNotFound
488
+ })), De = U ? "postgres" : "memory", Oe = {
489
+ kind: "web",
490
+ meta: {
491
+ name: p.name ?? "(unnamed)",
492
+ voltroVersion: fe,
493
+ port: D,
494
+ mode: "start",
495
+ apis: Object.entries(p.apis ?? {}).map(([e, t]) => {
496
+ let n = {
497
+ name: e,
498
+ package: t.package
499
+ };
500
+ return t.url !== void 0 && (n.url = t.url), n;
501
+ })
502
+ },
503
+ routes: Te,
504
+ dirs: Ee,
505
+ cache: () => {
506
+ let e = Date.now(), t = H.snapshot().map(([t, n]) => ({
507
+ key: t,
508
+ renderedAt: n.renderedAt,
509
+ expiresAt: n.expiresAt,
510
+ servableUntil: n.servableUntil,
511
+ ageMs: e - n.renderedAt,
512
+ htmlBytes: Buffer.byteLength(n.html, "utf8"),
513
+ revalidateMs: n.revalidateMs,
514
+ staleWhileRevalidateMs: n.staleWhileRevalidateMs
515
+ }));
516
+ return {
517
+ backend: De,
518
+ size: H.size(),
519
+ entries: t
520
+ };
521
+ },
522
+ metrics: () => se()
523
+ }, ke = async (t, n) => {
524
+ let r = t.split("?")[0] ?? "/";
525
+ if (p.name === "voltroDashboard" && r === "/api/dashboard/registry") try {
526
+ let e = await l();
527
+ return {
528
+ status: 200,
529
+ headers: { "content-type": "application/json; charset=utf-8" },
530
+ body: JSON.stringify(e)
531
+ };
532
+ } catch (e) {
533
+ return {
534
+ status: 500,
535
+ headers: { "content-type": "text/plain" },
536
+ body: `registry read error: ${e.message}`
537
+ };
538
+ }
539
+ if (p.name === "voltroDashboard" && r === "/api/dashboard/config") {
540
+ let e = process.env.VOLTRO_DASHBOARD_APPS, t = [];
541
+ if (e && e.trim().length > 0) {
542
+ let n;
543
+ try {
544
+ n = JSON.parse(e);
545
+ } catch (e) {
546
+ return {
547
+ status: 500,
548
+ headers: { "content-type": "text/plain" },
549
+ body: `VOLTRO_DASHBOARD_APPS is not valid JSON: ${e.message}`
550
+ };
551
+ }
552
+ for (let e of Array.isArray(n) ? n : []) {
553
+ if (typeof e != "object" || !e) continue;
554
+ let n = e;
555
+ typeof n.url != "string" || n.url.length === 0 || t.push({
556
+ url: n.url,
557
+ ...typeof n.name == "string" ? { name: n.name } : {},
558
+ ...typeof n.token == "string" ? { token: n.token } : {}
559
+ });
560
+ }
561
+ }
562
+ return {
563
+ status: 200,
564
+ headers: { "content-type": "application/json; charset=utf-8" },
565
+ body: JSON.stringify({ apps: t })
566
+ };
567
+ }
568
+ if (r === "/_voltro/inspect/logs") {
569
+ let e = a(t, d());
570
+ return {
571
+ status: e.status,
572
+ headers: { "content-type": e.contentType },
573
+ body: e.body
574
+ };
575
+ }
576
+ if (r !== "/_voltro/inspect/stream") {
577
+ let e = ne(t, Oe, n);
578
+ if (e !== null) return {
579
+ status: e.status,
580
+ headers: {
581
+ "content-type": e.contentType,
582
+ ...e.headers ?? {}
583
+ },
584
+ body: e.body
585
+ };
586
+ }
587
+ let i = _e(r, q), c = i ? Z.get(i.route.pattern)?.renderMode : void 0, u = c === "ssr" || c === "isr", f = r === "/" ? N(j, "index.html") : N(j, r.replace(/^\//, ""), "index.html");
588
+ if (!u && await X(f)) return {
589
+ status: 200,
590
+ headers: { "content-type": "text/html; charset=utf-8" },
591
+ body: await R.readFile(f)
592
+ };
593
+ if (/\.[a-z0-9]+$/i.test(r)) {
594
+ let e = N(j, r.replace(/^\//, ""));
595
+ if (await X(e)) {
596
+ let t = await R.readFile(e);
597
+ return {
598
+ status: 200,
599
+ headers: { "content-type": me(e) },
600
+ body: t
601
+ };
602
+ }
603
+ }
604
+ let m = _e(r, q);
605
+ if (!m) return {
606
+ status: 404,
607
+ headers: { "content-type": "text/plain" },
608
+ body: "not found"
609
+ };
610
+ let h = Z.get(m.route.pattern), g = n["x-tenant"] ?? "anonymous", _ = (await Y.getHelpers()).i18n?.resolve(n.cookie, n["accept-language"])?.locale, v = s(r, {
611
+ ...h?.tenantAware ? { tenant: g } : {},
612
+ ..._ ? { locale: _ } : {}
613
+ }), b = H.get(v), x = Date.now();
614
+ if (b && S(b, x)) return w.emit("cache", {
615
+ kind: "HIT",
616
+ key: v,
617
+ ageMs: x - b.renderedAt
618
+ }), {
619
+ status: 200,
620
+ headers: {
621
+ "content-type": "text/html; charset=utf-8",
622
+ "x-voltro-rendered-by": "isr",
623
+ "x-voltro-cache": "HIT",
624
+ "x-voltro-cache-key": v,
625
+ "x-voltro-cache-age-ms": String(x - b.renderedAt)
626
+ },
627
+ body: b.html
628
+ };
629
+ if (b && e(b, x)) return w.emit("cache", {
630
+ kind: "STALE",
631
+ key: v,
632
+ ageMs: x - b.renderedAt
633
+ }), (async () => {
634
+ try {
635
+ let e = await ve({
636
+ provider: Y,
637
+ root: o,
638
+ route: m.route,
639
+ params: m.params,
640
+ pathname: r,
641
+ dirs: G,
642
+ shellTemplate: z,
643
+ requestHeaders: n,
644
+ apiOrigin: V,
645
+ requestUrl: t,
646
+ buffered: !0
647
+ });
648
+ if (e && "html" in e && e.renderMode === "isr" && e.revalidateMs !== null) {
649
+ let t = Z.get(m.route.pattern)?.staleWhileRevalidateMs ?? 0, n = Date.now();
650
+ H.set(v, {
651
+ html: e.html,
652
+ renderedAt: n,
653
+ expiresAt: n + e.revalidateMs,
654
+ servableUntil: n + e.revalidateMs + t,
655
+ revalidateMs: e.revalidateMs,
656
+ staleWhileRevalidateMs: t
657
+ });
658
+ }
659
+ } catch (e) {
660
+ K.warn("background SWR refresh failed", { url: r }, e);
661
+ }
662
+ })(), {
663
+ status: 200,
664
+ headers: {
665
+ "content-type": "text/html; charset=utf-8",
666
+ "x-voltro-rendered-by": "isr",
667
+ "x-voltro-cache": "STALE",
668
+ "x-voltro-cache-key": v,
669
+ "x-voltro-cache-age-ms": String(x - b.renderedAt)
670
+ },
671
+ body: b.html
672
+ };
673
+ try {
674
+ let e = await ve({
675
+ provider: Y,
676
+ root: o,
677
+ route: m.route,
678
+ params: m.params,
679
+ pathname: r,
680
+ dirs: G,
681
+ shellTemplate: z,
682
+ requestHeaders: n,
683
+ apiOrigin: V,
684
+ requestUrl: t
685
+ });
686
+ if (!e) return {
687
+ status: 200,
688
+ headers: {
689
+ "content-type": "text/html; charset=utf-8",
690
+ "x-voltro-rendered-by": "spa-fallback"
691
+ },
692
+ body: z
693
+ };
694
+ if ("stream" in e) return {
695
+ status: 200,
696
+ headers: { "x-voltro-rendered-by": "ssr" },
697
+ body: "",
698
+ stream: e.stream
699
+ };
700
+ if ("control" in e) return e.control === "redirect" ? {
701
+ status: e.status,
702
+ headers: {
703
+ location: e.location,
704
+ "x-voltro-rendered-by": "loader-redirect"
705
+ },
706
+ body: ""
707
+ } : {
708
+ status: 404,
709
+ headers: {
710
+ "content-type": "text/plain; charset=utf-8",
711
+ "x-voltro-rendered-by": "loader-not-found"
712
+ },
713
+ body: "not found"
714
+ };
715
+ let i = {
716
+ "content-type": "text/html; charset=utf-8",
717
+ "x-voltro-rendered-by": e.renderMode
718
+ };
719
+ if (e.renderMode === "isr" && e.revalidateMs !== null) {
720
+ let t = Date.now(), n = Z.get(m.route.pattern)?.staleWhileRevalidateMs ?? 0;
721
+ H.set(v, {
722
+ html: e.html,
723
+ renderedAt: t,
724
+ expiresAt: t + e.revalidateMs,
725
+ servableUntil: t + e.revalidateMs + n,
726
+ revalidateMs: e.revalidateMs,
727
+ staleWhileRevalidateMs: n
728
+ }), i["x-voltro-cache"] = "MISS", i["x-voltro-cache-key"] = v, i["x-voltro-cache-revalidate-ms"] = String(e.revalidateMs), n > 0 && (i["x-voltro-cache-swr-ms"] = String(n)), w.emit("cache", {
729
+ kind: "MISS",
730
+ key: v,
731
+ revalidateMs: e.revalidateMs,
732
+ swrMs: n
733
+ });
734
+ } else e.renderMode === "isr" && (i["x-voltro-cache"] = "BYPASS", w.emit("cache", {
735
+ kind: "BYPASS",
736
+ key: v
737
+ }));
738
+ return {
739
+ status: 200,
740
+ headers: i,
741
+ body: e.html
742
+ };
743
+ } catch (e) {
744
+ return K.error("render error", { url: r }, e), y(e) ? {
745
+ status: 500,
746
+ headers: {
747
+ "content-type": "text/plain; charset=utf-8",
748
+ "x-voltro-rendered-by": "defer-unsupported"
749
+ },
750
+ body: e.message
751
+ } : {
752
+ status: 500,
753
+ headers: { "content-type": "text/plain" },
754
+ body: "server error"
755
+ };
756
+ }
757
+ }, Ae = !1, je = ce(async (e, t) => {
758
+ let n = e.url ?? "/", r = Date.now(), a = {};
759
+ for (let [t, n] of Object.entries(e.headers)) n !== void 0 && (a[t.toLowerCase()] = Array.isArray(n) ? n.join(", ") : n);
760
+ let o = n.split("?")[0] ?? "/", s = (e.method ?? "GET").toUpperCase();
761
+ if (o === "/internal/liveness") {
762
+ t.writeHead(200, {
763
+ "content-type": "text/plain",
764
+ "cache-control": "no-store"
765
+ }), t.end("ok");
766
+ return;
767
+ }
768
+ if (o === "/internal/readiness") {
769
+ let e = Ae ? 200 : 503;
770
+ t.writeHead(e, {
771
+ "content-type": "text/plain",
772
+ "cache-control": "no-store"
773
+ }), t.end(Ae ? "ready" : "not-ready");
774
+ return;
775
+ }
776
+ if (s === "OPTIONS" && o.startsWith("/_voltro/inspect")) {
777
+ let e = _(a.origin);
778
+ t.writeHead(204, e), t.end();
779
+ return;
780
+ }
781
+ if (p.name === "voltroDashboard" && o === "/api/dashboard/proxy") {
782
+ let r = new URL(n, "http://x").searchParams.get("target");
783
+ if (!r) {
784
+ t.writeHead(400, { "content-type": "text/plain" }), t.end("missing ?target=");
785
+ return;
786
+ }
787
+ try {
788
+ let n = { accept: a.accept ?? "application/json" }, i = a.authorization;
789
+ i && (n.authorization = Array.isArray(i) ? i[0] : i);
790
+ let o;
791
+ if (s && s !== "GET" && s !== "HEAD") {
792
+ let t = [];
793
+ for await (let n of e) t.push(n);
794
+ o = Buffer.concat(t);
795
+ let r = a["content-type"];
796
+ r && (n["content-type"] = Array.isArray(r) ? r[0] : r);
797
+ }
798
+ let c = await fetch(r, {
799
+ method: s,
800
+ headers: n,
801
+ ...o ? { body: new Uint8Array(o.buffer, o.byteOffset, o.byteLength) } : {}
802
+ }), l = Buffer.from(await c.arrayBuffer());
803
+ t.writeHead(c.status, {
804
+ "content-type": c.headers.get("content-type") ?? "application/octet-stream",
805
+ "content-length": String(l.length)
806
+ }), t.end(l);
807
+ } catch (e) {
808
+ t.writeHead(502, { "content-type": "text/plain" }), t.end(`proxy error: ${e.message}`);
809
+ }
810
+ return;
811
+ }
812
+ if (p.name === "voltroDashboard" && o === "/api/dashboard/proxy/stream") {
813
+ let r = new URL(n, "http://x").searchParams.get("target");
814
+ if (!r) {
815
+ t.writeHead(400, { "content-type": "text/plain" }), t.end("missing ?target=");
816
+ return;
817
+ }
818
+ let i = new AbortController();
819
+ e.on("close", () => i.abort()), t.on("close", () => i.abort());
820
+ try {
821
+ let e = a.authorization, n = await fetch(r, {
822
+ method: "GET",
823
+ headers: {
824
+ accept: "text/event-stream",
825
+ ...e ? { authorization: Array.isArray(e) ? e[0] : e } : {}
826
+ },
827
+ signal: i.signal
828
+ });
829
+ if (!n.ok || !n.body) {
830
+ t.writeHead(n.status, { "content-type": "text/plain" }), t.end(`upstream ${n.status}`);
831
+ return;
832
+ }
833
+ t.writeHead(200, {
834
+ "content-type": "text/event-stream; charset=utf-8",
835
+ "cache-control": "no-cache, no-transform",
836
+ connection: "keep-alive",
837
+ "x-accel-buffering": "no"
838
+ }), t.flushHeaders();
839
+ let o = n.body.getReader();
840
+ try {
841
+ for (;;) {
842
+ let { done: e, value: n } = await o.read();
843
+ if (e) break;
844
+ n && t.write(Buffer.from(n));
845
+ }
846
+ } finally {
847
+ o.releaseLock(), t.end();
848
+ }
849
+ } catch (e) {
850
+ e.name !== "AbortError" && (t.writeHead(502, { "content-type": "text/plain" }), t.end(`proxy stream error: ${e.message}`));
851
+ }
852
+ return;
853
+ }
854
+ if (o === "/_voltro/inspect/stream") {
855
+ let n = _(a.origin);
856
+ if (T()) {
857
+ t.writeHead(503, {
858
+ "content-type": "application/json",
859
+ ...n
860
+ }), t.end(JSON.stringify({ error: "inspect disabled (VOLTRO_INSPECT=off)" }));
861
+ return;
862
+ }
863
+ let r = E(a);
864
+ if (!r.ok) {
865
+ t.writeHead(401, {
866
+ "content-type": "application/json",
867
+ ...n
868
+ }), t.end(JSON.stringify({
869
+ error: "unauthorized",
870
+ reason: r.reason
871
+ }));
872
+ return;
873
+ }
874
+ t.writeHead(200, {
875
+ "content-type": "text/event-stream; charset=utf-8",
876
+ "cache-control": "no-cache, no-transform",
877
+ connection: "keep-alive",
878
+ "x-accel-buffering": "no",
879
+ ...n
880
+ }), t.flushHeaders(), t.write("retry: 5000\n\n");
881
+ for (let e of w.buffered()) t.write(i(e));
882
+ let o = w.subscribe((e) => {
883
+ t.write(i(e));
884
+ }), s = setInterval(() => {
885
+ t.write(f());
886
+ }, 15e3), c = () => {
887
+ clearInterval(s), o();
888
+ };
889
+ e.on("close", c), t.on("close", c);
890
+ return;
891
+ }
892
+ try {
893
+ let e = await ke(n, a);
894
+ if (e.stream ? e.stream(t) : (t.writeHead(e.status, e.headers), t.end(e.body)), !o.startsWith("/_voltro/inspect")) {
895
+ let t = Date.now() - r;
896
+ w.emit("request", {
897
+ url: o,
898
+ status: e.status,
899
+ durationMs: t,
900
+ renderedBy: e.headers["x-voltro-rendered-by"],
901
+ cache: e.headers["x-voltro-cache"]
902
+ });
903
+ let n = _e(o, q)?.route.pattern ?? (e.status === 404 ? "<unmatched>" : "<asset>");
904
+ we.record({
905
+ kind: "request",
906
+ tag: n,
907
+ durationMs: t,
908
+ status: e.status >= 500 ? "error" : "ok"
909
+ });
910
+ }
911
+ } catch (e) {
912
+ t.writeHead(500, { "content-type": "text/plain" }), t.end(`internal error: ${e.message}`);
913
+ }
914
+ });
915
+ await new Promise((e) => je.listen(D, "::", e)), Ae = !0, K.info(`listening on http://localhost:${D}`, {
916
+ port: D,
917
+ distDir: j
918
+ }), K.debug("dynamic + ssr/isr routes resolve via SSR loader");
919
+ try {
920
+ await n({
921
+ id: F,
922
+ name: P,
923
+ kind: "web",
924
+ url: `http://localhost:${D}`,
925
+ pid: process.pid,
926
+ status: "live",
927
+ logFile: I.path
928
+ });
929
+ let e = m(o);
930
+ if (e) {
931
+ let t = await ae({ dashboardAppPath: e });
932
+ t.status === "launched" ? K.info(`dashboard launched at http://localhost:${t.port}`, { port: t.port }) : t.status === "already-running" && K.debug(`dashboard already running on :${t.port}`, { port: t.port });
933
+ }
934
+ setInterval(() => {
935
+ n({
936
+ id: F,
937
+ name: P,
938
+ kind: "web",
939
+ url: `http://localhost:${D}`,
940
+ pid: process.pid,
941
+ status: "live",
942
+ logFile: I.path
943
+ }).catch(() => {});
944
+ }, 1e4).unref();
945
+ } catch (e) {
946
+ K.warn("runtime-registry write failed", {}, e);
947
+ }
948
+ oe({
949
+ app: p.name ?? "unnamed",
950
+ kind: "web",
951
+ url: `http://localhost:${D}`,
952
+ bootMs: Date.now() - ee
953
+ });
954
+ let Me = !1, $ = (e) => async () => {
955
+ if (!Me) {
956
+ if (Me = !0, K.info(`received ${e}, shutting down`, { signal: e }), je.close(), await Ce.stop(), U) try {
957
+ await U.end();
958
+ } catch {}
959
+ await Y.close(), await r(F), await I.dispose(), process.exit(0);
960
+ }
961
+ };
962
+ process.on("SIGINT", $("SIGINT")), process.on("SIGTERM", $("SIGTERM"));
963
+ let Ne = process.ppid;
964
+ if (Ne > 1) {
965
+ let e = setInterval(() => {
966
+ (process.ppid !== Ne || process.ppid === 1) && (clearInterval(e), $("SIGTERM")());
967
+ }, 2e3);
968
+ e.unref();
969
+ }
970
+ return new Promise(() => {});
971
+ }, xe = async (e) => {
972
+ process.env.NODE_ENV || (process.env.NODE_ENV = "production");
973
+ let t = await Q(P(e.find((e) => !e.startsWith("-")) ?? process.cwd()));
974
+ return t.ran ? t.exitCode : (K.error("no web app found at this path"), 1);
975
+ };
976
+ //#endregion
977
+ export { le as n, xe as t };