@voltro/cli 0.31.0 → 0.33.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 +368 -0
- package/THIRD-PARTY-NOTICES.md +1 -29
- package/dist/apiBuild-C-x9YacA.js +2 -0
- package/dist/{apiBuild-CLPt-pX6.js → apiBuild-h9VHtnlw.js} +2 -2
- package/dist/bin.js +3 -3
- package/dist/checkCommand-DRovTKza.js +2 -0
- package/dist/{checkCommand-D7r6_pk7.js → checkCommand-xGhRFFg2.js} +330 -290
- package/dist/{commands-CNVVv03h.js → commands-CJfepbm4.js} +139 -13
- package/dist/{dbCommand-BH8GAYuf.js → dbCommand-DTLKAfbA.js} +431 -263
- package/dist/dbCommand-b1gum4td.js +2 -0
- package/dist/{dev-DdQEn2Ia.js → dev-C_P8FLSx.js} +2141 -2081
- package/dist/{dev-DOZVMVfi.js → dev-iiMtlkfs.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-BeEAnjqw.js → inspectMetrics-1xzTKAFx.js} +750 -685
- package/dist/{sdkgen-v9WV8zRi.js → sdkgen-C81QIkiL.js} +1 -1
- package/dist/serveCommand-DdaM4Hup.js +1608 -0
- package/dist/serveEntry.js +2 -2
- package/dist/{start-CI2QdKy9.js → start-C0koT0UO.js} +1 -1
- package/dist/startEntry.js +2 -2
- package/package.json +431 -18
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/database/misc.md +37 -0
- package/templates/agent-docs/database/schema.md +4 -2
- package/templates/agent-docs/deployment.md +87 -1
- package/templates/agent-docs/plugins/audit.md +24 -0
- package/templates/agent-docs/plugins.md +50 -0
- package/templates/agent-docs/scheduling.md +4 -1
- package/templates/agent-docs/security.md +31 -1
- package/templates/agent-docs/whats-new.md +149 -59
- 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-BOqL1dlg.js +0 -2
- package/dist/checkCommand-ClRvH_SN.js +0 -2
- package/dist/dbCommand-BwQ_Jl98.js +0 -2
- package/dist/serveCommand-KsKXXWn4.js +0 -1578
|
@@ -1,31 +1,32 @@
|
|
|
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-
|
|
2
|
-
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-1xzTKAFx.js";
|
|
2
|
+
import { Gt as l, Wt as u } from "./dev-C_P8FLSx.js";
|
|
3
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-
|
|
4
|
+
import { r as ne, u as re } from "./apiBuild-h9VHtnlw.js";
|
|
5
5
|
import { r as ie } from "./appGraph-gQ_6GkQQ.js";
|
|
6
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 {
|
|
8
|
-
import {
|
|
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";
|
|
9
10
|
import { Node as k, Project as A, SyntaxKind as j } from "ts-morph";
|
|
10
11
|
//#region src/cliOutput.ts
|
|
11
|
-
var
|
|
12
|
-
|
|
12
|
+
var le = () => {
|
|
13
|
+
se();
|
|
13
14
|
}, M = (e, t = {}) => {
|
|
14
15
|
console.log(JSON.stringify(e, null, t.pretty === !1 ? 0 : 2));
|
|
15
|
-
},
|
|
16
|
+
}, ue = (e) => {
|
|
16
17
|
for (let t = 0; t < e.length; t++) {
|
|
17
18
|
let n = e[t];
|
|
18
19
|
if (n === "--json" || n === "--format=json" || n === "--format" && e[t + 1] === "json") return !0;
|
|
19
20
|
}
|
|
20
21
|
return !1;
|
|
21
|
-
},
|
|
22
|
+
}, de = (e, t, n = !1) => {
|
|
22
23
|
let r = { ...e ?? {} }, i = t ?? process.env.VOLTRO_INSPECT_TOKEN;
|
|
23
24
|
if (i !== void 0 && i !== "" && (r.authorization = `Bearer ${i}`), n) {
|
|
24
25
|
let e = process.env.VOLTRO_INSPECT_WRITE_TOKEN;
|
|
25
26
|
e !== void 0 && e !== "" && (r["x-voltro-inspect-write"] = e);
|
|
26
27
|
}
|
|
27
28
|
return r;
|
|
28
|
-
},
|
|
29
|
+
}, fe = /* @__PURE__ */ new Set([
|
|
29
30
|
"POST",
|
|
30
31
|
"PUT",
|
|
31
32
|
"PATCH",
|
|
@@ -35,7 +36,7 @@ var ce = () => {
|
|
|
35
36
|
try {
|
|
36
37
|
let t = await fetch(r, {
|
|
37
38
|
method: i,
|
|
38
|
-
headers:
|
|
39
|
+
headers: de(a ? { "content-type": "application/json" } : void 0, e.inspectToken, fe.has(i)),
|
|
39
40
|
...a ? { body: JSON.stringify(n.body) } : {}
|
|
40
41
|
}), o = await t.text(), s;
|
|
41
42
|
if (o.length > 0) try {
|
|
@@ -44,7 +45,7 @@ var ce = () => {
|
|
|
44
45
|
s = o;
|
|
45
46
|
}
|
|
46
47
|
if (!t.ok) {
|
|
47
|
-
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);
|
|
48
49
|
return {
|
|
49
50
|
kind: "err",
|
|
50
51
|
name: e.name,
|
|
@@ -109,7 +110,7 @@ var ce = () => {
|
|
|
109
110
|
},
|
|
110
111
|
rest: n
|
|
111
112
|
};
|
|
112
|
-
},
|
|
113
|
+
}, pe = (e) => ({
|
|
113
114
|
id: `remote:${e.url}`,
|
|
114
115
|
name: e.name,
|
|
115
116
|
kind: "api",
|
|
@@ -120,7 +121,7 @@ var ce = () => {
|
|
|
120
121
|
status: "live",
|
|
121
122
|
logFile: "",
|
|
122
123
|
...e.token === void 0 ? {} : { inspectToken: e.token }
|
|
123
|
-
}),
|
|
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) => {
|
|
124
125
|
let t = "pretty", n = [];
|
|
125
126
|
for (let r = 0; r < e.length; r++) {
|
|
126
127
|
let i = e[r];
|
|
@@ -144,7 +145,7 @@ var ce = () => {
|
|
|
144
145
|
format: t,
|
|
145
146
|
rest: n
|
|
146
147
|
};
|
|
147
|
-
},
|
|
148
|
+
}, ge = (e, t = process.env) => {
|
|
148
149
|
let { target: n, rest: r, error: i } = P(e, t), a, o = [];
|
|
149
150
|
for (let e = 0; e < r.length; e++) {
|
|
150
151
|
let t = r[e];
|
|
@@ -164,7 +165,7 @@ var ce = () => {
|
|
|
164
165
|
rest: o,
|
|
165
166
|
...i === void 0 ? {} : { error: i }
|
|
166
167
|
};
|
|
167
|
-
},
|
|
168
|
+
}, _e = (e) => {
|
|
168
169
|
let t = "", n = !1;
|
|
169
170
|
for (let r = 0; r < e.length; r++) {
|
|
170
171
|
let i = e[r];
|
|
@@ -192,8 +193,8 @@ var ce = () => {
|
|
|
192
193
|
}
|
|
193
194
|
t += i;
|
|
194
195
|
}
|
|
195
|
-
return JSON.parse(
|
|
196
|
-
},
|
|
196
|
+
return JSON.parse(ve(t));
|
|
197
|
+
}, ve = (e) => {
|
|
197
198
|
let t = "", n = !1;
|
|
198
199
|
for (let r = 0; r < e.length; r++) {
|
|
199
200
|
let i = e[r];
|
|
@@ -221,7 +222,7 @@ var ce = () => {
|
|
|
221
222
|
t.add(e);
|
|
222
223
|
let n;
|
|
223
224
|
try {
|
|
224
|
-
n =
|
|
225
|
+
n = _e(E(e, "utf8"));
|
|
225
226
|
} catch {
|
|
226
227
|
return;
|
|
227
228
|
}
|
|
@@ -237,7 +238,7 @@ var ce = () => {
|
|
|
237
238
|
...n.compilerOptions?.paths
|
|
238
239
|
}
|
|
239
240
|
} };
|
|
240
|
-
},
|
|
241
|
+
}, ye = (e, t = "tsconfig.json") => {
|
|
241
242
|
let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
|
|
242
243
|
if (i === void 0) return;
|
|
243
244
|
let a = {};
|
|
@@ -261,11 +262,11 @@ var ce = () => {
|
|
|
261
262
|
});
|
|
262
263
|
}
|
|
263
264
|
return o.sort((e, t) => t.prefix.length - e.prefix.length);
|
|
264
|
-
},
|
|
265
|
+
}, be = (e, t) => {
|
|
265
266
|
for (let n of t) if (n.wildcard) {
|
|
266
267
|
if (e.startsWith(n.prefix)) return x(n.target, e.slice(n.prefix.length));
|
|
267
268
|
} else if (e === n.prefix) return n.target;
|
|
268
|
-
}, L = ".voltro-vendored",
|
|
269
|
+
}, L = ".voltro-vendored", xe = (e, t) => {
|
|
269
270
|
let n = [], r = /* @__PURE__ */ new Set(), i = (e, t) => {
|
|
270
271
|
let i = C(e);
|
|
271
272
|
r.has(i) || !T(i) || (r.add(i), n.push({
|
|
@@ -296,7 +297,7 @@ var ce = () => {
|
|
|
296
297
|
}
|
|
297
298
|
}
|
|
298
299
|
return n;
|
|
299
|
-
},
|
|
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 = [
|
|
300
301
|
{
|
|
301
302
|
id: "component.ui",
|
|
302
303
|
re: _,
|
|
@@ -337,7 +338,7 @@ var ce = () => {
|
|
|
337
338
|
re: p,
|
|
338
339
|
needsTest: !1
|
|
339
340
|
}
|
|
340
|
-
], R = (e) =>
|
|
341
|
+
], R = (e) => we.find((t) => t.re.test(e))?.id ?? null, Te = (e) => m.test(e), Ee = /* @__PURE__ */ new Set([
|
|
341
342
|
"useMutation",
|
|
342
343
|
"useAction",
|
|
343
344
|
"useUpload",
|
|
@@ -347,12 +348,12 @@ var ce = () => {
|
|
|
347
348
|
"useAgentStream",
|
|
348
349
|
"useResumableAgentStream",
|
|
349
350
|
"useWorkflow"
|
|
350
|
-
]),
|
|
351
|
+
]), De = /* @__PURE__ */ new Set(["defineTracking"]), Oe = /* @__PURE__ */ new Set([
|
|
351
352
|
"useSubscription",
|
|
352
353
|
"useWindowedSubscription",
|
|
353
354
|
"useRecord",
|
|
354
355
|
"useQuery"
|
|
355
|
-
]),
|
|
356
|
+
]), ke = /* @__PURE__ */ new Set([
|
|
356
357
|
"node_modules",
|
|
357
358
|
"dist",
|
|
358
359
|
"build",
|
|
@@ -363,7 +364,7 @@ var ce = () => {
|
|
|
363
364
|
".git",
|
|
364
365
|
"coverage",
|
|
365
366
|
".publish"
|
|
366
|
-
]),
|
|
367
|
+
]), Ae = /\.tsx?$/, je = (e, t = [], n = 0) => {
|
|
367
368
|
if (n > 12) return t;
|
|
368
369
|
let r;
|
|
369
370
|
try {
|
|
@@ -372,27 +373,27 @@ var ce = () => {
|
|
|
372
373
|
return t;
|
|
373
374
|
}
|
|
374
375
|
for (let i of r) {
|
|
375
|
-
if (
|
|
376
|
+
if (ke.has(i)) continue;
|
|
376
377
|
let r = x(e, i), a;
|
|
377
378
|
try {
|
|
378
379
|
a = O(r);
|
|
379
380
|
} catch {
|
|
380
381
|
continue;
|
|
381
382
|
}
|
|
382
|
-
a.isDirectory() ?
|
|
383
|
+
a.isDirectory() ? je(r, t, n + 1) : a.isFile() && Ae.test(i) && !i.endsWith(".d.ts") && t.push(r);
|
|
383
384
|
}
|
|
384
385
|
return t;
|
|
385
|
-
},
|
|
386
|
-
if (
|
|
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;
|
|
387
388
|
if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return z(e.getExpression());
|
|
388
389
|
if (k.isVariableDeclaration(e)) {
|
|
389
390
|
let t = e.getTypeNode();
|
|
390
|
-
if (t !== void 0 &&
|
|
391
|
+
if (t !== void 0 && Me.test(t.getText())) return !0;
|
|
391
392
|
let n = e.getInitializer();
|
|
392
393
|
return n === void 0 || z(n);
|
|
393
394
|
}
|
|
394
|
-
return k.isCallExpression(e) ?
|
|
395
|
-
},
|
|
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) => {
|
|
396
397
|
let t = [], n = [], r = [], i = /* @__PURE__ */ new Set();
|
|
397
398
|
for (let [a, o] of e.getExportedDeclarations()) {
|
|
398
399
|
let e = o[0];
|
|
@@ -415,7 +416,7 @@ var ce = () => {
|
|
|
415
416
|
others: r,
|
|
416
417
|
hasRuntimeExport: t.length + n.length + r.length > 0
|
|
417
418
|
};
|
|
418
|
-
}, B = [],
|
|
419
|
+
}, B = [], Ie = (e, t, n) => {
|
|
419
420
|
let r = b(e), i = [];
|
|
420
421
|
for (let e = r;; e = b(e)) {
|
|
421
422
|
let r = n.get(e);
|
|
@@ -434,8 +435,8 @@ var ce = () => {
|
|
|
434
435
|
}
|
|
435
436
|
for (let e of i) n.set(e, B);
|
|
436
437
|
return B;
|
|
437
|
-
},
|
|
438
|
-
let r = t.startsWith(".") ? C(b(e), t) :
|
|
438
|
+
}, Le = (e, t, n) => {
|
|
439
|
+
let r = t.startsWith(".") ? C(b(e), t) : be(t, n);
|
|
439
440
|
if (r === void 0) return null;
|
|
440
441
|
for (let e of [
|
|
441
442
|
r,
|
|
@@ -447,9 +448,9 @@ var ce = () => {
|
|
|
447
448
|
if (O(e).isFile()) return e;
|
|
448
449
|
} catch {}
|
|
449
450
|
return null;
|
|
450
|
-
},
|
|
451
|
+
}, Re = (e, t, n) => {
|
|
451
452
|
let r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = (e) => {
|
|
452
|
-
let i =
|
|
453
|
+
let i = Le(t, e, n);
|
|
453
454
|
return i !== null && r.push(i), i;
|
|
454
455
|
};
|
|
455
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());
|
|
@@ -476,29 +477,29 @@ var ce = () => {
|
|
|
476
477
|
namedBindings: i,
|
|
477
478
|
reExports: a
|
|
478
479
|
};
|
|
479
|
-
},
|
|
480
|
+
}, ze = (e) => {
|
|
480
481
|
let t = /* @__PURE__ */ new Map();
|
|
481
482
|
for (let n of e.getDescendantsOfKind(j.CallExpression)) {
|
|
482
483
|
let e = n.getExpression(), r = k.isPropertyAccessExpression(e) ? e.getName() : e.getText();
|
|
483
484
|
t.set(r, (t.get(r) ?? 0) + 1);
|
|
484
485
|
}
|
|
485
486
|
return t;
|
|
486
|
-
},
|
|
487
|
+
}, Be = (e) => {
|
|
487
488
|
if (e.length <= 1) return e.length === 1;
|
|
488
489
|
let t = [...e].sort((e, t) => e.length - t.length)[0];
|
|
489
490
|
return e.every((e) => e.startsWith(t));
|
|
490
|
-
},
|
|
491
|
+
}, Ve = (e, t) => {
|
|
491
492
|
let n = b(t);
|
|
492
493
|
return e.startsWith(`${n}${w}`) || b(e) === n;
|
|
493
494
|
}, V = (e) => {
|
|
494
|
-
let t =
|
|
495
|
+
let t = je(e);
|
|
495
496
|
if (t.length === 0) return {
|
|
496
497
|
findings: [],
|
|
497
498
|
declared: 0,
|
|
498
499
|
scanned: 0,
|
|
499
500
|
vendored: []
|
|
500
501
|
};
|
|
501
|
-
let n =
|
|
502
|
+
let n = xe(e, t), r = t.filter((e) => !Se(e, n));
|
|
502
503
|
if (r.length === 0) return {
|
|
503
504
|
findings: [],
|
|
504
505
|
declared: 0,
|
|
@@ -522,7 +523,7 @@ var ce = () => {
|
|
|
522
523
|
} catch {
|
|
523
524
|
continue;
|
|
524
525
|
}
|
|
525
|
-
s.set(t,
|
|
526
|
+
s.set(t, Fe(r)), c.set(t, Re(r, t, Ie(t, e, u))), l.set(t, ze(r));
|
|
526
527
|
}
|
|
527
528
|
let d = /* @__PURE__ */ new Map();
|
|
528
529
|
for (let [e, t] of c) for (let n of t.localTargets) {
|
|
@@ -554,11 +555,11 @@ var ce = () => {
|
|
|
554
555
|
for (let e of r) {
|
|
555
556
|
let t = R(e), n = s.get(e), r = c.get(e);
|
|
556
557
|
if (n === void 0 || r === void 0) continue;
|
|
557
|
-
let i =
|
|
558
|
+
let i = Te(e);
|
|
558
559
|
t !== null && !i && (p += 1);
|
|
559
560
|
let u = d.get(e) ?? [];
|
|
560
561
|
if ((t === "component" || t === "component.ui") && !i) {
|
|
561
|
-
let t =
|
|
562
|
+
let t = Be(n.components);
|
|
562
563
|
(n.components.length === 0 || !t) && a.push({
|
|
563
564
|
rule: "component/one-per-file",
|
|
564
565
|
file: o(e),
|
|
@@ -596,7 +597,7 @@ var ce = () => {
|
|
|
596
597
|
].join(", ")}`,
|
|
597
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"
|
|
598
599
|
}), t === "internal") {
|
|
599
|
-
let t = u.filter((t) => !
|
|
600
|
+
let t = u.filter((t) => !Ve(t, e));
|
|
600
601
|
t.length > 0 && a.push({
|
|
601
602
|
rule: "internal/foreign-import",
|
|
602
603
|
file: o(e),
|
|
@@ -606,7 +607,7 @@ var ce = () => {
|
|
|
606
607
|
});
|
|
607
608
|
}
|
|
608
609
|
if (t === "fixture") {
|
|
609
|
-
let t = u.filter((e) => !
|
|
610
|
+
let t = u.filter((e) => !Te(e) && R(e) !== "fixture");
|
|
610
611
|
t.length > 0 && a.push({
|
|
611
612
|
rule: "fixture/production-import",
|
|
612
613
|
file: o(e),
|
|
@@ -616,7 +617,7 @@ var ce = () => {
|
|
|
616
617
|
});
|
|
617
618
|
}
|
|
618
619
|
if (t === "component.ui" && !i) {
|
|
619
|
-
let t = [...r.namedBindings].filter((e) =>
|
|
620
|
+
let t = [...r.namedBindings].filter((e) => Ee.has(e)).sort();
|
|
620
621
|
t.length > 0 && a.push({
|
|
621
622
|
rule: "ui/no-write",
|
|
622
623
|
file: o(e),
|
|
@@ -651,7 +652,7 @@ var ce = () => {
|
|
|
651
652
|
detail: t === 0 ? "declares a store file but calls no defineStore()" : `declares ${t} stores`,
|
|
652
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"
|
|
653
654
|
});
|
|
654
|
-
let n = [...l.get(e)?.keys() ?? []].filter((e) =>
|
|
655
|
+
let n = [...l.get(e)?.keys() ?? []].filter((e) => Oe.has(e)).sort();
|
|
655
656
|
n.length > 0 && a.push({
|
|
656
657
|
rule: "store/mirrors-server-state",
|
|
657
658
|
file: o(e),
|
|
@@ -661,7 +662,7 @@ var ce = () => {
|
|
|
661
662
|
});
|
|
662
663
|
}
|
|
663
664
|
if (t !== "tracking" && !i) {
|
|
664
|
-
let t = [...l.get(e)?.keys() ?? []].filter((e) =>
|
|
665
|
+
let t = [...l.get(e)?.keys() ?? []].filter((e) => De.has(e)).sort();
|
|
665
666
|
t.length > 0 && a.push({
|
|
666
667
|
rule: "tracking/outside-tracking-file",
|
|
667
668
|
file: o(e),
|
|
@@ -670,7 +671,7 @@ var ce = () => {
|
|
|
670
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"
|
|
671
672
|
});
|
|
672
673
|
}
|
|
673
|
-
if (
|
|
674
|
+
if (we.find((e) => e.id === t)?.needsTest === !0 && !i) {
|
|
674
675
|
let t = e.replace(/\.tsx?$/, ""), n = e.endsWith("x") ? "tsx" : "ts";
|
|
675
676
|
["test", "spec"].flatMap((e) => [`${t}.${e}.ts`, `${t}.${e}.tsx`]).some((e) => {
|
|
676
677
|
try {
|
|
@@ -693,10 +694,10 @@ var ce = () => {
|
|
|
693
694
|
scanned: r.length,
|
|
694
695
|
vendored: n
|
|
695
696
|
};
|
|
696
|
-
},
|
|
697
|
+
}, He = 10, Ue = (e) => {
|
|
697
698
|
let { findings: t, declared: n, scanned: r, vendored: i } = V(e);
|
|
698
699
|
console.log("");
|
|
699
|
-
for (let t of
|
|
700
|
+
for (let t of Ce(e, i)) console.log(`· not ours, exempt: ${t}`);
|
|
700
701
|
if (r === 0) return console.log("• file-taxonomy scan: no sources here — nothing to check."), 0;
|
|
701
702
|
if (t.length === 0) return console.log(`✓ file-taxonomy scan: ${n} declared file(s) keep their contract (${r} scanned).`), 0;
|
|
702
703
|
let a = /* @__PURE__ */ new Map();
|
|
@@ -708,12 +709,12 @@ var ce = () => {
|
|
|
708
709
|
console.log(`• file taxonomy: ${t.length} finding(s) — ${o} error, ${t.length - o} advisory (${n} declared / ${r} scanned):`);
|
|
709
710
|
for (let [e, t] of [...a].sort()) {
|
|
710
711
|
console.log(` ${t[0].severity === "error" ? "✗" : "·"} ${e} (${t.length})`);
|
|
711
|
-
for (let e of t.slice(0,
|
|
712
|
-
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;
|
|
713
714
|
n > 0 && console.log(` (+${n} more — \`voltro doctor --json\` prints every one.)`), console.log(` → ${t[0].fix}`);
|
|
714
715
|
}
|
|
715
716
|
return t.length;
|
|
716
|
-
},
|
|
717
|
+
}, We = /* @__PURE__ */ new Set([
|
|
717
718
|
"eq",
|
|
718
719
|
"ne",
|
|
719
720
|
"gt",
|
|
@@ -731,10 +732,10 @@ var ce = () => {
|
|
|
731
732
|
"endsWith",
|
|
732
733
|
"between",
|
|
733
734
|
"jsonField"
|
|
734
|
-
]),
|
|
735
|
+
]), Ge = (e) => {
|
|
735
736
|
if (k.isPropertyAccessExpression(e)) return e.getName();
|
|
736
737
|
if (k.isIdentifier(e)) return e.getText();
|
|
737
|
-
},
|
|
738
|
+
}, Ke = (e) => {
|
|
738
739
|
let t = e;
|
|
739
740
|
for (; k.isCallExpression(t);) {
|
|
740
741
|
let e = t.getExpression();
|
|
@@ -742,7 +743,7 @@ var ce = () => {
|
|
|
742
743
|
t = e.getExpression();
|
|
743
744
|
}
|
|
744
745
|
return t;
|
|
745
|
-
},
|
|
746
|
+
}, qe = (e, t, n) => {
|
|
746
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({
|
|
747
748
|
useInMemoryFileSystem: !0,
|
|
748
749
|
skipAddingFilesFromTsConfig: !0
|
|
@@ -760,7 +761,7 @@ var ce = () => {
|
|
|
760
761
|
for (let t of e.getDescendantsOfKind(j.CallExpression)) {
|
|
761
762
|
let e = t.getExpression();
|
|
762
763
|
if (!k.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
|
|
763
|
-
let n =
|
|
764
|
+
let n = Ge(Ke(e.getExpression()));
|
|
764
765
|
if (n === void 0 || !r.has(n)) {
|
|
765
766
|
let t = e.getExpression();
|
|
766
767
|
if (k.isCallExpression(t)) {
|
|
@@ -783,7 +784,7 @@ var ce = () => {
|
|
|
783
784
|
else for (let e of [u, ...u.getDescendantsOfKind(j.CallExpression)]) {
|
|
784
785
|
if (!k.isCallExpression(e)) continue;
|
|
785
786
|
let t = e.getExpression(), n = k.isIdentifier(t) ? t.getText() : k.isPropertyAccessExpression(t) ? t.getName() : void 0;
|
|
786
|
-
if (n === void 0 || !
|
|
787
|
+
if (n === void 0 || !We.has(n)) continue;
|
|
787
788
|
let r = e.getArguments()[0];
|
|
788
789
|
r !== void 0 && k.isStringLiteral(r) && l.push({
|
|
789
790
|
column: r.getLiteralValue(),
|
|
@@ -804,7 +805,7 @@ var ce = () => {
|
|
|
804
805
|
issues: a,
|
|
805
806
|
checked: o
|
|
806
807
|
};
|
|
807
|
-
}, H = 12,
|
|
808
|
+
}, H = 12, Je = (e) => {
|
|
808
809
|
if (e.checked === 0) return null;
|
|
809
810
|
if (e.issues.length === 0) return `predicate columns: ${e.checked} checked — every filtered column exists on its table`;
|
|
810
811
|
let t = [`predicate columns: ${e.issues.length} filter${e.issues.length, "s"} on a column that does not exist (${e.checked} checked)`];
|
|
@@ -813,7 +814,7 @@ var ce = () => {
|
|
|
813
814
|
t.push(` ${n.file}:${n.line} '${n.table}' has no column '${n.column}'`), t.push(` columns: ${e}${r}`);
|
|
814
815
|
}
|
|
815
816
|
return t.push(" This type-checks today and fails at runtime as a bare SQL error."), t.join("\n");
|
|
816
|
-
},
|
|
817
|
+
}, Ye = /* @__PURE__ */ new Set([
|
|
817
818
|
"id",
|
|
818
819
|
"tenantId",
|
|
819
820
|
"tenant_id",
|
|
@@ -833,7 +834,7 @@ var ce = () => {
|
|
|
833
834
|
"idempotency_id",
|
|
834
835
|
"idempotencyKey",
|
|
835
836
|
"idempotency_key"
|
|
836
|
-
]),
|
|
837
|
+
]), Xe = /* @__PURE__ */ new Set([
|
|
837
838
|
"id",
|
|
838
839
|
"createdAt",
|
|
839
840
|
"created_at",
|
|
@@ -843,10 +844,10 @@ var ce = () => {
|
|
|
843
844
|
"deleted_at",
|
|
844
845
|
"tenantId",
|
|
845
846
|
"tenant_id"
|
|
846
|
-
]),
|
|
847
|
+
]), Ze = (e) => Ye.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Qe = (e) => e.type === "reference", $e = (e) => {
|
|
847
848
|
let t = [];
|
|
848
849
|
for (let n of e) {
|
|
849
|
-
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);
|
|
850
851
|
if (r.length === 0) continue;
|
|
851
852
|
let i = new Set(n.primaryKey), a = r.filter((e) => i.has(e));
|
|
852
853
|
if (n.primaryKey.length >= 2 && a.length > 0) {
|
|
@@ -857,7 +858,7 @@ var ce = () => {
|
|
|
857
858
|
});
|
|
858
859
|
continue;
|
|
859
860
|
}
|
|
860
|
-
if (e.some(
|
|
861
|
+
if (e.some(Qe) && e.length >= 2) {
|
|
861
862
|
t.push({
|
|
862
863
|
table: n.name,
|
|
863
864
|
columns: r,
|
|
@@ -865,7 +866,7 @@ var ce = () => {
|
|
|
865
866
|
});
|
|
866
867
|
continue;
|
|
867
868
|
}
|
|
868
|
-
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({
|
|
869
870
|
table: n.name,
|
|
870
871
|
columns: r,
|
|
871
872
|
signal: "link-only"
|
|
@@ -875,20 +876,20 @@ var ce = () => {
|
|
|
875
876
|
issues: t,
|
|
876
877
|
tablesChecked: e.length
|
|
877
878
|
};
|
|
878
|
-
},
|
|
879
|
+
}, et = {
|
|
879
880
|
"composite-pk": "part of a composite primary key",
|
|
880
881
|
"reference-sibling": "sits beside a wired reference() on this table",
|
|
881
882
|
"link-only": "this table is nothing but link columns"
|
|
882
|
-
},
|
|
883
|
+
}, tt = (e) => {
|
|
883
884
|
if (e.tablesChecked === 0) return null;
|
|
884
885
|
if (e.issues.length === 0) return `junction FKs: ${e.tablesChecked} table${e.tablesChecked === 1 ? "" : "s"} checked — every link column is a wired reference()`;
|
|
885
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()`];
|
|
886
887
|
for (let t of e.issues) {
|
|
887
888
|
let e = t.columns.map((e) => `'${e}'`).join(", ");
|
|
888
|
-
n.push(` '${t.table}': ${e} (${
|
|
889
|
+
n.push(` '${t.table}': ${e} (${et[t.signal]})`);
|
|
889
890
|
}
|
|
890
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");
|
|
891
|
-
},
|
|
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) => {
|
|
892
893
|
let t = [], n = [];
|
|
893
894
|
for (let r of e) t.push({
|
|
894
895
|
event: r.name,
|
|
@@ -897,7 +898,7 @@ var ce = () => {
|
|
|
897
898
|
event: r.name,
|
|
898
899
|
kind: "wide-key",
|
|
899
900
|
keyFieldCount: r.keyFieldCount
|
|
900
|
-
}), r.hasWebhook &&
|
|
901
|
+
}), r.hasWebhook && it(r.name) && n.push({
|
|
901
902
|
event: r.name,
|
|
902
903
|
kind: "webhook-high-rate"
|
|
903
904
|
});
|
|
@@ -906,26 +907,26 @@ var ce = () => {
|
|
|
906
907
|
modes: t,
|
|
907
908
|
warnings: n
|
|
908
909
|
};
|
|
909
|
-
},
|
|
910
|
+
}, ot = (e) => {
|
|
910
911
|
if (e.eventsChecked === 0) return null;
|
|
911
912
|
let t = e.eventsChecked, n = [`event delivery: ${t} declared event${t === 1 ? "" : "s"} — the mode decides what a MISSING message means`];
|
|
912
913
|
for (let t of e.modes) n.push(` '${t.event}': ${t.delivery}`);
|
|
913
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");
|
|
914
|
-
},
|
|
915
|
+
}, st = (e) => {
|
|
915
916
|
if (e.warnings.length === 0) return null;
|
|
916
917
|
let t = e.warnings.length, n = [`events that may not scale as declared: ${t} finding${t === 1 ? "" : "s"}`];
|
|
917
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."));
|
|
918
919
|
return n.push(" WARNING, not a fault — a producer may intend the fan-out. `voltro doctor --json` prints every finding."), n.join("\n");
|
|
919
|
-
},
|
|
920
|
+
}, ct = /* @__PURE__ */ new Set([
|
|
920
921
|
"start",
|
|
921
922
|
"run",
|
|
922
923
|
"child"
|
|
923
|
-
]),
|
|
924
|
+
]), lt = (e) => k.isCallExpression(e) && (() => {
|
|
924
925
|
let t = e.getExpression();
|
|
925
|
-
if (!k.isPropertyAccessExpression(t) || !
|
|
926
|
+
if (!k.isPropertyAccessExpression(t) || !ct.has(t.getName())) return !1;
|
|
926
927
|
let n = t.getExpression();
|
|
927
928
|
return k.isPropertyAccessExpression(n) ? n.getName() === "workflows" : k.isIdentifier(n) && n.getText() === "workflows";
|
|
928
|
-
})(),
|
|
929
|
+
})(), ut = (e, t, n) => {
|
|
929
930
|
let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new A({
|
|
930
931
|
useInMemoryFileSystem: !0,
|
|
931
932
|
skipAddingFilesFromTsConfig: !0
|
|
@@ -941,7 +942,7 @@ var ce = () => {
|
|
|
941
942
|
}
|
|
942
943
|
let u = S(n, t);
|
|
943
944
|
for (let t of e.getDescendantsOfKind(j.CallExpression)) {
|
|
944
|
-
if (!
|
|
945
|
+
if (!lt(t)) continue;
|
|
945
946
|
c++;
|
|
946
947
|
let e = t.getStartLineNumber(), [n, l] = t.getArguments();
|
|
947
948
|
if (n === void 0 || !k.isStringLiteral(n)) {
|
|
@@ -1025,7 +1026,7 @@ var ce = () => {
|
|
|
1025
1026
|
checked: s,
|
|
1026
1027
|
callSites: c
|
|
1027
1028
|
};
|
|
1028
|
-
},
|
|
1029
|
+
}, dt = (e) => {
|
|
1029
1030
|
if (e.callSites === 0) return null;
|
|
1030
1031
|
let t = [];
|
|
1031
1032
|
t.push(`workflow starts: ${e.checked}/${e.callSites} call sites checked`);
|
|
@@ -1038,7 +1039,7 @@ var ce = () => {
|
|
|
1038
1039
|
for (let n of e.unchecked) t.push(` ${n.file}:${n.line} — ${n.reason}`);
|
|
1039
1040
|
}
|
|
1040
1041
|
return e.issues.length === 0 && e.unchecked.length === 0 && t.push(" every call site matches its workflow"), t.join("\n");
|
|
1041
|
-
},
|
|
1042
|
+
}, ft = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, U = "VOLTRO_FIELD_ENCRYPTION_KEY", pt = (e) => {
|
|
1042
1043
|
let t;
|
|
1043
1044
|
for (let [n, r] of e.sources) if (t === void 0 && /\bfieldEncryption\b/.test(r) && (t = {
|
|
1044
1045
|
reason: "fieldEncryption",
|
|
@@ -1051,7 +1052,7 @@ var ce = () => {
|
|
|
1051
1052
|
for (let [, t] of e.envFiles) if (t.includes(U)) return;
|
|
1052
1053
|
return t;
|
|
1053
1054
|
}
|
|
1054
|
-
},
|
|
1055
|
+
}, mt = (e) => [
|
|
1055
1056
|
`✗ ${U} is named nowhere under this app root, and the app encrypts columns at rest`,
|
|
1056
1057
|
e.reason === "fieldEncryption" ? ` (\`fieldEncryption\` in ${e.file})` : ` (an \`.encrypted()\` column in ${e.file})`,
|
|
1057
1058
|
"",
|
|
@@ -1068,14 +1069,14 @@ var ce = () => {
|
|
|
1068
1069
|
" reaches your deploy target is not knowable from here — a secret manager",
|
|
1069
1070
|
" or a CI variable is not a file. Naming it nowhere is conclusive; naming",
|
|
1070
1071
|
" it somewhere is not a guarantee."
|
|
1071
|
-
].join("\n"),
|
|
1072
|
+
].join("\n"), ht = (e) => {
|
|
1072
1073
|
if (e.catalogCodes.length === 0) return;
|
|
1073
1074
|
let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
|
|
1074
1075
|
if (n.length !== 0) return {
|
|
1075
1076
|
orphaned: n,
|
|
1076
1077
|
noneDeclared: t.size === 0
|
|
1077
1078
|
};
|
|
1078
|
-
},
|
|
1079
|
+
}, gt = (e, t) => {
|
|
1079
1080
|
let n = e.orphaned.join(", ");
|
|
1080
1081
|
return e.noneDeclared ? [
|
|
1081
1082
|
`⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
|
|
@@ -1087,33 +1088,33 @@ var ce = () => {
|
|
|
1087
1088
|
" …or delete the files. A catalog that is never loaded looks from the inside",
|
|
1088
1089
|
" exactly like one that works, which is why this is worth a line."
|
|
1089
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)."];
|
|
1090
|
-
},
|
|
1091
|
+
}, _t = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), vt = /* @__PURE__ */ new Set([
|
|
1091
1092
|
"requireScope",
|
|
1092
1093
|
"requireAnyScope",
|
|
1093
1094
|
"requireAuth",
|
|
1094
1095
|
"assertScope",
|
|
1095
1096
|
"assertCan",
|
|
1096
1097
|
"can"
|
|
1097
|
-
]),
|
|
1098
|
+
]), yt = (e) => {
|
|
1098
1099
|
let t = /* @__PURE__ */ new Set();
|
|
1099
|
-
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]);
|
|
1100
1101
|
return t;
|
|
1101
|
-
},
|
|
1102
|
+
}, bt = (e, t) => {
|
|
1102
1103
|
for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
|
|
1103
|
-
},
|
|
1104
|
+
}, xt = (e) => {
|
|
1104
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);
|
|
1105
1106
|
return t && n;
|
|
1106
|
-
},
|
|
1107
|
+
}, St = (e, t, n) => {
|
|
1107
1108
|
if (e.hasDeclaredGuards) return { kind: "declared-guard" };
|
|
1108
|
-
let r =
|
|
1109
|
-
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" } : {
|
|
1110
1111
|
kind: "vocabulary-guard",
|
|
1111
1112
|
name: i
|
|
1112
1113
|
};
|
|
1113
|
-
},
|
|
1114
|
+
}, Ct = (e, t, n) => {
|
|
1114
1115
|
let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
|
|
1115
1116
|
return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
|
|
1116
|
-
},
|
|
1117
|
+
}, wt = (e, t, n) => {
|
|
1117
1118
|
switch (t.kind) {
|
|
1118
1119
|
case "declared-guard": return "guards: on the descriptor";
|
|
1119
1120
|
case "vocabulary-guard": return `calls ${t.name}()`;
|
|
@@ -1121,15 +1122,15 @@ var ce = () => {
|
|
|
1121
1122
|
case "allowlisted": return "accepted by a previous run";
|
|
1122
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`;
|
|
1123
1124
|
}
|
|
1124
|
-
},
|
|
1125
|
+
}, Tt = (e) => {
|
|
1125
1126
|
let t = e.executors.map((t) => {
|
|
1126
|
-
let n =
|
|
1127
|
+
let n = St(t, e.guardVocabulary, e.allowlist);
|
|
1127
1128
|
return {
|
|
1128
1129
|
tag: t.tag,
|
|
1129
1130
|
path: t.path,
|
|
1130
1131
|
verdict: n,
|
|
1131
|
-
severity:
|
|
1132
|
-
why:
|
|
1132
|
+
severity: Ct(t, e.tenantScopedTables, e.referencedTables),
|
|
1133
|
+
why: wt(t, n, e.rowFilterRegistered === !0)
|
|
1133
1134
|
};
|
|
1134
1135
|
}), n = (e) => t.filter((t) => t.verdict.kind === e).length;
|
|
1135
1136
|
return {
|
|
@@ -1143,7 +1144,7 @@ var ce = () => {
|
|
|
1143
1144
|
},
|
|
1144
1145
|
unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
|
|
1145
1146
|
};
|
|
1146
|
-
},
|
|
1147
|
+
}, Et = [
|
|
1147
1148
|
"# voltro doctor — authz allowlist",
|
|
1148
1149
|
"#",
|
|
1149
1150
|
"# Two KINDS of line, and the difference is the point:",
|
|
@@ -1168,18 +1169,18 @@ var ce = () => {
|
|
|
1168
1169
|
"# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
|
|
1169
1170
|
"# lines for executors that are newly unchecked.",
|
|
1170
1171
|
""
|
|
1171
|
-
],
|
|
1172
|
+
], Dt = (e) => {
|
|
1172
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));
|
|
1173
1174
|
return [
|
|
1174
|
-
...
|
|
1175
|
+
...Et,
|
|
1175
1176
|
...n,
|
|
1176
1177
|
...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
|
|
1177
1178
|
...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
|
|
1178
1179
|
""
|
|
1179
1180
|
].join("\n");
|
|
1180
|
-
},
|
|
1181
|
+
}, Ot = (e, t) => {
|
|
1181
1182
|
let n = [...new Set(t)].sort();
|
|
1182
|
-
if (e === void 0) return
|
|
1183
|
+
if (e === void 0) return Dt(n.map((e) => ({ tag: e })));
|
|
1183
1184
|
let r = W(e).tags, i = n.filter((e) => !r.has(e));
|
|
1184
1185
|
return i.length === 0 ? e : [
|
|
1185
1186
|
e.replace(/\n+$/, ""),
|
|
@@ -1189,7 +1190,7 @@ var ce = () => {
|
|
|
1189
1190
|
...i,
|
|
1190
1191
|
""
|
|
1191
1192
|
].join("\n");
|
|
1192
|
-
},
|
|
1193
|
+
}, kt = (e, t) => {
|
|
1193
1194
|
let n = [...new Set(t)];
|
|
1194
1195
|
if (e === void 0) return {
|
|
1195
1196
|
added: n.length,
|
|
@@ -1202,7 +1203,7 @@ var ce = () => {
|
|
|
1202
1203
|
kept: r.size,
|
|
1203
1204
|
removed: 0
|
|
1204
1205
|
};
|
|
1205
|
-
},
|
|
1206
|
+
}, At = class extends Error {
|
|
1206
1207
|
tag;
|
|
1207
1208
|
constructor(e) {
|
|
1208
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";
|
|
@@ -1220,7 +1221,7 @@ var ce = () => {
|
|
|
1220
1221
|
continue;
|
|
1221
1222
|
}
|
|
1222
1223
|
let a = r[2].trim();
|
|
1223
|
-
if (a.length === 0) throw new
|
|
1224
|
+
if (a.length === 0) throw new At(i);
|
|
1224
1225
|
t.push({
|
|
1225
1226
|
tag: i,
|
|
1226
1227
|
reviewed: a
|
|
@@ -1230,13 +1231,13 @@ var ce = () => {
|
|
|
1230
1231
|
tags: new Set(t.map((e) => e.tag)),
|
|
1231
1232
|
entries: t
|
|
1232
1233
|
};
|
|
1233
|
-
},
|
|
1234
|
+
}, jt = (e) => {
|
|
1234
1235
|
let t = e.indexOf(".");
|
|
1235
1236
|
return t <= 0 ? void 0 : e.slice(0, t);
|
|
1236
|
-
},
|
|
1237
|
+
}, Mt = (e, t) => {
|
|
1237
1238
|
let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
|
|
1238
1239
|
for (let e of t.rpcTags) {
|
|
1239
|
-
let t =
|
|
1240
|
+
let t = jt(e);
|
|
1240
1241
|
if (t === void 0) continue;
|
|
1241
1242
|
let n = i.get(t);
|
|
1242
1243
|
n ? n.push(e) : i.set(t, [e]);
|
|
@@ -1265,7 +1266,7 @@ var ce = () => {
|
|
|
1265
1266
|
});
|
|
1266
1267
|
continue;
|
|
1267
1268
|
}
|
|
1268
|
-
let s = new Set(o.rpcTags.map(
|
|
1269
|
+
let s = new Set(o.rpcTags.map(jt).filter((e) => e !== void 0));
|
|
1269
1270
|
for (let e of s) {
|
|
1270
1271
|
let t = i.get(e);
|
|
1271
1272
|
t !== void 0 && a.push({
|
|
@@ -1278,12 +1279,12 @@ var ce = () => {
|
|
|
1278
1279
|
}
|
|
1279
1280
|
}
|
|
1280
1281
|
return a;
|
|
1281
|
-
},
|
|
1282
|
+
}, Nt = (e, t) => {
|
|
1282
1283
|
let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
|
|
1283
1284
|
if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
|
|
1284
1285
|
for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
|
|
1285
1286
|
return r;
|
|
1286
|
-
},
|
|
1287
|
+
}, Pt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Ft = (e) => {
|
|
1287
1288
|
let t = re(e), n = ne(e);
|
|
1288
1289
|
return {
|
|
1289
1290
|
root: e,
|
|
@@ -1292,19 +1293,19 @@ var ce = () => {
|
|
|
1292
1293
|
apiBundle: n,
|
|
1293
1294
|
apiBundlePresent: T(n)
|
|
1294
1295
|
};
|
|
1295
|
-
},
|
|
1296
|
+
}, It = [
|
|
1296
1297
|
"Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
|
|
1297
1298
|
"Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
|
|
1298
1299
|
"(Generated Dockerfiles from `voltro create-project` already do this.)"
|
|
1299
|
-
].join("\n "),
|
|
1300
|
-
let n =
|
|
1301
|
-
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;
|
|
1302
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})`);
|
|
1303
1304
|
let r = process.env.NODE_ENV === "production";
|
|
1304
1305
|
if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
|
|
1305
1306
|
let i = t || r;
|
|
1306
|
-
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${
|
|
1307
|
-
},
|
|
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) => {
|
|
1308
1309
|
let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
|
|
1309
1310
|
`${e}.ts`,
|
|
1310
1311
|
`${e}.tsx`,
|
|
@@ -1324,13 +1325,13 @@ var ce = () => {
|
|
|
1324
1325
|
}
|
|
1325
1326
|
let o = [];
|
|
1326
1327
|
for (let t of r) {
|
|
1327
|
-
let n = t.startsWith(".") ? C(b(e), t) :
|
|
1328
|
+
let n = t.startsWith(".") ? C(b(e), t) : be(t, i);
|
|
1328
1329
|
if (n === void 0) continue;
|
|
1329
1330
|
let r = a(n);
|
|
1330
1331
|
r !== void 0 && o.push(r);
|
|
1331
1332
|
}
|
|
1332
1333
|
return o;
|
|
1333
|
-
}, 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);
|
|
1334
1335
|
for (; c.length > 0;) {
|
|
1335
1336
|
let e = c.shift();
|
|
1336
1337
|
if (!s.has(e)) {
|
|
@@ -1343,11 +1344,11 @@ var ce = () => {
|
|
|
1343
1344
|
let d = /* @__PURE__ */ new Set();
|
|
1344
1345
|
for (let e of s) l.has(e) || u.has(e) || d.add(e);
|
|
1345
1346
|
return d;
|
|
1346
|
-
},
|
|
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) => {
|
|
1347
1348
|
if (e.src.includes(t)) return !0;
|
|
1348
1349
|
let n = t.replace(/^@voltro\/plugin-/, "");
|
|
1349
1350
|
return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
|
|
1350
|
-
},
|
|
1351
|
+
}, Ht = (e, t) => {
|
|
1351
1352
|
let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
|
|
1352
1353
|
for (; (i = r.exec(e)) !== null;) {
|
|
1353
1354
|
let t = i.index + i[0].length, r = 1, a = t;
|
|
@@ -1355,10 +1356,10 @@ var ce = () => {
|
|
|
1355
1356
|
n.push(e.slice(t, a - 1));
|
|
1356
1357
|
}
|
|
1357
1358
|
return n;
|
|
1358
|
-
},
|
|
1359
|
+
}, Ut = (e, t) => {
|
|
1359
1360
|
let n = t.getSymbol()?.getDeclarations()?.[0];
|
|
1360
1361
|
return n === void 0 ? [] : e.getDescendantsOfKind(j.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
|
|
1361
|
-
},
|
|
1362
|
+
}, Wt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Gt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Kt = [
|
|
1362
1363
|
"Id",
|
|
1363
1364
|
"ID",
|
|
1364
1365
|
"Hash",
|
|
@@ -1384,7 +1385,7 @@ var ce = () => {
|
|
|
1384
1385
|
"Comment",
|
|
1385
1386
|
"Provider",
|
|
1386
1387
|
"Format"
|
|
1387
|
-
],
|
|
1388
|
+
], qt = RegExp(`(?:${Kt.join("|")})$|_(?:${Kt.map((e) => e.toLowerCase()).join("|")})$`), Jt = [
|
|
1388
1389
|
{
|
|
1389
1390
|
id: "hardcoded-framework-cookie",
|
|
1390
1391
|
scope: "client",
|
|
@@ -1397,7 +1398,7 @@ var ce = () => {
|
|
|
1397
1398
|
scope: "server",
|
|
1398
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",
|
|
1399
1400
|
use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
|
|
1400
|
-
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"))
|
|
1401
1402
|
},
|
|
1402
1403
|
{
|
|
1403
1404
|
id: "subject-write-no-guard",
|
|
@@ -1405,8 +1406,8 @@ var ce = () => {
|
|
|
1405
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",
|
|
1406
1407
|
use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
|
|
1407
1408
|
match: ({ src: e, path: t }) => {
|
|
1408
|
-
if (!
|
|
1409
|
-
let n =
|
|
1409
|
+
if (!Gt.test(t)) return !1;
|
|
1410
|
+
let n = zt(e);
|
|
1410
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);
|
|
1411
1412
|
}
|
|
1412
1413
|
},
|
|
@@ -1415,7 +1416,7 @@ var ce = () => {
|
|
|
1415
1416
|
scope: "server",
|
|
1416
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)",
|
|
1417
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",
|
|
1418
|
-
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
|
|
1419
1420
|
},
|
|
1420
1421
|
{
|
|
1421
1422
|
id: "form",
|
|
@@ -1450,7 +1451,7 @@ var ce = () => {
|
|
|
1450
1451
|
scope: "client",
|
|
1451
1452
|
smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
|
|
1452
1453
|
use: "useDerived — dependency-tracked, referentially stable",
|
|
1453
|
-
match: ({ src: e }) =>
|
|
1454
|
+
match: ({ src: e }) => Bt(e, /\buseSubscription\s*[(<]/g) >= 3 && Bt(e, /\buseMemo\s*\(/g) >= 3
|
|
1454
1455
|
},
|
|
1455
1456
|
{
|
|
1456
1457
|
id: "next-shim",
|
|
@@ -1475,7 +1476,7 @@ var ce = () => {
|
|
|
1475
1476
|
match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
|
|
1476
1477
|
let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
|
|
1477
1478
|
return t === void 0 ? [] : k.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
|
|
1478
|
-
}).some((t) =>
|
|
1479
|
+
}).some((t) => Ut(e, t).some((e) => {
|
|
1479
1480
|
let t = e.getParent();
|
|
1480
1481
|
if (k.isBinaryExpression(t)) {
|
|
1481
1482
|
let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
|
|
@@ -1526,7 +1527,7 @@ var ce = () => {
|
|
|
1526
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)",
|
|
1527
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) => {
|
|
1528
1529
|
let n = t[1];
|
|
1529
|
-
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);
|
|
1530
1531
|
}))
|
|
1531
1532
|
},
|
|
1532
1533
|
{
|
|
@@ -1536,7 +1537,7 @@ var ce = () => {
|
|
|
1536
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",
|
|
1537
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) => {
|
|
1538
1539
|
let n = t[1];
|
|
1539
|
-
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);
|
|
1540
1541
|
}))
|
|
1541
1542
|
},
|
|
1542
1543
|
{
|
|
@@ -1601,8 +1602,8 @@ var ce = () => {
|
|
|
1601
1602
|
smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
|
|
1602
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",
|
|
1603
1604
|
match: ({ file: e, testSources: t }) => G(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
|
|
1604
|
-
let t =
|
|
1605
|
-
return
|
|
1605
|
+
let t = zt(e);
|
|
1606
|
+
return Ht(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
|
|
1606
1607
|
})
|
|
1607
1608
|
},
|
|
1608
1609
|
{
|
|
@@ -1612,14 +1613,14 @@ var ce = () => {
|
|
|
1612
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",
|
|
1613
1614
|
match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
|
|
1614
1615
|
}
|
|
1615
|
-
],
|
|
1616
|
+
], Yt = (e) => q(e), Xt = (e, t) => {
|
|
1616
1617
|
let n = /* @__PURE__ */ new Set();
|
|
1617
1618
|
for (let r of t) {
|
|
1618
1619
|
let t = S(e, r), i = t.split(/[\\/]/)[0];
|
|
1619
1620
|
n.add(i !== void 0 && i !== t ? i : ".");
|
|
1620
1621
|
}
|
|
1621
1622
|
return [...n].sort();
|
|
1622
|
-
},
|
|
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) => {
|
|
1623
1624
|
if (n > 12 || K.test(e)) return t;
|
|
1624
1625
|
let r;
|
|
1625
1626
|
try {
|
|
@@ -1631,8 +1632,8 @@ var ce = () => {
|
|
|
1631
1632
|
let r = x(e, i.name);
|
|
1632
1633
|
if (i.isDirectory()) {
|
|
1633
1634
|
if (i.name.startsWith(".")) continue;
|
|
1634
|
-
|
|
1635
|
-
} else
|
|
1635
|
+
$t(r, t, n + 1);
|
|
1636
|
+
} else ft.test(i.name) && t.push(r);
|
|
1636
1637
|
}
|
|
1637
1638
|
return t;
|
|
1638
1639
|
}, q = (e, t = [], n = 0) => {
|
|
@@ -1650,10 +1651,10 @@ var ce = () => {
|
|
|
1650
1651
|
} catch {
|
|
1651
1652
|
continue;
|
|
1652
1653
|
}
|
|
1653
|
-
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);
|
|
1654
1655
|
}
|
|
1655
1656
|
return t;
|
|
1656
|
-
},
|
|
1657
|
+
}, en = (e, t = [], n = 0) => {
|
|
1657
1658
|
if (n > 12 || K.test(e)) return t;
|
|
1658
1659
|
let r;
|
|
1659
1660
|
try {
|
|
@@ -1668,17 +1669,17 @@ var ce = () => {
|
|
|
1668
1669
|
} catch {
|
|
1669
1670
|
continue;
|
|
1670
1671
|
}
|
|
1671
|
-
a.isDirectory() ?
|
|
1672
|
+
a.isDirectory() ? en(r, t, n + 1) : Zt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
|
|
1672
1673
|
}
|
|
1673
1674
|
return t;
|
|
1674
|
-
},
|
|
1675
|
-
let t =
|
|
1675
|
+
}, tn = (e) => {
|
|
1676
|
+
let t = Yt(e);
|
|
1676
1677
|
return {
|
|
1677
|
-
dirs:
|
|
1678
|
+
dirs: Xt(e, t),
|
|
1678
1679
|
fileCount: t.length
|
|
1679
1680
|
};
|
|
1680
|
-
},
|
|
1681
|
-
let t =
|
|
1681
|
+
}, nn = (e) => {
|
|
1682
|
+
let t = Yt(e);
|
|
1682
1683
|
if (t.length === 0) return [];
|
|
1683
1684
|
let n = new A({
|
|
1684
1685
|
useInMemoryFileSystem: !0,
|
|
@@ -1687,8 +1688,8 @@ var ce = () => {
|
|
|
1687
1688
|
for (let e of t) try {
|
|
1688
1689
|
i.set(e, E(e, "utf8"));
|
|
1689
1690
|
} catch {}
|
|
1690
|
-
let a =
|
|
1691
|
-
for (let t of
|
|
1691
|
+
let a = Rt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
|
|
1692
|
+
for (let t of en(e)) try {
|
|
1692
1693
|
o.set(t, E(t, "utf8"));
|
|
1693
1694
|
} catch {}
|
|
1694
1695
|
for (let s of t) {
|
|
@@ -1706,23 +1707,23 @@ var ce = () => {
|
|
|
1706
1707
|
} catch {
|
|
1707
1708
|
continue;
|
|
1708
1709
|
}
|
|
1709
|
-
for (let t of
|
|
1710
|
-
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;
|
|
1711
1712
|
let n = r.get(t.id) ?? [];
|
|
1712
1713
|
n.push(S(e, s)), r.set(t.id, n);
|
|
1713
1714
|
}
|
|
1714
1715
|
}
|
|
1715
|
-
return
|
|
1716
|
+
return Jt.filter((e) => r.has(e.id)).map((e) => ({
|
|
1716
1717
|
rule: e,
|
|
1717
1718
|
files: r.get(e.id)
|
|
1718
1719
|
})).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
|
|
1719
|
-
},
|
|
1720
|
-
let t =
|
|
1720
|
+
}, rn = (e) => {
|
|
1721
|
+
let t = tn(e);
|
|
1721
1722
|
return {
|
|
1722
1723
|
root: e,
|
|
1723
1724
|
scannedFiles: t.fileCount,
|
|
1724
1725
|
scannedDirs: t.dirs,
|
|
1725
|
-
findings:
|
|
1726
|
+
findings: nn(e).map(({ rule: e, files: t }) => ({
|
|
1726
1727
|
id: e.id,
|
|
1727
1728
|
scope: e.scope,
|
|
1728
1729
|
smell: e.smell,
|
|
@@ -1730,22 +1731,22 @@ var ce = () => {
|
|
|
1730
1731
|
files: t
|
|
1731
1732
|
}))
|
|
1732
1733
|
};
|
|
1733
|
-
},
|
|
1734
|
+
}, an = 3, on = .2, sn = 50, cn = (e, t) => e >= sn && t > 0 && e / t >= on, ln = (e) => {
|
|
1734
1735
|
let t = /* @__PURE__ */ new Map();
|
|
1735
1736
|
for (let n of q(e)) try {
|
|
1736
1737
|
t.set(S(e, n), E(n, "utf8"));
|
|
1737
1738
|
} catch {}
|
|
1738
1739
|
let n = /* @__PURE__ */ new Map();
|
|
1739
|
-
for (let t of
|
|
1740
|
+
for (let t of $t(e)) try {
|
|
1740
1741
|
n.set(S(e, t), E(t, "utf8"));
|
|
1741
1742
|
} catch {}
|
|
1742
|
-
let r =
|
|
1743
|
+
let r = pt({
|
|
1743
1744
|
sources: t,
|
|
1744
1745
|
envFiles: n
|
|
1745
1746
|
});
|
|
1746
|
-
r !== void 0 && (console.log(""), console.log(
|
|
1747
|
-
},
|
|
1748
|
-
let t =
|
|
1747
|
+
r !== void 0 && (console.log(""), console.log(mt(r)));
|
|
1748
|
+
}, un = (e) => {
|
|
1749
|
+
let t = rn(e);
|
|
1749
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;
|
|
1750
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;
|
|
1751
1752
|
console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
|
|
@@ -1756,24 +1757,24 @@ var ce = () => {
|
|
|
1756
1757
|
console.log(` [${e}]`);
|
|
1757
1758
|
let i = [...r].sort((e, t) => e.files.length - t.files.length);
|
|
1758
1759
|
for (let e of i) {
|
|
1759
|
-
if (
|
|
1760
|
+
if (cn(e.files.length, t.scannedFiles)) {
|
|
1760
1761
|
let n = Math.round(e.files.length / t.scannedFiles * 100);
|
|
1761
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.)");
|
|
1762
1763
|
continue;
|
|
1763
1764
|
}
|
|
1764
|
-
let r = e.files.slice(0,
|
|
1765
|
+
let r = e.files.slice(0, an).join(", "), i = e.files.length - an;
|
|
1765
1766
|
i > 0 && (n += i);
|
|
1766
1767
|
let a = i > 0 ? `, +${i} more` : "";
|
|
1767
1768
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
|
|
1768
1769
|
}
|
|
1769
1770
|
}
|
|
1770
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;
|
|
1771
|
-
},
|
|
1772
|
-
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];
|
|
1773
1774
|
if (t !== void 0) return t === "ssr" ? "ssr" : null;
|
|
1774
|
-
let n =
|
|
1775
|
+
let n = pn.exec(e)?.[1];
|
|
1775
1776
|
return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
|
|
1776
|
-
},
|
|
1777
|
+
}, hn = async (e) => {
|
|
1777
1778
|
let t = x(e, "src", "pages");
|
|
1778
1779
|
if (!T(t)) return [];
|
|
1779
1780
|
let { pages: i, dirs: o } = await n(t), s = [];
|
|
@@ -1786,7 +1787,7 @@ var ce = () => {
|
|
|
1786
1787
|
continue;
|
|
1787
1788
|
}
|
|
1788
1789
|
if (r.test(i)) continue;
|
|
1789
|
-
let c =
|
|
1790
|
+
let c = mn(i);
|
|
1790
1791
|
c !== null && s.push({
|
|
1791
1792
|
file: S(e, x(t, n.file)),
|
|
1792
1793
|
pattern: n.pattern,
|
|
@@ -1794,18 +1795,18 @@ var ce = () => {
|
|
|
1794
1795
|
});
|
|
1795
1796
|
}
|
|
1796
1797
|
return s;
|
|
1797
|
-
},
|
|
1798
|
+
}, gn = 10, _n = async (e) => {
|
|
1798
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;
|
|
1799
|
-
let t = await
|
|
1800
|
+
let t = await hn(e);
|
|
1800
1801
|
if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
|
|
1801
1802
|
console.log(`• renderMode:'spa' candidates (${t.length} page${t.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
|
|
1802
|
-
for (let e of t.slice(0,
|
|
1803
|
+
for (let e of t.slice(0, gn)) {
|
|
1803
1804
|
let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
|
|
1804
1805
|
console.log(` ${e.file} (${e.pattern}) — ${t}`);
|
|
1805
1806
|
}
|
|
1806
|
-
let n = t.length -
|
|
1807
|
-
return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${
|
|
1808
|
-
},
|
|
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([
|
|
1809
1810
|
"node_modules",
|
|
1810
1811
|
"dist",
|
|
1811
1812
|
"build",
|
|
@@ -1813,7 +1814,7 @@ var ce = () => {
|
|
|
1813
1814
|
".vite",
|
|
1814
1815
|
".framework",
|
|
1815
1816
|
".git"
|
|
1816
|
-
]),
|
|
1817
|
+
]), yn = (e, t, n) => {
|
|
1817
1818
|
let r;
|
|
1818
1819
|
try {
|
|
1819
1820
|
r = D(e, { withFileTypes: !0 });
|
|
@@ -1821,11 +1822,11 @@ var ce = () => {
|
|
|
1821
1822
|
return;
|
|
1822
1823
|
}
|
|
1823
1824
|
for (let i of r) {
|
|
1824
|
-
if (
|
|
1825
|
+
if (vn.has(i.name)) continue;
|
|
1825
1826
|
let r = x(e, i.name);
|
|
1826
|
-
i.isDirectory() ?
|
|
1827
|
+
i.isDirectory() ? yn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
|
|
1827
1828
|
}
|
|
1828
|
-
},
|
|
1829
|
+
}, bn = (e, t) => {
|
|
1829
1830
|
let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
|
|
1830
1831
|
for (let i of t) {
|
|
1831
1832
|
let t;
|
|
@@ -1850,16 +1851,16 @@ var ce = () => {
|
|
|
1850
1851
|
}
|
|
1851
1852
|
}
|
|
1852
1853
|
return n;
|
|
1853
|
-
},
|
|
1854
|
+
}, xn = (e, t) => {
|
|
1854
1855
|
let n = e.slice(t.length).split(w).slice(0, -1)[0];
|
|
1855
1856
|
return n !== void 0 && n.startsWith("[");
|
|
1856
1857
|
}, J = (e) => {
|
|
1857
1858
|
let t = [], n = x(e, "src", "pages"), r = x(e, "src");
|
|
1858
1859
|
if (!T(r)) return t;
|
|
1859
1860
|
let i = [];
|
|
1860
|
-
|
|
1861
|
+
yn(r, e, i), i.sort();
|
|
1861
1862
|
let a = `${S(e, n)}${w}`;
|
|
1862
|
-
|
|
1863
|
+
bn(e, i);
|
|
1863
1864
|
for (let n of i) {
|
|
1864
1865
|
let r = n.slice(n.lastIndexOf(w) + 1);
|
|
1865
1866
|
if (n.startsWith(a)) {
|
|
@@ -1875,7 +1876,7 @@ var ce = () => {
|
|
|
1875
1876
|
file: n
|
|
1876
1877
|
});
|
|
1877
1878
|
} catch {}
|
|
1878
|
-
|
|
1879
|
+
xn(n, a) && t.push({
|
|
1879
1880
|
kind: "top-level-dynamic-route",
|
|
1880
1881
|
file: n
|
|
1881
1882
|
});
|
|
@@ -1887,30 +1888,30 @@ var ce = () => {
|
|
|
1887
1888
|
});
|
|
1888
1889
|
}
|
|
1889
1890
|
return t;
|
|
1890
|
-
},
|
|
1891
|
+
}, Sn = 10, Y = (e, t, n) => {
|
|
1891
1892
|
if (t.length === 0) return;
|
|
1892
1893
|
console.log(`• ${e} (${t.length}):`);
|
|
1893
|
-
for (let e of t.slice(0,
|
|
1894
|
-
let r = t.length -
|
|
1894
|
+
for (let e of t.slice(0, Sn)) console.log(` ${e}`);
|
|
1895
|
+
let r = t.length - Sn;
|
|
1895
1896
|
r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
|
|
1896
|
-
},
|
|
1897
|
+
}, Cn = (e) => {
|
|
1897
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;
|
|
1898
1899
|
let t = J(e);
|
|
1899
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;
|
|
1900
1901
|
let n = (e) => t.filter((t) => t.kind === e).map((e) => e.file);
|
|
1901
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;
|
|
1902
|
-
},
|
|
1903
|
-
let t =
|
|
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);
|
|
1904
1905
|
if (t !== void 0) return t;
|
|
1905
1906
|
let n = (async () => {
|
|
1906
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
1907
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-iiMtlkfs.js"), r = await t(e);
|
|
1907
1908
|
return {
|
|
1908
1909
|
files: r,
|
|
1909
1910
|
discovered: await n(r)
|
|
1910
1911
|
};
|
|
1911
1912
|
})();
|
|
1912
|
-
return
|
|
1913
|
-
},
|
|
1913
|
+
return Tn.set(e, n), n;
|
|
1914
|
+
}, En = async (e) => {
|
|
1914
1915
|
let t;
|
|
1915
1916
|
try {
|
|
1916
1917
|
let { files: n, discovered: r } = await X(e), i = r.tables.map((e) => {
|
|
@@ -1921,12 +1922,12 @@ var ce = () => {
|
|
|
1921
1922
|
};
|
|
1922
1923
|
});
|
|
1923
1924
|
if (i.length === 0) return;
|
|
1924
|
-
t = qe(
|
|
1925
|
+
t = Je(qe(n, i, e));
|
|
1925
1926
|
} catch {
|
|
1926
1927
|
return;
|
|
1927
1928
|
}
|
|
1928
1929
|
t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
|
|
1929
|
-
},
|
|
1930
|
+
}, Dn = (e) => e.map((e) => {
|
|
1930
1931
|
let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
|
|
1931
1932
|
name: e,
|
|
1932
1933
|
type: typeof t?.type == "string" ? t.type : ""
|
|
@@ -1939,19 +1940,19 @@ var ce = () => {
|
|
|
1939
1940
|
}), Z = async (e) => {
|
|
1940
1941
|
try {
|
|
1941
1942
|
let { discovered: t } = await X(e);
|
|
1942
|
-
return
|
|
1943
|
+
return $e(Dn(t.tables));
|
|
1943
1944
|
} catch {
|
|
1944
1945
|
return null;
|
|
1945
1946
|
}
|
|
1946
|
-
},
|
|
1947
|
+
}, On = async (e) => {
|
|
1947
1948
|
let t = await Z(e);
|
|
1948
1949
|
if (t === null || t.tablesChecked === 0) return;
|
|
1949
|
-
let n =
|
|
1950
|
+
let n = tt(t);
|
|
1950
1951
|
n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
|
|
1951
|
-
},
|
|
1952
|
+
}, kn = async (e) => {
|
|
1952
1953
|
try {
|
|
1953
1954
|
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await X(e);
|
|
1954
|
-
return
|
|
1955
|
+
return at(n.events.map((e) => {
|
|
1955
1956
|
let n = e.descriptor, r = t(n.key);
|
|
1956
1957
|
return {
|
|
1957
1958
|
name: n.name,
|
|
@@ -1963,17 +1964,17 @@ var ce = () => {
|
|
|
1963
1964
|
} catch {
|
|
1964
1965
|
return null;
|
|
1965
1966
|
}
|
|
1966
|
-
},
|
|
1967
|
-
let t = await
|
|
1967
|
+
}, An = async (e) => {
|
|
1968
|
+
let t = await kn(e);
|
|
1968
1969
|
if (t === null || t.eventsChecked === 0) return;
|
|
1969
|
-
let n =
|
|
1970
|
+
let n = ot(t);
|
|
1970
1971
|
n !== null && (console.log(""), console.log(`• ${n}`));
|
|
1971
|
-
let r =
|
|
1972
|
+
let r = st(t);
|
|
1972
1973
|
r !== null && (console.log(""), console.log(`⚠️ ${r}`));
|
|
1973
|
-
},
|
|
1974
|
+
}, jn = (e) => {
|
|
1974
1975
|
let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
|
|
1975
1976
|
return r === void 0 ? n : `${r}: ${n}`;
|
|
1976
|
-
},
|
|
1977
|
+
}, Mn = async (e) => {
|
|
1977
1978
|
try {
|
|
1978
1979
|
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await X(e)).discovered.serverOnlyLeaks, r = t(n);
|
|
1979
1980
|
return {
|
|
@@ -1986,17 +1987,17 @@ var ce = () => {
|
|
|
1986
1987
|
} catch (e) {
|
|
1987
1988
|
return { status: {
|
|
1988
1989
|
checked: !1,
|
|
1989
|
-
reason:
|
|
1990
|
+
reason: jn(e)
|
|
1990
1991
|
} };
|
|
1991
1992
|
}
|
|
1992
|
-
}, Mn = async (e) => {
|
|
1993
|
-
let { status: t, message: n } = await jn(e);
|
|
1994
|
-
return t.checked ? n === void 0 ? !1 : (console.log(""), console.log(`✗ [serverOnly] ${n}`), !0) : (console.log(""), console.log("• serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), console.log(" This audit is the automated check that a hash or a token cannot reach the wire, so"), console.log(" an app that leans on `.serverOnly()` should treat a persistent skip as a failure."), console.log(" `voltro doctor --json` reports it as `serverOnly.checked: false` for a CI gate."), !1);
|
|
1995
1993
|
}, Nn = async (e) => {
|
|
1994
|
+
let { status: t, message: n } = await Mn(e);
|
|
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);
|
|
1996
|
+
}, Pn = async (e) => {
|
|
1996
1997
|
let t;
|
|
1997
1998
|
try {
|
|
1998
1999
|
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await X(e);
|
|
1999
|
-
t =
|
|
2000
|
+
t = ut(r, i.workflows.map((e) => {
|
|
2000
2001
|
let t = e.definition, r = t.payloadSchema === void 0 ? {
|
|
2001
2002
|
required: [],
|
|
2002
2003
|
optional: []
|
|
@@ -2010,59 +2011,98 @@ var ce = () => {
|
|
|
2010
2011
|
} catch {
|
|
2011
2012
|
return;
|
|
2012
2013
|
}
|
|
2013
|
-
let n =
|
|
2014
|
+
let n = dt(t);
|
|
2014
2015
|
if (n === null) return;
|
|
2015
2016
|
console.log("");
|
|
2016
2017
|
let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
|
|
2017
2018
|
console.log(`${r} ${n}`);
|
|
2018
|
-
}, Q = "voltro-authz-allowlist.txt",
|
|
2019
|
+
}, Q = "voltro-authz-allowlist.txt", Fn = (e, t = (e) => {
|
|
2019
2020
|
try {
|
|
2020
2021
|
return E(e, "utf8");
|
|
2021
2022
|
} catch {
|
|
2022
2023
|
return "";
|
|
2023
2024
|
}
|
|
2024
|
-
}) => /* @__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) => {
|
|
2025
2029
|
try {
|
|
2026
|
-
let { discovered: t } = await X(e), n = t.events.map((e) =>
|
|
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");
|
|
2027
2034
|
if (n.length === 0) return [];
|
|
2028
|
-
let r = new Set(t.events.map((e) => e.file)), i =
|
|
2035
|
+
let r = new Set(t.events.map((e) => e.file)), i = (e) => {
|
|
2029
2036
|
try {
|
|
2030
2037
|
return E(e, "utf8");
|
|
2031
2038
|
} catch {
|
|
2032
2039
|
return "";
|
|
2033
2040
|
}
|
|
2034
|
-
}).join("\n");
|
|
2035
|
-
return n.filter((e) =>
|
|
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();
|
|
2036
2047
|
} catch {
|
|
2037
2048
|
return [];
|
|
2038
2049
|
}
|
|
2039
|
-
},
|
|
2040
|
-
let t = await
|
|
2050
|
+
}, Rn = async (e) => {
|
|
2051
|
+
let t = await Ln(e);
|
|
2041
2052
|
if (t.length !== 0) {
|
|
2042
2053
|
console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
|
|
2043
2054
|
for (let e of t) console.log(` ${e}`);
|
|
2044
|
-
console.log(""), console.log(" An event you declare is an event partners can subscribe to. One that"), console.log(" never fires is indistinguishable, from their side, from a broken"), console.log(" endpoint of their own — so they debug your contract instead of"), console.log(" reading it."), console.log(" ADVISORY: this looks for the event
|
|
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.");
|
|
2045
2056
|
}
|
|
2046
|
-
},
|
|
2057
|
+
}, zn = async (e) => {
|
|
2047
2058
|
try {
|
|
2048
|
-
let {
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
}));
|
|
2059
|
+
let { loadApiConfig: t } = await import("./dev-iiMtlkfs.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") : [];
|
|
2055
2065
|
} catch {
|
|
2056
2066
|
return [];
|
|
2057
2067
|
}
|
|
2058
|
-
},
|
|
2059
|
-
let t =
|
|
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-DRovTKza.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
|
+
}
|
|
2060
2100
|
if (t.length !== 0) {
|
|
2061
2101
|
console.log(""), console.log(`• ${t.length} procedure(s) require a scope no declared role grants:`);
|
|
2062
2102
|
for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
|
|
2063
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}`);
|
|
2064
2104
|
}
|
|
2065
|
-
},
|
|
2105
|
+
}, Hn = async (e) => {
|
|
2066
2106
|
let t = ["src/locales", "locales"], n, r = [];
|
|
2067
2107
|
for (let i of t) {
|
|
2068
2108
|
let t = x(e, i);
|
|
@@ -2074,23 +2114,23 @@ var ce = () => {
|
|
|
2074
2114
|
if (n === void 0 || r.length === 0) return;
|
|
2075
2115
|
let i = [];
|
|
2076
2116
|
try {
|
|
2077
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2117
|
+
let { loadApiConfig: t } = await import("./dev-iiMtlkfs.js");
|
|
2078
2118
|
i = (await t(e))?.locales ?? [];
|
|
2079
2119
|
} catch {
|
|
2080
2120
|
return;
|
|
2081
2121
|
}
|
|
2082
|
-
let a =
|
|
2122
|
+
let a = ht({
|
|
2083
2123
|
catalogCodes: r,
|
|
2084
2124
|
declaredLocales: i
|
|
2085
2125
|
});
|
|
2086
2126
|
if (a !== void 0) {
|
|
2087
2127
|
console.log("");
|
|
2088
|
-
for (let e of
|
|
2128
|
+
for (let e of gt(a, n)) console.log(e);
|
|
2089
2129
|
}
|
|
2090
|
-
},
|
|
2130
|
+
}, Un = async (e) => {
|
|
2091
2131
|
let { discovered: t } = await X(e), n = [];
|
|
2092
2132
|
try {
|
|
2093
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
2133
|
+
let { loadApiConfig: t } = await import("./dev-iiMtlkfs.js");
|
|
2094
2134
|
n = ((await t(e))?.plugins ?? []).map((e) => {
|
|
2095
2135
|
let t = (e.extendSchema?.tables ?? []).map((e) => {
|
|
2096
2136
|
let t = e;
|
|
@@ -2106,8 +2146,8 @@ var ce = () => {
|
|
|
2106
2146
|
plugin: e.name,
|
|
2107
2147
|
tables: t,
|
|
2108
2148
|
rpcTags: n,
|
|
2109
|
-
canDisableTables:
|
|
2110
|
-
canAlias:
|
|
2149
|
+
canDisableTables: Wn.has(e.name),
|
|
2150
|
+
canAlias: Gn.has(e.name)
|
|
2111
2151
|
};
|
|
2112
2152
|
});
|
|
2113
2153
|
} catch {
|
|
@@ -2119,14 +2159,14 @@ var ce = () => {
|
|
|
2119
2159
|
...t.mutations,
|
|
2120
2160
|
...t.actions,
|
|
2121
2161
|
...t.streams ?? []
|
|
2122
|
-
].map((e) => String(e.descriptor.name)), i =
|
|
2162
|
+
].map((e) => String(e.descriptor.name)), i = Mt(n, {
|
|
2123
2163
|
tables: t.tables.map((e) => e.tableName),
|
|
2124
2164
|
rpcTags: r
|
|
2125
2165
|
});
|
|
2126
2166
|
console.log();
|
|
2127
|
-
for (let e of
|
|
2128
|
-
},
|
|
2129
|
-
let t = await
|
|
2167
|
+
for (let e of Nt(n, i)) console.log(e);
|
|
2168
|
+
}, Wn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Gn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Kn = async (e) => {
|
|
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;
|
|
2130
2170
|
return {
|
|
2131
2171
|
executors: t.executorCount,
|
|
2132
2172
|
guardVocabulary: [...t.vocabulary].sort(),
|
|
@@ -2138,7 +2178,7 @@ var ce = () => {
|
|
|
2138
2178
|
},
|
|
2139
2179
|
unchecked: t.result.unchecked
|
|
2140
2180
|
};
|
|
2141
|
-
},
|
|
2181
|
+
}, qn = async (e) => {
|
|
2142
2182
|
let { discovered: t } = await X(e), n = /* @__PURE__ */ new Map(), r = (e) => {
|
|
2143
2183
|
if (!n.has(e)) try {
|
|
2144
2184
|
n.set(e, E(e, "utf8"));
|
|
@@ -2146,7 +2186,7 @@ var ce = () => {
|
|
|
2146
2186
|
n.set(e, "");
|
|
2147
2187
|
}
|
|
2148
2188
|
return n.get(e);
|
|
2149
|
-
}, i =
|
|
2189
|
+
}, i = Fn(e, r), a = /* @__PURE__ */ new Set();
|
|
2150
2190
|
for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
|
|
2151
2191
|
let e = t.references?.();
|
|
2152
2192
|
e && a.add(e.tableName);
|
|
@@ -2183,7 +2223,7 @@ var ce = () => {
|
|
|
2183
2223
|
l = !0;
|
|
2184
2224
|
break;
|
|
2185
2225
|
}
|
|
2186
|
-
let u =
|
|
2226
|
+
let u = Tt({
|
|
2187
2227
|
executors: o,
|
|
2188
2228
|
guardVocabulary: i,
|
|
2189
2229
|
tenantScopedTables: t.tenantScopedTables,
|
|
@@ -2200,49 +2240,49 @@ var ce = () => {
|
|
|
2200
2240
|
executorCount: o.length,
|
|
2201
2241
|
allowPath: s
|
|
2202
2242
|
};
|
|
2203
|
-
},
|
|
2204
|
-
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await
|
|
2243
|
+
}, Jn = async (e, t) => {
|
|
2244
|
+
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await qn(e), l = n.counts;
|
|
2205
2245
|
console.log(`\nauthz scan · ${o} executor(s)`);
|
|
2206
|
-
let u = [...r].filter((e) => !
|
|
2246
|
+
let u = [...r].filter((e) => !vt.has(e)).sort();
|
|
2207
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)}`);
|
|
2208
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;
|
|
2209
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) {
|
|
2210
|
-
let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r =
|
|
2211
|
-
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;
|
|
2212
2252
|
}
|
|
2213
2253
|
if (n.unchecked.length === 0) return !1;
|
|
2214
2254
|
console.log("");
|
|
2215
2255
|
for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
|
|
2216
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);
|
|
2217
|
-
},
|
|
2218
|
-
if (e.includes("--help") || e.includes("-h")) return console.log(
|
|
2257
|
+
}, Yn = async (e) => {
|
|
2258
|
+
if (e.includes("--help") || e.includes("-h")) return console.log(wn), 0;
|
|
2219
2259
|
let n = e.includes("--strict"), r = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
|
|
2220
|
-
if (
|
|
2221
|
-
...
|
|
2222
|
-
spaCandidates: await
|
|
2260
|
+
if (ue(e)) return le(), M({
|
|
2261
|
+
...rn(r),
|
|
2262
|
+
spaCandidates: await hn(r),
|
|
2223
2263
|
pageConvention: J(r),
|
|
2224
2264
|
fileTaxonomy: V(r).findings,
|
|
2225
2265
|
junctionFks: (await Z(r))?.issues ?? [],
|
|
2226
|
-
eventDelivery: await
|
|
2266
|
+
eventDelivery: await kn(r),
|
|
2227
2267
|
duplicateInstances: t(r),
|
|
2228
2268
|
versionSkew: s(r) ?? null,
|
|
2229
|
-
serverOnly: (await
|
|
2230
|
-
authz: await
|
|
2231
|
-
ungrantedScopes: await
|
|
2232
|
-
neverEmittedEvents: await
|
|
2269
|
+
serverOnly: (await Mn(r)).status,
|
|
2270
|
+
authz: await Kn(r),
|
|
2271
|
+
ungrantedScopes: await Bn(r),
|
|
2272
|
+
neverEmittedEvents: await Ln(r)
|
|
2233
2273
|
}), 0;
|
|
2234
|
-
let a =
|
|
2235
|
-
|
|
2236
|
-
let c = await
|
|
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));
|
|
2237
2277
|
u !== void 0 && (console.log(""), console.log(u));
|
|
2238
2278
|
let d = i(t(r));
|
|
2239
2279
|
return d !== void 0 && console.log(`\n${d}`), c || l ? Math.max(a, 1) : a;
|
|
2240
|
-
},
|
|
2280
|
+
}, Xn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Zn = /* @__PURE__ */ new Set([
|
|
2241
2281
|
"insert",
|
|
2242
2282
|
"update",
|
|
2243
2283
|
"delete",
|
|
2244
2284
|
"upsert"
|
|
2245
|
-
]),
|
|
2285
|
+
]), Qn = (e, t) => {
|
|
2246
2286
|
let n = [], r = new Set(t.exercised), i = /* @__PURE__ */ new Map();
|
|
2247
2287
|
for (let e of t.edges) {
|
|
2248
2288
|
let t = i.get(e.tag);
|
|
@@ -2254,8 +2294,8 @@ var ce = () => {
|
|
|
2254
2294
|
a.push(t.tag);
|
|
2255
2295
|
continue;
|
|
2256
2296
|
}
|
|
2257
|
-
let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(
|
|
2258
|
-
for (let t of c) if (
|
|
2297
|
+
let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(Xn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
|
|
2298
|
+
for (let t of c) if (Zn.has(t.op)) {
|
|
2259
2299
|
let r = u.get(t.table);
|
|
2260
2300
|
r === void 0 ? n.push({
|
|
2261
2301
|
rule: "observed/undeclared-write",
|
|
@@ -2311,7 +2351,7 @@ var ce = () => {
|
|
|
2311
2351
|
total: e.length
|
|
2312
2352
|
}
|
|
2313
2353
|
};
|
|
2314
|
-
},
|
|
2354
|
+
}, $n = (e, t = [], n = 0) => {
|
|
2315
2355
|
if (n > 12) return t;
|
|
2316
2356
|
let r;
|
|
2317
2357
|
try {
|
|
@@ -2335,24 +2375,24 @@ var ce = () => {
|
|
|
2335
2375
|
} catch {
|
|
2336
2376
|
continue;
|
|
2337
2377
|
}
|
|
2338
|
-
a.isDirectory() ?
|
|
2378
|
+
a.isDirectory() ? $n(r, t, n + 1) : a.isFile() && u.test(i) && t.push(r);
|
|
2339
2379
|
}
|
|
2340
2380
|
return t;
|
|
2341
|
-
},
|
|
2381
|
+
}, er = /* @__PURE__ */ new Set(["actors", "tenants"]), tr = async (e) => {
|
|
2342
2382
|
try {
|
|
2343
2383
|
let { readFile: t } = await import("node:fs/promises"), { join: n } = await import("node:path"), r = await t(n(e, "app.graph.observed.generated.json"), "utf8"), i = JSON.parse(r);
|
|
2344
2384
|
return i?.version !== 1 || !Array.isArray(i.edges) || !Array.isArray(i.exercised) ? null : i;
|
|
2345
2385
|
} catch {
|
|
2346
2386
|
return null;
|
|
2347
2387
|
}
|
|
2348
|
-
},
|
|
2388
|
+
}, nr = (e) => {
|
|
2349
2389
|
let { exercised: t, total: n } = e.coverage, r = `\nobserved: ${t}/${n} procedures exercised (${n === 0 ? 0 : Math.round(t / n * 100)}%)`;
|
|
2350
2390
|
if (e.diagnostics.length === 0) return `${r}\n no declared/observed mismatches${e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : ""}`;
|
|
2351
2391
|
let i = e.diagnostics.map((e) => ` ${e.node}${e.file === void 0 ? "" : ` (${e.file})`}\n` + e.breaks.map((e) => ` ${e}`).join("\n") + `\n fix: ${e.fix}`), a = e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : "";
|
|
2352
2392
|
return `${r}\n${i.join("\n")}${a}`;
|
|
2353
|
-
},
|
|
2393
|
+
}, rr = (e, t) => ({
|
|
2354
2394
|
tables: (t.tables ?? []).map((e) => e.name),
|
|
2355
|
-
frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") ||
|
|
2395
|
+
frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || er.has(e.name)).map((e) => e.name),
|
|
2356
2396
|
scopes: e.scopes ?? [],
|
|
2357
2397
|
procedures: (e.procedures ?? []).map((e) => {
|
|
2358
2398
|
let t = (e.guards ?? []).flatMap((e) => e.kind === "scope" ? e.scope ?? [] : []);
|
|
@@ -2365,11 +2405,11 @@ var ce = () => {
|
|
|
2365
2405
|
...e.guards === void 0 ? {} : { hasGuards: e.guards.length > 0 }
|
|
2366
2406
|
};
|
|
2367
2407
|
})
|
|
2368
|
-
}),
|
|
2408
|
+
}), ir = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
|
|
2369
2409
|
let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
|
|
2370
2410
|
return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
|
|
2371
|
-
}).join("\n"),
|
|
2372
|
-
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-
|
|
2411
|
+
}).join("\n"), ar = async (e) => {
|
|
2412
|
+
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-iiMtlkfs.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 = [
|
|
2373
2413
|
...s.queries.map((e) => c(e.descriptor, "query", e.file)),
|
|
2374
2414
|
...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
|
|
2375
2415
|
...s.actions.map((e) => c(e.descriptor, "action", e.file)),
|
|
@@ -2385,10 +2425,10 @@ var ce = () => {
|
|
|
2385
2425
|
})), [], m),
|
|
2386
2426
|
tables: { tables: p }
|
|
2387
2427
|
};
|
|
2388
|
-
},
|
|
2389
|
-
let t =
|
|
2428
|
+
}, or = async (e) => {
|
|
2429
|
+
let t = he(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
|
|
2390
2430
|
if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
|
|
2391
|
-
let { process: n, target: r, rest: i } =
|
|
2431
|
+
let { process: n, target: r, rest: i } = ge(t.rest), a;
|
|
2392
2432
|
for (let e = 0; e < i.length; e++) if (i[e] === "--diff") {
|
|
2393
2433
|
let [t, n] = (i[++e] ?? "").split(":");
|
|
2394
2434
|
if (t === "removeTable" && n) a = {
|
|
@@ -2405,21 +2445,21 @@ var ce = () => {
|
|
|
2405
2445
|
};
|
|
2406
2446
|
else return console.error("voltro check --diff: expected removeTable:<name> | removeProcedure:<tag> | removeScope:<name>"), 1;
|
|
2407
2447
|
}
|
|
2408
|
-
let o = i.find((e) => !e.startsWith("-")) ?? process.cwd(), s = i.includes("--offline"), c = s ? [] : await
|
|
2448
|
+
let o = i.find((e) => !e.startsWith("-")) ?? process.cwd(), s = i.includes("--offline"), c = s ? [] : await me(n, r), u, d;
|
|
2409
2449
|
if (c.length === 0) {
|
|
2410
2450
|
if (!s && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
|
|
2411
2451
|
try {
|
|
2412
|
-
let e = await
|
|
2413
|
-
|
|
2452
|
+
let e = await ar(C(o));
|
|
2453
|
+
u = e.manifest, d = e.tables;
|
|
2414
2454
|
} catch (e) {
|
|
2415
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;
|
|
2416
2456
|
}
|
|
2417
2457
|
} else {
|
|
2418
2458
|
let e = c[0], [t, n] = await Promise.all([N(e, "/_voltro/inspect/manifest"), N(e, "/_voltro/inspect/data/tables")]);
|
|
2419
2459
|
if (t.kind === "err") return console.error(`voltro check: manifest fetch failed: ${t.error}`), 1;
|
|
2420
|
-
|
|
2460
|
+
u = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
|
|
2421
2461
|
}
|
|
2422
|
-
let f =
|
|
2462
|
+
let f = rr(u, d), p = ie(f, a), m = [...V(o).findings.map((e) => ({
|
|
2423
2463
|
rule: e.rule,
|
|
2424
2464
|
node: e.file,
|
|
2425
2465
|
file: e.file,
|
|
@@ -2433,7 +2473,7 @@ var ce = () => {
|
|
|
2433
2473
|
breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
|
|
2434
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",
|
|
2435
2475
|
severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
|
|
2436
|
-
}))], h = (await
|
|
2476
|
+
}))], h = (await l(o, $n(o))).map((e) => ({
|
|
2437
2477
|
rule: "client/not-browser-safe",
|
|
2438
2478
|
node: e.file,
|
|
2439
2479
|
file: e.file,
|
|
@@ -2444,7 +2484,7 @@ var ce = () => {
|
|
|
2444
2484
|
...p.diagnostics,
|
|
2445
2485
|
...m,
|
|
2446
2486
|
...h
|
|
2447
|
-
], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await
|
|
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);
|
|
2448
2488
|
return t.format === "json" ? M(y === null ? {
|
|
2449
2489
|
...p,
|
|
2450
2490
|
ok: _,
|
|
@@ -2454,7 +2494,7 @@ var ce = () => {
|
|
|
2454
2494
|
ok: _,
|
|
2455
2495
|
diagnostics: g,
|
|
2456
2496
|
observed: y
|
|
2457
|
-
}) : (console.log(
|
|
2497
|
+
}) : (console.log(ir(g)), y !== null && console.log(nr(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
|
|
2458
2498
|
};
|
|
2459
2499
|
//#endregion
|
|
2460
|
-
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 };
|