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