@voltro/cli 0.55.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +691 -0
- package/bin/voltro.mjs +24 -0
- package/dist/apiBuild-B83Cb2Rv.js +2 -0
- package/dist/{apiBuild-CMvLJM_K.js → apiBuild-DDr2aNFd.js} +119 -88
- package/dist/bin.js +1 -1
- package/dist/{build-S0QOzqPT.js → build-QKP6Bm0J.js} +308 -279
- package/dist/buildReport-52gHKgfO.js +64 -0
- package/dist/{checkCommand-DNkY5kwF.js → checkCommand-BISqx1OJ.js} +1 -1
- package/dist/{checkCommand-fbj9GDjN.js → checkCommand-DUtMWjcR.js} +6 -6
- package/dist/{clusterCmd-DrVFCzSj.js → clusterCmd-CdLB1GkT.js} +1 -1
- package/dist/{codegen-SIepQtUl.js → codegen-Bth5lUTU.js} +2 -1
- package/dist/codegen-DbH7NbCR.js +2 -0
- package/dist/{codegenCommand-3TDJezom.js → codegenCommand-DnVuDxwT.js} +11 -11
- package/dist/{codemodRunner-C2zxZUIw.js → codemodRunner-BlQPfjzA.js} +222 -0
- package/dist/{commands-BBYJ7Q3B.js → commands-CRbxgxv0.js} +35 -35
- package/dist/{dashboardCommand-D2kmyCLL.js → dashboardCommand-Bf_-Ne3P.js} +5 -5
- package/dist/{dataCommand-BEPPQiTl.js → dataCommand-BoBJJ-Gb.js} +3 -3
- package/dist/{dbCommand-DZTmOFT4.js → dbCommand-CMAIz-Bf.js} +457 -441
- package/dist/dbCommand-DHi_RuDl.js +2 -0
- package/dist/dev-Bl9HqtV7.js +3 -0
- package/dist/{dev-Ca_A_S9v.js → dev-DOEJXicj.js} +2623 -2485
- package/dist/{doctorCommand-CGZJK_4o.js → doctorCommand-BrWu67JZ.js} +524 -280
- package/dist/doctorCommand-DrQv9SL3.js +2 -0
- package/dist/{dormancyCommand-DY2rYpTa.js → dormancyCommand-xn2y-pJm.js} +1 -1
- package/dist/{embeddingsCommand-BoCqZsgp.js → embeddingsCommand-Cn5MbRDM.js} +1 -1
- package/dist/emptyResultHeadline-Csa5fZOF.js +18 -0
- package/dist/{envCommand-Bxy2fOjc.js → envCommand-UJmJIbs9.js} +8 -8
- package/dist/{evolveCommand-BsbZ-XDg.js → evolveCommand-Db30twUy.js} +2 -2
- package/dist/frameworkTableAssembly-B96WCNJA.js +2 -0
- package/dist/{frameworkTableAssembly-Do-cf6RJ.js → frameworkTableAssembly-vfkzuzEo.js} +131 -104
- package/dist/index.js +1 -1
- package/dist/{infoCommand-EmM3jPKD.js → infoCommand-BjVXpMlP.js} +1 -1
- package/dist/inspect-B0hL41s0.js +2 -0
- package/dist/inspect-DZnan87F.js +1500 -0
- package/dist/{inspectCmd-niF97fAq.js → inspectCmd-CP-G0sVK.js} +1 -1
- package/dist/{inspectFetch-EMuhTG_9.js → inspectFetch-BU1NyzxV.js} +36 -24
- package/dist/{inspectMetrics-CGF94puw.js → inspectMetrics-BY0Sjb2F.js} +19 -19
- package/dist/{logsCmd-B6oNsfaZ.js → logsCmd-C5y9HyrG.js} +59 -54
- package/dist/{manifestBuild-DjX5MoXy.js → manifestBuild-DnbFKF6w.js} +1 -1
- package/dist/manifestBuild-UXrnUcXP.js +2 -0
- package/dist/{migrate-CGFZS-1a.js → migrate-DtC3lu7H.js} +4 -4
- package/dist/precompressAssets-YhTi1aWp.js +40 -0
- package/dist/{probeCommand-Bs3iVBSL.js → probeCommand-C5fuN6Z2.js} +2 -2
- package/dist/{runtimeTrace-C1BTpHGQ.js → runtimeTrace-BQL_lfz6.js} +1 -1
- package/dist/{scheduleCmd-DQRu6BZC.js → scheduleCmd-DYBUfo_T.js} +1 -1
- package/dist/{sdkgen-CXMwLg9n.js → sdkgen-CMUPrDjH.js} +1 -1
- package/dist/{seedRunner-Dgsiwk_e.js → seedRunner-DISBKow-.js} +16 -16
- package/dist/serveCommand-B_w-Mpb_.js +2544 -0
- package/dist/serveCommand-Dtb48ffg.js +2 -0
- package/dist/serveEntry.js +1 -1
- package/dist/{start-EOV7s1NZ.js → start-YaUehtDV.js} +580 -558
- package/dist/{start-DH7cat4-.js → start-s25GAIgn.js} +1 -1
- package/dist/startEntry.js +1 -1
- package/dist/staticCachePolicy-CIyj6DbS.js +15 -0
- package/dist/{test-DO27-x2P.js → test-jipIQ5Mx.js} +1 -1
- package/dist/{tracesCmd-DgtgOUdi.js → tracesCmd-C1qKa94g.js} +69 -64
- package/dist/{updateCommand-C_jN1w18.js → updateCommand-C9n_Z_oG.js} +8 -2
- package/dist/updateCommand-DsXEAHbd.js +2 -0
- package/dist/webDev-B6ZMX42w.js +2 -0
- package/dist/{webDev-B7vNj4Bq.js → webDev-BgdkyjP6.js} +1379 -1303
- package/dist/{webhooksCommand-B1LVcyO3.js → webhooksCommand-CYXTNvXq.js} +2 -2
- package/dist/{workflowsCmd-BGF-mRZ5.js → workflowsCmd-g-DNpaUc.js} +1 -1
- package/package.json +55 -19
- package/templates/AGENTS.core.md +20 -1
- package/templates/AGENTS.md +21 -2
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/_manifest.json +2 -2
- package/templates/agent-docs/ai.md +1 -1
- package/templates/agent-docs/authentication.md +34 -19
- package/templates/agent-docs/cli.md +170 -0
- package/templates/agent-docs/configuration.md +29 -4
- package/templates/agent-docs/data.md +145 -5
- package/templates/agent-docs/database/scaling.md +50 -2
- package/templates/agent-docs/deployment.md +75 -1
- package/templates/agent-docs/internationalization.md +32 -0
- package/templates/agent-docs/local-first-mobile.md +9 -2
- package/templates/agent-docs/observability.md +227 -0
- package/templates/agent-docs/plugins/audit.md +21 -5
- package/templates/agent-docs/plugins/billing.md +17 -0
- package/templates/agent-docs/plugins/broadcast.md +2 -1
- package/templates/agent-docs/plugins/ratelimit.md +6 -1
- package/templates/agent-docs/plugins/row-history.md +11 -0
- package/templates/agent-docs/plugins.md +19 -0
- package/templates/agent-docs/reference.md +1 -0
- package/templates/agent-docs/scheduling.md +23 -0
- package/templates/agent-docs/schema-driven-ui.md +81 -0
- package/templates/agent-docs/templates/appshells.md +3 -3
- package/templates/agent-docs/whats-new.md +133 -77
- package/templates/apps/api-ai/package.json +6 -6
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +7 -7
- package/templates/apps/changelog/src/pages/[locale]/page.tsx +7 -1
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +7 -8
- package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +1 -2
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +1 -2
- package/templates/apps/frontend-app/package.json +8 -9
- package/templates/apps/frontend-app/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-auth/package.json +7 -8
- package/templates/apps/frontend-auth/src/components/AuthShell.tsx +1 -2
- package/templates/apps/frontend-blank/package.json +6 -7
- package/templates/apps/frontend-blank/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-cms/package.json +8 -9
- package/templates/apps/frontend-cms/src/pages/(app)/layout.tsx +1 -2
- package/templates/apps/frontend-collab/README.md +7 -2
- package/templates/apps/frontend-collab/package.json +9 -10
- package/templates/apps/frontend-collab/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +8 -4
- package/templates/apps/frontend-collab/src/pages/page.tsx +10 -5
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +1 -1
- package/templates/apps/frontend-dashboard/package.json +6 -7
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +1 -2
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +1 -2
- package/templates/apps/frontend-docs/package.json +8 -8
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +7 -8
- package/templates/apps/frontend-portal/src/pages/(portal)/layout.tsx +1 -2
- package/templates/apps/frontend-saas/package.json +7 -8
- package/templates/apps/frontend-saas/src/pages/(marketing)/layout.tsx +1 -2
- package/templates/apps/frontend-saas/src/pages/dashboard/layout.tsx +1 -2
- package/templates/apps/frontend-spa/package.json +6 -7
- package/templates/apps/frontend-spa/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-ssr/package.json +6 -7
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-ssr-api/package.json +7 -8
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-static-blog/package.json +8 -8
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +1 -1
- package/templates/apps/frontend-static-blog/src/pages/[locale]/page.tsx +7 -1
- package/templates/apps/frontend-status/package.json +7 -8
- package/templates/apps/frontend-status/src/pages/layout.tsx +1 -2
- package/templates/apps/mobile-app/package.json +4 -4
- package/templates/baselines/compose/docker/api.Dockerfile +61 -5
- package/templates/baselines/compose/docker/web.Dockerfile +55 -10
- package/templates/baselines/compose-mariadb/docker/api.Dockerfile +61 -5
- package/templates/baselines/compose-mariadb/docker/web.Dockerfile +55 -10
- package/dist/apiBuild-Cl0IDx8c.js +0 -2
- package/dist/codegen-CN6vMM4J.js +0 -2
- package/dist/dbCommand-BTyBGhIA.js +0 -2
- package/dist/dev-DfVZaoys.js +0 -3
- package/dist/doctorCommand-djmqEcDC.js +0 -2
- package/dist/frameworkTableAssembly-Df2Ymp2f.js +0 -2
- package/dist/inspect-DCqILJ1G.js +0 -1197
- package/dist/inspect-DGJwpOAb.js +0 -2
- package/dist/manifestBuild-CJ2zvPvT.js +0 -2
- package/dist/serveCommand-C7IrCD58.js +0 -2445
- package/dist/serveCommand-Cjt5S9hD.js +0 -2
- package/dist/updateCommand-nnFjDbl4.js +0 -2
- package/dist/webDev-1XpVnYkW.js +0 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { Jt as e, Xt as t, Yt as n } from "./dev-DOEJXicj.js";
|
|
2
|
+
import { Bt as r, Lt as i, R as a, Rt as o, S as s, t as c, u as l, zt as u } from "./webDev-BgdkyjP6.js";
|
|
3
|
+
import { r as d, u as f } from "./apiBuild-DDr2aNFd.js";
|
|
4
|
+
import { S as p } from "./frameworkTableAssembly-vfkzuzEo.js";
|
|
5
|
+
import { n as m } from "./pageConvention-CMpfDN6r.js";
|
|
6
6
|
import { n as h, r as g, t as ee } from "./cliOutput-D1tSBoRM.js";
|
|
7
7
|
import { n as _, t as te } from "./tsconfigPaths-BWXBWgcl.js";
|
|
8
8
|
import { n as ne, r as re } from "./fileTaxonomy-CbyMQYx_.js";
|
|
@@ -367,15 +367,15 @@ var le = /* @__PURE__ */ new Set([
|
|
|
367
367
|
" reaches your deploy target is not knowable from here — a secret manager",
|
|
368
368
|
" or a CI variable is not a file. Naming it nowhere is conclusive; naming",
|
|
369
369
|
" it somewhere is not a guarantee."
|
|
370
|
-
].join("\n"),
|
|
370
|
+
].join("\n"), Pe = {
|
|
371
371
|
"@voltro/cli": "provides the `voltro` binary; consumed by scripts, never imported",
|
|
372
372
|
"@voltro/devtools": "mounted by `voltro dev` itself",
|
|
373
373
|
"@voltro/sql-postgres": "dialect driver, loaded by the framework from config",
|
|
374
374
|
"@voltro/sql-mysql": "dialect driver, loaded by the framework from config",
|
|
375
375
|
"@voltro/sql-sqlite": "dialect driver, loaded by the framework from config",
|
|
376
376
|
"@voltro/sql-mssql": "dialect driver, loaded by the framework from config"
|
|
377
|
-
},
|
|
378
|
-
if (n > 12 ||
|
|
377
|
+
}, Fe = /* @__PURE__ */ new Set(["@voltro/client", "@voltro/web"]), Ie = /(^|\/)(node_modules|dist|build|\.framework|\.next|\.turbo|\.git|coverage|\.publish)(\/|$)/, Le = /\.([cm]?[jt]sx?)$/, Re = /\.generated\.[cm]?[jt]sx?$/, ze = (e, t = [], n = 0) => {
|
|
378
|
+
if (n > 12 || Ie.test(e)) return t;
|
|
379
379
|
let r;
|
|
380
380
|
try {
|
|
381
381
|
r = w(e);
|
|
@@ -389,10 +389,10 @@ var le = /* @__PURE__ */ new Set([
|
|
|
389
389
|
} catch {
|
|
390
390
|
continue;
|
|
391
391
|
}
|
|
392
|
-
a.isDirectory() ?
|
|
392
|
+
a.isDirectory() ? ze(r, t, n + 1) : Le.test(i) && t.push(r);
|
|
393
393
|
}
|
|
394
394
|
return t;
|
|
395
|
-
},
|
|
395
|
+
}, Be = /(?:\bfrom\s*|\bimport\s+|\bimport\s*\(\s*|\brequire\s*\(\s*)['"](@voltro\/[a-z0-9-]+)(?:\/[^'"]*)?['"]/g, Ve = (e) => e.split("\n").filter((e) => !/^\s*(\/\/|\*|\/\*)/.test(e)).join("\n"), He = (e) => {
|
|
396
396
|
let t;
|
|
397
397
|
try {
|
|
398
398
|
t = JSON.parse(C(v(e, "package.json"), "utf8"));
|
|
@@ -402,7 +402,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
402
402
|
let n = (e) => Object.keys(t[e] ?? {}).filter((e) => e.startsWith("@voltro/")).map((t) => ({
|
|
403
403
|
pkg: t,
|
|
404
404
|
declaredIn: e
|
|
405
|
-
})), r = /* @__PURE__ */ new Set(), i = [...n("dependencies"), ...n("devDependencies")].filter((e) => !r.has(e.pkg) && (r.add(e.pkg), !0)), a =
|
|
405
|
+
})), r = /* @__PURE__ */ new Set(), i = [...n("dependencies"), ...n("devDependencies")].filter((e) => !r.has(e.pkg) && (r.add(e.pkg), !0)), a = ze(e), o = a.some((e) => Re.test(e)), s = /* @__PURE__ */ new Set();
|
|
406
406
|
for (let e of a) {
|
|
407
407
|
let t;
|
|
408
408
|
try {
|
|
@@ -410,17 +410,17 @@ var le = /* @__PURE__ */ new Set([
|
|
|
410
410
|
} catch {
|
|
411
411
|
continue;
|
|
412
412
|
}
|
|
413
|
-
for (let e of
|
|
413
|
+
for (let e of Ve(t).matchAll(Be)) s.add(e[1]);
|
|
414
414
|
}
|
|
415
|
-
let c = i.filter((e) => !(e.pkg in
|
|
415
|
+
let c = i.filter((e) => !(e.pkg in Pe) && !s.has(e.pkg));
|
|
416
416
|
return {
|
|
417
417
|
declared: i.map((e) => e.pkg).sort(),
|
|
418
|
-
unimported: c.filter((e) => o || !
|
|
419
|
-
pendingCodegen: o ? [] : c.map((e) => e.pkg).filter((e) =>
|
|
420
|
-
exempt: i.map((e) => e.pkg).filter((e) => e in
|
|
418
|
+
unimported: c.filter((e) => o || !Fe.has(e.pkg)).sort((e, t) => e.pkg.localeCompare(t.pkg)),
|
|
419
|
+
pendingCodegen: o ? [] : c.map((e) => e.pkg).filter((e) => Fe.has(e)).sort(),
|
|
420
|
+
exempt: i.map((e) => e.pkg).filter((e) => e in Pe).sort(),
|
|
421
421
|
scannedFiles: a.length
|
|
422
422
|
};
|
|
423
|
-
},
|
|
423
|
+
}, Ue = (e) => {
|
|
424
424
|
if (e === null) return ["unimported deps · no package.json here — could not check"];
|
|
425
425
|
let t = `unimported deps · ${e.declared.length} @voltro/* package(s) declared, ${e.scannedFiles} file(s) scanned`;
|
|
426
426
|
if (e.declared.length === 0) return [t, " no @voltro/* dependencies declared — nothing to check"];
|
|
@@ -437,14 +437,14 @@ var le = /* @__PURE__ */ new Set([
|
|
|
437
437
|
let i = [t];
|
|
438
438
|
for (let t of e.unimported) i.push(` ⚠ ${t.pkg} — declared in ${t.declaredIn}, imported nowhere`);
|
|
439
439
|
return i.push(" a dependency nobody imports still gets installed, updated, and read as"), i.push(" evidence the package is in use — its breaking-change notes included."), i.push(" Remove it, or if it IS imported through an assembled specifier the scan"), i.push(" cannot see, keep it and ignore this line — the rule is advisory."), i.push(...r), n !== "" && i.push(` ·${n.slice(1)}`), i;
|
|
440
|
-
},
|
|
440
|
+
}, We = (e) => {
|
|
441
441
|
if (e.catalogCodes.length === 0) return;
|
|
442
442
|
let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
|
|
443
443
|
if (n.length !== 0) return {
|
|
444
444
|
orphaned: n,
|
|
445
445
|
noneDeclared: t.size === 0
|
|
446
446
|
};
|
|
447
|
-
},
|
|
447
|
+
}, Ge = (e, t) => {
|
|
448
448
|
let n = e.orphaned.join(", ");
|
|
449
449
|
return e.noneDeclared ? [
|
|
450
450
|
`⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
|
|
@@ -456,7 +456,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
456
456
|
" …or delete the files. A catalog that is never loaded looks from the inside",
|
|
457
457
|
" exactly like one that works, which is why this is worth a line."
|
|
458
458
|
] : [`⚠ ${t} has catalog(s) not in \`locales:\` — ${n} — so they are never imported.`, " Add the code(s) to `locales:` in your app config, or delete the file(s)."];
|
|
459
|
-
},
|
|
459
|
+
}, Ke = (e) => {
|
|
460
460
|
let t = e.getProperty("match");
|
|
461
461
|
if (t === void 0) return null;
|
|
462
462
|
let n = (t.asKind(O.PropertyAssignment)?.getInitializer())?.asKind(O.ObjectLiteralExpression);
|
|
@@ -493,7 +493,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
493
493
|
else return;
|
|
494
494
|
}
|
|
495
495
|
return r;
|
|
496
|
-
},
|
|
496
|
+
}, qe = (e, t) => {
|
|
497
497
|
let n = ["middleware.ts", "middleware.tsx"].find((t) => S(v(e, t)));
|
|
498
498
|
if (n === void 0) return;
|
|
499
499
|
let r = new D({
|
|
@@ -510,7 +510,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
510
510
|
file: n
|
|
511
511
|
};
|
|
512
512
|
}
|
|
513
|
-
let
|
|
513
|
+
let o = [], s = [], c = 0;
|
|
514
514
|
for (let e of i.getVariableDeclarations()) {
|
|
515
515
|
let t = e.getInitializerIfKind(O.CallExpression);
|
|
516
516
|
if (t?.getExpression().getText() !== "defineMiddleware") continue;
|
|
@@ -520,12 +520,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
520
520
|
s.push(n);
|
|
521
521
|
continue;
|
|
522
522
|
}
|
|
523
|
-
let i =
|
|
523
|
+
let i = Ke(r);
|
|
524
524
|
if (i === void 0) {
|
|
525
525
|
s.push(n);
|
|
526
526
|
continue;
|
|
527
527
|
}
|
|
528
|
-
|
|
528
|
+
o.push({
|
|
529
529
|
exportName: n,
|
|
530
530
|
middleware: {
|
|
531
531
|
_tag: "VoltroMiddleware",
|
|
@@ -535,45 +535,45 @@ var le = /* @__PURE__ */ new Set([
|
|
|
535
535
|
});
|
|
536
536
|
}
|
|
537
537
|
return {
|
|
538
|
-
problems: o
|
|
538
|
+
problems: a(o, t).map((e) => e.message),
|
|
539
539
|
unreadable: s,
|
|
540
540
|
seen: c,
|
|
541
541
|
file: n
|
|
542
542
|
};
|
|
543
|
-
},
|
|
543
|
+
}, Je = (e) => {
|
|
544
544
|
let t = [];
|
|
545
545
|
if (e.problems.length > 0) {
|
|
546
546
|
t.push(`✗ ${e.file}: ${e.problems.length} matcher problem(s) — this REFUSES the boot`);
|
|
547
547
|
for (let n of e.problems) t.push(` • ${n}`);
|
|
548
548
|
}
|
|
549
549
|
return e.unreadable.length > 0 && t.push(`… ${e.file}: ${e.unreadable.length} matcher(s) not statically readable (${e.unreadable.join(", ")}) — doctor did NOT check them. The boot still does.`), e.seen === 0 && t.push(`… ${e.file}: no \`defineMiddleware(...)\` found — doctor checked nothing here. A middleware assigned through a variable or re-exported from another module is invisible to this rule.`), t.length === 0 && e.seen > 0 && t.push(`✓ ${e.file}: ${e.seen} middleware(s), matchers resolve and do not overlap`), t;
|
|
550
|
-
},
|
|
550
|
+
}, Ye = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), Xe = /* @__PURE__ */ new Set([
|
|
551
551
|
"requireScope",
|
|
552
552
|
"requireAnyScope",
|
|
553
553
|
"requireAuth",
|
|
554
554
|
"assertScope",
|
|
555
555
|
"assertCan",
|
|
556
556
|
"can"
|
|
557
|
-
]),
|
|
557
|
+
]), Ze = (e) => {
|
|
558
558
|
let t = /* @__PURE__ */ new Set();
|
|
559
|
-
for (let n of e) for (let e of
|
|
559
|
+
for (let n of e) for (let e of Ye(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
|
|
560
560
|
return t;
|
|
561
|
-
},
|
|
561
|
+
}, Qe = (e, t) => {
|
|
562
562
|
for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
|
|
563
|
-
},
|
|
563
|
+
}, $e = (e) => {
|
|
564
564
|
let t = /\bsubject\s*\.\s*id\b[^\n]*?(?:!==|===|!=|==)|(?:!==|===|!=|==)[^\n]*?\bsubject\s*\.\s*id\b/.test(e), n = /(?:Error|Denied|Forbidden|Unauthorized)\b|\b(?:fail|die|throw)\b/.test(e);
|
|
565
565
|
return t && n;
|
|
566
|
-
},
|
|
566
|
+
}, et = (e, t, n) => {
|
|
567
567
|
if (e.hasDeclaredGuards) return { kind: "declared-guard" };
|
|
568
|
-
let r =
|
|
569
|
-
return i === void 0 ?
|
|
568
|
+
let r = Ye(e.src), i = Qe(r, t);
|
|
569
|
+
return i === void 0 ? $e(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
|
|
570
570
|
kind: "vocabulary-guard",
|
|
571
571
|
name: i
|
|
572
572
|
};
|
|
573
|
-
},
|
|
573
|
+
}, tt = (e, t, n) => {
|
|
574
574
|
let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
|
|
575
575
|
return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
|
|
576
|
-
},
|
|
576
|
+
}, nt = (e, t, n) => {
|
|
577
577
|
switch (t.kind) {
|
|
578
578
|
case "declared-guard": return "guards: on the descriptor";
|
|
579
579
|
case "vocabulary-guard": return `calls ${t.name}()`;
|
|
@@ -581,15 +581,15 @@ var le = /* @__PURE__ */ new Set([
|
|
|
581
581
|
case "allowlisted": return "accepted by a previous run";
|
|
582
582
|
case "unchecked": return n ? e.op === "read" ? "no check in this file — this app registers a row filter; VERIFY it covers this table, then record it as reviewed= rather than debt" : `${e.op}s${e.table ? ` ${e.table}` : ""} with no check in this file — this app registers a row filter; VERIFY it covers this table, then record it as reviewed= rather than debt` : e.op === "read" ? "returns a row the client selected, with nothing compared to the caller" : `${e.op}s${e.table ? ` ${e.table}` : ""} with nothing constraining WHICH row`;
|
|
583
583
|
}
|
|
584
|
-
},
|
|
584
|
+
}, rt = (e) => {
|
|
585
585
|
let t = e.executors.map((t) => {
|
|
586
|
-
let n =
|
|
586
|
+
let n = et(t, e.guardVocabulary, e.allowlist);
|
|
587
587
|
return {
|
|
588
588
|
tag: t.tag,
|
|
589
589
|
path: t.path,
|
|
590
590
|
verdict: n,
|
|
591
|
-
severity:
|
|
592
|
-
why:
|
|
591
|
+
severity: tt(t, e.tenantScopedTables, e.referencedTables),
|
|
592
|
+
why: nt(t, n, e.rowFilterRegistered === !0)
|
|
593
593
|
};
|
|
594
594
|
}), n = (e) => t.filter((t) => t.verdict.kind === e).length;
|
|
595
595
|
return {
|
|
@@ -603,7 +603,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
603
603
|
},
|
|
604
604
|
unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
|
|
605
605
|
};
|
|
606
|
-
},
|
|
606
|
+
}, it = [
|
|
607
607
|
"# voltro doctor — authz allowlist",
|
|
608
608
|
"#",
|
|
609
609
|
"# Two KINDS of line, and the difference is the point:",
|
|
@@ -628,19 +628,19 @@ var le = /* @__PURE__ */ new Set([
|
|
|
628
628
|
"# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
|
|
629
629
|
"# lines for executors that are newly unchecked.",
|
|
630
630
|
""
|
|
631
|
-
],
|
|
631
|
+
], at = (e) => {
|
|
632
632
|
let t = [...e].map((e) => typeof e == "string" ? { tag: e } : e), n = t.filter((e) => e.reviewed === void 0).map((e) => e.tag).sort(), r = t.filter((e) => e.reviewed !== void 0).sort((e, t) => e.tag.localeCompare(t.tag));
|
|
633
633
|
return [
|
|
634
|
-
...
|
|
634
|
+
...it,
|
|
635
635
|
...n,
|
|
636
636
|
...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
|
|
637
637
|
...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
|
|
638
638
|
""
|
|
639
639
|
].join("\n");
|
|
640
|
-
},
|
|
640
|
+
}, ot = (e, t) => {
|
|
641
641
|
let n = [...new Set(t)].sort();
|
|
642
|
-
if (e === void 0) return
|
|
643
|
-
let r =
|
|
642
|
+
if (e === void 0) return at(n.map((e) => ({ tag: e })));
|
|
643
|
+
let r = j(e).tags, i = n.filter((e) => !r.has(e));
|
|
644
644
|
return i.length === 0 ? e : [
|
|
645
645
|
e.replace(/\n+$/, ""),
|
|
646
646
|
"",
|
|
@@ -649,25 +649,25 @@ var le = /* @__PURE__ */ new Set([
|
|
|
649
649
|
...i,
|
|
650
650
|
""
|
|
651
651
|
].join("\n");
|
|
652
|
-
},
|
|
652
|
+
}, st = (e, t) => {
|
|
653
653
|
let n = [...new Set(t)];
|
|
654
654
|
if (e === void 0) return {
|
|
655
655
|
added: n.length,
|
|
656
656
|
kept: 0,
|
|
657
657
|
removed: 0
|
|
658
658
|
};
|
|
659
|
-
let r =
|
|
659
|
+
let r = j(e).tags;
|
|
660
660
|
return {
|
|
661
661
|
added: n.filter((e) => !r.has(e)).length,
|
|
662
662
|
kept: r.size,
|
|
663
663
|
removed: 0
|
|
664
664
|
};
|
|
665
|
-
},
|
|
665
|
+
}, ct = class extends Error {
|
|
666
666
|
tag;
|
|
667
667
|
constructor(e) {
|
|
668
668
|
super(`authz allowlist: '${e}' is marked reviewed with no reason. Write \`${e} reviewed=<why it is genuinely open>\`. A bare tag is the other, honest option — it records the executor as DEBT, which is what an unexplained exemption is.`), this.tag = e, this.name = "AllowlistReasonMissingError";
|
|
669
669
|
}
|
|
670
|
-
},
|
|
670
|
+
}, j = (e) => {
|
|
671
671
|
let t = [];
|
|
672
672
|
for (let n of e.split("\n")) {
|
|
673
673
|
let e = n.trim();
|
|
@@ -680,7 +680,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
680
680
|
continue;
|
|
681
681
|
}
|
|
682
682
|
let a = r[2].trim();
|
|
683
|
-
if (a.length === 0) throw new
|
|
683
|
+
if (a.length === 0) throw new ct(i);
|
|
684
684
|
t.push({
|
|
685
685
|
tag: i,
|
|
686
686
|
reviewed: a
|
|
@@ -690,13 +690,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
690
690
|
tags: new Set(t.map((e) => e.tag)),
|
|
691
691
|
entries: t
|
|
692
692
|
};
|
|
693
|
-
},
|
|
693
|
+
}, lt = (e) => {
|
|
694
694
|
let t = e.indexOf(".");
|
|
695
695
|
return t <= 0 ? void 0 : e.slice(0, t);
|
|
696
|
-
},
|
|
696
|
+
}, ut = (e, t) => {
|
|
697
697
|
let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
|
|
698
698
|
for (let e of t.rpcTags) {
|
|
699
|
-
let t =
|
|
699
|
+
let t = lt(e);
|
|
700
700
|
if (t === void 0) continue;
|
|
701
701
|
let n = i.get(t);
|
|
702
702
|
n ? n.push(e) : i.set(t, [e]);
|
|
@@ -725,7 +725,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
725
725
|
});
|
|
726
726
|
continue;
|
|
727
727
|
}
|
|
728
|
-
let s = new Set(o.rpcTags.map(
|
|
728
|
+
let s = new Set(o.rpcTags.map(lt).filter((e) => e !== void 0));
|
|
729
729
|
for (let e of s) {
|
|
730
730
|
let t = i.get(e);
|
|
731
731
|
t !== void 0 && a.push({
|
|
@@ -738,12 +738,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
738
738
|
}
|
|
739
739
|
}
|
|
740
740
|
return a;
|
|
741
|
-
},
|
|
741
|
+
}, dt = (e, t) => {
|
|
742
742
|
let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
|
|
743
743
|
if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
|
|
744
744
|
for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
|
|
745
745
|
return r;
|
|
746
|
-
},
|
|
746
|
+
}, ft = async (e) => {
|
|
747
747
|
if (!await e.hasWorkflows().catch(() => !1)) return { kind: "not-applicable" };
|
|
748
748
|
let t;
|
|
749
749
|
try {
|
|
@@ -767,12 +767,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
767
767
|
} finally {
|
|
768
768
|
await t.close().catch(() => {});
|
|
769
769
|
}
|
|
770
|
-
},
|
|
770
|
+
}, pt = (e) => {
|
|
771
771
|
let t = Math.round(e / 6e4);
|
|
772
772
|
if (t < 90) return `${t}m`;
|
|
773
773
|
let n = Math.round(t / 60);
|
|
774
774
|
return n < 48 ? `${n}h` : `${Math.round(n / 24)}d`;
|
|
775
|
-
},
|
|
775
|
+
}, mt = (e) => {
|
|
776
776
|
if (e.kind === "not-applicable") return null;
|
|
777
777
|
if (e.kind === "skipped") return `• stuck runs: NOT CHECKED — ${e.reason}\n This is the one doctor rule that reads the database rather than your source.
|
|
778
778
|
Run it where the app's DB env vars are set, or ignore this line locally.`;
|
|
@@ -780,14 +780,41 @@ var le = /* @__PURE__ */ new Set([
|
|
|
780
780
|
if (t.stalled.length === 0) n.push(`✓ stuck runs: none (${t.examined} live run(s) old enough to examine${t.waitingOnTimer > 0 ? `, ${t.waitingOnTimer} waiting on a durable timer` : ""}${t.alreadyReported > 0 ? `, ${t.alreadyReported} already reported` : ""})`);
|
|
781
781
|
else {
|
|
782
782
|
n.push(`✗ stuck runs: ${t.stalled.length} run(s) have made no progress`);
|
|
783
|
-
for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${
|
|
783
|
+
for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${pt(e.idleMs)} ${e.reason}`);
|
|
784
784
|
t.stalled.length > 20 && n.push(` … and ${t.stalled.length - 20} more`), n.push(" Nothing was changed — this is a SUSPICION, not a verdict. `awaiting-signal` usually means the sender never came.");
|
|
785
785
|
}
|
|
786
786
|
t.sawFullRunPage && n.push(" (the examined page filled up — more old runs exist than were read this pass)");
|
|
787
787
|
for (let e of t.failures) n.push(` ! could not examine ${e.subject}: ${e.detail}`);
|
|
788
788
|
return n.join("\n");
|
|
789
|
-
},
|
|
790
|
-
let t =
|
|
789
|
+
}, ht = /(?:^|\n)\s*import\s+(?!type\s)[^;'"]*?from\s+['"]@voltro\/ui-shadcn(?:\/(?!tokens\.css)[^'"]+)?['"]/, gt = /@import\s+["']@voltro\/ui-shadcn\/tokens\.css["']|@import\s+["']tailwindcss["']|from\s+['"]@voltro\/ui-shadcn\/tokens\.css['"]|import\s+['"][^'"]*@voltro\/ui-shadcn\/tokens\.css['"]/, _t = (e) => {
|
|
790
|
+
let t = [], n = [];
|
|
791
|
+
for (let r of e) gt.test(r.source) ? n.push(r.path) : !r.path.endsWith(".css") && ht.test(r.source) && t.push(r.path);
|
|
792
|
+
return {
|
|
793
|
+
kitImporters: t,
|
|
794
|
+
styleEntries: n
|
|
795
|
+
};
|
|
796
|
+
}, vt = (e) => e.kitImporters.length > 0 && e.styleEntries.length === 0, yt = (e) => {
|
|
797
|
+
if (!vt(e)) return [];
|
|
798
|
+
let t = e.kitImporters.slice(0, 5);
|
|
799
|
+
return [
|
|
800
|
+
`⚠ ui-shadcn: ${e.kitImporters.length} file(s) render kit components, and no CSS entry imports the kit's stylesheet.`,
|
|
801
|
+
...t.map((e) => ` ${e}`),
|
|
802
|
+
...e.kitImporters.length > t.length ? [` … and ${e.kitImporters.length - t.length} more`] : [],
|
|
803
|
+
"",
|
|
804
|
+
" The kit's components are Tailwind utility classes and nothing else, and",
|
|
805
|
+
" Tailwind v4 emits a utility only when a CSS entry declares it. Without",
|
|
806
|
+
" the stylesheet those classes name rules that do not exist — the markup",
|
|
807
|
+
" renders as bare browser controls. Nothing else reports this: the build",
|
|
808
|
+
" succeeds, `tsc` succeeds, and the page renders.",
|
|
809
|
+
"",
|
|
810
|
+
" Fix — add ONE line to the CSS your root layout imports:",
|
|
811
|
+
" @import \"@voltro/ui-shadcn/tokens.css\";",
|
|
812
|
+
"",
|
|
813
|
+
" Or, if this app is not a kit app, import the unstyled control instead:",
|
|
814
|
+
" import { LocaleSwitcher } from '@voltro/i18n'"
|
|
815
|
+
];
|
|
816
|
+
}, bt = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), xt = (e) => {
|
|
817
|
+
let t = f(e), n = d(e);
|
|
791
818
|
return {
|
|
792
819
|
root: e,
|
|
793
820
|
serveBundle: t,
|
|
@@ -795,22 +822,22 @@ var le = /* @__PURE__ */ new Set([
|
|
|
795
822
|
apiBundle: n,
|
|
796
823
|
apiBundlePresent: S(n)
|
|
797
824
|
};
|
|
798
|
-
},
|
|
825
|
+
}, St = [
|
|
799
826
|
"Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
|
|
800
827
|
"Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
|
|
801
828
|
"(Generated Dockerfiles from `voltro create-project` already do this.)"
|
|
802
|
-
].join("\n "),
|
|
803
|
-
let n =
|
|
804
|
-
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !
|
|
829
|
+
].join("\n "), Ct = (e, t = !1) => {
|
|
830
|
+
let n = xt(e);
|
|
831
|
+
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !bt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
|
|
805
832
|
console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
|
|
806
833
|
let r = process.env.NODE_ENV === "production";
|
|
807
834
|
if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
|
|
808
835
|
let i = t || r;
|
|
809
|
-
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${
|
|
810
|
-
},
|
|
836
|
+
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${St}`), +!!i;
|
|
837
|
+
}, wt = (e) => {
|
|
811
838
|
let t = e.absPath.replace(/\.server\.(tsx?)$/, ".$1");
|
|
812
839
|
if (t !== e.absPath) return e.sources.get(t) ?? e.sources.get(t.replace(/\.ts$/, ".tsx"));
|
|
813
|
-
},
|
|
840
|
+
}, Tt = (e, t, n) => {
|
|
814
841
|
let r = new Set(e), i = _(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
|
|
815
842
|
`${e}.ts`,
|
|
816
843
|
`${e}.tsx`,
|
|
@@ -836,7 +863,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
836
863
|
r !== void 0 && o.push(r);
|
|
837
864
|
}
|
|
838
865
|
return o;
|
|
839
|
-
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) =>
|
|
866
|
+
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) => zt.test(y(n, e))), l = new Set(c);
|
|
840
867
|
for (; c.length > 0;) {
|
|
841
868
|
let e = c.shift();
|
|
842
869
|
if (!s.has(e)) {
|
|
@@ -849,11 +876,11 @@ var le = /* @__PURE__ */ new Set([
|
|
|
849
876
|
let d = /* @__PURE__ */ new Set();
|
|
850
877
|
for (let e of s) l.has(e) || u.has(e) || d.add(e);
|
|
851
878
|
return d;
|
|
852
|
-
},
|
|
879
|
+
}, M = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Et = (e, t) => (e.match(t) ?? []).length, N = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Dt = (e, t) => {
|
|
853
880
|
if (e.src.includes(t)) return !0;
|
|
854
881
|
let n = t.replace(/^@voltro\/plugin-/, "");
|
|
855
|
-
return n !== t &&
|
|
856
|
-
},
|
|
882
|
+
return n !== t && N(e.file, RegExp(`^${n}Plugin$`)) > 0;
|
|
883
|
+
}, Ot = (e, t) => {
|
|
857
884
|
let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
|
|
858
885
|
for (; (i = r.exec(e)) !== null;) {
|
|
859
886
|
let t = i.index + i[0].length, r = 1, a = t;
|
|
@@ -861,10 +888,122 @@ var le = /* @__PURE__ */ new Set([
|
|
|
861
888
|
n.push(e.slice(t, a - 1));
|
|
862
889
|
}
|
|
863
890
|
return n;
|
|
864
|
-
},
|
|
891
|
+
}, kt = (e, t) => {
|
|
865
892
|
let n = t.getSymbol()?.getDeclarations()?.[0];
|
|
866
893
|
return n === void 0 ? [] : e.getDescendantsOfKind(O.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
|
|
867
|
-
},
|
|
894
|
+
}, At = /^(insert|insertMany|insertIgnore|update|updateMany|upsert|delete|deleteMany|hardDelete|patchJson)$/, jt = /^(notify|sendNotification|sendWebhook|dispatchWebhook|sendMail|trigger)\w*$/, Mt = (e, t) => {
|
|
895
|
+
if (!E.isObjectLiteralExpression(e)) return;
|
|
896
|
+
let n = e.getProperty("handler");
|
|
897
|
+
if (n === void 0 || !E.isPropertyAssignment(n)) return;
|
|
898
|
+
let r = n.getInitializer();
|
|
899
|
+
if (r === void 0) return;
|
|
900
|
+
if (E.isArrowFunction(r) || E.isFunctionExpression(r)) return r;
|
|
901
|
+
if (!E.isIdentifier(r)) return;
|
|
902
|
+
let i = t.getFunction(r.getText());
|
|
903
|
+
if (i !== void 0) return i;
|
|
904
|
+
let a = t.getVariableDeclaration(r.getText())?.getInitializer();
|
|
905
|
+
if (a !== void 0 && (E.isArrowFunction(a) || E.isFunctionExpression(a))) return a;
|
|
906
|
+
}, Nt = (e) => {
|
|
907
|
+
let t = [jt], n = (E.isArrowFunction(e) || E.isFunctionExpression(e) || E.isFunctionDeclaration(e) ? e.getParameters() : [])[1];
|
|
908
|
+
if (n === void 0) return t;
|
|
909
|
+
let r = n.getNameNode();
|
|
910
|
+
if (E.isIdentifier(r)) return t.push(RegExp(`^${r.getText()}\\.publish$`)), t.push(RegExp(`^${r.getText()}\\.store\\.${At.source.replace(/^\^|\$$/g, "")}$`)), t;
|
|
911
|
+
if (E.isObjectBindingPattern(r)) for (let e of r.getElements()) {
|
|
912
|
+
let n = e.getPropertyNameNode()?.getText() ?? e.getName(), r = e.getName();
|
|
913
|
+
n === "publish" && t.push(RegExp(`^${r}$`)), n === "store" && t.push(RegExp(`^${r}\\.${At.source.replace(/^\^|\$$/g, "")}$`));
|
|
914
|
+
}
|
|
915
|
+
return t;
|
|
916
|
+
}, Pt = (e, t, n, r = 0, i = /* @__PURE__ */ new Set()) => {
|
|
917
|
+
let a = e.getDescendantsOfKind(O.CallExpression);
|
|
918
|
+
for (let e of a) {
|
|
919
|
+
let t = e.getExpression().getText();
|
|
920
|
+
if (n.some((e) => e.test(t))) return !0;
|
|
921
|
+
}
|
|
922
|
+
if (r >= 2) return !1;
|
|
923
|
+
for (let e of a) {
|
|
924
|
+
let a = e.getExpression().getText();
|
|
925
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(a) || i.has(a)) continue;
|
|
926
|
+
i.add(a);
|
|
927
|
+
let o = t.getFunction(a) ?? (() => {
|
|
928
|
+
let e = t.getVariableDeclaration(a)?.getInitializer();
|
|
929
|
+
return e !== void 0 && (E.isArrowFunction(e) || E.isFunctionExpression(e)) ? e : void 0;
|
|
930
|
+
})();
|
|
931
|
+
if (o !== void 0 && Pt(o, t, [...n, ...Nt(o)], r + 1, i)) return !0;
|
|
932
|
+
}
|
|
933
|
+
return !1;
|
|
934
|
+
}, Ft = (e) => {
|
|
935
|
+
let t = /* @__PURE__ */ new Set();
|
|
936
|
+
for (let n of e.values()) {
|
|
937
|
+
if (!/\bsetRowFilter\s*[<(]/.test(n)) continue;
|
|
938
|
+
let e = /\btables\s*:\s*\[([^\]]*)\]/.exec(n);
|
|
939
|
+
if (e !== null) for (let n of e[1].matchAll(/['"`]([A-Za-z_][\w]*)['"`]/g)) t.add(n[1]);
|
|
940
|
+
}
|
|
941
|
+
return t;
|
|
942
|
+
}, It = (e) => {
|
|
943
|
+
if (!/\.(query|stream|mutation|action)\.server\.tsx?$/.test(e.path)) return !1;
|
|
944
|
+
let t = Ft(e.sources);
|
|
945
|
+
return t.size === 0 || ['"`]/g)].map((e) => e[1]).some((e) => t.has(e)) ? !1 : /\b(eq|and|inSet)\s*\(/.test(e.src) || /\bwhere\s*:/.test(e.src);
|
|
946
|
+
}, Lt = (e) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").some((t) => {
|
|
947
|
+
let n = t.getArguments()[0];
|
|
948
|
+
if (n === void 0 || !E.isObjectLiteralExpression(n) || n.getProperty("once") !== void 0) return !1;
|
|
949
|
+
let r = Mt(n, e);
|
|
950
|
+
return r !== void 0 && Pt(r, e, Nt(r));
|
|
951
|
+
}), Rt = (e) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").filter((e) => {
|
|
952
|
+
let t = e.getArguments()[0];
|
|
953
|
+
return t !== void 0 && E.isObjectLiteralExpression(t) && t.getProperty("once") === void 0;
|
|
954
|
+
}).length, zt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Bt = (e, t) => {
|
|
955
|
+
let n;
|
|
956
|
+
try {
|
|
957
|
+
n = t.createSourceFile("/descriptor-scan.ts", e, { overwrite: !0 });
|
|
958
|
+
} catch {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
for (let e of n.getDescendantsOfKind(O.PropertyAssignment)) {
|
|
962
|
+
if (e.getName() !== "output") continue;
|
|
963
|
+
let t = e.getInitializer();
|
|
964
|
+
if (t !== void 0 && E.isCallExpression(t) && /\.Array$/.test(t.getExpression().getText()) && (t = t.getArguments()[0]), t === void 0 || !E.isCallExpression(t) || !/\.Struct$/.test(t.getExpression().getText())) return;
|
|
965
|
+
let n = t.getArguments()[0];
|
|
966
|
+
if (n === void 0 || !E.isObjectLiteralExpression(n)) return;
|
|
967
|
+
let r = [];
|
|
968
|
+
for (let e of [n, ...n.getDescendantsOfKind(O.ObjectLiteralExpression)]) for (let t of e.getProperties()) if (E.isPropertyAssignment(t) || E.isShorthandPropertyAssignment(t)) r.push(t.getName());
|
|
969
|
+
else if (E.isSpreadAssignment(t)) return;
|
|
970
|
+
return r;
|
|
971
|
+
}
|
|
972
|
+
}, Vt = (e, t) => {
|
|
973
|
+
let n = [], r = (e) => {
|
|
974
|
+
e !== void 0 && (E.isObjectLiteralExpression(e) ? n.push(e) : E.isParenthesizedExpression(e) && r(e.getExpression()));
|
|
975
|
+
};
|
|
976
|
+
for (let t of e.getDescendantsOfKind(O.ReturnStatement)) r(t.getExpression());
|
|
977
|
+
for (let t of e.getDescendantsOfKind(O.ArrowFunction)) r(t.getBody());
|
|
978
|
+
let i = /* @__PURE__ */ new Set();
|
|
979
|
+
for (let e of n) {
|
|
980
|
+
let n = [];
|
|
981
|
+
for (let t of e.getProperties()) if (E.isPropertyAssignment(t) || E.isShorthandPropertyAssignment(t)) n.push(t.getName());
|
|
982
|
+
else if (E.isSpreadAssignment(t)) return;
|
|
983
|
+
if (n.some((e) => t.has(e))) for (let e of n) i.add(e);
|
|
984
|
+
}
|
|
985
|
+
return [...i];
|
|
986
|
+
}, Ht = (e, t, n) => {
|
|
987
|
+
let r = RegExp(`\\btable\\(\\s*['"\`]${e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"\`]`);
|
|
988
|
+
for (let [, i] of t) {
|
|
989
|
+
if (!r.test(i)) continue;
|
|
990
|
+
let t;
|
|
991
|
+
try {
|
|
992
|
+
t = n.createSourceFile("/entity-scan.ts", i, { overwrite: !0 });
|
|
993
|
+
} catch {
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
for (let n of t.getDescendantsOfKind(O.CallExpression)) {
|
|
997
|
+
if (n.getExpression().getText() !== "table") continue;
|
|
998
|
+
let [t, r] = n.getArguments();
|
|
999
|
+
if (t === void 0 || !E.isStringLiteral(t) || t.getLiteralValue() !== e) continue;
|
|
1000
|
+
if (r === void 0 || !E.isObjectLiteralExpression(r)) return;
|
|
1001
|
+
let i = /* @__PURE__ */ new Set();
|
|
1002
|
+
for (let e of r.getProperties()) E.isPropertyAssignment(e) && /\.nullable\s*\(/.test(e.getText()) && i.add(e.getName());
|
|
1003
|
+
return i;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}, Ut = (e) => /\bNullOr\b|\bSchema\.Null\b|\bnullable\s*\(/.test(e), P = /\.(query|mutation|action|stream)\.server\.tsx?$/, Wt = [
|
|
868
1007
|
"Id",
|
|
869
1008
|
"ID",
|
|
870
1009
|
"Hash",
|
|
@@ -890,7 +1029,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
890
1029
|
"Comment",
|
|
891
1030
|
"Provider",
|
|
892
1031
|
"Format"
|
|
893
|
-
],
|
|
1032
|
+
], Gt = RegExp(`(?:${Wt.join("|")})$|_(?:${Wt.map((e) => e.toLowerCase()).join("|")})$`), Kt = [
|
|
894
1033
|
{
|
|
895
1034
|
id: "hardcoded-framework-cookie",
|
|
896
1035
|
scope: "client",
|
|
@@ -903,7 +1042,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
903
1042
|
scope: "server",
|
|
904
1043
|
smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
|
|
905
1044
|
use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
|
|
906
|
-
match: ({ src: e, path: t }) =>
|
|
1045
|
+
match: ({ src: e, path: t }) => P.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
|
|
907
1046
|
},
|
|
908
1047
|
{
|
|
909
1048
|
id: "subject-write-no-guard",
|
|
@@ -912,12 +1051,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
912
1051
|
use: "declare `guards:` on the descriptor, or — if the procedure really is open — compare the loaded row's owner against `ctx.request.subject.id` and fail with a typed error",
|
|
913
1052
|
match: (e) => {
|
|
914
1053
|
let { src: t, path: n } = e;
|
|
915
|
-
if (!
|
|
916
|
-
let r =
|
|
1054
|
+
if (!P.test(n)) return !1;
|
|
1055
|
+
let r = M(t);
|
|
917
1056
|
if (!(/\bsubject\s*\.\s*id\b/.test(r) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(r)) || /\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(r) || /[=!]==?\s*[^;\n]*\bsubject\s*\.\s*id\b|\bsubject\s*\.\s*id\b\s*[=!]==/.test(r)) return !1;
|
|
918
|
-
let i =
|
|
1057
|
+
let i = wt(e);
|
|
919
1058
|
if (i === void 0) return !1;
|
|
920
|
-
let a =
|
|
1059
|
+
let a = M(i);
|
|
921
1060
|
return /\binternal\s*:\s*true\b/.test(a) || /\bguards\s*:\s*\[/.test(a) || /\.guard\s*\(/.test(a) ? !1 : /\bopenAccess\s*:/.test(a);
|
|
922
1061
|
}
|
|
923
1062
|
},
|
|
@@ -926,14 +1065,14 @@ var le = /* @__PURE__ */ new Set([
|
|
|
926
1065
|
scope: "server",
|
|
927
1066
|
smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
|
|
928
1067
|
use: "yield* HttpClient — the framework client. It injects traceparent, retries, and honours `http.allowHosts`. Where the URL is built from anything a caller supplied it ALSO blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop — for a hard-coded host that half is redundant, and the first three are still the reason to switch",
|
|
929
|
-
match: ({ file: e, path: t, serverOnly: n }) => (
|
|
1068
|
+
match: ({ file: e, path: t, serverOnly: n }) => (zt.test(t) || n) && N(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
|
|
930
1069
|
},
|
|
931
1070
|
{
|
|
932
1071
|
id: "form",
|
|
933
1072
|
scope: "client",
|
|
934
1073
|
smell: "per-field useState + a submit flag (hand-rolled form)",
|
|
935
1074
|
use: "useFormBinding — fields + validation from the mutation input Schema; server ValidationError({ field }) routes to the field",
|
|
936
|
-
match: ({ src: e, file: t }) =>
|
|
1075
|
+
match: ({ src: e, file: t }) => N(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
|
|
937
1076
|
},
|
|
938
1077
|
{
|
|
939
1078
|
id: "table",
|
|
@@ -961,7 +1100,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
961
1100
|
scope: "client",
|
|
962
1101
|
smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
|
|
963
1102
|
use: "useDerived — dependency-tracked, referentially stable",
|
|
964
|
-
match: ({ src: e }) =>
|
|
1103
|
+
match: ({ src: e }) => Et(e, /\buseSubscription\s*[(<]/g) >= 3 && Et(e, /\buseMemo\s*\(/g) >= 3
|
|
965
1104
|
},
|
|
966
1105
|
{
|
|
967
1106
|
id: "next-shim",
|
|
@@ -993,7 +1132,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
993
1132
|
match: ({ file: e }) => e.getDescendantsOfKind(O.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
|
|
994
1133
|
let t = e.getFirstAncestorByKind(O.VariableDeclaration)?.getNameNode();
|
|
995
1134
|
return t === void 0 ? [] : E.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
|
|
996
|
-
}).some((t) =>
|
|
1135
|
+
}).some((t) => kt(e, t).some((e) => {
|
|
997
1136
|
let t = e.getParent();
|
|
998
1137
|
if (E.isBinaryExpression(t)) {
|
|
999
1138
|
let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
|
|
@@ -1021,7 +1160,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1021
1160
|
scope: "server",
|
|
1022
1161
|
smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
|
|
1023
1162
|
use: "reads are a parent→child walk on ONE key → relations() in a *.relations.ts + .with({ … }) (ONE JSON-aggregate query, every dialect); reads collect ids from SEVERAL sources → keep the assembly and run the independent leading reads under Effect.all (same queries, same results, only concurrent — zero parity risk)",
|
|
1024
|
-
match: (e) =>
|
|
1163
|
+
match: (e) => N(e.file, /\bstore\.query$/) >= 3 || N(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
|
|
1025
1164
|
},
|
|
1026
1165
|
{
|
|
1027
1166
|
id: "store-effect-lift",
|
|
@@ -1044,7 +1183,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1044
1183
|
use: ".encrypted() on the column — boot fails loudly if no cipher is configured, so it can never silently persist plaintext (a *Hash column and a vault* handle are NOT plaintext secrets: the hash IS the protection — often a unique lookup key — and the handle only names a secret held elsewhere, so both are skipped)",
|
|
1045
1184
|
match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
|
|
1046
1185
|
let n = t[1];
|
|
1047
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
1186
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Gt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
|
|
1048
1187
|
}))
|
|
1049
1188
|
},
|
|
1050
1189
|
{
|
|
@@ -1054,15 +1193,67 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1054
1193
|
use: "you cannot look a value up by equality AND encrypt it non-deterministically — `.encrypted()` would make this column unfindable. Store the HASH as the unique lookup key and keep the secret itself either `.encrypted()` in a second column or nowhere (the framework's own `_voltro_api_keys.hashedKey` is the shape: the presented value is hashed and compared, never read back). A deterministic cipher is the other option and buys equality lookup at the cost of leaking which rows share a value",
|
|
1055
1194
|
match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
|
|
1056
1195
|
let n = t[1];
|
|
1057
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
1196
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Gt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
|
|
1058
1197
|
}))
|
|
1059
1198
|
},
|
|
1199
|
+
{
|
|
1200
|
+
id: "nullable-column-a-mutation-cannot-clear",
|
|
1201
|
+
scope: "server",
|
|
1202
|
+
smell: "a mutation writes a NULLABLE column through an input field that does not accept `null` — the value can be set once and never cleared",
|
|
1203
|
+
use: "declare the field as `Schema.optional(Schema.NullOr(...))` — a column the schema lets be empty needs a wire shape that can say so",
|
|
1204
|
+
match: (e) => {
|
|
1205
|
+
if (!/\.mutation\.tsx?$/.test(e.path)) return !1;
|
|
1206
|
+
let t = /target\s*:\s*\{[^}]*\btable\s*:\s*['"`]([^'"`]+)['"`]/.exec(e.src)?.[1];
|
|
1207
|
+
if (t === void 0) return !1;
|
|
1208
|
+
let n = Ht(t, e.sources, e.file.getProject());
|
|
1209
|
+
if (n === void 0 || n.size === 0) return !1;
|
|
1210
|
+
for (let t of e.file.getDescendantsOfKind(O.PropertyAssignment)) {
|
|
1211
|
+
if (t.getName() !== "input") continue;
|
|
1212
|
+
let e = t.getInitializer();
|
|
1213
|
+
if (e === void 0 || !E.isCallExpression(e) || !/\.Struct$/.test(e.getExpression().getText())) continue;
|
|
1214
|
+
let r = e.getArguments()[0];
|
|
1215
|
+
if (!(r === void 0 || !E.isObjectLiteralExpression(r))) {
|
|
1216
|
+
for (let e of r.getProperties()) if (E.isPropertyAssignment(e) && n.has(e.getName()) && !Ut(e.getText())) return !0;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return !1;
|
|
1220
|
+
}
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
id: "executor-builds-an-undeclared-field",
|
|
1224
|
+
scope: "server",
|
|
1225
|
+
smell: "an executor builds a field its descriptor's `output` does not declare (silently stripped on the way out)",
|
|
1226
|
+
use: "add the field to the descriptor's `output` struct, or stop building it — `output` IS the serializer, so an undeclared key never reaches the caller",
|
|
1227
|
+
match: (e) => {
|
|
1228
|
+
if (!P.test(e.path)) return !1;
|
|
1229
|
+
let t = wt(e);
|
|
1230
|
+
if (t === void 0) return !1;
|
|
1231
|
+
let n = Bt(t, e.file.getProject());
|
|
1232
|
+
if (n === void 0 || n.length === 0) return !1;
|
|
1233
|
+
let r = Vt(e.file, new Set(n));
|
|
1234
|
+
return r === void 0 || r.length === 0 ? !1 : r.some((e) => !n.includes(e));
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
id: "subscriber-effect-without-once",
|
|
1239
|
+
scope: "server",
|
|
1240
|
+
smell: "a *.subscribe.ts handler that WRITES or publishes with no `once:` (it runs on every replica, so the effect repeats once per replica)",
|
|
1241
|
+
use: "once: true on defineSubscriber — one INSERT-wins claim per change, fleet-wide (or `once: (event) => key` for a coarser key). A hand-rolled `insertIgnore` dedupe is this, written out.",
|
|
1242
|
+
match: ({ file: e }) => Lt(e)
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
id: "row-filter-duplicated-by-hand",
|
|
1246
|
+
scope: "server",
|
|
1247
|
+
smell: "a handler that constrains a table your registered `setRowFilter` already constrains",
|
|
1248
|
+
use: "drop the hand-written predicate — the filter covers the query, the mutation, the stream AND the subscription delivery. Keep it only if this handler must be NARROWER than the filter, and say so in a comment (two identical predicates ANDed are as tight as one, so this is maintenance, not a leak)",
|
|
1249
|
+
match: (e) => It(e)
|
|
1250
|
+
},
|
|
1060
1251
|
{
|
|
1061
1252
|
id: "mutation-tail-effect",
|
|
1062
1253
|
scope: "server",
|
|
1063
1254
|
smell: "a notify / webhook helper called at the tail of a mutation",
|
|
1064
1255
|
use: "defineSubscriber (*.subscribe.ts) or defineReaction (*.reaction.tsx) — declarative, post-commit, with the old/new diff, instead of reactivity hidden inside every executor",
|
|
1065
|
-
match: ({ src: e, path: t }) => /\b(notify|sendNotification|sendWebhook|dispatchWebhook|trigger[A-Z]\w*)\s*\(/.test(e) && (/\bexport\s+default\b/.test(e) || /\.(mutation|action)\.server\.tsx?$/.test(t))
|
|
1256
|
+
match: ({ src: e, path: t, file: n }) => /\b(notify|sendNotification|sendWebhook|dispatchWebhook|trigger[A-Z]\w*)\s*\(/.test(e) && (/\bexport\s+default\b/.test(e) || /\.(mutation|action)\.server\.tsx?$/.test(t)) && N(n, /^(defineSubscriber|defineReaction)$/) === 0
|
|
1066
1257
|
},
|
|
1067
1258
|
{
|
|
1068
1259
|
id: "hand-cursor",
|
|
@@ -1118,9 +1309,9 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1118
1309
|
scope: "server",
|
|
1119
1310
|
smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
|
|
1120
1311
|
use: "wire the filter into at least one test (`makeTestContext({ subject, store, rowFilter })`) and sabotage the predicate once to watch it go red — an untested row filter is a visibility rule nothing verifies",
|
|
1121
|
-
match: ({ file: e, testSources: t }) =>
|
|
1122
|
-
let t =
|
|
1123
|
-
return
|
|
1312
|
+
match: ({ file: e, testSources: t }) => N(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
|
|
1313
|
+
let t = M(e);
|
|
1314
|
+
return Ot(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
|
|
1124
1315
|
})
|
|
1125
1316
|
},
|
|
1126
1317
|
{
|
|
@@ -1130,15 +1321,15 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1130
1321
|
use: "timestampMs / timestampMsOrNull from @voltro/database/wire in the descriptor's output struct — `output` IS the serializer (it is the rpc success schema), so a Date field declared as timestampMs crosses as epoch ms on its own; declaring Schema.Number describes the WIRE type and leaves nothing to convert",
|
|
1131
1322
|
match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
|
|
1132
1323
|
}
|
|
1133
|
-
],
|
|
1324
|
+
], F = (e) => L(e), qt = (e, t) => {
|
|
1134
1325
|
let n = /* @__PURE__ */ new Set();
|
|
1135
1326
|
for (let r of t) {
|
|
1136
1327
|
let t = y(e, r), i = t.split(/[\\/]/)[0];
|
|
1137
1328
|
n.add(i !== void 0 && i !== t ? i : ".");
|
|
1138
1329
|
}
|
|
1139
1330
|
return [...n].sort();
|
|
1140
|
-
},
|
|
1141
|
-
if (n > 12 ||
|
|
1331
|
+
}, Jt = /\.(tsx?|jsx?)$/, I = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Yt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Xt = (e, t = [], n = 0) => {
|
|
1332
|
+
if (n > 12 || I.test(e)) return t;
|
|
1142
1333
|
let r;
|
|
1143
1334
|
try {
|
|
1144
1335
|
r = w(e, { withFileTypes: !0 });
|
|
@@ -1149,12 +1340,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1149
1340
|
let r = v(e, i.name);
|
|
1150
1341
|
if (i.isDirectory()) {
|
|
1151
1342
|
if (i.name.startsWith(".")) continue;
|
|
1152
|
-
|
|
1343
|
+
Xt(r, t, n + 1);
|
|
1153
1344
|
} else je.test(i.name) && t.push(r);
|
|
1154
1345
|
}
|
|
1155
1346
|
return t;
|
|
1156
|
-
},
|
|
1157
|
-
if (n > 12 ||
|
|
1347
|
+
}, L = (e, t = [], n = 0) => {
|
|
1348
|
+
if (n > 12 || I.test(e)) return t;
|
|
1158
1349
|
let r;
|
|
1159
1350
|
try {
|
|
1160
1351
|
r = w(e);
|
|
@@ -1168,11 +1359,11 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1168
1359
|
} catch {
|
|
1169
1360
|
continue;
|
|
1170
1361
|
}
|
|
1171
|
-
a.isDirectory() ?
|
|
1362
|
+
a.isDirectory() ? L(r, t, n + 1) : Jt.test(i) && !/\.(test|spec)\./.test(i) && !Yt.test(i) && t.push(r);
|
|
1172
1363
|
}
|
|
1173
1364
|
return t;
|
|
1174
|
-
},
|
|
1175
|
-
if (n > 12 ||
|
|
1365
|
+
}, Zt = (e, t = [], n = 0) => {
|
|
1366
|
+
if (n > 12 || I.test(e)) return t;
|
|
1176
1367
|
let r;
|
|
1177
1368
|
try {
|
|
1178
1369
|
r = w(e);
|
|
@@ -1186,17 +1377,17 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1186
1377
|
} catch {
|
|
1187
1378
|
continue;
|
|
1188
1379
|
}
|
|
1189
|
-
a.isDirectory() ?
|
|
1380
|
+
a.isDirectory() ? Zt(r, t, n + 1) : Jt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
|
|
1190
1381
|
}
|
|
1191
1382
|
return t;
|
|
1192
|
-
},
|
|
1193
|
-
let t =
|
|
1383
|
+
}, Qt = (e) => {
|
|
1384
|
+
let t = F(e);
|
|
1194
1385
|
return {
|
|
1195
|
-
dirs:
|
|
1386
|
+
dirs: qt(e, t),
|
|
1196
1387
|
fileCount: t.length
|
|
1197
1388
|
};
|
|
1198
|
-
},
|
|
1199
|
-
let t =
|
|
1389
|
+
}, $t = (e) => {
|
|
1390
|
+
let t = F(e);
|
|
1200
1391
|
if (t.length === 0) return [];
|
|
1201
1392
|
let n = new D({
|
|
1202
1393
|
useInMemoryFileSystem: !0,
|
|
@@ -1205,8 +1396,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1205
1396
|
for (let e of t) try {
|
|
1206
1397
|
i.set(e, C(e, "utf8"));
|
|
1207
1398
|
} catch {}
|
|
1208
|
-
let a =
|
|
1209
|
-
for (let t of
|
|
1399
|
+
let a = Tt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
|
|
1400
|
+
for (let t of Zt(e)) try {
|
|
1210
1401
|
o.set(t, C(t, "utf8"));
|
|
1211
1402
|
} catch {}
|
|
1212
1403
|
for (let s of t) {
|
|
@@ -1226,23 +1417,34 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1226
1417
|
} catch {
|
|
1227
1418
|
continue;
|
|
1228
1419
|
}
|
|
1229
|
-
for (let t of
|
|
1230
|
-
if (t.plugin !== void 0 &&
|
|
1420
|
+
for (let t of Kt) {
|
|
1421
|
+
if (t.plugin !== void 0 && Dt(c, t.plugin) || !t.match(c)) continue;
|
|
1231
1422
|
let n = r.get(t.id) ?? [];
|
|
1232
1423
|
n.push(y(e, s)), r.set(t.id, n);
|
|
1233
1424
|
}
|
|
1234
1425
|
}
|
|
1235
|
-
return
|
|
1426
|
+
return Kt.filter((e) => r.has(e.id)).map((e) => ({
|
|
1236
1427
|
rule: e,
|
|
1237
1428
|
files: r.get(e.id)
|
|
1238
1429
|
})).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
|
|
1239
|
-
},
|
|
1240
|
-
let t =
|
|
1430
|
+
}, en = (e) => {
|
|
1431
|
+
let t = new D({
|
|
1432
|
+
useInMemoryFileSystem: !0,
|
|
1433
|
+
skipAddingFilesFromTsConfig: !0
|
|
1434
|
+
}), n = 0;
|
|
1435
|
+
for (let r of F(e)) if (/\.subscribe\.tsx?$/.test(r)) try {
|
|
1436
|
+
let e = t.createSourceFile(`/count${r.endsWith("x") ? ".tsx" : ".ts"}`, C(r, "utf8"), { overwrite: !0 });
|
|
1437
|
+
n += Rt(e);
|
|
1438
|
+
} catch {}
|
|
1439
|
+
return n;
|
|
1440
|
+
}, R = (e) => {
|
|
1441
|
+
let t = Qt(e);
|
|
1241
1442
|
return {
|
|
1242
1443
|
root: e,
|
|
1243
1444
|
scannedFiles: t.fileCount,
|
|
1244
1445
|
scannedDirs: t.dirs,
|
|
1245
|
-
|
|
1446
|
+
subscribersWithoutOnce: en(e),
|
|
1447
|
+
findings: $t(e).map(({ rule: e, files: t }) => ({
|
|
1246
1448
|
id: e.id,
|
|
1247
1449
|
scope: e.scope,
|
|
1248
1450
|
smell: e.smell,
|
|
@@ -1250,13 +1452,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1250
1452
|
files: t
|
|
1251
1453
|
}))
|
|
1252
1454
|
};
|
|
1253
|
-
},
|
|
1455
|
+
}, tn = 3, nn = .2, rn = 50, an = (e, t) => e >= rn && t > 0 && e / t >= nn, on = (e) => {
|
|
1254
1456
|
let t = /* @__PURE__ */ new Map();
|
|
1255
|
-
for (let n of
|
|
1457
|
+
for (let n of L(e)) try {
|
|
1256
1458
|
t.set(y(e, n), C(n, "utf8"));
|
|
1257
1459
|
} catch {}
|
|
1258
1460
|
let n = /* @__PURE__ */ new Map();
|
|
1259
|
-
for (let t of
|
|
1461
|
+
for (let t of Xt(e)) try {
|
|
1260
1462
|
n.set(y(e, t), C(t, "utf8"));
|
|
1261
1463
|
} catch {}
|
|
1262
1464
|
let r = Me({
|
|
@@ -1264,8 +1466,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1264
1466
|
envFiles: n
|
|
1265
1467
|
});
|
|
1266
1468
|
r !== void 0 && (console.log(""), console.log(Ne(r)));
|
|
1267
|
-
},
|
|
1268
|
-
let t =
|
|
1469
|
+
}, sn = (e) => {
|
|
1470
|
+
let t = R(e);
|
|
1269
1471
|
if (console.log(""), t.scannedFiles === 0) return console.log(`\u2022 hand-roll scan: no source directories found under ${e}.`), console.log(" Walked the app root recursively (node_modules / dist / .framework pruned). Run `voltro doctor <app>` against an app root."), 0;
|
|
1270
1472
|
if (t.findings.length === 0) return console.log(`\u2713 hand-roll scan: ${t.scannedFiles} file${t.scannedFiles === 1 ? "" : "s"} across ${t.scannedDirs.join(", ")} \u2014 no hand-rolled primitives found.`), 0;
|
|
1271
1473
|
console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
|
|
@@ -1276,37 +1478,43 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1276
1478
|
console.log(` [${e}]`);
|
|
1277
1479
|
let i = [...r].sort((e, t) => e.files.length - t.files.length);
|
|
1278
1480
|
for (let e of i) {
|
|
1279
|
-
if (
|
|
1481
|
+
if (an(e.files.length, t.scannedFiles)) {
|
|
1280
1482
|
let n = Math.round(e.files.length / t.scannedFiles * 100);
|
|
1281
1483
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s), ${n}% of those scanned: a codebase-wide pattern rather than a work list`), console.log(` \u2192 ${e.use}`), console.log(" (paths omitted on purpose at this scale — `voltro doctor --json` has every one.)");
|
|
1282
1484
|
continue;
|
|
1283
1485
|
}
|
|
1284
|
-
let r = e.files.slice(0,
|
|
1486
|
+
let r = e.files.slice(0, tn).join(", "), i = e.files.length - tn;
|
|
1285
1487
|
i > 0 && (n += i);
|
|
1286
1488
|
let a = i > 0 ? `, +${i} more` : "";
|
|
1287
1489
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
|
|
1288
1490
|
}
|
|
1289
1491
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1492
|
+
n > 0 && console.log(` (${n} more file path${n === 1 ? "" : "s"} not shown \u2014 \`voltro doctor --json\` prints every one.)`);
|
|
1493
|
+
let r = t.findings.find((e) => e.id === "subscriber-effect-without-once");
|
|
1494
|
+
if (t.subscribersWithoutOnce > 0) {
|
|
1495
|
+
let e = r?.files.length ?? 0;
|
|
1496
|
+
console.log(` \u24d8 ${t.subscribersWithoutOnce} subscriber(s) declare no \`once:\`; an effect was found in ${e}. The rule reads each subscriber's OWN file (its handler and the functions that file declares) and does not resolve imports — a write in another module is unexamined, not absent.`);
|
|
1497
|
+
}
|
|
1498
|
+
return console.log(" (See the \"Pick the SERVER primitive\" / \"Pick the CLIENT primitive\" rubrics in AGENTS.md / CLAUDE.md.)"), t.findings.length;
|
|
1499
|
+
}, cn = "renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.", ln = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, un = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, dn = (e) => {
|
|
1500
|
+
let t = ln.exec(e)?.[1];
|
|
1293
1501
|
if (t !== void 0) return t === "ssr" ? "ssr" : null;
|
|
1294
|
-
let n =
|
|
1502
|
+
let n = un.exec(e)?.[1];
|
|
1295
1503
|
return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
|
|
1296
|
-
},
|
|
1504
|
+
}, z = async (e) => {
|
|
1297
1505
|
let t = v(e, "src", "pages");
|
|
1298
1506
|
if (!S(t)) return [];
|
|
1299
|
-
let { pages: n, dirs: r } = await
|
|
1507
|
+
let { pages: n, dirs: r } = await s(t), i = [];
|
|
1300
1508
|
for (let a of n) {
|
|
1301
|
-
if (!
|
|
1509
|
+
if (!l(r, a.dirChain).some((e) => e.hasLayout)) continue;
|
|
1302
1510
|
let n;
|
|
1303
1511
|
try {
|
|
1304
1512
|
n = C(v(t, a.file), "utf8");
|
|
1305
1513
|
} catch {
|
|
1306
1514
|
continue;
|
|
1307
1515
|
}
|
|
1308
|
-
if (
|
|
1309
|
-
let o =
|
|
1516
|
+
if (c.test(n)) continue;
|
|
1517
|
+
let o = dn(n);
|
|
1310
1518
|
o !== null && i.push({
|
|
1311
1519
|
file: y(e, v(t, a.file)),
|
|
1312
1520
|
pattern: a.pattern,
|
|
@@ -1314,20 +1522,20 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1314
1522
|
});
|
|
1315
1523
|
}
|
|
1316
1524
|
return i;
|
|
1317
|
-
},
|
|
1525
|
+
}, fn = 10, pn = async (e) => {
|
|
1318
1526
|
console.log("");
|
|
1319
1527
|
let t = v(e, "src", "pages");
|
|
1320
1528
|
if (!S(t)) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
|
|
1321
|
-
let n = await
|
|
1529
|
+
let n = await z(e);
|
|
1322
1530
|
if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
|
|
1323
1531
|
console.log(`• renderMode:'spa' candidates (${n.length} page${n.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
|
|
1324
|
-
for (let e of n.slice(0,
|
|
1532
|
+
for (let e of n.slice(0, fn)) {
|
|
1325
1533
|
let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
|
|
1326
1534
|
console.log(` ${e.file} (${e.pattern}) — ${t}`);
|
|
1327
1535
|
}
|
|
1328
|
-
let r = n.length -
|
|
1329
|
-
return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${
|
|
1330
|
-
},
|
|
1536
|
+
let r = n.length - fn;
|
|
1537
|
+
return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${cn}`), n.length;
|
|
1538
|
+
}, mn = /* @__PURE__ */ new Set([
|
|
1331
1539
|
"node_modules",
|
|
1332
1540
|
"dist",
|
|
1333
1541
|
"build",
|
|
@@ -1335,7 +1543,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1335
1543
|
".vite",
|
|
1336
1544
|
".framework",
|
|
1337
1545
|
".git"
|
|
1338
|
-
]),
|
|
1546
|
+
]), hn = (e, t, n) => {
|
|
1339
1547
|
let r;
|
|
1340
1548
|
try {
|
|
1341
1549
|
r = w(e, { withFileTypes: !0 });
|
|
@@ -1343,11 +1551,11 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1343
1551
|
return;
|
|
1344
1552
|
}
|
|
1345
1553
|
for (let i of r) {
|
|
1346
|
-
if (
|
|
1554
|
+
if (mn.has(i.name)) continue;
|
|
1347
1555
|
let r = v(e, i.name);
|
|
1348
|
-
i.isDirectory() ?
|
|
1556
|
+
i.isDirectory() ? hn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
|
|
1349
1557
|
}
|
|
1350
|
-
},
|
|
1558
|
+
}, gn = (e, t) => {
|
|
1351
1559
|
let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
|
|
1352
1560
|
for (let i of t) {
|
|
1353
1561
|
let t;
|
|
@@ -1372,21 +1580,21 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1372
1580
|
}
|
|
1373
1581
|
}
|
|
1374
1582
|
return n;
|
|
1375
|
-
},
|
|
1583
|
+
}, _n = (e, t) => {
|
|
1376
1584
|
let n = e.slice(t.length).split(x).slice(0, -1)[0];
|
|
1377
1585
|
return n !== void 0 && n.startsWith("[");
|
|
1378
|
-
},
|
|
1586
|
+
}, B = (e) => {
|
|
1379
1587
|
let t = [], n = v(e, "src", "pages"), r = v(e, "src");
|
|
1380
1588
|
if (!S(r)) return t;
|
|
1381
1589
|
let i = [];
|
|
1382
|
-
|
|
1590
|
+
hn(r, e, i), i.sort();
|
|
1383
1591
|
let a = `${y(e, n)}${x}`;
|
|
1384
|
-
|
|
1592
|
+
gn(e, i);
|
|
1385
1593
|
for (let n of i) {
|
|
1386
1594
|
let r = n.slice(n.lastIndexOf(x) + 1);
|
|
1387
1595
|
if (n.startsWith(a)) {
|
|
1388
1596
|
if (r !== "page.tsx") continue;
|
|
1389
|
-
S(v(e,
|
|
1597
|
+
S(v(e, m(n))) || t.push({
|
|
1390
1598
|
kind: "page-without-test",
|
|
1391
1599
|
file: n
|
|
1392
1600
|
});
|
|
@@ -1397,7 +1605,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1397
1605
|
file: n
|
|
1398
1606
|
});
|
|
1399
1607
|
} catch {}
|
|
1400
|
-
|
|
1608
|
+
_n(n, a) && t.push({
|
|
1401
1609
|
kind: "top-level-dynamic-route",
|
|
1402
1610
|
file: n
|
|
1403
1611
|
});
|
|
@@ -1409,35 +1617,35 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1409
1617
|
});
|
|
1410
1618
|
}
|
|
1411
1619
|
return t;
|
|
1412
|
-
},
|
|
1620
|
+
}, vn = 10, V = (e, t, n) => {
|
|
1413
1621
|
if (t.length === 0) return;
|
|
1414
1622
|
console.log(`• ${e} (${t.length}):`);
|
|
1415
|
-
for (let e of t.slice(0,
|
|
1416
|
-
let r = t.length -
|
|
1623
|
+
for (let e of t.slice(0, vn)) console.log(` ${e}`);
|
|
1624
|
+
let r = t.length - vn;
|
|
1417
1625
|
r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
|
|
1418
|
-
},
|
|
1626
|
+
}, yn = (e) => {
|
|
1419
1627
|
console.log("");
|
|
1420
1628
|
let t = v(e, "src", "pages");
|
|
1421
1629
|
if (!S(t)) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
|
|
1422
|
-
let n =
|
|
1630
|
+
let n = B(e);
|
|
1423
1631
|
if (n.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
|
|
1424
1632
|
let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
|
|
1425
|
-
return
|
|
1426
|
-
},
|
|
1427
|
-
let t =
|
|
1633
|
+
return V("will NEVER route — page.tsx outside src/pages/", r("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), V("pages with no test beside them", r("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), V("routes whose FIRST segment is dynamic — they claim every URL of that depth", r("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), V("pages with no `meta` export — they serve the APP name as their title", r("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). If such a page also has no real body — it redirects from an effect, say — the missing title is the symptom rather than the bug: on an `ssr` route the server answers 200 with an empty document and the redirect happens only once JavaScript runs. Move it into the loader (`redirect(...)`, a real 303). Advisory."), n.length;
|
|
1634
|
+
}, bn = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", xn = /* @__PURE__ */ new Map(), H = (e) => {
|
|
1635
|
+
let t = xn.get(e);
|
|
1428
1636
|
if (t !== void 0) return t;
|
|
1429
1637
|
let n = (async () => {
|
|
1430
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
1638
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-Bl9HqtV7.js"), r = await t(e);
|
|
1431
1639
|
return {
|
|
1432
1640
|
files: r,
|
|
1433
|
-
discovered: await n(r)
|
|
1641
|
+
discovered: await n(r, { root: e })
|
|
1434
1642
|
};
|
|
1435
1643
|
})();
|
|
1436
|
-
return
|
|
1437
|
-
},
|
|
1644
|
+
return xn.set(e, n), n;
|
|
1645
|
+
}, Sn = async (e) => {
|
|
1438
1646
|
let t;
|
|
1439
1647
|
try {
|
|
1440
|
-
let { files: n, discovered: r } = await
|
|
1648
|
+
let { files: n, discovered: r } = await H(e), i = r.tables.map((e) => {
|
|
1441
1649
|
let t = e;
|
|
1442
1650
|
return {
|
|
1443
1651
|
name: t.tableName,
|
|
@@ -1450,7 +1658,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1450
1658
|
return;
|
|
1451
1659
|
}
|
|
1452
1660
|
t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
|
|
1453
|
-
},
|
|
1661
|
+
}, Cn = (e) => e.map((e) => {
|
|
1454
1662
|
let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
|
|
1455
1663
|
name: e,
|
|
1456
1664
|
type: typeof t?.type == "string" ? t.type : ""
|
|
@@ -1460,13 +1668,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1460
1668
|
columns: n,
|
|
1461
1669
|
primaryKey: t.appliedPrimaryKey ?? []
|
|
1462
1670
|
};
|
|
1463
|
-
}),
|
|
1671
|
+
}), wn = async (e) => {
|
|
1464
1672
|
try {
|
|
1465
|
-
let { missingRelationSources: t } = await import("./doctorSourceRelations-c5mlN5HI.js"), { sourceKeys: n } = await import("@voltro/protocol"), { readFile: r } = await import("node:fs/promises"), { discovered: i } = await
|
|
1673
|
+
let { missingRelationSources: t } = await import("./doctorSourceRelations-c5mlN5HI.js"), { sourceKeys: n } = await import("@voltro/protocol"), { readFile: r } = await import("node:fs/promises"), { discovered: i } = await H(e), a = [], o = [];
|
|
1466
1674
|
for (let e of i.queries) {
|
|
1467
1675
|
let i = n(e.descriptor.source);
|
|
1468
1676
|
if (i.length === 0) continue;
|
|
1469
|
-
let s =
|
|
1677
|
+
let s = Tn(e.file);
|
|
1470
1678
|
if (s === void 0) continue;
|
|
1471
1679
|
let c = await r(s, "utf8").catch(() => null);
|
|
1472
1680
|
if (c === null) continue;
|
|
@@ -1483,8 +1691,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1483
1691
|
} catch {
|
|
1484
1692
|
return null;
|
|
1485
1693
|
}
|
|
1486
|
-
},
|
|
1487
|
-
let t = await
|
|
1694
|
+
}, Tn = (e) => e.endsWith(".ts") && !e.endsWith(".server.ts") ? `${e.slice(0, -3)}.server.ts` : void 0, En = async (e) => {
|
|
1695
|
+
let t = await wn(e);
|
|
1488
1696
|
if (t === null) return;
|
|
1489
1697
|
let n = t.findings;
|
|
1490
1698
|
if (n.length > 0) {
|
|
@@ -1497,21 +1705,21 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1497
1705
|
for (let e of t.unresolved) console.log(` ${e.procedure}: ${e.relations.map((e) => `\`${e}\``).join(", ")} resolved against no table`);
|
|
1498
1706
|
console.log(" declare the base table in `source:` and re-run — relations under it are unchecked until then.");
|
|
1499
1707
|
}
|
|
1500
|
-
},
|
|
1708
|
+
}, U = async (e) => {
|
|
1501
1709
|
try {
|
|
1502
|
-
let { discovered: t } = await
|
|
1503
|
-
return ve(
|
|
1710
|
+
let { discovered: t } = await H(e);
|
|
1711
|
+
return ve(Cn(t.tables));
|
|
1504
1712
|
} catch {
|
|
1505
1713
|
return null;
|
|
1506
1714
|
}
|
|
1507
|
-
},
|
|
1508
|
-
let t = await
|
|
1715
|
+
}, Dn = async (e) => {
|
|
1716
|
+
let t = await U(e);
|
|
1509
1717
|
if (t === null || t.tablesChecked === 0) return;
|
|
1510
1718
|
let n = be(t);
|
|
1511
1719
|
n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
|
|
1512
|
-
},
|
|
1720
|
+
}, W = async (e) => {
|
|
1513
1721
|
try {
|
|
1514
|
-
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await
|
|
1722
|
+
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await H(e);
|
|
1515
1723
|
return we(n.events.map((e) => {
|
|
1516
1724
|
let n = e.descriptor, r = t(n.key);
|
|
1517
1725
|
return {
|
|
@@ -1524,19 +1732,19 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1524
1732
|
} catch {
|
|
1525
1733
|
return null;
|
|
1526
1734
|
}
|
|
1527
|
-
},
|
|
1528
|
-
let t = await
|
|
1735
|
+
}, On = async (e) => {
|
|
1736
|
+
let t = await W(e);
|
|
1529
1737
|
if (t === null || t.eventsChecked === 0) return;
|
|
1530
1738
|
let n = Te(t);
|
|
1531
1739
|
n !== null && (console.log(""), console.log(`• ${n}`));
|
|
1532
1740
|
let r = Ee(t);
|
|
1533
1741
|
r !== null && (console.log(""), console.log(`⚠️ ${r}`));
|
|
1534
|
-
},
|
|
1742
|
+
}, G = (e) => {
|
|
1535
1743
|
let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
|
|
1536
1744
|
return r === void 0 ? n : `${r}: ${n}`;
|
|
1537
|
-
},
|
|
1745
|
+
}, K = async (e) => {
|
|
1538
1746
|
try {
|
|
1539
|
-
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await
|
|
1747
|
+
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await H(e)).discovered.serverOnlyLeaks, r = t(n);
|
|
1540
1748
|
return {
|
|
1541
1749
|
status: {
|
|
1542
1750
|
checked: !0,
|
|
@@ -1547,12 +1755,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1547
1755
|
} catch (e) {
|
|
1548
1756
|
return { status: {
|
|
1549
1757
|
checked: !1,
|
|
1550
|
-
reason:
|
|
1758
|
+
reason: G(e)
|
|
1551
1759
|
} };
|
|
1552
1760
|
}
|
|
1553
|
-
},
|
|
1761
|
+
}, q = async (r) => {
|
|
1554
1762
|
try {
|
|
1555
|
-
let { loadApiConfig: i } = await import("./dev-
|
|
1763
|
+
let { loadApiConfig: i } = await import("./dev-Bl9HqtV7.js"), { discovered: a } = await H(r), o = n(await i(r)), s = {
|
|
1556
1764
|
queries: a.queries,
|
|
1557
1765
|
mutations: a.mutations,
|
|
1558
1766
|
actions: a.actions,
|
|
@@ -1562,32 +1770,32 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1562
1770
|
return {
|
|
1563
1771
|
checked: !0,
|
|
1564
1772
|
defaultDeny: o,
|
|
1565
|
-
undecided:
|
|
1773
|
+
undecided: t(s),
|
|
1566
1774
|
openAccess: e(s)
|
|
1567
1775
|
};
|
|
1568
1776
|
} catch (e) {
|
|
1569
1777
|
return {
|
|
1570
1778
|
checked: !1,
|
|
1571
|
-
reason:
|
|
1779
|
+
reason: G(e),
|
|
1572
1780
|
defaultDeny: !0,
|
|
1573
1781
|
undecided: [],
|
|
1574
1782
|
openAccess: []
|
|
1575
1783
|
};
|
|
1576
1784
|
}
|
|
1577
|
-
},
|
|
1578
|
-
let t = await
|
|
1785
|
+
}, kn = async (e) => {
|
|
1786
|
+
let t = await q(e);
|
|
1579
1787
|
if (!t.checked) return console.log(""), console.log("• access decisions: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
|
|
1580
1788
|
console.log(""), console.log(`access decisions · security.defaultDeny ${t.defaultDeny ? "ON" : "OFF"}`), console.log(` ✗ no access decision ${String(t.undecided.length).padStart(4)}`), console.log(` ✓ openAccess, declared on purpose ${String(t.openAccess.length).padStart(4)}`);
|
|
1581
1789
|
for (let e of t.openAccess) console.log(` ${e.tag} — ${e.openAccess ?? ""}`);
|
|
1582
1790
|
if (t.undecided.length === 0) return !1;
|
|
1583
1791
|
console.log("");
|
|
1584
1792
|
for (let e of t.undecided) console.log(` ✗ ${e.tag} (${e.kind})${e.file === void 0 ? "" : `\n ${e.file}`}`);
|
|
1585
|
-
return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"),
|
|
1586
|
-
},
|
|
1793
|
+
return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), An(), !0) : (console.log(" This app sets `security: { defaultDeny: false }`, so these BOOT — and each one is"), console.log(" callable by any authenticated session. Advisory here for that reason only."), An(), !1);
|
|
1794
|
+
}, An = () => {
|
|
1587
1795
|
console.log(" `guards: [{ scope: '…' }]` authority the SUBJECT holds globally"), console.log(" `guards: [{ action, resourceType, resource }]` authority per ROW / per resource"), console.log(" — backed by `defineResourcePolicy` + a tuple source you register. If your"), console.log(" relationships already live in your own tables (a `teamMembers` row), register"), console.log(" a tuple source over them; nothing is copied. Fail-closed without a resolver."), console.log(" Docs: Authentication → Authorization."), console.log(" `openAccess: '<why anyone may call it>'` deliberately unauthenticated");
|
|
1588
|
-
},
|
|
1796
|
+
}, J = async (e) => {
|
|
1589
1797
|
try {
|
|
1590
|
-
let { files: t } = await
|
|
1798
|
+
let { files: t } = await H(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
|
|
1591
1799
|
if (a.length === 0) return {
|
|
1592
1800
|
checked: !0,
|
|
1593
1801
|
unverified: [],
|
|
@@ -1614,14 +1822,14 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1614
1822
|
} catch (e) {
|
|
1615
1823
|
return {
|
|
1616
1824
|
checked: !1,
|
|
1617
|
-
reason:
|
|
1825
|
+
reason: G(e),
|
|
1618
1826
|
unverified: [],
|
|
1619
1827
|
open: [],
|
|
1620
1828
|
verified: 0
|
|
1621
1829
|
};
|
|
1622
1830
|
}
|
|
1623
|
-
},
|
|
1624
|
-
let t = await
|
|
1831
|
+
}, jn = async (e) => {
|
|
1832
|
+
let t = await J(e);
|
|
1625
1833
|
if (!t.checked) return console.log(""), console.log("• webhook verification: NOT CHECKED — the app's webhooks could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
|
|
1626
1834
|
if (t.verified + t.open.length + t.unverified.length === 0) return !1;
|
|
1627
1835
|
console.log(""), console.log("incoming webhook verification"), console.log(` ✓ signature / provider verified ${String(t.verified).padStart(4)}`), console.log(` ! deliberately public (none) ${String(t.open.length).padStart(4)}`), console.log(` ✗ nothing verifies the caller ${String(t.unverified.length).padStart(4)}`);
|
|
@@ -1630,13 +1838,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1630
1838
|
console.log("");
|
|
1631
1839
|
for (let e of t.unverified) console.log(` ✗ ${e.id}\n ${e.file}`);
|
|
1632
1840
|
return console.log(""), console.log(" These are public POSTs that run your application code, and `voltro dev` /"), console.log(" `voltro serve` REFUSE to mount them. Declare one of: `provider:` (a preset),"), console.log(" `signature:` (your own HMAC scheme), `verification: 'provider'` (your handler"), console.log(" verifies), or `verification: 'none'` (deliberately public)."), !0;
|
|
1633
|
-
},
|
|
1634
|
-
let { status: t, message: n } = await
|
|
1841
|
+
}, Mn = async (e) => {
|
|
1842
|
+
let { status: t, message: n } = await K(e);
|
|
1635
1843
|
return t.checked ? n !== void 0 && (console.log(""), console.log(`✗ [serverOnly] ${n}`), !0) : (console.log(""), console.log("• serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), console.log(" This audit is the automated check that a hash or a token cannot reach the wire, so"), console.log(" an app that leans on `.serverOnly()` should treat a persistent skip as a failure."), console.log(" `voltro doctor --json` reports it as `serverOnly.checked: false` for a CI gate."), !1);
|
|
1636
|
-
},
|
|
1637
|
-
hasWorkflows: async () => (await
|
|
1844
|
+
}, Nn = async (e) => ft({
|
|
1845
|
+
hasWorkflows: async () => (await H(e)).discovered.workflows.length > 0,
|
|
1638
1846
|
openStore: async () => {
|
|
1639
|
-
let { loadApiConfig: t, buildStore: n } = await import("./dev-
|
|
1847
|
+
let { loadApiConfig: t, buildStore: n } = await import("./dev-Bl9HqtV7.js"), r = await t(e);
|
|
1640
1848
|
if (r === null) throw Error("no app.config.ts in this directory");
|
|
1641
1849
|
let i = await n(r, void 0);
|
|
1642
1850
|
return {
|
|
@@ -1650,13 +1858,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1650
1858
|
let { sweepStalledRuns: t } = await import("@voltro/workflow");
|
|
1651
1859
|
return t({ store: e });
|
|
1652
1860
|
}
|
|
1653
|
-
}),
|
|
1654
|
-
let t =
|
|
1861
|
+
}), Pn = async (e) => {
|
|
1862
|
+
let t = mt(await Nn(e));
|
|
1655
1863
|
t !== null && (console.log(""), console.log(t));
|
|
1656
|
-
},
|
|
1864
|
+
}, Fn = async (e) => {
|
|
1657
1865
|
let t;
|
|
1658
1866
|
try {
|
|
1659
|
-
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await
|
|
1867
|
+
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await H(e);
|
|
1660
1868
|
t = ke(r, i.workflows.map((e) => {
|
|
1661
1869
|
let t = e.definition, r = t.payloadSchema === void 0 ? {
|
|
1662
1870
|
required: [],
|
|
@@ -1676,18 +1884,18 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1676
1884
|
console.log("");
|
|
1677
1885
|
let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
|
|
1678
1886
|
console.log(`${r} ${n}`);
|
|
1679
|
-
},
|
|
1887
|
+
}, Y = "voltro-authz-allowlist.txt", In = (e, t = (e) => {
|
|
1680
1888
|
try {
|
|
1681
1889
|
return C(e, "utf8");
|
|
1682
1890
|
} catch {
|
|
1683
1891
|
return "";
|
|
1684
1892
|
}
|
|
1685
|
-
}) => /* @__PURE__ */ new Set([...
|
|
1893
|
+
}) => /* @__PURE__ */ new Set([...Xe, ...Ze(L(e).map(t))]), X = (e, t) => {
|
|
1686
1894
|
let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1687
1895
|
return new RegExp(String.raw`(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=\s*define[A-Za-z]*Event\s*\(\s*\{[^}]*?name\s*:\s*['"\`]${n}['"\`]`, "s").exec(e)?.[1];
|
|
1688
1896
|
}, Z = async (e) => {
|
|
1689
1897
|
try {
|
|
1690
|
-
let { discovered: t } = await
|
|
1898
|
+
let { discovered: t } = await H(e), n = t.events.map((e) => ({
|
|
1691
1899
|
name: e.descriptor.name,
|
|
1692
1900
|
file: e.file
|
|
1693
1901
|
})).filter((e) => typeof e.name == "string");
|
|
@@ -1698,25 +1906,25 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1698
1906
|
} catch {
|
|
1699
1907
|
return "";
|
|
1700
1908
|
}
|
|
1701
|
-
}, a =
|
|
1909
|
+
}, a = L(e).filter((e) => !r.has(e)).map(i).join("\n");
|
|
1702
1910
|
return n.filter(({ name: e, file: t }) => {
|
|
1703
1911
|
if (a.includes(e)) return !1;
|
|
1704
|
-
let n =
|
|
1912
|
+
let n = X(i(t), e);
|
|
1705
1913
|
return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
|
|
1706
1914
|
}).map((e) => e.name).sort();
|
|
1707
1915
|
} catch {
|
|
1708
1916
|
return [];
|
|
1709
1917
|
}
|
|
1710
|
-
},
|
|
1918
|
+
}, Ln = async (e) => {
|
|
1711
1919
|
let t = await Z(e);
|
|
1712
1920
|
if (t.length !== 0) {
|
|
1713
1921
|
console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
|
|
1714
1922
|
for (let e of t) console.log(` ${e}`);
|
|
1715
1923
|
console.log(""), console.log(" An event you declare is an event partners can subscribe to. One that"), console.log(" never fires is indistinguishable, from their side, from a broken"), console.log(" endpoint of their own — so they debug your contract instead of"), console.log(" reading it."), console.log(" ADVISORY: this looks for the event name AND the identifier its"), console.log(" declaration binds it to, so `emit(ctx, apiKeyCreated, …)` counts. An"), console.log(" emit through an ASSEMBLED name still reads as missing here.");
|
|
1716
1924
|
}
|
|
1717
|
-
},
|
|
1925
|
+
}, Rn = async (e) => {
|
|
1718
1926
|
try {
|
|
1719
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
1927
|
+
let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js"), n = (await t(e))?.doctor?.scopeVocabulary;
|
|
1720
1928
|
if (typeof n != "string") return [];
|
|
1721
1929
|
let [r, i] = n.split("#");
|
|
1722
1930
|
if (r === void 0 || i === void 0) return [];
|
|
@@ -1733,10 +1941,10 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1733
1941
|
findings: []
|
|
1734
1942
|
});
|
|
1735
1943
|
try {
|
|
1736
|
-
let { offlineManifest: n, toInput: r } = await import("./checkCommand-
|
|
1944
|
+
let { offlineManifest: n, toInput: r } = await import("./checkCommand-BISqx1OJ.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CFmFKHb2.js"), o = await n(e), s = r(o.manifest, o.tables), c = await Rn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
|
|
1737
1945
|
if (l.length === 0) {
|
|
1738
1946
|
try {
|
|
1739
|
-
let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-
|
|
1947
|
+
let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-B96WCNJA.js");
|
|
1740
1948
|
await t(e);
|
|
1741
1949
|
} catch {
|
|
1742
1950
|
return t("app-not-importable");
|
|
@@ -1760,7 +1968,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1760
1968
|
} catch {
|
|
1761
1969
|
return t("app-not-importable");
|
|
1762
1970
|
}
|
|
1763
|
-
},
|
|
1971
|
+
}, zn = async (e) => {
|
|
1764
1972
|
let { findings: t, evaluated: n, notEvaluated: r } = await Q(e);
|
|
1765
1973
|
if (!n) {
|
|
1766
1974
|
if (console.log(""), r === "app-not-importable") {
|
|
@@ -1775,7 +1983,43 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1775
1983
|
for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
|
|
1776
1984
|
console.log(""), console.log(" Each of these is uncallable by every user in every role — not"), console.log(" misconfigured, DEAD — and nothing fails at boot to say so. The"), console.log(" usual cause is a typo or a rename that moved one side only."), console.log(` ${t[0].fix}`);
|
|
1777
1985
|
}
|
|
1778
|
-
},
|
|
1986
|
+
}, Bn = (e) => {
|
|
1987
|
+
let t = v(e, "src");
|
|
1988
|
+
if (!S(t)) return;
|
|
1989
|
+
let n = [], r = (t, i = 0) => {
|
|
1990
|
+
if (i > 12 || I.test(t)) return;
|
|
1991
|
+
let a;
|
|
1992
|
+
try {
|
|
1993
|
+
a = w(t);
|
|
1994
|
+
} catch {
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
for (let o of a) {
|
|
1998
|
+
let a = v(t, o), s;
|
|
1999
|
+
try {
|
|
2000
|
+
s = T(a);
|
|
2001
|
+
} catch {
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
if (s.isDirectory()) {
|
|
2005
|
+
r(a, i + 1);
|
|
2006
|
+
continue;
|
|
2007
|
+
}
|
|
2008
|
+
if (!(!/\.(tsx?|css)$/.test(o) || /\.(test|spec)\./.test(o))) try {
|
|
2009
|
+
n.push({
|
|
2010
|
+
path: y(e, a).split("\\").join("/"),
|
|
2011
|
+
source: C(a, "utf8")
|
|
2012
|
+
});
|
|
2013
|
+
} catch {}
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
r(t);
|
|
2017
|
+
let i = yt(_t(n));
|
|
2018
|
+
if (i.length !== 0) {
|
|
2019
|
+
console.log("");
|
|
2020
|
+
for (let e of i) console.log(e);
|
|
2021
|
+
}
|
|
2022
|
+
}, Vn = async (e) => {
|
|
1779
2023
|
let t = ["src/locales", "locales"], n, r = [];
|
|
1780
2024
|
for (let i of t) {
|
|
1781
2025
|
let t = v(e, i);
|
|
@@ -1787,20 +2031,20 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1787
2031
|
if (n === void 0 || r.length === 0) return;
|
|
1788
2032
|
let i = [];
|
|
1789
2033
|
try {
|
|
1790
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2034
|
+
let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js");
|
|
1791
2035
|
i = (await t(e))?.locales ?? [];
|
|
1792
2036
|
} catch {
|
|
1793
2037
|
return;
|
|
1794
2038
|
}
|
|
1795
|
-
let a =
|
|
2039
|
+
let a = We({
|
|
1796
2040
|
catalogCodes: r,
|
|
1797
2041
|
declaredLocales: i
|
|
1798
2042
|
});
|
|
1799
2043
|
if (a !== void 0) {
|
|
1800
2044
|
console.log("");
|
|
1801
|
-
for (let e of
|
|
2045
|
+
for (let e of Ge(a, n)) console.log(e);
|
|
1802
2046
|
}
|
|
1803
|
-
},
|
|
2047
|
+
}, Hn = async (e) => {
|
|
1804
2048
|
let t = v(e, "src");
|
|
1805
2049
|
if (!S(t)) return;
|
|
1806
2050
|
let n = !1, r = (e) => {
|
|
@@ -1819,9 +2063,9 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1819
2063
|
return;
|
|
1820
2064
|
}
|
|
1821
2065
|
a()?.reason === "binary-missing" ? (console.log("image pipeline: sharp is INSTALLED but its platform binary is missing —"), console.log(" an install that omitted optional dependencies drops the @img/* prebuilds."), console.log(" Fix: reinstall without omitting optional deps, or `pnpm add -D sharp` in this app.")) : (console.log("image pipeline: sharp is NOT installed — ?image imports serve originals (no srcSet, no blur)."), console.log(" Fix: `pnpm add -D sharp` in this app (it is an optional dependency of @voltro/cli;"), console.log(" your installer skipped optional dependencies)."));
|
|
1822
|
-
},
|
|
2066
|
+
}, Un = async (e) => {
|
|
1823
2067
|
try {
|
|
1824
|
-
let { loadConfig: t } = await import("./webDev-
|
|
2068
|
+
let { loadConfig: t } = await import("./webDev-B6ZMX42w.js"), n = (await t(e, { preferSource: !0 }))?.fonts;
|
|
1825
2069
|
if (n === void 0 || n.length === 0) return;
|
|
1826
2070
|
let { loadFontkit: r, loadSubsetFont: i } = await import("./fontPipeline-LxIHa1vo.js"), a = [];
|
|
1827
2071
|
if (await r() === null && a.push("fontkit (size-adjust fallback metrics — the CLS guard — are skipped)"), await i() === null && a.push("subset-font (declared `subsets:` ship the full files)"), console.log(""), a.length === 0) {
|
|
@@ -1831,25 +2075,25 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1831
2075
|
for (let e of a) console.log(`font pipeline: ${e}`);
|
|
1832
2076
|
console.log(" Fix: reinstall without omitting optional deps, or `pnpm add -D fontkit subset-font` in this app.");
|
|
1833
2077
|
} catch {}
|
|
1834
|
-
},
|
|
2078
|
+
}, Wn = async (e) => {
|
|
1835
2079
|
let t = v(e, "src", "pages");
|
|
1836
2080
|
if (!S(t)) return !1;
|
|
1837
2081
|
let n = [];
|
|
1838
2082
|
try {
|
|
1839
|
-
let { walkPagesTree: e } = await import("./webDev-
|
|
2083
|
+
let { walkPagesTree: e } = await import("./webDev-B6ZMX42w.js");
|
|
1840
2084
|
n = (await e(t)).pages.map((e) => e.pattern);
|
|
1841
2085
|
} catch {
|
|
1842
2086
|
return !1;
|
|
1843
2087
|
}
|
|
1844
|
-
let r =
|
|
2088
|
+
let r = qe(e, n);
|
|
1845
2089
|
if (r === void 0) return !1;
|
|
1846
2090
|
console.log("");
|
|
1847
|
-
for (let e of
|
|
2091
|
+
for (let e of Je(r)) console.log(e);
|
|
1848
2092
|
return r.problems.length > 0;
|
|
1849
|
-
},
|
|
1850
|
-
let { discovered: t } = await
|
|
2093
|
+
}, Gn = async (e) => {
|
|
2094
|
+
let { discovered: t } = await H(e), n = [];
|
|
1851
2095
|
try {
|
|
1852
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2096
|
+
let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js");
|
|
1853
2097
|
n = ((await t(e))?.plugins ?? []).map((e) => {
|
|
1854
2098
|
let t = (e.extendSchema?.tables ?? []).map((e) => {
|
|
1855
2099
|
let t = e;
|
|
@@ -1857,13 +2101,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1857
2101
|
tableName: typeof t.tableName == "string" ? t.tableName : "",
|
|
1858
2102
|
renamedFrom: typeof t.renamedFrom == "string" ? t.renamedFrom : void 0
|
|
1859
2103
|
};
|
|
1860
|
-
}).filter((e) => e.tableName !== ""), n = (e.rpcClientDescriptors ?? []).map((t) => typeof t.tag == "string" ?
|
|
2104
|
+
}).filter((e) => e.tableName !== ""), n = (e.rpcClientDescriptors ?? []).map((t) => typeof t.tag == "string" ? p(e, t.tag) : "").filter((e) => e !== ""), r = e.baseName ?? e.name;
|
|
1861
2105
|
return {
|
|
1862
2106
|
plugin: e.name,
|
|
1863
2107
|
tables: t,
|
|
1864
2108
|
rpcTags: n,
|
|
1865
|
-
canDisableTables:
|
|
1866
|
-
canAlias:
|
|
2109
|
+
canDisableTables: Kn.has(r),
|
|
2110
|
+
canAlias: qn.has(r)
|
|
1867
2111
|
};
|
|
1868
2112
|
});
|
|
1869
2113
|
} catch {
|
|
@@ -1875,18 +2119,18 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1875
2119
|
...t.mutations,
|
|
1876
2120
|
...t.actions,
|
|
1877
2121
|
...t.streams ?? []
|
|
1878
|
-
].map((e) => String(e.descriptor.name)), i =
|
|
2122
|
+
].map((e) => String(e.descriptor.name)), i = ut(n, {
|
|
1879
2123
|
tables: t.tables.map((e) => e.tableName),
|
|
1880
2124
|
rpcTags: r
|
|
1881
2125
|
});
|
|
1882
2126
|
console.log();
|
|
1883
|
-
for (let e of
|
|
1884
|
-
},
|
|
2127
|
+
for (let e of dt(n, i)) console.log(e);
|
|
2128
|
+
}, Kn = /* @__PURE__ */ new Set([
|
|
1885
2129
|
"@voltro/plugin-rbac",
|
|
1886
2130
|
"@voltro/plugin-audit",
|
|
1887
2131
|
"@voltro/plugin-notifications",
|
|
1888
2132
|
"aiFlows"
|
|
1889
|
-
]),
|
|
2133
|
+
]), qn = /* @__PURE__ */ new Set([
|
|
1890
2134
|
"aiFlows",
|
|
1891
2135
|
"@voltro/plugin-audit",
|
|
1892
2136
|
"@voltro/plugin-billing",
|
|
@@ -1904,8 +2148,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1904
2148
|
"@voltro/plugin-sso-saml",
|
|
1905
2149
|
"@voltro/plugin-storage",
|
|
1906
2150
|
"@voltro/plugin-row-history"
|
|
1907
|
-
]), $ = 20,
|
|
1908
|
-
let t = await
|
|
2151
|
+
]), $ = 20, Jn = async (e) => {
|
|
2152
|
+
let t = await Yn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
|
|
1909
2153
|
return {
|
|
1910
2154
|
executors: t.executorCount,
|
|
1911
2155
|
guardVocabulary: [...t.vocabulary].sort(),
|
|
@@ -1917,15 +2161,15 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1917
2161
|
},
|
|
1918
2162
|
unchecked: t.result.unchecked
|
|
1919
2163
|
};
|
|
1920
|
-
},
|
|
1921
|
-
let { discovered: t } = await
|
|
2164
|
+
}, Yn = async (e) => {
|
|
2165
|
+
let { discovered: t } = await H(e), n = /* @__PURE__ */ new Map(), r = (e) => {
|
|
1922
2166
|
if (!n.has(e)) try {
|
|
1923
2167
|
n.set(e, C(e, "utf8"));
|
|
1924
2168
|
} catch {
|
|
1925
2169
|
n.set(e, "");
|
|
1926
2170
|
}
|
|
1927
2171
|
return n.get(e);
|
|
1928
|
-
}, i =
|
|
2172
|
+
}, i = In(e, r), a = /* @__PURE__ */ new Set();
|
|
1929
2173
|
for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
|
|
1930
2174
|
let e = t.references?.();
|
|
1931
2175
|
e && a.add(e.tableName);
|
|
@@ -1957,12 +2201,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1957
2201
|
op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
|
|
1958
2202
|
...a?.table === void 0 ? {} : { table: a.table }
|
|
1959
2203
|
};
|
|
1960
|
-
})), s = v(e,
|
|
1961
|
-
for (let t of
|
|
2204
|
+
})), s = v(e, Y), c = S(s) ? j(C(s, "utf8")) : void 0, l = !1;
|
|
2205
|
+
for (let t of L(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
|
|
1962
2206
|
l = !0;
|
|
1963
2207
|
break;
|
|
1964
2208
|
}
|
|
1965
|
-
let u =
|
|
2209
|
+
let u = rt({
|
|
1966
2210
|
executors: o,
|
|
1967
2211
|
guardVocabulary: i,
|
|
1968
2212
|
tenantScopedTables: t.tenantScopedTables,
|
|
@@ -1979,23 +2223,23 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1979
2223
|
executorCount: o.length,
|
|
1980
2224
|
allowPath: s
|
|
1981
2225
|
};
|
|
1982
|
-
},
|
|
1983
|
-
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await
|
|
2226
|
+
}, Xn = async (e, t) => {
|
|
2227
|
+
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Yn(e), l = n.counts;
|
|
1984
2228
|
console.log(`\nauthz scan · ${o} executor(s)`);
|
|
1985
|
-
let u = [...r].filter((e) => !
|
|
2229
|
+
let u = [...r].filter((e) => !Xe.has(e)).sort();
|
|
1986
2230
|
console.log(u.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${u.length} from your source (${u.slice(0, 6).join(", ")}${u.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(l.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(l.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(l.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(l.vocabularyGuard).padStart(4)}`);
|
|
1987
2231
|
let d = new Set(a.map((e) => e.tag)), f = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !d.has(e.tag)).length, p = l.allowlisted - f;
|
|
1988
|
-
if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${
|
|
1989
|
-
let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r =
|
|
1990
|
-
return oe(s,
|
|
2232
|
+
if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Y})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
|
|
2233
|
+
let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = st(e, t);
|
|
2234
|
+
return oe(s, ot(e, t), "utf8"), console.log(`\n${Y}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
|
|
1991
2235
|
}
|
|
1992
2236
|
if (n.unchecked.length === 0) return !1;
|
|
1993
2237
|
console.log("");
|
|
1994
2238
|
for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
|
|
1995
|
-
return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${
|
|
1996
|
-
},
|
|
2239
|
+
return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Y} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Y}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
|
|
2240
|
+
}, Zn = async (e) => {
|
|
1997
2241
|
try {
|
|
1998
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2242
|
+
let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
|
|
1999
2243
|
return r == null ? null : n({
|
|
2000
2244
|
...r.schema ? { declared: r.schema } : {},
|
|
2001
2245
|
...r.store === void 0 ? {} : { store: r.store },
|
|
@@ -2004,42 +2248,42 @@ var le = /* @__PURE__ */ new Set([
|
|
|
2004
2248
|
} catch {
|
|
2005
2249
|
return null;
|
|
2006
2250
|
}
|
|
2007
|
-
},
|
|
2008
|
-
let t = await
|
|
2251
|
+
}, Qn = async (e) => {
|
|
2252
|
+
let t = await Zn(e);
|
|
2009
2253
|
if (t !== null) {
|
|
2010
2254
|
console.log(""), console.log("• framework tables whose presence is DECIDED rather than implied by your source tree:");
|
|
2011
2255
|
for (let e of t.text.split("\n")) console.log(` ${e}`);
|
|
2012
2256
|
}
|
|
2013
|
-
},
|
|
2014
|
-
if (e.includes("--help") || e.includes("-h")) return console.log(
|
|
2015
|
-
let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")),
|
|
2257
|
+
}, $n = async (e) => {
|
|
2258
|
+
if (e.includes("--help") || e.includes("-h")) return console.log(bn), 0;
|
|
2259
|
+
let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), a = b(n ?? process.cwd());
|
|
2016
2260
|
if (g(e)) return ee(), h({
|
|
2017
|
-
...
|
|
2018
|
-
spaCandidates: await
|
|
2019
|
-
pageConvention:
|
|
2020
|
-
fileTaxonomy: re(
|
|
2021
|
-
junctionFks: (await
|
|
2022
|
-
eventDelivery: await
|
|
2023
|
-
duplicateInstances:
|
|
2024
|
-
versionSkew:
|
|
2025
|
-
serverOnly: (await
|
|
2026
|
-
accessDecisions: await
|
|
2027
|
-
webhookVerification: await
|
|
2028
|
-
authz: await
|
|
2029
|
-
ungrantedScopes: await Q(
|
|
2030
|
-
neverEmittedEvents: await Z(
|
|
2031
|
-
unimportedDeps:
|
|
2032
|
-
stalledRuns: await
|
|
2033
|
-
decidedSchema: await
|
|
2261
|
+
...R(a),
|
|
2262
|
+
spaCandidates: await z(a),
|
|
2263
|
+
pageConvention: B(a),
|
|
2264
|
+
fileTaxonomy: re(a).findings,
|
|
2265
|
+
junctionFks: (await U(a))?.issues ?? [],
|
|
2266
|
+
eventDelivery: await W(a),
|
|
2267
|
+
duplicateInstances: i(a),
|
|
2268
|
+
versionSkew: o(a) ?? null,
|
|
2269
|
+
serverOnly: (await K(a)).status,
|
|
2270
|
+
accessDecisions: await q(a),
|
|
2271
|
+
webhookVerification: await J(a),
|
|
2272
|
+
authz: await Jn(a),
|
|
2273
|
+
ungrantedScopes: await Q(a),
|
|
2274
|
+
neverEmittedEvents: await Z(a),
|
|
2275
|
+
unimportedDeps: He(a),
|
|
2276
|
+
stalledRuns: await Nn(a),
|
|
2277
|
+
decidedSchema: await Zn(a)
|
|
2034
2278
|
}), 0;
|
|
2035
|
-
let
|
|
2036
|
-
|
|
2037
|
-
for (let e of
|
|
2038
|
-
await
|
|
2039
|
-
let
|
|
2279
|
+
let s = Ct(a, t);
|
|
2280
|
+
sn(a), on(a), yn(a), ne(a), await pn(a), await Fn(a), await Pn(a), await Sn(a), await Dn(a), await En(a), await Qn(a), await On(a), await Gn(a), await zn(a), await Ln(a), console.log("");
|
|
2281
|
+
for (let e of Ue(He(a))) console.log(e);
|
|
2282
|
+
await Vn(a), Bn(a), await Hn(a), await Un(a);
|
|
2283
|
+
let c = await Mn(a), l = await Xn(a, e.includes("--write-authz-allowlist")), d = await kn(a), f = await jn(a), p = await Wn(a), m = r(o(a));
|
|
2040
2284
|
m !== void 0 && (console.log(""), console.log(m));
|
|
2041
|
-
let _ = i(
|
|
2042
|
-
return _ !== void 0 && console.log(`\n${_}`),
|
|
2285
|
+
let _ = u(i(a));
|
|
2286
|
+
return _ !== void 0 && console.log(`\n${_}`), c || l || d || p || f ? Math.max(s, 1) : s;
|
|
2043
2287
|
};
|
|
2044
2288
|
//#endregion
|
|
2045
|
-
export {
|
|
2289
|
+
export { z as C, J as D, Q as E, B as S, Rt as T, yn as _, Jn as a, Ct as b, W as c, Qt as d, xt as f, sn as g, on as h, q as i, In as l, wn as m, St as n, Z as o, U as p, cn as r, X as s, bn as t, R as u, pn as v, K as w, $t as x, $n as y };
|