@voltro/cli 0.30.2 → 0.32.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 +278 -0
- package/dist/{apiBuild-BUS1RfPi.js → apiBuild-C4uEbs3s.js} +2 -2
- package/dist/apiBuild-OJEjtwcn.js +2 -0
- package/dist/appGraph-CvQCte0z.js +2 -0
- package/dist/appGraph-gQ_6GkQQ.js +110 -0
- package/dist/bin.js +3 -3
- package/dist/checkCommand-CwMrzAgV.js +2 -0
- package/dist/{checkCommand-D_wdOFhw.js → checkCommand-dm7OHtPt.js} +401 -505
- package/dist/{commands-BgGAUcku.js → commands-C0nEePif.js} +2601 -2498
- package/dist/dbCommand-By__Ev0R.js +2 -0
- package/dist/{dbCommand-CbPRCD1x.js → dbCommand-ifOMafuG.js} +112 -84
- package/dist/{dev-GAOIot9G.js → dev-5ficNnvF.js} +1843 -1832
- package/dist/{dev-tPcOmjPJ.js → dev-rc3fwPSZ.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-BM1vrsga.js → inspectMetrics-BqO4E9G0.js} +1008 -719
- package/dist/{sdkgen-BV4fFucu.js → sdkgen-B_5mHQS2.js} +1 -1
- package/dist/{serveCommand-ClqXCX1y.js → serveCommand-PxMmn96o.js} +454 -454
- package/dist/serveEntry.js +2 -2
- package/dist/{start-CTnNvKlk.js → start-D1-8eKrO.js} +204 -199
- package/dist/startEntry.js +2 -2
- package/package.json +417 -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/database/migrations.md +14 -1
- package/templates/agent-docs/internationalization.md +29 -0
- 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 +348 -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 +21 -1
- package/templates/agent-docs/whats-new.md +137 -22
- 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-DMlxvL8h.js +0 -2
- package/dist/checkCommand-CARZ36Tr.js +0 -2
- package/dist/dbCommand-Ht47LrV-.js +0 -2
|
@@ -1,30 +1,32 @@
|
|
|
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-BqO4E9G0.js";
|
|
2
|
+
import { Ht as l, Ut as u } from "./dev-5ficNnvF.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-C4uEbs3s.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 { pathToFileURL as oe } from "node:url";
|
|
8
|
+
import { routeDiagnosticsToStderr as se } from "@voltro/logger";
|
|
9
|
+
import { existsSync as T, readFileSync as E, readdirSync as D, statSync as O, writeFileSync as ce } from "node:fs";
|
|
8
10
|
import { Node as k, Project as A, SyntaxKind as j } from "ts-morph";
|
|
9
11
|
//#region src/cliOutput.ts
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
+
var le = () => {
|
|
13
|
+
se();
|
|
12
14
|
}, M = (e, t = {}) => {
|
|
13
15
|
console.log(JSON.stringify(e, null, t.pretty === !1 ? 0 : 2));
|
|
14
|
-
},
|
|
16
|
+
}, ue = (e) => {
|
|
15
17
|
for (let t = 0; t < e.length; t++) {
|
|
16
18
|
let n = e[t];
|
|
17
19
|
if (n === "--json" || n === "--format=json" || n === "--format" && e[t + 1] === "json") return !0;
|
|
18
20
|
}
|
|
19
21
|
return !1;
|
|
20
|
-
},
|
|
22
|
+
}, de = (e, t, n = !1) => {
|
|
21
23
|
let r = { ...e ?? {} }, i = t ?? process.env.VOLTRO_INSPECT_TOKEN;
|
|
22
24
|
if (i !== void 0 && i !== "" && (r.authorization = `Bearer ${i}`), n) {
|
|
23
25
|
let e = process.env.VOLTRO_INSPECT_WRITE_TOKEN;
|
|
24
26
|
e !== void 0 && e !== "" && (r["x-voltro-inspect-write"] = e);
|
|
25
27
|
}
|
|
26
28
|
return r;
|
|
27
|
-
},
|
|
29
|
+
}, fe = /* @__PURE__ */ new Set([
|
|
28
30
|
"POST",
|
|
29
31
|
"PUT",
|
|
30
32
|
"PATCH",
|
|
@@ -34,7 +36,7 @@ var re = () => {
|
|
|
34
36
|
try {
|
|
35
37
|
let t = await fetch(r, {
|
|
36
38
|
method: i,
|
|
37
|
-
headers:
|
|
39
|
+
headers: de(a ? { "content-type": "application/json" } : void 0, e.inspectToken, fe.has(i)),
|
|
38
40
|
...a ? { body: JSON.stringify(n.body) } : {}
|
|
39
41
|
}), o = await t.text(), s;
|
|
40
42
|
if (o.length > 0) try {
|
|
@@ -43,7 +45,7 @@ var re = () => {
|
|
|
43
45
|
s = o;
|
|
44
46
|
}
|
|
45
47
|
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 =
|
|
48
|
+
let n = s, r = typeof n == "object" && n ? String(n.message ?? n.reason ?? n.error ?? `HTTP ${t.status}`) : `HTTP ${t.status}`, a = fe.has(i) && (process.env.VOLTRO_INSPECT_WRITE_TOKEN ?? "") === "" && /write/i.test(r);
|
|
47
49
|
return {
|
|
48
50
|
kind: "err",
|
|
49
51
|
name: e.name,
|
|
@@ -63,7 +65,7 @@ var re = () => {
|
|
|
63
65
|
error: t.message || String(t)
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
|
-
},
|
|
68
|
+
}, P = (e, t = process.env) => {
|
|
67
69
|
let n = [], r, i;
|
|
68
70
|
for (let t = 0; t < e.length; t++) {
|
|
69
71
|
let a = e[t];
|
|
@@ -108,7 +110,7 @@ var re = () => {
|
|
|
108
110
|
},
|
|
109
111
|
rest: n
|
|
110
112
|
};
|
|
111
|
-
},
|
|
113
|
+
}, pe = (e) => ({
|
|
112
114
|
id: `remote:${e.url}`,
|
|
113
115
|
name: e.name,
|
|
114
116
|
kind: "api",
|
|
@@ -119,7 +121,7 @@ var re = () => {
|
|
|
119
121
|
status: "live",
|
|
120
122
|
logFile: "",
|
|
121
123
|
...e.token === void 0 ? {} : { inspectToken: e.token }
|
|
122
|
-
}),
|
|
124
|
+
}), me = 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)) : [pe(n)], he = (e) => {
|
|
123
125
|
let t = "pretty", n = [];
|
|
124
126
|
for (let r = 0; r < e.length; r++) {
|
|
125
127
|
let i = e[r];
|
|
@@ -143,8 +145,8 @@ var re = () => {
|
|
|
143
145
|
format: t,
|
|
144
146
|
rest: n
|
|
145
147
|
};
|
|
146
|
-
},
|
|
147
|
-
let { target: n, rest: r, error: i } =
|
|
148
|
+
}, ge = (e, t = process.env) => {
|
|
149
|
+
let { target: n, rest: r, error: i } = P(e, t), a, o = [];
|
|
148
150
|
for (let e = 0; e < r.length; e++) {
|
|
149
151
|
let t = r[e];
|
|
150
152
|
if (t === "--process") {
|
|
@@ -163,113 +165,7 @@ var re = () => {
|
|
|
163
165
|
rest: o,
|
|
164
166
|
...i === void 0 ? {} : { error: i }
|
|
165
167
|
};
|
|
166
|
-
},
|
|
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
|
-
}, ge = (e) => {
|
|
168
|
+
}, _e = (e) => {
|
|
273
169
|
let t = "", n = !1;
|
|
274
170
|
for (let r = 0; r < e.length; r++) {
|
|
275
171
|
let i = e[r];
|
|
@@ -297,8 +193,8 @@ var re = () => {
|
|
|
297
193
|
}
|
|
298
194
|
t += i;
|
|
299
195
|
}
|
|
300
|
-
return JSON.parse(
|
|
301
|
-
},
|
|
196
|
+
return JSON.parse(ve(t));
|
|
197
|
+
}, ve = (e) => {
|
|
302
198
|
let t = "", n = !1;
|
|
303
199
|
for (let r = 0; r < e.length; r++) {
|
|
304
200
|
let i = e[r];
|
|
@@ -326,7 +222,7 @@ var re = () => {
|
|
|
326
222
|
t.add(e);
|
|
327
223
|
let n;
|
|
328
224
|
try {
|
|
329
|
-
n =
|
|
225
|
+
n = _e(E(e, "utf8"));
|
|
330
226
|
} catch {
|
|
331
227
|
return;
|
|
332
228
|
}
|
|
@@ -342,8 +238,8 @@ var re = () => {
|
|
|
342
238
|
...n.compilerOptions?.paths
|
|
343
239
|
}
|
|
344
240
|
} };
|
|
345
|
-
},
|
|
346
|
-
let n =
|
|
241
|
+
}, ye = (e, t = "tsconfig.json") => {
|
|
242
|
+
let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
|
|
347
243
|
if (i === void 0) return;
|
|
348
244
|
let a = {};
|
|
349
245
|
for (let [e, t] of Object.entries(i)) Array.isArray(t) && (a[e] = t.filter((e) => typeof e == "string"));
|
|
@@ -352,7 +248,7 @@ var re = () => {
|
|
|
352
248
|
paths: a
|
|
353
249
|
};
|
|
354
250
|
}, I = (e, t = "tsconfig.json") => {
|
|
355
|
-
let n =
|
|
251
|
+
let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
|
|
356
252
|
if (i === void 0) return [];
|
|
357
253
|
let a = C(b(n), r?.compilerOptions?.baseUrl ?? "."), o = [];
|
|
358
254
|
for (let [e, t] of Object.entries(i)) {
|
|
@@ -366,11 +262,11 @@ var re = () => {
|
|
|
366
262
|
});
|
|
367
263
|
}
|
|
368
264
|
return o.sort((e, t) => t.prefix.length - e.prefix.length);
|
|
369
|
-
},
|
|
265
|
+
}, be = (e, t) => {
|
|
370
266
|
for (let n of t) if (n.wildcard) {
|
|
371
267
|
if (e.startsWith(n.prefix)) return x(n.target, e.slice(n.prefix.length));
|
|
372
268
|
} else if (e === n.prefix) return n.target;
|
|
373
|
-
}, L = ".voltro-vendored",
|
|
269
|
+
}, L = ".voltro-vendored", xe = (e, t) => {
|
|
374
270
|
let n = [], r = /* @__PURE__ */ new Set(), i = (e, t) => {
|
|
375
271
|
let i = C(e);
|
|
376
272
|
r.has(i) || !T(i) || (r.add(i), n.push({
|
|
@@ -401,48 +297,48 @@ var re = () => {
|
|
|
401
297
|
}
|
|
402
298
|
}
|
|
403
299
|
return n;
|
|
404
|
-
},
|
|
300
|
+
}, Se = (e, t) => t.some((t) => e === t.dir || e.startsWith(`${t.dir}${w}`)), Ce = (e, t) => t.map((t) => `${S(e, t.dir) || "."} (${t.source})`), we = [
|
|
405
301
|
{
|
|
406
302
|
id: "component.ui",
|
|
407
|
-
re:
|
|
303
|
+
re: _,
|
|
408
304
|
needsTest: !0
|
|
409
305
|
},
|
|
410
306
|
{
|
|
411
307
|
id: "component",
|
|
412
|
-
re:
|
|
308
|
+
re: ee,
|
|
413
309
|
needsTest: !0
|
|
414
310
|
},
|
|
415
311
|
{
|
|
416
312
|
id: "hook",
|
|
417
|
-
re:
|
|
313
|
+
re: te,
|
|
418
314
|
needsTest: !0
|
|
419
315
|
},
|
|
420
316
|
{
|
|
421
317
|
id: "tracking",
|
|
422
|
-
re:
|
|
318
|
+
re: f,
|
|
423
319
|
needsTest: !0
|
|
424
320
|
},
|
|
425
321
|
{
|
|
426
322
|
id: "store",
|
|
427
|
-
re:
|
|
323
|
+
re: d,
|
|
428
324
|
needsTest: !0
|
|
429
325
|
},
|
|
430
326
|
{
|
|
431
327
|
id: "internal",
|
|
432
|
-
re:
|
|
328
|
+
re: h,
|
|
433
329
|
needsTest: !0
|
|
434
330
|
},
|
|
435
331
|
{
|
|
436
332
|
id: "fixture",
|
|
437
|
-
re:
|
|
333
|
+
re: v,
|
|
438
334
|
needsTest: !1
|
|
439
335
|
},
|
|
440
336
|
{
|
|
441
337
|
id: "types",
|
|
442
|
-
re:
|
|
338
|
+
re: p,
|
|
443
339
|
needsTest: !1
|
|
444
340
|
}
|
|
445
|
-
], R = (e) =>
|
|
341
|
+
], R = (e) => we.find((t) => t.re.test(e))?.id ?? null, Te = (e) => m.test(e), Ee = /* @__PURE__ */ new Set([
|
|
446
342
|
"useMutation",
|
|
447
343
|
"useAction",
|
|
448
344
|
"useUpload",
|
|
@@ -452,12 +348,12 @@ var re = () => {
|
|
|
452
348
|
"useAgentStream",
|
|
453
349
|
"useResumableAgentStream",
|
|
454
350
|
"useWorkflow"
|
|
455
|
-
]),
|
|
351
|
+
]), De = /* @__PURE__ */ new Set(["defineTracking"]), Oe = /* @__PURE__ */ new Set([
|
|
456
352
|
"useSubscription",
|
|
457
353
|
"useWindowedSubscription",
|
|
458
354
|
"useRecord",
|
|
459
355
|
"useQuery"
|
|
460
|
-
]),
|
|
356
|
+
]), ke = /* @__PURE__ */ new Set([
|
|
461
357
|
"node_modules",
|
|
462
358
|
"dist",
|
|
463
359
|
"build",
|
|
@@ -468,7 +364,7 @@ var re = () => {
|
|
|
468
364
|
".git",
|
|
469
365
|
"coverage",
|
|
470
366
|
".publish"
|
|
471
|
-
]),
|
|
367
|
+
]), Ae = /\.tsx?$/, je = (e, t = [], n = 0) => {
|
|
472
368
|
if (n > 12) return t;
|
|
473
369
|
let r;
|
|
474
370
|
try {
|
|
@@ -477,27 +373,27 @@ var re = () => {
|
|
|
477
373
|
return t;
|
|
478
374
|
}
|
|
479
375
|
for (let i of r) {
|
|
480
|
-
if (
|
|
376
|
+
if (ke.has(i)) continue;
|
|
481
377
|
let r = x(e, i), a;
|
|
482
378
|
try {
|
|
483
379
|
a = O(r);
|
|
484
380
|
} catch {
|
|
485
381
|
continue;
|
|
486
382
|
}
|
|
487
|
-
a.isDirectory() ?
|
|
383
|
+
a.isDirectory() ? je(r, t, n + 1) : a.isFile() && Ae.test(i) && !i.endsWith(".d.ts") && t.push(r);
|
|
488
384
|
}
|
|
489
385
|
return t;
|
|
490
|
-
},
|
|
491
|
-
if (
|
|
492
|
-
if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return
|
|
386
|
+
}, Me = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, Ne = /\b(?:memo|forwardRef|observer|styled)\b/, Pe = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), z = (e) => {
|
|
387
|
+
if (Pe(e)) return !0;
|
|
388
|
+
if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return z(e.getExpression());
|
|
493
389
|
if (k.isVariableDeclaration(e)) {
|
|
494
390
|
let t = e.getTypeNode();
|
|
495
|
-
if (t !== void 0 &&
|
|
391
|
+
if (t !== void 0 && Me.test(t.getText())) return !0;
|
|
496
392
|
let n = e.getInitializer();
|
|
497
|
-
return n === void 0 ||
|
|
393
|
+
return n === void 0 || z(n);
|
|
498
394
|
}
|
|
499
|
-
return k.isCallExpression(e) ?
|
|
500
|
-
},
|
|
395
|
+
return k.isCallExpression(e) ? Ne.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => Pe(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);
|
|
396
|
+
}, Fe = (e) => {
|
|
501
397
|
let t = [], n = [], r = [], i = /* @__PURE__ */ new Set();
|
|
502
398
|
for (let [a, o] of e.getExportedDeclarations()) {
|
|
503
399
|
let e = o[0];
|
|
@@ -508,7 +404,7 @@ var re = () => {
|
|
|
508
404
|
n.push(s);
|
|
509
405
|
continue;
|
|
510
406
|
}
|
|
511
|
-
if (/^[A-Z]/.test(s) &&
|
|
407
|
+
if (/^[A-Z]/.test(s) && z(e)) {
|
|
512
408
|
t.push(s);
|
|
513
409
|
continue;
|
|
514
410
|
}
|
|
@@ -520,7 +416,7 @@ var re = () => {
|
|
|
520
416
|
others: r,
|
|
521
417
|
hasRuntimeExport: t.length + n.length + r.length > 0
|
|
522
418
|
};
|
|
523
|
-
},
|
|
419
|
+
}, B = [], Ie = (e, t, n) => {
|
|
524
420
|
let r = b(e), i = [];
|
|
525
421
|
for (let e = r;; e = b(e)) {
|
|
526
422
|
let r = n.get(e);
|
|
@@ -537,10 +433,10 @@ var re = () => {
|
|
|
537
433
|
}
|
|
538
434
|
if (e === t || b(e) === e) break;
|
|
539
435
|
}
|
|
540
|
-
for (let e of i) n.set(e,
|
|
541
|
-
return
|
|
542
|
-
},
|
|
543
|
-
let r = t.startsWith(".") ? C(b(e), t) :
|
|
436
|
+
for (let e of i) n.set(e, B);
|
|
437
|
+
return B;
|
|
438
|
+
}, Le = (e, t, n) => {
|
|
439
|
+
let r = t.startsWith(".") ? C(b(e), t) : be(t, n);
|
|
544
440
|
if (r === void 0) return null;
|
|
545
441
|
for (let e of [
|
|
546
442
|
r,
|
|
@@ -552,9 +448,9 @@ var re = () => {
|
|
|
552
448
|
if (O(e).isFile()) return e;
|
|
553
449
|
} catch {}
|
|
554
450
|
return null;
|
|
555
|
-
},
|
|
451
|
+
}, Re = (e, t, n) => {
|
|
556
452
|
let r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = (e) => {
|
|
557
|
-
let i =
|
|
453
|
+
let i = Le(t, e, n);
|
|
558
454
|
return i !== null && r.push(i), i;
|
|
559
455
|
};
|
|
560
456
|
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,29 +477,29 @@ var re = () => {
|
|
|
581
477
|
namedBindings: i,
|
|
582
478
|
reExports: a
|
|
583
479
|
};
|
|
584
|
-
},
|
|
480
|
+
}, ze = (e) => {
|
|
585
481
|
let t = /* @__PURE__ */ new Map();
|
|
586
482
|
for (let n of e.getDescendantsOfKind(j.CallExpression)) {
|
|
587
483
|
let e = n.getExpression(), r = k.isPropertyAccessExpression(e) ? e.getName() : e.getText();
|
|
588
484
|
t.set(r, (t.get(r) ?? 0) + 1);
|
|
589
485
|
}
|
|
590
486
|
return t;
|
|
591
|
-
},
|
|
487
|
+
}, Be = (e) => {
|
|
592
488
|
if (e.length <= 1) return e.length === 1;
|
|
593
489
|
let t = [...e].sort((e, t) => e.length - t.length)[0];
|
|
594
490
|
return e.every((e) => e.startsWith(t));
|
|
595
|
-
},
|
|
491
|
+
}, Ve = (e, t) => {
|
|
596
492
|
let n = b(t);
|
|
597
493
|
return e.startsWith(`${n}${w}`) || b(e) === n;
|
|
598
494
|
}, V = (e) => {
|
|
599
|
-
let t =
|
|
495
|
+
let t = je(e);
|
|
600
496
|
if (t.length === 0) return {
|
|
601
497
|
findings: [],
|
|
602
498
|
declared: 0,
|
|
603
499
|
scanned: 0,
|
|
604
500
|
vendored: []
|
|
605
501
|
};
|
|
606
|
-
let n =
|
|
502
|
+
let n = xe(e, t), r = t.filter((e) => !Se(e, n));
|
|
607
503
|
if (r.length === 0) return {
|
|
608
504
|
findings: [],
|
|
609
505
|
declared: 0,
|
|
@@ -627,14 +523,14 @@ var re = () => {
|
|
|
627
523
|
} catch {
|
|
628
524
|
continue;
|
|
629
525
|
}
|
|
630
|
-
s.set(t,
|
|
526
|
+
s.set(t, Fe(r)), c.set(t, Re(r, t, Ie(t, e, u))), l.set(t, ze(r));
|
|
631
527
|
}
|
|
632
528
|
let d = /* @__PURE__ */ new Map();
|
|
633
529
|
for (let [e, t] of c) for (let n of t.localTargets) {
|
|
634
530
|
let t = d.get(n) ?? [];
|
|
635
531
|
t.push(e), d.set(n, t);
|
|
636
532
|
}
|
|
637
|
-
let
|
|
533
|
+
let f = (e) => {
|
|
638
534
|
let t = s.get(e), n = /* @__PURE__ */ new Set([
|
|
639
535
|
...t?.components ?? [],
|
|
640
536
|
...t?.hooks ?? [],
|
|
@@ -655,15 +551,15 @@ var re = () => {
|
|
|
655
551
|
}
|
|
656
552
|
};
|
|
657
553
|
return a(e, null), [...r];
|
|
658
|
-
},
|
|
554
|
+
}, p = 0;
|
|
659
555
|
for (let e of r) {
|
|
660
556
|
let t = R(e), n = s.get(e), r = c.get(e);
|
|
661
557
|
if (n === void 0 || r === void 0) continue;
|
|
662
|
-
let i =
|
|
663
|
-
t !== null && !i && (
|
|
558
|
+
let i = Te(e);
|
|
559
|
+
t !== null && !i && (p += 1);
|
|
664
560
|
let u = d.get(e) ?? [];
|
|
665
561
|
if ((t === "component" || t === "component.ui") && !i) {
|
|
666
|
-
let t =
|
|
562
|
+
let t = Be(n.components);
|
|
667
563
|
(n.components.length === 0 || !t) && a.push({
|
|
668
564
|
rule: "component/one-per-file",
|
|
669
565
|
file: o(e),
|
|
@@ -701,7 +597,7 @@ var re = () => {
|
|
|
701
597
|
].join(", ")}`,
|
|
702
598
|
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
599
|
}), t === "internal") {
|
|
704
|
-
let t = u.filter((t) => !
|
|
600
|
+
let t = u.filter((t) => !Ve(t, e));
|
|
705
601
|
t.length > 0 && a.push({
|
|
706
602
|
rule: "internal/foreign-import",
|
|
707
603
|
file: o(e),
|
|
@@ -711,7 +607,7 @@ var re = () => {
|
|
|
711
607
|
});
|
|
712
608
|
}
|
|
713
609
|
if (t === "fixture") {
|
|
714
|
-
let t = u.filter((e) => !
|
|
610
|
+
let t = u.filter((e) => !Te(e) && R(e) !== "fixture");
|
|
715
611
|
t.length > 0 && a.push({
|
|
716
612
|
rule: "fixture/production-import",
|
|
717
613
|
file: o(e),
|
|
@@ -721,7 +617,7 @@ var re = () => {
|
|
|
721
617
|
});
|
|
722
618
|
}
|
|
723
619
|
if (t === "component.ui" && !i) {
|
|
724
|
-
let t = [...r.namedBindings].filter((e) =>
|
|
620
|
+
let t = [...r.namedBindings].filter((e) => Ee.has(e)).sort();
|
|
725
621
|
t.length > 0 && a.push({
|
|
726
622
|
rule: "ui/no-write",
|
|
727
623
|
file: o(e),
|
|
@@ -729,9 +625,9 @@ var re = () => {
|
|
|
729
625
|
detail: `imports write hook(s): ${t.join(", ")}`,
|
|
730
626
|
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
627
|
});
|
|
732
|
-
let n =
|
|
628
|
+
let n = f(e), i = n.filter((e) => {
|
|
733
629
|
let t = R(e);
|
|
734
|
-
return t === "component" || t === "component.ui" ||
|
|
630
|
+
return t === "component" || t === "component.ui" || y.test(e) || g.has(e.slice(e.lastIndexOf(w) + 1));
|
|
735
631
|
});
|
|
736
632
|
n.length === 0 ? a.push({
|
|
737
633
|
rule: "ui/orphaned",
|
|
@@ -756,7 +652,7 @@ var re = () => {
|
|
|
756
652
|
detail: t === 0 ? "declares a store file but calls no defineStore()" : `declares ${t} stores`,
|
|
757
653
|
fix: "a store file defines exactly ONE store — the SSR seed pass and devtools address stores by name, so two in a file makes both harder to find and neither easier to reason about"
|
|
758
654
|
});
|
|
759
|
-
let n = [...l.get(e)?.keys() ?? []].filter((e) =>
|
|
655
|
+
let n = [...l.get(e)?.keys() ?? []].filter((e) => Oe.has(e)).sort();
|
|
760
656
|
n.length > 0 && a.push({
|
|
761
657
|
rule: "store/mirrors-server-state",
|
|
762
658
|
file: o(e),
|
|
@@ -766,7 +662,7 @@ var re = () => {
|
|
|
766
662
|
});
|
|
767
663
|
}
|
|
768
664
|
if (t !== "tracking" && !i) {
|
|
769
|
-
let t = [...l.get(e)?.keys() ?? []].filter((e) =>
|
|
665
|
+
let t = [...l.get(e)?.keys() ?? []].filter((e) => De.has(e)).sort();
|
|
770
666
|
t.length > 0 && a.push({
|
|
771
667
|
rule: "tracking/outside-tracking-file",
|
|
772
668
|
file: o(e),
|
|
@@ -775,7 +671,7 @@ var re = () => {
|
|
|
775
671
|
fix: "move the defineTracking(...) spec into a *.tracking.ts and import it here. The event catalogue — names, property bags, the decisions about what personal data goes out — belongs in one place you can list; calling useTracking with a spec from there is fine"
|
|
776
672
|
});
|
|
777
673
|
}
|
|
778
|
-
if (
|
|
674
|
+
if (we.find((e) => e.id === t)?.needsTest === !0 && !i) {
|
|
779
675
|
let t = e.replace(/\.tsx?$/, ""), n = e.endsWith("x") ? "tsx" : "ts";
|
|
780
676
|
["test", "spec"].flatMap((e) => [`${t}.${e}.ts`, `${t}.${e}.tsx`]).some((e) => {
|
|
781
677
|
try {
|
|
@@ -794,14 +690,14 @@ var re = () => {
|
|
|
794
690
|
}
|
|
795
691
|
return a.sort((e, t) => e.file === t.file ? e.rule.localeCompare(t.rule) : e.file.localeCompare(t.file)), {
|
|
796
692
|
findings: a,
|
|
797
|
-
declared:
|
|
693
|
+
declared: p,
|
|
798
694
|
scanned: r.length,
|
|
799
695
|
vendored: n
|
|
800
696
|
};
|
|
801
|
-
},
|
|
697
|
+
}, He = 10, Ue = (e) => {
|
|
802
698
|
let { findings: t, declared: n, scanned: r, vendored: i } = V(e);
|
|
803
699
|
console.log("");
|
|
804
|
-
for (let t of
|
|
700
|
+
for (let t of Ce(e, i)) console.log(`· not ours, exempt: ${t}`);
|
|
805
701
|
if (r === 0) return console.log("• file-taxonomy scan: no sources here — nothing to check."), 0;
|
|
806
702
|
if (t.length === 0) return console.log(`✓ file-taxonomy scan: ${n} declared file(s) keep their contract (${r} scanned).`), 0;
|
|
807
703
|
let a = /* @__PURE__ */ new Map();
|
|
@@ -813,12 +709,12 @@ var re = () => {
|
|
|
813
709
|
console.log(`• file taxonomy: ${t.length} finding(s) — ${o} error, ${t.length - o} advisory (${n} declared / ${r} scanned):`);
|
|
814
710
|
for (let [e, t] of [...a].sort()) {
|
|
815
711
|
console.log(` ${t[0].severity === "error" ? "✗" : "·"} ${e} (${t.length})`);
|
|
816
|
-
for (let e of t.slice(0,
|
|
817
|
-
let n = t.length -
|
|
712
|
+
for (let e of t.slice(0, He)) console.log(` ${e.file} — ${e.detail}`);
|
|
713
|
+
let n = t.length - He;
|
|
818
714
|
n > 0 && console.log(` (+${n} more — \`voltro doctor --json\` prints every one.)`), console.log(` → ${t[0].fix}`);
|
|
819
715
|
}
|
|
820
716
|
return t.length;
|
|
821
|
-
},
|
|
717
|
+
}, We = /* @__PURE__ */ new Set([
|
|
822
718
|
"eq",
|
|
823
719
|
"ne",
|
|
824
720
|
"gt",
|
|
@@ -836,10 +732,10 @@ var re = () => {
|
|
|
836
732
|
"endsWith",
|
|
837
733
|
"between",
|
|
838
734
|
"jsonField"
|
|
839
|
-
]),
|
|
735
|
+
]), Ge = (e) => {
|
|
840
736
|
if (k.isPropertyAccessExpression(e)) return e.getName();
|
|
841
737
|
if (k.isIdentifier(e)) return e.getText();
|
|
842
|
-
},
|
|
738
|
+
}, Ke = (e) => {
|
|
843
739
|
let t = e;
|
|
844
740
|
for (; k.isCallExpression(t);) {
|
|
845
741
|
let e = t.getExpression();
|
|
@@ -847,7 +743,7 @@ var re = () => {
|
|
|
847
743
|
t = e.getExpression();
|
|
848
744
|
}
|
|
849
745
|
return t;
|
|
850
|
-
},
|
|
746
|
+
}, qe = (e, t, n) => {
|
|
851
747
|
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
748
|
useInMemoryFileSystem: !0,
|
|
853
749
|
skipAddingFilesFromTsConfig: !0
|
|
@@ -865,7 +761,7 @@ var re = () => {
|
|
|
865
761
|
for (let t of e.getDescendantsOfKind(j.CallExpression)) {
|
|
866
762
|
let e = t.getExpression();
|
|
867
763
|
if (!k.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
|
|
868
|
-
let n =
|
|
764
|
+
let n = Ge(Ke(e.getExpression()));
|
|
869
765
|
if (n === void 0 || !r.has(n)) {
|
|
870
766
|
let t = e.getExpression();
|
|
871
767
|
if (k.isCallExpression(t)) {
|
|
@@ -888,7 +784,7 @@ var re = () => {
|
|
|
888
784
|
else for (let e of [u, ...u.getDescendantsOfKind(j.CallExpression)]) {
|
|
889
785
|
if (!k.isCallExpression(e)) continue;
|
|
890
786
|
let t = e.getExpression(), n = k.isIdentifier(t) ? t.getText() : k.isPropertyAccessExpression(t) ? t.getName() : void 0;
|
|
891
|
-
if (n === void 0 || !
|
|
787
|
+
if (n === void 0 || !We.has(n)) continue;
|
|
892
788
|
let r = e.getArguments()[0];
|
|
893
789
|
r !== void 0 && k.isStringLiteral(r) && l.push({
|
|
894
790
|
column: r.getLiteralValue(),
|
|
@@ -909,7 +805,7 @@ var re = () => {
|
|
|
909
805
|
issues: a,
|
|
910
806
|
checked: o
|
|
911
807
|
};
|
|
912
|
-
}, H = 12,
|
|
808
|
+
}, H = 12, Je = (e) => {
|
|
913
809
|
if (e.checked === 0) return null;
|
|
914
810
|
if (e.issues.length === 0) return `predicate columns: ${e.checked} checked — every filtered column exists on its table`;
|
|
915
811
|
let t = [`predicate columns: ${e.issues.length} filter${e.issues.length, "s"} on a column that does not exist (${e.checked} checked)`];
|
|
@@ -918,7 +814,7 @@ var re = () => {
|
|
|
918
814
|
t.push(` ${n.file}:${n.line} '${n.table}' has no column '${n.column}'`), t.push(` columns: ${e}${r}`);
|
|
919
815
|
}
|
|
920
816
|
return t.push(" This type-checks today and fails at runtime as a bare SQL error."), t.join("\n");
|
|
921
|
-
},
|
|
817
|
+
}, Ye = /* @__PURE__ */ new Set([
|
|
922
818
|
"id",
|
|
923
819
|
"tenantId",
|
|
924
820
|
"tenant_id",
|
|
@@ -938,7 +834,7 @@ var re = () => {
|
|
|
938
834
|
"idempotency_id",
|
|
939
835
|
"idempotencyKey",
|
|
940
836
|
"idempotency_key"
|
|
941
|
-
]),
|
|
837
|
+
]), Xe = /* @__PURE__ */ new Set([
|
|
942
838
|
"id",
|
|
943
839
|
"createdAt",
|
|
944
840
|
"created_at",
|
|
@@ -948,10 +844,10 @@ var re = () => {
|
|
|
948
844
|
"deleted_at",
|
|
949
845
|
"tenantId",
|
|
950
846
|
"tenant_id"
|
|
951
|
-
]),
|
|
847
|
+
]), Ze = (e) => Ye.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Qe = (e) => e.type === "reference", $e = (e) => {
|
|
952
848
|
let t = [];
|
|
953
849
|
for (let n of e) {
|
|
954
|
-
let e = n.columns.filter((e) =>
|
|
850
|
+
let e = n.columns.filter((e) => Ze(e.name)), r = e.filter((e) => !Qe(e)).map((e) => e.name);
|
|
955
851
|
if (r.length === 0) continue;
|
|
956
852
|
let i = new Set(n.primaryKey), a = r.filter((e) => i.has(e));
|
|
957
853
|
if (n.primaryKey.length >= 2 && a.length > 0) {
|
|
@@ -962,7 +858,7 @@ var re = () => {
|
|
|
962
858
|
});
|
|
963
859
|
continue;
|
|
964
860
|
}
|
|
965
|
-
if (e.some(
|
|
861
|
+
if (e.some(Qe) && e.length >= 2) {
|
|
966
862
|
t.push({
|
|
967
863
|
table: n.name,
|
|
968
864
|
columns: r,
|
|
@@ -970,7 +866,7 @@ var re = () => {
|
|
|
970
866
|
});
|
|
971
867
|
continue;
|
|
972
868
|
}
|
|
973
|
-
e.length >= 2 && n.columns.filter((e) => !
|
|
869
|
+
e.length >= 2 && n.columns.filter((e) => !Ze(e.name) && !Xe.has(e.name)).length === 0 && t.push({
|
|
974
870
|
table: n.name,
|
|
975
871
|
columns: r,
|
|
976
872
|
signal: "link-only"
|
|
@@ -980,20 +876,20 @@ var re = () => {
|
|
|
980
876
|
issues: t,
|
|
981
877
|
tablesChecked: e.length
|
|
982
878
|
};
|
|
983
|
-
},
|
|
879
|
+
}, et = {
|
|
984
880
|
"composite-pk": "part of a composite primary key",
|
|
985
881
|
"reference-sibling": "sits beside a wired reference() on this table",
|
|
986
882
|
"link-only": "this table is nothing but link columns"
|
|
987
|
-
},
|
|
883
|
+
}, tt = (e) => {
|
|
988
884
|
if (e.tablesChecked === 0) return null;
|
|
989
885
|
if (e.issues.length === 0) return `junction FKs: ${e.tablesChecked} table${e.tablesChecked === 1 ? "" : "s"} checked — every link column is a wired reference()`;
|
|
990
886
|
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
887
|
for (let t of e.issues) {
|
|
992
888
|
let e = t.columns.map((e) => `'${e}'`).join(", ");
|
|
993
|
-
n.push(` '${t.table}': ${e} (${
|
|
889
|
+
n.push(` '${t.table}': ${e} (${et[t.signal]})`);
|
|
994
890
|
}
|
|
995
891
|
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
|
-
},
|
|
892
|
+
}, nt = /* @__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(".")), rt = (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()), it = (e) => rt(e).some((e) => nt.has(e)), at = (e) => {
|
|
997
893
|
let t = [], n = [];
|
|
998
894
|
for (let r of e) t.push({
|
|
999
895
|
event: r.name,
|
|
@@ -1002,7 +898,7 @@ var re = () => {
|
|
|
1002
898
|
event: r.name,
|
|
1003
899
|
kind: "wide-key",
|
|
1004
900
|
keyFieldCount: r.keyFieldCount
|
|
1005
|
-
}), r.hasWebhook &&
|
|
901
|
+
}), r.hasWebhook && it(r.name) && n.push({
|
|
1006
902
|
event: r.name,
|
|
1007
903
|
kind: "webhook-high-rate"
|
|
1008
904
|
});
|
|
@@ -1011,26 +907,26 @@ var re = () => {
|
|
|
1011
907
|
modes: t,
|
|
1012
908
|
warnings: n
|
|
1013
909
|
};
|
|
1014
|
-
},
|
|
910
|
+
}, ot = (e) => {
|
|
1015
911
|
if (e.eventsChecked === 0) return null;
|
|
1016
912
|
let t = e.eventsChecked, n = [`event delivery: ${t} declared event${t === 1 ? "" : "s"} — the mode decides what a MISSING message means`];
|
|
1017
913
|
for (let t of e.modes) n.push(` '${t.event}': ${t.delivery}`);
|
|
1018
914
|
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
|
-
},
|
|
915
|
+
}, st = (e) => {
|
|
1020
916
|
if (e.warnings.length === 0) return null;
|
|
1021
917
|
let t = e.warnings.length, n = [`events that may not scale as declared: ${t} finding${t === 1 ? "" : "s"}`];
|
|
1022
918
|
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
919
|
return n.push(" WARNING, not a fault — a producer may intend the fan-out. `voltro doctor --json` prints every finding."), n.join("\n");
|
|
1024
|
-
},
|
|
920
|
+
}, ct = /* @__PURE__ */ new Set([
|
|
1025
921
|
"start",
|
|
1026
922
|
"run",
|
|
1027
923
|
"child"
|
|
1028
|
-
]),
|
|
924
|
+
]), lt = (e) => k.isCallExpression(e) && (() => {
|
|
1029
925
|
let t = e.getExpression();
|
|
1030
|
-
if (!k.isPropertyAccessExpression(t) || !
|
|
926
|
+
if (!k.isPropertyAccessExpression(t) || !ct.has(t.getName())) return !1;
|
|
1031
927
|
let n = t.getExpression();
|
|
1032
928
|
return k.isPropertyAccessExpression(n) ? n.getName() === "workflows" : k.isIdentifier(n) && n.getText() === "workflows";
|
|
1033
|
-
})(),
|
|
929
|
+
})(), ut = (e, t, n) => {
|
|
1034
930
|
let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new A({
|
|
1035
931
|
useInMemoryFileSystem: !0,
|
|
1036
932
|
skipAddingFilesFromTsConfig: !0
|
|
@@ -1046,7 +942,7 @@ var re = () => {
|
|
|
1046
942
|
}
|
|
1047
943
|
let u = S(n, t);
|
|
1048
944
|
for (let t of e.getDescendantsOfKind(j.CallExpression)) {
|
|
1049
|
-
if (!
|
|
945
|
+
if (!lt(t)) continue;
|
|
1050
946
|
c++;
|
|
1051
947
|
let e = t.getStartLineNumber(), [n, l] = t.getArguments();
|
|
1052
948
|
if (n === void 0 || !k.isStringLiteral(n)) {
|
|
@@ -1130,7 +1026,7 @@ var re = () => {
|
|
|
1130
1026
|
checked: s,
|
|
1131
1027
|
callSites: c
|
|
1132
1028
|
};
|
|
1133
|
-
},
|
|
1029
|
+
}, dt = (e) => {
|
|
1134
1030
|
if (e.callSites === 0) return null;
|
|
1135
1031
|
let t = [];
|
|
1136
1032
|
t.push(`workflow starts: ${e.checked}/${e.callSites} call sites checked`);
|
|
@@ -1143,7 +1039,7 @@ var re = () => {
|
|
|
1143
1039
|
for (let n of e.unchecked) t.push(` ${n.file}:${n.line} — ${n.reason}`);
|
|
1144
1040
|
}
|
|
1145
1041
|
return e.issues.length === 0 && e.unchecked.length === 0 && t.push(" every call site matches its workflow"), t.join("\n");
|
|
1146
|
-
},
|
|
1042
|
+
}, ft = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, U = "VOLTRO_FIELD_ENCRYPTION_KEY", pt = (e) => {
|
|
1147
1043
|
let t;
|
|
1148
1044
|
for (let [n, r] of e.sources) if (t === void 0 && /\bfieldEncryption\b/.test(r) && (t = {
|
|
1149
1045
|
reason: "fieldEncryption",
|
|
@@ -1156,7 +1052,7 @@ var re = () => {
|
|
|
1156
1052
|
for (let [, t] of e.envFiles) if (t.includes(U)) return;
|
|
1157
1053
|
return t;
|
|
1158
1054
|
}
|
|
1159
|
-
},
|
|
1055
|
+
}, mt = (e) => [
|
|
1160
1056
|
`✗ ${U} is named nowhere under this app root, and the app encrypts columns at rest`,
|
|
1161
1057
|
e.reason === "fieldEncryption" ? ` (\`fieldEncryption\` in ${e.file})` : ` (an \`.encrypted()\` column in ${e.file})`,
|
|
1162
1058
|
"",
|
|
@@ -1173,99 +1069,14 @@ var re = () => {
|
|
|
1173
1069
|
" reaches your deploy target is not knowable from here — a secret manager",
|
|
1174
1070
|
" or a CI variable is not a file. Naming it nowhere is conclusive; naming",
|
|
1175
1071
|
" 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
|
-
}, _t = (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
|
-
}, vt = (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(".") || ht.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
|
-
}, yt = (e) => {
|
|
1228
|
-
let t = /* @__PURE__ */ new Map();
|
|
1229
|
-
for (let n of gt(e)) for (let { pkg: e, dir: r } of vt(n)) {
|
|
1230
|
-
if (!mt(e)) continue;
|
|
1231
|
-
let n = _t(r);
|
|
1232
|
-
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
|
-
}, bt = (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
|
-
}, xt = (e) => {
|
|
1072
|
+
].join("\n"), ht = (e) => {
|
|
1262
1073
|
if (e.catalogCodes.length === 0) return;
|
|
1263
1074
|
let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
|
|
1264
1075
|
if (n.length !== 0) return {
|
|
1265
1076
|
orphaned: n,
|
|
1266
1077
|
noneDeclared: t.size === 0
|
|
1267
1078
|
};
|
|
1268
|
-
},
|
|
1079
|
+
}, gt = (e, t) => {
|
|
1269
1080
|
let n = e.orphaned.join(", ");
|
|
1270
1081
|
return e.noneDeclared ? [
|
|
1271
1082
|
`⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
|
|
@@ -1277,33 +1088,33 @@ var re = () => {
|
|
|
1277
1088
|
" …or delete the files. A catalog that is never loaded looks from the inside",
|
|
1278
1089
|
" exactly like one that works, which is why this is worth a line."
|
|
1279
1090
|
] : [`⚠ ${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
|
-
},
|
|
1091
|
+
}, _t = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), vt = /* @__PURE__ */ new Set([
|
|
1281
1092
|
"requireScope",
|
|
1282
1093
|
"requireAnyScope",
|
|
1283
1094
|
"requireAuth",
|
|
1284
1095
|
"assertScope",
|
|
1285
1096
|
"assertCan",
|
|
1286
1097
|
"can"
|
|
1287
|
-
]),
|
|
1098
|
+
]), yt = (e) => {
|
|
1288
1099
|
let t = /* @__PURE__ */ new Set();
|
|
1289
|
-
for (let n of e) for (let e of
|
|
1100
|
+
for (let n of e) for (let e of _t(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
|
|
1290
1101
|
return t;
|
|
1291
|
-
},
|
|
1102
|
+
}, bt = (e, t) => {
|
|
1292
1103
|
for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
|
|
1293
|
-
},
|
|
1104
|
+
}, xt = (e) => {
|
|
1294
1105
|
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
1106
|
return t && n;
|
|
1296
|
-
},
|
|
1107
|
+
}, St = (e, t, n) => {
|
|
1297
1108
|
if (e.hasDeclaredGuards) return { kind: "declared-guard" };
|
|
1298
|
-
let r =
|
|
1299
|
-
return i === void 0 ?
|
|
1109
|
+
let r = _t(e.src), i = bt(r, t);
|
|
1110
|
+
return i === void 0 ? xt(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
|
|
1300
1111
|
kind: "vocabulary-guard",
|
|
1301
1112
|
name: i
|
|
1302
1113
|
};
|
|
1303
|
-
},
|
|
1114
|
+
}, Ct = (e, t, n) => {
|
|
1304
1115
|
let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
|
|
1305
1116
|
return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
|
|
1306
|
-
},
|
|
1117
|
+
}, wt = (e, t, n) => {
|
|
1307
1118
|
switch (t.kind) {
|
|
1308
1119
|
case "declared-guard": return "guards: on the descriptor";
|
|
1309
1120
|
case "vocabulary-guard": return `calls ${t.name}()`;
|
|
@@ -1311,15 +1122,15 @@ var re = () => {
|
|
|
1311
1122
|
case "allowlisted": return "accepted by a previous run";
|
|
1312
1123
|
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
1124
|
}
|
|
1314
|
-
},
|
|
1125
|
+
}, Tt = (e) => {
|
|
1315
1126
|
let t = e.executors.map((t) => {
|
|
1316
|
-
let n =
|
|
1127
|
+
let n = St(t, e.guardVocabulary, e.allowlist);
|
|
1317
1128
|
return {
|
|
1318
1129
|
tag: t.tag,
|
|
1319
1130
|
path: t.path,
|
|
1320
1131
|
verdict: n,
|
|
1321
|
-
severity:
|
|
1322
|
-
why:
|
|
1132
|
+
severity: Ct(t, e.tenantScopedTables, e.referencedTables),
|
|
1133
|
+
why: wt(t, n, e.rowFilterRegistered === !0)
|
|
1323
1134
|
};
|
|
1324
1135
|
}), n = (e) => t.filter((t) => t.verdict.kind === e).length;
|
|
1325
1136
|
return {
|
|
@@ -1333,7 +1144,7 @@ var re = () => {
|
|
|
1333
1144
|
},
|
|
1334
1145
|
unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
|
|
1335
1146
|
};
|
|
1336
|
-
},
|
|
1147
|
+
}, Et = [
|
|
1337
1148
|
"# voltro doctor — authz allowlist",
|
|
1338
1149
|
"#",
|
|
1339
1150
|
"# Two KINDS of line, and the difference is the point:",
|
|
@@ -1358,18 +1169,18 @@ var re = () => {
|
|
|
1358
1169
|
"# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
|
|
1359
1170
|
"# lines for executors that are newly unchecked.",
|
|
1360
1171
|
""
|
|
1361
|
-
],
|
|
1172
|
+
], Dt = (e) => {
|
|
1362
1173
|
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
1174
|
return [
|
|
1364
|
-
...
|
|
1175
|
+
...Et,
|
|
1365
1176
|
...n,
|
|
1366
1177
|
...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
|
|
1367
1178
|
...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
|
|
1368
1179
|
""
|
|
1369
1180
|
].join("\n");
|
|
1370
|
-
},
|
|
1181
|
+
}, Ot = (e, t) => {
|
|
1371
1182
|
let n = [...new Set(t)].sort();
|
|
1372
|
-
if (e === void 0) return
|
|
1183
|
+
if (e === void 0) return Dt(n.map((e) => ({ tag: e })));
|
|
1373
1184
|
let r = W(e).tags, i = n.filter((e) => !r.has(e));
|
|
1374
1185
|
return i.length === 0 ? e : [
|
|
1375
1186
|
e.replace(/\n+$/, ""),
|
|
@@ -1379,7 +1190,7 @@ var re = () => {
|
|
|
1379
1190
|
...i,
|
|
1380
1191
|
""
|
|
1381
1192
|
].join("\n");
|
|
1382
|
-
},
|
|
1193
|
+
}, kt = (e, t) => {
|
|
1383
1194
|
let n = [...new Set(t)];
|
|
1384
1195
|
if (e === void 0) return {
|
|
1385
1196
|
added: n.length,
|
|
@@ -1392,7 +1203,7 @@ var re = () => {
|
|
|
1392
1203
|
kept: r.size,
|
|
1393
1204
|
removed: 0
|
|
1394
1205
|
};
|
|
1395
|
-
},
|
|
1206
|
+
}, At = class extends Error {
|
|
1396
1207
|
tag;
|
|
1397
1208
|
constructor(e) {
|
|
1398
1209
|
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";
|
|
@@ -1410,7 +1221,7 @@ var re = () => {
|
|
|
1410
1221
|
continue;
|
|
1411
1222
|
}
|
|
1412
1223
|
let a = r[2].trim();
|
|
1413
|
-
if (a.length === 0) throw new
|
|
1224
|
+
if (a.length === 0) throw new At(i);
|
|
1414
1225
|
t.push({
|
|
1415
1226
|
tag: i,
|
|
1416
1227
|
reviewed: a
|
|
@@ -1420,13 +1231,13 @@ var re = () => {
|
|
|
1420
1231
|
tags: new Set(t.map((e) => e.tag)),
|
|
1421
1232
|
entries: t
|
|
1422
1233
|
};
|
|
1423
|
-
},
|
|
1234
|
+
}, jt = (e) => {
|
|
1424
1235
|
let t = e.indexOf(".");
|
|
1425
1236
|
return t <= 0 ? void 0 : e.slice(0, t);
|
|
1426
|
-
},
|
|
1237
|
+
}, Mt = (e, t) => {
|
|
1427
1238
|
let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
|
|
1428
1239
|
for (let e of t.rpcTags) {
|
|
1429
|
-
let t =
|
|
1240
|
+
let t = jt(e);
|
|
1430
1241
|
if (t === void 0) continue;
|
|
1431
1242
|
let n = i.get(t);
|
|
1432
1243
|
n ? n.push(e) : i.set(t, [e]);
|
|
@@ -1455,7 +1266,7 @@ var re = () => {
|
|
|
1455
1266
|
});
|
|
1456
1267
|
continue;
|
|
1457
1268
|
}
|
|
1458
|
-
let s = new Set(o.rpcTags.map(
|
|
1269
|
+
let s = new Set(o.rpcTags.map(jt).filter((e) => e !== void 0));
|
|
1459
1270
|
for (let e of s) {
|
|
1460
1271
|
let t = i.get(e);
|
|
1461
1272
|
t !== void 0 && a.push({
|
|
@@ -1468,13 +1279,13 @@ var re = () => {
|
|
|
1468
1279
|
}
|
|
1469
1280
|
}
|
|
1470
1281
|
return a;
|
|
1471
|
-
},
|
|
1282
|
+
}, Nt = (e, t) => {
|
|
1472
1283
|
let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
|
|
1473
1284
|
if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
|
|
1474
1285
|
for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
|
|
1475
1286
|
return r;
|
|
1476
|
-
},
|
|
1477
|
-
let t =
|
|
1287
|
+
}, Pt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Ft = (e) => {
|
|
1288
|
+
let t = re(e), n = ne(e);
|
|
1478
1289
|
return {
|
|
1479
1290
|
root: e,
|
|
1480
1291
|
serveBundle: t,
|
|
@@ -1482,19 +1293,19 @@ var re = () => {
|
|
|
1482
1293
|
apiBundle: n,
|
|
1483
1294
|
apiBundlePresent: T(n)
|
|
1484
1295
|
};
|
|
1485
|
-
},
|
|
1296
|
+
}, It = [
|
|
1486
1297
|
"Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
|
|
1487
1298
|
"Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
|
|
1488
1299
|
"(Generated Dockerfiles from `voltro create-project` already do this.)"
|
|
1489
|
-
].join("\n "),
|
|
1490
|
-
let n =
|
|
1491
|
-
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !
|
|
1300
|
+
].join("\n "), Lt = (e, t = !1) => {
|
|
1301
|
+
let n = Ft(e);
|
|
1302
|
+
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Pt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
|
|
1492
1303
|
console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
|
|
1493
1304
|
let r = process.env.NODE_ENV === "production";
|
|
1494
1305
|
if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
|
|
1495
1306
|
let i = t || r;
|
|
1496
|
-
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${
|
|
1497
|
-
},
|
|
1307
|
+
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${It}`), +!!i;
|
|
1308
|
+
}, Rt = (e, t, n) => {
|
|
1498
1309
|
let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
|
|
1499
1310
|
`${e}.ts`,
|
|
1500
1311
|
`${e}.tsx`,
|
|
@@ -1514,13 +1325,13 @@ var re = () => {
|
|
|
1514
1325
|
}
|
|
1515
1326
|
let o = [];
|
|
1516
1327
|
for (let t of r) {
|
|
1517
|
-
let n = t.startsWith(".") ? C(b(e), t) :
|
|
1328
|
+
let n = t.startsWith(".") ? C(b(e), t) : be(t, i);
|
|
1518
1329
|
if (n === void 0) continue;
|
|
1519
1330
|
let r = a(n);
|
|
1520
1331
|
r !== void 0 && o.push(r);
|
|
1521
1332
|
}
|
|
1522
1333
|
return o;
|
|
1523
|
-
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) =>
|
|
1334
|
+
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Wt.test(S(n, e))), l = new Set(c);
|
|
1524
1335
|
for (; c.length > 0;) {
|
|
1525
1336
|
let e = c.shift();
|
|
1526
1337
|
if (!s.has(e)) {
|
|
@@ -1533,11 +1344,11 @@ var re = () => {
|
|
|
1533
1344
|
let d = /* @__PURE__ */ new Set();
|
|
1534
1345
|
for (let e of s) l.has(e) || u.has(e) || d.add(e);
|
|
1535
1346
|
return d;
|
|
1536
|
-
},
|
|
1347
|
+
}, zt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Bt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Vt = (e, t) => {
|
|
1537
1348
|
if (e.src.includes(t)) return !0;
|
|
1538
1349
|
let n = t.replace(/^@voltro\/plugin-/, "");
|
|
1539
1350
|
return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
|
|
1540
|
-
},
|
|
1351
|
+
}, Ht = (e, t) => {
|
|
1541
1352
|
let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
|
|
1542
1353
|
for (; (i = r.exec(e)) !== null;) {
|
|
1543
1354
|
let t = i.index + i[0].length, r = 1, a = t;
|
|
@@ -1545,10 +1356,10 @@ var re = () => {
|
|
|
1545
1356
|
n.push(e.slice(t, a - 1));
|
|
1546
1357
|
}
|
|
1547
1358
|
return n;
|
|
1548
|
-
},
|
|
1359
|
+
}, Ut = (e, t) => {
|
|
1549
1360
|
let n = t.getSymbol()?.getDeclarations()?.[0];
|
|
1550
1361
|
return n === void 0 ? [] : e.getDescendantsOfKind(j.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
|
|
1551
|
-
},
|
|
1362
|
+
}, Wt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Gt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Kt = [
|
|
1552
1363
|
"Id",
|
|
1553
1364
|
"ID",
|
|
1554
1365
|
"Hash",
|
|
@@ -1574,12 +1385,12 @@ var re = () => {
|
|
|
1574
1385
|
"Comment",
|
|
1575
1386
|
"Provider",
|
|
1576
1387
|
"Format"
|
|
1577
|
-
],
|
|
1388
|
+
], qt = RegExp(`(?:${Kt.join("|")})$|_(?:${Kt.map((e) => e.toLowerCase()).join("|")})$`), Jt = [
|
|
1578
1389
|
{
|
|
1579
1390
|
id: "hardcoded-framework-cookie",
|
|
1580
1391
|
scope: "client",
|
|
1581
1392
|
smell: "a framework cookie name written as a string literal instead of imported",
|
|
1582
|
-
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",
|
|
1393
|
+
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",
|
|
1583
1394
|
match: ({ src: e }) => /['"`]voltro:(?:locale|theme|lang)['"`]/.test(e)
|
|
1584
1395
|
},
|
|
1585
1396
|
{
|
|
@@ -1587,7 +1398,7 @@ var re = () => {
|
|
|
1587
1398
|
scope: "server",
|
|
1588
1399
|
smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
|
|
1589
1400
|
use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
|
|
1590
|
-
match: ({ src: e, path: t }) =>
|
|
1401
|
+
match: ({ src: e, path: t }) => Gt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
|
|
1591
1402
|
},
|
|
1592
1403
|
{
|
|
1593
1404
|
id: "subject-write-no-guard",
|
|
@@ -1595,8 +1406,8 @@ var re = () => {
|
|
|
1595
1406
|
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",
|
|
1596
1407
|
use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
|
|
1597
1408
|
match: ({ src: e, path: t }) => {
|
|
1598
|
-
if (!
|
|
1599
|
-
let n =
|
|
1409
|
+
if (!Gt.test(t)) return !1;
|
|
1410
|
+
let n = zt(e);
|
|
1600
1411
|
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);
|
|
1601
1412
|
}
|
|
1602
1413
|
},
|
|
@@ -1605,7 +1416,7 @@ var re = () => {
|
|
|
1605
1416
|
scope: "server",
|
|
1606
1417
|
smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
|
|
1607
1418
|
use: "yield* HttpClient — the framework client. It injects traceparent, retries, and honours `http.allowHosts`. Where the URL is built from anything a caller supplied it ALSO blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop — for a hard-coded host that half is redundant, and the first three are still the reason to switch",
|
|
1608
|
-
match: ({ file: e, path: t, serverOnly: n }) => (
|
|
1419
|
+
match: ({ file: e, path: t, serverOnly: n }) => (Wt.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
|
|
1609
1420
|
},
|
|
1610
1421
|
{
|
|
1611
1422
|
id: "form",
|
|
@@ -1640,7 +1451,7 @@ var re = () => {
|
|
|
1640
1451
|
scope: "client",
|
|
1641
1452
|
smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
|
|
1642
1453
|
use: "useDerived — dependency-tracked, referentially stable",
|
|
1643
|
-
match: ({ src: e }) =>
|
|
1454
|
+
match: ({ src: e }) => Bt(e, /\buseSubscription\s*[(<]/g) >= 3 && Bt(e, /\buseMemo\s*\(/g) >= 3
|
|
1644
1455
|
},
|
|
1645
1456
|
{
|
|
1646
1457
|
id: "next-shim",
|
|
@@ -1665,7 +1476,7 @@ var re = () => {
|
|
|
1665
1476
|
match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
|
|
1666
1477
|
let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
|
|
1667
1478
|
return t === void 0 ? [] : k.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
|
|
1668
|
-
}).some((t) =>
|
|
1479
|
+
}).some((t) => Ut(e, t).some((e) => {
|
|
1669
1480
|
let t = e.getParent();
|
|
1670
1481
|
if (k.isBinaryExpression(t)) {
|
|
1671
1482
|
let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
|
|
@@ -1716,7 +1527,7 @@ var re = () => {
|
|
|
1716
1527
|
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)",
|
|
1717
1528
|
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) => {
|
|
1718
1529
|
let n = t[1];
|
|
1719
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
1530
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || qt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
|
|
1720
1531
|
}))
|
|
1721
1532
|
},
|
|
1722
1533
|
{
|
|
@@ -1726,7 +1537,7 @@ var re = () => {
|
|
|
1726
1537
|
use: "you cannot look a value up by equality AND encrypt it non-deterministically — `.encrypted()` would make this column unfindable. Store the HASH as the unique lookup key and keep the secret itself either `.encrypted()` in a second column or nowhere (the framework's own `_voltro_api_keys.hashedKey` is the shape: the presented value is hashed and compared, never read back). A deterministic cipher is the other option and buys equality lookup at the cost of leaking which rows share a value",
|
|
1727
1538
|
match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
|
|
1728
1539
|
let n = t[1];
|
|
1729
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
1540
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || qt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
|
|
1730
1541
|
}))
|
|
1731
1542
|
},
|
|
1732
1543
|
{
|
|
@@ -1791,8 +1602,8 @@ var re = () => {
|
|
|
1791
1602
|
smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
|
|
1792
1603
|
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",
|
|
1793
1604
|
match: ({ file: e, testSources: t }) => G(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
|
|
1794
|
-
let t =
|
|
1795
|
-
return
|
|
1605
|
+
let t = zt(e);
|
|
1606
|
+
return Ht(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
|
|
1796
1607
|
})
|
|
1797
1608
|
},
|
|
1798
1609
|
{
|
|
@@ -1802,14 +1613,14 @@ var re = () => {
|
|
|
1802
1613
|
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",
|
|
1803
1614
|
match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
|
|
1804
1615
|
}
|
|
1805
|
-
],
|
|
1616
|
+
], Yt = (e) => q(e), Xt = (e, t) => {
|
|
1806
1617
|
let n = /* @__PURE__ */ new Set();
|
|
1807
1618
|
for (let r of t) {
|
|
1808
1619
|
let t = S(e, r), i = t.split(/[\\/]/)[0];
|
|
1809
1620
|
n.add(i !== void 0 && i !== t ? i : ".");
|
|
1810
1621
|
}
|
|
1811
1622
|
return [...n].sort();
|
|
1812
|
-
},
|
|
1623
|
+
}, Zt = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Qt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, $t = (e, t = [], n = 0) => {
|
|
1813
1624
|
if (n > 12 || K.test(e)) return t;
|
|
1814
1625
|
let r;
|
|
1815
1626
|
try {
|
|
@@ -1821,8 +1632,8 @@ var re = () => {
|
|
|
1821
1632
|
let r = x(e, i.name);
|
|
1822
1633
|
if (i.isDirectory()) {
|
|
1823
1634
|
if (i.name.startsWith(".")) continue;
|
|
1824
|
-
|
|
1825
|
-
} else
|
|
1635
|
+
$t(r, t, n + 1);
|
|
1636
|
+
} else ft.test(i.name) && t.push(r);
|
|
1826
1637
|
}
|
|
1827
1638
|
return t;
|
|
1828
1639
|
}, q = (e, t = [], n = 0) => {
|
|
@@ -1840,10 +1651,10 @@ var re = () => {
|
|
|
1840
1651
|
} catch {
|
|
1841
1652
|
continue;
|
|
1842
1653
|
}
|
|
1843
|
-
a.isDirectory() ? q(r, t, n + 1) :
|
|
1654
|
+
a.isDirectory() ? q(r, t, n + 1) : Zt.test(i) && !/\.(test|spec)\./.test(i) && !Qt.test(i) && t.push(r);
|
|
1844
1655
|
}
|
|
1845
1656
|
return t;
|
|
1846
|
-
},
|
|
1657
|
+
}, en = (e, t = [], n = 0) => {
|
|
1847
1658
|
if (n > 12 || K.test(e)) return t;
|
|
1848
1659
|
let r;
|
|
1849
1660
|
try {
|
|
@@ -1858,17 +1669,17 @@ var re = () => {
|
|
|
1858
1669
|
} catch {
|
|
1859
1670
|
continue;
|
|
1860
1671
|
}
|
|
1861
|
-
a.isDirectory() ?
|
|
1672
|
+
a.isDirectory() ? en(r, t, n + 1) : Zt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
|
|
1862
1673
|
}
|
|
1863
1674
|
return t;
|
|
1864
|
-
},
|
|
1865
|
-
let t =
|
|
1675
|
+
}, tn = (e) => {
|
|
1676
|
+
let t = Yt(e);
|
|
1866
1677
|
return {
|
|
1867
|
-
dirs:
|
|
1678
|
+
dirs: Xt(e, t),
|
|
1868
1679
|
fileCount: t.length
|
|
1869
1680
|
};
|
|
1870
|
-
},
|
|
1871
|
-
let t =
|
|
1681
|
+
}, nn = (e) => {
|
|
1682
|
+
let t = Yt(e);
|
|
1872
1683
|
if (t.length === 0) return [];
|
|
1873
1684
|
let n = new A({
|
|
1874
1685
|
useInMemoryFileSystem: !0,
|
|
@@ -1877,8 +1688,8 @@ var re = () => {
|
|
|
1877
1688
|
for (let e of t) try {
|
|
1878
1689
|
i.set(e, E(e, "utf8"));
|
|
1879
1690
|
} catch {}
|
|
1880
|
-
let a =
|
|
1881
|
-
for (let t of
|
|
1691
|
+
let a = Rt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
|
|
1692
|
+
for (let t of en(e)) try {
|
|
1882
1693
|
o.set(t, E(t, "utf8"));
|
|
1883
1694
|
} catch {}
|
|
1884
1695
|
for (let s of t) {
|
|
@@ -1896,23 +1707,23 @@ var re = () => {
|
|
|
1896
1707
|
} catch {
|
|
1897
1708
|
continue;
|
|
1898
1709
|
}
|
|
1899
|
-
for (let t of
|
|
1900
|
-
if (t.plugin !== void 0 &&
|
|
1710
|
+
for (let t of Jt) {
|
|
1711
|
+
if (t.plugin !== void 0 && Vt(c, t.plugin) || !t.match(c)) continue;
|
|
1901
1712
|
let n = r.get(t.id) ?? [];
|
|
1902
1713
|
n.push(S(e, s)), r.set(t.id, n);
|
|
1903
1714
|
}
|
|
1904
1715
|
}
|
|
1905
|
-
return
|
|
1716
|
+
return Jt.filter((e) => r.has(e.id)).map((e) => ({
|
|
1906
1717
|
rule: e,
|
|
1907
1718
|
files: r.get(e.id)
|
|
1908
1719
|
})).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
|
|
1909
|
-
},
|
|
1910
|
-
let t =
|
|
1720
|
+
}, rn = (e) => {
|
|
1721
|
+
let t = tn(e);
|
|
1911
1722
|
return {
|
|
1912
1723
|
root: e,
|
|
1913
1724
|
scannedFiles: t.fileCount,
|
|
1914
1725
|
scannedDirs: t.dirs,
|
|
1915
|
-
findings:
|
|
1726
|
+
findings: nn(e).map(({ rule: e, files: t }) => ({
|
|
1916
1727
|
id: e.id,
|
|
1917
1728
|
scope: e.scope,
|
|
1918
1729
|
smell: e.smell,
|
|
@@ -1920,22 +1731,22 @@ var re = () => {
|
|
|
1920
1731
|
files: t
|
|
1921
1732
|
}))
|
|
1922
1733
|
};
|
|
1923
|
-
},
|
|
1734
|
+
}, an = 3, on = .2, sn = 50, cn = (e, t) => e >= sn && t > 0 && e / t >= on, ln = (e) => {
|
|
1924
1735
|
let t = /* @__PURE__ */ new Map();
|
|
1925
1736
|
for (let n of q(e)) try {
|
|
1926
1737
|
t.set(S(e, n), E(n, "utf8"));
|
|
1927
1738
|
} catch {}
|
|
1928
1739
|
let n = /* @__PURE__ */ new Map();
|
|
1929
|
-
for (let t of
|
|
1740
|
+
for (let t of $t(e)) try {
|
|
1930
1741
|
n.set(S(e, t), E(t, "utf8"));
|
|
1931
1742
|
} catch {}
|
|
1932
|
-
let r =
|
|
1743
|
+
let r = pt({
|
|
1933
1744
|
sources: t,
|
|
1934
1745
|
envFiles: n
|
|
1935
1746
|
});
|
|
1936
|
-
r !== void 0 && (console.log(""), console.log(
|
|
1937
|
-
},
|
|
1938
|
-
let t =
|
|
1747
|
+
r !== void 0 && (console.log(""), console.log(mt(r)));
|
|
1748
|
+
}, un = (e) => {
|
|
1749
|
+
let t = rn(e);
|
|
1939
1750
|
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;
|
|
1940
1751
|
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;
|
|
1941
1752
|
console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
|
|
@@ -1946,56 +1757,56 @@ var re = () => {
|
|
|
1946
1757
|
console.log(` [${e}]`);
|
|
1947
1758
|
let i = [...r].sort((e, t) => e.files.length - t.files.length);
|
|
1948
1759
|
for (let e of i) {
|
|
1949
|
-
if (
|
|
1760
|
+
if (cn(e.files.length, t.scannedFiles)) {
|
|
1950
1761
|
let n = Math.round(e.files.length / t.scannedFiles * 100);
|
|
1951
1762
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s), ${n}% of those scanned: a codebase-wide pattern rather than a work list`), console.log(` \u2192 ${e.use}`), console.log(" (paths omitted on purpose at this scale — `voltro doctor --json` has every one.)");
|
|
1952
1763
|
continue;
|
|
1953
1764
|
}
|
|
1954
|
-
let r = e.files.slice(0,
|
|
1765
|
+
let r = e.files.slice(0, an).join(", "), i = e.files.length - an;
|
|
1955
1766
|
i > 0 && (n += i);
|
|
1956
1767
|
let a = i > 0 ? `, +${i} more` : "";
|
|
1957
1768
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
|
|
1958
1769
|
}
|
|
1959
1770
|
}
|
|
1960
1771
|
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;
|
|
1961
|
-
},
|
|
1962
|
-
let t =
|
|
1772
|
+
}, dn = "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.", fn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, pn = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, mn = (e) => {
|
|
1773
|
+
let t = fn.exec(e)?.[1];
|
|
1963
1774
|
if (t !== void 0) return t === "ssr" ? "ssr" : null;
|
|
1964
|
-
let n =
|
|
1775
|
+
let n = pn.exec(e)?.[1];
|
|
1965
1776
|
return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
|
|
1966
|
-
},
|
|
1967
|
-
let
|
|
1968
|
-
if (!T(
|
|
1969
|
-
let { pages:
|
|
1970
|
-
for (let
|
|
1971
|
-
if (!
|
|
1972
|
-
let
|
|
1777
|
+
}, hn = async (e) => {
|
|
1778
|
+
let t = x(e, "src", "pages");
|
|
1779
|
+
if (!T(t)) return [];
|
|
1780
|
+
let { pages: i, dirs: o } = await n(t), s = [];
|
|
1781
|
+
for (let n of i) {
|
|
1782
|
+
if (!a(o, n.dirChain).some((e) => e.hasLayout)) continue;
|
|
1783
|
+
let i;
|
|
1973
1784
|
try {
|
|
1974
|
-
|
|
1785
|
+
i = E(x(t, n.file), "utf8");
|
|
1975
1786
|
} catch {
|
|
1976
1787
|
continue;
|
|
1977
1788
|
}
|
|
1978
|
-
if (
|
|
1979
|
-
let c =
|
|
1789
|
+
if (r.test(i)) continue;
|
|
1790
|
+
let c = mn(i);
|
|
1980
1791
|
c !== null && s.push({
|
|
1981
|
-
file: S(
|
|
1982
|
-
pattern:
|
|
1792
|
+
file: S(e, x(t, n.file)),
|
|
1793
|
+
pattern: n.pattern,
|
|
1983
1794
|
currentMode: c
|
|
1984
1795
|
});
|
|
1985
1796
|
}
|
|
1986
1797
|
return s;
|
|
1987
|
-
},
|
|
1798
|
+
}, gn = 10, _n = async (e) => {
|
|
1988
1799
|
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;
|
|
1989
|
-
let t = await
|
|
1800
|
+
let t = await hn(e);
|
|
1990
1801
|
if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
|
|
1991
1802
|
console.log(`• renderMode:'spa' candidates (${t.length} page${t.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
|
|
1992
|
-
for (let e of t.slice(0,
|
|
1803
|
+
for (let e of t.slice(0, gn)) {
|
|
1993
1804
|
let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
|
|
1994
1805
|
console.log(` ${e.file} (${e.pattern}) — ${t}`);
|
|
1995
1806
|
}
|
|
1996
|
-
let n = t.length -
|
|
1997
|
-
return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${
|
|
1998
|
-
},
|
|
1807
|
+
let n = t.length - gn;
|
|
1808
|
+
return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${dn}`), t.length;
|
|
1809
|
+
}, vn = /* @__PURE__ */ new Set([
|
|
1999
1810
|
"node_modules",
|
|
2000
1811
|
"dist",
|
|
2001
1812
|
"build",
|
|
@@ -2003,7 +1814,7 @@ var re = () => {
|
|
|
2003
1814
|
".vite",
|
|
2004
1815
|
".framework",
|
|
2005
1816
|
".git"
|
|
2006
|
-
]),
|
|
1817
|
+
]), yn = (e, t, n) => {
|
|
2007
1818
|
let r;
|
|
2008
1819
|
try {
|
|
2009
1820
|
r = D(e, { withFileTypes: !0 });
|
|
@@ -2011,11 +1822,11 @@ var re = () => {
|
|
|
2011
1822
|
return;
|
|
2012
1823
|
}
|
|
2013
1824
|
for (let i of r) {
|
|
2014
|
-
if (
|
|
1825
|
+
if (vn.has(i.name)) continue;
|
|
2015
1826
|
let r = x(e, i.name);
|
|
2016
|
-
i.isDirectory() ?
|
|
1827
|
+
i.isDirectory() ? yn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
|
|
2017
1828
|
}
|
|
2018
|
-
},
|
|
1829
|
+
}, bn = (e, t) => {
|
|
2019
1830
|
let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
|
|
2020
1831
|
for (let i of t) {
|
|
2021
1832
|
let t;
|
|
@@ -2040,21 +1851,21 @@ var re = () => {
|
|
|
2040
1851
|
}
|
|
2041
1852
|
}
|
|
2042
1853
|
return n;
|
|
2043
|
-
},
|
|
1854
|
+
}, xn = (e, t) => {
|
|
2044
1855
|
let n = e.slice(t.length).split(w).slice(0, -1)[0];
|
|
2045
1856
|
return n !== void 0 && n.startsWith("[");
|
|
2046
|
-
},
|
|
1857
|
+
}, J = (e) => {
|
|
2047
1858
|
let t = [], n = x(e, "src", "pages"), r = x(e, "src");
|
|
2048
1859
|
if (!T(r)) return t;
|
|
2049
|
-
let
|
|
2050
|
-
|
|
2051
|
-
let
|
|
2052
|
-
|
|
2053
|
-
for (let n of
|
|
1860
|
+
let i = [];
|
|
1861
|
+
yn(r, e, i), i.sort();
|
|
1862
|
+
let a = `${S(e, n)}${w}`;
|
|
1863
|
+
bn(e, i);
|
|
1864
|
+
for (let n of i) {
|
|
2054
1865
|
let r = n.slice(n.lastIndexOf(w) + 1);
|
|
2055
|
-
if (n.startsWith(
|
|
1866
|
+
if (n.startsWith(a)) {
|
|
2056
1867
|
if (r !== "page.tsx") continue;
|
|
2057
|
-
T(x(e,
|
|
1868
|
+
T(x(e, c(n))) || t.push({
|
|
2058
1869
|
kind: "page-without-test",
|
|
2059
1870
|
file: n
|
|
2060
1871
|
});
|
|
@@ -2065,7 +1876,7 @@ var re = () => {
|
|
|
2065
1876
|
file: n
|
|
2066
1877
|
});
|
|
2067
1878
|
} catch {}
|
|
2068
|
-
|
|
1879
|
+
xn(n, a) && t.push({
|
|
2069
1880
|
kind: "top-level-dynamic-route",
|
|
2070
1881
|
file: n
|
|
2071
1882
|
});
|
|
@@ -2077,33 +1888,33 @@ var re = () => {
|
|
|
2077
1888
|
});
|
|
2078
1889
|
}
|
|
2079
1890
|
return t;
|
|
2080
|
-
},
|
|
1891
|
+
}, Sn = 10, Y = (e, t, n) => {
|
|
2081
1892
|
if (t.length === 0) return;
|
|
2082
1893
|
console.log(`• ${e} (${t.length}):`);
|
|
2083
|
-
for (let e of t.slice(0,
|
|
2084
|
-
let r = t.length -
|
|
1894
|
+
for (let e of t.slice(0, Sn)) console.log(` ${e}`);
|
|
1895
|
+
let r = t.length - Sn;
|
|
2085
1896
|
r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
|
|
2086
|
-
},
|
|
1897
|
+
}, Cn = (e) => {
|
|
2087
1898
|
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;
|
|
2088
|
-
let t =
|
|
1899
|
+
let t = J(e);
|
|
2089
1900
|
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;
|
|
2090
1901
|
let n = (e) => t.filter((t) => t.kind === e).map((e) => e.file);
|
|
2091
|
-
return
|
|
2092
|
-
},
|
|
2093
|
-
let t =
|
|
1902
|
+
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;
|
|
1903
|
+
}, wn = "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.", Tn = /* @__PURE__ */ new Map(), X = (e) => {
|
|
1904
|
+
let t = Tn.get(e);
|
|
2094
1905
|
if (t !== void 0) return t;
|
|
2095
1906
|
let n = (async () => {
|
|
2096
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
1907
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-rc3fwPSZ.js"), r = await t(e);
|
|
2097
1908
|
return {
|
|
2098
1909
|
files: r,
|
|
2099
1910
|
discovered: await n(r)
|
|
2100
1911
|
};
|
|
2101
1912
|
})();
|
|
2102
|
-
return
|
|
2103
|
-
},
|
|
1913
|
+
return Tn.set(e, n), n;
|
|
1914
|
+
}, En = async (e) => {
|
|
2104
1915
|
let t;
|
|
2105
1916
|
try {
|
|
2106
|
-
let { files: n, discovered: r } = await
|
|
1917
|
+
let { files: n, discovered: r } = await X(e), i = r.tables.map((e) => {
|
|
2107
1918
|
let t = e;
|
|
2108
1919
|
return {
|
|
2109
1920
|
name: t.tableName,
|
|
@@ -2111,12 +1922,12 @@ var re = () => {
|
|
|
2111
1922
|
};
|
|
2112
1923
|
});
|
|
2113
1924
|
if (i.length === 0) return;
|
|
2114
|
-
t = qe(
|
|
1925
|
+
t = Je(qe(n, i, e));
|
|
2115
1926
|
} catch {
|
|
2116
1927
|
return;
|
|
2117
1928
|
}
|
|
2118
1929
|
t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
|
|
2119
|
-
},
|
|
1930
|
+
}, Dn = (e) => e.map((e) => {
|
|
2120
1931
|
let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
|
|
2121
1932
|
name: e,
|
|
2122
1933
|
type: typeof t?.type == "string" ? t.type : ""
|
|
@@ -2126,22 +1937,22 @@ var re = () => {
|
|
|
2126
1937
|
columns: n,
|
|
2127
1938
|
primaryKey: t.appliedPrimaryKey ?? []
|
|
2128
1939
|
};
|
|
2129
|
-
}),
|
|
1940
|
+
}), Z = async (e) => {
|
|
2130
1941
|
try {
|
|
2131
|
-
let { discovered: t } = await
|
|
2132
|
-
return
|
|
1942
|
+
let { discovered: t } = await X(e);
|
|
1943
|
+
return $e(Dn(t.tables));
|
|
2133
1944
|
} catch {
|
|
2134
1945
|
return null;
|
|
2135
1946
|
}
|
|
2136
|
-
},
|
|
2137
|
-
let t = await
|
|
1947
|
+
}, On = async (e) => {
|
|
1948
|
+
let t = await Z(e);
|
|
2138
1949
|
if (t === null || t.tablesChecked === 0) return;
|
|
2139
|
-
let n =
|
|
1950
|
+
let n = tt(t);
|
|
2140
1951
|
n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
|
|
2141
|
-
},
|
|
1952
|
+
}, kn = async (e) => {
|
|
2142
1953
|
try {
|
|
2143
|
-
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await
|
|
2144
|
-
return
|
|
1954
|
+
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await X(e);
|
|
1955
|
+
return at(n.events.map((e) => {
|
|
2145
1956
|
let n = e.descriptor, r = t(n.key);
|
|
2146
1957
|
return {
|
|
2147
1958
|
name: n.name,
|
|
@@ -2153,19 +1964,19 @@ var re = () => {
|
|
|
2153
1964
|
} catch {
|
|
2154
1965
|
return null;
|
|
2155
1966
|
}
|
|
2156
|
-
},
|
|
2157
|
-
let t = await
|
|
1967
|
+
}, An = async (e) => {
|
|
1968
|
+
let t = await kn(e);
|
|
2158
1969
|
if (t === null || t.eventsChecked === 0) return;
|
|
2159
|
-
let n =
|
|
1970
|
+
let n = ot(t);
|
|
2160
1971
|
n !== null && (console.log(""), console.log(`• ${n}`));
|
|
2161
|
-
let r =
|
|
1972
|
+
let r = st(t);
|
|
2162
1973
|
r !== null && (console.log(""), console.log(`⚠️ ${r}`));
|
|
2163
|
-
},
|
|
1974
|
+
}, jn = (e) => {
|
|
2164
1975
|
let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
|
|
2165
1976
|
return r === void 0 ? n : `${r}: ${n}`;
|
|
2166
|
-
},
|
|
1977
|
+
}, Mn = async (e) => {
|
|
2167
1978
|
try {
|
|
2168
|
-
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await
|
|
1979
|
+
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await X(e)).discovered.serverOnlyLeaks, r = t(n);
|
|
2169
1980
|
return {
|
|
2170
1981
|
status: {
|
|
2171
1982
|
checked: !0,
|
|
@@ -2176,17 +1987,17 @@ var re = () => {
|
|
|
2176
1987
|
} catch (e) {
|
|
2177
1988
|
return { status: {
|
|
2178
1989
|
checked: !1,
|
|
2179
|
-
reason:
|
|
1990
|
+
reason: jn(e)
|
|
2180
1991
|
} };
|
|
2181
1992
|
}
|
|
2182
|
-
},
|
|
2183
|
-
let { status: t, message: n } = await
|
|
1993
|
+
}, Nn = async (e) => {
|
|
1994
|
+
let { status: t, message: n } = await Mn(e);
|
|
2184
1995
|
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);
|
|
2185
|
-
},
|
|
1996
|
+
}, Pn = async (e) => {
|
|
2186
1997
|
let t;
|
|
2187
1998
|
try {
|
|
2188
|
-
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await
|
|
2189
|
-
t =
|
|
1999
|
+
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await X(e);
|
|
2000
|
+
t = ut(r, i.workflows.map((e) => {
|
|
2190
2001
|
let t = e.definition, r = t.payloadSchema === void 0 ? {
|
|
2191
2002
|
required: [],
|
|
2192
2003
|
optional: []
|
|
@@ -2200,18 +2011,98 @@ var re = () => {
|
|
|
2200
2011
|
} catch {
|
|
2201
2012
|
return;
|
|
2202
2013
|
}
|
|
2203
|
-
let n =
|
|
2014
|
+
let n = dt(t);
|
|
2204
2015
|
if (n === null) return;
|
|
2205
2016
|
console.log("");
|
|
2206
2017
|
let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
|
|
2207
2018
|
console.log(`${r} ${n}`);
|
|
2208
|
-
}, Q = "voltro-authz-allowlist.txt",
|
|
2019
|
+
}, Q = "voltro-authz-allowlist.txt", Fn = (e, t = (e) => {
|
|
2209
2020
|
try {
|
|
2210
2021
|
return E(e, "utf8");
|
|
2211
2022
|
} catch {
|
|
2212
2023
|
return "";
|
|
2213
2024
|
}
|
|
2214
|
-
}) => /* @__PURE__ */ new Set([...
|
|
2025
|
+
}) => /* @__PURE__ */ new Set([...vt, ...yt(q(e).map(t))]), In = (e, t) => {
|
|
2026
|
+
let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2027
|
+
return new RegExp(String.raw`(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=\s*define[A-Za-z]*Event\s*\(\s*\{[^}]*?name\s*:\s*['"\`]${n}['"\`]`, "s").exec(e)?.[1];
|
|
2028
|
+
}, Ln = async (e) => {
|
|
2029
|
+
try {
|
|
2030
|
+
let { discovered: t } = await X(e), n = t.events.map((e) => ({
|
|
2031
|
+
name: e.descriptor.name,
|
|
2032
|
+
file: e.file
|
|
2033
|
+
})).filter((e) => typeof e.name == "string");
|
|
2034
|
+
if (n.length === 0) return [];
|
|
2035
|
+
let r = new Set(t.events.map((e) => e.file)), i = (e) => {
|
|
2036
|
+
try {
|
|
2037
|
+
return E(e, "utf8");
|
|
2038
|
+
} catch {
|
|
2039
|
+
return "";
|
|
2040
|
+
}
|
|
2041
|
+
}, a = q(e).filter((e) => !r.has(e)).map(i).join("\n");
|
|
2042
|
+
return n.filter(({ name: e, file: t }) => {
|
|
2043
|
+
if (a.includes(e)) return !1;
|
|
2044
|
+
let n = In(i(t), e);
|
|
2045
|
+
return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
|
|
2046
|
+
}).map((e) => e.name).sort();
|
|
2047
|
+
} catch {
|
|
2048
|
+
return [];
|
|
2049
|
+
}
|
|
2050
|
+
}, Rn = async (e) => {
|
|
2051
|
+
let t = await Ln(e);
|
|
2052
|
+
if (t.length !== 0) {
|
|
2053
|
+
console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
|
|
2054
|
+
for (let e of t) console.log(` ${e}`);
|
|
2055
|
+
console.log(""), console.log(" An event you declare is an event partners can subscribe to. One that"), console.log(" never fires is indistinguishable, from their side, from a broken"), console.log(" endpoint of their own — so they debug your contract instead of"), console.log(" reading it."), console.log(" ADVISORY: this looks for the event name AND the identifier its"), console.log(" declaration binds it to, so `emit(ctx, apiKeyCreated, …)` counts. An"), console.log(" emit through an ASSEMBLED name still reads as missing here.");
|
|
2056
|
+
}
|
|
2057
|
+
}, zn = async (e) => {
|
|
2058
|
+
try {
|
|
2059
|
+
let { loadApiConfig: t } = await import("./dev-rc3fwPSZ.js"), n = (await t(e))?.doctor?.scopeVocabulary;
|
|
2060
|
+
if (typeof n != "string") return [];
|
|
2061
|
+
let [r, i] = n.split("#");
|
|
2062
|
+
if (r === void 0 || i === void 0) return [];
|
|
2063
|
+
let a = (await import(oe(C(e, r)).href))[i];
|
|
2064
|
+
return Array.isArray(a) ? a.filter((e) => typeof e == "string") : [];
|
|
2065
|
+
} catch {
|
|
2066
|
+
return [];
|
|
2067
|
+
}
|
|
2068
|
+
}, Bn = async (e) => {
|
|
2069
|
+
let t = {
|
|
2070
|
+
evaluated: !1,
|
|
2071
|
+
vocabularySize: 0,
|
|
2072
|
+
findings: []
|
|
2073
|
+
};
|
|
2074
|
+
try {
|
|
2075
|
+
let { offlineManifest: n, toInput: r } = await import("./checkCommand-CwMrzAgV.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CvQCte0z.js"), o = await n(e), s = r(o.manifest, o.tables), c = await zn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
|
|
2076
|
+
if (l.length === 0) return t;
|
|
2077
|
+
let u = i({
|
|
2078
|
+
...s,
|
|
2079
|
+
scopes: l
|
|
2080
|
+
});
|
|
2081
|
+
return {
|
|
2082
|
+
evaluated: !0,
|
|
2083
|
+
vocabularySize: l.length,
|
|
2084
|
+
findings: a(u).filter((e) => e.rule === "rbac/unknown-scope").map((e) => ({
|
|
2085
|
+
node: e.node,
|
|
2086
|
+
...e.file === void 0 ? {} : { file: e.file },
|
|
2087
|
+
detail: e.breaks.join("; "),
|
|
2088
|
+
fix: e.fix
|
|
2089
|
+
}))
|
|
2090
|
+
};
|
|
2091
|
+
} catch {
|
|
2092
|
+
return t;
|
|
2093
|
+
}
|
|
2094
|
+
}, Vn = async (e) => {
|
|
2095
|
+
let { findings: t, evaluated: n } = await Bn(e);
|
|
2096
|
+
if (!n) {
|
|
2097
|
+
console.log(""), console.log("• scope vocabulary: none found — the unknown-scope rule did NOT run."), console.log(" It compares each procedure's required scopes against the scopes your"), console.log(" roles actually grant, and needs somewhere to read that set from."), console.log(" `@voltro/plugin-rbac` publishes it from its `roles` map; an app doing"), console.log(" RBAC without the plugin can point at its own:"), console.log(""), console.log(" // app.config.ts"), console.log(" doctor: { scopeVocabulary: './lib/teamRoles.ts#ALL_TEAM_SCOPES' }"), console.log(""), console.log(" Until then this section is NOT a clean bill of health — a procedure"), console.log(" requiring a scope no role grants is uncallable by everyone, and"), console.log(" nothing here would have told you.");
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
if (t.length !== 0) {
|
|
2101
|
+
console.log(""), console.log(`• ${t.length} procedure(s) require a scope no declared role grants:`);
|
|
2102
|
+
for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
|
|
2103
|
+
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}`);
|
|
2104
|
+
}
|
|
2105
|
+
}, Hn = async (e) => {
|
|
2215
2106
|
let t = ["src/locales", "locales"], n, r = [];
|
|
2216
2107
|
for (let i of t) {
|
|
2217
2108
|
let t = x(e, i);
|
|
@@ -2223,23 +2114,23 @@ var re = () => {
|
|
|
2223
2114
|
if (n === void 0 || r.length === 0) return;
|
|
2224
2115
|
let i = [];
|
|
2225
2116
|
try {
|
|
2226
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2117
|
+
let { loadApiConfig: t } = await import("./dev-rc3fwPSZ.js");
|
|
2227
2118
|
i = (await t(e))?.locales ?? [];
|
|
2228
2119
|
} catch {
|
|
2229
2120
|
return;
|
|
2230
2121
|
}
|
|
2231
|
-
let a =
|
|
2122
|
+
let a = ht({
|
|
2232
2123
|
catalogCodes: r,
|
|
2233
2124
|
declaredLocales: i
|
|
2234
2125
|
});
|
|
2235
2126
|
if (a !== void 0) {
|
|
2236
2127
|
console.log("");
|
|
2237
|
-
for (let e of
|
|
2128
|
+
for (let e of gt(a, n)) console.log(e);
|
|
2238
2129
|
}
|
|
2239
2130
|
}, Un = async (e) => {
|
|
2240
|
-
let { discovered: t } = await
|
|
2131
|
+
let { discovered: t } = await X(e), n = [];
|
|
2241
2132
|
try {
|
|
2242
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2133
|
+
let { loadApiConfig: t } = await import("./dev-rc3fwPSZ.js");
|
|
2243
2134
|
n = ((await t(e))?.plugins ?? []).map((e) => {
|
|
2244
2135
|
let t = (e.extendSchema?.tables ?? []).map((e) => {
|
|
2245
2136
|
let t = e;
|
|
@@ -2268,12 +2159,12 @@ var re = () => {
|
|
|
2268
2159
|
...t.mutations,
|
|
2269
2160
|
...t.actions,
|
|
2270
2161
|
...t.streams ?? []
|
|
2271
|
-
].map((e) => String(e.descriptor.name)), i =
|
|
2162
|
+
].map((e) => String(e.descriptor.name)), i = Mt(n, {
|
|
2272
2163
|
tables: t.tables.map((e) => e.tableName),
|
|
2273
2164
|
rpcTags: r
|
|
2274
2165
|
});
|
|
2275
2166
|
console.log();
|
|
2276
|
-
for (let e of
|
|
2167
|
+
for (let e of Nt(n, i)) console.log(e);
|
|
2277
2168
|
}, Wn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Gn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Kn = async (e) => {
|
|
2278
2169
|
let t = await qn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
|
|
2279
2170
|
return {
|
|
@@ -2288,14 +2179,14 @@ var re = () => {
|
|
|
2288
2179
|
unchecked: t.result.unchecked
|
|
2289
2180
|
};
|
|
2290
2181
|
}, qn = async (e) => {
|
|
2291
|
-
let { discovered: t } = await
|
|
2182
|
+
let { discovered: t } = await X(e), n = /* @__PURE__ */ new Map(), r = (e) => {
|
|
2292
2183
|
if (!n.has(e)) try {
|
|
2293
2184
|
n.set(e, E(e, "utf8"));
|
|
2294
2185
|
} catch {
|
|
2295
2186
|
n.set(e, "");
|
|
2296
2187
|
}
|
|
2297
2188
|
return n.get(e);
|
|
2298
|
-
}, i =
|
|
2189
|
+
}, i = Fn(e, r), a = /* @__PURE__ */ new Set();
|
|
2299
2190
|
for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
|
|
2300
2191
|
let e = t.references?.();
|
|
2301
2192
|
e && a.add(e.tableName);
|
|
@@ -2332,7 +2223,7 @@ var re = () => {
|
|
|
2332
2223
|
l = !0;
|
|
2333
2224
|
break;
|
|
2334
2225
|
}
|
|
2335
|
-
let u =
|
|
2226
|
+
let u = Tt({
|
|
2336
2227
|
executors: o,
|
|
2337
2228
|
guardVocabulary: i,
|
|
2338
2229
|
tenantScopedTables: t.tenantScopedTables,
|
|
@@ -2352,35 +2243,40 @@ var re = () => {
|
|
|
2352
2243
|
}, Jn = async (e, t) => {
|
|
2353
2244
|
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await qn(e), l = n.counts;
|
|
2354
2245
|
console.log(`\nauthz scan · ${o} executor(s)`);
|
|
2355
|
-
let u = [...r].filter((e) => !
|
|
2246
|
+
let u = [...r].filter((e) => !vt.has(e)).sort();
|
|
2356
2247
|
console.log(u.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${u.length} from your source (${u.slice(0, 6).join(", ")}${u.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(l.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(l.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(l.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(l.vocabularyGuard).padStart(4)}`);
|
|
2357
2248
|
let d = new Set(a.map((e) => e.tag)), f = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !d.has(e.tag)).length, p = l.allowlisted - f;
|
|
2358
2249
|
if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Q})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
|
|
2359
|
-
let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r =
|
|
2360
|
-
return
|
|
2250
|
+
let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = kt(e, t);
|
|
2251
|
+
return ce(s, Ot(e, t), "utf8"), console.log(`\n${Q}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
|
|
2361
2252
|
}
|
|
2362
2253
|
if (n.unchecked.length === 0) return !1;
|
|
2363
2254
|
console.log("");
|
|
2364
2255
|
for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
|
|
2365
2256
|
return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Q} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Q}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
|
|
2366
2257
|
}, Yn = async (e) => {
|
|
2367
|
-
if (e.includes("--help") || e.includes("-h")) return console.log(
|
|
2368
|
-
let
|
|
2369
|
-
if (
|
|
2370
|
-
...
|
|
2371
|
-
spaCandidates: await
|
|
2372
|
-
pageConvention:
|
|
2373
|
-
fileTaxonomy: V(
|
|
2374
|
-
junctionFks: (await
|
|
2375
|
-
eventDelivery: await
|
|
2376
|
-
duplicateInstances:
|
|
2377
|
-
|
|
2378
|
-
|
|
2258
|
+
if (e.includes("--help") || e.includes("-h")) return console.log(wn), 0;
|
|
2259
|
+
let n = e.includes("--strict"), r = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
|
|
2260
|
+
if (ue(e)) return le(), M({
|
|
2261
|
+
...rn(r),
|
|
2262
|
+
spaCandidates: await hn(r),
|
|
2263
|
+
pageConvention: J(r),
|
|
2264
|
+
fileTaxonomy: V(r).findings,
|
|
2265
|
+
junctionFks: (await Z(r))?.issues ?? [],
|
|
2266
|
+
eventDelivery: await kn(r),
|
|
2267
|
+
duplicateInstances: t(r),
|
|
2268
|
+
versionSkew: s(r) ?? null,
|
|
2269
|
+
serverOnly: (await Mn(r)).status,
|
|
2270
|
+
authz: await Kn(r),
|
|
2271
|
+
ungrantedScopes: await Bn(r),
|
|
2272
|
+
neverEmittedEvents: await Ln(r)
|
|
2379
2273
|
}), 0;
|
|
2380
|
-
let
|
|
2381
|
-
|
|
2382
|
-
let
|
|
2383
|
-
|
|
2274
|
+
let a = Lt(r, n);
|
|
2275
|
+
un(r), ln(r), Cn(r), Ue(r), await _n(r), await Pn(r), await En(r), await On(r), await An(r), await Un(r), await Vn(r), await Rn(r), await Hn(r);
|
|
2276
|
+
let c = await Nn(r), l = await Jn(r, e.includes("--write-authz-allowlist")), u = o(s(r));
|
|
2277
|
+
u !== void 0 && (console.log(""), console.log(u));
|
|
2278
|
+
let d = i(t(r));
|
|
2279
|
+
return d !== void 0 && console.log(`\n${d}`), c || l ? Math.max(a, 1) : a;
|
|
2384
2280
|
}, Xn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Zn = /* @__PURE__ */ new Set([
|
|
2385
2281
|
"insert",
|
|
2386
2282
|
"update",
|
|
@@ -2473,13 +2369,13 @@ var re = () => {
|
|
|
2473
2369
|
".git",
|
|
2474
2370
|
"coverage"
|
|
2475
2371
|
].includes(i)) continue;
|
|
2476
|
-
let r = `${e}/${i}`,
|
|
2372
|
+
let r = `${e}/${i}`, a;
|
|
2477
2373
|
try {
|
|
2478
|
-
|
|
2374
|
+
a = O(r);
|
|
2479
2375
|
} catch {
|
|
2480
2376
|
continue;
|
|
2481
2377
|
}
|
|
2482
|
-
|
|
2378
|
+
a.isDirectory() ? $n(r, t, n + 1) : a.isFile() && l.test(i) && t.push(r);
|
|
2483
2379
|
}
|
|
2484
2380
|
return t;
|
|
2485
2381
|
}, er = /* @__PURE__ */ new Set(["actors", "tenants"]), tr = async (e) => {
|
|
@@ -2513,7 +2409,7 @@ var re = () => {
|
|
|
2513
2409
|
let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
|
|
2514
2410
|
return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
|
|
2515
2411
|
}).join("\n"), ar = async (e) => {
|
|
2516
|
-
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-
|
|
2412
|
+
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-rc3fwPSZ.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 = [
|
|
2517
2413
|
...s.queries.map((e) => c(e.descriptor, "query", e.file)),
|
|
2518
2414
|
...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
|
|
2519
2415
|
...s.actions.map((e) => c(e.descriptor, "action", e.file)),
|
|
@@ -2530,9 +2426,9 @@ var re = () => {
|
|
|
2530
2426
|
tables: { tables: p }
|
|
2531
2427
|
};
|
|
2532
2428
|
}, or = async (e) => {
|
|
2533
|
-
let t =
|
|
2429
|
+
let t = he(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
|
|
2534
2430
|
if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
|
|
2535
|
-
let { process: n, target: r, rest: i } =
|
|
2431
|
+
let { process: n, target: r, rest: i } = ge(t.rest), a;
|
|
2536
2432
|
for (let e = 0; e < i.length; e++) if (i[e] === "--diff") {
|
|
2537
2433
|
let [t, n] = (i[++e] ?? "").split(":");
|
|
2538
2434
|
if (t === "removeTable" && n) a = {
|
|
@@ -2549,35 +2445,35 @@ var re = () => {
|
|
|
2549
2445
|
};
|
|
2550
2446
|
else return console.error("voltro check --diff: expected removeTable:<name> | removeProcedure:<tag> | removeScope:<name>"), 1;
|
|
2551
2447
|
}
|
|
2552
|
-
let
|
|
2553
|
-
if (
|
|
2554
|
-
if (!
|
|
2448
|
+
let o = i.find((e) => !e.startsWith("-")) ?? process.cwd(), s = i.includes("--offline"), c = s ? [] : await me(n, r), l, d;
|
|
2449
|
+
if (c.length === 0) {
|
|
2450
|
+
if (!s && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
|
|
2555
2451
|
try {
|
|
2556
|
-
let e = await ar(C(
|
|
2557
|
-
|
|
2452
|
+
let e = await ar(C(o));
|
|
2453
|
+
l = e.manifest, d = e.tables;
|
|
2558
2454
|
} catch (e) {
|
|
2559
2455
|
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;
|
|
2560
2456
|
}
|
|
2561
2457
|
} else {
|
|
2562
|
-
let e =
|
|
2458
|
+
let e = c[0], [t, n] = await Promise.all([N(e, "/_voltro/inspect/manifest"), N(e, "/_voltro/inspect/data/tables")]);
|
|
2563
2459
|
if (t.kind === "err") return console.error(`voltro check: manifest fetch failed: ${t.error}`), 1;
|
|
2564
|
-
|
|
2460
|
+
l = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
|
|
2565
2461
|
}
|
|
2566
|
-
let f = rr(
|
|
2462
|
+
let f = rr(l, d), p = ie(f, a), m = [...V(o).findings.map((e) => ({
|
|
2567
2463
|
rule: e.rule,
|
|
2568
2464
|
node: e.file,
|
|
2569
2465
|
file: e.file,
|
|
2570
2466
|
breaks: [e.detail],
|
|
2571
2467
|
fix: e.fix,
|
|
2572
2468
|
severity: e.severity
|
|
2573
|
-
})), ...
|
|
2469
|
+
})), ...J(o).map((e) => ({
|
|
2574
2470
|
rule: `page/${e.kind}`,
|
|
2575
2471
|
node: e.file,
|
|
2576
2472
|
file: e.file,
|
|
2577
2473
|
breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
|
|
2578
2474
|
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",
|
|
2579
2475
|
severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
|
|
2580
|
-
}))], h = (await o
|
|
2476
|
+
}))], h = (await u(o, $n(o))).map((e) => ({
|
|
2581
2477
|
rule: "client/not-browser-safe",
|
|
2582
2478
|
node: e.file,
|
|
2583
2479
|
file: e.file,
|
|
@@ -2588,7 +2484,7 @@ var re = () => {
|
|
|
2588
2484
|
...p.diagnostics,
|
|
2589
2485
|
...m,
|
|
2590
2486
|
...h
|
|
2591
|
-
], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await tr(C(
|
|
2487
|
+
], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await tr(C(o)), y = v === null ? null : Qn(f.procedures, v);
|
|
2592
2488
|
return t.format === "json" ? M(y === null ? {
|
|
2593
2489
|
...p,
|
|
2594
2490
|
ok: _,
|
|
@@ -2601,4 +2497,4 @@ var re = () => {
|
|
|
2601
2497
|
}) : (console.log(ir(g)), y !== null && console.log(nr(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
|
|
2602
2498
|
};
|
|
2603
2499
|
//#endregion
|
|
2604
|
-
export {
|
|
2500
|
+
export { le as _, Lt as a, Se as c, N as d, he as f, me as g, pe as h, Yn as i, I as l, P as m, or as n, Be as o, ge as p, rr as r, xe as s, ar as t, ye as u, M as v, ue as y };
|