@voltro/cli 0.30.1 → 0.31.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 +163 -0
- package/dist/apiBuild-BOqL1dlg.js +2 -0
- package/dist/{apiBuild-DmkzRyv7.js → apiBuild-CLPt-pX6.js} +2 -2
- package/dist/appGraph-CvQCte0z.js +2 -0
- package/dist/appGraph-gQ_6GkQQ.js +110 -0
- package/dist/bin.js +3 -3
- package/dist/checkCommand-ClRvH_SN.js +2 -0
- package/dist/{checkCommand-C4konUy9.js → checkCommand-D7r6_pk7.js} +439 -530
- package/dist/{commands-BkJft9K2.js → commands-CNVVv03h.js} +1941 -1880
- package/dist/{dbCommand-qn__kLEt.js → dbCommand-BH8GAYuf.js} +76 -19
- package/dist/dbCommand-BwQ_Jl98.js +2 -0
- package/dist/{dev-DXhhZ7-f.js → dev-DOZVMVfi.js} +1 -1
- package/dist/{dev-C_NPcLDp.js → dev-DdQEn2Ia.js} +1986 -1967
- package/dist/{frameworkTableAssembly-Cpgn2e88.js → frameworkTableAssembly-BwIrO5nv.js} +122 -103
- package/dist/frameworkTableAssembly-D-EebUQX.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-CkB2J0Zn.js → inspectMetrics-BeEAnjqw.js} +774 -527
- package/dist/{sdkgen-wsWLrq4l.js → sdkgen-v9WV8zRi.js} +1 -1
- package/dist/{serveCommand-HtI4X5kj.js → serveCommand-KsKXXWn4.js} +294 -293
- package/dist/serveEntry.js +2 -2
- package/dist/{start-egkbpugc.js → start-CI2QdKy9.js} +351 -346
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.md +43 -43
- package/templates/agent-docs/_index.md +43 -43
- package/templates/agent-docs/_manifest.json +84 -42
- package/templates/agent-docs/cli.md +4 -2
- package/templates/agent-docs/database/migrations.md +14 -1
- package/templates/agent-docs/internationalization.md +54 -3
- package/templates/agent-docs/plugins/ai-flows.md +148 -0
- package/templates/agent-docs/plugins/analytics-postgres.md +90 -0
- package/templates/agent-docs/plugins/atlassian.md +378 -0
- package/templates/agent-docs/plugins/audit.md +324 -0
- package/templates/agent-docs/plugins/auth-auth0.md +92 -0
- package/templates/agent-docs/plugins/auth-clerk.md +90 -0
- package/templates/agent-docs/plugins/auth-kinde.md +88 -0
- package/templates/agent-docs/plugins/auth-oidc.md +95 -0
- package/templates/agent-docs/plugins/auth-supabase.md +103 -0
- package/templates/agent-docs/plugins/auth-workos.md +105 -0
- package/templates/agent-docs/plugins/auth.md +227 -0
- package/templates/agent-docs/plugins/billing.md +324 -0
- package/templates/agent-docs/plugins/broadcast.md +144 -0
- package/templates/agent-docs/plugins/cdc-out.md +116 -0
- package/templates/agent-docs/plugins/clickhouse.md +91 -0
- package/templates/agent-docs/plugins/datadog.md +105 -0
- package/templates/agent-docs/plugins/deactivation.md +114 -0
- package/templates/agent-docs/plugins/duckdb.md +82 -0
- package/templates/agent-docs/plugins/flags.md +146 -0
- package/templates/agent-docs/plugins/governance.md +143 -0
- package/templates/agent-docs/plugins/licensing.md +74 -0
- package/templates/agent-docs/plugins/logship.md +56 -0
- package/templates/agent-docs/plugins/mail.md +334 -0
- package/templates/agent-docs/plugins/moderation.md +65 -0
- package/templates/agent-docs/plugins/multitenancy.md +99 -0
- package/templates/agent-docs/plugins/notifications.md +252 -0
- package/templates/agent-docs/plugins/openapi.md +94 -0
- package/templates/agent-docs/plugins/postgis.md +140 -0
- package/templates/agent-docs/plugins/posthog.md +91 -0
- package/templates/agent-docs/plugins/presence.md +115 -0
- package/templates/agent-docs/plugins/prometheus.md +124 -0
- package/templates/agent-docs/plugins/ratelimit.md +274 -0
- package/templates/agent-docs/plugins/rbac.md +270 -0
- package/templates/agent-docs/plugins/scim.md +63 -0
- package/templates/agent-docs/plugins/search.md +116 -0
- package/templates/agent-docs/plugins/sentry.md +192 -0
- package/templates/agent-docs/plugins/soft-delete.md +72 -0
- package/templates/agent-docs/plugins/sso-saml.md +132 -0
- package/templates/agent-docs/plugins/storage.md +709 -0
- package/templates/agent-docs/plugins/tinybird.md +92 -0
- package/templates/agent-docs/plugins/versioning.md +226 -0
- package/templates/agent-docs/plugins/webhooks.md +585 -0
- package/templates/agent-docs/security.md +12 -1
- package/templates/agent-docs/whats-new.md +81 -37
- 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-Ca6Sq5jy.js +0 -2
- package/dist/checkCommand-DHD0aBeR.js +0 -2
- package/dist/dbCommand-wfGi3FgG.js +0 -2
- package/dist/frameworkTableAssembly-Bn_c4Mnl.js +0 -2
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { C as
|
|
4
|
-
import { r as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { Ct as e, et as t, f as n, n as r, nt as i, o as a, rt as o, tt as s, z as c } from "./inspectMetrics-BeEAnjqw.js";
|
|
2
|
+
import { Ht as l, Ut as u } from "./dev-DdQEn2Ia.js";
|
|
3
|
+
import { C as d, E as f, O as p, T as m, d as h, h as g, i as _, l as v, m as y, r as ee, u as te } from "./fileConventions-Cof68_BL.js";
|
|
4
|
+
import { r as ne, u as re } from "./apiBuild-CLPt-pX6.js";
|
|
5
|
+
import { r as ie } from "./appGraph-gQ_6GkQQ.js";
|
|
6
|
+
import { dirname as b, isAbsolute as ae, join as x, relative as S, resolve as C, sep as w } from "node:path";
|
|
7
|
+
import { routeDiagnosticsToStderr as oe } from "@voltro/logger";
|
|
8
|
+
import { existsSync as T, readFileSync as E, readdirSync as D, statSync as O, writeFileSync as se } from "node:fs";
|
|
8
9
|
import { Node as k, Project as A, SyntaxKind as j } from "ts-morph";
|
|
9
10
|
//#region src/cliOutput.ts
|
|
10
|
-
var
|
|
11
|
-
|
|
11
|
+
var ce = () => {
|
|
12
|
+
oe();
|
|
12
13
|
}, M = (e, t = {}) => {
|
|
13
14
|
console.log(JSON.stringify(e, null, t.pretty === !1 ? 0 : 2));
|
|
14
|
-
},
|
|
15
|
+
}, le = (e) => {
|
|
15
16
|
for (let t = 0; t < e.length; t++) {
|
|
16
17
|
let n = e[t];
|
|
17
18
|
if (n === "--json" || n === "--format=json" || n === "--format" && e[t + 1] === "json") return !0;
|
|
18
19
|
}
|
|
19
20
|
return !1;
|
|
20
|
-
},
|
|
21
|
+
}, ue = (e, t, n = !1) => {
|
|
21
22
|
let r = { ...e ?? {} }, i = t ?? process.env.VOLTRO_INSPECT_TOKEN;
|
|
22
23
|
if (i !== void 0 && i !== "" && (r.authorization = `Bearer ${i}`), n) {
|
|
23
24
|
let e = process.env.VOLTRO_INSPECT_WRITE_TOKEN;
|
|
24
25
|
e !== void 0 && e !== "" && (r["x-voltro-inspect-write"] = e);
|
|
25
26
|
}
|
|
26
27
|
return r;
|
|
27
|
-
},
|
|
28
|
+
}, de = /* @__PURE__ */ new Set([
|
|
28
29
|
"POST",
|
|
29
30
|
"PUT",
|
|
30
31
|
"PATCH",
|
|
@@ -34,7 +35,7 @@ var re = () => {
|
|
|
34
35
|
try {
|
|
35
36
|
let t = await fetch(r, {
|
|
36
37
|
method: i,
|
|
37
|
-
headers:
|
|
38
|
+
headers: ue(a ? { "content-type": "application/json" } : void 0, e.inspectToken, de.has(i)),
|
|
38
39
|
...a ? { body: JSON.stringify(n.body) } : {}
|
|
39
40
|
}), o = await t.text(), s;
|
|
40
41
|
if (o.length > 0) try {
|
|
@@ -43,7 +44,7 @@ var re = () => {
|
|
|
43
44
|
s = o;
|
|
44
45
|
}
|
|
45
46
|
if (!t.ok) {
|
|
46
|
-
let n = s, r = typeof n == "object" && n ? String(n.message ?? n.reason ?? n.error ?? `HTTP ${t.status}`) : `HTTP ${t.status}`, a =
|
|
47
|
+
let n = s, r = typeof n == "object" && n ? String(n.message ?? n.reason ?? n.error ?? `HTTP ${t.status}`) : `HTTP ${t.status}`, a = de.has(i) && (process.env.VOLTRO_INSPECT_WRITE_TOKEN ?? "") === "" && /write/i.test(r);
|
|
47
48
|
return {
|
|
48
49
|
kind: "err",
|
|
49
50
|
name: e.name,
|
|
@@ -63,7 +64,7 @@ var re = () => {
|
|
|
63
64
|
error: t.message || String(t)
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
|
-
},
|
|
67
|
+
}, P = (e, t = process.env) => {
|
|
67
68
|
let n = [], r, i;
|
|
68
69
|
for (let t = 0; t < e.length; t++) {
|
|
69
70
|
let a = e[t];
|
|
@@ -108,7 +109,7 @@ var re = () => {
|
|
|
108
109
|
},
|
|
109
110
|
rest: n
|
|
110
111
|
};
|
|
111
|
-
},
|
|
112
|
+
}, fe = (e) => ({
|
|
112
113
|
id: `remote:${e.url}`,
|
|
113
114
|
name: e.name,
|
|
114
115
|
kind: "api",
|
|
@@ -119,7 +120,7 @@ var re = () => {
|
|
|
119
120
|
status: "live",
|
|
120
121
|
logFile: "",
|
|
121
122
|
...e.token === void 0 ? {} : { inspectToken: e.token }
|
|
122
|
-
}),
|
|
123
|
+
}), pe = async (t, n) => n === void 0 ? (await e()).apps.filter((e) => e.kind === "api" && e.status === "live" && e.url.length > 0 && (t === void 0 || e.name === t)) : [fe(n)], me = (e) => {
|
|
123
124
|
let t = "pretty", n = [];
|
|
124
125
|
for (let r = 0; r < e.length; r++) {
|
|
125
126
|
let i = e[r];
|
|
@@ -143,8 +144,8 @@ var re = () => {
|
|
|
143
144
|
format: t,
|
|
144
145
|
rest: n
|
|
145
146
|
};
|
|
146
|
-
},
|
|
147
|
-
let { target: n, rest: r, error: i } =
|
|
147
|
+
}, he = (e, t = process.env) => {
|
|
148
|
+
let { target: n, rest: r, error: i } = P(e, t), a, o = [];
|
|
148
149
|
for (let e = 0; e < r.length; e++) {
|
|
149
150
|
let t = r[e];
|
|
150
151
|
if (t === "--process") {
|
|
@@ -163,112 +164,6 @@ var re = () => {
|
|
|
163
164
|
rest: o,
|
|
164
165
|
...i === void 0 ? {} : { error: i }
|
|
165
166
|
};
|
|
166
|
-
}, P = (e) => e.source === void 0 ? [] : typeof e.source == "string" ? [e.source] : e.source, fe = (e) => ({
|
|
167
|
-
tables: new Set(e.tables),
|
|
168
|
-
procedures: new Map(e.procedures.map((e) => [e.tag, e])),
|
|
169
|
-
scopes: new Set(e.scopes ?? []),
|
|
170
|
-
input: e
|
|
171
|
-
}), pe = (e) => {
|
|
172
|
-
let t = [], n = /* @__PURE__ */ new Set();
|
|
173
|
-
for (let r of e.input.procedures) {
|
|
174
|
-
for (let i of P(r)) n.add(i), e.tables.has(i) || t.push({
|
|
175
|
-
rule: "reference/dangling-source",
|
|
176
|
-
node: `${r.kind}(${r.tag})`,
|
|
177
|
-
...r.file === void 0 ? {} : { file: r.file },
|
|
178
|
-
breaks: [`reads table '${i}' which does not exist`],
|
|
179
|
-
fix: `declare a '${i}.entity.ts' table or fix the query's source`,
|
|
180
|
-
severity: "error"
|
|
181
|
-
});
|
|
182
|
-
for (let i of r.targets ?? []) n.add(i.table), e.tables.has(i.table) || t.push({
|
|
183
|
-
rule: "reference/dangling-target",
|
|
184
|
-
node: `${r.kind}(${r.tag})`,
|
|
185
|
-
...r.file === void 0 ? {} : { file: r.file },
|
|
186
|
-
breaks: [`targets table '${i.table}' (op '${i.op}') which does not exist`],
|
|
187
|
-
fix: `declare a '${i.table}.entity.ts' table or fix the mutation's target`,
|
|
188
|
-
severity: "error"
|
|
189
|
-
});
|
|
190
|
-
for (let n of r.requiresScopes ?? []) e.scopes.size > 0 && !e.scopes.has(n) && t.push({
|
|
191
|
-
rule: "rbac/unknown-scope",
|
|
192
|
-
node: `${r.kind}(${r.tag})`,
|
|
193
|
-
...r.file === void 0 ? {} : { file: r.file },
|
|
194
|
-
breaks: [`requires scope '${n}' which is not declared`],
|
|
195
|
-
fix: `declare the scope '${n}' or fix the requireScope(...) call`,
|
|
196
|
-
severity: "error"
|
|
197
|
-
});
|
|
198
|
-
r.kind === "mutation" && r.hasGuards === !1 && t.push({
|
|
199
|
-
rule: "rbac/unguarded-mutation",
|
|
200
|
-
node: `${r.kind}(${r.tag})`,
|
|
201
|
-
...r.file === void 0 ? {} : { file: r.file },
|
|
202
|
-
breaks: [`'${r.tag}' changes state but declares no guard — any caller who can reach the rpc surface can invoke it`],
|
|
203
|
-
fix: "add a guard (e.g. `.guard(requireScope('…'))`), or confirm it is intentionally public",
|
|
204
|
-
severity: "warn"
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
for (let n of e.input.routes ?? []) for (let r of n.binds) e.procedures.has(r) || t.push({
|
|
208
|
-
rule: "binding/unknown-tag",
|
|
209
|
-
node: `route(${n.pattern})`,
|
|
210
|
-
...n.file === void 0 ? {} : { file: n.file },
|
|
211
|
-
breaks: [`binds rpc tag '${r}' which does not exist`],
|
|
212
|
-
fix: `bind an existing tag, or restore '${r}' (it may have been renamed/removed)`,
|
|
213
|
-
severity: "error"
|
|
214
|
-
});
|
|
215
|
-
for (let t of e.input.procedures) for (let e of t.targets ?? []) n.add(e.table);
|
|
216
|
-
let r = new Set(e.input.frameworkOwnedTables ?? []), i = e.input.procedures.filter((e) => e.kind === "action" && P(e).length === 0 && (e.targets ?? []).length === 0).length;
|
|
217
|
-
for (let a of e.tables) r.has(a) || n.has(a) || t.push({
|
|
218
|
-
rule: "orphan/unread-table",
|
|
219
|
-
node: `table(${a})`,
|
|
220
|
-
breaks: i > 0 ? [`no query reads and no mutation writes '${a}'`, `${i} action(s) declare neither source nor target, so one of them may still use it`] : [`no query reads and no mutation writes '${a}'`],
|
|
221
|
-
fix: i > 0 ? "declare `source`/`target` on your actions so this can be answered, then re-run" : `add a query/mutation for '${a}', or remove the table if it's dead`,
|
|
222
|
-
severity: "warn"
|
|
223
|
-
});
|
|
224
|
-
if (e.scopes.size > 0) {
|
|
225
|
-
let n = /* @__PURE__ */ new Set();
|
|
226
|
-
for (let t of e.input.procedures) for (let e of t.requiresScopes ?? []) n.add(e);
|
|
227
|
-
for (let r of [...e.scopes].sort()) r === "admin:full" || n.has(r) || t.push({
|
|
228
|
-
rule: "rbac/unenforced-scope",
|
|
229
|
-
node: `scope(${r})`,
|
|
230
|
-
breaks: [`'${r}' is granted by a role but NO query/mutation/action/stream guards on it`, "a scope nobody checks grants nothing and withholds nothing — the role map reads stricter than the app is"],
|
|
231
|
-
fix: `add \`.guard(requireScope('${r}'))\` to the handlers it is meant to protect — or, if a plugin route, a REST route, or a UI-only affordance enforces it, say so where the role is declared`,
|
|
232
|
-
severity: "warn"
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
return t;
|
|
236
|
-
}, me = (e, t) => {
|
|
237
|
-
let n = [];
|
|
238
|
-
if (t.kind === "removeTable") for (let r of e.input.procedures) {
|
|
239
|
-
let e = P(r).includes(t.name), i = (r.targets ?? []).some((e) => e.table === t.name);
|
|
240
|
-
(e || i) && n.push({
|
|
241
|
-
rule: "blast/table-removed",
|
|
242
|
-
node: `${r.kind}(${r.tag})`,
|
|
243
|
-
...r.file === void 0 ? {} : { file: r.file },
|
|
244
|
-
breaks: [`${e ? "reads" : "writes"} table '${t.name}' — would break if removed`],
|
|
245
|
-
fix: `migrate or remove ${r.tag} before dropping '${t.name}'`,
|
|
246
|
-
severity: "error"
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
else if (t.kind === "removeProcedure") for (let r of e.input.routes ?? []) r.binds.includes(t.tag) && n.push({
|
|
250
|
-
rule: "blast/procedure-removed",
|
|
251
|
-
node: `route(${r.pattern})`,
|
|
252
|
-
...r.file === void 0 ? {} : { file: r.file },
|
|
253
|
-
breaks: [`binds '${t.tag}' — would break if removed`],
|
|
254
|
-
fix: `rebind ${r.pattern} to another tag before removing '${t.tag}'`,
|
|
255
|
-
severity: "error"
|
|
256
|
-
});
|
|
257
|
-
else for (let r of e.input.procedures) (r.requiresScopes ?? []).includes(t.name) && n.push({
|
|
258
|
-
rule: "blast/scope-removed",
|
|
259
|
-
node: `${r.kind}(${r.tag})`,
|
|
260
|
-
...r.file === void 0 ? {} : { file: r.file },
|
|
261
|
-
breaks: [`requires scope '${t.name}' — would become unguarded/dangling if removed`],
|
|
262
|
-
fix: `remove the requireScope('${t.name}') call or keep the scope`,
|
|
263
|
-
severity: "error"
|
|
264
|
-
});
|
|
265
|
-
return n;
|
|
266
|
-
}, he = (e, t) => {
|
|
267
|
-
let n = fe(e), r = [...pe(n), ...t === void 0 ? [] : me(n, t)];
|
|
268
|
-
return {
|
|
269
|
-
ok: !r.some((e) => e.severity === "error"),
|
|
270
|
-
diagnostics: r
|
|
271
|
-
};
|
|
272
167
|
}, ge = (e) => {
|
|
273
168
|
let t = "", n = !1;
|
|
274
169
|
for (let r = 0; r < e.length; r++) {
|
|
@@ -343,7 +238,7 @@ var re = () => {
|
|
|
343
238
|
}
|
|
344
239
|
} };
|
|
345
240
|
}, ve = (e, t = "tsconfig.json") => {
|
|
346
|
-
let n =
|
|
241
|
+
let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
|
|
347
242
|
if (i === void 0) return;
|
|
348
243
|
let a = {};
|
|
349
244
|
for (let [e, t] of Object.entries(i)) Array.isArray(t) && (a[e] = t.filter((e) => typeof e == "string"));
|
|
@@ -352,7 +247,7 @@ var re = () => {
|
|
|
352
247
|
paths: a
|
|
353
248
|
};
|
|
354
249
|
}, I = (e, t = "tsconfig.json") => {
|
|
355
|
-
let n =
|
|
250
|
+
let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
|
|
356
251
|
if (i === void 0) return [];
|
|
357
252
|
let a = C(b(n), r?.compilerOptions?.baseUrl ?? "."), o = [];
|
|
358
253
|
for (let [e, t] of Object.entries(i)) {
|
|
@@ -404,45 +299,45 @@ var re = () => {
|
|
|
404
299
|
}, xe = (e, t) => t.some((t) => e === t.dir || e.startsWith(`${t.dir}${w}`)), Se = (e, t) => t.map((t) => `${S(e, t.dir) || "."} (${t.source})`), Ce = [
|
|
405
300
|
{
|
|
406
301
|
id: "component.ui",
|
|
407
|
-
re:
|
|
302
|
+
re: _,
|
|
408
303
|
needsTest: !0
|
|
409
304
|
},
|
|
410
305
|
{
|
|
411
306
|
id: "component",
|
|
412
|
-
re:
|
|
307
|
+
re: ee,
|
|
413
308
|
needsTest: !0
|
|
414
309
|
},
|
|
415
310
|
{
|
|
416
311
|
id: "hook",
|
|
417
|
-
re:
|
|
312
|
+
re: te,
|
|
418
313
|
needsTest: !0
|
|
419
314
|
},
|
|
420
315
|
{
|
|
421
316
|
id: "tracking",
|
|
422
|
-
re:
|
|
317
|
+
re: f,
|
|
423
318
|
needsTest: !0
|
|
424
319
|
},
|
|
425
320
|
{
|
|
426
321
|
id: "store",
|
|
427
|
-
re:
|
|
322
|
+
re: d,
|
|
428
323
|
needsTest: !0
|
|
429
324
|
},
|
|
430
325
|
{
|
|
431
326
|
id: "internal",
|
|
432
|
-
re:
|
|
327
|
+
re: h,
|
|
433
328
|
needsTest: !0
|
|
434
329
|
},
|
|
435
330
|
{
|
|
436
331
|
id: "fixture",
|
|
437
|
-
re:
|
|
332
|
+
re: v,
|
|
438
333
|
needsTest: !1
|
|
439
334
|
},
|
|
440
335
|
{
|
|
441
336
|
id: "types",
|
|
442
|
-
re:
|
|
337
|
+
re: p,
|
|
443
338
|
needsTest: !1
|
|
444
339
|
}
|
|
445
|
-
], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, we = (e) =>
|
|
340
|
+
], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, we = (e) => m.test(e), Te = /* @__PURE__ */ new Set([
|
|
446
341
|
"useMutation",
|
|
447
342
|
"useAction",
|
|
448
343
|
"useUpload",
|
|
@@ -468,7 +363,7 @@ var re = () => {
|
|
|
468
363
|
".git",
|
|
469
364
|
"coverage",
|
|
470
365
|
".publish"
|
|
471
|
-
]), ke = /\.tsx?$/,
|
|
366
|
+
]), ke = /\.tsx?$/, Ae = (e, t = [], n = 0) => {
|
|
472
367
|
if (n > 12) return t;
|
|
473
368
|
let r;
|
|
474
369
|
try {
|
|
@@ -484,20 +379,20 @@ var re = () => {
|
|
|
484
379
|
} catch {
|
|
485
380
|
continue;
|
|
486
381
|
}
|
|
487
|
-
a.isDirectory() ?
|
|
382
|
+
a.isDirectory() ? Ae(r, t, n + 1) : a.isFile() && ke.test(i) && !i.endsWith(".d.ts") && t.push(r);
|
|
488
383
|
}
|
|
489
384
|
return t;
|
|
490
|
-
},
|
|
491
|
-
if (
|
|
492
|
-
if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return
|
|
385
|
+
}, je = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, Me = /\b(?:memo|forwardRef|observer|styled)\b/, Ne = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), z = (e) => {
|
|
386
|
+
if (Ne(e)) return !0;
|
|
387
|
+
if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return z(e.getExpression());
|
|
493
388
|
if (k.isVariableDeclaration(e)) {
|
|
494
389
|
let t = e.getTypeNode();
|
|
495
|
-
if (t !== void 0 &&
|
|
390
|
+
if (t !== void 0 && je.test(t.getText())) return !0;
|
|
496
391
|
let n = e.getInitializer();
|
|
497
|
-
return n === void 0 ||
|
|
392
|
+
return n === void 0 || z(n);
|
|
498
393
|
}
|
|
499
|
-
return k.isCallExpression(e) ?
|
|
500
|
-
},
|
|
394
|
+
return k.isCallExpression(e) ? Me.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => Ne(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);
|
|
395
|
+
}, Pe = (e) => {
|
|
501
396
|
let t = [], n = [], r = [], i = /* @__PURE__ */ new Set();
|
|
502
397
|
for (let [a, o] of e.getExportedDeclarations()) {
|
|
503
398
|
let e = o[0];
|
|
@@ -508,7 +403,7 @@ var re = () => {
|
|
|
508
403
|
n.push(s);
|
|
509
404
|
continue;
|
|
510
405
|
}
|
|
511
|
-
if (/^[A-Z]/.test(s) &&
|
|
406
|
+
if (/^[A-Z]/.test(s) && z(e)) {
|
|
512
407
|
t.push(s);
|
|
513
408
|
continue;
|
|
514
409
|
}
|
|
@@ -520,7 +415,7 @@ var re = () => {
|
|
|
520
415
|
others: r,
|
|
521
416
|
hasRuntimeExport: t.length + n.length + r.length > 0
|
|
522
417
|
};
|
|
523
|
-
},
|
|
418
|
+
}, B = [], Fe = (e, t, n) => {
|
|
524
419
|
let r = b(e), i = [];
|
|
525
420
|
for (let e = r;; e = b(e)) {
|
|
526
421
|
let r = n.get(e);
|
|
@@ -537,9 +432,9 @@ var re = () => {
|
|
|
537
432
|
}
|
|
538
433
|
if (e === t || b(e) === e) break;
|
|
539
434
|
}
|
|
540
|
-
for (let e of i) n.set(e,
|
|
541
|
-
return
|
|
542
|
-
},
|
|
435
|
+
for (let e of i) n.set(e, B);
|
|
436
|
+
return B;
|
|
437
|
+
}, Ie = (e, t, n) => {
|
|
543
438
|
let r = t.startsWith(".") ? C(b(e), t) : ye(t, n);
|
|
544
439
|
if (r === void 0) return null;
|
|
545
440
|
for (let e of [
|
|
@@ -552,9 +447,9 @@ var re = () => {
|
|
|
552
447
|
if (O(e).isFile()) return e;
|
|
553
448
|
} catch {}
|
|
554
449
|
return null;
|
|
555
|
-
},
|
|
450
|
+
}, Le = (e, t, n) => {
|
|
556
451
|
let r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = (e) => {
|
|
557
|
-
let i =
|
|
452
|
+
let i = Ie(t, e, n);
|
|
558
453
|
return i !== null && r.push(i), i;
|
|
559
454
|
};
|
|
560
455
|
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 +476,22 @@ var re = () => {
|
|
|
581
476
|
namedBindings: i,
|
|
582
477
|
reExports: a
|
|
583
478
|
};
|
|
584
|
-
},
|
|
479
|
+
}, Re = (e) => {
|
|
585
480
|
let t = /* @__PURE__ */ new Map();
|
|
586
481
|
for (let n of e.getDescendantsOfKind(j.CallExpression)) {
|
|
587
482
|
let e = n.getExpression(), r = k.isPropertyAccessExpression(e) ? e.getName() : e.getText();
|
|
588
483
|
t.set(r, (t.get(r) ?? 0) + 1);
|
|
589
484
|
}
|
|
590
485
|
return t;
|
|
591
|
-
},
|
|
486
|
+
}, ze = (e) => {
|
|
592
487
|
if (e.length <= 1) return e.length === 1;
|
|
593
488
|
let t = [...e].sort((e, t) => e.length - t.length)[0];
|
|
594
489
|
return e.every((e) => e.startsWith(t));
|
|
595
|
-
},
|
|
490
|
+
}, Be = (e, t) => {
|
|
596
491
|
let n = b(t);
|
|
597
492
|
return e.startsWith(`${n}${w}`) || b(e) === n;
|
|
598
|
-
},
|
|
599
|
-
let t =
|
|
493
|
+
}, V = (e) => {
|
|
494
|
+
let t = Ae(e);
|
|
600
495
|
if (t.length === 0) return {
|
|
601
496
|
findings: [],
|
|
602
497
|
declared: 0,
|
|
@@ -627,14 +522,14 @@ var re = () => {
|
|
|
627
522
|
} catch {
|
|
628
523
|
continue;
|
|
629
524
|
}
|
|
630
|
-
s.set(t,
|
|
525
|
+
s.set(t, Pe(r)), c.set(t, Le(r, t, Fe(t, e, u))), l.set(t, Re(r));
|
|
631
526
|
}
|
|
632
527
|
let d = /* @__PURE__ */ new Map();
|
|
633
528
|
for (let [e, t] of c) for (let n of t.localTargets) {
|
|
634
529
|
let t = d.get(n) ?? [];
|
|
635
530
|
t.push(e), d.set(n, t);
|
|
636
531
|
}
|
|
637
|
-
let
|
|
532
|
+
let f = (e) => {
|
|
638
533
|
let t = s.get(e), n = /* @__PURE__ */ new Set([
|
|
639
534
|
...t?.components ?? [],
|
|
640
535
|
...t?.hooks ?? [],
|
|
@@ -655,15 +550,15 @@ var re = () => {
|
|
|
655
550
|
}
|
|
656
551
|
};
|
|
657
552
|
return a(e, null), [...r];
|
|
658
|
-
},
|
|
553
|
+
}, p = 0;
|
|
659
554
|
for (let e of r) {
|
|
660
555
|
let t = R(e), n = s.get(e), r = c.get(e);
|
|
661
556
|
if (n === void 0 || r === void 0) continue;
|
|
662
557
|
let i = we(e);
|
|
663
|
-
t !== null && !i && (
|
|
558
|
+
t !== null && !i && (p += 1);
|
|
664
559
|
let u = d.get(e) ?? [];
|
|
665
560
|
if ((t === "component" || t === "component.ui") && !i) {
|
|
666
|
-
let t =
|
|
561
|
+
let t = ze(n.components);
|
|
667
562
|
(n.components.length === 0 || !t) && a.push({
|
|
668
563
|
rule: "component/one-per-file",
|
|
669
564
|
file: o(e),
|
|
@@ -701,7 +596,7 @@ var re = () => {
|
|
|
701
596
|
].join(", ")}`,
|
|
702
597
|
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
598
|
}), t === "internal") {
|
|
704
|
-
let t = u.filter((t) => !
|
|
599
|
+
let t = u.filter((t) => !Be(t, e));
|
|
705
600
|
t.length > 0 && a.push({
|
|
706
601
|
rule: "internal/foreign-import",
|
|
707
602
|
file: o(e),
|
|
@@ -729,9 +624,9 @@ var re = () => {
|
|
|
729
624
|
detail: `imports write hook(s): ${t.join(", ")}`,
|
|
730
625
|
fix: "a *.component.ui.tsx renders; it does not write. Lift the mutation into the *.component.tsx that owns it and pass a handler down. Reading (useT / useCan / usePermissions) stays allowed — threading those through props is prop-drilling, not reusability"
|
|
731
626
|
});
|
|
732
|
-
let n =
|
|
627
|
+
let n = f(e), i = n.filter((e) => {
|
|
733
628
|
let t = R(e);
|
|
734
|
-
return t === "component" || t === "component.ui" ||
|
|
629
|
+
return t === "component" || t === "component.ui" || y.test(e) || g.has(e.slice(e.lastIndexOf(w) + 1));
|
|
735
630
|
});
|
|
736
631
|
n.length === 0 ? a.push({
|
|
737
632
|
rule: "ui/orphaned",
|
|
@@ -794,12 +689,12 @@ var re = () => {
|
|
|
794
689
|
}
|
|
795
690
|
return a.sort((e, t) => e.file === t.file ? e.rule.localeCompare(t.rule) : e.file.localeCompare(t.file)), {
|
|
796
691
|
findings: a,
|
|
797
|
-
declared:
|
|
692
|
+
declared: p,
|
|
798
693
|
scanned: r.length,
|
|
799
694
|
vendored: n
|
|
800
695
|
};
|
|
801
|
-
},
|
|
802
|
-
let { findings: t, declared: n, scanned: r, vendored: i } =
|
|
696
|
+
}, Ve = 10, He = (e) => {
|
|
697
|
+
let { findings: t, declared: n, scanned: r, vendored: i } = V(e);
|
|
803
698
|
console.log("");
|
|
804
699
|
for (let t of Se(e, i)) console.log(`· not ours, exempt: ${t}`);
|
|
805
700
|
if (r === 0) return console.log("• file-taxonomy scan: no sources here — nothing to check."), 0;
|
|
@@ -813,12 +708,12 @@ var re = () => {
|
|
|
813
708
|
console.log(`• file taxonomy: ${t.length} finding(s) — ${o} error, ${t.length - o} advisory (${n} declared / ${r} scanned):`);
|
|
814
709
|
for (let [e, t] of [...a].sort()) {
|
|
815
710
|
console.log(` ${t[0].severity === "error" ? "✗" : "·"} ${e} (${t.length})`);
|
|
816
|
-
for (let e of t.slice(0,
|
|
817
|
-
let n = t.length -
|
|
711
|
+
for (let e of t.slice(0, Ve)) console.log(` ${e.file} — ${e.detail}`);
|
|
712
|
+
let n = t.length - Ve;
|
|
818
713
|
n > 0 && console.log(` (+${n} more — \`voltro doctor --json\` prints every one.)`), console.log(` → ${t[0].fix}`);
|
|
819
714
|
}
|
|
820
715
|
return t.length;
|
|
821
|
-
},
|
|
716
|
+
}, Ue = /* @__PURE__ */ new Set([
|
|
822
717
|
"eq",
|
|
823
718
|
"ne",
|
|
824
719
|
"gt",
|
|
@@ -836,10 +731,10 @@ var re = () => {
|
|
|
836
731
|
"endsWith",
|
|
837
732
|
"between",
|
|
838
733
|
"jsonField"
|
|
839
|
-
]),
|
|
734
|
+
]), We = (e) => {
|
|
840
735
|
if (k.isPropertyAccessExpression(e)) return e.getName();
|
|
841
736
|
if (k.isIdentifier(e)) return e.getText();
|
|
842
|
-
},
|
|
737
|
+
}, Ge = (e) => {
|
|
843
738
|
let t = e;
|
|
844
739
|
for (; k.isCallExpression(t);) {
|
|
845
740
|
let e = t.getExpression();
|
|
@@ -847,7 +742,7 @@ var re = () => {
|
|
|
847
742
|
t = e.getExpression();
|
|
848
743
|
}
|
|
849
744
|
return t;
|
|
850
|
-
},
|
|
745
|
+
}, Ke = (e, t, n) => {
|
|
851
746
|
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
747
|
useInMemoryFileSystem: !0,
|
|
853
748
|
skipAddingFilesFromTsConfig: !0
|
|
@@ -865,7 +760,7 @@ var re = () => {
|
|
|
865
760
|
for (let t of e.getDescendantsOfKind(j.CallExpression)) {
|
|
866
761
|
let e = t.getExpression();
|
|
867
762
|
if (!k.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
|
|
868
|
-
let n =
|
|
763
|
+
let n = We(Ge(e.getExpression()));
|
|
869
764
|
if (n === void 0 || !r.has(n)) {
|
|
870
765
|
let t = e.getExpression();
|
|
871
766
|
if (k.isCallExpression(t)) {
|
|
@@ -888,7 +783,7 @@ var re = () => {
|
|
|
888
783
|
else for (let e of [u, ...u.getDescendantsOfKind(j.CallExpression)]) {
|
|
889
784
|
if (!k.isCallExpression(e)) continue;
|
|
890
785
|
let t = e.getExpression(), n = k.isIdentifier(t) ? t.getText() : k.isPropertyAccessExpression(t) ? t.getName() : void 0;
|
|
891
|
-
if (n === void 0 || !
|
|
786
|
+
if (n === void 0 || !Ue.has(n)) continue;
|
|
892
787
|
let r = e.getArguments()[0];
|
|
893
788
|
r !== void 0 && k.isStringLiteral(r) && l.push({
|
|
894
789
|
column: r.getLiteralValue(),
|
|
@@ -909,16 +804,16 @@ var re = () => {
|
|
|
909
804
|
issues: a,
|
|
910
805
|
checked: o
|
|
911
806
|
};
|
|
912
|
-
},
|
|
807
|
+
}, H = 12, qe = (e) => {
|
|
913
808
|
if (e.checked === 0) return null;
|
|
914
809
|
if (e.issues.length === 0) return `predicate columns: ${e.checked} checked — every filtered column exists on its table`;
|
|
915
810
|
let t = [`predicate columns: ${e.issues.length} filter${e.issues.length, "s"} on a column that does not exist (${e.checked} checked)`];
|
|
916
811
|
for (let n of e.issues) {
|
|
917
|
-
let e = n.available.slice(0,
|
|
812
|
+
let e = n.available.slice(0, H).join(", "), r = n.available.length > H ? `, … (${n.available.length - H} more)` : "";
|
|
918
813
|
t.push(` ${n.file}:${n.line} '${n.table}' has no column '${n.column}'`), t.push(` columns: ${e}${r}`);
|
|
919
814
|
}
|
|
920
815
|
return t.push(" This type-checks today and fails at runtime as a bare SQL error."), t.join("\n");
|
|
921
|
-
},
|
|
816
|
+
}, Je = /* @__PURE__ */ new Set([
|
|
922
817
|
"id",
|
|
923
818
|
"tenantId",
|
|
924
819
|
"tenant_id",
|
|
@@ -938,7 +833,7 @@ var re = () => {
|
|
|
938
833
|
"idempotency_id",
|
|
939
834
|
"idempotencyKey",
|
|
940
835
|
"idempotency_key"
|
|
941
|
-
]),
|
|
836
|
+
]), Ye = /* @__PURE__ */ new Set([
|
|
942
837
|
"id",
|
|
943
838
|
"createdAt",
|
|
944
839
|
"created_at",
|
|
@@ -948,10 +843,10 @@ var re = () => {
|
|
|
948
843
|
"deleted_at",
|
|
949
844
|
"tenantId",
|
|
950
845
|
"tenant_id"
|
|
951
|
-
]),
|
|
846
|
+
]), Xe = (e) => Je.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Ze = (e) => e.type === "reference", Qe = (e) => {
|
|
952
847
|
let t = [];
|
|
953
848
|
for (let n of e) {
|
|
954
|
-
let e = n.columns.filter((e) =>
|
|
849
|
+
let e = n.columns.filter((e) => Xe(e.name)), r = e.filter((e) => !Ze(e)).map((e) => e.name);
|
|
955
850
|
if (r.length === 0) continue;
|
|
956
851
|
let i = new Set(n.primaryKey), a = r.filter((e) => i.has(e));
|
|
957
852
|
if (n.primaryKey.length >= 2 && a.length > 0) {
|
|
@@ -962,7 +857,7 @@ var re = () => {
|
|
|
962
857
|
});
|
|
963
858
|
continue;
|
|
964
859
|
}
|
|
965
|
-
if (e.some(
|
|
860
|
+
if (e.some(Ze) && e.length >= 2) {
|
|
966
861
|
t.push({
|
|
967
862
|
table: n.name,
|
|
968
863
|
columns: r,
|
|
@@ -970,7 +865,7 @@ var re = () => {
|
|
|
970
865
|
});
|
|
971
866
|
continue;
|
|
972
867
|
}
|
|
973
|
-
e.length >= 2 && n.columns.filter((e) => !
|
|
868
|
+
e.length >= 2 && n.columns.filter((e) => !Xe(e.name) && !Ye.has(e.name)).length === 0 && t.push({
|
|
974
869
|
table: n.name,
|
|
975
870
|
columns: r,
|
|
976
871
|
signal: "link-only"
|
|
@@ -980,20 +875,20 @@ var re = () => {
|
|
|
980
875
|
issues: t,
|
|
981
876
|
tablesChecked: e.length
|
|
982
877
|
};
|
|
983
|
-
},
|
|
878
|
+
}, $e = {
|
|
984
879
|
"composite-pk": "part of a composite primary key",
|
|
985
880
|
"reference-sibling": "sits beside a wired reference() on this table",
|
|
986
881
|
"link-only": "this table is nothing but link columns"
|
|
987
|
-
},
|
|
882
|
+
}, et = (e) => {
|
|
988
883
|
if (e.tablesChecked === 0) return null;
|
|
989
884
|
if (e.issues.length === 0) return `junction FKs: ${e.tablesChecked} table${e.tablesChecked === 1 ? "" : "s"} checked — every link column is a wired reference()`;
|
|
990
885
|
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
886
|
for (let t of e.issues) {
|
|
992
887
|
let e = t.columns.map((e) => `'${e}'`).join(", ");
|
|
993
|
-
n.push(` '${t.table}': ${e} (${
|
|
888
|
+
n.push(` '${t.table}': ${e} (${$e[t.signal]})`);
|
|
994
889
|
}
|
|
995
890
|
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
|
-
},
|
|
891
|
+
}, 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
892
|
let t = [], n = [];
|
|
998
893
|
for (let r of e) t.push({
|
|
999
894
|
event: r.name,
|
|
@@ -1002,7 +897,7 @@ var re = () => {
|
|
|
1002
897
|
event: r.name,
|
|
1003
898
|
kind: "wide-key",
|
|
1004
899
|
keyFieldCount: r.keyFieldCount
|
|
1005
|
-
}), r.hasWebhook &&
|
|
900
|
+
}), r.hasWebhook && rt(r.name) && n.push({
|
|
1006
901
|
event: r.name,
|
|
1007
902
|
kind: "webhook-high-rate"
|
|
1008
903
|
});
|
|
@@ -1011,26 +906,26 @@ var re = () => {
|
|
|
1011
906
|
modes: t,
|
|
1012
907
|
warnings: n
|
|
1013
908
|
};
|
|
1014
|
-
},
|
|
909
|
+
}, at = (e) => {
|
|
1015
910
|
if (e.eventsChecked === 0) return null;
|
|
1016
911
|
let t = e.eventsChecked, n = [`event delivery: ${t} declared event${t === 1 ? "" : "s"} — the mode decides what a MISSING message means`];
|
|
1017
912
|
for (let t of e.modes) n.push(` '${t.event}': ${t.delivery}`);
|
|
1018
913
|
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
|
-
},
|
|
914
|
+
}, ot = (e) => {
|
|
1020
915
|
if (e.warnings.length === 0) return null;
|
|
1021
916
|
let t = e.warnings.length, n = [`events that may not scale as declared: ${t} finding${t === 1 ? "" : "s"}`];
|
|
1022
917
|
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
918
|
return n.push(" WARNING, not a fault — a producer may intend the fan-out. `voltro doctor --json` prints every finding."), n.join("\n");
|
|
1024
|
-
},
|
|
919
|
+
}, st = /* @__PURE__ */ new Set([
|
|
1025
920
|
"start",
|
|
1026
921
|
"run",
|
|
1027
922
|
"child"
|
|
1028
|
-
]),
|
|
923
|
+
]), ct = (e) => k.isCallExpression(e) && (() => {
|
|
1029
924
|
let t = e.getExpression();
|
|
1030
|
-
if (!k.isPropertyAccessExpression(t) || !
|
|
925
|
+
if (!k.isPropertyAccessExpression(t) || !st.has(t.getName())) return !1;
|
|
1031
926
|
let n = t.getExpression();
|
|
1032
927
|
return k.isPropertyAccessExpression(n) ? n.getName() === "workflows" : k.isIdentifier(n) && n.getText() === "workflows";
|
|
1033
|
-
})(),
|
|
928
|
+
})(), lt = (e, t, n) => {
|
|
1034
929
|
let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new A({
|
|
1035
930
|
useInMemoryFileSystem: !0,
|
|
1036
931
|
skipAddingFilesFromTsConfig: !0
|
|
@@ -1046,7 +941,7 @@ var re = () => {
|
|
|
1046
941
|
}
|
|
1047
942
|
let u = S(n, t);
|
|
1048
943
|
for (let t of e.getDescendantsOfKind(j.CallExpression)) {
|
|
1049
|
-
if (!
|
|
944
|
+
if (!ct(t)) continue;
|
|
1050
945
|
c++;
|
|
1051
946
|
let e = t.getStartLineNumber(), [n, l] = t.getArguments();
|
|
1052
947
|
if (n === void 0 || !k.isStringLiteral(n)) {
|
|
@@ -1130,7 +1025,7 @@ var re = () => {
|
|
|
1130
1025
|
checked: s,
|
|
1131
1026
|
callSites: c
|
|
1132
1027
|
};
|
|
1133
|
-
},
|
|
1028
|
+
}, ut = (e) => {
|
|
1134
1029
|
if (e.callSites === 0) return null;
|
|
1135
1030
|
let t = [];
|
|
1136
1031
|
t.push(`workflow starts: ${e.checked}/${e.callSites} call sites checked`);
|
|
@@ -1143,7 +1038,7 @@ var re = () => {
|
|
|
1143
1038
|
for (let n of e.unchecked) t.push(` ${n.file}:${n.line} — ${n.reason}`);
|
|
1144
1039
|
}
|
|
1145
1040
|
return e.issues.length === 0 && e.unchecked.length === 0 && t.push(" every call site matches its workflow"), t.join("\n");
|
|
1146
|
-
},
|
|
1041
|
+
}, dt = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, U = "VOLTRO_FIELD_ENCRYPTION_KEY", ft = (e) => {
|
|
1147
1042
|
let t;
|
|
1148
1043
|
for (let [n, r] of e.sources) if (t === void 0 && /\bfieldEncryption\b/.test(r) && (t = {
|
|
1149
1044
|
reason: "fieldEncryption",
|
|
@@ -1151,13 +1046,13 @@ var re = () => {
|
|
|
1151
1046
|
}), t === void 0 && /\.encrypted\s*\(/.test(r) && (t = {
|
|
1152
1047
|
reason: "encrypted-column",
|
|
1153
1048
|
file: n
|
|
1154
|
-
}), r.includes(
|
|
1049
|
+
}), r.includes(U)) return;
|
|
1155
1050
|
if (t !== void 0) {
|
|
1156
|
-
for (let [, t] of e.envFiles) if (t.includes(
|
|
1051
|
+
for (let [, t] of e.envFiles) if (t.includes(U)) return;
|
|
1157
1052
|
return t;
|
|
1158
1053
|
}
|
|
1159
|
-
},
|
|
1160
|
-
`✗ ${
|
|
1054
|
+
}, pt = (e) => [
|
|
1055
|
+
`✗ ${U} is named nowhere under this app root, and the app encrypts columns at rest`,
|
|
1161
1056
|
e.reason === "fieldEncryption" ? ` (\`fieldEncryption\` in ${e.file})` : ` (an \`.encrypted()\` column in ${e.file})`,
|
|
1162
1057
|
"",
|
|
1163
1058
|
" A deploy without it does not degrade — it refuses to boot at `onActivate`,",
|
|
@@ -1173,99 +1068,14 @@ var re = () => {
|
|
|
1173
1068
|
" reaches your deploy target is not knowable from here — a secret manager",
|
|
1174
1069
|
" or a CI variable is not a file. Naming it nowhere is conclusive; naming",
|
|
1175
1070
|
" it somewhere is not a guarantee."
|
|
1176
|
-
].join("\n"),
|
|
1177
|
-
let t = [], n = e;
|
|
1178
|
-
for (let e = 0; e < 12; e++) {
|
|
1179
|
-
let e = x(n, "node_modules");
|
|
1180
|
-
T(e) && t.push(e);
|
|
1181
|
-
let r = b(n);
|
|
1182
|
-
if (r === n) break;
|
|
1183
|
-
n = r;
|
|
1184
|
-
}
|
|
1185
|
-
return t;
|
|
1186
|
-
}, gt = (e) => {
|
|
1187
|
-
try {
|
|
1188
|
-
let t = JSON.parse(E(x(e, "package.json"), "utf8"));
|
|
1189
|
-
return typeof t.version == "string" ? t.version : void 0;
|
|
1190
|
-
} catch {
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
}, _t = (e) => {
|
|
1194
|
-
let t = [], n;
|
|
1195
|
-
try {
|
|
1196
|
-
n = D(e);
|
|
1197
|
-
} catch {
|
|
1198
|
-
return t;
|
|
1199
|
-
}
|
|
1200
|
-
for (let r of n) {
|
|
1201
|
-
if (r.startsWith(".") || mt.test(r)) continue;
|
|
1202
|
-
let n = x(e, r);
|
|
1203
|
-
if (r.startsWith("@")) {
|
|
1204
|
-
let e;
|
|
1205
|
-
try {
|
|
1206
|
-
e = D(n);
|
|
1207
|
-
} catch {
|
|
1208
|
-
continue;
|
|
1209
|
-
}
|
|
1210
|
-
for (let i of e) t.push({
|
|
1211
|
-
pkg: `${r}/${i}`,
|
|
1212
|
-
dir: x(n, i)
|
|
1213
|
-
});
|
|
1214
|
-
continue;
|
|
1215
|
-
}
|
|
1216
|
-
try {
|
|
1217
|
-
if (!O(n).isDirectory()) continue;
|
|
1218
|
-
} catch {
|
|
1219
|
-
continue;
|
|
1220
|
-
}
|
|
1221
|
-
t.push({
|
|
1222
|
-
pkg: r,
|
|
1223
|
-
dir: n
|
|
1224
|
-
});
|
|
1225
|
-
}
|
|
1226
|
-
return t;
|
|
1227
|
-
}, vt = (e) => {
|
|
1228
|
-
let t = /* @__PURE__ */ new Map();
|
|
1229
|
-
for (let n of ht(e)) for (let { pkg: e, dir: r } of _t(n)) {
|
|
1230
|
-
if (!pt(e)) continue;
|
|
1231
|
-
let n = gt(r);
|
|
1232
|
-
if (n === void 0) continue;
|
|
1233
|
-
let i = t.get(e) ?? /* @__PURE__ */ new Map();
|
|
1234
|
-
i.has(n) || i.set(n, r), t.set(e, i);
|
|
1235
|
-
}
|
|
1236
|
-
let n = [];
|
|
1237
|
-
for (let [r, i] of t) i.size < 2 || n.push({
|
|
1238
|
-
pkg: r,
|
|
1239
|
-
versions: [...i.keys()].sort(),
|
|
1240
|
-
paths: [...i.values()].map((t) => S(e, t)).sort()
|
|
1241
|
-
});
|
|
1242
|
-
return n.sort((e, t) => e.pkg.localeCompare(t.pkg));
|
|
1243
|
-
}, yt = (e) => {
|
|
1244
|
-
if (e.length !== 0) return [
|
|
1245
|
-
`• ${e.length} package(s) resolved at more than one version:`,
|
|
1246
|
-
...e.flatMap((e) => [` ${e.pkg} — ${e.versions.join(", ")}`, ...e.paths.map((e) => ` ${e}`)]),
|
|
1247
|
-
"",
|
|
1248
|
-
" Two copies of `effect` (or `@effect/*`) is the usual cause of type errors",
|
|
1249
|
-
" inside the GENERATED rpcGroup.generated.ts that look like framework bugs:",
|
|
1250
|
-
" \"Property '[TypeId]' is missing\", \"typeof Never is not assignable to All\",",
|
|
1251
|
-
" or an `Rpc<…, Stream<…>, …>` that will not go where `Any` is expected.",
|
|
1252
|
-
" Effect types are nominal, so a Schema built by one copy is not the same",
|
|
1253
|
-
" type as a Schema expected by the other — and the RUNTIME often still works,",
|
|
1254
|
-
" which is why this shows up as a red tsc against a green server.",
|
|
1255
|
-
"",
|
|
1256
|
-
" Fix it in the install, not the code: align the version across the workspace",
|
|
1257
|
-
" (a root `pnpm.overrides` / `resolutions` entry for `effect` is the blunt",
|
|
1258
|
-
" instrument), then reinstall. Do not silence the generated file — it is where",
|
|
1259
|
-
" a genuine mistake in your own descriptors surfaces."
|
|
1260
|
-
].join("\n");
|
|
1261
|
-
}, bt = (e) => {
|
|
1071
|
+
].join("\n"), mt = (e) => {
|
|
1262
1072
|
if (e.catalogCodes.length === 0) return;
|
|
1263
1073
|
let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
|
|
1264
1074
|
if (n.length !== 0) return {
|
|
1265
1075
|
orphaned: n,
|
|
1266
1076
|
noneDeclared: t.size === 0
|
|
1267
1077
|
};
|
|
1268
|
-
},
|
|
1078
|
+
}, ht = (e, t) => {
|
|
1269
1079
|
let n = e.orphaned.join(", ");
|
|
1270
1080
|
return e.noneDeclared ? [
|
|
1271
1081
|
`⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
|
|
@@ -1277,49 +1087,49 @@ var re = () => {
|
|
|
1277
1087
|
" …or delete the files. A catalog that is never loaded looks from the inside",
|
|
1278
1088
|
" exactly like one that works, which is why this is worth a line."
|
|
1279
1089
|
] : [`⚠ ${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
|
-
},
|
|
1090
|
+
}, gt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), _t = /* @__PURE__ */ new Set([
|
|
1281
1091
|
"requireScope",
|
|
1282
1092
|
"requireAnyScope",
|
|
1283
1093
|
"requireAuth",
|
|
1284
1094
|
"assertScope",
|
|
1285
1095
|
"assertCan",
|
|
1286
1096
|
"can"
|
|
1287
|
-
]),
|
|
1097
|
+
]), vt = (e) => {
|
|
1288
1098
|
let t = /* @__PURE__ */ new Set();
|
|
1289
|
-
for (let n of e) for (let e of
|
|
1099
|
+
for (let n of e) for (let e of gt(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
|
|
1290
1100
|
return t;
|
|
1291
|
-
},
|
|
1101
|
+
}, yt = (e, t) => {
|
|
1292
1102
|
for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
|
|
1293
|
-
},
|
|
1103
|
+
}, bt = (e) => {
|
|
1294
1104
|
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);
|
|
1295
1105
|
return t && n;
|
|
1296
|
-
},
|
|
1106
|
+
}, xt = (e, t, n) => {
|
|
1297
1107
|
if (e.hasDeclaredGuards) return { kind: "declared-guard" };
|
|
1298
|
-
let r =
|
|
1299
|
-
return i === void 0 ?
|
|
1108
|
+
let r = gt(e.src), i = yt(r, t);
|
|
1109
|
+
return i === void 0 ? bt(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
|
|
1300
1110
|
kind: "vocabulary-guard",
|
|
1301
1111
|
name: i
|
|
1302
1112
|
};
|
|
1303
|
-
},
|
|
1113
|
+
}, St = (e, t, n) => {
|
|
1304
1114
|
let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
|
|
1305
1115
|
return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
|
|
1306
|
-
},
|
|
1116
|
+
}, Ct = (e, t, n) => {
|
|
1307
1117
|
switch (t.kind) {
|
|
1308
1118
|
case "declared-guard": return "guards: on the descriptor";
|
|
1309
1119
|
case "vocabulary-guard": return `calls ${t.name}()`;
|
|
1310
1120
|
case "inline-ownership": return "compares the caller to the row and refuses — correct, but only this file knows it";
|
|
1311
1121
|
case "allowlisted": return "accepted by a previous run";
|
|
1312
|
-
case "unchecked": return e.op === "read" ? "returns a row the client selected, with nothing compared to the caller" : `${e.op}s${e.table ? ` ${e.table}` : ""} with nothing constraining WHICH row`;
|
|
1122
|
+
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`;
|
|
1313
1123
|
}
|
|
1314
|
-
},
|
|
1124
|
+
}, wt = (e) => {
|
|
1315
1125
|
let t = e.executors.map((t) => {
|
|
1316
|
-
let n =
|
|
1126
|
+
let n = xt(t, e.guardVocabulary, e.allowlist);
|
|
1317
1127
|
return {
|
|
1318
1128
|
tag: t.tag,
|
|
1319
1129
|
path: t.path,
|
|
1320
1130
|
verdict: n,
|
|
1321
|
-
severity:
|
|
1322
|
-
why:
|
|
1131
|
+
severity: St(t, e.tenantScopedTables, e.referencedTables),
|
|
1132
|
+
why: Ct(t, n, e.rowFilterRegistered === !0)
|
|
1323
1133
|
};
|
|
1324
1134
|
}), n = (e) => t.filter((t) => t.verdict.kind === e).length;
|
|
1325
1135
|
return {
|
|
@@ -1333,7 +1143,7 @@ var re = () => {
|
|
|
1333
1143
|
},
|
|
1334
1144
|
unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
|
|
1335
1145
|
};
|
|
1336
|
-
},
|
|
1146
|
+
}, Tt = [
|
|
1337
1147
|
"# voltro doctor — authz allowlist",
|
|
1338
1148
|
"#",
|
|
1339
1149
|
"# Two KINDS of line, and the difference is the point:",
|
|
@@ -1358,21 +1168,46 @@ var re = () => {
|
|
|
1358
1168
|
"# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
|
|
1359
1169
|
"# lines for executors that are newly unchecked.",
|
|
1360
1170
|
""
|
|
1361
|
-
],
|
|
1171
|
+
], Et = (e) => {
|
|
1362
1172
|
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
1173
|
return [
|
|
1364
|
-
...
|
|
1174
|
+
...Tt,
|
|
1365
1175
|
...n,
|
|
1366
1176
|
...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
|
|
1367
1177
|
...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
|
|
1368
1178
|
""
|
|
1369
1179
|
].join("\n");
|
|
1370
|
-
},
|
|
1180
|
+
}, Dt = (e, t) => {
|
|
1181
|
+
let n = [...new Set(t)].sort();
|
|
1182
|
+
if (e === void 0) return Et(n.map((e) => ({ tag: e })));
|
|
1183
|
+
let r = W(e).tags, i = n.filter((e) => !r.has(e));
|
|
1184
|
+
return i.length === 0 ? e : [
|
|
1185
|
+
e.replace(/\n+$/, ""),
|
|
1186
|
+
"",
|
|
1187
|
+
"# added by `voltro doctor --write-authz-allowlist` — newly unchecked executors.",
|
|
1188
|
+
"# Nothing above this line was touched.",
|
|
1189
|
+
...i,
|
|
1190
|
+
""
|
|
1191
|
+
].join("\n");
|
|
1192
|
+
}, Ot = (e, t) => {
|
|
1193
|
+
let n = [...new Set(t)];
|
|
1194
|
+
if (e === void 0) return {
|
|
1195
|
+
added: n.length,
|
|
1196
|
+
kept: 0,
|
|
1197
|
+
removed: 0
|
|
1198
|
+
};
|
|
1199
|
+
let r = W(e).tags;
|
|
1200
|
+
return {
|
|
1201
|
+
added: n.filter((e) => !r.has(e)).length,
|
|
1202
|
+
kept: r.size,
|
|
1203
|
+
removed: 0
|
|
1204
|
+
};
|
|
1205
|
+
}, kt = class extends Error {
|
|
1371
1206
|
tag;
|
|
1372
1207
|
constructor(e) {
|
|
1373
1208
|
super(`authz allowlist: '${e}' is marked reviewed with no reason. Write \`${e} reviewed=<why it is genuinely open>\`. A bare tag is the other, honest option — it records the executor as DEBT, which is what an unexplained exemption is.`), this.tag = e, this.name = "AllowlistReasonMissingError";
|
|
1374
1209
|
}
|
|
1375
|
-
},
|
|
1210
|
+
}, W = (e) => {
|
|
1376
1211
|
let t = [];
|
|
1377
1212
|
for (let n of e.split("\n")) {
|
|
1378
1213
|
let e = n.trim();
|
|
@@ -1385,7 +1220,7 @@ var re = () => {
|
|
|
1385
1220
|
continue;
|
|
1386
1221
|
}
|
|
1387
1222
|
let a = r[2].trim();
|
|
1388
|
-
if (a.length === 0) throw new
|
|
1223
|
+
if (a.length === 0) throw new kt(i);
|
|
1389
1224
|
t.push({
|
|
1390
1225
|
tag: i,
|
|
1391
1226
|
reviewed: a
|
|
@@ -1395,13 +1230,13 @@ var re = () => {
|
|
|
1395
1230
|
tags: new Set(t.map((e) => e.tag)),
|
|
1396
1231
|
entries: t
|
|
1397
1232
|
};
|
|
1398
|
-
},
|
|
1233
|
+
}, At = (e) => {
|
|
1399
1234
|
let t = e.indexOf(".");
|
|
1400
1235
|
return t <= 0 ? void 0 : e.slice(0, t);
|
|
1401
|
-
},
|
|
1236
|
+
}, jt = (e, t) => {
|
|
1402
1237
|
let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
|
|
1403
1238
|
for (let e of t.rpcTags) {
|
|
1404
|
-
let t =
|
|
1239
|
+
let t = At(e);
|
|
1405
1240
|
if (t === void 0) continue;
|
|
1406
1241
|
let n = i.get(t);
|
|
1407
1242
|
n ? n.push(e) : i.set(t, [e]);
|
|
@@ -1430,7 +1265,7 @@ var re = () => {
|
|
|
1430
1265
|
});
|
|
1431
1266
|
continue;
|
|
1432
1267
|
}
|
|
1433
|
-
let s = new Set(o.rpcTags.map(
|
|
1268
|
+
let s = new Set(o.rpcTags.map(At).filter((e) => e !== void 0));
|
|
1434
1269
|
for (let e of s) {
|
|
1435
1270
|
let t = i.get(e);
|
|
1436
1271
|
t !== void 0 && a.push({
|
|
@@ -1443,13 +1278,13 @@ var re = () => {
|
|
|
1443
1278
|
}
|
|
1444
1279
|
}
|
|
1445
1280
|
return a;
|
|
1446
|
-
},
|
|
1281
|
+
}, Mt = (e, t) => {
|
|
1447
1282
|
let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
|
|
1448
1283
|
if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
|
|
1449
1284
|
for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
|
|
1450
1285
|
return r;
|
|
1451
|
-
},
|
|
1452
|
-
let t =
|
|
1286
|
+
}, Nt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Pt = (e) => {
|
|
1287
|
+
let t = re(e), n = ne(e);
|
|
1453
1288
|
return {
|
|
1454
1289
|
root: e,
|
|
1455
1290
|
serveBundle: t,
|
|
@@ -1457,19 +1292,19 @@ var re = () => {
|
|
|
1457
1292
|
apiBundle: n,
|
|
1458
1293
|
apiBundlePresent: T(n)
|
|
1459
1294
|
};
|
|
1460
|
-
},
|
|
1295
|
+
}, Ft = [
|
|
1461
1296
|
"Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
|
|
1462
1297
|
"Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
|
|
1463
1298
|
"(Generated Dockerfiles from `voltro create-project` already do this.)"
|
|
1464
|
-
].join("\n "),
|
|
1465
|
-
let n =
|
|
1466
|
-
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !
|
|
1299
|
+
].join("\n "), It = (e, t = !1) => {
|
|
1300
|
+
let n = Pt(e);
|
|
1301
|
+
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Nt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
|
|
1467
1302
|
console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
|
|
1468
1303
|
let r = process.env.NODE_ENV === "production";
|
|
1469
1304
|
if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
|
|
1470
1305
|
let i = t || r;
|
|
1471
|
-
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${
|
|
1472
|
-
},
|
|
1306
|
+
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Ft}`), +!!i;
|
|
1307
|
+
}, Lt = (e, t, n) => {
|
|
1473
1308
|
let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
|
|
1474
1309
|
`${e}.ts`,
|
|
1475
1310
|
`${e}.tsx`,
|
|
@@ -1495,7 +1330,7 @@ var re = () => {
|
|
|
1495
1330
|
r !== void 0 && o.push(r);
|
|
1496
1331
|
}
|
|
1497
1332
|
return o;
|
|
1498
|
-
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) =>
|
|
1333
|
+
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Ut.test(S(n, e))), l = new Set(c);
|
|
1499
1334
|
for (; c.length > 0;) {
|
|
1500
1335
|
let e = c.shift();
|
|
1501
1336
|
if (!s.has(e)) {
|
|
@@ -1508,11 +1343,11 @@ var re = () => {
|
|
|
1508
1343
|
let d = /* @__PURE__ */ new Set();
|
|
1509
1344
|
for (let e of s) l.has(e) || u.has(e) || d.add(e);
|
|
1510
1345
|
return d;
|
|
1511
|
-
},
|
|
1346
|
+
}, Rt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), zt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Bt = (e, t) => {
|
|
1512
1347
|
if (e.src.includes(t)) return !0;
|
|
1513
1348
|
let n = t.replace(/^@voltro\/plugin-/, "");
|
|
1514
1349
|
return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
|
|
1515
|
-
},
|
|
1350
|
+
}, Vt = (e, t) => {
|
|
1516
1351
|
let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
|
|
1517
1352
|
for (; (i = r.exec(e)) !== null;) {
|
|
1518
1353
|
let t = i.index + i[0].length, r = 1, a = t;
|
|
@@ -1520,10 +1355,10 @@ var re = () => {
|
|
|
1520
1355
|
n.push(e.slice(t, a - 1));
|
|
1521
1356
|
}
|
|
1522
1357
|
return n;
|
|
1523
|
-
},
|
|
1358
|
+
}, Ht = (e, t) => {
|
|
1524
1359
|
let n = t.getSymbol()?.getDeclarations()?.[0];
|
|
1525
1360
|
return n === void 0 ? [] : e.getDescendantsOfKind(j.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
|
|
1526
|
-
},
|
|
1361
|
+
}, Ut = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Wt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Gt = [
|
|
1527
1362
|
"Id",
|
|
1528
1363
|
"ID",
|
|
1529
1364
|
"Hash",
|
|
@@ -1549,12 +1384,12 @@ var re = () => {
|
|
|
1549
1384
|
"Comment",
|
|
1550
1385
|
"Provider",
|
|
1551
1386
|
"Format"
|
|
1552
|
-
],
|
|
1387
|
+
], Kt = RegExp(`(?:${Gt.join("|")})$|_(?:${Gt.map((e) => e.toLowerCase()).join("|")})$`), qt = [
|
|
1553
1388
|
{
|
|
1554
1389
|
id: "hardcoded-framework-cookie",
|
|
1555
1390
|
scope: "client",
|
|
1556
1391
|
smell: "a framework cookie name written as a string literal instead of imported",
|
|
1557
|
-
use: "import the constant — `LOCALE_COOKIE` / `THEME_COOKIE` from `@voltro/ui-shadcn` — so a rename in the framework is a compile error in your app rather than a preference that quietly stops working",
|
|
1392
|
+
use: "import the constant — `LOCALE_COOKIE` / `THEME_COOKIE` from `@voltro/i18n` (or from `@voltro/ui-shadcn` if you use the kit) — so a rename in the framework is a compile error in your app rather than a preference that quietly stops working",
|
|
1558
1393
|
match: ({ src: e }) => /['"`]voltro:(?:locale|theme|lang)['"`]/.test(e)
|
|
1559
1394
|
},
|
|
1560
1395
|
{
|
|
@@ -1562,7 +1397,7 @@ var re = () => {
|
|
|
1562
1397
|
scope: "server",
|
|
1563
1398
|
smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
|
|
1564
1399
|
use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
|
|
1565
|
-
match: ({ src: e, path: t }) =>
|
|
1400
|
+
match: ({ src: e, path: t }) => Wt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
|
|
1566
1401
|
},
|
|
1567
1402
|
{
|
|
1568
1403
|
id: "subject-write-no-guard",
|
|
@@ -1570,17 +1405,17 @@ var re = () => {
|
|
|
1570
1405
|
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",
|
|
1571
1406
|
use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
|
|
1572
1407
|
match: ({ src: e, path: t }) => {
|
|
1573
|
-
if (!
|
|
1574
|
-
let n =
|
|
1408
|
+
if (!Wt.test(t)) return !1;
|
|
1409
|
+
let n = Rt(e);
|
|
1575
1410
|
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);
|
|
1576
1411
|
}
|
|
1577
1412
|
},
|
|
1578
1413
|
{
|
|
1579
1414
|
id: "raw-fetch",
|
|
1580
1415
|
scope: "server",
|
|
1581
|
-
smell: "raw fetch() for outbound HTTP on the server (no
|
|
1582
|
-
use: "yield* HttpClient — the framework client. It blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop
|
|
1583
|
-
match: ({ file: e, path: t, serverOnly: n }) => (
|
|
1416
|
+
smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
|
|
1417
|
+
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",
|
|
1418
|
+
match: ({ file: e, path: t, serverOnly: n }) => (Ut.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
|
|
1584
1419
|
},
|
|
1585
1420
|
{
|
|
1586
1421
|
id: "form",
|
|
@@ -1615,7 +1450,7 @@ var re = () => {
|
|
|
1615
1450
|
scope: "client",
|
|
1616
1451
|
smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
|
|
1617
1452
|
use: "useDerived — dependency-tracked, referentially stable",
|
|
1618
|
-
match: ({ src: e }) =>
|
|
1453
|
+
match: ({ src: e }) => zt(e, /\buseSubscription\s*[(<]/g) >= 3 && zt(e, /\buseMemo\s*\(/g) >= 3
|
|
1619
1454
|
},
|
|
1620
1455
|
{
|
|
1621
1456
|
id: "next-shim",
|
|
@@ -1640,7 +1475,7 @@ var re = () => {
|
|
|
1640
1475
|
match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
|
|
1641
1476
|
let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
|
|
1642
1477
|
return t === void 0 ? [] : k.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
|
|
1643
|
-
}).some((t) =>
|
|
1478
|
+
}).some((t) => Ht(e, t).some((e) => {
|
|
1644
1479
|
let t = e.getParent();
|
|
1645
1480
|
if (k.isBinaryExpression(t)) {
|
|
1646
1481
|
let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
|
|
@@ -1691,7 +1526,17 @@ var re = () => {
|
|
|
1691
1526
|
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)",
|
|
1692
1527
|
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) => {
|
|
1693
1528
|
let n = t[1];
|
|
1694
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
1529
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Kt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
|
|
1530
|
+
}))
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
id: "plaintext-secret-lookup-key",
|
|
1534
|
+
scope: "server",
|
|
1535
|
+
smell: "a credential-shaped column that is ALSO a unique lookup key",
|
|
1536
|
+
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",
|
|
1537
|
+
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) => {
|
|
1538
|
+
let n = t[1];
|
|
1539
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Kt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
|
|
1695
1540
|
}))
|
|
1696
1541
|
},
|
|
1697
1542
|
{
|
|
@@ -1756,8 +1601,8 @@ var re = () => {
|
|
|
1756
1601
|
smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
|
|
1757
1602
|
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",
|
|
1758
1603
|
match: ({ file: e, testSources: t }) => G(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
|
|
1759
|
-
let t =
|
|
1760
|
-
return
|
|
1604
|
+
let t = Rt(e);
|
|
1605
|
+
return Vt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
|
|
1761
1606
|
})
|
|
1762
1607
|
},
|
|
1763
1608
|
{
|
|
@@ -1767,14 +1612,14 @@ var re = () => {
|
|
|
1767
1612
|
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",
|
|
1768
1613
|
match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
|
|
1769
1614
|
}
|
|
1770
|
-
],
|
|
1615
|
+
], Jt = (e) => q(e), Yt = (e, t) => {
|
|
1771
1616
|
let n = /* @__PURE__ */ new Set();
|
|
1772
1617
|
for (let r of t) {
|
|
1773
1618
|
let t = S(e, r), i = t.split(/[\\/]/)[0];
|
|
1774
1619
|
n.add(i !== void 0 && i !== t ? i : ".");
|
|
1775
1620
|
}
|
|
1776
1621
|
return [...n].sort();
|
|
1777
|
-
},
|
|
1622
|
+
}, Xt = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Zt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Qt = (e, t = [], n = 0) => {
|
|
1778
1623
|
if (n > 12 || K.test(e)) return t;
|
|
1779
1624
|
let r;
|
|
1780
1625
|
try {
|
|
@@ -1786,8 +1631,8 @@ var re = () => {
|
|
|
1786
1631
|
let r = x(e, i.name);
|
|
1787
1632
|
if (i.isDirectory()) {
|
|
1788
1633
|
if (i.name.startsWith(".")) continue;
|
|
1789
|
-
|
|
1790
|
-
} else
|
|
1634
|
+
Qt(r, t, n + 1);
|
|
1635
|
+
} else dt.test(i.name) && t.push(r);
|
|
1791
1636
|
}
|
|
1792
1637
|
return t;
|
|
1793
1638
|
}, q = (e, t = [], n = 0) => {
|
|
@@ -1805,10 +1650,10 @@ var re = () => {
|
|
|
1805
1650
|
} catch {
|
|
1806
1651
|
continue;
|
|
1807
1652
|
}
|
|
1808
|
-
a.isDirectory() ? q(r, t, n + 1) :
|
|
1653
|
+
a.isDirectory() ? q(r, t, n + 1) : Xt.test(i) && !/\.(test|spec)\./.test(i) && !Zt.test(i) && t.push(r);
|
|
1809
1654
|
}
|
|
1810
1655
|
return t;
|
|
1811
|
-
},
|
|
1656
|
+
}, $t = (e, t = [], n = 0) => {
|
|
1812
1657
|
if (n > 12 || K.test(e)) return t;
|
|
1813
1658
|
let r;
|
|
1814
1659
|
try {
|
|
@@ -1823,17 +1668,17 @@ var re = () => {
|
|
|
1823
1668
|
} catch {
|
|
1824
1669
|
continue;
|
|
1825
1670
|
}
|
|
1826
|
-
a.isDirectory() ?
|
|
1671
|
+
a.isDirectory() ? $t(r, t, n + 1) : Xt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
|
|
1827
1672
|
}
|
|
1828
1673
|
return t;
|
|
1829
|
-
},
|
|
1830
|
-
let t =
|
|
1674
|
+
}, en = (e) => {
|
|
1675
|
+
let t = Jt(e);
|
|
1831
1676
|
return {
|
|
1832
|
-
dirs:
|
|
1677
|
+
dirs: Yt(e, t),
|
|
1833
1678
|
fileCount: t.length
|
|
1834
1679
|
};
|
|
1835
|
-
},
|
|
1836
|
-
let t =
|
|
1680
|
+
}, tn = (e) => {
|
|
1681
|
+
let t = Jt(e);
|
|
1837
1682
|
if (t.length === 0) return [];
|
|
1838
1683
|
let n = new A({
|
|
1839
1684
|
useInMemoryFileSystem: !0,
|
|
@@ -1842,8 +1687,8 @@ var re = () => {
|
|
|
1842
1687
|
for (let e of t) try {
|
|
1843
1688
|
i.set(e, E(e, "utf8"));
|
|
1844
1689
|
} catch {}
|
|
1845
|
-
let a =
|
|
1846
|
-
for (let t of
|
|
1690
|
+
let a = Lt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
|
|
1691
|
+
for (let t of $t(e)) try {
|
|
1847
1692
|
o.set(t, E(t, "utf8"));
|
|
1848
1693
|
} catch {}
|
|
1849
1694
|
for (let s of t) {
|
|
@@ -1861,23 +1706,23 @@ var re = () => {
|
|
|
1861
1706
|
} catch {
|
|
1862
1707
|
continue;
|
|
1863
1708
|
}
|
|
1864
|
-
for (let t of
|
|
1865
|
-
if (t.plugin !== void 0 &&
|
|
1709
|
+
for (let t of qt) {
|
|
1710
|
+
if (t.plugin !== void 0 && Bt(c, t.plugin) || !t.match(c)) continue;
|
|
1866
1711
|
let n = r.get(t.id) ?? [];
|
|
1867
1712
|
n.push(S(e, s)), r.set(t.id, n);
|
|
1868
1713
|
}
|
|
1869
1714
|
}
|
|
1870
|
-
return
|
|
1715
|
+
return qt.filter((e) => r.has(e.id)).map((e) => ({
|
|
1871
1716
|
rule: e,
|
|
1872
1717
|
files: r.get(e.id)
|
|
1873
1718
|
})).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
|
|
1874
|
-
},
|
|
1875
|
-
let t =
|
|
1719
|
+
}, nn = (e) => {
|
|
1720
|
+
let t = en(e);
|
|
1876
1721
|
return {
|
|
1877
1722
|
root: e,
|
|
1878
1723
|
scannedFiles: t.fileCount,
|
|
1879
1724
|
scannedDirs: t.dirs,
|
|
1880
|
-
findings:
|
|
1725
|
+
findings: tn(e).map(({ rule: e, files: t }) => ({
|
|
1881
1726
|
id: e.id,
|
|
1882
1727
|
scope: e.scope,
|
|
1883
1728
|
smell: e.smell,
|
|
@@ -1885,22 +1730,22 @@ var re = () => {
|
|
|
1885
1730
|
files: t
|
|
1886
1731
|
}))
|
|
1887
1732
|
};
|
|
1888
|
-
},
|
|
1733
|
+
}, rn = 3, an = .2, on = 50, sn = (e, t) => e >= on && t > 0 && e / t >= an, cn = (e) => {
|
|
1889
1734
|
let t = /* @__PURE__ */ new Map();
|
|
1890
1735
|
for (let n of q(e)) try {
|
|
1891
1736
|
t.set(S(e, n), E(n, "utf8"));
|
|
1892
1737
|
} catch {}
|
|
1893
1738
|
let n = /* @__PURE__ */ new Map();
|
|
1894
|
-
for (let t of
|
|
1739
|
+
for (let t of Qt(e)) try {
|
|
1895
1740
|
n.set(S(e, t), E(t, "utf8"));
|
|
1896
1741
|
} catch {}
|
|
1897
|
-
let r =
|
|
1742
|
+
let r = ft({
|
|
1898
1743
|
sources: t,
|
|
1899
1744
|
envFiles: n
|
|
1900
1745
|
});
|
|
1901
|
-
r !== void 0 && (console.log(""), console.log(
|
|
1902
|
-
},
|
|
1903
|
-
let t =
|
|
1746
|
+
r !== void 0 && (console.log(""), console.log(pt(r)));
|
|
1747
|
+
}, ln = (e) => {
|
|
1748
|
+
let t = nn(e);
|
|
1904
1749
|
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;
|
|
1905
1750
|
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;
|
|
1906
1751
|
console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
|
|
@@ -1911,56 +1756,56 @@ var re = () => {
|
|
|
1911
1756
|
console.log(` [${e}]`);
|
|
1912
1757
|
let i = [...r].sort((e, t) => e.files.length - t.files.length);
|
|
1913
1758
|
for (let e of i) {
|
|
1914
|
-
if (
|
|
1759
|
+
if (sn(e.files.length, t.scannedFiles)) {
|
|
1915
1760
|
let n = Math.round(e.files.length / t.scannedFiles * 100);
|
|
1916
1761
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s), ${n}% of those scanned: a codebase-wide pattern rather than a work list`), console.log(` \u2192 ${e.use}`), console.log(" (paths omitted on purpose at this scale — `voltro doctor --json` has every one.)");
|
|
1917
1762
|
continue;
|
|
1918
1763
|
}
|
|
1919
|
-
let r = e.files.slice(0,
|
|
1764
|
+
let r = e.files.slice(0, rn).join(", "), i = e.files.length - rn;
|
|
1920
1765
|
i > 0 && (n += i);
|
|
1921
1766
|
let a = i > 0 ? `, +${i} more` : "";
|
|
1922
1767
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
|
|
1923
1768
|
}
|
|
1924
1769
|
}
|
|
1925
1770
|
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;
|
|
1926
|
-
},
|
|
1927
|
-
let t =
|
|
1771
|
+
}, un = "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.", dn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, fn = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, pn = (e) => {
|
|
1772
|
+
let t = dn.exec(e)?.[1];
|
|
1928
1773
|
if (t !== void 0) return t === "ssr" ? "ssr" : null;
|
|
1929
|
-
let n =
|
|
1774
|
+
let n = fn.exec(e)?.[1];
|
|
1930
1775
|
return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
|
|
1931
|
-
},
|
|
1932
|
-
let
|
|
1933
|
-
if (!T(
|
|
1934
|
-
let { pages:
|
|
1935
|
-
for (let
|
|
1936
|
-
if (!
|
|
1937
|
-
let
|
|
1776
|
+
}, mn = async (e) => {
|
|
1777
|
+
let t = x(e, "src", "pages");
|
|
1778
|
+
if (!T(t)) return [];
|
|
1779
|
+
let { pages: i, dirs: o } = await n(t), s = [];
|
|
1780
|
+
for (let n of i) {
|
|
1781
|
+
if (!a(o, n.dirChain).some((e) => e.hasLayout)) continue;
|
|
1782
|
+
let i;
|
|
1938
1783
|
try {
|
|
1939
|
-
|
|
1784
|
+
i = E(x(t, n.file), "utf8");
|
|
1940
1785
|
} catch {
|
|
1941
1786
|
continue;
|
|
1942
1787
|
}
|
|
1943
|
-
if (r.test(
|
|
1944
|
-
let c =
|
|
1788
|
+
if (r.test(i)) continue;
|
|
1789
|
+
let c = pn(i);
|
|
1945
1790
|
c !== null && s.push({
|
|
1946
|
-
file: S(e, x(
|
|
1947
|
-
pattern:
|
|
1791
|
+
file: S(e, x(t, n.file)),
|
|
1792
|
+
pattern: n.pattern,
|
|
1948
1793
|
currentMode: c
|
|
1949
1794
|
});
|
|
1950
1795
|
}
|
|
1951
1796
|
return s;
|
|
1952
|
-
},
|
|
1797
|
+
}, hn = 10, gn = async (e) => {
|
|
1953
1798
|
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;
|
|
1954
|
-
let t = await
|
|
1799
|
+
let t = await mn(e);
|
|
1955
1800
|
if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
|
|
1956
1801
|
console.log(`• renderMode:'spa' candidates (${t.length} page${t.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
|
|
1957
|
-
for (let e of t.slice(0,
|
|
1802
|
+
for (let e of t.slice(0, hn)) {
|
|
1958
1803
|
let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
|
|
1959
1804
|
console.log(` ${e.file} (${e.pattern}) — ${t}`);
|
|
1960
1805
|
}
|
|
1961
|
-
let n = t.length -
|
|
1962
|
-
return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${
|
|
1963
|
-
},
|
|
1806
|
+
let n = t.length - hn;
|
|
1807
|
+
return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${un}`), t.length;
|
|
1808
|
+
}, _n = /* @__PURE__ */ new Set([
|
|
1964
1809
|
"node_modules",
|
|
1965
1810
|
"dist",
|
|
1966
1811
|
"build",
|
|
@@ -1968,7 +1813,7 @@ var re = () => {
|
|
|
1968
1813
|
".vite",
|
|
1969
1814
|
".framework",
|
|
1970
1815
|
".git"
|
|
1971
|
-
]),
|
|
1816
|
+
]), vn = (e, t, n) => {
|
|
1972
1817
|
let r;
|
|
1973
1818
|
try {
|
|
1974
1819
|
r = D(e, { withFileTypes: !0 });
|
|
@@ -1976,11 +1821,11 @@ var re = () => {
|
|
|
1976
1821
|
return;
|
|
1977
1822
|
}
|
|
1978
1823
|
for (let i of r) {
|
|
1979
|
-
if (
|
|
1824
|
+
if (_n.has(i.name)) continue;
|
|
1980
1825
|
let r = x(e, i.name);
|
|
1981
|
-
i.isDirectory() ?
|
|
1826
|
+
i.isDirectory() ? vn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
|
|
1982
1827
|
}
|
|
1983
|
-
},
|
|
1828
|
+
}, yn = (e, t) => {
|
|
1984
1829
|
let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
|
|
1985
1830
|
for (let i of t) {
|
|
1986
1831
|
let t;
|
|
@@ -2005,62 +1850,70 @@ var re = () => {
|
|
|
2005
1850
|
}
|
|
2006
1851
|
}
|
|
2007
1852
|
return n;
|
|
2008
|
-
},
|
|
1853
|
+
}, bn = (e, t) => {
|
|
2009
1854
|
let n = e.slice(t.length).split(w).slice(0, -1)[0];
|
|
2010
1855
|
return n !== void 0 && n.startsWith("[");
|
|
2011
|
-
}, J = (
|
|
2012
|
-
let
|
|
2013
|
-
if (!T(
|
|
2014
|
-
let
|
|
2015
|
-
|
|
2016
|
-
let
|
|
2017
|
-
|
|
2018
|
-
for (let
|
|
2019
|
-
let
|
|
2020
|
-
if (
|
|
2021
|
-
if (
|
|
2022
|
-
T(x(
|
|
1856
|
+
}, J = (e) => {
|
|
1857
|
+
let t = [], n = x(e, "src", "pages"), r = x(e, "src");
|
|
1858
|
+
if (!T(r)) return t;
|
|
1859
|
+
let i = [];
|
|
1860
|
+
vn(r, e, i), i.sort();
|
|
1861
|
+
let a = `${S(e, n)}${w}`;
|
|
1862
|
+
yn(e, i);
|
|
1863
|
+
for (let n of i) {
|
|
1864
|
+
let r = n.slice(n.lastIndexOf(w) + 1);
|
|
1865
|
+
if (n.startsWith(a)) {
|
|
1866
|
+
if (r !== "page.tsx") continue;
|
|
1867
|
+
T(x(e, c(n))) || t.push({
|
|
2023
1868
|
kind: "page-without-test",
|
|
2024
|
-
file:
|
|
2025
|
-
})
|
|
1869
|
+
file: n
|
|
1870
|
+
});
|
|
1871
|
+
try {
|
|
1872
|
+
let r = E(x(e, n), "utf8");
|
|
1873
|
+
!/\bexport\s+(?:const|let|var|function)\s+meta\b/.test(r) && !/\bexport\s*\{[^}]*\bmeta\b/.test(r) && t.push({
|
|
1874
|
+
kind: "page-without-meta",
|
|
1875
|
+
file: n
|
|
1876
|
+
});
|
|
1877
|
+
} catch {}
|
|
1878
|
+
bn(n, a) && t.push({
|
|
2026
1879
|
kind: "top-level-dynamic-route",
|
|
2027
|
-
file:
|
|
1880
|
+
file: n
|
|
2028
1881
|
});
|
|
2029
1882
|
continue;
|
|
2030
1883
|
}
|
|
2031
|
-
|
|
1884
|
+
r === "page.tsx" && t.push({
|
|
2032
1885
|
kind: "suffixed-outside-pages",
|
|
2033
|
-
file:
|
|
1886
|
+
file: n
|
|
2034
1887
|
});
|
|
2035
1888
|
}
|
|
2036
|
-
return
|
|
2037
|
-
},
|
|
1889
|
+
return t;
|
|
1890
|
+
}, xn = 10, Y = (e, t, n) => {
|
|
2038
1891
|
if (t.length === 0) return;
|
|
2039
1892
|
console.log(`• ${e} (${t.length}):`);
|
|
2040
|
-
for (let e of t.slice(0,
|
|
2041
|
-
let r = t.length -
|
|
1893
|
+
for (let e of t.slice(0, xn)) console.log(` ${e}`);
|
|
1894
|
+
let r = t.length - xn;
|
|
2042
1895
|
r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
|
|
2043
|
-
},
|
|
1896
|
+
}, Sn = (e) => {
|
|
2044
1897
|
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;
|
|
2045
1898
|
let t = J(e);
|
|
2046
1899
|
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;
|
|
2047
1900
|
let n = (e) => t.filter((t) => t.kind === e).map((e) => e.file);
|
|
2048
|
-
return
|
|
2049
|
-
},
|
|
2050
|
-
let t =
|
|
1901
|
+
return Y("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."), Y("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."), Y("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."), Y("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). 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."), t.length;
|
|
1902
|
+
}, Cn = "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.", wn = /* @__PURE__ */ new Map(), X = (e) => {
|
|
1903
|
+
let t = wn.get(e);
|
|
2051
1904
|
if (t !== void 0) return t;
|
|
2052
1905
|
let n = (async () => {
|
|
2053
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
1906
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-DOZVMVfi.js"), r = await t(e);
|
|
2054
1907
|
return {
|
|
2055
1908
|
files: r,
|
|
2056
1909
|
discovered: await n(r)
|
|
2057
1910
|
};
|
|
2058
1911
|
})();
|
|
2059
|
-
return
|
|
2060
|
-
},
|
|
1912
|
+
return wn.set(e, n), n;
|
|
1913
|
+
}, Tn = async (e) => {
|
|
2061
1914
|
let t;
|
|
2062
1915
|
try {
|
|
2063
|
-
let { files: n, discovered: r } = await
|
|
1916
|
+
let { files: n, discovered: r } = await X(e), i = r.tables.map((e) => {
|
|
2064
1917
|
let t = e;
|
|
2065
1918
|
return {
|
|
2066
1919
|
name: t.tableName,
|
|
@@ -2068,12 +1921,12 @@ var re = () => {
|
|
|
2068
1921
|
};
|
|
2069
1922
|
});
|
|
2070
1923
|
if (i.length === 0) return;
|
|
2071
|
-
t = Ke(
|
|
1924
|
+
t = qe(Ke(n, i, e));
|
|
2072
1925
|
} catch {
|
|
2073
1926
|
return;
|
|
2074
1927
|
}
|
|
2075
1928
|
t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
|
|
2076
|
-
},
|
|
1929
|
+
}, En = (e) => e.map((e) => {
|
|
2077
1930
|
let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
|
|
2078
1931
|
name: e,
|
|
2079
1932
|
type: typeof t?.type == "string" ? t.type : ""
|
|
@@ -2083,22 +1936,22 @@ var re = () => {
|
|
|
2083
1936
|
columns: n,
|
|
2084
1937
|
primaryKey: t.appliedPrimaryKey ?? []
|
|
2085
1938
|
};
|
|
2086
|
-
}),
|
|
1939
|
+
}), Z = async (e) => {
|
|
2087
1940
|
try {
|
|
2088
|
-
let { discovered: t } = await
|
|
2089
|
-
return
|
|
1941
|
+
let { discovered: t } = await X(e);
|
|
1942
|
+
return Qe(En(t.tables));
|
|
2090
1943
|
} catch {
|
|
2091
1944
|
return null;
|
|
2092
1945
|
}
|
|
2093
|
-
},
|
|
2094
|
-
let t = await
|
|
1946
|
+
}, Dn = async (e) => {
|
|
1947
|
+
let t = await Z(e);
|
|
2095
1948
|
if (t === null || t.tablesChecked === 0) return;
|
|
2096
|
-
let n =
|
|
1949
|
+
let n = et(t);
|
|
2097
1950
|
n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
|
|
2098
|
-
},
|
|
1951
|
+
}, On = async (e) => {
|
|
2099
1952
|
try {
|
|
2100
|
-
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await
|
|
2101
|
-
return
|
|
1953
|
+
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await X(e);
|
|
1954
|
+
return it(n.events.map((e) => {
|
|
2102
1955
|
let n = e.descriptor, r = t(n.key);
|
|
2103
1956
|
return {
|
|
2104
1957
|
name: n.name,
|
|
@@ -2110,19 +1963,19 @@ var re = () => {
|
|
|
2110
1963
|
} catch {
|
|
2111
1964
|
return null;
|
|
2112
1965
|
}
|
|
2113
|
-
},
|
|
2114
|
-
let t = await
|
|
1966
|
+
}, kn = async (e) => {
|
|
1967
|
+
let t = await On(e);
|
|
2115
1968
|
if (t === null || t.eventsChecked === 0) return;
|
|
2116
|
-
let n =
|
|
1969
|
+
let n = at(t);
|
|
2117
1970
|
n !== null && (console.log(""), console.log(`• ${n}`));
|
|
2118
|
-
let r =
|
|
1971
|
+
let r = ot(t);
|
|
2119
1972
|
r !== null && (console.log(""), console.log(`⚠️ ${r}`));
|
|
2120
|
-
},
|
|
1973
|
+
}, An = (e) => {
|
|
2121
1974
|
let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
|
|
2122
1975
|
return r === void 0 ? n : `${r}: ${n}`;
|
|
2123
|
-
},
|
|
1976
|
+
}, jn = async (e) => {
|
|
2124
1977
|
try {
|
|
2125
|
-
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await
|
|
1978
|
+
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await X(e)).discovered.serverOnlyLeaks, r = t(n);
|
|
2126
1979
|
return {
|
|
2127
1980
|
status: {
|
|
2128
1981
|
checked: !0,
|
|
@@ -2133,17 +1986,17 @@ var re = () => {
|
|
|
2133
1986
|
} catch (e) {
|
|
2134
1987
|
return { status: {
|
|
2135
1988
|
checked: !1,
|
|
2136
|
-
reason:
|
|
1989
|
+
reason: An(e)
|
|
2137
1990
|
} };
|
|
2138
1991
|
}
|
|
2139
|
-
},
|
|
2140
|
-
let { status: t, message: n } = await
|
|
1992
|
+
}, Mn = async (e) => {
|
|
1993
|
+
let { status: t, message: n } = await jn(e);
|
|
2141
1994
|
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);
|
|
2142
|
-
},
|
|
1995
|
+
}, Nn = async (e) => {
|
|
2143
1996
|
let t;
|
|
2144
1997
|
try {
|
|
2145
|
-
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await
|
|
2146
|
-
t =
|
|
1998
|
+
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await X(e);
|
|
1999
|
+
t = lt(r, i.workflows.map((e) => {
|
|
2147
2000
|
let t = e.definition, r = t.payloadSchema === void 0 ? {
|
|
2148
2001
|
required: [],
|
|
2149
2002
|
optional: []
|
|
@@ -2157,18 +2010,59 @@ var re = () => {
|
|
|
2157
2010
|
} catch {
|
|
2158
2011
|
return;
|
|
2159
2012
|
}
|
|
2160
|
-
let n =
|
|
2013
|
+
let n = ut(t);
|
|
2161
2014
|
if (n === null) return;
|
|
2162
2015
|
console.log("");
|
|
2163
2016
|
let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
|
|
2164
2017
|
console.log(`${r} ${n}`);
|
|
2165
|
-
}, Q = "voltro-authz-allowlist.txt",
|
|
2018
|
+
}, Q = "voltro-authz-allowlist.txt", Pn = (e, t = (e) => {
|
|
2166
2019
|
try {
|
|
2167
2020
|
return E(e, "utf8");
|
|
2168
2021
|
} catch {
|
|
2169
2022
|
return "";
|
|
2170
2023
|
}
|
|
2171
|
-
}) => /* @__PURE__ */ new Set([...
|
|
2024
|
+
}) => /* @__PURE__ */ new Set([..._t, ...vt(q(e).map(t))]), Fn = async (e) => {
|
|
2025
|
+
try {
|
|
2026
|
+
let { discovered: t } = await X(e), n = t.events.map((e) => e.descriptor.name).filter((e) => typeof e == "string");
|
|
2027
|
+
if (n.length === 0) return [];
|
|
2028
|
+
let r = new Set(t.events.map((e) => e.file)), i = q(e).filter((e) => !r.has(e)).map((e) => {
|
|
2029
|
+
try {
|
|
2030
|
+
return E(e, "utf8");
|
|
2031
|
+
} catch {
|
|
2032
|
+
return "";
|
|
2033
|
+
}
|
|
2034
|
+
}).join("\n");
|
|
2035
|
+
return n.filter((e) => !i.includes(e)).sort();
|
|
2036
|
+
} catch {
|
|
2037
|
+
return [];
|
|
2038
|
+
}
|
|
2039
|
+
}, In = async (e) => {
|
|
2040
|
+
let t = await Fn(e);
|
|
2041
|
+
if (t.length !== 0) {
|
|
2042
|
+
console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
|
|
2043
|
+
for (let e of t) console.log(` ${e}`);
|
|
2044
|
+
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 in your sources. An emit"), console.log(" through a variable or an assembled name will read as missing here.");
|
|
2045
|
+
}
|
|
2046
|
+
}, Ln = async (e) => {
|
|
2047
|
+
try {
|
|
2048
|
+
let { offlineManifest: t, toInput: n } = await import("./checkCommand-ClRvH_SN.js"), { buildAppGraph: r, checkAppGraph: i } = await import("./appGraph-CvQCte0z.js"), a = await t(e);
|
|
2049
|
+
return i(r(n(a.manifest, a.tables))).filter((e) => e.rule === "rbac/unknown-scope").map((e) => ({
|
|
2050
|
+
node: e.node,
|
|
2051
|
+
...e.file === void 0 ? {} : { file: e.file },
|
|
2052
|
+
detail: e.breaks.join("; "),
|
|
2053
|
+
fix: e.fix
|
|
2054
|
+
}));
|
|
2055
|
+
} catch {
|
|
2056
|
+
return [];
|
|
2057
|
+
}
|
|
2058
|
+
}, Rn = async (e) => {
|
|
2059
|
+
let t = await Ln(e);
|
|
2060
|
+
if (t.length !== 0) {
|
|
2061
|
+
console.log(""), console.log(`• ${t.length} procedure(s) require a scope no declared role grants:`);
|
|
2062
|
+
for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
|
|
2063
|
+
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}`);
|
|
2064
|
+
}
|
|
2065
|
+
}, zn = async (e) => {
|
|
2172
2066
|
let t = ["src/locales", "locales"], n, r = [];
|
|
2173
2067
|
for (let i of t) {
|
|
2174
2068
|
let t = x(e, i);
|
|
@@ -2180,23 +2074,23 @@ var re = () => {
|
|
|
2180
2074
|
if (n === void 0 || r.length === 0) return;
|
|
2181
2075
|
let i = [];
|
|
2182
2076
|
try {
|
|
2183
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2077
|
+
let { loadApiConfig: t } = await import("./dev-DOZVMVfi.js");
|
|
2184
2078
|
i = (await t(e))?.locales ?? [];
|
|
2185
2079
|
} catch {
|
|
2186
2080
|
return;
|
|
2187
2081
|
}
|
|
2188
|
-
let a =
|
|
2082
|
+
let a = mt({
|
|
2189
2083
|
catalogCodes: r,
|
|
2190
2084
|
declaredLocales: i
|
|
2191
2085
|
});
|
|
2192
2086
|
if (a !== void 0) {
|
|
2193
2087
|
console.log("");
|
|
2194
|
-
for (let e of
|
|
2088
|
+
for (let e of ht(a, n)) console.log(e);
|
|
2195
2089
|
}
|
|
2196
|
-
},
|
|
2197
|
-
let { discovered: t } = await
|
|
2090
|
+
}, Bn = async (e) => {
|
|
2091
|
+
let { discovered: t } = await X(e), n = [];
|
|
2198
2092
|
try {
|
|
2199
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2093
|
+
let { loadApiConfig: t } = await import("./dev-DOZVMVfi.js");
|
|
2200
2094
|
n = ((await t(e))?.plugins ?? []).map((e) => {
|
|
2201
2095
|
let t = (e.extendSchema?.tables ?? []).map((e) => {
|
|
2202
2096
|
let t = e;
|
|
@@ -2212,8 +2106,8 @@ var re = () => {
|
|
|
2212
2106
|
plugin: e.name,
|
|
2213
2107
|
tables: t,
|
|
2214
2108
|
rpcTags: n,
|
|
2215
|
-
canDisableTables:
|
|
2216
|
-
canAlias:
|
|
2109
|
+
canDisableTables: Vn.has(e.name),
|
|
2110
|
+
canAlias: Hn.has(e.name)
|
|
2217
2111
|
};
|
|
2218
2112
|
});
|
|
2219
2113
|
} catch {
|
|
@@ -2225,14 +2119,14 @@ var re = () => {
|
|
|
2225
2119
|
...t.mutations,
|
|
2226
2120
|
...t.actions,
|
|
2227
2121
|
...t.streams ?? []
|
|
2228
|
-
].map((e) => String(e.descriptor.name)), i =
|
|
2122
|
+
].map((e) => String(e.descriptor.name)), i = jt(n, {
|
|
2229
2123
|
tables: t.tables.map((e) => e.tableName),
|
|
2230
2124
|
rpcTags: r
|
|
2231
2125
|
});
|
|
2232
2126
|
console.log();
|
|
2233
|
-
for (let e of
|
|
2234
|
-
},
|
|
2235
|
-
let t = await
|
|
2127
|
+
for (let e of Mt(n, i)) console.log(e);
|
|
2128
|
+
}, Vn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Hn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Un = async (e) => {
|
|
2129
|
+
let t = await Wn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
|
|
2236
2130
|
return {
|
|
2237
2131
|
executors: t.executorCount,
|
|
2238
2132
|
guardVocabulary: [...t.vocabulary].sort(),
|
|
@@ -2244,15 +2138,15 @@ var re = () => {
|
|
|
2244
2138
|
},
|
|
2245
2139
|
unchecked: t.result.unchecked
|
|
2246
2140
|
};
|
|
2247
|
-
},
|
|
2248
|
-
let { discovered: t } = await
|
|
2141
|
+
}, Wn = async (e) => {
|
|
2142
|
+
let { discovered: t } = await X(e), n = /* @__PURE__ */ new Map(), r = (e) => {
|
|
2249
2143
|
if (!n.has(e)) try {
|
|
2250
2144
|
n.set(e, E(e, "utf8"));
|
|
2251
2145
|
} catch {
|
|
2252
2146
|
n.set(e, "");
|
|
2253
2147
|
}
|
|
2254
2148
|
return n.get(e);
|
|
2255
|
-
}, i =
|
|
2149
|
+
}, i = Pn(e, r), a = /* @__PURE__ */ new Set();
|
|
2256
2150
|
for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
|
|
2257
2151
|
let e = t.references?.();
|
|
2258
2152
|
e && a.add(e.tableName);
|
|
@@ -2284,56 +2178,71 @@ var re = () => {
|
|
|
2284
2178
|
op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
|
|
2285
2179
|
...a?.table === void 0 ? {} : { table: a.table }
|
|
2286
2180
|
};
|
|
2287
|
-
})), s = x(e, Q), c = T(s) ?
|
|
2181
|
+
})), s = x(e, Q), c = T(s) ? W(E(s, "utf8")) : void 0, l = !1;
|
|
2182
|
+
for (let t of q(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
|
|
2183
|
+
l = !0;
|
|
2184
|
+
break;
|
|
2185
|
+
}
|
|
2186
|
+
let u = wt({
|
|
2187
|
+
executors: o,
|
|
2188
|
+
guardVocabulary: i,
|
|
2189
|
+
tenantScopedTables: t.tenantScopedTables,
|
|
2190
|
+
referencedTables: a,
|
|
2191
|
+
allowlist: c?.tags ?? /* @__PURE__ */ new Set(),
|
|
2192
|
+
rowFilterRegistered: l
|
|
2193
|
+
}), d = (c?.entries ?? []).filter((e) => e.reviewed !== void 0);
|
|
2288
2194
|
return {
|
|
2289
|
-
result:
|
|
2290
|
-
executors: o,
|
|
2291
|
-
guardVocabulary: i,
|
|
2292
|
-
tenantScopedTables: t.tenantScopedTables,
|
|
2293
|
-
referencedTables: a,
|
|
2294
|
-
allowlist: c?.tags ?? /* @__PURE__ */ new Set()
|
|
2295
|
-
}),
|
|
2195
|
+
result: u,
|
|
2296
2196
|
vocabulary: i,
|
|
2197
|
+
rowFilterRegistered: l,
|
|
2297
2198
|
existing: c,
|
|
2298
|
-
reviewed:
|
|
2199
|
+
reviewed: d,
|
|
2299
2200
|
executorCount: o.length,
|
|
2300
2201
|
allowPath: s
|
|
2301
2202
|
};
|
|
2302
|
-
},
|
|
2303
|
-
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s } = await
|
|
2203
|
+
}, Gn = async (e, t) => {
|
|
2204
|
+
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Wn(e), l = n.counts;
|
|
2304
2205
|
console.log(`\nauthz scan · ${o} executor(s)`);
|
|
2305
|
-
let
|
|
2306
|
-
console.log(
|
|
2307
|
-
let
|
|
2308
|
-
if (console.log(` – accepted as recorded debt ${String(
|
|
2206
|
+
let u = [...r].filter((e) => !_t.has(e)).sort();
|
|
2207
|
+
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)}`);
|
|
2208
|
+
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;
|
|
2209
|
+
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) {
|
|
2210
|
+
let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ot(e, t);
|
|
2211
|
+
return se(s, Dt(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;
|
|
2212
|
+
}
|
|
2309
2213
|
if (n.unchecked.length === 0) return !1;
|
|
2310
2214
|
console.log("");
|
|
2311
2215
|
for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
|
|
2312
2216
|
return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Q} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Q}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
|
|
2313
|
-
},
|
|
2314
|
-
if (e.includes("--help") || e.includes("-h")) return console.log(
|
|
2315
|
-
let
|
|
2316
|
-
if (
|
|
2317
|
-
...
|
|
2318
|
-
spaCandidates: await
|
|
2319
|
-
pageConvention: J(
|
|
2320
|
-
fileTaxonomy:
|
|
2321
|
-
junctionFks: (await
|
|
2322
|
-
eventDelivery: await
|
|
2323
|
-
duplicateInstances:
|
|
2324
|
-
|
|
2325
|
-
|
|
2217
|
+
}, Kn = async (e) => {
|
|
2218
|
+
if (e.includes("--help") || e.includes("-h")) return console.log(Cn), 0;
|
|
2219
|
+
let n = e.includes("--strict"), r = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
|
|
2220
|
+
if (le(e)) return ce(), M({
|
|
2221
|
+
...nn(r),
|
|
2222
|
+
spaCandidates: await mn(r),
|
|
2223
|
+
pageConvention: J(r),
|
|
2224
|
+
fileTaxonomy: V(r).findings,
|
|
2225
|
+
junctionFks: (await Z(r))?.issues ?? [],
|
|
2226
|
+
eventDelivery: await On(r),
|
|
2227
|
+
duplicateInstances: t(r),
|
|
2228
|
+
versionSkew: s(r) ?? null,
|
|
2229
|
+
serverOnly: (await jn(r)).status,
|
|
2230
|
+
authz: await Un(r),
|
|
2231
|
+
ungrantedScopes: await Ln(r),
|
|
2232
|
+
neverEmittedEvents: await Fn(r)
|
|
2326
2233
|
}), 0;
|
|
2327
|
-
let
|
|
2328
|
-
|
|
2329
|
-
let
|
|
2330
|
-
|
|
2331
|
-
|
|
2234
|
+
let a = It(r, n);
|
|
2235
|
+
ln(r), cn(r), Sn(r), He(r), await gn(r), await Nn(r), await Tn(r), await Dn(r), await kn(r), await Bn(r), await Rn(r), await In(r), await zn(r);
|
|
2236
|
+
let c = await Mn(r), l = await Gn(r, e.includes("--write-authz-allowlist")), u = o(s(r));
|
|
2237
|
+
u !== void 0 && (console.log(""), console.log(u));
|
|
2238
|
+
let d = i(t(r));
|
|
2239
|
+
return d !== void 0 && console.log(`\n${d}`), c || l ? Math.max(a, 1) : a;
|
|
2240
|
+
}, qn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Jn = /* @__PURE__ */ new Set([
|
|
2332
2241
|
"insert",
|
|
2333
2242
|
"update",
|
|
2334
2243
|
"delete",
|
|
2335
2244
|
"upsert"
|
|
2336
|
-
]),
|
|
2245
|
+
]), Yn = (e, t) => {
|
|
2337
2246
|
let n = [], r = new Set(t.exercised), i = /* @__PURE__ */ new Map();
|
|
2338
2247
|
for (let e of t.edges) {
|
|
2339
2248
|
let t = i.get(e.tag);
|
|
@@ -2345,8 +2254,8 @@ var re = () => {
|
|
|
2345
2254
|
a.push(t.tag);
|
|
2346
2255
|
continue;
|
|
2347
2256
|
}
|
|
2348
|
-
let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(
|
|
2349
|
-
for (let t of c) if (
|
|
2257
|
+
let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(qn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
|
|
2258
|
+
for (let t of c) if (Jn.has(t.op)) {
|
|
2350
2259
|
let r = u.get(t.table);
|
|
2351
2260
|
r === void 0 ? n.push({
|
|
2352
2261
|
rule: "observed/undeclared-write",
|
|
@@ -2402,7 +2311,7 @@ var re = () => {
|
|
|
2402
2311
|
total: e.length
|
|
2403
2312
|
}
|
|
2404
2313
|
};
|
|
2405
|
-
},
|
|
2314
|
+
}, Xn = (e, t = [], n = 0) => {
|
|
2406
2315
|
if (n > 12) return t;
|
|
2407
2316
|
let r;
|
|
2408
2317
|
try {
|
|
@@ -2420,30 +2329,30 @@ var re = () => {
|
|
|
2420
2329
|
".git",
|
|
2421
2330
|
"coverage"
|
|
2422
2331
|
].includes(i)) continue;
|
|
2423
|
-
let r = `${e}/${i}`,
|
|
2332
|
+
let r = `${e}/${i}`, a;
|
|
2424
2333
|
try {
|
|
2425
|
-
|
|
2334
|
+
a = O(r);
|
|
2426
2335
|
} catch {
|
|
2427
2336
|
continue;
|
|
2428
2337
|
}
|
|
2429
|
-
|
|
2338
|
+
a.isDirectory() ? Xn(r, t, n + 1) : a.isFile() && l.test(i) && t.push(r);
|
|
2430
2339
|
}
|
|
2431
2340
|
return t;
|
|
2432
|
-
},
|
|
2341
|
+
}, Zn = /* @__PURE__ */ new Set(["actors", "tenants"]), Qn = async (e) => {
|
|
2433
2342
|
try {
|
|
2434
2343
|
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);
|
|
2435
2344
|
return i?.version !== 1 || !Array.isArray(i.edges) || !Array.isArray(i.exercised) ? null : i;
|
|
2436
2345
|
} catch {
|
|
2437
2346
|
return null;
|
|
2438
2347
|
}
|
|
2439
|
-
},
|
|
2348
|
+
}, $n = (e) => {
|
|
2440
2349
|
let { exercised: t, total: n } = e.coverage, r = `\nobserved: ${t}/${n} procedures exercised (${n === 0 ? 0 : Math.round(t / n * 100)}%)`;
|
|
2441
2350
|
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` : ""}`;
|
|
2442
2351
|
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` : "";
|
|
2443
2352
|
return `${r}\n${i.join("\n")}${a}`;
|
|
2444
|
-
},
|
|
2353
|
+
}, er = (e, t) => ({
|
|
2445
2354
|
tables: (t.tables ?? []).map((e) => e.name),
|
|
2446
|
-
frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") ||
|
|
2355
|
+
frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || Zn.has(e.name)).map((e) => e.name),
|
|
2447
2356
|
scopes: e.scopes ?? [],
|
|
2448
2357
|
procedures: (e.procedures ?? []).map((e) => {
|
|
2449
2358
|
let t = (e.guards ?? []).flatMap((e) => e.kind === "scope" ? e.scope ?? [] : []);
|
|
@@ -2456,16 +2365,16 @@ var re = () => {
|
|
|
2456
2365
|
...e.guards === void 0 ? {} : { hasGuards: e.guards.length > 0 }
|
|
2457
2366
|
};
|
|
2458
2367
|
})
|
|
2459
|
-
}),
|
|
2368
|
+
}), tr = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
|
|
2460
2369
|
let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
|
|
2461
2370
|
return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
|
|
2462
|
-
}).join("\n"),
|
|
2463
|
-
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-
|
|
2371
|
+
}).join("\n"), nr = async (e) => {
|
|
2372
|
+
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-DOZVMVfi.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 = [
|
|
2464
2373
|
...s.queries.map((e) => c(e.descriptor, "query", e.file)),
|
|
2465
2374
|
...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
|
|
2466
2375
|
...s.actions.map((e) => c(e.descriptor, "action", e.file)),
|
|
2467
2376
|
...s.streams.map((e) => c(e.descriptor, "stream", e.file))
|
|
2468
|
-
], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-
|
|
2377
|
+
], { 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) => ({
|
|
2469
2378
|
name: e,
|
|
2470
2379
|
framework: !0
|
|
2471
2380
|
}))], m = await r(e).then((e) => e?.plugins ?? []).catch(() => []);
|
|
@@ -2476,10 +2385,10 @@ var re = () => {
|
|
|
2476
2385
|
})), [], m),
|
|
2477
2386
|
tables: { tables: p }
|
|
2478
2387
|
};
|
|
2479
|
-
},
|
|
2480
|
-
let t =
|
|
2388
|
+
}, rr = async (e) => {
|
|
2389
|
+
let t = me(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
|
|
2481
2390
|
if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
|
|
2482
|
-
let { process: n, target: r, rest: i } =
|
|
2391
|
+
let { process: n, target: r, rest: i } = he(t.rest), a;
|
|
2483
2392
|
for (let e = 0; e < i.length; e++) if (i[e] === "--diff") {
|
|
2484
2393
|
let [t, n] = (i[++e] ?? "").split(":");
|
|
2485
2394
|
if (t === "removeTable" && n) a = {
|
|
@@ -2496,35 +2405,35 @@ var re = () => {
|
|
|
2496
2405
|
};
|
|
2497
2406
|
else return console.error("voltro check --diff: expected removeTable:<name> | removeProcedure:<tag> | removeScope:<name>"), 1;
|
|
2498
2407
|
}
|
|
2499
|
-
let
|
|
2500
|
-
if (
|
|
2501
|
-
if (!
|
|
2408
|
+
let o = i.find((e) => !e.startsWith("-")) ?? process.cwd(), s = i.includes("--offline"), c = s ? [] : await pe(n, r), l, d;
|
|
2409
|
+
if (c.length === 0) {
|
|
2410
|
+
if (!s && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
|
|
2502
2411
|
try {
|
|
2503
|
-
let e = await
|
|
2504
|
-
|
|
2412
|
+
let e = await nr(C(o));
|
|
2413
|
+
l = e.manifest, d = e.tables;
|
|
2505
2414
|
} catch (e) {
|
|
2506
2415
|
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;
|
|
2507
2416
|
}
|
|
2508
2417
|
} else {
|
|
2509
|
-
let e =
|
|
2418
|
+
let e = c[0], [t, n] = await Promise.all([N(e, "/_voltro/inspect/manifest"), N(e, "/_voltro/inspect/data/tables")]);
|
|
2510
2419
|
if (t.kind === "err") return console.error(`voltro check: manifest fetch failed: ${t.error}`), 1;
|
|
2511
|
-
|
|
2420
|
+
l = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
|
|
2512
2421
|
}
|
|
2513
|
-
let f =
|
|
2422
|
+
let f = er(l, d), p = ie(f, a), m = [...V(o).findings.map((e) => ({
|
|
2514
2423
|
rule: e.rule,
|
|
2515
2424
|
node: e.file,
|
|
2516
2425
|
file: e.file,
|
|
2517
2426
|
breaks: [e.detail],
|
|
2518
2427
|
fix: e.fix,
|
|
2519
2428
|
severity: e.severity
|
|
2520
|
-
})), ...J(
|
|
2429
|
+
})), ...J(o).map((e) => ({
|
|
2521
2430
|
rule: `page/${e.kind}`,
|
|
2522
2431
|
node: e.file,
|
|
2523
2432
|
file: e.file,
|
|
2524
2433
|
breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
|
|
2525
2434
|
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",
|
|
2526
2435
|
severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
|
|
2527
|
-
}))], h = (await o
|
|
2436
|
+
}))], h = (await u(o, Xn(o))).map((e) => ({
|
|
2528
2437
|
rule: "client/not-browser-safe",
|
|
2529
2438
|
node: e.file,
|
|
2530
2439
|
file: e.file,
|
|
@@ -2535,7 +2444,7 @@ var re = () => {
|
|
|
2535
2444
|
...p.diagnostics,
|
|
2536
2445
|
...m,
|
|
2537
2446
|
...h
|
|
2538
|
-
], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await
|
|
2447
|
+
], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await Qn(C(o)), y = v === null ? null : Yn(f.procedures, v);
|
|
2539
2448
|
return t.format === "json" ? M(y === null ? {
|
|
2540
2449
|
...p,
|
|
2541
2450
|
ok: _,
|
|
@@ -2545,7 +2454,7 @@ var re = () => {
|
|
|
2545
2454
|
ok: _,
|
|
2546
2455
|
diagnostics: g,
|
|
2547
2456
|
observed: y
|
|
2548
|
-
}) : (console.log(
|
|
2457
|
+
}) : (console.log(tr(g)), y !== null && console.log($n(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
|
|
2549
2458
|
};
|
|
2550
2459
|
//#endregion
|
|
2551
|
-
export {
|
|
2460
|
+
export { ce as _, It as a, xe as c, N as d, me as f, pe as g, fe as h, Kn as i, I as l, P as m, rr as n, ze as o, he as p, er as r, be as s, nr as t, ve as u, M as v, le as y };
|