@voltro/cli 0.56.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +194 -0
- package/dist/apiBuild-B83Cb2Rv.js +2 -0
- package/dist/{apiBuild-Bdaetr37.js → apiBuild-DDr2aNFd.js} +2 -2
- package/dist/bin.js +1 -1
- package/dist/{build-CI36wL4R.js → build-QKP6Bm0J.js} +191 -187
- package/dist/{checkCommand-D0QV_zM_.js → checkCommand-BISqx1OJ.js} +1 -1
- package/dist/{checkCommand-CAwFXrxA.js → checkCommand-DUtMWjcR.js} +4 -4
- package/dist/{codegenCommand-CidbQzbv.js → codegenCommand-DnVuDxwT.js} +10 -10
- package/dist/{commands-CWjfThXv.js → commands-CRbxgxv0.js} +22 -22
- package/dist/{dashboardCommand-BekcY5Ls.js → dashboardCommand-Bf_-Ne3P.js} +5 -5
- package/dist/{dataCommand-2pccgbIy.js → dataCommand-BoBJJ-Gb.js} +2 -2
- package/dist/{dbCommand-DpK_vQET.js → dbCommand-CMAIz-Bf.js} +3 -3
- package/dist/dbCommand-DHi_RuDl.js +2 -0
- package/dist/dev-Bl9HqtV7.js +3 -0
- package/dist/{dev-Dw263KPu.js → dev-DOEJXicj.js} +2109 -2092
- package/dist/{doctorCommand-aR_bFmIi.js → doctorCommand-BrWu67JZ.js} +331 -189
- package/dist/doctorCommand-DrQv9SL3.js +2 -0
- package/dist/{dormancyCommand-eXTQMbHU.js → dormancyCommand-xn2y-pJm.js} +1 -1
- package/dist/{embeddingsCommand-CTmiQvwa.js → embeddingsCommand-Cn5MbRDM.js} +1 -1
- package/dist/emptyResultHeadline-Csa5fZOF.js +18 -0
- package/dist/{envCommand-BDUgV7EM.js → envCommand-UJmJIbs9.js} +8 -8
- package/dist/{evolveCommand-YV8qW1LU.js → evolveCommand-Db30twUy.js} +1 -1
- package/dist/frameworkTableAssembly-B96WCNJA.js +2 -0
- package/dist/{frameworkTableAssembly-DNOFXfEQ.js → frameworkTableAssembly-vfkzuzEo.js} +104 -85
- package/dist/index.js +1 -1
- package/dist/inspect-B0hL41s0.js +2 -0
- package/dist/{inspect-CNYvNXPU.js → inspect-DZnan87F.js} +68 -52
- package/dist/{logsCmd-BU8uCdys.js → logsCmd-C5y9HyrG.js} +58 -53
- package/dist/{manifestBuild-CEkjfpwc.js → manifestBuild-DnbFKF6w.js} +1 -1
- package/dist/manifestBuild-UXrnUcXP.js +2 -0
- package/dist/{migrate-SICulyz1.js → migrate-DtC3lu7H.js} +4 -4
- package/dist/{probeCommand-6HxEkNDG.js → probeCommand-C5fuN6Z2.js} +1 -1
- package/dist/{runtimeTrace-DgYMc09E.js → runtimeTrace-BQL_lfz6.js} +1 -1
- package/dist/{sdkgen-PY-umd6O.js → sdkgen-CMUPrDjH.js} +1 -1
- package/dist/{serveCommand-DIJ3ma76.js → serveCommand-B_w-Mpb_.js} +1013 -954
- package/dist/serveCommand-Dtb48ffg.js +2 -0
- package/dist/serveEntry.js +1 -1
- package/dist/{start-B9NGB8gn.js → start-YaUehtDV.js} +564 -571
- package/dist/{start-BFQQkL1i.js → start-s25GAIgn.js} +1 -1
- package/dist/startEntry.js +1 -1
- package/dist/{tracesCmd-BWYDqMy6.js → tracesCmd-C1qKa94g.js} +68 -63
- package/dist/{webDev-C2dRz9s5.js → webDev-B6ZMX42w.js} +1 -1
- package/dist/{webDev-C53hJdcL.js → webDev-BgdkyjP6.js} +881 -774
- package/dist/{webhooksCommand-uuPu8qQX.js → webhooksCommand-CYXTNvXq.js} +2 -2
- package/package.json +19 -19
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/_manifest.json +1 -1
- package/templates/agent-docs/ai.md +1 -1
- package/templates/agent-docs/authentication.md +34 -19
- package/templates/agent-docs/cli.md +48 -0
- package/templates/agent-docs/configuration.md +29 -4
- package/templates/agent-docs/database/scaling.md +10 -1
- package/templates/agent-docs/deployment.md +22 -0
- package/templates/agent-docs/plugins/audit.md +21 -5
- package/templates/agent-docs/plugins/billing.md +2 -0
- package/templates/agent-docs/reference.md +1 -0
- package/templates/agent-docs/scheduling.md +23 -0
- package/templates/agent-docs/schema-driven-ui.md +81 -0
- package/templates/agent-docs/whats-new.md +100 -403
- package/templates/apps/api-ai/package.json +6 -6
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +7 -7
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +7 -7
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-auth/package.json +7 -7
- package/templates/apps/frontend-blank/package.json +6 -6
- package/templates/apps/frontend-cms/package.json +8 -8
- 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 +6 -6
- package/templates/apps/frontend-docs/package.json +8 -8
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +7 -7
- package/templates/apps/frontend-saas/package.json +7 -7
- package/templates/apps/frontend-spa/package.json +6 -6
- package/templates/apps/frontend-ssr/package.json +6 -6
- package/templates/apps/frontend-ssr-api/package.json +7 -7
- package/templates/apps/frontend-static-blog/package.json +8 -8
- package/templates/apps/frontend-status/package.json +7 -7
- package/templates/apps/mobile-app/package.json +4 -4
- package/dist/apiBuild-Vw1figjO.js +0 -2
- package/dist/dbCommand-DrycGWWt.js +0 -2
- package/dist/dev-B9Gz0k85.js +0 -3
- package/dist/doctorCommand-BMWs6aVm.js +0 -2
- package/dist/frameworkTableAssembly-CGNC0qr7.js +0 -2
- package/dist/inspect-S2rWy1Ys.js +0 -2
- package/dist/manifestBuild-DIa_s6u0.js +0 -2
- package/dist/serveCommand-BITS8Hpj.js +0 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { r as d, u as f } from "./apiBuild-
|
|
4
|
-
import {
|
|
1
|
+
import { Jt as e, Xt as t, Yt as n } from "./dev-DOEJXicj.js";
|
|
2
|
+
import { Bt as r, Lt as i, R as a, Rt as o, S as s, t as c, u as l, zt as u } from "./webDev-BgdkyjP6.js";
|
|
3
|
+
import { r as d, u as f } from "./apiBuild-DDr2aNFd.js";
|
|
4
|
+
import { S as p } from "./frameworkTableAssembly-vfkzuzEo.js";
|
|
5
5
|
import { n as m } from "./pageConvention-CMpfDN6r.js";
|
|
6
6
|
import { n as h, r as g, t as ee } from "./cliOutput-D1tSBoRM.js";
|
|
7
7
|
import { n as _, t as te } from "./tsconfigPaths-BWXBWgcl.js";
|
|
@@ -496,12 +496,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
496
496
|
}, qe = (e, t) => {
|
|
497
497
|
let n = ["middleware.ts", "middleware.tsx"].find((t) => S(v(e, t)));
|
|
498
498
|
if (n === void 0) return;
|
|
499
|
-
let
|
|
499
|
+
let r = new D({
|
|
500
500
|
useInMemoryFileSystem: !0,
|
|
501
501
|
skipAddingFilesFromTsConfig: !0
|
|
502
|
-
}),
|
|
502
|
+
}), i;
|
|
503
503
|
try {
|
|
504
|
-
|
|
504
|
+
i = r.createSourceFile(n, C(v(e, n), "utf8"));
|
|
505
505
|
} catch {
|
|
506
506
|
return {
|
|
507
507
|
problems: [],
|
|
@@ -511,7 +511,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
511
511
|
};
|
|
512
512
|
}
|
|
513
513
|
let o = [], s = [], c = 0;
|
|
514
|
-
for (let e of
|
|
514
|
+
for (let e of i.getVariableDeclarations()) {
|
|
515
515
|
let t = e.getInitializerIfKind(O.CallExpression);
|
|
516
516
|
if (t?.getExpression().getText() !== "defineMiddleware") continue;
|
|
517
517
|
c += 1;
|
|
@@ -535,7 +535,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
535
535
|
});
|
|
536
536
|
}
|
|
537
537
|
return {
|
|
538
|
-
problems:
|
|
538
|
+
problems: a(o, t).map((e) => e.message),
|
|
539
539
|
unreadable: s,
|
|
540
540
|
seen: c,
|
|
541
541
|
file: n
|
|
@@ -863,7 +863,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
863
863
|
r !== void 0 && o.push(r);
|
|
864
864
|
}
|
|
865
865
|
return o;
|
|
866
|
-
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) =>
|
|
866
|
+
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) => zt.test(y(n, e))), l = new Set(c);
|
|
867
867
|
for (; c.length > 0;) {
|
|
868
868
|
let e = c.shift();
|
|
869
869
|
if (!s.has(e)) {
|
|
@@ -891,7 +891,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
891
891
|
}, kt = (e, t) => {
|
|
892
892
|
let n = t.getSymbol()?.getDeclarations()?.[0];
|
|
893
893
|
return n === void 0 ? [] : e.getDescendantsOfKind(O.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
|
|
894
|
-
}, At = /^(insert|insertMany|insertIgnore|update|updateMany|upsert|delete|deleteMany|hardDelete|patchJson)$/, jt = /^(notify|sendNotification|sendWebhook|dispatchWebhook|sendMail|trigger
|
|
894
|
+
}, At = /^(insert|insertMany|insertIgnore|update|updateMany|upsert|delete|deleteMany|hardDelete|patchJson)$/, jt = /^(notify|sendNotification|sendWebhook|dispatchWebhook|sendMail|trigger)\w*$/, Mt = (e, t) => {
|
|
895
895
|
if (!E.isObjectLiteralExpression(e)) return;
|
|
896
896
|
let n = e.getProperty("handler");
|
|
897
897
|
if (n === void 0 || !E.isPropertyAssignment(n)) return;
|
|
@@ -913,17 +913,97 @@ var le = /* @__PURE__ */ new Set([
|
|
|
913
913
|
n === "publish" && t.push(RegExp(`^${r}$`)), n === "store" && t.push(RegExp(`^${r}\\.${At.source.replace(/^\^|\$$/g, "")}$`));
|
|
914
914
|
}
|
|
915
915
|
return t;
|
|
916
|
-
}, Pt = (e
|
|
916
|
+
}, Pt = (e, t, n, r = 0, i = /* @__PURE__ */ new Set()) => {
|
|
917
|
+
let a = e.getDescendantsOfKind(O.CallExpression);
|
|
918
|
+
for (let e of a) {
|
|
919
|
+
let t = e.getExpression().getText();
|
|
920
|
+
if (n.some((e) => e.test(t))) return !0;
|
|
921
|
+
}
|
|
922
|
+
if (r >= 2) return !1;
|
|
923
|
+
for (let e of a) {
|
|
924
|
+
let a = e.getExpression().getText();
|
|
925
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(a) || i.has(a)) continue;
|
|
926
|
+
i.add(a);
|
|
927
|
+
let o = t.getFunction(a) ?? (() => {
|
|
928
|
+
let e = t.getVariableDeclaration(a)?.getInitializer();
|
|
929
|
+
return e !== void 0 && (E.isArrowFunction(e) || E.isFunctionExpression(e)) ? e : void 0;
|
|
930
|
+
})();
|
|
931
|
+
if (o !== void 0 && Pt(o, t, [...n, ...Nt(o)], r + 1, i)) return !0;
|
|
932
|
+
}
|
|
933
|
+
return !1;
|
|
934
|
+
}, Ft = (e) => {
|
|
935
|
+
let t = /* @__PURE__ */ new Set();
|
|
936
|
+
for (let n of e.values()) {
|
|
937
|
+
if (!/\bsetRowFilter\s*[<(]/.test(n)) continue;
|
|
938
|
+
let e = /\btables\s*:\s*\[([^\]]*)\]/.exec(n);
|
|
939
|
+
if (e !== null) for (let n of e[1].matchAll(/['"`]([A-Za-z_][\w]*)['"`]/g)) t.add(n[1]);
|
|
940
|
+
}
|
|
941
|
+
return t;
|
|
942
|
+
}, It = (e) => {
|
|
943
|
+
if (!/\.(query|stream|mutation|action)\.server\.tsx?$/.test(e.path)) return !1;
|
|
944
|
+
let t = Ft(e.sources);
|
|
945
|
+
return t.size === 0 || ['"`]/g)].map((e) => e[1]).some((e) => t.has(e)) ? !1 : /\b(eq|and|inSet)\s*\(/.test(e.src) || /\bwhere\s*:/.test(e.src);
|
|
946
|
+
}, Lt = (e) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").some((t) => {
|
|
917
947
|
let n = t.getArguments()[0];
|
|
918
948
|
if (n === void 0 || !E.isObjectLiteralExpression(n) || n.getProperty("once") !== void 0) return !1;
|
|
919
949
|
let r = Mt(n, e);
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
950
|
+
return r !== void 0 && Pt(r, e, Nt(r));
|
|
951
|
+
}), Rt = (e) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").filter((e) => {
|
|
952
|
+
let t = e.getArguments()[0];
|
|
953
|
+
return t !== void 0 && E.isObjectLiteralExpression(t) && t.getProperty("once") === void 0;
|
|
954
|
+
}).length, zt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Bt = (e, t) => {
|
|
955
|
+
let n;
|
|
956
|
+
try {
|
|
957
|
+
n = t.createSourceFile("/descriptor-scan.ts", e, { overwrite: !0 });
|
|
958
|
+
} catch {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
for (let e of n.getDescendantsOfKind(O.PropertyAssignment)) {
|
|
962
|
+
if (e.getName() !== "output") continue;
|
|
963
|
+
let t = e.getInitializer();
|
|
964
|
+
if (t !== void 0 && E.isCallExpression(t) && /\.Array$/.test(t.getExpression().getText()) && (t = t.getArguments()[0]), t === void 0 || !E.isCallExpression(t) || !/\.Struct$/.test(t.getExpression().getText())) return;
|
|
965
|
+
let n = t.getArguments()[0];
|
|
966
|
+
if (n === void 0 || !E.isObjectLiteralExpression(n)) return;
|
|
967
|
+
let r = [];
|
|
968
|
+
for (let e of [n, ...n.getDescendantsOfKind(O.ObjectLiteralExpression)]) for (let t of e.getProperties()) if (E.isPropertyAssignment(t) || E.isShorthandPropertyAssignment(t)) r.push(t.getName());
|
|
969
|
+
else if (E.isSpreadAssignment(t)) return;
|
|
970
|
+
return r;
|
|
971
|
+
}
|
|
972
|
+
}, Vt = (e, t) => {
|
|
973
|
+
let n = [], r = (e) => {
|
|
974
|
+
e !== void 0 && (E.isObjectLiteralExpression(e) ? n.push(e) : E.isParenthesizedExpression(e) && r(e.getExpression()));
|
|
975
|
+
};
|
|
976
|
+
for (let t of e.getDescendantsOfKind(O.ReturnStatement)) r(t.getExpression());
|
|
977
|
+
for (let t of e.getDescendantsOfKind(O.ArrowFunction)) r(t.getBody());
|
|
978
|
+
let i = /* @__PURE__ */ new Set();
|
|
979
|
+
for (let e of n) {
|
|
980
|
+
let n = [];
|
|
981
|
+
for (let t of e.getProperties()) if (E.isPropertyAssignment(t) || E.isShorthandPropertyAssignment(t)) n.push(t.getName());
|
|
982
|
+
else if (E.isSpreadAssignment(t)) return;
|
|
983
|
+
if (n.some((e) => t.has(e))) for (let e of n) i.add(e);
|
|
984
|
+
}
|
|
985
|
+
return [...i];
|
|
986
|
+
}, Ht = (e, t, n) => {
|
|
987
|
+
let r = RegExp(`\\btable\\(\\s*['"\`]${e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"\`]`);
|
|
988
|
+
for (let [, i] of t) {
|
|
989
|
+
if (!r.test(i)) continue;
|
|
990
|
+
let t;
|
|
991
|
+
try {
|
|
992
|
+
t = n.createSourceFile("/entity-scan.ts", i, { overwrite: !0 });
|
|
993
|
+
} catch {
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
for (let n of t.getDescendantsOfKind(O.CallExpression)) {
|
|
997
|
+
if (n.getExpression().getText() !== "table") continue;
|
|
998
|
+
let [t, r] = n.getArguments();
|
|
999
|
+
if (t === void 0 || !E.isStringLiteral(t) || t.getLiteralValue() !== e) continue;
|
|
1000
|
+
if (r === void 0 || !E.isObjectLiteralExpression(r)) return;
|
|
1001
|
+
let i = /* @__PURE__ */ new Set();
|
|
1002
|
+
for (let e of r.getProperties()) E.isPropertyAssignment(e) && /\.nullable\s*\(/.test(e.getText()) && i.add(e.getName());
|
|
1003
|
+
return i;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}, Ut = (e) => /\bNullOr\b|\bSchema\.Null\b|\bnullable\s*\(/.test(e), P = /\.(query|mutation|action|stream)\.server\.tsx?$/, Wt = [
|
|
927
1007
|
"Id",
|
|
928
1008
|
"ID",
|
|
929
1009
|
"Hash",
|
|
@@ -949,7 +1029,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
949
1029
|
"Comment",
|
|
950
1030
|
"Provider",
|
|
951
1031
|
"Format"
|
|
952
|
-
],
|
|
1032
|
+
], Gt = RegExp(`(?:${Wt.join("|")})$|_(?:${Wt.map((e) => e.toLowerCase()).join("|")})$`), Kt = [
|
|
953
1033
|
{
|
|
954
1034
|
id: "hardcoded-framework-cookie",
|
|
955
1035
|
scope: "client",
|
|
@@ -962,7 +1042,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
962
1042
|
scope: "server",
|
|
963
1043
|
smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
|
|
964
1044
|
use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
|
|
965
|
-
match: ({ src: e, path: t }) =>
|
|
1045
|
+
match: ({ src: e, path: t }) => P.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
|
|
966
1046
|
},
|
|
967
1047
|
{
|
|
968
1048
|
id: "subject-write-no-guard",
|
|
@@ -971,7 +1051,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
971
1051
|
use: "declare `guards:` on the descriptor, or — if the procedure really is open — compare the loaded row's owner against `ctx.request.subject.id` and fail with a typed error",
|
|
972
1052
|
match: (e) => {
|
|
973
1053
|
let { src: t, path: n } = e;
|
|
974
|
-
if (!
|
|
1054
|
+
if (!P.test(n)) return !1;
|
|
975
1055
|
let r = M(t);
|
|
976
1056
|
if (!(/\bsubject\s*\.\s*id\b/.test(r) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(r)) || /\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(r) || /[=!]==?\s*[^;\n]*\bsubject\s*\.\s*id\b|\bsubject\s*\.\s*id\b\s*[=!]==/.test(r)) return !1;
|
|
977
1057
|
let i = wt(e);
|
|
@@ -985,7 +1065,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
985
1065
|
scope: "server",
|
|
986
1066
|
smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
|
|
987
1067
|
use: "yield* HttpClient — the framework client. It injects traceparent, retries, and honours `http.allowHosts`. Where the URL is built from anything a caller supplied it ALSO blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop — for a hard-coded host that half is redundant, and the first three are still the reason to switch",
|
|
988
|
-
match: ({ file: e, path: t, serverOnly: n }) => (
|
|
1068
|
+
match: ({ file: e, path: t, serverOnly: n }) => (zt.test(t) || n) && N(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
|
|
989
1069
|
},
|
|
990
1070
|
{
|
|
991
1071
|
id: "form",
|
|
@@ -1103,7 +1183,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1103
1183
|
use: ".encrypted() on the column — boot fails loudly if no cipher is configured, so it can never silently persist plaintext (a *Hash column and a vault* handle are NOT plaintext secrets: the hash IS the protection — often a unique lookup key — and the handle only names a secret held elsewhere, so both are skipped)",
|
|
1104
1184
|
match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
|
|
1105
1185
|
let n = t[1];
|
|
1106
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
1186
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Gt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
|
|
1107
1187
|
}))
|
|
1108
1188
|
},
|
|
1109
1189
|
{
|
|
@@ -1113,15 +1193,60 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1113
1193
|
use: "you cannot look a value up by equality AND encrypt it non-deterministically — `.encrypted()` would make this column unfindable. Store the HASH as the unique lookup key and keep the secret itself either `.encrypted()` in a second column or nowhere (the framework's own `_voltro_api_keys.hashedKey` is the shape: the presented value is hashed and compared, never read back). A deterministic cipher is the other option and buys equality lookup at the cost of leaking which rows share a value",
|
|
1114
1194
|
match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
|
|
1115
1195
|
let n = t[1];
|
|
1116
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
1196
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Gt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
|
|
1117
1197
|
}))
|
|
1118
1198
|
},
|
|
1199
|
+
{
|
|
1200
|
+
id: "nullable-column-a-mutation-cannot-clear",
|
|
1201
|
+
scope: "server",
|
|
1202
|
+
smell: "a mutation writes a NULLABLE column through an input field that does not accept `null` — the value can be set once and never cleared",
|
|
1203
|
+
use: "declare the field as `Schema.optional(Schema.NullOr(...))` — a column the schema lets be empty needs a wire shape that can say so",
|
|
1204
|
+
match: (e) => {
|
|
1205
|
+
if (!/\.mutation\.tsx?$/.test(e.path)) return !1;
|
|
1206
|
+
let t = /target\s*:\s*\{[^}]*\btable\s*:\s*['"`]([^'"`]+)['"`]/.exec(e.src)?.[1];
|
|
1207
|
+
if (t === void 0) return !1;
|
|
1208
|
+
let n = Ht(t, e.sources, e.file.getProject());
|
|
1209
|
+
if (n === void 0 || n.size === 0) return !1;
|
|
1210
|
+
for (let t of e.file.getDescendantsOfKind(O.PropertyAssignment)) {
|
|
1211
|
+
if (t.getName() !== "input") continue;
|
|
1212
|
+
let e = t.getInitializer();
|
|
1213
|
+
if (e === void 0 || !E.isCallExpression(e) || !/\.Struct$/.test(e.getExpression().getText())) continue;
|
|
1214
|
+
let r = e.getArguments()[0];
|
|
1215
|
+
if (!(r === void 0 || !E.isObjectLiteralExpression(r))) {
|
|
1216
|
+
for (let e of r.getProperties()) if (E.isPropertyAssignment(e) && n.has(e.getName()) && !Ut(e.getText())) return !0;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return !1;
|
|
1220
|
+
}
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
id: "executor-builds-an-undeclared-field",
|
|
1224
|
+
scope: "server",
|
|
1225
|
+
smell: "an executor builds a field its descriptor's `output` does not declare (silently stripped on the way out)",
|
|
1226
|
+
use: "add the field to the descriptor's `output` struct, or stop building it — `output` IS the serializer, so an undeclared key never reaches the caller",
|
|
1227
|
+
match: (e) => {
|
|
1228
|
+
if (!P.test(e.path)) return !1;
|
|
1229
|
+
let t = wt(e);
|
|
1230
|
+
if (t === void 0) return !1;
|
|
1231
|
+
let n = Bt(t, e.file.getProject());
|
|
1232
|
+
if (n === void 0 || n.length === 0) return !1;
|
|
1233
|
+
let r = Vt(e.file, new Set(n));
|
|
1234
|
+
return r === void 0 || r.length === 0 ? !1 : r.some((e) => !n.includes(e));
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1119
1237
|
{
|
|
1120
1238
|
id: "subscriber-effect-without-once",
|
|
1121
1239
|
scope: "server",
|
|
1122
1240
|
smell: "a *.subscribe.ts handler that WRITES or publishes with no `once:` (it runs on every replica, so the effect repeats once per replica)",
|
|
1123
1241
|
use: "once: true on defineSubscriber — one INSERT-wins claim per change, fleet-wide (or `once: (event) => key` for a coarser key). A hand-rolled `insertIgnore` dedupe is this, written out.",
|
|
1124
|
-
match: ({ file: e }) =>
|
|
1242
|
+
match: ({ file: e }) => Lt(e)
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
id: "row-filter-duplicated-by-hand",
|
|
1246
|
+
scope: "server",
|
|
1247
|
+
smell: "a handler that constrains a table your registered `setRowFilter` already constrains",
|
|
1248
|
+
use: "drop the hand-written predicate — the filter covers the query, the mutation, the stream AND the subscription delivery. Keep it only if this handler must be NARROWER than the filter, and say so in a comment (two identical predicates ANDed are as tight as one, so this is maintenance, not a leak)",
|
|
1249
|
+
match: (e) => It(e)
|
|
1125
1250
|
},
|
|
1126
1251
|
{
|
|
1127
1252
|
id: "mutation-tail-effect",
|
|
@@ -1196,14 +1321,14 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1196
1321
|
use: "timestampMs / timestampMsOrNull from @voltro/database/wire in the descriptor's output struct — `output` IS the serializer (it is the rpc success schema), so a Date field declared as timestampMs crosses as epoch ms on its own; declaring Schema.Number describes the WIRE type and leaves nothing to convert",
|
|
1197
1322
|
match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
|
|
1198
1323
|
}
|
|
1199
|
-
],
|
|
1324
|
+
], F = (e) => L(e), qt = (e, t) => {
|
|
1200
1325
|
let n = /* @__PURE__ */ new Set();
|
|
1201
1326
|
for (let r of t) {
|
|
1202
1327
|
let t = y(e, r), i = t.split(/[\\/]/)[0];
|
|
1203
1328
|
n.add(i !== void 0 && i !== t ? i : ".");
|
|
1204
1329
|
}
|
|
1205
1330
|
return [...n].sort();
|
|
1206
|
-
},
|
|
1331
|
+
}, Jt = /\.(tsx?|jsx?)$/, I = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Yt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Xt = (e, t = [], n = 0) => {
|
|
1207
1332
|
if (n > 12 || I.test(e)) return t;
|
|
1208
1333
|
let r;
|
|
1209
1334
|
try {
|
|
@@ -1215,7 +1340,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1215
1340
|
let r = v(e, i.name);
|
|
1216
1341
|
if (i.isDirectory()) {
|
|
1217
1342
|
if (i.name.startsWith(".")) continue;
|
|
1218
|
-
|
|
1343
|
+
Xt(r, t, n + 1);
|
|
1219
1344
|
} else je.test(i.name) && t.push(r);
|
|
1220
1345
|
}
|
|
1221
1346
|
return t;
|
|
@@ -1234,10 +1359,10 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1234
1359
|
} catch {
|
|
1235
1360
|
continue;
|
|
1236
1361
|
}
|
|
1237
|
-
a.isDirectory() ? L(r, t, n + 1) :
|
|
1362
|
+
a.isDirectory() ? L(r, t, n + 1) : Jt.test(i) && !/\.(test|spec)\./.test(i) && !Yt.test(i) && t.push(r);
|
|
1238
1363
|
}
|
|
1239
1364
|
return t;
|
|
1240
|
-
},
|
|
1365
|
+
}, Zt = (e, t = [], n = 0) => {
|
|
1241
1366
|
if (n > 12 || I.test(e)) return t;
|
|
1242
1367
|
let r;
|
|
1243
1368
|
try {
|
|
@@ -1252,17 +1377,17 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1252
1377
|
} catch {
|
|
1253
1378
|
continue;
|
|
1254
1379
|
}
|
|
1255
|
-
a.isDirectory() ?
|
|
1380
|
+
a.isDirectory() ? Zt(r, t, n + 1) : Jt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
|
|
1256
1381
|
}
|
|
1257
1382
|
return t;
|
|
1258
|
-
},
|
|
1259
|
-
let t =
|
|
1383
|
+
}, Qt = (e) => {
|
|
1384
|
+
let t = F(e);
|
|
1260
1385
|
return {
|
|
1261
|
-
dirs:
|
|
1386
|
+
dirs: qt(e, t),
|
|
1262
1387
|
fileCount: t.length
|
|
1263
1388
|
};
|
|
1264
|
-
},
|
|
1265
|
-
let t =
|
|
1389
|
+
}, $t = (e) => {
|
|
1390
|
+
let t = F(e);
|
|
1266
1391
|
if (t.length === 0) return [];
|
|
1267
1392
|
let n = new D({
|
|
1268
1393
|
useInMemoryFileSystem: !0,
|
|
@@ -1272,7 +1397,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1272
1397
|
i.set(e, C(e, "utf8"));
|
|
1273
1398
|
} catch {}
|
|
1274
1399
|
let a = Tt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
|
|
1275
|
-
for (let t of
|
|
1400
|
+
for (let t of Zt(e)) try {
|
|
1276
1401
|
o.set(t, C(t, "utf8"));
|
|
1277
1402
|
} catch {}
|
|
1278
1403
|
for (let s of t) {
|
|
@@ -1292,23 +1417,34 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1292
1417
|
} catch {
|
|
1293
1418
|
continue;
|
|
1294
1419
|
}
|
|
1295
|
-
for (let t of
|
|
1420
|
+
for (let t of Kt) {
|
|
1296
1421
|
if (t.plugin !== void 0 && Dt(c, t.plugin) || !t.match(c)) continue;
|
|
1297
1422
|
let n = r.get(t.id) ?? [];
|
|
1298
1423
|
n.push(y(e, s)), r.set(t.id, n);
|
|
1299
1424
|
}
|
|
1300
1425
|
}
|
|
1301
|
-
return
|
|
1426
|
+
return Kt.filter((e) => r.has(e.id)).map((e) => ({
|
|
1302
1427
|
rule: e,
|
|
1303
1428
|
files: r.get(e.id)
|
|
1304
1429
|
})).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
|
|
1430
|
+
}, en = (e) => {
|
|
1431
|
+
let t = new D({
|
|
1432
|
+
useInMemoryFileSystem: !0,
|
|
1433
|
+
skipAddingFilesFromTsConfig: !0
|
|
1434
|
+
}), n = 0;
|
|
1435
|
+
for (let r of F(e)) if (/\.subscribe\.tsx?$/.test(r)) try {
|
|
1436
|
+
let e = t.createSourceFile(`/count${r.endsWith("x") ? ".tsx" : ".ts"}`, C(r, "utf8"), { overwrite: !0 });
|
|
1437
|
+
n += Rt(e);
|
|
1438
|
+
} catch {}
|
|
1439
|
+
return n;
|
|
1305
1440
|
}, R = (e) => {
|
|
1306
|
-
let t =
|
|
1441
|
+
let t = Qt(e);
|
|
1307
1442
|
return {
|
|
1308
1443
|
root: e,
|
|
1309
1444
|
scannedFiles: t.fileCount,
|
|
1310
1445
|
scannedDirs: t.dirs,
|
|
1311
|
-
|
|
1446
|
+
subscribersWithoutOnce: en(e),
|
|
1447
|
+
findings: $t(e).map(({ rule: e, files: t }) => ({
|
|
1312
1448
|
id: e.id,
|
|
1313
1449
|
scope: e.scope,
|
|
1314
1450
|
smell: e.smell,
|
|
@@ -1316,13 +1452,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1316
1452
|
files: t
|
|
1317
1453
|
}))
|
|
1318
1454
|
};
|
|
1319
|
-
},
|
|
1455
|
+
}, tn = 3, nn = .2, rn = 50, an = (e, t) => e >= rn && t > 0 && e / t >= nn, on = (e) => {
|
|
1320
1456
|
let t = /* @__PURE__ */ new Map();
|
|
1321
1457
|
for (let n of L(e)) try {
|
|
1322
1458
|
t.set(y(e, n), C(n, "utf8"));
|
|
1323
1459
|
} catch {}
|
|
1324
1460
|
let n = /* @__PURE__ */ new Map();
|
|
1325
|
-
for (let t of
|
|
1461
|
+
for (let t of Xt(e)) try {
|
|
1326
1462
|
n.set(y(e, t), C(t, "utf8"));
|
|
1327
1463
|
} catch {}
|
|
1328
1464
|
let r = Me({
|
|
@@ -1330,7 +1466,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1330
1466
|
envFiles: n
|
|
1331
1467
|
});
|
|
1332
1468
|
r !== void 0 && (console.log(""), console.log(Ne(r)));
|
|
1333
|
-
},
|
|
1469
|
+
}, sn = (e) => {
|
|
1334
1470
|
let t = R(e);
|
|
1335
1471
|
if (console.log(""), t.scannedFiles === 0) return console.log(`\u2022 hand-roll scan: no source directories found under ${e}.`), console.log(" Walked the app root recursively (node_modules / dist / .framework pruned). Run `voltro doctor <app>` against an app root."), 0;
|
|
1336
1472
|
if (t.findings.length === 0) return console.log(`\u2713 hand-roll scan: ${t.scannedFiles} file${t.scannedFiles === 1 ? "" : "s"} across ${t.scannedDirs.join(", ")} \u2014 no hand-rolled primitives found.`), 0;
|
|
@@ -1342,37 +1478,43 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1342
1478
|
console.log(` [${e}]`);
|
|
1343
1479
|
let i = [...r].sort((e, t) => e.files.length - t.files.length);
|
|
1344
1480
|
for (let e of i) {
|
|
1345
|
-
if (
|
|
1481
|
+
if (an(e.files.length, t.scannedFiles)) {
|
|
1346
1482
|
let n = Math.round(e.files.length / t.scannedFiles * 100);
|
|
1347
1483
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s), ${n}% of those scanned: a codebase-wide pattern rather than a work list`), console.log(` \u2192 ${e.use}`), console.log(" (paths omitted on purpose at this scale — `voltro doctor --json` has every one.)");
|
|
1348
1484
|
continue;
|
|
1349
1485
|
}
|
|
1350
|
-
let r = e.files.slice(0,
|
|
1486
|
+
let r = e.files.slice(0, tn).join(", "), i = e.files.length - tn;
|
|
1351
1487
|
i > 0 && (n += i);
|
|
1352
1488
|
let a = i > 0 ? `, +${i} more` : "";
|
|
1353
1489
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
|
|
1354
1490
|
}
|
|
1355
1491
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1492
|
+
n > 0 && console.log(` (${n} more file path${n === 1 ? "" : "s"} not shown \u2014 \`voltro doctor --json\` prints every one.)`);
|
|
1493
|
+
let r = t.findings.find((e) => e.id === "subscriber-effect-without-once");
|
|
1494
|
+
if (t.subscribersWithoutOnce > 0) {
|
|
1495
|
+
let e = r?.files.length ?? 0;
|
|
1496
|
+
console.log(` \u24d8 ${t.subscribersWithoutOnce} subscriber(s) declare no \`once:\`; an effect was found in ${e}. The rule reads each subscriber's OWN file (its handler and the functions that file declares) and does not resolve imports — a write in another module is unexamined, not absent.`);
|
|
1497
|
+
}
|
|
1498
|
+
return console.log(" (See the \"Pick the SERVER primitive\" / \"Pick the CLIENT primitive\" rubrics in AGENTS.md / CLAUDE.md.)"), t.findings.length;
|
|
1499
|
+
}, cn = "renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.", ln = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, un = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, dn = (e) => {
|
|
1500
|
+
let t = ln.exec(e)?.[1];
|
|
1359
1501
|
if (t !== void 0) return t === "ssr" ? "ssr" : null;
|
|
1360
|
-
let n =
|
|
1502
|
+
let n = un.exec(e)?.[1];
|
|
1361
1503
|
return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
|
|
1362
1504
|
}, z = async (e) => {
|
|
1363
1505
|
let t = v(e, "src", "pages");
|
|
1364
1506
|
if (!S(t)) return [];
|
|
1365
|
-
let { pages: n, dirs: r } = await
|
|
1507
|
+
let { pages: n, dirs: r } = await s(t), i = [];
|
|
1366
1508
|
for (let a of n) {
|
|
1367
|
-
if (!
|
|
1509
|
+
if (!l(r, a.dirChain).some((e) => e.hasLayout)) continue;
|
|
1368
1510
|
let n;
|
|
1369
1511
|
try {
|
|
1370
1512
|
n = C(v(t, a.file), "utf8");
|
|
1371
1513
|
} catch {
|
|
1372
1514
|
continue;
|
|
1373
1515
|
}
|
|
1374
|
-
if (
|
|
1375
|
-
let o =
|
|
1516
|
+
if (c.test(n)) continue;
|
|
1517
|
+
let o = dn(n);
|
|
1376
1518
|
o !== null && i.push({
|
|
1377
1519
|
file: y(e, v(t, a.file)),
|
|
1378
1520
|
pattern: a.pattern,
|
|
@@ -1380,20 +1522,20 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1380
1522
|
});
|
|
1381
1523
|
}
|
|
1382
1524
|
return i;
|
|
1383
|
-
},
|
|
1525
|
+
}, fn = 10, pn = async (e) => {
|
|
1384
1526
|
console.log("");
|
|
1385
1527
|
let t = v(e, "src", "pages");
|
|
1386
1528
|
if (!S(t)) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
|
|
1387
1529
|
let n = await z(e);
|
|
1388
1530
|
if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
|
|
1389
1531
|
console.log(`• renderMode:'spa' candidates (${n.length} page${n.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
|
|
1390
|
-
for (let e of n.slice(0,
|
|
1532
|
+
for (let e of n.slice(0, fn)) {
|
|
1391
1533
|
let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
|
|
1392
1534
|
console.log(` ${e.file} (${e.pattern}) — ${t}`);
|
|
1393
1535
|
}
|
|
1394
|
-
let r = n.length -
|
|
1395
|
-
return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${
|
|
1396
|
-
},
|
|
1536
|
+
let r = n.length - fn;
|
|
1537
|
+
return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${cn}`), n.length;
|
|
1538
|
+
}, mn = /* @__PURE__ */ new Set([
|
|
1397
1539
|
"node_modules",
|
|
1398
1540
|
"dist",
|
|
1399
1541
|
"build",
|
|
@@ -1401,7 +1543,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1401
1543
|
".vite",
|
|
1402
1544
|
".framework",
|
|
1403
1545
|
".git"
|
|
1404
|
-
]),
|
|
1546
|
+
]), hn = (e, t, n) => {
|
|
1405
1547
|
let r;
|
|
1406
1548
|
try {
|
|
1407
1549
|
r = w(e, { withFileTypes: !0 });
|
|
@@ -1409,11 +1551,11 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1409
1551
|
return;
|
|
1410
1552
|
}
|
|
1411
1553
|
for (let i of r) {
|
|
1412
|
-
if (
|
|
1554
|
+
if (mn.has(i.name)) continue;
|
|
1413
1555
|
let r = v(e, i.name);
|
|
1414
|
-
i.isDirectory() ?
|
|
1556
|
+
i.isDirectory() ? hn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
|
|
1415
1557
|
}
|
|
1416
|
-
},
|
|
1558
|
+
}, gn = (e, t) => {
|
|
1417
1559
|
let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
|
|
1418
1560
|
for (let i of t) {
|
|
1419
1561
|
let t;
|
|
@@ -1438,16 +1580,16 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1438
1580
|
}
|
|
1439
1581
|
}
|
|
1440
1582
|
return n;
|
|
1441
|
-
},
|
|
1583
|
+
}, _n = (e, t) => {
|
|
1442
1584
|
let n = e.slice(t.length).split(x).slice(0, -1)[0];
|
|
1443
1585
|
return n !== void 0 && n.startsWith("[");
|
|
1444
1586
|
}, B = (e) => {
|
|
1445
1587
|
let t = [], n = v(e, "src", "pages"), r = v(e, "src");
|
|
1446
1588
|
if (!S(r)) return t;
|
|
1447
1589
|
let i = [];
|
|
1448
|
-
|
|
1590
|
+
hn(r, e, i), i.sort();
|
|
1449
1591
|
let a = `${y(e, n)}${x}`;
|
|
1450
|
-
|
|
1592
|
+
gn(e, i);
|
|
1451
1593
|
for (let n of i) {
|
|
1452
1594
|
let r = n.slice(n.lastIndexOf(x) + 1);
|
|
1453
1595
|
if (n.startsWith(a)) {
|
|
@@ -1463,7 +1605,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1463
1605
|
file: n
|
|
1464
1606
|
});
|
|
1465
1607
|
} catch {}
|
|
1466
|
-
|
|
1608
|
+
_n(n, a) && t.push({
|
|
1467
1609
|
kind: "top-level-dynamic-route",
|
|
1468
1610
|
file: n
|
|
1469
1611
|
});
|
|
@@ -1475,35 +1617,35 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1475
1617
|
});
|
|
1476
1618
|
}
|
|
1477
1619
|
return t;
|
|
1478
|
-
},
|
|
1620
|
+
}, vn = 10, V = (e, t, n) => {
|
|
1479
1621
|
if (t.length === 0) return;
|
|
1480
1622
|
console.log(`• ${e} (${t.length}):`);
|
|
1481
|
-
for (let e of t.slice(0,
|
|
1482
|
-
let r = t.length -
|
|
1623
|
+
for (let e of t.slice(0, vn)) console.log(` ${e}`);
|
|
1624
|
+
let r = t.length - vn;
|
|
1483
1625
|
r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
|
|
1484
|
-
},
|
|
1626
|
+
}, yn = (e) => {
|
|
1485
1627
|
console.log("");
|
|
1486
1628
|
let t = v(e, "src", "pages");
|
|
1487
1629
|
if (!S(t)) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
|
|
1488
1630
|
let n = B(e);
|
|
1489
1631
|
if (n.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
|
|
1490
1632
|
let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
|
|
1491
|
-
return
|
|
1492
|
-
},
|
|
1493
|
-
let t =
|
|
1633
|
+
return V("will NEVER route — page.tsx outside src/pages/", r("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), V("pages with no test beside them", r("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), V("routes whose FIRST segment is dynamic — they claim every URL of that depth", r("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), V("pages with no `meta` export — they serve the APP name as their title", r("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). If such a page also has no real body — it redirects from an effect, say — the missing title is the symptom rather than the bug: on an `ssr` route the server answers 200 with an empty document and the redirect happens only once JavaScript runs. Move it into the loader (`redirect(...)`, a real 303). Advisory."), n.length;
|
|
1634
|
+
}, bn = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", xn = /* @__PURE__ */ new Map(), H = (e) => {
|
|
1635
|
+
let t = xn.get(e);
|
|
1494
1636
|
if (t !== void 0) return t;
|
|
1495
1637
|
let n = (async () => {
|
|
1496
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
1638
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-Bl9HqtV7.js"), r = await t(e);
|
|
1497
1639
|
return {
|
|
1498
1640
|
files: r,
|
|
1499
|
-
discovered: await n(r)
|
|
1641
|
+
discovered: await n(r, { root: e })
|
|
1500
1642
|
};
|
|
1501
1643
|
})();
|
|
1502
|
-
return
|
|
1503
|
-
},
|
|
1644
|
+
return xn.set(e, n), n;
|
|
1645
|
+
}, Sn = async (e) => {
|
|
1504
1646
|
let t;
|
|
1505
1647
|
try {
|
|
1506
|
-
let { files: n, discovered: r } = await
|
|
1648
|
+
let { files: n, discovered: r } = await H(e), i = r.tables.map((e) => {
|
|
1507
1649
|
let t = e;
|
|
1508
1650
|
return {
|
|
1509
1651
|
name: t.tableName,
|
|
@@ -1516,7 +1658,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1516
1658
|
return;
|
|
1517
1659
|
}
|
|
1518
1660
|
t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
|
|
1519
|
-
},
|
|
1661
|
+
}, Cn = (e) => e.map((e) => {
|
|
1520
1662
|
let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
|
|
1521
1663
|
name: e,
|
|
1522
1664
|
type: typeof t?.type == "string" ? t.type : ""
|
|
@@ -1526,13 +1668,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1526
1668
|
columns: n,
|
|
1527
1669
|
primaryKey: t.appliedPrimaryKey ?? []
|
|
1528
1670
|
};
|
|
1529
|
-
}),
|
|
1671
|
+
}), wn = async (e) => {
|
|
1530
1672
|
try {
|
|
1531
|
-
let { missingRelationSources: t } = await import("./doctorSourceRelations-c5mlN5HI.js"), { sourceKeys: n } = await import("@voltro/protocol"), { readFile: r } = await import("node:fs/promises"), { discovered: i } = await
|
|
1673
|
+
let { missingRelationSources: t } = await import("./doctorSourceRelations-c5mlN5HI.js"), { sourceKeys: n } = await import("@voltro/protocol"), { readFile: r } = await import("node:fs/promises"), { discovered: i } = await H(e), a = [], o = [];
|
|
1532
1674
|
for (let e of i.queries) {
|
|
1533
1675
|
let i = n(e.descriptor.source);
|
|
1534
1676
|
if (i.length === 0) continue;
|
|
1535
|
-
let s =
|
|
1677
|
+
let s = Tn(e.file);
|
|
1536
1678
|
if (s === void 0) continue;
|
|
1537
1679
|
let c = await r(s, "utf8").catch(() => null);
|
|
1538
1680
|
if (c === null) continue;
|
|
@@ -1549,8 +1691,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1549
1691
|
} catch {
|
|
1550
1692
|
return null;
|
|
1551
1693
|
}
|
|
1552
|
-
},
|
|
1553
|
-
let t = await
|
|
1694
|
+
}, Tn = (e) => e.endsWith(".ts") && !e.endsWith(".server.ts") ? `${e.slice(0, -3)}.server.ts` : void 0, En = async (e) => {
|
|
1695
|
+
let t = await wn(e);
|
|
1554
1696
|
if (t === null) return;
|
|
1555
1697
|
let n = t.findings;
|
|
1556
1698
|
if (n.length > 0) {
|
|
@@ -1563,21 +1705,21 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1563
1705
|
for (let e of t.unresolved) console.log(` ${e.procedure}: ${e.relations.map((e) => `\`${e}\``).join(", ")} resolved against no table`);
|
|
1564
1706
|
console.log(" declare the base table in `source:` and re-run — relations under it are unchecked until then.");
|
|
1565
1707
|
}
|
|
1566
|
-
},
|
|
1708
|
+
}, U = async (e) => {
|
|
1567
1709
|
try {
|
|
1568
|
-
let { discovered: t } = await
|
|
1569
|
-
return ve(
|
|
1710
|
+
let { discovered: t } = await H(e);
|
|
1711
|
+
return ve(Cn(t.tables));
|
|
1570
1712
|
} catch {
|
|
1571
1713
|
return null;
|
|
1572
1714
|
}
|
|
1573
|
-
},
|
|
1574
|
-
let t = await
|
|
1715
|
+
}, Dn = async (e) => {
|
|
1716
|
+
let t = await U(e);
|
|
1575
1717
|
if (t === null || t.tablesChecked === 0) return;
|
|
1576
1718
|
let n = be(t);
|
|
1577
1719
|
n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
|
|
1578
|
-
},
|
|
1720
|
+
}, W = async (e) => {
|
|
1579
1721
|
try {
|
|
1580
|
-
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await
|
|
1722
|
+
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await H(e);
|
|
1581
1723
|
return we(n.events.map((e) => {
|
|
1582
1724
|
let n = e.descriptor, r = t(n.key);
|
|
1583
1725
|
return {
|
|
@@ -1590,19 +1732,19 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1590
1732
|
} catch {
|
|
1591
1733
|
return null;
|
|
1592
1734
|
}
|
|
1593
|
-
},
|
|
1594
|
-
let t = await
|
|
1735
|
+
}, On = async (e) => {
|
|
1736
|
+
let t = await W(e);
|
|
1595
1737
|
if (t === null || t.eventsChecked === 0) return;
|
|
1596
1738
|
let n = Te(t);
|
|
1597
1739
|
n !== null && (console.log(""), console.log(`• ${n}`));
|
|
1598
1740
|
let r = Ee(t);
|
|
1599
1741
|
r !== null && (console.log(""), console.log(`⚠️ ${r}`));
|
|
1600
|
-
},
|
|
1742
|
+
}, G = (e) => {
|
|
1601
1743
|
let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
|
|
1602
1744
|
return r === void 0 ? n : `${r}: ${n}`;
|
|
1603
|
-
},
|
|
1745
|
+
}, K = async (e) => {
|
|
1604
1746
|
try {
|
|
1605
|
-
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await
|
|
1747
|
+
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await H(e)).discovered.serverOnlyLeaks, r = t(n);
|
|
1606
1748
|
return {
|
|
1607
1749
|
status: {
|
|
1608
1750
|
checked: !0,
|
|
@@ -1613,12 +1755,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1613
1755
|
} catch (e) {
|
|
1614
1756
|
return { status: {
|
|
1615
1757
|
checked: !1,
|
|
1616
|
-
reason:
|
|
1758
|
+
reason: G(e)
|
|
1617
1759
|
} };
|
|
1618
1760
|
}
|
|
1619
|
-
},
|
|
1761
|
+
}, q = async (r) => {
|
|
1620
1762
|
try {
|
|
1621
|
-
let { loadApiConfig: i } = await import("./dev-
|
|
1763
|
+
let { loadApiConfig: i } = await import("./dev-Bl9HqtV7.js"), { discovered: a } = await H(r), o = n(await i(r)), s = {
|
|
1622
1764
|
queries: a.queries,
|
|
1623
1765
|
mutations: a.mutations,
|
|
1624
1766
|
actions: a.actions,
|
|
@@ -1628,32 +1770,32 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1628
1770
|
return {
|
|
1629
1771
|
checked: !0,
|
|
1630
1772
|
defaultDeny: o,
|
|
1631
|
-
undecided:
|
|
1773
|
+
undecided: t(s),
|
|
1632
1774
|
openAccess: e(s)
|
|
1633
1775
|
};
|
|
1634
1776
|
} catch (e) {
|
|
1635
1777
|
return {
|
|
1636
1778
|
checked: !1,
|
|
1637
|
-
reason:
|
|
1779
|
+
reason: G(e),
|
|
1638
1780
|
defaultDeny: !0,
|
|
1639
1781
|
undecided: [],
|
|
1640
1782
|
openAccess: []
|
|
1641
1783
|
};
|
|
1642
1784
|
}
|
|
1643
|
-
},
|
|
1644
|
-
let t = await
|
|
1785
|
+
}, kn = async (e) => {
|
|
1786
|
+
let t = await q(e);
|
|
1645
1787
|
if (!t.checked) return console.log(""), console.log("• access decisions: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
|
|
1646
1788
|
console.log(""), console.log(`access decisions · security.defaultDeny ${t.defaultDeny ? "ON" : "OFF"}`), console.log(` ✗ no access decision ${String(t.undecided.length).padStart(4)}`), console.log(` ✓ openAccess, declared on purpose ${String(t.openAccess.length).padStart(4)}`);
|
|
1647
1789
|
for (let e of t.openAccess) console.log(` ${e.tag} — ${e.openAccess ?? ""}`);
|
|
1648
1790
|
if (t.undecided.length === 0) return !1;
|
|
1649
1791
|
console.log("");
|
|
1650
1792
|
for (let e of t.undecided) console.log(` ✗ ${e.tag} (${e.kind})${e.file === void 0 ? "" : `\n ${e.file}`}`);
|
|
1651
|
-
return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"),
|
|
1652
|
-
},
|
|
1793
|
+
return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), An(), !0) : (console.log(" This app sets `security: { defaultDeny: false }`, so these BOOT — and each one is"), console.log(" callable by any authenticated session. Advisory here for that reason only."), An(), !1);
|
|
1794
|
+
}, An = () => {
|
|
1653
1795
|
console.log(" `guards: [{ scope: '…' }]` authority the SUBJECT holds globally"), console.log(" `guards: [{ action, resourceType, resource }]` authority per ROW / per resource"), console.log(" — backed by `defineResourcePolicy` + a tuple source you register. If your"), console.log(" relationships already live in your own tables (a `teamMembers` row), register"), console.log(" a tuple source over them; nothing is copied. Fail-closed without a resolver."), console.log(" Docs: Authentication → Authorization."), console.log(" `openAccess: '<why anyone may call it>'` deliberately unauthenticated");
|
|
1654
|
-
},
|
|
1796
|
+
}, J = async (e) => {
|
|
1655
1797
|
try {
|
|
1656
|
-
let { files: t } = await
|
|
1798
|
+
let { files: t } = await H(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
|
|
1657
1799
|
if (a.length === 0) return {
|
|
1658
1800
|
checked: !0,
|
|
1659
1801
|
unverified: [],
|
|
@@ -1680,14 +1822,14 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1680
1822
|
} catch (e) {
|
|
1681
1823
|
return {
|
|
1682
1824
|
checked: !1,
|
|
1683
|
-
reason:
|
|
1825
|
+
reason: G(e),
|
|
1684
1826
|
unverified: [],
|
|
1685
1827
|
open: [],
|
|
1686
1828
|
verified: 0
|
|
1687
1829
|
};
|
|
1688
1830
|
}
|
|
1689
|
-
},
|
|
1690
|
-
let t = await
|
|
1831
|
+
}, jn = async (e) => {
|
|
1832
|
+
let t = await J(e);
|
|
1691
1833
|
if (!t.checked) return console.log(""), console.log("• webhook verification: NOT CHECKED — the app's webhooks could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
|
|
1692
1834
|
if (t.verified + t.open.length + t.unverified.length === 0) return !1;
|
|
1693
1835
|
console.log(""), console.log("incoming webhook verification"), console.log(` ✓ signature / provider verified ${String(t.verified).padStart(4)}`), console.log(` ! deliberately public (none) ${String(t.open.length).padStart(4)}`), console.log(` ✗ nothing verifies the caller ${String(t.unverified.length).padStart(4)}`);
|
|
@@ -1696,13 +1838,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1696
1838
|
console.log("");
|
|
1697
1839
|
for (let e of t.unverified) console.log(` ✗ ${e.id}\n ${e.file}`);
|
|
1698
1840
|
return console.log(""), console.log(" These are public POSTs that run your application code, and `voltro dev` /"), console.log(" `voltro serve` REFUSE to mount them. Declare one of: `provider:` (a preset),"), console.log(" `signature:` (your own HMAC scheme), `verification: 'provider'` (your handler"), console.log(" verifies), or `verification: 'none'` (deliberately public)."), !0;
|
|
1699
|
-
},
|
|
1700
|
-
let { status: t, message: n } = await
|
|
1841
|
+
}, Mn = async (e) => {
|
|
1842
|
+
let { status: t, message: n } = await K(e);
|
|
1701
1843
|
return t.checked ? n !== void 0 && (console.log(""), console.log(`✗ [serverOnly] ${n}`), !0) : (console.log(""), console.log("• serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), console.log(" This audit is the automated check that a hash or a token cannot reach the wire, so"), console.log(" an app that leans on `.serverOnly()` should treat a persistent skip as a failure."), console.log(" `voltro doctor --json` reports it as `serverOnly.checked: false` for a CI gate."), !1);
|
|
1702
|
-
},
|
|
1703
|
-
hasWorkflows: async () => (await
|
|
1844
|
+
}, Nn = async (e) => ft({
|
|
1845
|
+
hasWorkflows: async () => (await H(e)).discovered.workflows.length > 0,
|
|
1704
1846
|
openStore: async () => {
|
|
1705
|
-
let { loadApiConfig: t, buildStore: n } = await import("./dev-
|
|
1847
|
+
let { loadApiConfig: t, buildStore: n } = await import("./dev-Bl9HqtV7.js"), r = await t(e);
|
|
1706
1848
|
if (r === null) throw Error("no app.config.ts in this directory");
|
|
1707
1849
|
let i = await n(r, void 0);
|
|
1708
1850
|
return {
|
|
@@ -1716,13 +1858,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1716
1858
|
let { sweepStalledRuns: t } = await import("@voltro/workflow");
|
|
1717
1859
|
return t({ store: e });
|
|
1718
1860
|
}
|
|
1719
|
-
}),
|
|
1720
|
-
let t = mt(await
|
|
1861
|
+
}), Pn = async (e) => {
|
|
1862
|
+
let t = mt(await Nn(e));
|
|
1721
1863
|
t !== null && (console.log(""), console.log(t));
|
|
1722
|
-
},
|
|
1864
|
+
}, Fn = async (e) => {
|
|
1723
1865
|
let t;
|
|
1724
1866
|
try {
|
|
1725
|
-
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await
|
|
1867
|
+
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await H(e);
|
|
1726
1868
|
t = ke(r, i.workflows.map((e) => {
|
|
1727
1869
|
let t = e.definition, r = t.payloadSchema === void 0 ? {
|
|
1728
1870
|
required: [],
|
|
@@ -1742,18 +1884,18 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1742
1884
|
console.log("");
|
|
1743
1885
|
let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
|
|
1744
1886
|
console.log(`${r} ${n}`);
|
|
1745
|
-
},
|
|
1887
|
+
}, Y = "voltro-authz-allowlist.txt", In = (e, t = (e) => {
|
|
1746
1888
|
try {
|
|
1747
1889
|
return C(e, "utf8");
|
|
1748
1890
|
} catch {
|
|
1749
1891
|
return "";
|
|
1750
1892
|
}
|
|
1751
|
-
}) => /* @__PURE__ */ new Set([...Xe, ...Ze(L(e).map(t))]),
|
|
1893
|
+
}) => /* @__PURE__ */ new Set([...Xe, ...Ze(L(e).map(t))]), X = (e, t) => {
|
|
1752
1894
|
let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1753
1895
|
return new RegExp(String.raw`(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=\s*define[A-Za-z]*Event\s*\(\s*\{[^}]*?name\s*:\s*['"\`]${n}['"\`]`, "s").exec(e)?.[1];
|
|
1754
1896
|
}, Z = async (e) => {
|
|
1755
1897
|
try {
|
|
1756
|
-
let { discovered: t } = await
|
|
1898
|
+
let { discovered: t } = await H(e), n = t.events.map((e) => ({
|
|
1757
1899
|
name: e.descriptor.name,
|
|
1758
1900
|
file: e.file
|
|
1759
1901
|
})).filter((e) => typeof e.name == "string");
|
|
@@ -1767,22 +1909,22 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1767
1909
|
}, a = L(e).filter((e) => !r.has(e)).map(i).join("\n");
|
|
1768
1910
|
return n.filter(({ name: e, file: t }) => {
|
|
1769
1911
|
if (a.includes(e)) return !1;
|
|
1770
|
-
let n =
|
|
1912
|
+
let n = X(i(t), e);
|
|
1771
1913
|
return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
|
|
1772
1914
|
}).map((e) => e.name).sort();
|
|
1773
1915
|
} catch {
|
|
1774
1916
|
return [];
|
|
1775
1917
|
}
|
|
1776
|
-
},
|
|
1918
|
+
}, Ln = async (e) => {
|
|
1777
1919
|
let t = await Z(e);
|
|
1778
1920
|
if (t.length !== 0) {
|
|
1779
1921
|
console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
|
|
1780
1922
|
for (let e of t) console.log(` ${e}`);
|
|
1781
1923
|
console.log(""), console.log(" An event you declare is an event partners can subscribe to. One that"), console.log(" never fires is indistinguishable, from their side, from a broken"), console.log(" endpoint of their own — so they debug your contract instead of"), console.log(" reading it."), console.log(" ADVISORY: this looks for the event name AND the identifier its"), console.log(" declaration binds it to, so `emit(ctx, apiKeyCreated, …)` counts. An"), console.log(" emit through an ASSEMBLED name still reads as missing here.");
|
|
1782
1924
|
}
|
|
1783
|
-
},
|
|
1925
|
+
}, Rn = async (e) => {
|
|
1784
1926
|
try {
|
|
1785
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
1927
|
+
let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js"), n = (await t(e))?.doctor?.scopeVocabulary;
|
|
1786
1928
|
if (typeof n != "string") return [];
|
|
1787
1929
|
let [r, i] = n.split("#");
|
|
1788
1930
|
if (r === void 0 || i === void 0) return [];
|
|
@@ -1799,10 +1941,10 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1799
1941
|
findings: []
|
|
1800
1942
|
});
|
|
1801
1943
|
try {
|
|
1802
|
-
let { offlineManifest: n, toInput: r } = await import("./checkCommand-
|
|
1944
|
+
let { offlineManifest: n, toInput: r } = await import("./checkCommand-BISqx1OJ.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CFmFKHb2.js"), o = await n(e), s = r(o.manifest, o.tables), c = await Rn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
|
|
1803
1945
|
if (l.length === 0) {
|
|
1804
1946
|
try {
|
|
1805
|
-
let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-
|
|
1947
|
+
let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-B96WCNJA.js");
|
|
1806
1948
|
await t(e);
|
|
1807
1949
|
} catch {
|
|
1808
1950
|
return t("app-not-importable");
|
|
@@ -1826,7 +1968,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1826
1968
|
} catch {
|
|
1827
1969
|
return t("app-not-importable");
|
|
1828
1970
|
}
|
|
1829
|
-
},
|
|
1971
|
+
}, zn = async (e) => {
|
|
1830
1972
|
let { findings: t, evaluated: n, notEvaluated: r } = await Q(e);
|
|
1831
1973
|
if (!n) {
|
|
1832
1974
|
if (console.log(""), r === "app-not-importable") {
|
|
@@ -1841,7 +1983,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1841
1983
|
for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
|
|
1842
1984
|
console.log(""), console.log(" Each of these is uncallable by every user in every role — not"), console.log(" misconfigured, DEAD — and nothing fails at boot to say so. The"), console.log(" usual cause is a typo or a rename that moved one side only."), console.log(` ${t[0].fix}`);
|
|
1843
1985
|
}
|
|
1844
|
-
},
|
|
1986
|
+
}, Bn = (e) => {
|
|
1845
1987
|
let t = v(e, "src");
|
|
1846
1988
|
if (!S(t)) return;
|
|
1847
1989
|
let n = [], r = (t, i = 0) => {
|
|
@@ -1877,7 +2019,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1877
2019
|
console.log("");
|
|
1878
2020
|
for (let e of i) console.log(e);
|
|
1879
2021
|
}
|
|
1880
|
-
},
|
|
2022
|
+
}, Vn = async (e) => {
|
|
1881
2023
|
let t = ["src/locales", "locales"], n, r = [];
|
|
1882
2024
|
for (let i of t) {
|
|
1883
2025
|
let t = v(e, i);
|
|
@@ -1889,7 +2031,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1889
2031
|
if (n === void 0 || r.length === 0) return;
|
|
1890
2032
|
let i = [];
|
|
1891
2033
|
try {
|
|
1892
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2034
|
+
let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js");
|
|
1893
2035
|
i = (await t(e))?.locales ?? [];
|
|
1894
2036
|
} catch {
|
|
1895
2037
|
return;
|
|
@@ -1902,7 +2044,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1902
2044
|
console.log("");
|
|
1903
2045
|
for (let e of Ge(a, n)) console.log(e);
|
|
1904
2046
|
}
|
|
1905
|
-
},
|
|
2047
|
+
}, Hn = async (e) => {
|
|
1906
2048
|
let t = v(e, "src");
|
|
1907
2049
|
if (!S(t)) return;
|
|
1908
2050
|
let n = !1, r = (e) => {
|
|
@@ -1921,9 +2063,9 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1921
2063
|
return;
|
|
1922
2064
|
}
|
|
1923
2065
|
a()?.reason === "binary-missing" ? (console.log("image pipeline: sharp is INSTALLED but its platform binary is missing —"), console.log(" an install that omitted optional dependencies drops the @img/* prebuilds."), console.log(" Fix: reinstall without omitting optional deps, or `pnpm add -D sharp` in this app.")) : (console.log("image pipeline: sharp is NOT installed — ?image imports serve originals (no srcSet, no blur)."), console.log(" Fix: `pnpm add -D sharp` in this app (it is an optional dependency of @voltro/cli;"), console.log(" your installer skipped optional dependencies)."));
|
|
1924
|
-
},
|
|
2066
|
+
}, Un = async (e) => {
|
|
1925
2067
|
try {
|
|
1926
|
-
let { loadConfig: t } = await import("./webDev-
|
|
2068
|
+
let { loadConfig: t } = await import("./webDev-B6ZMX42w.js"), n = (await t(e, { preferSource: !0 }))?.fonts;
|
|
1927
2069
|
if (n === void 0 || n.length === 0) return;
|
|
1928
2070
|
let { loadFontkit: r, loadSubsetFont: i } = await import("./fontPipeline-LxIHa1vo.js"), a = [];
|
|
1929
2071
|
if (await r() === null && a.push("fontkit (size-adjust fallback metrics — the CLS guard — are skipped)"), await i() === null && a.push("subset-font (declared `subsets:` ship the full files)"), console.log(""), a.length === 0) {
|
|
@@ -1933,12 +2075,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1933
2075
|
for (let e of a) console.log(`font pipeline: ${e}`);
|
|
1934
2076
|
console.log(" Fix: reinstall without omitting optional deps, or `pnpm add -D fontkit subset-font` in this app.");
|
|
1935
2077
|
} catch {}
|
|
1936
|
-
},
|
|
2078
|
+
}, Wn = async (e) => {
|
|
1937
2079
|
let t = v(e, "src", "pages");
|
|
1938
2080
|
if (!S(t)) return !1;
|
|
1939
2081
|
let n = [];
|
|
1940
2082
|
try {
|
|
1941
|
-
let { walkPagesTree: e } = await import("./webDev-
|
|
2083
|
+
let { walkPagesTree: e } = await import("./webDev-B6ZMX42w.js");
|
|
1942
2084
|
n = (await e(t)).pages.map((e) => e.pattern);
|
|
1943
2085
|
} catch {
|
|
1944
2086
|
return !1;
|
|
@@ -1948,10 +2090,10 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1948
2090
|
console.log("");
|
|
1949
2091
|
for (let e of Je(r)) console.log(e);
|
|
1950
2092
|
return r.problems.length > 0;
|
|
1951
|
-
},
|
|
1952
|
-
let { discovered: t } = await
|
|
2093
|
+
}, Gn = async (e) => {
|
|
2094
|
+
let { discovered: t } = await H(e), n = [];
|
|
1953
2095
|
try {
|
|
1954
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2096
|
+
let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js");
|
|
1955
2097
|
n = ((await t(e))?.plugins ?? []).map((e) => {
|
|
1956
2098
|
let t = (e.extendSchema?.tables ?? []).map((e) => {
|
|
1957
2099
|
let t = e;
|
|
@@ -1964,8 +2106,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1964
2106
|
plugin: e.name,
|
|
1965
2107
|
tables: t,
|
|
1966
2108
|
rpcTags: n,
|
|
1967
|
-
canDisableTables:
|
|
1968
|
-
canAlias:
|
|
2109
|
+
canDisableTables: Kn.has(r),
|
|
2110
|
+
canAlias: qn.has(r)
|
|
1969
2111
|
};
|
|
1970
2112
|
});
|
|
1971
2113
|
} catch {
|
|
@@ -1983,12 +2125,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1983
2125
|
});
|
|
1984
2126
|
console.log();
|
|
1985
2127
|
for (let e of dt(n, i)) console.log(e);
|
|
1986
|
-
},
|
|
2128
|
+
}, Kn = /* @__PURE__ */ new Set([
|
|
1987
2129
|
"@voltro/plugin-rbac",
|
|
1988
2130
|
"@voltro/plugin-audit",
|
|
1989
2131
|
"@voltro/plugin-notifications",
|
|
1990
2132
|
"aiFlows"
|
|
1991
|
-
]),
|
|
2133
|
+
]), qn = /* @__PURE__ */ new Set([
|
|
1992
2134
|
"aiFlows",
|
|
1993
2135
|
"@voltro/plugin-audit",
|
|
1994
2136
|
"@voltro/plugin-billing",
|
|
@@ -2006,8 +2148,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
2006
2148
|
"@voltro/plugin-sso-saml",
|
|
2007
2149
|
"@voltro/plugin-storage",
|
|
2008
2150
|
"@voltro/plugin-row-history"
|
|
2009
|
-
]), $ = 20,
|
|
2010
|
-
let t = await
|
|
2151
|
+
]), $ = 20, Jn = async (e) => {
|
|
2152
|
+
let t = await Yn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
|
|
2011
2153
|
return {
|
|
2012
2154
|
executors: t.executorCount,
|
|
2013
2155
|
guardVocabulary: [...t.vocabulary].sort(),
|
|
@@ -2019,15 +2161,15 @@ var le = /* @__PURE__ */ new Set([
|
|
|
2019
2161
|
},
|
|
2020
2162
|
unchecked: t.result.unchecked
|
|
2021
2163
|
};
|
|
2022
|
-
},
|
|
2023
|
-
let { discovered: t } = await
|
|
2164
|
+
}, Yn = async (e) => {
|
|
2165
|
+
let { discovered: t } = await H(e), n = /* @__PURE__ */ new Map(), r = (e) => {
|
|
2024
2166
|
if (!n.has(e)) try {
|
|
2025
2167
|
n.set(e, C(e, "utf8"));
|
|
2026
2168
|
} catch {
|
|
2027
2169
|
n.set(e, "");
|
|
2028
2170
|
}
|
|
2029
2171
|
return n.get(e);
|
|
2030
|
-
}, i =
|
|
2172
|
+
}, i = In(e, r), a = /* @__PURE__ */ new Set();
|
|
2031
2173
|
for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
|
|
2032
2174
|
let e = t.references?.();
|
|
2033
2175
|
e && a.add(e.tableName);
|
|
@@ -2059,7 +2201,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
2059
2201
|
op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
|
|
2060
2202
|
...a?.table === void 0 ? {} : { table: a.table }
|
|
2061
2203
|
};
|
|
2062
|
-
})), s = v(e,
|
|
2204
|
+
})), s = v(e, Y), c = S(s) ? j(C(s, "utf8")) : void 0, l = !1;
|
|
2063
2205
|
for (let t of L(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
|
|
2064
2206
|
l = !0;
|
|
2065
2207
|
break;
|
|
@@ -2081,23 +2223,23 @@ var le = /* @__PURE__ */ new Set([
|
|
|
2081
2223
|
executorCount: o.length,
|
|
2082
2224
|
allowPath: s
|
|
2083
2225
|
};
|
|
2084
|
-
},
|
|
2085
|
-
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await
|
|
2226
|
+
}, Xn = async (e, t) => {
|
|
2227
|
+
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Yn(e), l = n.counts;
|
|
2086
2228
|
console.log(`\nauthz scan · ${o} executor(s)`);
|
|
2087
2229
|
let u = [...r].filter((e) => !Xe.has(e)).sort();
|
|
2088
2230
|
console.log(u.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${u.length} from your source (${u.slice(0, 6).join(", ")}${u.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(l.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(l.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(l.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(l.vocabularyGuard).padStart(4)}`);
|
|
2089
2231
|
let d = new Set(a.map((e) => e.tag)), f = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !d.has(e.tag)).length, p = l.allowlisted - f;
|
|
2090
|
-
if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${
|
|
2232
|
+
if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Y})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
|
|
2091
2233
|
let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = st(e, t);
|
|
2092
|
-
return oe(s, ot(e, t), "utf8"), console.log(`\n${
|
|
2234
|
+
return oe(s, ot(e, t), "utf8"), console.log(`\n${Y}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
|
|
2093
2235
|
}
|
|
2094
2236
|
if (n.unchecked.length === 0) return !1;
|
|
2095
2237
|
console.log("");
|
|
2096
2238
|
for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
|
|
2097
|
-
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 ${
|
|
2098
|
-
},
|
|
2239
|
+
return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Y} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Y}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
|
|
2240
|
+
}, Zn = async (e) => {
|
|
2099
2241
|
try {
|
|
2100
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2242
|
+
let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
|
|
2101
2243
|
return r == null ? null : n({
|
|
2102
2244
|
...r.schema ? { declared: r.schema } : {},
|
|
2103
2245
|
...r.store === void 0 ? {} : { store: r.store },
|
|
@@ -2106,42 +2248,42 @@ var le = /* @__PURE__ */ new Set([
|
|
|
2106
2248
|
} catch {
|
|
2107
2249
|
return null;
|
|
2108
2250
|
}
|
|
2109
|
-
},
|
|
2110
|
-
let t = await
|
|
2251
|
+
}, Qn = async (e) => {
|
|
2252
|
+
let t = await Zn(e);
|
|
2111
2253
|
if (t !== null) {
|
|
2112
2254
|
console.log(""), console.log("• framework tables whose presence is DECIDED rather than implied by your source tree:");
|
|
2113
2255
|
for (let e of t.text.split("\n")) console.log(` ${e}`);
|
|
2114
2256
|
}
|
|
2115
|
-
},
|
|
2116
|
-
if (e.includes("--help") || e.includes("-h")) return console.log(
|
|
2117
|
-
let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")),
|
|
2257
|
+
}, $n = async (e) => {
|
|
2258
|
+
if (e.includes("--help") || e.includes("-h")) return console.log(bn), 0;
|
|
2259
|
+
let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), a = b(n ?? process.cwd());
|
|
2118
2260
|
if (g(e)) return ee(), h({
|
|
2119
|
-
...R(
|
|
2120
|
-
spaCandidates: await z(
|
|
2121
|
-
pageConvention: B(
|
|
2122
|
-
fileTaxonomy: re(
|
|
2123
|
-
junctionFks: (await
|
|
2124
|
-
eventDelivery: await
|
|
2125
|
-
duplicateInstances: a
|
|
2126
|
-
versionSkew: o(
|
|
2127
|
-
serverOnly: (await
|
|
2128
|
-
accessDecisions: await
|
|
2129
|
-
webhookVerification: await
|
|
2130
|
-
authz: await
|
|
2131
|
-
ungrantedScopes: await Q(
|
|
2132
|
-
neverEmittedEvents: await Z(
|
|
2133
|
-
unimportedDeps: He(
|
|
2134
|
-
stalledRuns: await
|
|
2135
|
-
decidedSchema: await
|
|
2261
|
+
...R(a),
|
|
2262
|
+
spaCandidates: await z(a),
|
|
2263
|
+
pageConvention: B(a),
|
|
2264
|
+
fileTaxonomy: re(a).findings,
|
|
2265
|
+
junctionFks: (await U(a))?.issues ?? [],
|
|
2266
|
+
eventDelivery: await W(a),
|
|
2267
|
+
duplicateInstances: i(a),
|
|
2268
|
+
versionSkew: o(a) ?? null,
|
|
2269
|
+
serverOnly: (await K(a)).status,
|
|
2270
|
+
accessDecisions: await q(a),
|
|
2271
|
+
webhookVerification: await J(a),
|
|
2272
|
+
authz: await Jn(a),
|
|
2273
|
+
ungrantedScopes: await Q(a),
|
|
2274
|
+
neverEmittedEvents: await Z(a),
|
|
2275
|
+
unimportedDeps: He(a),
|
|
2276
|
+
stalledRuns: await Nn(a),
|
|
2277
|
+
decidedSchema: await Zn(a)
|
|
2136
2278
|
}), 0;
|
|
2137
|
-
let
|
|
2138
|
-
|
|
2139
|
-
for (let e of Ue(He(
|
|
2140
|
-
await
|
|
2141
|
-
let
|
|
2279
|
+
let s = Ct(a, t);
|
|
2280
|
+
sn(a), on(a), yn(a), ne(a), await pn(a), await Fn(a), await Pn(a), await Sn(a), await Dn(a), await En(a), await Qn(a), await On(a), await Gn(a), await zn(a), await Ln(a), console.log("");
|
|
2281
|
+
for (let e of Ue(He(a))) console.log(e);
|
|
2282
|
+
await Vn(a), Bn(a), await Hn(a), await Un(a);
|
|
2283
|
+
let c = await Mn(a), l = await Xn(a, e.includes("--write-authz-allowlist")), d = await kn(a), f = await jn(a), p = await Wn(a), m = r(o(a));
|
|
2142
2284
|
m !== void 0 && (console.log(""), console.log(m));
|
|
2143
|
-
let _ =
|
|
2144
|
-
return _ !== void 0 && console.log(`\n${_}`),
|
|
2285
|
+
let _ = u(i(a));
|
|
2286
|
+
return _ !== void 0 && console.log(`\n${_}`), c || l || d || p || f ? Math.max(s, 1) : s;
|
|
2145
2287
|
};
|
|
2146
2288
|
//#endregion
|
|
2147
|
-
export { z as C,
|
|
2289
|
+
export { z as C, J as D, Q as E, B as S, Rt as T, yn as _, Jn as a, Ct as b, W as c, Qt as d, xt as f, sn as g, on as h, q as i, In as l, wn as m, St as n, Z as o, U as p, cn as r, X as s, bn as t, R as u, pn as v, K as w, $t as x, $n as y };
|