@voltro/cli 0.29.0 → 0.30.1
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 +448 -0
- package/THIRD-PARTY-NOTICES.md +2 -2
- package/dist/apiBuild-Ca6Sq5jy.js +2 -0
- package/dist/{apiBuild-BESZbTjA.js → apiBuild-DmkzRyv7.js} +2 -2
- package/dist/bin.js +3 -3
- package/dist/{checkCommand-BPAtyWs8.js → checkCommand-C4konUy9.js} +435 -304
- package/dist/checkCommand-DHD0aBeR.js +2 -0
- package/dist/{commands-82BDEktg.js → commands-BkJft9K2.js} +2413 -1814
- package/dist/{dbCommand-bCBbk6tz.js → dbCommand-qn__kLEt.js} +344 -265
- package/dist/dbCommand-wfGi3FgG.js +2 -0
- package/dist/{dev-Cg4BKLmi.js → dev-C_NPcLDp.js} +2716 -1782
- package/dist/{dev-DeEJj5T4.js → dev-DXhhZ7-f.js} +1 -1
- package/dist/frameworkTableAssembly-Bn_c4Mnl.js +2 -0
- package/dist/{frameworkTableAssembly-4Db40V23.js → frameworkTableAssembly-Cpgn2e88.js} +155 -141
- package/dist/index.d.ts +7 -2
- package/dist/index.js +1 -1
- package/dist/{inspect-BMvQpwqy.js → inspect-BUUjt773.js} +150 -14
- package/dist/inspect-mmBuRXmy.js +2 -0
- package/dist/{inspectMetrics-DHYygE4s.js → inspectMetrics-CkB2J0Zn.js} +566 -512
- package/dist/{manifestBuild-C4R1EdHn.js → manifestBuild-BLrVuSlM.js} +1 -1
- package/dist/manifestBuild-Dj8Jjoto.js +2 -0
- package/dist/{sdkgen-CW5NbYF0.js → sdkgen-wsWLrq4l.js} +1 -1
- package/dist/serveCommand-HtI4X5kj.js +1577 -0
- package/dist/serveEntry.js +2 -2
- package/dist/{start-CsCIaW4-.js → start-egkbpugc.js} +177 -175
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.core.md +50 -0
- package/templates/AGENTS.md +53 -2
- package/templates/agent-docs/_index.md +3 -2
- package/templates/agent-docs/_manifest.json +16 -4
- package/templates/agent-docs/ai.md +56 -0
- package/templates/agent-docs/cli.md +46 -0
- package/templates/agent-docs/internationalization.md +8 -7
- package/templates/agent-docs/local-first-mobile.md +18 -6
- package/templates/agent-docs/routing.md +15 -1
- package/templates/agent-docs/security.md +17 -0
- package/templates/agent-docs/templates/apibackends.md +209 -0
- package/templates/agent-docs/templates/appshells.md +144 -1
- package/templates/agent-docs/templates/mobile.md +60 -0
- package/templates/agent-docs/templates/overview.md +17 -7
- package/templates/agent-docs/whats-new.md +36 -243
- package/templates/agent-docs/workflows.md +367 -0
- package/templates/apps/api-ai/package.json +7 -7
- 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 +10 -10
- package/templates/apps/api-collab/README.md +70 -0
- package/templates/apps/api-collab/app.config.ts +28 -0
- package/templates/apps/api-collab/database/schema.ts +63 -0
- package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +19 -0
- package/templates/apps/api-collab/mutations/documents.create.mutation.ts +36 -0
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +20 -0
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +30 -0
- package/templates/apps/api-collab/package.json +30 -0
- package/templates/apps/api-collab/queries/documents.query.server.ts +16 -0
- package/templates/apps/api-collab/queries/documents.query.ts +23 -0
- package/templates/apps/api-collab/template.json +6 -0
- package/templates/apps/api-collab/tests/documents.setBody.test.ts +89 -0
- package/templates/apps/api-collab/tsconfig.json +5 -0
- 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-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-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/app.config.ts +1 -1
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-admin/src/locales/index.ts +1 -1
- package/templates/apps/frontend-app/app.config.ts +1 -1
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-app/src/locales/index.ts +1 -1
- package/templates/apps/frontend-app/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-app/src/pages/page.tsx +1 -1
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-auth/src/locales/index.ts +1 -1
- package/templates/apps/frontend-blank/app.config.ts +1 -1
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-blank/src/locales/index.ts +1 -1
- package/templates/apps/frontend-blank/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-blank/src/pages/page.tsx +1 -1
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-cms/src/locales/index.ts +1 -1
- package/templates/apps/frontend-collab/README.md +80 -0
- package/templates/apps/frontend-collab/app.config.ts +38 -0
- package/templates/apps/frontend-collab/package.json +33 -0
- package/templates/apps/frontend-collab/src/locales/de.ts +29 -0
- package/templates/apps/frontend-collab/src/locales/en.ts +31 -0
- package/templates/apps/frontend-collab/src/locales/index.ts +15 -0
- package/templates/apps/frontend-collab/src/pages/layout.tsx +39 -0
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +150 -0
- package/templates/apps/frontend-collab/src/pages/page.tsx +186 -0
- package/templates/apps/frontend-collab/template.json +6 -0
- package/templates/apps/frontend-collab/tsconfig.json +11 -0
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/app.config.ts +1 -1
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-dashboard/src/locales/index.ts +1 -1
- package/templates/apps/frontend-docs/package.json +7 -7
- 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 +8 -8
- package/templates/apps/frontend-portal/src/locales/index.ts +1 -1
- package/templates/apps/frontend-saas/README.md +1 -1
- package/templates/apps/frontend-saas/app.config.ts +1 -1
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-saas/src/locales/index.ts +1 -1
- package/templates/apps/frontend-spa/app.config.ts +1 -1
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-spa/src/locales/index.ts +1 -1
- package/templates/apps/frontend-spa/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-spa/src/pages/page.tsx +1 -1
- package/templates/apps/frontend-ssr/app.config.ts +1 -1
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr/src/locales/index.ts +1 -1
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-ssr/src/pages/page.tsx +1 -1
- package/templates/apps/frontend-ssr-api/app.config.ts +1 -1
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-ssr-api/src/locales/index.ts +1 -1
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +1 -1
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/frontend-status/src/locales/index.ts +1 -1
- package/templates/apps/mobile-app/README.md +58 -0
- package/templates/apps/mobile-app/app.config.ts +33 -0
- package/templates/apps/mobile-app/babel.config.js +5 -0
- package/templates/apps/mobile-app/metro.config.js +20 -0
- package/templates/apps/mobile-app/package.json +33 -0
- package/templates/apps/mobile-app/src/app/_layout.tsx +61 -0
- package/templates/apps/mobile-app/src/app/index.tsx +62 -0
- package/templates/apps/mobile-app/src/app/orders/[id].tsx +19 -0
- package/templates/apps/mobile-app/src/app/settings.tsx +51 -0
- package/templates/apps/mobile-app/src/client.ts +74 -0
- package/templates/apps/mobile-app/src/lib/deeplinks.ts +39 -0
- package/templates/apps/mobile-app/src/lib/notifications.ts +31 -0
- package/templates/apps/mobile-app/src/lib/sync.ts +29 -0
- package/templates/apps/mobile-app/src/persistence.ts +38 -0
- package/templates/apps/mobile-app/template.json +6 -0
- package/templates/apps/mobile-app/tests/deeplinks.test.ts +32 -0
- package/templates/apps/mobile-app/tests/notifications.test.ts +26 -0
- package/templates/apps/mobile-app/tests/sync.test.ts +34 -0
- package/templates/apps/mobile-app/tsconfig.json +10 -0
- package/templates/apps/mobile-app/voltro.mobile.ts +11 -0
- package/templates/patches/@effect__cluster@0.60.0.patch +6 -6
- package/dist/apiBuild-C8d74T6g.js +0 -2
- package/dist/checkCommand-DBvZvCh1.js +0 -2
- package/dist/dbCommand-CaIVTp-a.js +0 -2
- package/dist/frameworkTableAssembly-BTNirAAk.js +0 -2
- package/dist/inspect-DHeaMZak.js +0 -2
- package/dist/manifestBuild-Ck_90gZy.js +0 -2
- package/dist/serveCommand-BXl8mhZD.js +0 -1489
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as e, f as t, ht as n, n as r, o as i } from "./inspectMetrics-
|
|
2
|
-
import {
|
|
1
|
+
import { R as e, f as t, ht as n, n as r, o as i } from "./inspectMetrics-CkB2J0Zn.js";
|
|
2
|
+
import { Ut as a, Wt as o } from "./dev-C_NPcLDp.js";
|
|
3
3
|
import { C as s, E as c, O as l, T as u, d, h as f, i as p, l as m, m as h, r as g, u as _ } from "./fileConventions-Cof68_BL.js";
|
|
4
|
-
import { r as v, u as y } from "./apiBuild-
|
|
4
|
+
import { r as v, u as y } from "./apiBuild-DmkzRyv7.js";
|
|
5
5
|
import { dirname as b, isAbsolute as ee, join as x, relative as S, resolve as C, sep as w } from "node:path";
|
|
6
6
|
import { routeDiagnosticsToStderr as te } from "@voltro/logger";
|
|
7
7
|
import { existsSync as T, readFileSync as E, readdirSync as D, statSync as O, writeFileSync as ne } from "node:fs";
|
|
@@ -442,7 +442,7 @@ var re = () => {
|
|
|
442
442
|
re: l,
|
|
443
443
|
needsTest: !1
|
|
444
444
|
}
|
|
445
|
-
], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null,
|
|
445
|
+
], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, we = (e) => u.test(e), Te = /* @__PURE__ */ new Set([
|
|
446
446
|
"useMutation",
|
|
447
447
|
"useAction",
|
|
448
448
|
"useUpload",
|
|
@@ -452,12 +452,12 @@ var re = () => {
|
|
|
452
452
|
"useAgentStream",
|
|
453
453
|
"useResumableAgentStream",
|
|
454
454
|
"useWorkflow"
|
|
455
|
-
]),
|
|
455
|
+
]), Ee = /* @__PURE__ */ new Set(["defineTracking"]), De = /* @__PURE__ */ new Set([
|
|
456
456
|
"useSubscription",
|
|
457
457
|
"useWindowedSubscription",
|
|
458
458
|
"useRecord",
|
|
459
459
|
"useQuery"
|
|
460
|
-
]),
|
|
460
|
+
]), Oe = /* @__PURE__ */ new Set([
|
|
461
461
|
"node_modules",
|
|
462
462
|
"dist",
|
|
463
463
|
"build",
|
|
@@ -468,7 +468,7 @@ var re = () => {
|
|
|
468
468
|
".git",
|
|
469
469
|
"coverage",
|
|
470
470
|
".publish"
|
|
471
|
-
]),
|
|
471
|
+
]), ke = /\.tsx?$/, z = (e, t = [], n = 0) => {
|
|
472
472
|
if (n > 12) return t;
|
|
473
473
|
let r;
|
|
474
474
|
try {
|
|
@@ -477,27 +477,27 @@ var re = () => {
|
|
|
477
477
|
return t;
|
|
478
478
|
}
|
|
479
479
|
for (let i of r) {
|
|
480
|
-
if (
|
|
480
|
+
if (Oe.has(i)) continue;
|
|
481
481
|
let r = x(e, i), a;
|
|
482
482
|
try {
|
|
483
483
|
a = O(r);
|
|
484
484
|
} catch {
|
|
485
485
|
continue;
|
|
486
486
|
}
|
|
487
|
-
a.isDirectory() ?
|
|
487
|
+
a.isDirectory() ? z(r, t, n + 1) : a.isFile() && ke.test(i) && !i.endsWith(".d.ts") && t.push(r);
|
|
488
488
|
}
|
|
489
489
|
return t;
|
|
490
|
-
},
|
|
491
|
-
if (
|
|
492
|
-
if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return
|
|
490
|
+
}, Ae = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, je = /\b(?:memo|forwardRef|observer|styled)\b/, B = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), V = (e) => {
|
|
491
|
+
if (B(e)) return !0;
|
|
492
|
+
if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return V(e.getExpression());
|
|
493
493
|
if (k.isVariableDeclaration(e)) {
|
|
494
494
|
let t = e.getTypeNode();
|
|
495
|
-
if (t !== void 0 &&
|
|
495
|
+
if (t !== void 0 && Ae.test(t.getText())) return !0;
|
|
496
496
|
let n = e.getInitializer();
|
|
497
|
-
return n === void 0 ||
|
|
497
|
+
return n === void 0 || V(n);
|
|
498
498
|
}
|
|
499
|
-
return k.isCallExpression(e) ?
|
|
500
|
-
},
|
|
499
|
+
return k.isCallExpression(e) ? je.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => B(e) || k.isIdentifier(e)) : k.isTaggedTemplateExpression(e) ? !0 : !(k.isObjectLiteralExpression(e) || k.isArrayLiteralExpression(e) || k.isStringLiteral(e) || k.isNoSubstitutionTemplateLiteral(e) || k.isNumericLiteral(e) || k.isTemplateExpression(e) || k.isNewExpression(e) || k.isPropertyAccessExpression(e) || e.getKind() === j.TrueKeyword || e.getKind() === j.FalseKeyword || e.getKind() === j.NullKeyword);
|
|
500
|
+
}, Me = (e) => {
|
|
501
501
|
let t = [], n = [], r = [], i = /* @__PURE__ */ new Set();
|
|
502
502
|
for (let [a, o] of e.getExportedDeclarations()) {
|
|
503
503
|
let e = o[0];
|
|
@@ -508,7 +508,7 @@ var re = () => {
|
|
|
508
508
|
n.push(s);
|
|
509
509
|
continue;
|
|
510
510
|
}
|
|
511
|
-
if (/^[A-Z]/.test(s) &&
|
|
511
|
+
if (/^[A-Z]/.test(s) && V(e)) {
|
|
512
512
|
t.push(s);
|
|
513
513
|
continue;
|
|
514
514
|
}
|
|
@@ -520,7 +520,7 @@ var re = () => {
|
|
|
520
520
|
others: r,
|
|
521
521
|
hasRuntimeExport: t.length + n.length + r.length > 0
|
|
522
522
|
};
|
|
523
|
-
},
|
|
523
|
+
}, Ne = [], Pe = (e, t, n) => {
|
|
524
524
|
let r = b(e), i = [];
|
|
525
525
|
for (let e = r;; e = b(e)) {
|
|
526
526
|
let r = n.get(e);
|
|
@@ -537,9 +537,9 @@ var re = () => {
|
|
|
537
537
|
}
|
|
538
538
|
if (e === t || b(e) === e) break;
|
|
539
539
|
}
|
|
540
|
-
for (let e of i) n.set(e,
|
|
541
|
-
return
|
|
542
|
-
},
|
|
540
|
+
for (let e of i) n.set(e, Ne);
|
|
541
|
+
return Ne;
|
|
542
|
+
}, Fe = (e, t, n) => {
|
|
543
543
|
let r = t.startsWith(".") ? C(b(e), t) : ye(t, n);
|
|
544
544
|
if (r === void 0) return null;
|
|
545
545
|
for (let e of [
|
|
@@ -552,9 +552,9 @@ var re = () => {
|
|
|
552
552
|
if (O(e).isFile()) return e;
|
|
553
553
|
} catch {}
|
|
554
554
|
return null;
|
|
555
|
-
},
|
|
555
|
+
}, Ie = (e, t, n) => {
|
|
556
556
|
let r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = (e) => {
|
|
557
|
-
let i =
|
|
557
|
+
let i = Fe(t, e, n);
|
|
558
558
|
return i !== null && r.push(i), i;
|
|
559
559
|
};
|
|
560
560
|
for (let t of e.getImportDeclarations()) if (o(t.getModuleSpecifierValue()), !t.isTypeOnly()) for (let e of t.getNamedImports()) e.isTypeOnly() || i.add(e.getName());
|
|
@@ -581,22 +581,22 @@ var re = () => {
|
|
|
581
581
|
namedBindings: i,
|
|
582
582
|
reExports: a
|
|
583
583
|
};
|
|
584
|
-
},
|
|
584
|
+
}, Le = (e) => {
|
|
585
585
|
let t = /* @__PURE__ */ new Map();
|
|
586
586
|
for (let n of e.getDescendantsOfKind(j.CallExpression)) {
|
|
587
587
|
let e = n.getExpression(), r = k.isPropertyAccessExpression(e) ? e.getName() : e.getText();
|
|
588
588
|
t.set(r, (t.get(r) ?? 0) + 1);
|
|
589
589
|
}
|
|
590
590
|
return t;
|
|
591
|
-
},
|
|
591
|
+
}, Re = (e) => {
|
|
592
592
|
if (e.length <= 1) return e.length === 1;
|
|
593
593
|
let t = [...e].sort((e, t) => e.length - t.length)[0];
|
|
594
594
|
return e.every((e) => e.startsWith(t));
|
|
595
|
-
},
|
|
595
|
+
}, ze = (e, t) => {
|
|
596
596
|
let n = b(t);
|
|
597
597
|
return e.startsWith(`${n}${w}`) || b(e) === n;
|
|
598
|
-
},
|
|
599
|
-
let t =
|
|
598
|
+
}, H = (e) => {
|
|
599
|
+
let t = z(e);
|
|
600
600
|
if (t.length === 0) return {
|
|
601
601
|
findings: [],
|
|
602
602
|
declared: 0,
|
|
@@ -627,7 +627,7 @@ var re = () => {
|
|
|
627
627
|
} catch {
|
|
628
628
|
continue;
|
|
629
629
|
}
|
|
630
|
-
s.set(t,
|
|
630
|
+
s.set(t, Me(r)), c.set(t, Ie(r, t, Pe(t, e, u))), l.set(t, Le(r));
|
|
631
631
|
}
|
|
632
632
|
let d = /* @__PURE__ */ new Map();
|
|
633
633
|
for (let [e, t] of c) for (let n of t.localTargets) {
|
|
@@ -659,11 +659,11 @@ var re = () => {
|
|
|
659
659
|
for (let e of r) {
|
|
660
660
|
let t = R(e), n = s.get(e), r = c.get(e);
|
|
661
661
|
if (n === void 0 || r === void 0) continue;
|
|
662
|
-
let i =
|
|
662
|
+
let i = we(e);
|
|
663
663
|
t !== null && !i && (m += 1);
|
|
664
664
|
let u = d.get(e) ?? [];
|
|
665
665
|
if ((t === "component" || t === "component.ui") && !i) {
|
|
666
|
-
let t =
|
|
666
|
+
let t = Re(n.components);
|
|
667
667
|
(n.components.length === 0 || !t) && a.push({
|
|
668
668
|
rule: "component/one-per-file",
|
|
669
669
|
file: o(e),
|
|
@@ -701,7 +701,7 @@ var re = () => {
|
|
|
701
701
|
].join(", ")}`,
|
|
702
702
|
fix: "a *.types.ts has NO runtime export. That is what makes importing it provably free AND makes it impossible for it to sit in a runtime import cycle — the guarantee is the point, so move the value to a sibling module"
|
|
703
703
|
}), t === "internal") {
|
|
704
|
-
let t = u.filter((t) => !
|
|
704
|
+
let t = u.filter((t) => !ze(t, e));
|
|
705
705
|
t.length > 0 && a.push({
|
|
706
706
|
rule: "internal/foreign-import",
|
|
707
707
|
file: o(e),
|
|
@@ -711,7 +711,7 @@ var re = () => {
|
|
|
711
711
|
});
|
|
712
712
|
}
|
|
713
713
|
if (t === "fixture") {
|
|
714
|
-
let t = u.filter((e) => !
|
|
714
|
+
let t = u.filter((e) => !we(e) && R(e) !== "fixture");
|
|
715
715
|
t.length > 0 && a.push({
|
|
716
716
|
rule: "fixture/production-import",
|
|
717
717
|
file: o(e),
|
|
@@ -721,7 +721,7 @@ var re = () => {
|
|
|
721
721
|
});
|
|
722
722
|
}
|
|
723
723
|
if (t === "component.ui" && !i) {
|
|
724
|
-
let t = [...r.namedBindings].filter((e) =>
|
|
724
|
+
let t = [...r.namedBindings].filter((e) => Te.has(e)).sort();
|
|
725
725
|
t.length > 0 && a.push({
|
|
726
726
|
rule: "ui/no-write",
|
|
727
727
|
file: o(e),
|
|
@@ -756,7 +756,7 @@ var re = () => {
|
|
|
756
756
|
detail: t === 0 ? "declares a store file but calls no defineStore()" : `declares ${t} stores`,
|
|
757
757
|
fix: "a store file defines exactly ONE store — the SSR seed pass and devtools address stores by name, so two in a file makes both harder to find and neither easier to reason about"
|
|
758
758
|
});
|
|
759
|
-
let n = [...l.get(e)?.keys() ?? []].filter((e) =>
|
|
759
|
+
let n = [...l.get(e)?.keys() ?? []].filter((e) => De.has(e)).sort();
|
|
760
760
|
n.length > 0 && a.push({
|
|
761
761
|
rule: "store/mirrors-server-state",
|
|
762
762
|
file: o(e),
|
|
@@ -766,7 +766,7 @@ var re = () => {
|
|
|
766
766
|
});
|
|
767
767
|
}
|
|
768
768
|
if (t !== "tracking" && !i) {
|
|
769
|
-
let t = [...l.get(e)?.keys() ?? []].filter((e) =>
|
|
769
|
+
let t = [...l.get(e)?.keys() ?? []].filter((e) => Ee.has(e)).sort();
|
|
770
770
|
t.length > 0 && a.push({
|
|
771
771
|
rule: "tracking/outside-tracking-file",
|
|
772
772
|
file: o(e),
|
|
@@ -798,8 +798,8 @@ var re = () => {
|
|
|
798
798
|
scanned: r.length,
|
|
799
799
|
vendored: n
|
|
800
800
|
};
|
|
801
|
-
},
|
|
802
|
-
let { findings: t, declared: n, scanned: r, vendored: i } =
|
|
801
|
+
}, Be = 10, Ve = (e) => {
|
|
802
|
+
let { findings: t, declared: n, scanned: r, vendored: i } = H(e);
|
|
803
803
|
console.log("");
|
|
804
804
|
for (let t of Se(e, i)) console.log(`· not ours, exempt: ${t}`);
|
|
805
805
|
if (r === 0) return console.log("• file-taxonomy scan: no sources here — nothing to check."), 0;
|
|
@@ -813,12 +813,12 @@ var re = () => {
|
|
|
813
813
|
console.log(`• file taxonomy: ${t.length} finding(s) — ${o} error, ${t.length - o} advisory (${n} declared / ${r} scanned):`);
|
|
814
814
|
for (let [e, t] of [...a].sort()) {
|
|
815
815
|
console.log(` ${t[0].severity === "error" ? "✗" : "·"} ${e} (${t.length})`);
|
|
816
|
-
for (let e of t.slice(0,
|
|
817
|
-
let n = t.length -
|
|
816
|
+
for (let e of t.slice(0, Be)) console.log(` ${e.file} — ${e.detail}`);
|
|
817
|
+
let n = t.length - Be;
|
|
818
818
|
n > 0 && console.log(` (+${n} more — \`voltro doctor --json\` prints every one.)`), console.log(` → ${t[0].fix}`);
|
|
819
819
|
}
|
|
820
820
|
return t.length;
|
|
821
|
-
},
|
|
821
|
+
}, He = /* @__PURE__ */ new Set([
|
|
822
822
|
"eq",
|
|
823
823
|
"ne",
|
|
824
824
|
"gt",
|
|
@@ -836,10 +836,10 @@ var re = () => {
|
|
|
836
836
|
"endsWith",
|
|
837
837
|
"between",
|
|
838
838
|
"jsonField"
|
|
839
|
-
]),
|
|
839
|
+
]), Ue = (e) => {
|
|
840
840
|
if (k.isPropertyAccessExpression(e)) return e.getName();
|
|
841
841
|
if (k.isIdentifier(e)) return e.getText();
|
|
842
|
-
},
|
|
842
|
+
}, We = (e) => {
|
|
843
843
|
let t = e;
|
|
844
844
|
for (; k.isCallExpression(t);) {
|
|
845
845
|
let e = t.getExpression();
|
|
@@ -847,7 +847,7 @@ var re = () => {
|
|
|
847
847
|
t = e.getExpression();
|
|
848
848
|
}
|
|
849
849
|
return t;
|
|
850
|
-
},
|
|
850
|
+
}, Ge = (e, t, n) => {
|
|
851
851
|
let r = new Map(t.map((e) => [e.name, new Set(e.columns)])), i = new Map(t.map((e) => [e.name, e.columns])), a = [], o = 0, s = new A({
|
|
852
852
|
useInMemoryFileSystem: !0,
|
|
853
853
|
skipAddingFilesFromTsConfig: !0
|
|
@@ -865,7 +865,7 @@ var re = () => {
|
|
|
865
865
|
for (let t of e.getDescendantsOfKind(j.CallExpression)) {
|
|
866
866
|
let e = t.getExpression();
|
|
867
867
|
if (!k.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
|
|
868
|
-
let n =
|
|
868
|
+
let n = Ue(We(e.getExpression()));
|
|
869
869
|
if (n === void 0 || !r.has(n)) {
|
|
870
870
|
let t = e.getExpression();
|
|
871
871
|
if (k.isCallExpression(t)) {
|
|
@@ -888,7 +888,7 @@ var re = () => {
|
|
|
888
888
|
else for (let e of [u, ...u.getDescendantsOfKind(j.CallExpression)]) {
|
|
889
889
|
if (!k.isCallExpression(e)) continue;
|
|
890
890
|
let t = e.getExpression(), n = k.isIdentifier(t) ? t.getText() : k.isPropertyAccessExpression(t) ? t.getName() : void 0;
|
|
891
|
-
if (n === void 0 || !
|
|
891
|
+
if (n === void 0 || !He.has(n)) continue;
|
|
892
892
|
let r = e.getArguments()[0];
|
|
893
893
|
r !== void 0 && k.isStringLiteral(r) && l.push({
|
|
894
894
|
column: r.getLiteralValue(),
|
|
@@ -909,16 +909,16 @@ var re = () => {
|
|
|
909
909
|
issues: a,
|
|
910
910
|
checked: o
|
|
911
911
|
};
|
|
912
|
-
},
|
|
912
|
+
}, U = 12, Ke = (e) => {
|
|
913
913
|
if (e.checked === 0) return null;
|
|
914
914
|
if (e.issues.length === 0) return `predicate columns: ${e.checked} checked — every filtered column exists on its table`;
|
|
915
915
|
let t = [`predicate columns: ${e.issues.length} filter${e.issues.length, "s"} on a column that does not exist (${e.checked} checked)`];
|
|
916
916
|
for (let n of e.issues) {
|
|
917
|
-
let e = n.available.slice(0,
|
|
917
|
+
let e = n.available.slice(0, U).join(", "), r = n.available.length > U ? `, … (${n.available.length - U} more)` : "";
|
|
918
918
|
t.push(` ${n.file}:${n.line} '${n.table}' has no column '${n.column}'`), t.push(` columns: ${e}${r}`);
|
|
919
919
|
}
|
|
920
920
|
return t.push(" This type-checks today and fails at runtime as a bare SQL error."), t.join("\n");
|
|
921
|
-
},
|
|
921
|
+
}, qe = /* @__PURE__ */ new Set([
|
|
922
922
|
"id",
|
|
923
923
|
"tenantId",
|
|
924
924
|
"tenant_id",
|
|
@@ -938,7 +938,7 @@ var re = () => {
|
|
|
938
938
|
"idempotency_id",
|
|
939
939
|
"idempotencyKey",
|
|
940
940
|
"idempotency_key"
|
|
941
|
-
]),
|
|
941
|
+
]), Je = /* @__PURE__ */ new Set([
|
|
942
942
|
"id",
|
|
943
943
|
"createdAt",
|
|
944
944
|
"created_at",
|
|
@@ -948,10 +948,10 @@ var re = () => {
|
|
|
948
948
|
"deleted_at",
|
|
949
949
|
"tenantId",
|
|
950
950
|
"tenant_id"
|
|
951
|
-
]),
|
|
951
|
+
]), Ye = (e) => qe.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Xe = (e) => e.type === "reference", Ze = (e) => {
|
|
952
952
|
let t = [];
|
|
953
953
|
for (let n of e) {
|
|
954
|
-
let e = n.columns.filter((e) =>
|
|
954
|
+
let e = n.columns.filter((e) => Ye(e.name)), r = e.filter((e) => !Xe(e)).map((e) => e.name);
|
|
955
955
|
if (r.length === 0) continue;
|
|
956
956
|
let i = new Set(n.primaryKey), a = r.filter((e) => i.has(e));
|
|
957
957
|
if (n.primaryKey.length >= 2 && a.length > 0) {
|
|
@@ -962,7 +962,7 @@ var re = () => {
|
|
|
962
962
|
});
|
|
963
963
|
continue;
|
|
964
964
|
}
|
|
965
|
-
if (e.some(
|
|
965
|
+
if (e.some(Xe) && e.length >= 2) {
|
|
966
966
|
t.push({
|
|
967
967
|
table: n.name,
|
|
968
968
|
columns: r,
|
|
@@ -970,7 +970,7 @@ var re = () => {
|
|
|
970
970
|
});
|
|
971
971
|
continue;
|
|
972
972
|
}
|
|
973
|
-
e.length >= 2 && n.columns.filter((e) => !
|
|
973
|
+
e.length >= 2 && n.columns.filter((e) => !Ye(e.name) && !Je.has(e.name)).length === 0 && t.push({
|
|
974
974
|
table: n.name,
|
|
975
975
|
columns: r,
|
|
976
976
|
signal: "link-only"
|
|
@@ -980,20 +980,20 @@ var re = () => {
|
|
|
980
980
|
issues: t,
|
|
981
981
|
tablesChecked: e.length
|
|
982
982
|
};
|
|
983
|
-
},
|
|
983
|
+
}, Qe = {
|
|
984
984
|
"composite-pk": "part of a composite primary key",
|
|
985
985
|
"reference-sibling": "sits beside a wired reference() on this table",
|
|
986
986
|
"link-only": "this table is nothing but link columns"
|
|
987
|
-
},
|
|
987
|
+
}, $e = (e) => {
|
|
988
988
|
if (e.tablesChecked === 0) return null;
|
|
989
989
|
if (e.issues.length === 0) return `junction FKs: ${e.tablesChecked} table${e.tablesChecked === 1 ? "" : "s"} checked — every link column is a wired reference()`;
|
|
990
990
|
let t = e.issues.length, n = [`junction FKs: ${t} junction table${t === 1 ? "" : "s"} with an id column that is a plain text() and not a reference()`];
|
|
991
991
|
for (let t of e.issues) {
|
|
992
992
|
let e = t.columns.map((e) => `'${e}'`).join(", ");
|
|
993
|
-
n.push(` '${t.table}': ${e} (${
|
|
993
|
+
n.push(` '${t.table}': ${e} (${Qe[t.signal]})`);
|
|
994
994
|
}
|
|
995
995
|
return n.push(" Declare each as reference(() => <table>): the FK is enforced, the column is auto-indexed,"), n.push(" and the relationship becomes walkable (a plain text() id column is an invisible edge)."), n.join("\n");
|
|
996
|
-
},
|
|
996
|
+
}, et = /* @__PURE__ */ new Set(/* @__PURE__ */ "position.positions.move.moved.moving.cursor.pointer.mouse.drag.dragging.hover.frame.frames.tick.ticks.ping.heartbeat.typing.scroll.scrolling.zoom.pan.coord.coords.coordinate.coordinates.telemetry.sensor.sample.samples.viewport".split(".")), tt = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((e) => e.length > 0).map((e) => e.toLowerCase()), nt = (e) => tt(e).some((e) => et.has(e)), rt = (e) => {
|
|
997
997
|
let t = [], n = [];
|
|
998
998
|
for (let r of e) t.push({
|
|
999
999
|
event: r.name,
|
|
@@ -1002,7 +1002,7 @@ var re = () => {
|
|
|
1002
1002
|
event: r.name,
|
|
1003
1003
|
kind: "wide-key",
|
|
1004
1004
|
keyFieldCount: r.keyFieldCount
|
|
1005
|
-
}), r.hasWebhook &&
|
|
1005
|
+
}), r.hasWebhook && nt(r.name) && n.push({
|
|
1006
1006
|
event: r.name,
|
|
1007
1007
|
kind: "webhook-high-rate"
|
|
1008
1008
|
});
|
|
@@ -1011,26 +1011,26 @@ var re = () => {
|
|
|
1011
1011
|
modes: t,
|
|
1012
1012
|
warnings: n
|
|
1013
1013
|
};
|
|
1014
|
-
},
|
|
1014
|
+
}, it = (e) => {
|
|
1015
1015
|
if (e.eventsChecked === 0) return null;
|
|
1016
1016
|
let t = e.eventsChecked, n = [`event delivery: ${t} declared event${t === 1 ? "" : "s"} — the mode decides what a MISSING message means`];
|
|
1017
1017
|
for (let t of e.modes) n.push(` '${t.event}': ${t.delivery}`);
|
|
1018
1018
|
return n.push(" each — every delivery matters; a slow subscriber loses the oldest and is TOLD how many (the default)."), n.push(" latest — a newer delivery supersedes a pending one; a slow subscriber gets the current value, told nothing."), n.push(" Two events with identical route/subscriber/buffer numbers mean OPPOSITE things about a drop — the mode is the difference."), n.join("\n");
|
|
1019
|
-
},
|
|
1019
|
+
}, at = (e) => {
|
|
1020
1020
|
if (e.warnings.length === 0) return null;
|
|
1021
1021
|
let t = e.warnings.length, n = [`events that may not scale as declared: ${t} finding${t === 1 ? "" : "s"}`];
|
|
1022
1022
|
for (let t of e.warnings) t.kind === "wide-key" ? (n.push(` '${t.event}': routing key has ${t.keyFieldCount ?? 0} fields — every field FRAGMENTS the subscriber set (distinct routes are the PRODUCT of the fields’ value spaces).`), n.push(" Check each is an ADDRESS the delivery is decided by (arenaId), not a discriminator the handler reads (gameType) — the latter is payload, not key.")) : (n.push(` '${t.event}': a webhook block on a per-frame-looking event — every publish is N HTTP deliveries per second per subscribed target.`), n.push(" The webhook rate limit DEFERS the excess as pending rows rather than failing, so the symptom is a growing table. Publish a coarser event (a summary / state change) for the outside world."));
|
|
1023
1023
|
return n.push(" WARNING, not a fault — a producer may intend the fan-out. `voltro doctor --json` prints every finding."), n.join("\n");
|
|
1024
|
-
},
|
|
1024
|
+
}, ot = /* @__PURE__ */ new Set([
|
|
1025
1025
|
"start",
|
|
1026
1026
|
"run",
|
|
1027
1027
|
"child"
|
|
1028
|
-
]),
|
|
1028
|
+
]), st = (e) => k.isCallExpression(e) && (() => {
|
|
1029
1029
|
let t = e.getExpression();
|
|
1030
|
-
if (!k.isPropertyAccessExpression(t) || !
|
|
1030
|
+
if (!k.isPropertyAccessExpression(t) || !ot.has(t.getName())) return !1;
|
|
1031
1031
|
let n = t.getExpression();
|
|
1032
1032
|
return k.isPropertyAccessExpression(n) ? n.getName() === "workflows" : k.isIdentifier(n) && n.getText() === "workflows";
|
|
1033
|
-
})(),
|
|
1033
|
+
})(), ct = (e, t, n) => {
|
|
1034
1034
|
let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new A({
|
|
1035
1035
|
useInMemoryFileSystem: !0,
|
|
1036
1036
|
skipAddingFilesFromTsConfig: !0
|
|
@@ -1046,7 +1046,7 @@ var re = () => {
|
|
|
1046
1046
|
}
|
|
1047
1047
|
let u = S(n, t);
|
|
1048
1048
|
for (let t of e.getDescendantsOfKind(j.CallExpression)) {
|
|
1049
|
-
if (!
|
|
1049
|
+
if (!st(t)) continue;
|
|
1050
1050
|
c++;
|
|
1051
1051
|
let e = t.getStartLineNumber(), [n, l] = t.getArguments();
|
|
1052
1052
|
if (n === void 0 || !k.isStringLiteral(n)) {
|
|
@@ -1130,7 +1130,7 @@ var re = () => {
|
|
|
1130
1130
|
checked: s,
|
|
1131
1131
|
callSites: c
|
|
1132
1132
|
};
|
|
1133
|
-
},
|
|
1133
|
+
}, lt = (e) => {
|
|
1134
1134
|
if (e.callSites === 0) return null;
|
|
1135
1135
|
let t = [];
|
|
1136
1136
|
t.push(`workflow starts: ${e.checked}/${e.callSites} call sites checked`);
|
|
@@ -1143,7 +1143,7 @@ var re = () => {
|
|
|
1143
1143
|
for (let n of e.unchecked) t.push(` ${n.file}:${n.line} — ${n.reason}`);
|
|
1144
1144
|
}
|
|
1145
1145
|
return e.issues.length === 0 && e.unchecked.length === 0 && t.push(" every call site matches its workflow"), t.join("\n");
|
|
1146
|
-
},
|
|
1146
|
+
}, ut = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, W = "VOLTRO_FIELD_ENCRYPTION_KEY", dt = (e) => {
|
|
1147
1147
|
let t;
|
|
1148
1148
|
for (let [n, r] of e.sources) if (t === void 0 && /\bfieldEncryption\b/.test(r) && (t = {
|
|
1149
1149
|
reason: "fieldEncryption",
|
|
@@ -1151,13 +1151,13 @@ var re = () => {
|
|
|
1151
1151
|
}), t === void 0 && /\.encrypted\s*\(/.test(r) && (t = {
|
|
1152
1152
|
reason: "encrypted-column",
|
|
1153
1153
|
file: n
|
|
1154
|
-
}), r.includes(
|
|
1154
|
+
}), r.includes(W)) return;
|
|
1155
1155
|
if (t !== void 0) {
|
|
1156
|
-
for (let [, t] of e.envFiles) if (t.includes(
|
|
1156
|
+
for (let [, t] of e.envFiles) if (t.includes(W)) return;
|
|
1157
1157
|
return t;
|
|
1158
1158
|
}
|
|
1159
|
-
},
|
|
1160
|
-
`✗ ${
|
|
1159
|
+
}, ft = (e) => [
|
|
1160
|
+
`✗ ${W} is named nowhere under this app root, and the app encrypts columns at rest`,
|
|
1161
1161
|
e.reason === "fieldEncryption" ? ` (\`fieldEncryption\` in ${e.file})` : ` (an \`.encrypted()\` column in ${e.file})`,
|
|
1162
1162
|
"",
|
|
1163
1163
|
" A deploy without it does not degrade — it refuses to boot at `onActivate`,",
|
|
@@ -1173,7 +1173,7 @@ var re = () => {
|
|
|
1173
1173
|
" reaches your deploy target is not knowable from here — a secret manager",
|
|
1174
1174
|
" or a CI variable is not a file. Naming it nowhere is conclusive; naming",
|
|
1175
1175
|
" it somewhere is not a guarantee."
|
|
1176
|
-
].join("\n"),
|
|
1176
|
+
].join("\n"), pt = (e) => e === "effect" || e.startsWith("@effect/") || e.startsWith("@voltro/") || e === "react" || e === "react-dom", mt = /(^|\/)(\.bin|\.cache|\.pnpm-store|dist|build)(\/|$)/, ht = (e) => {
|
|
1177
1177
|
let t = [], n = e;
|
|
1178
1178
|
for (let e = 0; e < 12; e++) {
|
|
1179
1179
|
let e = x(n, "node_modules");
|
|
@@ -1183,14 +1183,14 @@ var re = () => {
|
|
|
1183
1183
|
n = r;
|
|
1184
1184
|
}
|
|
1185
1185
|
return t;
|
|
1186
|
-
},
|
|
1186
|
+
}, gt = (e) => {
|
|
1187
1187
|
try {
|
|
1188
1188
|
let t = JSON.parse(E(x(e, "package.json"), "utf8"));
|
|
1189
1189
|
return typeof t.version == "string" ? t.version : void 0;
|
|
1190
1190
|
} catch {
|
|
1191
1191
|
return;
|
|
1192
1192
|
}
|
|
1193
|
-
},
|
|
1193
|
+
}, _t = (e) => {
|
|
1194
1194
|
let t = [], n;
|
|
1195
1195
|
try {
|
|
1196
1196
|
n = D(e);
|
|
@@ -1198,7 +1198,7 @@ var re = () => {
|
|
|
1198
1198
|
return t;
|
|
1199
1199
|
}
|
|
1200
1200
|
for (let r of n) {
|
|
1201
|
-
if (r.startsWith(".") ||
|
|
1201
|
+
if (r.startsWith(".") || mt.test(r)) continue;
|
|
1202
1202
|
let n = x(e, r);
|
|
1203
1203
|
if (r.startsWith("@")) {
|
|
1204
1204
|
let e;
|
|
@@ -1224,11 +1224,11 @@ var re = () => {
|
|
|
1224
1224
|
});
|
|
1225
1225
|
}
|
|
1226
1226
|
return t;
|
|
1227
|
-
},
|
|
1227
|
+
}, vt = (e) => {
|
|
1228
1228
|
let t = /* @__PURE__ */ new Map();
|
|
1229
|
-
for (let n of
|
|
1230
|
-
if (!
|
|
1231
|
-
let n =
|
|
1229
|
+
for (let n of ht(e)) for (let { pkg: e, dir: r } of _t(n)) {
|
|
1230
|
+
if (!pt(e)) continue;
|
|
1231
|
+
let n = gt(r);
|
|
1232
1232
|
if (n === void 0) continue;
|
|
1233
1233
|
let i = t.get(e) ?? /* @__PURE__ */ new Map();
|
|
1234
1234
|
i.has(n) || i.set(n, r), t.set(e, i);
|
|
@@ -1240,7 +1240,7 @@ var re = () => {
|
|
|
1240
1240
|
paths: [...i.values()].map((t) => S(e, t)).sort()
|
|
1241
1241
|
});
|
|
1242
1242
|
return n.sort((e, t) => e.pkg.localeCompare(t.pkg));
|
|
1243
|
-
},
|
|
1243
|
+
}, yt = (e) => {
|
|
1244
1244
|
if (e.length !== 0) return [
|
|
1245
1245
|
`• ${e.length} package(s) resolved at more than one version:`,
|
|
1246
1246
|
...e.flatMap((e) => [` ${e.pkg} — ${e.versions.join(", ")}`, ...e.paths.map((e) => ` ${e}`)]),
|
|
@@ -1258,33 +1258,52 @@ var re = () => {
|
|
|
1258
1258
|
" instrument), then reinstall. Do not silence the generated file — it is where",
|
|
1259
1259
|
" a genuine mistake in your own descriptors surfaces."
|
|
1260
1260
|
].join("\n");
|
|
1261
|
-
},
|
|
1261
|
+
}, bt = (e) => {
|
|
1262
|
+
if (e.catalogCodes.length === 0) return;
|
|
1263
|
+
let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
|
|
1264
|
+
if (n.length !== 0) return {
|
|
1265
|
+
orphaned: n,
|
|
1266
|
+
noneDeclared: t.size === 0
|
|
1267
|
+
};
|
|
1268
|
+
}, xt = (e, t) => {
|
|
1269
|
+
let n = e.orphaned.join(", ");
|
|
1270
|
+
return e.noneDeclared ? [
|
|
1271
|
+
`⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
|
|
1272
|
+
" Nothing imports them: no provider is generated and `useT()` would not find a catalog.",
|
|
1273
|
+
" Either declare them in your app config:",
|
|
1274
|
+
"",
|
|
1275
|
+
` locales: [${e.orphaned.map((e) => `'${e}'`).join(", ")}],`,
|
|
1276
|
+
"",
|
|
1277
|
+
" …or delete the files. A catalog that is never loaded looks from the inside",
|
|
1278
|
+
" exactly like one that works, which is why this is worth a line."
|
|
1279
|
+
] : [`⚠ ${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)."];
|
|
1280
|
+
}, St = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), Ct = /* @__PURE__ */ new Set([
|
|
1262
1281
|
"requireScope",
|
|
1263
1282
|
"requireAnyScope",
|
|
1264
1283
|
"requireAuth",
|
|
1265
1284
|
"assertScope",
|
|
1266
1285
|
"assertCan",
|
|
1267
1286
|
"can"
|
|
1268
|
-
]),
|
|
1287
|
+
]), wt = (e) => {
|
|
1269
1288
|
let t = /* @__PURE__ */ new Set();
|
|
1270
|
-
for (let n of e) for (let e of
|
|
1289
|
+
for (let n of e) for (let e of St(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
|
|
1271
1290
|
return t;
|
|
1272
|
-
},
|
|
1291
|
+
}, Tt = (e, t) => {
|
|
1273
1292
|
for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
|
|
1274
|
-
},
|
|
1293
|
+
}, Et = (e) => {
|
|
1275
1294
|
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);
|
|
1276
1295
|
return t && n;
|
|
1277
|
-
},
|
|
1296
|
+
}, Dt = (e, t, n) => {
|
|
1278
1297
|
if (e.hasDeclaredGuards) return { kind: "declared-guard" };
|
|
1279
|
-
let r =
|
|
1280
|
-
return i === void 0 ?
|
|
1298
|
+
let r = St(e.src), i = Tt(r, t);
|
|
1299
|
+
return i === void 0 ? Et(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
|
|
1281
1300
|
kind: "vocabulary-guard",
|
|
1282
1301
|
name: i
|
|
1283
1302
|
};
|
|
1284
|
-
},
|
|
1303
|
+
}, Ot = (e, t, n) => {
|
|
1285
1304
|
let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
|
|
1286
1305
|
return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
|
|
1287
|
-
},
|
|
1306
|
+
}, kt = (e, t) => {
|
|
1288
1307
|
switch (t.kind) {
|
|
1289
1308
|
case "declared-guard": return "guards: on the descriptor";
|
|
1290
1309
|
case "vocabulary-guard": return `calls ${t.name}()`;
|
|
@@ -1292,15 +1311,15 @@ var re = () => {
|
|
|
1292
1311
|
case "allowlisted": return "accepted by a previous run";
|
|
1293
1312
|
case "unchecked": return 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`;
|
|
1294
1313
|
}
|
|
1295
|
-
},
|
|
1314
|
+
}, At = (e) => {
|
|
1296
1315
|
let t = e.executors.map((t) => {
|
|
1297
|
-
let n =
|
|
1316
|
+
let n = Dt(t, e.guardVocabulary, e.allowlist);
|
|
1298
1317
|
return {
|
|
1299
1318
|
tag: t.tag,
|
|
1300
1319
|
path: t.path,
|
|
1301
1320
|
verdict: n,
|
|
1302
|
-
severity:
|
|
1303
|
-
why:
|
|
1321
|
+
severity: Ot(t, e.tenantScopedTables, e.referencedTables),
|
|
1322
|
+
why: kt(t, n)
|
|
1304
1323
|
};
|
|
1305
1324
|
}), n = (e) => t.filter((t) => t.verdict.kind === e).length;
|
|
1306
1325
|
return {
|
|
@@ -1314,26 +1333,75 @@ var re = () => {
|
|
|
1314
1333
|
},
|
|
1315
1334
|
unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
|
|
1316
1335
|
};
|
|
1317
|
-
},
|
|
1336
|
+
}, jt = [
|
|
1318
1337
|
"# voltro doctor — authz allowlist",
|
|
1319
1338
|
"#",
|
|
1320
|
-
"#
|
|
1321
|
-
"#
|
|
1339
|
+
"# Two KINDS of line, and the difference is the point:",
|
|
1340
|
+
"#",
|
|
1341
|
+
"# <tag> DEBT, not approval. Nobody has confirmed",
|
|
1342
|
+
"# this executor is safe; it was accepted so",
|
|
1343
|
+
"# the ratchet could start. Every one of",
|
|
1344
|
+
"# these is owed work.",
|
|
1345
|
+
"#",
|
|
1346
|
+
"# <tag> reviewed=<why> REVIEWED. A human read it and states why it",
|
|
1347
|
+
"# is genuinely open — constrained by something",
|
|
1348
|
+
"# the scanner cannot see. The reason is",
|
|
1349
|
+
"# REQUIRED: \"reviewed\" with no why is the",
|
|
1350
|
+
"# claim without the evidence, and doctor",
|
|
1351
|
+
"# refuses it.",
|
|
1322
1352
|
"#",
|
|
1323
1353
|
"# Doctor fails on any executor NOT listed here, so the list can only shrink",
|
|
1324
1354
|
"# unless someone adds a line deliberately. Delete a line once its executor",
|
|
1325
1355
|
"# has a guard — doctor reports it as guarded from then on, whether or not the",
|
|
1326
1356
|
"# line is still here.",
|
|
1327
|
-
"",
|
|
1328
|
-
|
|
1357
|
+
"#",
|
|
1358
|
+
"# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
|
|
1359
|
+
"# lines for executors that are newly unchecked.",
|
|
1329
1360
|
""
|
|
1330
|
-
]
|
|
1361
|
+
], Mt = (e) => {
|
|
1362
|
+
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));
|
|
1363
|
+
return [
|
|
1364
|
+
...jt,
|
|
1365
|
+
...n,
|
|
1366
|
+
...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
|
|
1367
|
+
...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
|
|
1368
|
+
""
|
|
1369
|
+
].join("\n");
|
|
1370
|
+
}, Nt = class extends Error {
|
|
1371
|
+
tag;
|
|
1372
|
+
constructor(e) {
|
|
1373
|
+
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";
|
|
1374
|
+
}
|
|
1375
|
+
}, Pt = (e) => {
|
|
1376
|
+
let t = [];
|
|
1377
|
+
for (let n of e.split("\n")) {
|
|
1378
|
+
let e = n.trim();
|
|
1379
|
+
if (e.length === 0 || e.startsWith("#")) continue;
|
|
1380
|
+
let r = /^(\S+)(?:\s+reviewed=(.*))?$/.exec(e);
|
|
1381
|
+
if (!r) continue;
|
|
1382
|
+
let i = r[1];
|
|
1383
|
+
if (r[2] === void 0) {
|
|
1384
|
+
t.push({ tag: i });
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
let a = r[2].trim();
|
|
1388
|
+
if (a.length === 0) throw new Nt(i);
|
|
1389
|
+
t.push({
|
|
1390
|
+
tag: i,
|
|
1391
|
+
reviewed: a
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
return {
|
|
1395
|
+
tags: new Set(t.map((e) => e.tag)),
|
|
1396
|
+
entries: t
|
|
1397
|
+
};
|
|
1398
|
+
}, Ft = (e) => {
|
|
1331
1399
|
let t = e.indexOf(".");
|
|
1332
1400
|
return t <= 0 ? void 0 : e.slice(0, t);
|
|
1333
|
-
},
|
|
1401
|
+
}, It = (e, t) => {
|
|
1334
1402
|
let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
|
|
1335
1403
|
for (let e of t.rpcTags) {
|
|
1336
|
-
let t =
|
|
1404
|
+
let t = Ft(e);
|
|
1337
1405
|
if (t === void 0) continue;
|
|
1338
1406
|
let n = i.get(t);
|
|
1339
1407
|
n ? n.push(e) : i.set(t, [e]);
|
|
@@ -1362,7 +1430,7 @@ var re = () => {
|
|
|
1362
1430
|
});
|
|
1363
1431
|
continue;
|
|
1364
1432
|
}
|
|
1365
|
-
let s = new Set(o.rpcTags.map(
|
|
1433
|
+
let s = new Set(o.rpcTags.map(Ft).filter((e) => e !== void 0));
|
|
1366
1434
|
for (let e of s) {
|
|
1367
1435
|
let t = i.get(e);
|
|
1368
1436
|
t !== void 0 && a.push({
|
|
@@ -1375,12 +1443,12 @@ var re = () => {
|
|
|
1375
1443
|
}
|
|
1376
1444
|
}
|
|
1377
1445
|
return a;
|
|
1378
|
-
},
|
|
1446
|
+
}, Lt = (e, t) => {
|
|
1379
1447
|
let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
|
|
1380
1448
|
if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
|
|
1381
1449
|
for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
|
|
1382
1450
|
return r;
|
|
1383
|
-
},
|
|
1451
|
+
}, Rt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), zt = (e) => {
|
|
1384
1452
|
let t = y(e), n = v(e);
|
|
1385
1453
|
return {
|
|
1386
1454
|
root: e,
|
|
@@ -1389,19 +1457,19 @@ var re = () => {
|
|
|
1389
1457
|
apiBundle: n,
|
|
1390
1458
|
apiBundlePresent: T(n)
|
|
1391
1459
|
};
|
|
1392
|
-
},
|
|
1460
|
+
}, Bt = [
|
|
1393
1461
|
"Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
|
|
1394
1462
|
"Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
|
|
1395
1463
|
"(Generated Dockerfiles from `voltro create-project` already do this.)"
|
|
1396
|
-
].join("\n "),
|
|
1397
|
-
let n =
|
|
1398
|
-
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !
|
|
1464
|
+
].join("\n "), Vt = (e, t = !1) => {
|
|
1465
|
+
let n = zt(e);
|
|
1466
|
+
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Rt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
|
|
1399
1467
|
console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
|
|
1400
1468
|
let r = process.env.NODE_ENV === "production";
|
|
1401
1469
|
if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
|
|
1402
1470
|
let i = t || r;
|
|
1403
|
-
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${
|
|
1404
|
-
},
|
|
1471
|
+
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Bt}`), +!!i;
|
|
1472
|
+
}, Ht = (e, t, n) => {
|
|
1405
1473
|
let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
|
|
1406
1474
|
`${e}.ts`,
|
|
1407
1475
|
`${e}.tsx`,
|
|
@@ -1427,7 +1495,7 @@ var re = () => {
|
|
|
1427
1495
|
r !== void 0 && o.push(r);
|
|
1428
1496
|
}
|
|
1429
1497
|
return o;
|
|
1430
|
-
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) =>
|
|
1498
|
+
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Jt.test(S(n, e))), l = new Set(c);
|
|
1431
1499
|
for (; c.length > 0;) {
|
|
1432
1500
|
let e = c.shift();
|
|
1433
1501
|
if (!s.has(e)) {
|
|
@@ -1440,11 +1508,11 @@ var re = () => {
|
|
|
1440
1508
|
let d = /* @__PURE__ */ new Set();
|
|
1441
1509
|
for (let e of s) l.has(e) || u.has(e) || d.add(e);
|
|
1442
1510
|
return d;
|
|
1443
|
-
},
|
|
1511
|
+
}, Ut = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Wt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Gt = (e, t) => {
|
|
1444
1512
|
if (e.src.includes(t)) return !0;
|
|
1445
1513
|
let n = t.replace(/^@voltro\/plugin-/, "");
|
|
1446
|
-
return n !== t &&
|
|
1447
|
-
},
|
|
1514
|
+
return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
|
|
1515
|
+
}, Kt = (e, t) => {
|
|
1448
1516
|
let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
|
|
1449
1517
|
for (; (i = r.exec(e)) !== null;) {
|
|
1450
1518
|
let t = i.index + i[0].length, r = 1, a = t;
|
|
@@ -1452,10 +1520,10 @@ var re = () => {
|
|
|
1452
1520
|
n.push(e.slice(t, a - 1));
|
|
1453
1521
|
}
|
|
1454
1522
|
return n;
|
|
1455
|
-
},
|
|
1523
|
+
}, qt = (e, t) => {
|
|
1456
1524
|
let n = t.getSymbol()?.getDeclarations()?.[0];
|
|
1457
1525
|
return n === void 0 ? [] : e.getDescendantsOfKind(j.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
|
|
1458
|
-
},
|
|
1526
|
+
}, Jt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Yt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Xt = [
|
|
1459
1527
|
"Id",
|
|
1460
1528
|
"ID",
|
|
1461
1529
|
"Hash",
|
|
@@ -1481,13 +1549,20 @@ var re = () => {
|
|
|
1481
1549
|
"Comment",
|
|
1482
1550
|
"Provider",
|
|
1483
1551
|
"Format"
|
|
1484
|
-
],
|
|
1552
|
+
], Zt = RegExp(`(?:${Xt.join("|")})$|_(?:${Xt.map((e) => e.toLowerCase()).join("|")})$`), Qt = [
|
|
1553
|
+
{
|
|
1554
|
+
id: "hardcoded-framework-cookie",
|
|
1555
|
+
scope: "client",
|
|
1556
|
+
smell: "a framework cookie name written as a string literal instead of imported",
|
|
1557
|
+
use: "import the constant — `LOCALE_COOKIE` / `THEME_COOKIE` from `@voltro/ui-shadcn` — so a rename in the framework is a compile error in your app rather than a preference that quietly stops working",
|
|
1558
|
+
match: ({ src: e }) => /['"`]voltro:(?:locale|theme|lang)['"`]/.test(e)
|
|
1559
|
+
},
|
|
1485
1560
|
{
|
|
1486
1561
|
id: "executor-without-descriptor",
|
|
1487
1562
|
scope: "server",
|
|
1488
1563
|
smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
|
|
1489
1564
|
use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
|
|
1490
|
-
match: ({ src: e, path: t }) =>
|
|
1565
|
+
match: ({ src: e, path: t }) => Yt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
|
|
1491
1566
|
},
|
|
1492
1567
|
{
|
|
1493
1568
|
id: "subject-write-no-guard",
|
|
@@ -1495,8 +1570,8 @@ var re = () => {
|
|
|
1495
1570
|
smell: "writes ctx.request.subject.id into the database with no guard on the descriptor — an anonymous caller reaches the write and gets a raw statement failure instead of a typed refusal",
|
|
1496
1571
|
use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
|
|
1497
1572
|
match: ({ src: e, path: t }) => {
|
|
1498
|
-
if (!
|
|
1499
|
-
let n =
|
|
1573
|
+
if (!Yt.test(t)) return !1;
|
|
1574
|
+
let n = Ut(e);
|
|
1500
1575
|
return /\bsubject\s*\.\s*id\b/.test(n) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(n) && !/\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(n);
|
|
1501
1576
|
}
|
|
1502
1577
|
},
|
|
@@ -1505,14 +1580,14 @@ var re = () => {
|
|
|
1505
1580
|
scope: "server",
|
|
1506
1581
|
smell: "raw fetch() for outbound HTTP on the server (no SSRF guard, no traceparent, no retry)",
|
|
1507
1582
|
use: "yield* HttpClient — the framework client. It blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop, injects traceparent, and honours `http.allowHosts` for deliberate internal targets",
|
|
1508
|
-
match: ({ file: e, path: t, serverOnly: n }) => (
|
|
1583
|
+
match: ({ file: e, path: t, serverOnly: n }) => (Jt.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
|
|
1509
1584
|
},
|
|
1510
1585
|
{
|
|
1511
1586
|
id: "form",
|
|
1512
1587
|
scope: "client",
|
|
1513
1588
|
smell: "per-field useState + a submit flag (hand-rolled form)",
|
|
1514
1589
|
use: "useFormBinding — fields + validation from the mutation input Schema; server ValidationError({ field }) routes to the field",
|
|
1515
|
-
match: ({ src: e, file: t }) =>
|
|
1590
|
+
match: ({ src: e, file: t }) => G(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
|
|
1516
1591
|
},
|
|
1517
1592
|
{
|
|
1518
1593
|
id: "table",
|
|
@@ -1540,7 +1615,7 @@ var re = () => {
|
|
|
1540
1615
|
scope: "client",
|
|
1541
1616
|
smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
|
|
1542
1617
|
use: "useDerived — dependency-tracked, referentially stable",
|
|
1543
|
-
match: ({ src: e }) =>
|
|
1618
|
+
match: ({ src: e }) => Wt(e, /\buseSubscription\s*[(<]/g) >= 3 && Wt(e, /\buseMemo\s*\(/g) >= 3
|
|
1544
1619
|
},
|
|
1545
1620
|
{
|
|
1546
1621
|
id: "next-shim",
|
|
@@ -1565,7 +1640,7 @@ var re = () => {
|
|
|
1565
1640
|
match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
|
|
1566
1641
|
let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
|
|
1567
1642
|
return t === void 0 ? [] : k.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
|
|
1568
|
-
}).some((t) =>
|
|
1643
|
+
}).some((t) => qt(e, t).some((e) => {
|
|
1569
1644
|
let t = e.getParent();
|
|
1570
1645
|
if (k.isBinaryExpression(t)) {
|
|
1571
1646
|
let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
|
|
@@ -1593,7 +1668,7 @@ var re = () => {
|
|
|
1593
1668
|
scope: "server",
|
|
1594
1669
|
smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
|
|
1595
1670
|
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)",
|
|
1596
|
-
match: (e) =>
|
|
1671
|
+
match: (e) => G(e.file, /\bstore\.query$/) >= 3 || G(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
|
|
1597
1672
|
},
|
|
1598
1673
|
{
|
|
1599
1674
|
id: "store-effect-lift",
|
|
@@ -1616,7 +1691,7 @@ var re = () => {
|
|
|
1616
1691
|
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)",
|
|
1617
1692
|
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) => {
|
|
1618
1693
|
let n = t[1];
|
|
1619
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
1694
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Zt.test(n) || /^vault/i.test(n) ? !1 : !/\.encrypted\s*\(/.test(e);
|
|
1620
1695
|
}))
|
|
1621
1696
|
},
|
|
1622
1697
|
{
|
|
@@ -1680,9 +1755,9 @@ var re = () => {
|
|
|
1680
1755
|
scope: "server",
|
|
1681
1756
|
smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
|
|
1682
1757
|
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",
|
|
1683
|
-
match: ({ file: e, testSources: t }) =>
|
|
1684
|
-
let t =
|
|
1685
|
-
return
|
|
1758
|
+
match: ({ file: e, testSources: t }) => G(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
|
|
1759
|
+
let t = Ut(e);
|
|
1760
|
+
return Kt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
|
|
1686
1761
|
})
|
|
1687
1762
|
},
|
|
1688
1763
|
{
|
|
@@ -1692,15 +1767,15 @@ var re = () => {
|
|
|
1692
1767
|
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",
|
|
1693
1768
|
match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
|
|
1694
1769
|
}
|
|
1695
|
-
],
|
|
1770
|
+
], $t = (e) => q(e), en = (e, t) => {
|
|
1696
1771
|
let n = /* @__PURE__ */ new Set();
|
|
1697
1772
|
for (let r of t) {
|
|
1698
1773
|
let t = S(e, r), i = t.split(/[\\/]/)[0];
|
|
1699
1774
|
n.add(i !== void 0 && i !== t ? i : ".");
|
|
1700
1775
|
}
|
|
1701
1776
|
return [...n].sort();
|
|
1702
|
-
},
|
|
1703
|
-
if (n > 12 ||
|
|
1777
|
+
}, tn = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, nn = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, rn = (e, t = [], n = 0) => {
|
|
1778
|
+
if (n > 12 || K.test(e)) return t;
|
|
1704
1779
|
let r;
|
|
1705
1780
|
try {
|
|
1706
1781
|
r = D(e, { withFileTypes: !0 });
|
|
@@ -1711,12 +1786,12 @@ var re = () => {
|
|
|
1711
1786
|
let r = x(e, i.name);
|
|
1712
1787
|
if (i.isDirectory()) {
|
|
1713
1788
|
if (i.name.startsWith(".")) continue;
|
|
1714
|
-
|
|
1715
|
-
} else
|
|
1789
|
+
rn(r, t, n + 1);
|
|
1790
|
+
} else ut.test(i.name) && t.push(r);
|
|
1716
1791
|
}
|
|
1717
1792
|
return t;
|
|
1718
|
-
},
|
|
1719
|
-
if (n > 12 ||
|
|
1793
|
+
}, q = (e, t = [], n = 0) => {
|
|
1794
|
+
if (n > 12 || K.test(e)) return t;
|
|
1720
1795
|
let r;
|
|
1721
1796
|
try {
|
|
1722
1797
|
r = D(e);
|
|
@@ -1730,11 +1805,11 @@ var re = () => {
|
|
|
1730
1805
|
} catch {
|
|
1731
1806
|
continue;
|
|
1732
1807
|
}
|
|
1733
|
-
a.isDirectory() ?
|
|
1808
|
+
a.isDirectory() ? q(r, t, n + 1) : tn.test(i) && !/\.(test|spec)\./.test(i) && !nn.test(i) && t.push(r);
|
|
1734
1809
|
}
|
|
1735
1810
|
return t;
|
|
1736
|
-
},
|
|
1737
|
-
if (n > 12 ||
|
|
1811
|
+
}, an = (e, t = [], n = 0) => {
|
|
1812
|
+
if (n > 12 || K.test(e)) return t;
|
|
1738
1813
|
let r;
|
|
1739
1814
|
try {
|
|
1740
1815
|
r = D(e);
|
|
@@ -1748,17 +1823,17 @@ var re = () => {
|
|
|
1748
1823
|
} catch {
|
|
1749
1824
|
continue;
|
|
1750
1825
|
}
|
|
1751
|
-
a.isDirectory() ?
|
|
1826
|
+
a.isDirectory() ? an(r, t, n + 1) : tn.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
|
|
1752
1827
|
}
|
|
1753
1828
|
return t;
|
|
1754
|
-
},
|
|
1755
|
-
let t =
|
|
1829
|
+
}, on = (e) => {
|
|
1830
|
+
let t = $t(e);
|
|
1756
1831
|
return {
|
|
1757
|
-
dirs:
|
|
1832
|
+
dirs: en(e, t),
|
|
1758
1833
|
fileCount: t.length
|
|
1759
1834
|
};
|
|
1760
|
-
},
|
|
1761
|
-
let t =
|
|
1835
|
+
}, sn = (e) => {
|
|
1836
|
+
let t = $t(e);
|
|
1762
1837
|
if (t.length === 0) return [];
|
|
1763
1838
|
let n = new A({
|
|
1764
1839
|
useInMemoryFileSystem: !0,
|
|
@@ -1767,8 +1842,8 @@ var re = () => {
|
|
|
1767
1842
|
for (let e of t) try {
|
|
1768
1843
|
i.set(e, E(e, "utf8"));
|
|
1769
1844
|
} catch {}
|
|
1770
|
-
let a =
|
|
1771
|
-
for (let t of
|
|
1845
|
+
let a = Ht([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
|
|
1846
|
+
for (let t of an(e)) try {
|
|
1772
1847
|
o.set(t, E(t, "utf8"));
|
|
1773
1848
|
} catch {}
|
|
1774
1849
|
for (let s of t) {
|
|
@@ -1786,23 +1861,23 @@ var re = () => {
|
|
|
1786
1861
|
} catch {
|
|
1787
1862
|
continue;
|
|
1788
1863
|
}
|
|
1789
|
-
for (let t of
|
|
1790
|
-
if (t.plugin !== void 0 &&
|
|
1864
|
+
for (let t of Qt) {
|
|
1865
|
+
if (t.plugin !== void 0 && Gt(c, t.plugin) || !t.match(c)) continue;
|
|
1791
1866
|
let n = r.get(t.id) ?? [];
|
|
1792
1867
|
n.push(S(e, s)), r.set(t.id, n);
|
|
1793
1868
|
}
|
|
1794
1869
|
}
|
|
1795
|
-
return
|
|
1870
|
+
return Qt.filter((e) => r.has(e.id)).map((e) => ({
|
|
1796
1871
|
rule: e,
|
|
1797
1872
|
files: r.get(e.id)
|
|
1798
1873
|
})).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
|
|
1799
|
-
},
|
|
1800
|
-
let t =
|
|
1874
|
+
}, cn = (e) => {
|
|
1875
|
+
let t = on(e);
|
|
1801
1876
|
return {
|
|
1802
1877
|
root: e,
|
|
1803
1878
|
scannedFiles: t.fileCount,
|
|
1804
1879
|
scannedDirs: t.dirs,
|
|
1805
|
-
findings:
|
|
1880
|
+
findings: sn(e).map(({ rule: e, files: t }) => ({
|
|
1806
1881
|
id: e.id,
|
|
1807
1882
|
scope: e.scope,
|
|
1808
1883
|
smell: e.smell,
|
|
@@ -1810,45 +1885,50 @@ var re = () => {
|
|
|
1810
1885
|
files: t
|
|
1811
1886
|
}))
|
|
1812
1887
|
};
|
|
1813
|
-
},
|
|
1888
|
+
}, ln = 3, un = .2, dn = 50, fn = (e, t) => e >= dn && t > 0 && e / t >= un, pn = (e) => {
|
|
1814
1889
|
let t = /* @__PURE__ */ new Map();
|
|
1815
|
-
for (let n of
|
|
1890
|
+
for (let n of q(e)) try {
|
|
1816
1891
|
t.set(S(e, n), E(n, "utf8"));
|
|
1817
1892
|
} catch {}
|
|
1818
1893
|
let n = /* @__PURE__ */ new Map();
|
|
1819
|
-
for (let t of
|
|
1894
|
+
for (let t of rn(e)) try {
|
|
1820
1895
|
n.set(S(e, t), E(t, "utf8"));
|
|
1821
1896
|
} catch {}
|
|
1822
|
-
let r =
|
|
1897
|
+
let r = dt({
|
|
1823
1898
|
sources: t,
|
|
1824
1899
|
envFiles: n
|
|
1825
1900
|
});
|
|
1826
|
-
r !== void 0 && (console.log(""), console.log(
|
|
1827
|
-
},
|
|
1828
|
-
let t =
|
|
1901
|
+
r !== void 0 && (console.log(""), console.log(ft(r)));
|
|
1902
|
+
}, mn = (e) => {
|
|
1903
|
+
let t = cn(e);
|
|
1829
1904
|
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;
|
|
1830
1905
|
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;
|
|
1831
1906
|
console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
|
|
1832
1907
|
let n = 0;
|
|
1833
1908
|
for (let e of ["server", "client"]) {
|
|
1834
1909
|
let r = t.findings.filter((t) => t.scope === e);
|
|
1835
|
-
if (r.length
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
let
|
|
1841
|
-
console.log(` ${e.smell} \u2014 ${e.files.length} file(s)
|
|
1910
|
+
if (r.length === 0) continue;
|
|
1911
|
+
console.log(` [${e}]`);
|
|
1912
|
+
let i = [...r].sort((e, t) => e.files.length - t.files.length);
|
|
1913
|
+
for (let e of i) {
|
|
1914
|
+
if (fn(e.files.length, t.scannedFiles)) {
|
|
1915
|
+
let n = Math.round(e.files.length / t.scannedFiles * 100);
|
|
1916
|
+
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.)");
|
|
1917
|
+
continue;
|
|
1842
1918
|
}
|
|
1919
|
+
let r = e.files.slice(0, ln).join(", "), i = e.files.length - ln;
|
|
1920
|
+
i > 0 && (n += i);
|
|
1921
|
+
let a = i > 0 ? `, +${i} more` : "";
|
|
1922
|
+
console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
|
|
1843
1923
|
}
|
|
1844
1924
|
}
|
|
1845
1925
|
return n > 0 && console.log(` (${n} more file path${n === 1 ? "" : "s"} not shown \u2014 \`voltro doctor --json\` prints every one.)`), console.log(" (See the \"Pick the SERVER primitive\" / \"Pick the CLIENT primitive\" rubrics in AGENTS.md / CLAUDE.md.)"), t.findings.length;
|
|
1846
|
-
},
|
|
1847
|
-
let t =
|
|
1926
|
+
}, hn = "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.", gn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, _n = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, vn = (e) => {
|
|
1927
|
+
let t = gn.exec(e)?.[1];
|
|
1848
1928
|
if (t !== void 0) return t === "ssr" ? "ssr" : null;
|
|
1849
|
-
let n =
|
|
1929
|
+
let n = _n.exec(e)?.[1];
|
|
1850
1930
|
return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
|
|
1851
|
-
},
|
|
1931
|
+
}, yn = async (e) => {
|
|
1852
1932
|
let n = x(e, "src", "pages");
|
|
1853
1933
|
if (!T(n)) return [];
|
|
1854
1934
|
let { pages: a, dirs: o } = await t(n), s = [];
|
|
@@ -1861,7 +1941,7 @@ var re = () => {
|
|
|
1861
1941
|
continue;
|
|
1862
1942
|
}
|
|
1863
1943
|
if (r.test(a)) continue;
|
|
1864
|
-
let c =
|
|
1944
|
+
let c = vn(a);
|
|
1865
1945
|
c !== null && s.push({
|
|
1866
1946
|
file: S(e, x(n, t.file)),
|
|
1867
1947
|
pattern: t.pattern,
|
|
@@ -1869,18 +1949,18 @@ var re = () => {
|
|
|
1869
1949
|
});
|
|
1870
1950
|
}
|
|
1871
1951
|
return s;
|
|
1872
|
-
},
|
|
1952
|
+
}, bn = 10, xn = async (e) => {
|
|
1873
1953
|
if (console.log(""), !T(x(e, "src", "pages"))) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
|
|
1874
|
-
let t = await
|
|
1954
|
+
let t = await yn(e);
|
|
1875
1955
|
if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
|
|
1876
1956
|
console.log(`• renderMode:'spa' candidates (${t.length} page${t.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
|
|
1877
|
-
for (let e of t.slice(0,
|
|
1957
|
+
for (let e of t.slice(0, bn)) {
|
|
1878
1958
|
let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
|
|
1879
1959
|
console.log(` ${e.file} (${e.pattern}) — ${t}`);
|
|
1880
1960
|
}
|
|
1881
|
-
let n = t.length -
|
|
1882
|
-
return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${
|
|
1883
|
-
},
|
|
1961
|
+
let n = t.length - bn;
|
|
1962
|
+
return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${hn}`), t.length;
|
|
1963
|
+
}, Sn = /* @__PURE__ */ new Set([
|
|
1884
1964
|
"node_modules",
|
|
1885
1965
|
"dist",
|
|
1886
1966
|
"build",
|
|
@@ -1888,7 +1968,7 @@ var re = () => {
|
|
|
1888
1968
|
".vite",
|
|
1889
1969
|
".framework",
|
|
1890
1970
|
".git"
|
|
1891
|
-
]),
|
|
1971
|
+
]), Cn = (e, t, n) => {
|
|
1892
1972
|
let r;
|
|
1893
1973
|
try {
|
|
1894
1974
|
r = D(e, { withFileTypes: !0 });
|
|
@@ -1896,11 +1976,11 @@ var re = () => {
|
|
|
1896
1976
|
return;
|
|
1897
1977
|
}
|
|
1898
1978
|
for (let i of r) {
|
|
1899
|
-
if (
|
|
1979
|
+
if (Sn.has(i.name)) continue;
|
|
1900
1980
|
let r = x(e, i.name);
|
|
1901
|
-
i.isDirectory() ?
|
|
1981
|
+
i.isDirectory() ? Cn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
|
|
1902
1982
|
}
|
|
1903
|
-
},
|
|
1983
|
+
}, wn = (e, t) => {
|
|
1904
1984
|
let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
|
|
1905
1985
|
for (let i of t) {
|
|
1906
1986
|
let t;
|
|
@@ -1925,16 +2005,16 @@ var re = () => {
|
|
|
1925
2005
|
}
|
|
1926
2006
|
}
|
|
1927
2007
|
return n;
|
|
1928
|
-
},
|
|
2008
|
+
}, Tn = (e, t) => {
|
|
1929
2009
|
let n = e.slice(t.length).split(w).slice(0, -1)[0];
|
|
1930
2010
|
return n !== void 0 && n.startsWith("[");
|
|
1931
|
-
},
|
|
2011
|
+
}, J = (t) => {
|
|
1932
2012
|
let n = [], r = x(t, "src", "pages"), i = x(t, "src");
|
|
1933
2013
|
if (!T(i)) return n;
|
|
1934
2014
|
let a = [];
|
|
1935
|
-
|
|
2015
|
+
Cn(i, t, a), a.sort();
|
|
1936
2016
|
let o = `${S(t, r)}${w}`;
|
|
1937
|
-
|
|
2017
|
+
wn(t, a);
|
|
1938
2018
|
for (let r of a) {
|
|
1939
2019
|
let i = r.slice(r.lastIndexOf(w) + 1);
|
|
1940
2020
|
if (r.startsWith(o)) {
|
|
@@ -1942,7 +2022,7 @@ var re = () => {
|
|
|
1942
2022
|
T(x(t, e(r))) || n.push({
|
|
1943
2023
|
kind: "page-without-test",
|
|
1944
2024
|
file: r
|
|
1945
|
-
}),
|
|
2025
|
+
}), Tn(r, o) && n.push({
|
|
1946
2026
|
kind: "top-level-dynamic-route",
|
|
1947
2027
|
file: r
|
|
1948
2028
|
});
|
|
@@ -1954,33 +2034,33 @@ var re = () => {
|
|
|
1954
2034
|
});
|
|
1955
2035
|
}
|
|
1956
2036
|
return n;
|
|
1957
|
-
},
|
|
2037
|
+
}, Y = 10, X = (e, t, n) => {
|
|
1958
2038
|
if (t.length === 0) return;
|
|
1959
2039
|
console.log(`• ${e} (${t.length}):`);
|
|
1960
|
-
for (let e of t.slice(0,
|
|
1961
|
-
let r = t.length -
|
|
2040
|
+
for (let e of t.slice(0, Y)) console.log(` ${e}`);
|
|
2041
|
+
let r = t.length - Y;
|
|
1962
2042
|
r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
|
|
1963
|
-
},
|
|
2043
|
+
}, En = (e) => {
|
|
1964
2044
|
if (console.log(""), !T(x(e, "src", "pages"))) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
|
|
1965
|
-
let t =
|
|
2045
|
+
let t = J(e);
|
|
1966
2046
|
if (t.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
|
|
1967
2047
|
let n = (e) => t.filter((t) => t.kind === e).map((e) => e.file);
|
|
1968
|
-
return
|
|
1969
|
-
},
|
|
1970
|
-
let t =
|
|
2048
|
+
return X("will NEVER route — page.tsx outside src/pages/", n("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."), X("pages with no test beside them", n("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."), X("routes whose FIRST segment is dynamic — they claim every URL of that depth", n("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."), t.length;
|
|
2049
|
+
}, Dn = "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.", On = /* @__PURE__ */ new Map(), Z = (e) => {
|
|
2050
|
+
let t = On.get(e);
|
|
1971
2051
|
if (t !== void 0) return t;
|
|
1972
2052
|
let n = (async () => {
|
|
1973
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
2053
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-DXhhZ7-f.js"), r = await t(e);
|
|
1974
2054
|
return {
|
|
1975
2055
|
files: r,
|
|
1976
2056
|
discovered: await n(r)
|
|
1977
2057
|
};
|
|
1978
2058
|
})();
|
|
1979
|
-
return
|
|
1980
|
-
},
|
|
2059
|
+
return On.set(e, n), n;
|
|
2060
|
+
}, kn = async (e) => {
|
|
1981
2061
|
let t;
|
|
1982
2062
|
try {
|
|
1983
|
-
let { files: n, discovered: r } = await
|
|
2063
|
+
let { files: n, discovered: r } = await Z(e), i = r.tables.map((e) => {
|
|
1984
2064
|
let t = e;
|
|
1985
2065
|
return {
|
|
1986
2066
|
name: t.tableName,
|
|
@@ -1988,12 +2068,12 @@ var re = () => {
|
|
|
1988
2068
|
};
|
|
1989
2069
|
});
|
|
1990
2070
|
if (i.length === 0) return;
|
|
1991
|
-
t = Ge(
|
|
2071
|
+
t = Ke(Ge(n, i, e));
|
|
1992
2072
|
} catch {
|
|
1993
2073
|
return;
|
|
1994
2074
|
}
|
|
1995
2075
|
t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
|
|
1996
|
-
},
|
|
2076
|
+
}, An = (e) => e.map((e) => {
|
|
1997
2077
|
let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
|
|
1998
2078
|
name: e,
|
|
1999
2079
|
type: typeof t?.type == "string" ? t.type : ""
|
|
@@ -2003,22 +2083,22 @@ var re = () => {
|
|
|
2003
2083
|
columns: n,
|
|
2004
2084
|
primaryKey: t.appliedPrimaryKey ?? []
|
|
2005
2085
|
};
|
|
2006
|
-
}),
|
|
2086
|
+
}), jn = async (e) => {
|
|
2007
2087
|
try {
|
|
2008
|
-
let { discovered: t } = await
|
|
2009
|
-
return
|
|
2088
|
+
let { discovered: t } = await Z(e);
|
|
2089
|
+
return Ze(An(t.tables));
|
|
2010
2090
|
} catch {
|
|
2011
2091
|
return null;
|
|
2012
2092
|
}
|
|
2013
|
-
},
|
|
2014
|
-
let t = await
|
|
2093
|
+
}, Mn = async (e) => {
|
|
2094
|
+
let t = await jn(e);
|
|
2015
2095
|
if (t === null || t.tablesChecked === 0) return;
|
|
2016
|
-
let n =
|
|
2096
|
+
let n = $e(t);
|
|
2017
2097
|
n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
|
|
2018
|
-
},
|
|
2098
|
+
}, Nn = async (e) => {
|
|
2019
2099
|
try {
|
|
2020
|
-
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await
|
|
2021
|
-
return
|
|
2100
|
+
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await Z(e);
|
|
2101
|
+
return rt(n.events.map((e) => {
|
|
2022
2102
|
let n = e.descriptor, r = t(n.key);
|
|
2023
2103
|
return {
|
|
2024
2104
|
name: n.name,
|
|
@@ -2030,19 +2110,19 @@ var re = () => {
|
|
|
2030
2110
|
} catch {
|
|
2031
2111
|
return null;
|
|
2032
2112
|
}
|
|
2033
|
-
},
|
|
2034
|
-
let t = await
|
|
2113
|
+
}, Pn = async (e) => {
|
|
2114
|
+
let t = await Nn(e);
|
|
2035
2115
|
if (t === null || t.eventsChecked === 0) return;
|
|
2036
|
-
let n =
|
|
2116
|
+
let n = it(t);
|
|
2037
2117
|
n !== null && (console.log(""), console.log(`• ${n}`));
|
|
2038
|
-
let r =
|
|
2118
|
+
let r = at(t);
|
|
2039
2119
|
r !== null && (console.log(""), console.log(`⚠️ ${r}`));
|
|
2040
|
-
},
|
|
2120
|
+
}, Fn = (e) => {
|
|
2041
2121
|
let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
|
|
2042
2122
|
return r === void 0 ? n : `${r}: ${n}`;
|
|
2043
|
-
},
|
|
2123
|
+
}, In = async (e) => {
|
|
2044
2124
|
try {
|
|
2045
|
-
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await
|
|
2125
|
+
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await Z(e)).discovered.serverOnlyLeaks, r = t(n);
|
|
2046
2126
|
return {
|
|
2047
2127
|
status: {
|
|
2048
2128
|
checked: !0,
|
|
@@ -2053,17 +2133,17 @@ var re = () => {
|
|
|
2053
2133
|
} catch (e) {
|
|
2054
2134
|
return { status: {
|
|
2055
2135
|
checked: !1,
|
|
2056
|
-
reason:
|
|
2136
|
+
reason: Fn(e)
|
|
2057
2137
|
} };
|
|
2058
2138
|
}
|
|
2059
|
-
},
|
|
2060
|
-
let { status: t, message: n } = await
|
|
2139
|
+
}, Ln = async (e) => {
|
|
2140
|
+
let { status: t, message: n } = await In(e);
|
|
2061
2141
|
return t.checked ? n === void 0 ? !1 : (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);
|
|
2062
|
-
},
|
|
2142
|
+
}, Rn = async (e) => {
|
|
2063
2143
|
let t;
|
|
2064
2144
|
try {
|
|
2065
|
-
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await
|
|
2066
|
-
t =
|
|
2145
|
+
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await Z(e);
|
|
2146
|
+
t = ct(r, i.workflows.map((e) => {
|
|
2067
2147
|
let t = e.definition, r = t.payloadSchema === void 0 ? {
|
|
2068
2148
|
required: [],
|
|
2069
2149
|
optional: []
|
|
@@ -2077,21 +2157,46 @@ var re = () => {
|
|
|
2077
2157
|
} catch {
|
|
2078
2158
|
return;
|
|
2079
2159
|
}
|
|
2080
|
-
let n =
|
|
2160
|
+
let n = lt(t);
|
|
2081
2161
|
if (n === null) return;
|
|
2082
2162
|
console.log("");
|
|
2083
2163
|
let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
|
|
2084
2164
|
console.log(`${r} ${n}`);
|
|
2085
|
-
},
|
|
2165
|
+
}, Q = "voltro-authz-allowlist.txt", zn = (e, t = (e) => {
|
|
2086
2166
|
try {
|
|
2087
2167
|
return E(e, "utf8");
|
|
2088
2168
|
} catch {
|
|
2089
2169
|
return "";
|
|
2090
2170
|
}
|
|
2091
|
-
}) => /* @__PURE__ */ new Set([...
|
|
2092
|
-
let
|
|
2171
|
+
}) => /* @__PURE__ */ new Set([...Ct, ...wt(q(e).map(t))]), Bn = async (e) => {
|
|
2172
|
+
let t = ["src/locales", "locales"], n, r = [];
|
|
2173
|
+
for (let i of t) {
|
|
2174
|
+
let t = x(e, i);
|
|
2175
|
+
if (T(t)) try {
|
|
2176
|
+
r = D(t).filter((e) => /\.tsx?$/.test(e) && !e.endsWith(".d.ts")).map((e) => e.replace(/\.tsx?$/, "")), n = i;
|
|
2177
|
+
break;
|
|
2178
|
+
} catch {}
|
|
2179
|
+
}
|
|
2180
|
+
if (n === void 0 || r.length === 0) return;
|
|
2181
|
+
let i = [];
|
|
2093
2182
|
try {
|
|
2094
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2183
|
+
let { loadApiConfig: t } = await import("./dev-DXhhZ7-f.js");
|
|
2184
|
+
i = (await t(e))?.locales ?? [];
|
|
2185
|
+
} catch {
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
let a = bt({
|
|
2189
|
+
catalogCodes: r,
|
|
2190
|
+
declaredLocales: i
|
|
2191
|
+
});
|
|
2192
|
+
if (a !== void 0) {
|
|
2193
|
+
console.log("");
|
|
2194
|
+
for (let e of xt(a, n)) console.log(e);
|
|
2195
|
+
}
|
|
2196
|
+
}, Vn = async (e) => {
|
|
2197
|
+
let { discovered: t } = await Z(e), n = [];
|
|
2198
|
+
try {
|
|
2199
|
+
let { loadApiConfig: t } = await import("./dev-DXhhZ7-f.js");
|
|
2095
2200
|
n = ((await t(e))?.plugins ?? []).map((e) => {
|
|
2096
2201
|
let t = (e.extendSchema?.tables ?? []).map((e) => {
|
|
2097
2202
|
let t = e;
|
|
@@ -2107,8 +2212,8 @@ var re = () => {
|
|
|
2107
2212
|
plugin: e.name,
|
|
2108
2213
|
tables: t,
|
|
2109
2214
|
rpcTags: n,
|
|
2110
|
-
canDisableTables:
|
|
2111
|
-
canAlias:
|
|
2215
|
+
canDisableTables: Hn.has(e.name),
|
|
2216
|
+
canAlias: Un.has(e.name)
|
|
2112
2217
|
};
|
|
2113
2218
|
});
|
|
2114
2219
|
} catch {
|
|
@@ -2120,89 +2225,115 @@ var re = () => {
|
|
|
2120
2225
|
...t.mutations,
|
|
2121
2226
|
...t.actions,
|
|
2122
2227
|
...t.streams ?? []
|
|
2123
|
-
].map((e) => String(e.descriptor.name)), i =
|
|
2228
|
+
].map((e) => String(e.descriptor.name)), i = It(n, {
|
|
2124
2229
|
tables: t.tables.map((e) => e.tableName),
|
|
2125
2230
|
rpcTags: r
|
|
2126
2231
|
});
|
|
2127
2232
|
console.log();
|
|
2128
|
-
for (let e of
|
|
2129
|
-
},
|
|
2130
|
-
let
|
|
2131
|
-
|
|
2132
|
-
|
|
2233
|
+
for (let e of Lt(n, i)) console.log(e);
|
|
2234
|
+
}, Hn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Un = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Wn = async (e) => {
|
|
2235
|
+
let t = await Gn(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;
|
|
2236
|
+
return {
|
|
2237
|
+
executors: t.executorCount,
|
|
2238
|
+
guardVocabulary: [...t.vocabulary].sort(),
|
|
2239
|
+
counts: t.result.counts,
|
|
2240
|
+
allowlist: {
|
|
2241
|
+
exists: t.existing !== void 0,
|
|
2242
|
+
debt: r,
|
|
2243
|
+
reviewed: t.result.counts.allowlisted - r
|
|
2244
|
+
},
|
|
2245
|
+
unchecked: t.result.unchecked
|
|
2246
|
+
};
|
|
2247
|
+
}, Gn = async (e) => {
|
|
2248
|
+
let { discovered: t } = await Z(e), n = /* @__PURE__ */ new Map(), r = (e) => {
|
|
2249
|
+
if (!n.has(e)) try {
|
|
2250
|
+
n.set(e, E(e, "utf8"));
|
|
2133
2251
|
} catch {
|
|
2134
|
-
|
|
2252
|
+
n.set(e, "");
|
|
2135
2253
|
}
|
|
2136
|
-
return
|
|
2137
|
-
},
|
|
2138
|
-
for (let e of
|
|
2254
|
+
return n.get(e);
|
|
2255
|
+
}, i = zn(e, r), a = /* @__PURE__ */ new Set();
|
|
2256
|
+
for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
|
|
2139
2257
|
let e = t.references?.();
|
|
2140
|
-
e &&
|
|
2258
|
+
e && a.add(e.tableName);
|
|
2141
2259
|
} catch {}
|
|
2142
|
-
let
|
|
2260
|
+
let o = [
|
|
2143
2261
|
{
|
|
2144
|
-
list:
|
|
2262
|
+
list: t.queries,
|
|
2145
2263
|
op: "read"
|
|
2146
2264
|
},
|
|
2147
2265
|
{
|
|
2148
|
-
list:
|
|
2266
|
+
list: t.mutations,
|
|
2149
2267
|
op: "update"
|
|
2150
2268
|
},
|
|
2151
2269
|
{
|
|
2152
|
-
list:
|
|
2270
|
+
list: t.actions,
|
|
2153
2271
|
op: "update"
|
|
2154
2272
|
},
|
|
2155
2273
|
{
|
|
2156
|
-
list:
|
|
2274
|
+
list: t.streams,
|
|
2157
2275
|
op: "read"
|
|
2158
2276
|
}
|
|
2159
2277
|
].flatMap(({ list: e, op: t }) => e.map((e) => {
|
|
2160
|
-
let n = e.descriptor,
|
|
2278
|
+
let n = e.descriptor, i = n.target, a = Array.isArray(i) ? i[0] : i, o = e.file.replace(/\.(tsx?)$/, ".server.$1");
|
|
2161
2279
|
return {
|
|
2162
2280
|
tag: String(n.name ?? e.file),
|
|
2163
2281
|
path: o,
|
|
2164
|
-
src:
|
|
2282
|
+
src: r(o),
|
|
2165
2283
|
hasDeclaredGuards: Array.isArray(n.guards) && n.guards.length > 0,
|
|
2166
2284
|
op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
|
|
2167
2285
|
...a?.table === void 0 ? {} : { table: a.table }
|
|
2168
2286
|
};
|
|
2169
|
-
})),
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2287
|
+
})), s = x(e, Q), c = T(s) ? Pt(E(s, "utf8")) : void 0;
|
|
2288
|
+
return {
|
|
2289
|
+
result: At({
|
|
2290
|
+
executors: o,
|
|
2291
|
+
guardVocabulary: i,
|
|
2292
|
+
tenantScopedTables: t.tenantScopedTables,
|
|
2293
|
+
referencedTables: a,
|
|
2294
|
+
allowlist: c?.tags ?? /* @__PURE__ */ new Set()
|
|
2295
|
+
}),
|
|
2296
|
+
vocabulary: i,
|
|
2297
|
+
existing: c,
|
|
2298
|
+
reviewed: (c?.entries ?? []).filter((e) => e.reviewed !== void 0),
|
|
2299
|
+
executorCount: o.length,
|
|
2300
|
+
allowPath: s
|
|
2301
|
+
};
|
|
2302
|
+
}, Kn = async (e, t) => {
|
|
2303
|
+
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s } = await Gn(e), c = n.counts;
|
|
2304
|
+
console.log(`\nauthz scan · ${o} executor(s)`);
|
|
2305
|
+
let l = [...r].filter((e) => !Ct.has(e)).sort();
|
|
2306
|
+
console.log(l.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${l.length} from your source (${l.slice(0, 6).join(", ")}${l.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(c.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(c.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(c.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(c.vocabularyGuard).padStart(4)}`);
|
|
2307
|
+
let u = new Set(a.map((e) => e.tag)), d = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !u.has(e.tag)).length, f = c.allowlisted - d;
|
|
2308
|
+
if (console.log(` – accepted as recorded debt ${String(d).padStart(4)} (${Q})`), console.log(` ✓ reviewed, open on purpose ${String(f).padStart(4)} (\`reviewed=<why>\` in that file)`), t) return ne(s, Mt([...a, ...n.unchecked.map((e) => ({ tag: e.tag }))]), "utf8"), console.log(`\nwrote ${n.unchecked.length} debt tag(s) to ${Q}${a.length > 0 ? `, keeping ${a.length} reviewed line(s)` : ""}. Doctor now fails on ADDITIONS only.`), !1;
|
|
2309
|
+
if (n.unchecked.length === 0) return !1;
|
|
2180
2310
|
console.log("");
|
|
2181
|
-
for (let e of
|
|
2182
|
-
return
|
|
2183
|
-
},
|
|
2184
|
-
if (e.includes("--help") || e.includes("-h")) return console.log(
|
|
2311
|
+
for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
|
|
2312
|
+
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 ${Q} 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 ${Q}.`), 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);
|
|
2313
|
+
}, qn = async (e) => {
|
|
2314
|
+
if (e.includes("--help") || e.includes("-h")) return console.log(Dn), 0;
|
|
2185
2315
|
let t = e.includes("--strict"), n = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
|
|
2186
2316
|
if (ie(e)) return re(), M({
|
|
2187
|
-
...
|
|
2188
|
-
spaCandidates: await
|
|
2189
|
-
pageConvention:
|
|
2190
|
-
fileTaxonomy:
|
|
2191
|
-
junctionFks: (await
|
|
2192
|
-
eventDelivery: await
|
|
2193
|
-
duplicateInstances:
|
|
2194
|
-
serverOnly: (await
|
|
2317
|
+
...cn(n),
|
|
2318
|
+
spaCandidates: await yn(n),
|
|
2319
|
+
pageConvention: J(n),
|
|
2320
|
+
fileTaxonomy: H(n).findings,
|
|
2321
|
+
junctionFks: (await jn(n))?.issues ?? [],
|
|
2322
|
+
eventDelivery: await Nn(n),
|
|
2323
|
+
duplicateInstances: vt(n),
|
|
2324
|
+
serverOnly: (await In(n)).status,
|
|
2325
|
+
authz: await Wn(n)
|
|
2195
2326
|
}), 0;
|
|
2196
|
-
let r =
|
|
2197
|
-
|
|
2198
|
-
let i = await
|
|
2327
|
+
let r = Vt(n, t);
|
|
2328
|
+
mn(n), pn(n), En(n), Ve(n), await xn(n), await Rn(n), await kn(n), await Mn(n), await Pn(n), await Vn(n), await Bn(n);
|
|
2329
|
+
let i = await Ln(n), a = await Kn(n, e.includes("--write-authz-allowlist")), o = yt(vt(n));
|
|
2199
2330
|
return o !== void 0 && console.log(`\n${o}`), i || a ? Math.max(r, 1) : r;
|
|
2200
|
-
},
|
|
2331
|
+
}, Jn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Yn = /* @__PURE__ */ new Set([
|
|
2201
2332
|
"insert",
|
|
2202
2333
|
"update",
|
|
2203
2334
|
"delete",
|
|
2204
2335
|
"upsert"
|
|
2205
|
-
]),
|
|
2336
|
+
]), Xn = (e, t) => {
|
|
2206
2337
|
let n = [], r = new Set(t.exercised), i = /* @__PURE__ */ new Map();
|
|
2207
2338
|
for (let e of t.edges) {
|
|
2208
2339
|
let t = i.get(e.tag);
|
|
@@ -2214,8 +2345,8 @@ var re = () => {
|
|
|
2214
2345
|
a.push(t.tag);
|
|
2215
2346
|
continue;
|
|
2216
2347
|
}
|
|
2217
|
-
let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(
|
|
2218
|
-
for (let t of c) if (
|
|
2348
|
+
let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(Jn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
|
|
2349
|
+
for (let t of c) if (Yn.has(t.op)) {
|
|
2219
2350
|
let r = u.get(t.table);
|
|
2220
2351
|
r === void 0 ? n.push({
|
|
2221
2352
|
rule: "observed/undeclared-write",
|
|
@@ -2271,7 +2402,7 @@ var re = () => {
|
|
|
2271
2402
|
total: e.length
|
|
2272
2403
|
}
|
|
2273
2404
|
};
|
|
2274
|
-
},
|
|
2405
|
+
}, Zn = (e, t = [], n = 0) => {
|
|
2275
2406
|
if (n > 12) return t;
|
|
2276
2407
|
let r;
|
|
2277
2408
|
try {
|
|
@@ -2289,30 +2420,30 @@ var re = () => {
|
|
|
2289
2420
|
".git",
|
|
2290
2421
|
"coverage"
|
|
2291
2422
|
].includes(i)) continue;
|
|
2292
|
-
let r = `${e}/${i}`,
|
|
2423
|
+
let r = `${e}/${i}`, o;
|
|
2293
2424
|
try {
|
|
2294
|
-
|
|
2425
|
+
o = O(r);
|
|
2295
2426
|
} catch {
|
|
2296
2427
|
continue;
|
|
2297
2428
|
}
|
|
2298
|
-
|
|
2429
|
+
o.isDirectory() ? Zn(r, t, n + 1) : o.isFile() && a.test(i) && t.push(r);
|
|
2299
2430
|
}
|
|
2300
2431
|
return t;
|
|
2301
|
-
},
|
|
2432
|
+
}, Qn = /* @__PURE__ */ new Set(["actors", "tenants"]), $n = async (e) => {
|
|
2302
2433
|
try {
|
|
2303
2434
|
let { readFile: t } = await import("node:fs/promises"), { join: n } = await import("node:path"), r = await t(n(e, "app.graph.observed.generated.json"), "utf8"), i = JSON.parse(r);
|
|
2304
2435
|
return i?.version !== 1 || !Array.isArray(i.edges) || !Array.isArray(i.exercised) ? null : i;
|
|
2305
2436
|
} catch {
|
|
2306
2437
|
return null;
|
|
2307
2438
|
}
|
|
2308
|
-
},
|
|
2439
|
+
}, er = (e) => {
|
|
2309
2440
|
let { exercised: t, total: n } = e.coverage, r = `\nobserved: ${t}/${n} procedures exercised (${n === 0 ? 0 : Math.round(t / n * 100)}%)`;
|
|
2310
2441
|
if (e.diagnostics.length === 0) return `${r}\n no declared/observed mismatches${e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : ""}`;
|
|
2311
2442
|
let i = e.diagnostics.map((e) => ` ${e.node}${e.file === void 0 ? "" : ` (${e.file})`}\n` + e.breaks.map((e) => ` ${e}`).join("\n") + `\n fix: ${e.fix}`), a = e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : "";
|
|
2312
2443
|
return `${r}\n${i.join("\n")}${a}`;
|
|
2313
|
-
},
|
|
2444
|
+
}, tr = (e, t) => ({
|
|
2314
2445
|
tables: (t.tables ?? []).map((e) => e.name),
|
|
2315
|
-
frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") ||
|
|
2446
|
+
frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || Qn.has(e.name)).map((e) => e.name),
|
|
2316
2447
|
scopes: e.scopes ?? [],
|
|
2317
2448
|
procedures: (e.procedures ?? []).map((e) => {
|
|
2318
2449
|
let t = (e.guards ?? []).flatMap((e) => e.kind === "scope" ? e.scope ?? [] : []);
|
|
@@ -2325,16 +2456,16 @@ var re = () => {
|
|
|
2325
2456
|
...e.guards === void 0 ? {} : { hasGuards: e.guards.length > 0 }
|
|
2326
2457
|
};
|
|
2327
2458
|
})
|
|
2328
|
-
}),
|
|
2459
|
+
}), nr = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
|
|
2329
2460
|
let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
|
|
2330
2461
|
return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
|
|
2331
|
-
}).join("\n"),
|
|
2332
|
-
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-
|
|
2462
|
+
}).join("\n"), rr = async (e) => {
|
|
2463
|
+
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-DXhhZ7-f.js"), { buildRpcEntry: i } = await import("./manifestBuild-Dj8Jjoto.js"), { buildCapabilityManifest: a } = await import("./inspect-mmBuRXmy.js"), { relative: o } = await import("node:path"), s = await n(await t(e)), c = (t, n, r) => i(t, n, o(e, r)), l = [
|
|
2333
2464
|
...s.queries.map((e) => c(e.descriptor, "query", e.file)),
|
|
2334
2465
|
...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
|
|
2335
2466
|
...s.actions.map((e) => c(e.descriptor, "action", e.file)),
|
|
2336
2467
|
...s.streams.map((e) => c(e.descriptor, "stream", e.file))
|
|
2337
|
-
], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-
|
|
2468
|
+
], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-Bn_c4Mnl.js"), d = await u({ root: e }).catch(() => []), f = new Set(s.tables.map((e) => e.tableName)), p = [...[...f].map((e) => ({ name: e })), ...d.map((e) => e.tableName).filter((e) => !f.has(e)).map((e) => ({
|
|
2338
2469
|
name: e,
|
|
2339
2470
|
framework: !0
|
|
2340
2471
|
}))], m = await r(e).then((e) => e?.plugins ?? []).catch(() => []);
|
|
@@ -2345,21 +2476,21 @@ var re = () => {
|
|
|
2345
2476
|
})), [], m),
|
|
2346
2477
|
tables: { tables: p }
|
|
2347
2478
|
};
|
|
2348
|
-
},
|
|
2479
|
+
}, ir = async (e) => {
|
|
2349
2480
|
let t = ue(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
|
|
2350
2481
|
if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
|
|
2351
|
-
let { process: n, target: r, rest: i } = de(t.rest),
|
|
2482
|
+
let { process: n, target: r, rest: i } = de(t.rest), a;
|
|
2352
2483
|
for (let e = 0; e < i.length; e++) if (i[e] === "--diff") {
|
|
2353
2484
|
let [t, n] = (i[++e] ?? "").split(":");
|
|
2354
|
-
if (t === "removeTable" && n)
|
|
2485
|
+
if (t === "removeTable" && n) a = {
|
|
2355
2486
|
kind: "removeTable",
|
|
2356
2487
|
name: n
|
|
2357
2488
|
};
|
|
2358
|
-
else if (t === "removeProcedure" && n)
|
|
2489
|
+
else if (t === "removeProcedure" && n) a = {
|
|
2359
2490
|
kind: "removeProcedure",
|
|
2360
2491
|
tag: n
|
|
2361
2492
|
};
|
|
2362
|
-
else if (t === "removeScope" && n)
|
|
2493
|
+
else if (t === "removeScope" && n) a = {
|
|
2363
2494
|
kind: "removeScope",
|
|
2364
2495
|
name: n
|
|
2365
2496
|
};
|
|
@@ -2369,7 +2500,7 @@ var re = () => {
|
|
|
2369
2500
|
if (l.length === 0) {
|
|
2370
2501
|
if (!c && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
|
|
2371
2502
|
try {
|
|
2372
|
-
let e = await
|
|
2503
|
+
let e = await rr(C(s));
|
|
2373
2504
|
u = e.manifest, d = e.tables;
|
|
2374
2505
|
} catch (e) {
|
|
2375
2506
|
return console.error(`voltro check: could not assemble the graph from source: ${e.message}`), console.error(" Start `voltro dev` to check against a running api instead."), 1;
|
|
@@ -2379,21 +2510,21 @@ var re = () => {
|
|
|
2379
2510
|
if (t.kind === "err") return console.error(`voltro check: manifest fetch failed: ${t.error}`), 1;
|
|
2380
2511
|
u = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
|
|
2381
2512
|
}
|
|
2382
|
-
let f =
|
|
2513
|
+
let f = tr(u, d), p = he(f, a), m = [...H(s).findings.map((e) => ({
|
|
2383
2514
|
rule: e.rule,
|
|
2384
2515
|
node: e.file,
|
|
2385
2516
|
file: e.file,
|
|
2386
2517
|
breaks: [e.detail],
|
|
2387
2518
|
fix: e.fix,
|
|
2388
2519
|
severity: e.severity
|
|
2389
|
-
})), ...
|
|
2520
|
+
})), ...J(s).map((e) => ({
|
|
2390
2521
|
rule: `page/${e.kind}`,
|
|
2391
2522
|
node: e.file,
|
|
2392
2523
|
file: e.file,
|
|
2393
2524
|
breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
|
|
2394
2525
|
fix: e.kind === "suffixed-outside-pages" ? "move it under src/pages/ into the directory whose route it serves, or rename it — page.tsx is reserved" : "write the test with the file — the name is derivable",
|
|
2395
2526
|
severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
|
|
2396
|
-
}))], h = (await
|
|
2527
|
+
}))], h = (await o(s, Zn(s))).map((e) => ({
|
|
2397
2528
|
rule: "client/not-browser-safe",
|
|
2398
2529
|
node: e.file,
|
|
2399
2530
|
file: e.file,
|
|
@@ -2404,7 +2535,7 @@ var re = () => {
|
|
|
2404
2535
|
...p.diagnostics,
|
|
2405
2536
|
...m,
|
|
2406
2537
|
...h
|
|
2407
|
-
], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await
|
|
2538
|
+
], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await $n(C(s)), y = v === null ? null : Xn(f.procedures, v);
|
|
2408
2539
|
return t.format === "json" ? M(y === null ? {
|
|
2409
2540
|
...p,
|
|
2410
2541
|
ok: _,
|
|
@@ -2414,7 +2545,7 @@ var re = () => {
|
|
|
2414
2545
|
ok: _,
|
|
2415
2546
|
diagnostics: g,
|
|
2416
2547
|
observed: y
|
|
2417
|
-
}) : (console.log(
|
|
2548
|
+
}) : (console.log(nr(g)), y !== null && console.log(er(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
|
|
2418
2549
|
};
|
|
2419
2550
|
//#endregion
|
|
2420
|
-
export { re as _,
|
|
2551
|
+
export { re as _, Re as a, I as c, N as d, ue as f, le as g, ce as h, Vt as i, ve as l, se as m, ir as n, be as o, de as p, qn as r, xe as s, rr as t, he as u, M as v, ie as y };
|