@voltro/cli 0.30.0 → 0.30.2
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 +119 -0
- package/dist/{apiBuild-BceikpQf.js → apiBuild-BUS1RfPi.js} +2 -2
- package/dist/apiBuild-DMlxvL8h.js +2 -0
- package/dist/bin.js +3 -3
- package/dist/checkCommand-CARZ36Tr.js +2 -0
- package/dist/{checkCommand-BMr-X48w.js → checkCommand-D_wdOFhw.js} +498 -321
- package/dist/{commands-D4Y1-Nlu.js → commands-BgGAUcku.js} +2392 -2390
- package/dist/{dbCommand-C7hbl5pk.js → dbCommand-CbPRCD1x.js} +56 -19
- package/dist/dbCommand-Ht47LrV-.js +2 -0
- package/dist/{dev-DmnkH-wr.js → dev-GAOIot9G.js} +2202 -2170
- package/dist/{dev-Cr-teYsz.js → dev-tPcOmjPJ.js} +1 -1
- package/dist/{frameworkTableAssembly-BzAajuUv.js → frameworkTableAssembly-BwIrO5nv.js} +162 -145
- package/dist/frameworkTableAssembly-D-EebUQX.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-BeJdP_3b.js → inspectMetrics-BM1vrsga.js} +316 -314
- package/dist/{sdkgen-CqpOq_fy.js → sdkgen-BV4fFucu.js} +1 -1
- package/dist/{serveCommand-Ciq_wIY8.js → serveCommand-ClqXCX1y.js} +22 -21
- package/dist/serveEntry.js +2 -2
- package/dist/{start-ocqB1seX.js → start-CTnNvKlk.js} +370 -368
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/cli.md +29 -0
- package/templates/agent-docs/internationalization.md +25 -3
- package/templates/agent-docs/routing.md +14 -0
- package/templates/agent-docs/whats-new.md +23 -352
- package/templates/agent-docs/workflows.md +2 -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/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-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/package.json +8 -8
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/package.json +9 -9
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- 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-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +2 -2
- package/dist/apiBuild-CxvR1Dwt.js +0 -2
- package/dist/checkCommand-BOk9y264.js +0 -2
- package/dist/dbCommand-D8nhyppR.js +0 -2
- package/dist/frameworkTableAssembly-DU0aOxjL.js +0 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Ut as a, Wt as o } from "./dev-
|
|
1
|
+
import { f as e, gt as t, n, o as r, z as i } from "./inspectMetrics-BM1vrsga.js";
|
|
2
|
+
import { Ut as a, Wt as o } from "./dev-GAOIot9G.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-BUS1RfPi.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";
|
|
@@ -119,7 +119,7 @@ var re = () => {
|
|
|
119
119
|
status: "live",
|
|
120
120
|
logFile: "",
|
|
121
121
|
...e.token === void 0 ? {} : { inspectToken: e.token }
|
|
122
|
-
}), le = async (e,
|
|
122
|
+
}), le = async (e, n) => n === void 0 ? (await t()).apps.filter((t) => t.kind === "api" && t.status === "live" && t.url.length > 0 && (e === void 0 || t.name === e)) : [ce(n)], ue = (e) => {
|
|
123
123
|
let t = "pretty", n = [];
|
|
124
124
|
for (let r = 0; r < e.length; r++) {
|
|
125
125
|
let i = e[r];
|
|
@@ -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?$/, Ae = (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() ? Ae(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
|
+
}, je = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, Me = /\b(?:memo|forwardRef|observer|styled)\b/, z = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), B = (e) => {
|
|
491
|
+
if (z(e)) return !0;
|
|
492
|
+
if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return B(e.getExpression());
|
|
493
493
|
if (k.isVariableDeclaration(e)) {
|
|
494
494
|
let t = e.getTypeNode();
|
|
495
|
-
if (t !== void 0 &&
|
|
495
|
+
if (t !== void 0 && je.test(t.getText())) return !0;
|
|
496
496
|
let n = e.getInitializer();
|
|
497
|
-
return n === void 0 ||
|
|
497
|
+
return n === void 0 || B(n);
|
|
498
498
|
}
|
|
499
|
-
return k.isCallExpression(e) ?
|
|
500
|
-
},
|
|
499
|
+
return k.isCallExpression(e) ? Me.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => z(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
|
+
}, Ne = (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) && B(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
|
+
}, Pe = [], Fe = (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, Pe);
|
|
541
|
+
return Pe;
|
|
542
|
+
}, Ie = (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
|
+
}, Le = (e, t, n) => {
|
|
556
556
|
let r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = (e) => {
|
|
557
|
-
let i =
|
|
557
|
+
let i = Ie(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
|
+
}, Re = (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
|
+
}, ze = (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
|
+
}, Be = (e, t) => {
|
|
596
596
|
let n = b(t);
|
|
597
597
|
return e.startsWith(`${n}${w}`) || b(e) === n;
|
|
598
|
-
},
|
|
599
|
-
let t =
|
|
598
|
+
}, V = (e) => {
|
|
599
|
+
let t = Ae(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, Ne(r)), c.set(t, Le(r, t, Fe(t, e, u))), l.set(t, Re(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 = ze(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) => !Be(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
|
+
}, Ve = 10, He = (e) => {
|
|
802
|
+
let { findings: t, declared: n, scanned: r, vendored: i } = V(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, Ve)) console.log(` ${e.file} — ${e.detail}`);
|
|
817
|
+
let n = t.length - Ve;
|
|
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
|
+
}, Ue = /* @__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
|
+
]), We = (e) => {
|
|
840
840
|
if (k.isPropertyAccessExpression(e)) return e.getName();
|
|
841
841
|
if (k.isIdentifier(e)) return e.getText();
|
|
842
|
-
},
|
|
842
|
+
}, Ge = (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
|
+
}, Ke = (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 = We(Ge(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 || !Ue.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
|
+
}, H = 12, qe = (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, H).join(", "), r = n.available.length > H ? `, … (${n.available.length - H} 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
|
+
}, Je = /* @__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
|
+
]), Ye = /* @__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
|
+
]), Xe = (e) => Je.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Ze = (e) => e.type === "reference", Qe = (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) => Xe(e.name)), r = e.filter((e) => !Ze(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(Ze) && 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) => !Xe(e.name) && !Ye.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
|
+
}, $e = {
|
|
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
|
+
}, et = (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} (${$e[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
|
+
}, tt = /* @__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(".")), nt = (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()), rt = (e) => nt(e).some((e) => tt.has(e)), it = (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 && rt(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
|
+
}, at = (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
|
+
}, ot = (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
|
+
}, st = /* @__PURE__ */ new Set([
|
|
1025
1025
|
"start",
|
|
1026
1026
|
"run",
|
|
1027
1027
|
"child"
|
|
1028
|
-
]),
|
|
1028
|
+
]), ct = (e) => k.isCallExpression(e) && (() => {
|
|
1029
1029
|
let t = e.getExpression();
|
|
1030
|
-
if (!k.isPropertyAccessExpression(t) || !
|
|
1030
|
+
if (!k.isPropertyAccessExpression(t) || !st.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
|
+
})(), lt = (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 (!ct(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
|
+
}, ut = (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
|
+
}, dt = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, U = "VOLTRO_FIELD_ENCRYPTION_KEY", ft = (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(U)) 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(U)) return;
|
|
1157
1157
|
return t;
|
|
1158
1158
|
}
|
|
1159
|
-
},
|
|
1160
|
-
`✗ ${
|
|
1159
|
+
}, pt = (e) => [
|
|
1160
|
+
`✗ ${U} 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"), mt = (e) => e === "effect" || e.startsWith("@effect/") || e.startsWith("@voltro/") || e === "react" || e === "react-dom", ht = /(^|\/)(\.bin|\.cache|\.pnpm-store|dist|build)(\/|$)/, gt = (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
|
+
}, _t = (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
|
+
}, vt = (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(".") || ht.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
|
+
}, yt = (e) => {
|
|
1228
1228
|
let t = /* @__PURE__ */ new Map();
|
|
1229
|
-
for (let n of
|
|
1230
|
-
if (!
|
|
1231
|
-
let n =
|
|
1229
|
+
for (let n of gt(e)) for (let { pkg: e, dir: r } of vt(n)) {
|
|
1230
|
+
if (!mt(e)) continue;
|
|
1231
|
+
let n = _t(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
|
+
}, bt = (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,49 +1258,68 @@ 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
|
+
}, xt = (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
|
+
}, St = (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
|
+
}, Ct = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), wt = /* @__PURE__ */ new Set([
|
|
1262
1281
|
"requireScope",
|
|
1263
1282
|
"requireAnyScope",
|
|
1264
1283
|
"requireAuth",
|
|
1265
1284
|
"assertScope",
|
|
1266
1285
|
"assertCan",
|
|
1267
1286
|
"can"
|
|
1268
|
-
]),
|
|
1287
|
+
]), Tt = (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 Ct(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
|
+
}, Et = (e, t) => {
|
|
1273
1292
|
for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
|
|
1274
|
-
},
|
|
1293
|
+
}, Dt = (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
|
+
}, Ot = (e, t, n) => {
|
|
1278
1297
|
if (e.hasDeclaredGuards) return { kind: "declared-guard" };
|
|
1279
|
-
let r =
|
|
1280
|
-
return i === void 0 ?
|
|
1298
|
+
let r = Ct(e.src), i = Et(r, t);
|
|
1299
|
+
return i === void 0 ? Dt(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
|
+
}, kt = (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
|
+
}, At = (e, t, n) => {
|
|
1288
1307
|
switch (t.kind) {
|
|
1289
1308
|
case "declared-guard": return "guards: on the descriptor";
|
|
1290
1309
|
case "vocabulary-guard": return `calls ${t.name}()`;
|
|
1291
1310
|
case "inline-ownership": return "compares the caller to the row and refuses — correct, but only this file knows it";
|
|
1292
1311
|
case "allowlisted": return "accepted by a previous run";
|
|
1293
|
-
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`;
|
|
1312
|
+
case "unchecked": return n ? e.op === "read" ? "no check in this file — this app registers a row filter; VERIFY it covers this table, then record it as reviewed= rather than debt" : `${e.op}s${e.table ? ` ${e.table}` : ""} with no check in this file — this app registers a row filter; VERIFY it covers this table, then record it as reviewed= rather than debt` : e.op === "read" ? "returns a row the client selected, with nothing compared to the caller" : `${e.op}s${e.table ? ` ${e.table}` : ""} with nothing constraining WHICH row`;
|
|
1294
1313
|
}
|
|
1295
|
-
},
|
|
1314
|
+
}, jt = (e) => {
|
|
1296
1315
|
let t = e.executors.map((t) => {
|
|
1297
|
-
let n =
|
|
1316
|
+
let n = Ot(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: kt(t, e.tenantScopedTables, e.referencedTables),
|
|
1322
|
+
why: At(t, n, e.rowFilterRegistered === !0)
|
|
1304
1323
|
};
|
|
1305
1324
|
}), n = (e) => t.filter((t) => t.verdict.kind === e).length;
|
|
1306
1325
|
return {
|
|
@@ -1314,26 +1333,100 @@ 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
|
+
}, Mt = [
|
|
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
|
+
], Nt = (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
|
+
...Mt,
|
|
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
|
+
}, Pt = (e, t) => {
|
|
1371
|
+
let n = [...new Set(t)].sort();
|
|
1372
|
+
if (e === void 0) return Nt(n.map((e) => ({ tag: e })));
|
|
1373
|
+
let r = W(e).tags, i = n.filter((e) => !r.has(e));
|
|
1374
|
+
return i.length === 0 ? e : [
|
|
1375
|
+
e.replace(/\n+$/, ""),
|
|
1376
|
+
"",
|
|
1377
|
+
"# added by `voltro doctor --write-authz-allowlist` — newly unchecked executors.",
|
|
1378
|
+
"# Nothing above this line was touched.",
|
|
1379
|
+
...i,
|
|
1380
|
+
""
|
|
1381
|
+
].join("\n");
|
|
1382
|
+
}, Ft = (e, t) => {
|
|
1383
|
+
let n = [...new Set(t)];
|
|
1384
|
+
if (e === void 0) return {
|
|
1385
|
+
added: n.length,
|
|
1386
|
+
kept: 0,
|
|
1387
|
+
removed: 0
|
|
1388
|
+
};
|
|
1389
|
+
let r = W(e).tags;
|
|
1390
|
+
return {
|
|
1391
|
+
added: n.filter((e) => !r.has(e)).length,
|
|
1392
|
+
kept: r.size,
|
|
1393
|
+
removed: 0
|
|
1394
|
+
};
|
|
1395
|
+
}, It = class extends Error {
|
|
1396
|
+
tag;
|
|
1397
|
+
constructor(e) {
|
|
1398
|
+
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";
|
|
1399
|
+
}
|
|
1400
|
+
}, W = (e) => {
|
|
1401
|
+
let t = [];
|
|
1402
|
+
for (let n of e.split("\n")) {
|
|
1403
|
+
let e = n.trim();
|
|
1404
|
+
if (e.length === 0 || e.startsWith("#")) continue;
|
|
1405
|
+
let r = /^(\S+)(?:\s+reviewed=(.*))?$/.exec(e);
|
|
1406
|
+
if (!r) continue;
|
|
1407
|
+
let i = r[1];
|
|
1408
|
+
if (r[2] === void 0) {
|
|
1409
|
+
t.push({ tag: i });
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
let a = r[2].trim();
|
|
1413
|
+
if (a.length === 0) throw new It(i);
|
|
1414
|
+
t.push({
|
|
1415
|
+
tag: i,
|
|
1416
|
+
reviewed: a
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
return {
|
|
1420
|
+
tags: new Set(t.map((e) => e.tag)),
|
|
1421
|
+
entries: t
|
|
1422
|
+
};
|
|
1423
|
+
}, Lt = (e) => {
|
|
1331
1424
|
let t = e.indexOf(".");
|
|
1332
1425
|
return t <= 0 ? void 0 : e.slice(0, t);
|
|
1333
|
-
},
|
|
1426
|
+
}, Rt = (e, t) => {
|
|
1334
1427
|
let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
|
|
1335
1428
|
for (let e of t.rpcTags) {
|
|
1336
|
-
let t =
|
|
1429
|
+
let t = Lt(e);
|
|
1337
1430
|
if (t === void 0) continue;
|
|
1338
1431
|
let n = i.get(t);
|
|
1339
1432
|
n ? n.push(e) : i.set(t, [e]);
|
|
@@ -1362,7 +1455,7 @@ var re = () => {
|
|
|
1362
1455
|
});
|
|
1363
1456
|
continue;
|
|
1364
1457
|
}
|
|
1365
|
-
let s = new Set(o.rpcTags.map(
|
|
1458
|
+
let s = new Set(o.rpcTags.map(Lt).filter((e) => e !== void 0));
|
|
1366
1459
|
for (let e of s) {
|
|
1367
1460
|
let t = i.get(e);
|
|
1368
1461
|
t !== void 0 && a.push({
|
|
@@ -1375,12 +1468,12 @@ var re = () => {
|
|
|
1375
1468
|
}
|
|
1376
1469
|
}
|
|
1377
1470
|
return a;
|
|
1378
|
-
},
|
|
1471
|
+
}, zt = (e, t) => {
|
|
1379
1472
|
let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
|
|
1380
1473
|
if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
|
|
1381
1474
|
for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
|
|
1382
1475
|
return r;
|
|
1383
|
-
},
|
|
1476
|
+
}, Bt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Vt = (e) => {
|
|
1384
1477
|
let t = y(e), n = v(e);
|
|
1385
1478
|
return {
|
|
1386
1479
|
root: e,
|
|
@@ -1389,19 +1482,19 @@ var re = () => {
|
|
|
1389
1482
|
apiBundle: n,
|
|
1390
1483
|
apiBundlePresent: T(n)
|
|
1391
1484
|
};
|
|
1392
|
-
},
|
|
1485
|
+
}, Ht = [
|
|
1393
1486
|
"Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
|
|
1394
1487
|
"Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
|
|
1395
1488
|
"(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}`), !
|
|
1489
|
+
].join("\n "), Ut = (e, t = !1) => {
|
|
1490
|
+
let n = Vt(e);
|
|
1491
|
+
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Bt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
|
|
1399
1492
|
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
1493
|
let r = process.env.NODE_ENV === "production";
|
|
1401
1494
|
if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
|
|
1402
1495
|
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
|
-
},
|
|
1496
|
+
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Ht}`), +!!i;
|
|
1497
|
+
}, Wt = (e, t, n) => {
|
|
1405
1498
|
let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
|
|
1406
1499
|
`${e}.ts`,
|
|
1407
1500
|
`${e}.tsx`,
|
|
@@ -1427,7 +1520,7 @@ var re = () => {
|
|
|
1427
1520
|
r !== void 0 && o.push(r);
|
|
1428
1521
|
}
|
|
1429
1522
|
return o;
|
|
1430
|
-
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) =>
|
|
1523
|
+
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Xt.test(S(n, e))), l = new Set(c);
|
|
1431
1524
|
for (; c.length > 0;) {
|
|
1432
1525
|
let e = c.shift();
|
|
1433
1526
|
if (!s.has(e)) {
|
|
@@ -1440,11 +1533,11 @@ var re = () => {
|
|
|
1440
1533
|
let d = /* @__PURE__ */ new Set();
|
|
1441
1534
|
for (let e of s) l.has(e) || u.has(e) || d.add(e);
|
|
1442
1535
|
return d;
|
|
1443
|
-
},
|
|
1536
|
+
}, Gt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Kt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, qt = (e, t) => {
|
|
1444
1537
|
if (e.src.includes(t)) return !0;
|
|
1445
1538
|
let n = t.replace(/^@voltro\/plugin-/, "");
|
|
1446
|
-
return n !== t &&
|
|
1447
|
-
},
|
|
1539
|
+
return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
|
|
1540
|
+
}, Jt = (e, t) => {
|
|
1448
1541
|
let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
|
|
1449
1542
|
for (; (i = r.exec(e)) !== null;) {
|
|
1450
1543
|
let t = i.index + i[0].length, r = 1, a = t;
|
|
@@ -1452,10 +1545,10 @@ var re = () => {
|
|
|
1452
1545
|
n.push(e.slice(t, a - 1));
|
|
1453
1546
|
}
|
|
1454
1547
|
return n;
|
|
1455
|
-
},
|
|
1548
|
+
}, Yt = (e, t) => {
|
|
1456
1549
|
let n = t.getSymbol()?.getDeclarations()?.[0];
|
|
1457
1550
|
return n === void 0 ? [] : e.getDescendantsOfKind(j.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
|
|
1458
|
-
},
|
|
1551
|
+
}, Xt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Zt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Qt = [
|
|
1459
1552
|
"Id",
|
|
1460
1553
|
"ID",
|
|
1461
1554
|
"Hash",
|
|
@@ -1481,7 +1574,7 @@ var re = () => {
|
|
|
1481
1574
|
"Comment",
|
|
1482
1575
|
"Provider",
|
|
1483
1576
|
"Format"
|
|
1484
|
-
],
|
|
1577
|
+
], $t = RegExp(`(?:${Qt.join("|")})$|_(?:${Qt.map((e) => e.toLowerCase()).join("|")})$`), en = [
|
|
1485
1578
|
{
|
|
1486
1579
|
id: "hardcoded-framework-cookie",
|
|
1487
1580
|
scope: "client",
|
|
@@ -1494,7 +1587,7 @@ var re = () => {
|
|
|
1494
1587
|
scope: "server",
|
|
1495
1588
|
smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
|
|
1496
1589
|
use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
|
|
1497
|
-
match: ({ src: e, path: t }) =>
|
|
1590
|
+
match: ({ src: e, path: t }) => Zt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
|
|
1498
1591
|
},
|
|
1499
1592
|
{
|
|
1500
1593
|
id: "subject-write-no-guard",
|
|
@@ -1502,24 +1595,24 @@ var re = () => {
|
|
|
1502
1595
|
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",
|
|
1503
1596
|
use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
|
|
1504
1597
|
match: ({ src: e, path: t }) => {
|
|
1505
|
-
if (!
|
|
1506
|
-
let n =
|
|
1598
|
+
if (!Zt.test(t)) return !1;
|
|
1599
|
+
let n = Gt(e);
|
|
1507
1600
|
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);
|
|
1508
1601
|
}
|
|
1509
1602
|
},
|
|
1510
1603
|
{
|
|
1511
1604
|
id: "raw-fetch",
|
|
1512
1605
|
scope: "server",
|
|
1513
|
-
smell: "raw fetch() for outbound HTTP on the server (no
|
|
1514
|
-
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
|
|
1515
|
-
match: ({ file: e, path: t, serverOnly: n }) => (
|
|
1606
|
+
smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
|
|
1607
|
+
use: "yield* HttpClient — the framework client. It injects traceparent, retries, and honours `http.allowHosts`. Where the URL is built from anything a caller supplied it ALSO blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop — for a hard-coded host that half is redundant, and the first three are still the reason to switch",
|
|
1608
|
+
match: ({ file: e, path: t, serverOnly: n }) => (Xt.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
|
|
1516
1609
|
},
|
|
1517
1610
|
{
|
|
1518
1611
|
id: "form",
|
|
1519
1612
|
scope: "client",
|
|
1520
1613
|
smell: "per-field useState + a submit flag (hand-rolled form)",
|
|
1521
1614
|
use: "useFormBinding — fields + validation from the mutation input Schema; server ValidationError({ field }) routes to the field",
|
|
1522
|
-
match: ({ src: e, file: t }) =>
|
|
1615
|
+
match: ({ src: e, file: t }) => G(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
|
|
1523
1616
|
},
|
|
1524
1617
|
{
|
|
1525
1618
|
id: "table",
|
|
@@ -1547,7 +1640,7 @@ var re = () => {
|
|
|
1547
1640
|
scope: "client",
|
|
1548
1641
|
smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
|
|
1549
1642
|
use: "useDerived — dependency-tracked, referentially stable",
|
|
1550
|
-
match: ({ src: e }) =>
|
|
1643
|
+
match: ({ src: e }) => Kt(e, /\buseSubscription\s*[(<]/g) >= 3 && Kt(e, /\buseMemo\s*\(/g) >= 3
|
|
1551
1644
|
},
|
|
1552
1645
|
{
|
|
1553
1646
|
id: "next-shim",
|
|
@@ -1572,7 +1665,7 @@ var re = () => {
|
|
|
1572
1665
|
match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
|
|
1573
1666
|
let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
|
|
1574
1667
|
return t === void 0 ? [] : k.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
|
|
1575
|
-
}).some((t) =>
|
|
1668
|
+
}).some((t) => Yt(e, t).some((e) => {
|
|
1576
1669
|
let t = e.getParent();
|
|
1577
1670
|
if (k.isBinaryExpression(t)) {
|
|
1578
1671
|
let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
|
|
@@ -1600,7 +1693,7 @@ var re = () => {
|
|
|
1600
1693
|
scope: "server",
|
|
1601
1694
|
smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
|
|
1602
1695
|
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)",
|
|
1603
|
-
match: (e) =>
|
|
1696
|
+
match: (e) => G(e.file, /\bstore\.query$/) >= 3 || G(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
|
|
1604
1697
|
},
|
|
1605
1698
|
{
|
|
1606
1699
|
id: "store-effect-lift",
|
|
@@ -1623,7 +1716,17 @@ var re = () => {
|
|
|
1623
1716
|
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)",
|
|
1624
1717
|
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) => {
|
|
1625
1718
|
let n = t[1];
|
|
1626
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
1719
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || $t.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
|
|
1720
|
+
}))
|
|
1721
|
+
},
|
|
1722
|
+
{
|
|
1723
|
+
id: "plaintext-secret-lookup-key",
|
|
1724
|
+
scope: "server",
|
|
1725
|
+
smell: "a credential-shaped column that is ALSO a unique lookup key",
|
|
1726
|
+
use: "you cannot look a value up by equality AND encrypt it non-deterministically — `.encrypted()` would make this column unfindable. Store the HASH as the unique lookup key and keep the secret itself either `.encrypted()` in a second column or nowhere (the framework's own `_voltro_api_keys.hashedKey` is the shape: the presented value is hashed and compared, never read back). A deterministic cipher is the other option and buys equality lookup at the cost of leaking which rows share a value",
|
|
1727
|
+
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) => {
|
|
1728
|
+
let n = t[1];
|
|
1729
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || $t.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
|
|
1627
1730
|
}))
|
|
1628
1731
|
},
|
|
1629
1732
|
{
|
|
@@ -1687,9 +1790,9 @@ var re = () => {
|
|
|
1687
1790
|
scope: "server",
|
|
1688
1791
|
smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
|
|
1689
1792
|
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",
|
|
1690
|
-
match: ({ file: e, testSources: t }) =>
|
|
1691
|
-
let t =
|
|
1692
|
-
return
|
|
1793
|
+
match: ({ file: e, testSources: t }) => G(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
|
|
1794
|
+
let t = Gt(e);
|
|
1795
|
+
return Jt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
|
|
1693
1796
|
})
|
|
1694
1797
|
},
|
|
1695
1798
|
{
|
|
@@ -1699,15 +1802,15 @@ var re = () => {
|
|
|
1699
1802
|
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",
|
|
1700
1803
|
match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
|
|
1701
1804
|
}
|
|
1702
|
-
],
|
|
1805
|
+
], tn = (e) => q(e), nn = (e, t) => {
|
|
1703
1806
|
let n = /* @__PURE__ */ new Set();
|
|
1704
1807
|
for (let r of t) {
|
|
1705
1808
|
let t = S(e, r), i = t.split(/[\\/]/)[0];
|
|
1706
1809
|
n.add(i !== void 0 && i !== t ? i : ".");
|
|
1707
1810
|
}
|
|
1708
1811
|
return [...n].sort();
|
|
1709
|
-
},
|
|
1710
|
-
if (n > 12 ||
|
|
1812
|
+
}, rn = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, an = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, on = (e, t = [], n = 0) => {
|
|
1813
|
+
if (n > 12 || K.test(e)) return t;
|
|
1711
1814
|
let r;
|
|
1712
1815
|
try {
|
|
1713
1816
|
r = D(e, { withFileTypes: !0 });
|
|
@@ -1718,12 +1821,12 @@ var re = () => {
|
|
|
1718
1821
|
let r = x(e, i.name);
|
|
1719
1822
|
if (i.isDirectory()) {
|
|
1720
1823
|
if (i.name.startsWith(".")) continue;
|
|
1721
|
-
|
|
1722
|
-
} else
|
|
1824
|
+
on(r, t, n + 1);
|
|
1825
|
+
} else dt.test(i.name) && t.push(r);
|
|
1723
1826
|
}
|
|
1724
1827
|
return t;
|
|
1725
|
-
},
|
|
1726
|
-
if (n > 12 ||
|
|
1828
|
+
}, q = (e, t = [], n = 0) => {
|
|
1829
|
+
if (n > 12 || K.test(e)) return t;
|
|
1727
1830
|
let r;
|
|
1728
1831
|
try {
|
|
1729
1832
|
r = D(e);
|
|
@@ -1737,11 +1840,11 @@ var re = () => {
|
|
|
1737
1840
|
} catch {
|
|
1738
1841
|
continue;
|
|
1739
1842
|
}
|
|
1740
|
-
a.isDirectory() ?
|
|
1843
|
+
a.isDirectory() ? q(r, t, n + 1) : rn.test(i) && !/\.(test|spec)\./.test(i) && !an.test(i) && t.push(r);
|
|
1741
1844
|
}
|
|
1742
1845
|
return t;
|
|
1743
|
-
},
|
|
1744
|
-
if (n > 12 ||
|
|
1846
|
+
}, sn = (e, t = [], n = 0) => {
|
|
1847
|
+
if (n > 12 || K.test(e)) return t;
|
|
1745
1848
|
let r;
|
|
1746
1849
|
try {
|
|
1747
1850
|
r = D(e);
|
|
@@ -1755,17 +1858,17 @@ var re = () => {
|
|
|
1755
1858
|
} catch {
|
|
1756
1859
|
continue;
|
|
1757
1860
|
}
|
|
1758
|
-
a.isDirectory() ?
|
|
1861
|
+
a.isDirectory() ? sn(r, t, n + 1) : rn.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
|
|
1759
1862
|
}
|
|
1760
1863
|
return t;
|
|
1761
|
-
},
|
|
1762
|
-
let t =
|
|
1864
|
+
}, cn = (e) => {
|
|
1865
|
+
let t = tn(e);
|
|
1763
1866
|
return {
|
|
1764
|
-
dirs:
|
|
1867
|
+
dirs: nn(e, t),
|
|
1765
1868
|
fileCount: t.length
|
|
1766
1869
|
};
|
|
1767
|
-
},
|
|
1768
|
-
let t =
|
|
1870
|
+
}, ln = (e) => {
|
|
1871
|
+
let t = tn(e);
|
|
1769
1872
|
if (t.length === 0) return [];
|
|
1770
1873
|
let n = new A({
|
|
1771
1874
|
useInMemoryFileSystem: !0,
|
|
@@ -1774,8 +1877,8 @@ var re = () => {
|
|
|
1774
1877
|
for (let e of t) try {
|
|
1775
1878
|
i.set(e, E(e, "utf8"));
|
|
1776
1879
|
} catch {}
|
|
1777
|
-
let a =
|
|
1778
|
-
for (let t of
|
|
1880
|
+
let a = Wt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
|
|
1881
|
+
for (let t of sn(e)) try {
|
|
1779
1882
|
o.set(t, E(t, "utf8"));
|
|
1780
1883
|
} catch {}
|
|
1781
1884
|
for (let s of t) {
|
|
@@ -1793,23 +1896,23 @@ var re = () => {
|
|
|
1793
1896
|
} catch {
|
|
1794
1897
|
continue;
|
|
1795
1898
|
}
|
|
1796
|
-
for (let t of
|
|
1797
|
-
if (t.plugin !== void 0 &&
|
|
1899
|
+
for (let t of en) {
|
|
1900
|
+
if (t.plugin !== void 0 && qt(c, t.plugin) || !t.match(c)) continue;
|
|
1798
1901
|
let n = r.get(t.id) ?? [];
|
|
1799
1902
|
n.push(S(e, s)), r.set(t.id, n);
|
|
1800
1903
|
}
|
|
1801
1904
|
}
|
|
1802
|
-
return
|
|
1905
|
+
return en.filter((e) => r.has(e.id)).map((e) => ({
|
|
1803
1906
|
rule: e,
|
|
1804
1907
|
files: r.get(e.id)
|
|
1805
1908
|
})).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
|
|
1806
|
-
},
|
|
1807
|
-
let t =
|
|
1909
|
+
}, un = (e) => {
|
|
1910
|
+
let t = cn(e);
|
|
1808
1911
|
return {
|
|
1809
1912
|
root: e,
|
|
1810
1913
|
scannedFiles: t.fileCount,
|
|
1811
1914
|
scannedDirs: t.dirs,
|
|
1812
|
-
findings:
|
|
1915
|
+
findings: ln(e).map(({ rule: e, files: t }) => ({
|
|
1813
1916
|
id: e.id,
|
|
1814
1917
|
scope: e.scope,
|
|
1815
1918
|
smell: e.smell,
|
|
@@ -1817,77 +1920,82 @@ var re = () => {
|
|
|
1817
1920
|
files: t
|
|
1818
1921
|
}))
|
|
1819
1922
|
};
|
|
1820
|
-
},
|
|
1923
|
+
}, dn = 3, fn = .2, pn = 50, mn = (e, t) => e >= pn && t > 0 && e / t >= fn, hn = (e) => {
|
|
1821
1924
|
let t = /* @__PURE__ */ new Map();
|
|
1822
|
-
for (let n of
|
|
1925
|
+
for (let n of q(e)) try {
|
|
1823
1926
|
t.set(S(e, n), E(n, "utf8"));
|
|
1824
1927
|
} catch {}
|
|
1825
1928
|
let n = /* @__PURE__ */ new Map();
|
|
1826
|
-
for (let t of
|
|
1929
|
+
for (let t of on(e)) try {
|
|
1827
1930
|
n.set(S(e, t), E(t, "utf8"));
|
|
1828
1931
|
} catch {}
|
|
1829
|
-
let r =
|
|
1932
|
+
let r = ft({
|
|
1830
1933
|
sources: t,
|
|
1831
1934
|
envFiles: n
|
|
1832
1935
|
});
|
|
1833
|
-
r !== void 0 && (console.log(""), console.log(
|
|
1834
|
-
},
|
|
1835
|
-
let t =
|
|
1936
|
+
r !== void 0 && (console.log(""), console.log(pt(r)));
|
|
1937
|
+
}, gn = (e) => {
|
|
1938
|
+
let t = un(e);
|
|
1836
1939
|
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;
|
|
1837
1940
|
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;
|
|
1838
1941
|
console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
|
|
1839
1942
|
let n = 0;
|
|
1840
1943
|
for (let e of ["server", "client"]) {
|
|
1841
1944
|
let r = t.findings.filter((t) => t.scope === e);
|
|
1842
|
-
if (r.length
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
let
|
|
1848
|
-
console.log(` ${e.smell} \u2014 ${e.files.length} file(s)
|
|
1945
|
+
if (r.length === 0) continue;
|
|
1946
|
+
console.log(` [${e}]`);
|
|
1947
|
+
let i = [...r].sort((e, t) => e.files.length - t.files.length);
|
|
1948
|
+
for (let e of i) {
|
|
1949
|
+
if (mn(e.files.length, t.scannedFiles)) {
|
|
1950
|
+
let n = Math.round(e.files.length / t.scannedFiles * 100);
|
|
1951
|
+
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.)");
|
|
1952
|
+
continue;
|
|
1849
1953
|
}
|
|
1954
|
+
let r = e.files.slice(0, dn).join(", "), i = e.files.length - dn;
|
|
1955
|
+
i > 0 && (n += i);
|
|
1956
|
+
let a = i > 0 ? `, +${i} more` : "";
|
|
1957
|
+
console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
|
|
1850
1958
|
}
|
|
1851
1959
|
}
|
|
1852
1960
|
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;
|
|
1853
|
-
},
|
|
1854
|
-
let t =
|
|
1961
|
+
}, _n = "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.", vn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, yn = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, bn = (e) => {
|
|
1962
|
+
let t = vn.exec(e)?.[1];
|
|
1855
1963
|
if (t !== void 0) return t === "ssr" ? "ssr" : null;
|
|
1856
|
-
let n =
|
|
1964
|
+
let n = yn.exec(e)?.[1];
|
|
1857
1965
|
return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
|
|
1858
|
-
},
|
|
1859
|
-
let
|
|
1860
|
-
if (!T(
|
|
1861
|
-
let { pages: a, dirs: o } = await
|
|
1862
|
-
for (let
|
|
1863
|
-
if (!
|
|
1966
|
+
}, xn = async (t) => {
|
|
1967
|
+
let i = x(t, "src", "pages");
|
|
1968
|
+
if (!T(i)) return [];
|
|
1969
|
+
let { pages: a, dirs: o } = await e(i), s = [];
|
|
1970
|
+
for (let e of a) {
|
|
1971
|
+
if (!r(o, e.dirChain).some((e) => e.hasLayout)) continue;
|
|
1864
1972
|
let a;
|
|
1865
1973
|
try {
|
|
1866
|
-
a = E(x(
|
|
1974
|
+
a = E(x(i, e.file), "utf8");
|
|
1867
1975
|
} catch {
|
|
1868
1976
|
continue;
|
|
1869
1977
|
}
|
|
1870
|
-
if (
|
|
1871
|
-
let c =
|
|
1978
|
+
if (n.test(a)) continue;
|
|
1979
|
+
let c = bn(a);
|
|
1872
1980
|
c !== null && s.push({
|
|
1873
|
-
file: S(
|
|
1874
|
-
pattern:
|
|
1981
|
+
file: S(t, x(i, e.file)),
|
|
1982
|
+
pattern: e.pattern,
|
|
1875
1983
|
currentMode: c
|
|
1876
1984
|
});
|
|
1877
1985
|
}
|
|
1878
1986
|
return s;
|
|
1879
|
-
},
|
|
1987
|
+
}, Sn = 10, Cn = async (e) => {
|
|
1880
1988
|
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;
|
|
1881
|
-
let t = await
|
|
1989
|
+
let t = await xn(e);
|
|
1882
1990
|
if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
|
|
1883
1991
|
console.log(`• renderMode:'spa' candidates (${t.length} page${t.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
|
|
1884
|
-
for (let e of t.slice(0,
|
|
1992
|
+
for (let e of t.slice(0, Sn)) {
|
|
1885
1993
|
let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
|
|
1886
1994
|
console.log(` ${e.file} (${e.pattern}) — ${t}`);
|
|
1887
1995
|
}
|
|
1888
|
-
let n = t.length -
|
|
1889
|
-
return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${
|
|
1890
|
-
},
|
|
1996
|
+
let n = t.length - Sn;
|
|
1997
|
+
return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${_n}`), t.length;
|
|
1998
|
+
}, wn = /* @__PURE__ */ new Set([
|
|
1891
1999
|
"node_modules",
|
|
1892
2000
|
"dist",
|
|
1893
2001
|
"build",
|
|
@@ -1895,7 +2003,7 @@ var re = () => {
|
|
|
1895
2003
|
".vite",
|
|
1896
2004
|
".framework",
|
|
1897
2005
|
".git"
|
|
1898
|
-
]),
|
|
2006
|
+
]), J = (e, t, n) => {
|
|
1899
2007
|
let r;
|
|
1900
2008
|
try {
|
|
1901
2009
|
r = D(e, { withFileTypes: !0 });
|
|
@@ -1903,11 +2011,11 @@ var re = () => {
|
|
|
1903
2011
|
return;
|
|
1904
2012
|
}
|
|
1905
2013
|
for (let i of r) {
|
|
1906
|
-
if (
|
|
2014
|
+
if (wn.has(i.name)) continue;
|
|
1907
2015
|
let r = x(e, i.name);
|
|
1908
|
-
i.isDirectory() ?
|
|
2016
|
+
i.isDirectory() ? J(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
|
|
1909
2017
|
}
|
|
1910
|
-
},
|
|
2018
|
+
}, Tn = (e, t) => {
|
|
1911
2019
|
let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
|
|
1912
2020
|
for (let i of t) {
|
|
1913
2021
|
let t;
|
|
@@ -1932,62 +2040,70 @@ var re = () => {
|
|
|
1932
2040
|
}
|
|
1933
2041
|
}
|
|
1934
2042
|
return n;
|
|
1935
|
-
},
|
|
2043
|
+
}, En = (e, t) => {
|
|
1936
2044
|
let n = e.slice(t.length).split(w).slice(0, -1)[0];
|
|
1937
2045
|
return n !== void 0 && n.startsWith("[");
|
|
1938
|
-
}, Y = (
|
|
1939
|
-
let
|
|
1940
|
-
if (!T(
|
|
2046
|
+
}, Y = (e) => {
|
|
2047
|
+
let t = [], n = x(e, "src", "pages"), r = x(e, "src");
|
|
2048
|
+
if (!T(r)) return t;
|
|
1941
2049
|
let a = [];
|
|
1942
|
-
|
|
1943
|
-
let o = `${S(
|
|
1944
|
-
|
|
1945
|
-
for (let
|
|
1946
|
-
let
|
|
1947
|
-
if (
|
|
1948
|
-
if (
|
|
1949
|
-
T(x(
|
|
2050
|
+
J(r, e, a), a.sort();
|
|
2051
|
+
let o = `${S(e, n)}${w}`;
|
|
2052
|
+
Tn(e, a);
|
|
2053
|
+
for (let n of a) {
|
|
2054
|
+
let r = n.slice(n.lastIndexOf(w) + 1);
|
|
2055
|
+
if (n.startsWith(o)) {
|
|
2056
|
+
if (r !== "page.tsx") continue;
|
|
2057
|
+
T(x(e, i(n))) || t.push({
|
|
1950
2058
|
kind: "page-without-test",
|
|
1951
|
-
file:
|
|
1952
|
-
})
|
|
2059
|
+
file: n
|
|
2060
|
+
});
|
|
2061
|
+
try {
|
|
2062
|
+
let r = E(x(e, n), "utf8");
|
|
2063
|
+
!/\bexport\s+(?:const|let|var|function)\s+meta\b/.test(r) && !/\bexport\s*\{[^}]*\bmeta\b/.test(r) && t.push({
|
|
2064
|
+
kind: "page-without-meta",
|
|
2065
|
+
file: n
|
|
2066
|
+
});
|
|
2067
|
+
} catch {}
|
|
2068
|
+
En(n, o) && t.push({
|
|
1953
2069
|
kind: "top-level-dynamic-route",
|
|
1954
|
-
file:
|
|
2070
|
+
file: n
|
|
1955
2071
|
});
|
|
1956
2072
|
continue;
|
|
1957
2073
|
}
|
|
1958
|
-
|
|
2074
|
+
r === "page.tsx" && t.push({
|
|
1959
2075
|
kind: "suffixed-outside-pages",
|
|
1960
|
-
file:
|
|
2076
|
+
file: n
|
|
1961
2077
|
});
|
|
1962
2078
|
}
|
|
1963
|
-
return
|
|
1964
|
-
},
|
|
2079
|
+
return t;
|
|
2080
|
+
}, Dn = 10, X = (e, t, n) => {
|
|
1965
2081
|
if (t.length === 0) return;
|
|
1966
2082
|
console.log(`• ${e} (${t.length}):`);
|
|
1967
|
-
for (let e of t.slice(0,
|
|
1968
|
-
let r = t.length -
|
|
2083
|
+
for (let e of t.slice(0, Dn)) console.log(` ${e}`);
|
|
2084
|
+
let r = t.length - Dn;
|
|
1969
2085
|
r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
|
|
1970
|
-
},
|
|
2086
|
+
}, On = (e) => {
|
|
1971
2087
|
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;
|
|
1972
2088
|
let t = Y(e);
|
|
1973
2089
|
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;
|
|
1974
2090
|
let n = (e) => t.filter((t) => t.kind === e).map((e) => e.file);
|
|
1975
|
-
return
|
|
1976
|
-
},
|
|
1977
|
-
let t =
|
|
2091
|
+
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."), X("pages with no `meta` export — they serve the APP name as their title", n("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). Advisory."), t.length;
|
|
2092
|
+
}, kn = "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.", An = /* @__PURE__ */ new Map(), Z = (e) => {
|
|
2093
|
+
let t = An.get(e);
|
|
1978
2094
|
if (t !== void 0) return t;
|
|
1979
2095
|
let n = (async () => {
|
|
1980
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
2096
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-tPcOmjPJ.js"), r = await t(e);
|
|
1981
2097
|
return {
|
|
1982
2098
|
files: r,
|
|
1983
2099
|
discovered: await n(r)
|
|
1984
2100
|
};
|
|
1985
2101
|
})();
|
|
1986
|
-
return
|
|
1987
|
-
},
|
|
2102
|
+
return An.set(e, n), n;
|
|
2103
|
+
}, jn = async (e) => {
|
|
1988
2104
|
let t;
|
|
1989
2105
|
try {
|
|
1990
|
-
let { files: n, discovered: r } = await
|
|
2106
|
+
let { files: n, discovered: r } = await Z(e), i = r.tables.map((e) => {
|
|
1991
2107
|
let t = e;
|
|
1992
2108
|
return {
|
|
1993
2109
|
name: t.tableName,
|
|
@@ -1995,12 +2111,12 @@ var re = () => {
|
|
|
1995
2111
|
};
|
|
1996
2112
|
});
|
|
1997
2113
|
if (i.length === 0) return;
|
|
1998
|
-
t =
|
|
2114
|
+
t = qe(Ke(n, i, e));
|
|
1999
2115
|
} catch {
|
|
2000
2116
|
return;
|
|
2001
2117
|
}
|
|
2002
2118
|
t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
|
|
2003
|
-
},
|
|
2119
|
+
}, Mn = (e) => e.map((e) => {
|
|
2004
2120
|
let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
|
|
2005
2121
|
name: e,
|
|
2006
2122
|
type: typeof t?.type == "string" ? t.type : ""
|
|
@@ -2010,22 +2126,22 @@ var re = () => {
|
|
|
2010
2126
|
columns: n,
|
|
2011
2127
|
primaryKey: t.appliedPrimaryKey ?? []
|
|
2012
2128
|
};
|
|
2013
|
-
}),
|
|
2129
|
+
}), Nn = async (e) => {
|
|
2014
2130
|
try {
|
|
2015
|
-
let { discovered: t } = await
|
|
2016
|
-
return
|
|
2131
|
+
let { discovered: t } = await Z(e);
|
|
2132
|
+
return Qe(Mn(t.tables));
|
|
2017
2133
|
} catch {
|
|
2018
2134
|
return null;
|
|
2019
2135
|
}
|
|
2020
|
-
},
|
|
2021
|
-
let t = await
|
|
2136
|
+
}, Pn = async (e) => {
|
|
2137
|
+
let t = await Nn(e);
|
|
2022
2138
|
if (t === null || t.tablesChecked === 0) return;
|
|
2023
|
-
let n =
|
|
2139
|
+
let n = et(t);
|
|
2024
2140
|
n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
|
|
2025
|
-
},
|
|
2141
|
+
}, Fn = async (e) => {
|
|
2026
2142
|
try {
|
|
2027
|
-
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await
|
|
2028
|
-
return
|
|
2143
|
+
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await Z(e);
|
|
2144
|
+
return it(n.events.map((e) => {
|
|
2029
2145
|
let n = e.descriptor, r = t(n.key);
|
|
2030
2146
|
return {
|
|
2031
2147
|
name: n.name,
|
|
@@ -2037,19 +2153,19 @@ var re = () => {
|
|
|
2037
2153
|
} catch {
|
|
2038
2154
|
return null;
|
|
2039
2155
|
}
|
|
2040
|
-
},
|
|
2041
|
-
let t = await
|
|
2156
|
+
}, In = async (e) => {
|
|
2157
|
+
let t = await Fn(e);
|
|
2042
2158
|
if (t === null || t.eventsChecked === 0) return;
|
|
2043
|
-
let n =
|
|
2159
|
+
let n = at(t);
|
|
2044
2160
|
n !== null && (console.log(""), console.log(`• ${n}`));
|
|
2045
|
-
let r =
|
|
2161
|
+
let r = ot(t);
|
|
2046
2162
|
r !== null && (console.log(""), console.log(`⚠️ ${r}`));
|
|
2047
|
-
},
|
|
2163
|
+
}, Ln = (e) => {
|
|
2048
2164
|
let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
|
|
2049
2165
|
return r === void 0 ? n : `${r}: ${n}`;
|
|
2050
|
-
},
|
|
2166
|
+
}, Rn = async (e) => {
|
|
2051
2167
|
try {
|
|
2052
|
-
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await
|
|
2168
|
+
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await Z(e)).discovered.serverOnlyLeaks, r = t(n);
|
|
2053
2169
|
return {
|
|
2054
2170
|
status: {
|
|
2055
2171
|
checked: !0,
|
|
@@ -2060,17 +2176,17 @@ var re = () => {
|
|
|
2060
2176
|
} catch (e) {
|
|
2061
2177
|
return { status: {
|
|
2062
2178
|
checked: !1,
|
|
2063
|
-
reason:
|
|
2179
|
+
reason: Ln(e)
|
|
2064
2180
|
} };
|
|
2065
2181
|
}
|
|
2066
|
-
},
|
|
2067
|
-
let { status: t, message: n } = await
|
|
2182
|
+
}, zn = async (e) => {
|
|
2183
|
+
let { status: t, message: n } = await Rn(e);
|
|
2068
2184
|
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);
|
|
2069
|
-
},
|
|
2185
|
+
}, Bn = async (e) => {
|
|
2070
2186
|
let t;
|
|
2071
2187
|
try {
|
|
2072
|
-
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await
|
|
2073
|
-
t =
|
|
2188
|
+
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await Z(e);
|
|
2189
|
+
t = lt(r, i.workflows.map((e) => {
|
|
2074
2190
|
let t = e.definition, r = t.payloadSchema === void 0 ? {
|
|
2075
2191
|
required: [],
|
|
2076
2192
|
optional: []
|
|
@@ -2084,21 +2200,46 @@ var re = () => {
|
|
|
2084
2200
|
} catch {
|
|
2085
2201
|
return;
|
|
2086
2202
|
}
|
|
2087
|
-
let n =
|
|
2203
|
+
let n = ut(t);
|
|
2088
2204
|
if (n === null) return;
|
|
2089
2205
|
console.log("");
|
|
2090
2206
|
let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
|
|
2091
2207
|
console.log(`${r} ${n}`);
|
|
2092
|
-
},
|
|
2208
|
+
}, Q = "voltro-authz-allowlist.txt", Vn = (e, t = (e) => {
|
|
2093
2209
|
try {
|
|
2094
2210
|
return E(e, "utf8");
|
|
2095
2211
|
} catch {
|
|
2096
2212
|
return "";
|
|
2097
2213
|
}
|
|
2098
|
-
}) => /* @__PURE__ */ new Set([...
|
|
2099
|
-
let
|
|
2214
|
+
}) => /* @__PURE__ */ new Set([...wt, ...Tt(q(e).map(t))]), Hn = async (e) => {
|
|
2215
|
+
let t = ["src/locales", "locales"], n, r = [];
|
|
2216
|
+
for (let i of t) {
|
|
2217
|
+
let t = x(e, i);
|
|
2218
|
+
if (T(t)) try {
|
|
2219
|
+
r = D(t).filter((e) => /\.tsx?$/.test(e) && !e.endsWith(".d.ts")).map((e) => e.replace(/\.tsx?$/, "")), n = i;
|
|
2220
|
+
break;
|
|
2221
|
+
} catch {}
|
|
2222
|
+
}
|
|
2223
|
+
if (n === void 0 || r.length === 0) return;
|
|
2224
|
+
let i = [];
|
|
2225
|
+
try {
|
|
2226
|
+
let { loadApiConfig: t } = await import("./dev-tPcOmjPJ.js");
|
|
2227
|
+
i = (await t(e))?.locales ?? [];
|
|
2228
|
+
} catch {
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
let a = xt({
|
|
2232
|
+
catalogCodes: r,
|
|
2233
|
+
declaredLocales: i
|
|
2234
|
+
});
|
|
2235
|
+
if (a !== void 0) {
|
|
2236
|
+
console.log("");
|
|
2237
|
+
for (let e of St(a, n)) console.log(e);
|
|
2238
|
+
}
|
|
2239
|
+
}, Un = async (e) => {
|
|
2240
|
+
let { discovered: t } = await Z(e), n = [];
|
|
2100
2241
|
try {
|
|
2101
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2242
|
+
let { loadApiConfig: t } = await import("./dev-tPcOmjPJ.js");
|
|
2102
2243
|
n = ((await t(e))?.plugins ?? []).map((e) => {
|
|
2103
2244
|
let t = (e.extendSchema?.tables ?? []).map((e) => {
|
|
2104
2245
|
let t = e;
|
|
@@ -2114,8 +2255,8 @@ var re = () => {
|
|
|
2114
2255
|
plugin: e.name,
|
|
2115
2256
|
tables: t,
|
|
2116
2257
|
rpcTags: n,
|
|
2117
|
-
canDisableTables:
|
|
2118
|
-
canAlias:
|
|
2258
|
+
canDisableTables: Wn.has(e.name),
|
|
2259
|
+
canAlias: Gn.has(e.name)
|
|
2119
2260
|
};
|
|
2120
2261
|
});
|
|
2121
2262
|
} catch {
|
|
@@ -2127,89 +2268,125 @@ var re = () => {
|
|
|
2127
2268
|
...t.mutations,
|
|
2128
2269
|
...t.actions,
|
|
2129
2270
|
...t.streams ?? []
|
|
2130
|
-
].map((e) => String(e.descriptor.name)), i =
|
|
2271
|
+
].map((e) => String(e.descriptor.name)), i = Rt(n, {
|
|
2131
2272
|
tables: t.tables.map((e) => e.tableName),
|
|
2132
2273
|
rpcTags: r
|
|
2133
2274
|
});
|
|
2134
2275
|
console.log();
|
|
2135
|
-
for (let e of
|
|
2136
|
-
},
|
|
2137
|
-
let
|
|
2138
|
-
|
|
2139
|
-
|
|
2276
|
+
for (let e of zt(n, i)) console.log(e);
|
|
2277
|
+
}, Wn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Gn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Kn = async (e) => {
|
|
2278
|
+
let t = await qn(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;
|
|
2279
|
+
return {
|
|
2280
|
+
executors: t.executorCount,
|
|
2281
|
+
guardVocabulary: [...t.vocabulary].sort(),
|
|
2282
|
+
counts: t.result.counts,
|
|
2283
|
+
allowlist: {
|
|
2284
|
+
exists: t.existing !== void 0,
|
|
2285
|
+
debt: r,
|
|
2286
|
+
reviewed: t.result.counts.allowlisted - r
|
|
2287
|
+
},
|
|
2288
|
+
unchecked: t.result.unchecked
|
|
2289
|
+
};
|
|
2290
|
+
}, qn = async (e) => {
|
|
2291
|
+
let { discovered: t } = await Z(e), n = /* @__PURE__ */ new Map(), r = (e) => {
|
|
2292
|
+
if (!n.has(e)) try {
|
|
2293
|
+
n.set(e, E(e, "utf8"));
|
|
2140
2294
|
} catch {
|
|
2141
|
-
|
|
2295
|
+
n.set(e, "");
|
|
2142
2296
|
}
|
|
2143
|
-
return
|
|
2144
|
-
},
|
|
2145
|
-
for (let e of
|
|
2297
|
+
return n.get(e);
|
|
2298
|
+
}, i = Vn(e, r), a = /* @__PURE__ */ new Set();
|
|
2299
|
+
for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
|
|
2146
2300
|
let e = t.references?.();
|
|
2147
|
-
e &&
|
|
2301
|
+
e && a.add(e.tableName);
|
|
2148
2302
|
} catch {}
|
|
2149
|
-
let
|
|
2303
|
+
let o = [
|
|
2150
2304
|
{
|
|
2151
|
-
list:
|
|
2305
|
+
list: t.queries,
|
|
2152
2306
|
op: "read"
|
|
2153
2307
|
},
|
|
2154
2308
|
{
|
|
2155
|
-
list:
|
|
2309
|
+
list: t.mutations,
|
|
2156
2310
|
op: "update"
|
|
2157
2311
|
},
|
|
2158
2312
|
{
|
|
2159
|
-
list:
|
|
2313
|
+
list: t.actions,
|
|
2160
2314
|
op: "update"
|
|
2161
2315
|
},
|
|
2162
2316
|
{
|
|
2163
|
-
list:
|
|
2317
|
+
list: t.streams,
|
|
2164
2318
|
op: "read"
|
|
2165
2319
|
}
|
|
2166
2320
|
].flatMap(({ list: e, op: t }) => e.map((e) => {
|
|
2167
|
-
let n = e.descriptor,
|
|
2321
|
+
let n = e.descriptor, i = n.target, a = Array.isArray(i) ? i[0] : i, o = e.file.replace(/\.(tsx?)$/, ".server.$1");
|
|
2168
2322
|
return {
|
|
2169
2323
|
tag: String(n.name ?? e.file),
|
|
2170
2324
|
path: o,
|
|
2171
|
-
src:
|
|
2325
|
+
src: r(o),
|
|
2172
2326
|
hasDeclaredGuards: Array.isArray(n.guards) && n.guards.length > 0,
|
|
2173
2327
|
op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
|
|
2174
2328
|
...a?.table === void 0 ? {} : { table: a.table }
|
|
2175
2329
|
};
|
|
2176
|
-
})),
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2330
|
+
})), s = x(e, Q), c = T(s) ? W(E(s, "utf8")) : void 0, l = !1;
|
|
2331
|
+
for (let t of q(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
|
|
2332
|
+
l = !0;
|
|
2333
|
+
break;
|
|
2334
|
+
}
|
|
2335
|
+
let u = jt({
|
|
2336
|
+
executors: o,
|
|
2337
|
+
guardVocabulary: i,
|
|
2338
|
+
tenantScopedTables: t.tenantScopedTables,
|
|
2339
|
+
referencedTables: a,
|
|
2340
|
+
allowlist: c?.tags ?? /* @__PURE__ */ new Set(),
|
|
2341
|
+
rowFilterRegistered: l
|
|
2342
|
+
}), d = (c?.entries ?? []).filter((e) => e.reviewed !== void 0);
|
|
2343
|
+
return {
|
|
2344
|
+
result: u,
|
|
2345
|
+
vocabulary: i,
|
|
2346
|
+
rowFilterRegistered: l,
|
|
2347
|
+
existing: c,
|
|
2348
|
+
reviewed: d,
|
|
2349
|
+
executorCount: o.length,
|
|
2350
|
+
allowPath: s
|
|
2351
|
+
};
|
|
2352
|
+
}, Jn = async (e, t) => {
|
|
2353
|
+
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await qn(e), l = n.counts;
|
|
2354
|
+
console.log(`\nauthz scan · ${o} executor(s)`);
|
|
2355
|
+
let u = [...r].filter((e) => !wt.has(e)).sort();
|
|
2356
|
+
console.log(u.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${u.length} from your source (${u.slice(0, 6).join(", ")}${u.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(l.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(l.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(l.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(l.vocabularyGuard).padStart(4)}`);
|
|
2357
|
+
let d = new Set(a.map((e) => e.tag)), f = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !d.has(e.tag)).length, p = l.allowlisted - f;
|
|
2358
|
+
if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Q})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
|
|
2359
|
+
let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ft(e, t);
|
|
2360
|
+
return ne(s, Pt(e, t), "utf8"), console.log(`\n${Q}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
|
|
2361
|
+
}
|
|
2362
|
+
if (n.unchecked.length === 0) return !1;
|
|
2187
2363
|
console.log("");
|
|
2188
|
-
for (let e of
|
|
2189
|
-
return
|
|
2190
|
-
},
|
|
2191
|
-
if (e.includes("--help") || e.includes("-h")) return console.log(
|
|
2364
|
+
for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
|
|
2365
|
+
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);
|
|
2366
|
+
}, Yn = async (e) => {
|
|
2367
|
+
if (e.includes("--help") || e.includes("-h")) return console.log(kn), 0;
|
|
2192
2368
|
let t = e.includes("--strict"), n = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
|
|
2193
2369
|
if (ie(e)) return re(), M({
|
|
2194
|
-
...
|
|
2195
|
-
spaCandidates: await
|
|
2370
|
+
...un(n),
|
|
2371
|
+
spaCandidates: await xn(n),
|
|
2196
2372
|
pageConvention: Y(n),
|
|
2197
|
-
fileTaxonomy:
|
|
2198
|
-
junctionFks: (await
|
|
2199
|
-
eventDelivery: await
|
|
2200
|
-
duplicateInstances:
|
|
2201
|
-
serverOnly: (await
|
|
2373
|
+
fileTaxonomy: V(n).findings,
|
|
2374
|
+
junctionFks: (await Nn(n))?.issues ?? [],
|
|
2375
|
+
eventDelivery: await Fn(n),
|
|
2376
|
+
duplicateInstances: yt(n),
|
|
2377
|
+
serverOnly: (await Rn(n)).status,
|
|
2378
|
+
authz: await Kn(n)
|
|
2202
2379
|
}), 0;
|
|
2203
|
-
let r =
|
|
2204
|
-
|
|
2205
|
-
let i = await
|
|
2380
|
+
let r = Ut(n, t);
|
|
2381
|
+
gn(n), hn(n), On(n), He(n), await Cn(n), await Bn(n), await jn(n), await Pn(n), await In(n), await Un(n), await Hn(n);
|
|
2382
|
+
let i = await zn(n), a = await Jn(n, e.includes("--write-authz-allowlist")), o = bt(yt(n));
|
|
2206
2383
|
return o !== void 0 && console.log(`\n${o}`), i || a ? Math.max(r, 1) : r;
|
|
2207
|
-
},
|
|
2384
|
+
}, Xn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Zn = /* @__PURE__ */ new Set([
|
|
2208
2385
|
"insert",
|
|
2209
2386
|
"update",
|
|
2210
2387
|
"delete",
|
|
2211
2388
|
"upsert"
|
|
2212
|
-
]),
|
|
2389
|
+
]), Qn = (e, t) => {
|
|
2213
2390
|
let n = [], r = new Set(t.exercised), i = /* @__PURE__ */ new Map();
|
|
2214
2391
|
for (let e of t.edges) {
|
|
2215
2392
|
let t = i.get(e.tag);
|
|
@@ -2221,8 +2398,8 @@ var re = () => {
|
|
|
2221
2398
|
a.push(t.tag);
|
|
2222
2399
|
continue;
|
|
2223
2400
|
}
|
|
2224
|
-
let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(
|
|
2225
|
-
for (let t of c) if (
|
|
2401
|
+
let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(Xn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
|
|
2402
|
+
for (let t of c) if (Zn.has(t.op)) {
|
|
2226
2403
|
let r = u.get(t.table);
|
|
2227
2404
|
r === void 0 ? n.push({
|
|
2228
2405
|
rule: "observed/undeclared-write",
|
|
@@ -2278,7 +2455,7 @@ var re = () => {
|
|
|
2278
2455
|
total: e.length
|
|
2279
2456
|
}
|
|
2280
2457
|
};
|
|
2281
|
-
},
|
|
2458
|
+
}, $n = (e, t = [], n = 0) => {
|
|
2282
2459
|
if (n > 12) return t;
|
|
2283
2460
|
let r;
|
|
2284
2461
|
try {
|
|
@@ -2302,24 +2479,24 @@ var re = () => {
|
|
|
2302
2479
|
} catch {
|
|
2303
2480
|
continue;
|
|
2304
2481
|
}
|
|
2305
|
-
o.isDirectory() ?
|
|
2482
|
+
o.isDirectory() ? $n(r, t, n + 1) : o.isFile() && a.test(i) && t.push(r);
|
|
2306
2483
|
}
|
|
2307
2484
|
return t;
|
|
2308
|
-
},
|
|
2485
|
+
}, er = /* @__PURE__ */ new Set(["actors", "tenants"]), tr = async (e) => {
|
|
2309
2486
|
try {
|
|
2310
2487
|
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);
|
|
2311
2488
|
return i?.version !== 1 || !Array.isArray(i.edges) || !Array.isArray(i.exercised) ? null : i;
|
|
2312
2489
|
} catch {
|
|
2313
2490
|
return null;
|
|
2314
2491
|
}
|
|
2315
|
-
},
|
|
2492
|
+
}, nr = (e) => {
|
|
2316
2493
|
let { exercised: t, total: n } = e.coverage, r = `\nobserved: ${t}/${n} procedures exercised (${n === 0 ? 0 : Math.round(t / n * 100)}%)`;
|
|
2317
2494
|
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` : ""}`;
|
|
2318
2495
|
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` : "";
|
|
2319
2496
|
return `${r}\n${i.join("\n")}${a}`;
|
|
2320
|
-
},
|
|
2497
|
+
}, rr = (e, t) => ({
|
|
2321
2498
|
tables: (t.tables ?? []).map((e) => e.name),
|
|
2322
|
-
frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") ||
|
|
2499
|
+
frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || er.has(e.name)).map((e) => e.name),
|
|
2323
2500
|
scopes: e.scopes ?? [],
|
|
2324
2501
|
procedures: (e.procedures ?? []).map((e) => {
|
|
2325
2502
|
let t = (e.guards ?? []).flatMap((e) => e.kind === "scope" ? e.scope ?? [] : []);
|
|
@@ -2332,16 +2509,16 @@ var re = () => {
|
|
|
2332
2509
|
...e.guards === void 0 ? {} : { hasGuards: e.guards.length > 0 }
|
|
2333
2510
|
};
|
|
2334
2511
|
})
|
|
2335
|
-
}),
|
|
2512
|
+
}), ir = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
|
|
2336
2513
|
let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
|
|
2337
2514
|
return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
|
|
2338
|
-
}).join("\n"),
|
|
2339
|
-
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-
|
|
2515
|
+
}).join("\n"), ar = async (e) => {
|
|
2516
|
+
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-tPcOmjPJ.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 = [
|
|
2340
2517
|
...s.queries.map((e) => c(e.descriptor, "query", e.file)),
|
|
2341
2518
|
...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
|
|
2342
2519
|
...s.actions.map((e) => c(e.descriptor, "action", e.file)),
|
|
2343
2520
|
...s.streams.map((e) => c(e.descriptor, "stream", e.file))
|
|
2344
|
-
], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-
|
|
2521
|
+
], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-D-EebUQX.js"), d = await u({ root: e }).catch((e) => (console.warn(`\n⚠ plugin tables could not be collected — this report is INCOMPLETE.\n ${e instanceof Error ? e.message.split("\n")[0] : String(e)}\n`), [])), 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) => ({
|
|
2345
2522
|
name: e,
|
|
2346
2523
|
framework: !0
|
|
2347
2524
|
}))], m = await r(e).then((e) => e?.plugins ?? []).catch(() => []);
|
|
@@ -2352,7 +2529,7 @@ var re = () => {
|
|
|
2352
2529
|
})), [], m),
|
|
2353
2530
|
tables: { tables: p }
|
|
2354
2531
|
};
|
|
2355
|
-
},
|
|
2532
|
+
}, or = async (e) => {
|
|
2356
2533
|
let t = ue(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
|
|
2357
2534
|
if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
|
|
2358
2535
|
let { process: n, target: r, rest: i } = de(t.rest), a;
|
|
@@ -2376,7 +2553,7 @@ var re = () => {
|
|
|
2376
2553
|
if (l.length === 0) {
|
|
2377
2554
|
if (!c && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
|
|
2378
2555
|
try {
|
|
2379
|
-
let e = await
|
|
2556
|
+
let e = await ar(C(s));
|
|
2380
2557
|
u = e.manifest, d = e.tables;
|
|
2381
2558
|
} catch (e) {
|
|
2382
2559
|
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;
|
|
@@ -2386,7 +2563,7 @@ var re = () => {
|
|
|
2386
2563
|
if (t.kind === "err") return console.error(`voltro check: manifest fetch failed: ${t.error}`), 1;
|
|
2387
2564
|
u = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
|
|
2388
2565
|
}
|
|
2389
|
-
let f =
|
|
2566
|
+
let f = rr(u, d), p = he(f, a), m = [...V(s).findings.map((e) => ({
|
|
2390
2567
|
rule: e.rule,
|
|
2391
2568
|
node: e.file,
|
|
2392
2569
|
file: e.file,
|
|
@@ -2400,7 +2577,7 @@ var re = () => {
|
|
|
2400
2577
|
breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
|
|
2401
2578
|
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",
|
|
2402
2579
|
severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
|
|
2403
|
-
}))], h = (await o(s,
|
|
2580
|
+
}))], h = (await o(s, $n(s))).map((e) => ({
|
|
2404
2581
|
rule: "client/not-browser-safe",
|
|
2405
2582
|
node: e.file,
|
|
2406
2583
|
file: e.file,
|
|
@@ -2411,7 +2588,7 @@ var re = () => {
|
|
|
2411
2588
|
...p.diagnostics,
|
|
2412
2589
|
...m,
|
|
2413
2590
|
...h
|
|
2414
|
-
], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await
|
|
2591
|
+
], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await tr(C(s)), y = v === null ? null : Qn(f.procedures, v);
|
|
2415
2592
|
return t.format === "json" ? M(y === null ? {
|
|
2416
2593
|
...p,
|
|
2417
2594
|
ok: _,
|
|
@@ -2421,7 +2598,7 @@ var re = () => {
|
|
|
2421
2598
|
ok: _,
|
|
2422
2599
|
diagnostics: g,
|
|
2423
2600
|
observed: y
|
|
2424
|
-
}) : (console.log(
|
|
2601
|
+
}) : (console.log(ir(g)), y !== null && console.log(nr(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
|
|
2425
2602
|
};
|
|
2426
2603
|
//#endregion
|
|
2427
|
-
export { re as _,
|
|
2604
|
+
export { re as _, ze as a, I as c, N as d, ue as f, le as g, ce as h, Ut as i, ve as l, se as m, or as n, be as o, de as p, Yn as r, xe as s, ar as t, he as u, M as v, ie as y };
|