@voltro/cli 0.42.0 → 0.43.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 +91 -0
- package/dist/apiBuild-B7NgSMhX.js +2 -0
- package/dist/{apiBuild-B_iH24qf.js → apiBuild-DF0f69Gf.js} +2 -2
- package/dist/bin.js +1 -1
- package/dist/{build-DUdi6F_Q.js → build-LC_IJaHL.js} +7 -7
- package/dist/{checkCommand-B2RWrpWi.js → checkCommand-DBpTSK8d.js} +1 -1
- package/dist/{checkCommand-DWL2_PPR.js → checkCommand-DDxwJCja.js} +2 -2
- package/dist/{codegenCommand-DkjoJ30Q.js → codegenCommand-LjxfK4Am.js} +2 -2
- package/dist/{codemodRunner-sFuWDETH.js → codemodRunner-Cnnz4-I7.js} +19 -4
- package/dist/{commands-DoTClnSk.js → commands-CbQcuWsE.js} +20 -20
- package/dist/{dashboardCommand-Bro7Jwts.js → dashboardCommand-CAHjCe_9.js} +3 -3
- package/dist/{dataCommand-Rj57y1vg.js → dataCommand-CrqvbVOv.js} +143 -133
- package/dist/{dev-D4l_YOzI.js → dev-B2rZfqxw.js} +2 -2
- package/dist/{dev-DuqBJC7g.js → dev-D0E5x2Ue.js} +1 -1
- package/dist/doctorCommand-CdsctSwg.js +2 -0
- package/dist/{doctorCommand-TmZTAepc.js → doctorCommand-Ct_3BHgd.js} +226 -216
- package/dist/{dormancyCommand-CUKhRmGG.js → dormancyCommand-D1DKnYce.js} +1 -1
- package/dist/{embeddingsCommand-Db-iQM3l.js → embeddingsCommand-1sZRBKlM.js} +1 -1
- package/dist/{envCommand-DQ_URZq9.js → envCommand-7qjlQgLD.js} +2 -2
- package/dist/{evolveCommand-CghRPrGO.js → evolveCommand-D5opB48I.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{infoCommand-BCs_ZFnl.js → infoCommand-Bs_8RU22.js} +1 -1
- package/dist/{runtimeTrace-jFKvOPBz.js → runtimeTrace-Wmi7V-wN.js} +1 -1
- package/dist/{sdkgen-COQqHB9V.js → sdkgen-DbTFGCjg.js} +1 -1
- package/dist/{serveCommand-C512kzZA.js → serveCommand-C8xzYCwa.js} +4 -4
- package/dist/serveCommand-CGNTXafv.js +2 -0
- package/dist/serveEntry.js +1 -1
- package/dist/{start-fifc0xpW.js → start-BAlGcjF5.js} +1 -1
- package/dist/{start-BjcuFME_.js → start-Ds3xyn8R.js} +1 -1
- package/dist/startEntry.js +1 -1
- package/dist/startup.d.ts +1 -1
- package/dist/updateCommand-BvkKlV7D.js +2 -0
- package/dist/{updateCommand-HZQVBU3T.js → updateCommand-CrI9B4d7.js} +1 -1
- package/dist/{webDev-Dk8lFUzf.js → webDev-101csg6Q.js} +2 -2
- package/dist/webDev-BLKBO34K.js +2 -0
- package/dist/{webhooksCommand-D7ZpH7w0.js → webhooksCommand-BXUP5YjW.js} +1 -1
- package/package.json +23 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/authentication.md +1 -1
- package/templates/agent-docs/cli.md +2 -2
- package/templates/agent-docs/data.md +14 -12
- package/templates/agent-docs/database/migrations.md +1 -1
- package/templates/agent-docs/internationalization.md +2 -2
- package/templates/agent-docs/observability.md +1 -1
- package/templates/agent-docs/plugins/openapi.md +1 -1
- package/templates/agent-docs/plugins/versioning.md +1 -1
- package/templates/agent-docs/plugins/webhooks.md +1 -1
- package/templates/agent-docs/plugins.md +1 -1
- package/templates/agent-docs/routing.md +1 -1
- package/templates/agent-docs/scheduling.md +1 -1
- package/templates/agent-docs/security.md +2 -2
- package/templates/agent-docs/templates/apibackends.md +1 -1
- package/templates/agent-docs/whats-new.md +49 -55
- 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 +9 -9
- 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 +10 -10
- 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 +4 -4
- package/dist/apiBuild-RM5W5YXk.js +0 -2
- package/dist/doctorCommand-Bw3V4L1j.js +0 -2
- package/dist/serveCommand-gj7MsqXY.js +0 -2
- package/dist/updateCommand-B2alql9k.js +0 -2
- package/dist/webDev-D6QeXw3P.js +0 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Bt as e, Ht as t, Vt as n } from "./dev-
|
|
2
|
-
import { at as r, it as i, ot as a, s as o, st as s, t as c, w as l, y as u } from "./webDev-
|
|
1
|
+
import { Bt as e, Ht as t, Vt as n } from "./dev-B2rZfqxw.js";
|
|
2
|
+
import { at as r, it as i, ot as a, s as o, st as s, t as c, w as l, y as u } from "./webDev-101csg6Q.js";
|
|
3
3
|
import { w as d } from "./frameworkTableAssembly-oMPBKqlE.js";
|
|
4
4
|
import { n as f } from "./pageConvention-cEiRxdab.js";
|
|
5
|
-
import { r as p, u as m } from "./apiBuild-
|
|
5
|
+
import { r as p, u as m } from "./apiBuild-DF0f69Gf.js";
|
|
6
6
|
import { n as h, r as g, t as ee } from "./cliOutput-D1tSBoRM.js";
|
|
7
7
|
import { n as _, t as te } from "./tsconfigPaths-BWXBWgcl.js";
|
|
8
8
|
import { n as ne, r as re } from "./fileTaxonomy-CJfgOllU.js";
|
|
@@ -477,33 +477,33 @@ var le = /* @__PURE__ */ new Set([
|
|
|
477
477
|
for (let n of e.problems) t.push(` • ${n}`);
|
|
478
478
|
}
|
|
479
479
|
return e.unreadable.length > 0 && t.push(`… ${e.file}: ${e.unreadable.length} matcher(s) not statically readable (${e.unreadable.join(", ")}) — doctor did NOT check them. The boot still does.`), e.seen === 0 && t.push(`… ${e.file}: no \`defineMiddleware(...)\` found — doctor checked nothing here. A middleware assigned through a variable or re-exported from another module is invisible to this rule.`), t.length === 0 && e.seen > 0 && t.push(`✓ ${e.file}: ${e.seen} middleware(s), matchers resolve and do not overlap`), t;
|
|
480
|
-
}, j = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"),
|
|
480
|
+
}, j = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), ze = /* @__PURE__ */ new Set([
|
|
481
481
|
"requireScope",
|
|
482
482
|
"requireAnyScope",
|
|
483
483
|
"requireAuth",
|
|
484
484
|
"assertScope",
|
|
485
485
|
"assertCan",
|
|
486
486
|
"can"
|
|
487
|
-
]),
|
|
487
|
+
]), Be = (e) => {
|
|
488
488
|
let t = /* @__PURE__ */ new Set();
|
|
489
489
|
for (let n of e) for (let e of j(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
|
|
490
490
|
return t;
|
|
491
|
-
},
|
|
491
|
+
}, Ve = (e, t) => {
|
|
492
492
|
for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
|
|
493
|
-
},
|
|
493
|
+
}, He = (e) => {
|
|
494
494
|
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);
|
|
495
495
|
return t && n;
|
|
496
|
-
},
|
|
496
|
+
}, Ue = (e, t, n) => {
|
|
497
497
|
if (e.hasDeclaredGuards) return { kind: "declared-guard" };
|
|
498
|
-
let r = j(e.src), i =
|
|
499
|
-
return i === void 0 ?
|
|
498
|
+
let r = j(e.src), i = Ve(r, t);
|
|
499
|
+
return i === void 0 ? He(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
|
|
500
500
|
kind: "vocabulary-guard",
|
|
501
501
|
name: i
|
|
502
502
|
};
|
|
503
|
-
},
|
|
503
|
+
}, We = (e, t, n) => {
|
|
504
504
|
let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
|
|
505
505
|
return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
|
|
506
|
-
},
|
|
506
|
+
}, Ge = (e, t, n) => {
|
|
507
507
|
switch (t.kind) {
|
|
508
508
|
case "declared-guard": return "guards: on the descriptor";
|
|
509
509
|
case "vocabulary-guard": return `calls ${t.name}()`;
|
|
@@ -511,15 +511,15 @@ var le = /* @__PURE__ */ new Set([
|
|
|
511
511
|
case "allowlisted": return "accepted by a previous run";
|
|
512
512
|
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`;
|
|
513
513
|
}
|
|
514
|
-
},
|
|
514
|
+
}, Ke = (e) => {
|
|
515
515
|
let t = e.executors.map((t) => {
|
|
516
|
-
let n =
|
|
516
|
+
let n = Ue(t, e.guardVocabulary, e.allowlist);
|
|
517
517
|
return {
|
|
518
518
|
tag: t.tag,
|
|
519
519
|
path: t.path,
|
|
520
520
|
verdict: n,
|
|
521
|
-
severity:
|
|
522
|
-
why:
|
|
521
|
+
severity: We(t, e.tenantScopedTables, e.referencedTables),
|
|
522
|
+
why: Ge(t, n, e.rowFilterRegistered === !0)
|
|
523
523
|
};
|
|
524
524
|
}), n = (e) => t.filter((t) => t.verdict.kind === e).length;
|
|
525
525
|
return {
|
|
@@ -533,7 +533,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
533
533
|
},
|
|
534
534
|
unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
|
|
535
535
|
};
|
|
536
|
-
},
|
|
536
|
+
}, qe = [
|
|
537
537
|
"# voltro doctor — authz allowlist",
|
|
538
538
|
"#",
|
|
539
539
|
"# Two KINDS of line, and the difference is the point:",
|
|
@@ -558,19 +558,19 @@ var le = /* @__PURE__ */ new Set([
|
|
|
558
558
|
"# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
|
|
559
559
|
"# lines for executors that are newly unchecked.",
|
|
560
560
|
""
|
|
561
|
-
],
|
|
561
|
+
], Je = (e) => {
|
|
562
562
|
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));
|
|
563
563
|
return [
|
|
564
|
-
...
|
|
564
|
+
...qe,
|
|
565
565
|
...n,
|
|
566
566
|
...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
|
|
567
567
|
...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
|
|
568
568
|
""
|
|
569
569
|
].join("\n");
|
|
570
|
-
},
|
|
570
|
+
}, Ye = (e, t) => {
|
|
571
571
|
let n = [...new Set(t)].sort();
|
|
572
|
-
if (e === void 0) return
|
|
573
|
-
let r =
|
|
572
|
+
if (e === void 0) return Je(n.map((e) => ({ tag: e })));
|
|
573
|
+
let r = M(e).tags, i = n.filter((e) => !r.has(e));
|
|
574
574
|
return i.length === 0 ? e : [
|
|
575
575
|
e.replace(/\n+$/, ""),
|
|
576
576
|
"",
|
|
@@ -579,25 +579,25 @@ var le = /* @__PURE__ */ new Set([
|
|
|
579
579
|
...i,
|
|
580
580
|
""
|
|
581
581
|
].join("\n");
|
|
582
|
-
},
|
|
582
|
+
}, Xe = (e, t) => {
|
|
583
583
|
let n = [...new Set(t)];
|
|
584
584
|
if (e === void 0) return {
|
|
585
585
|
added: n.length,
|
|
586
586
|
kept: 0,
|
|
587
587
|
removed: 0
|
|
588
588
|
};
|
|
589
|
-
let r =
|
|
589
|
+
let r = M(e).tags;
|
|
590
590
|
return {
|
|
591
591
|
added: n.filter((e) => !r.has(e)).length,
|
|
592
592
|
kept: r.size,
|
|
593
593
|
removed: 0
|
|
594
594
|
};
|
|
595
|
-
},
|
|
595
|
+
}, Ze = class extends Error {
|
|
596
596
|
tag;
|
|
597
597
|
constructor(e) {
|
|
598
598
|
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";
|
|
599
599
|
}
|
|
600
|
-
},
|
|
600
|
+
}, M = (e) => {
|
|
601
601
|
let t = [];
|
|
602
602
|
for (let n of e.split("\n")) {
|
|
603
603
|
let e = n.trim();
|
|
@@ -610,7 +610,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
610
610
|
continue;
|
|
611
611
|
}
|
|
612
612
|
let a = r[2].trim();
|
|
613
|
-
if (a.length === 0) throw new
|
|
613
|
+
if (a.length === 0) throw new Ze(i);
|
|
614
614
|
t.push({
|
|
615
615
|
tag: i,
|
|
616
616
|
reviewed: a
|
|
@@ -620,13 +620,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
620
620
|
tags: new Set(t.map((e) => e.tag)),
|
|
621
621
|
entries: t
|
|
622
622
|
};
|
|
623
|
-
},
|
|
623
|
+
}, Qe = (e) => {
|
|
624
624
|
let t = e.indexOf(".");
|
|
625
625
|
return t <= 0 ? void 0 : e.slice(0, t);
|
|
626
|
-
},
|
|
626
|
+
}, $e = (e, t) => {
|
|
627
627
|
let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
|
|
628
628
|
for (let e of t.rpcTags) {
|
|
629
|
-
let t =
|
|
629
|
+
let t = Qe(e);
|
|
630
630
|
if (t === void 0) continue;
|
|
631
631
|
let n = i.get(t);
|
|
632
632
|
n ? n.push(e) : i.set(t, [e]);
|
|
@@ -655,7 +655,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
655
655
|
});
|
|
656
656
|
continue;
|
|
657
657
|
}
|
|
658
|
-
let s = new Set(o.rpcTags.map(
|
|
658
|
+
let s = new Set(o.rpcTags.map(Qe).filter((e) => e !== void 0));
|
|
659
659
|
for (let e of s) {
|
|
660
660
|
let t = i.get(e);
|
|
661
661
|
t !== void 0 && a.push({
|
|
@@ -668,12 +668,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
668
668
|
}
|
|
669
669
|
}
|
|
670
670
|
return a;
|
|
671
|
-
},
|
|
671
|
+
}, et = (e, t) => {
|
|
672
672
|
let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
|
|
673
673
|
if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
|
|
674
674
|
for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
|
|
675
675
|
return r;
|
|
676
|
-
},
|
|
676
|
+
}, tt = async (e) => {
|
|
677
677
|
if (!await e.hasWorkflows().catch(() => !1)) return { kind: "not-applicable" };
|
|
678
678
|
let t;
|
|
679
679
|
try {
|
|
@@ -697,12 +697,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
697
697
|
} finally {
|
|
698
698
|
await t.close().catch(() => {});
|
|
699
699
|
}
|
|
700
|
-
},
|
|
700
|
+
}, nt = (e) => {
|
|
701
701
|
let t = Math.round(e / 6e4);
|
|
702
702
|
if (t < 90) return `${t}m`;
|
|
703
703
|
let n = Math.round(t / 60);
|
|
704
704
|
return n < 48 ? `${n}h` : `${Math.round(n / 24)}d`;
|
|
705
|
-
},
|
|
705
|
+
}, rt = (e) => {
|
|
706
706
|
if (e.kind === "not-applicable") return null;
|
|
707
707
|
if (e.kind === "skipped") return `• stuck runs: NOT CHECKED — ${e.reason}\n This is the one doctor rule that reads the database rather than your source.
|
|
708
708
|
Run it where the app's DB env vars are set, or ignore this line locally.`;
|
|
@@ -710,13 +710,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
710
710
|
if (t.stalled.length === 0) n.push(`✓ stuck runs: none (${t.examined} live run(s) old enough to examine${t.waitingOnTimer > 0 ? `, ${t.waitingOnTimer} waiting on a durable timer` : ""}${t.alreadyReported > 0 ? `, ${t.alreadyReported} already reported` : ""})`);
|
|
711
711
|
else {
|
|
712
712
|
n.push(`✗ stuck runs: ${t.stalled.length} run(s) have made no progress`);
|
|
713
|
-
for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${
|
|
713
|
+
for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${nt(e.idleMs)} ${e.reason}`);
|
|
714
714
|
t.stalled.length > 20 && n.push(` … and ${t.stalled.length - 20} more`), n.push(" Nothing was changed — this is a SUSPICION, not a verdict. `awaiting-signal` usually means the sender never came.");
|
|
715
715
|
}
|
|
716
716
|
t.sawFullRunPage && n.push(" (the examined page filled up — more old runs exist than were read this pass)");
|
|
717
717
|
for (let e of t.failures) n.push(` ! could not examine ${e.subject}: ${e.detail}`);
|
|
718
718
|
return n.join("\n");
|
|
719
|
-
},
|
|
719
|
+
}, it = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), at = (e) => {
|
|
720
720
|
let t = m(e), n = p(e);
|
|
721
721
|
return {
|
|
722
722
|
root: e,
|
|
@@ -725,19 +725,22 @@ var le = /* @__PURE__ */ new Set([
|
|
|
725
725
|
apiBundle: n,
|
|
726
726
|
apiBundlePresent: S(n)
|
|
727
727
|
};
|
|
728
|
-
},
|
|
728
|
+
}, ot = [
|
|
729
729
|
"Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
|
|
730
730
|
"Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
|
|
731
731
|
"(Generated Dockerfiles from `voltro create-project` already do this.)"
|
|
732
|
-
].join("\n "),
|
|
733
|
-
let n =
|
|
734
|
-
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !
|
|
732
|
+
].join("\n "), st = (e, t = !1) => {
|
|
733
|
+
let n = at(e);
|
|
734
|
+
if (console.log(`voltro doctor — serve preflight for ${n.root}`), !it(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
|
|
735
735
|
console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
|
|
736
736
|
let r = process.env.NODE_ENV === "production";
|
|
737
737
|
if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
|
|
738
738
|
let i = t || r;
|
|
739
|
-
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${
|
|
740
|
-
},
|
|
739
|
+
return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${ot}`), +!!i;
|
|
740
|
+
}, ct = (e) => {
|
|
741
|
+
let t = e.absPath.replace(/\.server\.(tsx?)$/, ".$1");
|
|
742
|
+
if (t !== e.absPath) return e.sources.get(t) ?? e.sources.get(t.replace(/\.ts$/, ".tsx"));
|
|
743
|
+
}, lt = (e, t, n) => {
|
|
741
744
|
let r = new Set(e), i = _(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
|
|
742
745
|
`${e}.ts`,
|
|
743
746
|
`${e}.tsx`,
|
|
@@ -763,7 +766,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
763
766
|
r !== void 0 && o.push(r);
|
|
764
767
|
}
|
|
765
768
|
return o;
|
|
766
|
-
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) =>
|
|
769
|
+
}, s = /* @__PURE__ */ new Set(), c = e.filter((e) => mt.test(y(n, e))), l = new Set(c);
|
|
767
770
|
for (; c.length > 0;) {
|
|
768
771
|
let e = c.shift();
|
|
769
772
|
if (!s.has(e)) {
|
|
@@ -776,11 +779,11 @@ var le = /* @__PURE__ */ new Set([
|
|
|
776
779
|
let d = /* @__PURE__ */ new Set();
|
|
777
780
|
for (let e of s) l.has(e) || u.has(e) || d.add(e);
|
|
778
781
|
return d;
|
|
779
|
-
},
|
|
782
|
+
}, N = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), ut = (e, t) => (e.match(t) ?? []).length, P = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, dt = (e, t) => {
|
|
780
783
|
if (e.src.includes(t)) return !0;
|
|
781
784
|
let n = t.replace(/^@voltro\/plugin-/, "");
|
|
782
|
-
return n !== t &&
|
|
783
|
-
},
|
|
785
|
+
return n !== t && P(e.file, RegExp(`^${n}Plugin$`)) > 0;
|
|
786
|
+
}, ft = (e, t) => {
|
|
784
787
|
let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
|
|
785
788
|
for (; (i = r.exec(e)) !== null;) {
|
|
786
789
|
let t = i.index + i[0].length, r = 1, a = t;
|
|
@@ -788,10 +791,10 @@ var le = /* @__PURE__ */ new Set([
|
|
|
788
791
|
n.push(e.slice(t, a - 1));
|
|
789
792
|
}
|
|
790
793
|
return n;
|
|
791
|
-
},
|
|
794
|
+
}, pt = (e, t) => {
|
|
792
795
|
let n = t.getSymbol()?.getDeclarations()?.[0];
|
|
793
796
|
return n === void 0 ? [] : e.getDescendantsOfKind(O.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
|
|
794
|
-
},
|
|
797
|
+
}, mt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, ht = /\.(query|mutation|action|stream)\.server\.tsx?$/, gt = [
|
|
795
798
|
"Id",
|
|
796
799
|
"ID",
|
|
797
800
|
"Hash",
|
|
@@ -817,7 +820,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
817
820
|
"Comment",
|
|
818
821
|
"Provider",
|
|
819
822
|
"Format"
|
|
820
|
-
],
|
|
823
|
+
], _t = RegExp(`(?:${gt.join("|")})$|_(?:${gt.map((e) => e.toLowerCase()).join("|")})$`), vt = [
|
|
821
824
|
{
|
|
822
825
|
id: "hardcoded-framework-cookie",
|
|
823
826
|
scope: "client",
|
|
@@ -830,17 +833,22 @@ var le = /* @__PURE__ */ new Set([
|
|
|
830
833
|
scope: "server",
|
|
831
834
|
smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
|
|
832
835
|
use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
|
|
833
|
-
match: ({ src: e, path: t }) =>
|
|
836
|
+
match: ({ src: e, path: t }) => ht.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
|
|
834
837
|
},
|
|
835
838
|
{
|
|
836
839
|
id: "subject-write-no-guard",
|
|
837
840
|
scope: "server",
|
|
838
|
-
smell: "writes ctx.request.subject.id into the database
|
|
839
|
-
use: "
|
|
840
|
-
match: (
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
841
|
+
smell: "an openAccess procedure writes ctx.request.subject.id into the database and never checks whose row it is — an anonymous caller reaches the write and gets a raw statement failure instead of a typed refusal",
|
|
842
|
+
use: "declare `guards:` on the descriptor, or — if the procedure really is open — compare the loaded row's owner against `ctx.request.subject.id` and fail with a typed error",
|
|
843
|
+
match: (e) => {
|
|
844
|
+
let { src: t, path: n } = e;
|
|
845
|
+
if (!ht.test(n)) return !1;
|
|
846
|
+
let r = N(t);
|
|
847
|
+
if (!(/\bsubject\s*\.\s*id\b/.test(r) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(r)) || /\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(r) || /[=!]==?\s*[^;\n]*\bsubject\s*\.\s*id\b|\bsubject\s*\.\s*id\b\s*[=!]==/.test(r)) return !1;
|
|
848
|
+
let i = ct(e);
|
|
849
|
+
if (i === void 0) return !1;
|
|
850
|
+
let a = N(i);
|
|
851
|
+
return /\binternal\s*:\s*true\b/.test(a) || /\bguards\s*:\s*\[/.test(a) || /\.guard\s*\(/.test(a) ? !1 : /\bopenAccess\s*:/.test(a);
|
|
844
852
|
}
|
|
845
853
|
},
|
|
846
854
|
{
|
|
@@ -848,14 +856,14 @@ var le = /* @__PURE__ */ new Set([
|
|
|
848
856
|
scope: "server",
|
|
849
857
|
smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
|
|
850
858
|
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",
|
|
851
|
-
match: ({ file: e, path: t, serverOnly: n }) => (
|
|
859
|
+
match: ({ file: e, path: t, serverOnly: n }) => (mt.test(t) || n) && P(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
|
|
852
860
|
},
|
|
853
861
|
{
|
|
854
862
|
id: "form",
|
|
855
863
|
scope: "client",
|
|
856
864
|
smell: "per-field useState + a submit flag (hand-rolled form)",
|
|
857
865
|
use: "useFormBinding — fields + validation from the mutation input Schema; server ValidationError({ field }) routes to the field",
|
|
858
|
-
match: ({ src: e, file: t }) =>
|
|
866
|
+
match: ({ src: e, file: t }) => P(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
|
|
859
867
|
},
|
|
860
868
|
{
|
|
861
869
|
id: "table",
|
|
@@ -883,7 +891,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
883
891
|
scope: "client",
|
|
884
892
|
smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
|
|
885
893
|
use: "useDerived — dependency-tracked, referentially stable",
|
|
886
|
-
match: ({ src: e }) =>
|
|
894
|
+
match: ({ src: e }) => ut(e, /\buseSubscription\s*[(<]/g) >= 3 && ut(e, /\buseMemo\s*\(/g) >= 3
|
|
887
895
|
},
|
|
888
896
|
{
|
|
889
897
|
id: "next-shim",
|
|
@@ -908,7 +916,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
908
916
|
match: ({ file: e }) => e.getDescendantsOfKind(O.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
|
|
909
917
|
let t = e.getFirstAncestorByKind(O.VariableDeclaration)?.getNameNode();
|
|
910
918
|
return t === void 0 ? [] : E.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
|
|
911
|
-
}).some((t) =>
|
|
919
|
+
}).some((t) => pt(e, t).some((e) => {
|
|
912
920
|
let t = e.getParent();
|
|
913
921
|
if (E.isBinaryExpression(t)) {
|
|
914
922
|
let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
|
|
@@ -936,7 +944,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
936
944
|
scope: "server",
|
|
937
945
|
smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
|
|
938
946
|
use: "reads are a parent→child walk on ONE key → relations() in a *.relations.ts + .with({ … }) (ONE JSON-aggregate query, every dialect); reads collect ids from SEVERAL sources → keep the assembly and run the independent leading reads under Effect.all (same queries, same results, only concurrent — zero parity risk)",
|
|
939
|
-
match: (e) =>
|
|
947
|
+
match: (e) => P(e.file, /\bstore\.query$/) >= 3 || P(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
|
|
940
948
|
},
|
|
941
949
|
{
|
|
942
950
|
id: "store-effect-lift",
|
|
@@ -959,7 +967,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
959
967
|
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)",
|
|
960
968
|
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) => {
|
|
961
969
|
let n = t[1];
|
|
962
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
970
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || _t.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
|
|
963
971
|
}))
|
|
964
972
|
},
|
|
965
973
|
{
|
|
@@ -969,7 +977,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
969
977
|
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",
|
|
970
978
|
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) => {
|
|
971
979
|
let n = t[1];
|
|
972
|
-
return !/token|secret|credential|password|apikey|accesskey/i.test(n) ||
|
|
980
|
+
return !/token|secret|credential|password|apikey|accesskey/i.test(n) || _t.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
|
|
973
981
|
}))
|
|
974
982
|
},
|
|
975
983
|
{
|
|
@@ -1033,9 +1041,9 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1033
1041
|
scope: "server",
|
|
1034
1042
|
smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
|
|
1035
1043
|
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",
|
|
1036
|
-
match: ({ file: e, testSources: t }) =>
|
|
1037
|
-
let t =
|
|
1038
|
-
return
|
|
1044
|
+
match: ({ file: e, testSources: t }) => P(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
|
|
1045
|
+
let t = N(e);
|
|
1046
|
+
return ft(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
|
|
1039
1047
|
})
|
|
1040
1048
|
},
|
|
1041
1049
|
{
|
|
@@ -1045,15 +1053,15 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1045
1053
|
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",
|
|
1046
1054
|
match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
|
|
1047
1055
|
}
|
|
1048
|
-
],
|
|
1056
|
+
], yt = (e) => I(e), bt = (e, t) => {
|
|
1049
1057
|
let n = /* @__PURE__ */ new Set();
|
|
1050
1058
|
for (let r of t) {
|
|
1051
1059
|
let t = y(e, r), i = t.split(/[\\/]/)[0];
|
|
1052
1060
|
n.add(i !== void 0 && i !== t ? i : ".");
|
|
1053
1061
|
}
|
|
1054
1062
|
return [...n].sort();
|
|
1055
|
-
},
|
|
1056
|
-
if (n > 12 ||
|
|
1063
|
+
}, xt = /\.(tsx?|jsx?)$/, F = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, St = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Ct = (e, t = [], n = 0) => {
|
|
1064
|
+
if (n > 12 || F.test(e)) return t;
|
|
1057
1065
|
let r;
|
|
1058
1066
|
try {
|
|
1059
1067
|
r = w(e, { withFileTypes: !0 });
|
|
@@ -1064,12 +1072,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1064
1072
|
let r = v(e, i.name);
|
|
1065
1073
|
if (i.isDirectory()) {
|
|
1066
1074
|
if (i.name.startsWith(".")) continue;
|
|
1067
|
-
|
|
1075
|
+
Ct(r, t, n + 1);
|
|
1068
1076
|
} else je.test(i.name) && t.push(r);
|
|
1069
1077
|
}
|
|
1070
1078
|
return t;
|
|
1071
|
-
},
|
|
1072
|
-
if (n > 12 ||
|
|
1079
|
+
}, I = (e, t = [], n = 0) => {
|
|
1080
|
+
if (n > 12 || F.test(e)) return t;
|
|
1073
1081
|
let r;
|
|
1074
1082
|
try {
|
|
1075
1083
|
r = w(e);
|
|
@@ -1083,11 +1091,11 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1083
1091
|
} catch {
|
|
1084
1092
|
continue;
|
|
1085
1093
|
}
|
|
1086
|
-
a.isDirectory() ?
|
|
1094
|
+
a.isDirectory() ? I(r, t, n + 1) : xt.test(i) && !/\.(test|spec)\./.test(i) && !St.test(i) && t.push(r);
|
|
1087
1095
|
}
|
|
1088
1096
|
return t;
|
|
1089
|
-
},
|
|
1090
|
-
if (n > 12 ||
|
|
1097
|
+
}, wt = (e, t = [], n = 0) => {
|
|
1098
|
+
if (n > 12 || F.test(e)) return t;
|
|
1091
1099
|
let r;
|
|
1092
1100
|
try {
|
|
1093
1101
|
r = w(e);
|
|
@@ -1101,17 +1109,17 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1101
1109
|
} catch {
|
|
1102
1110
|
continue;
|
|
1103
1111
|
}
|
|
1104
|
-
a.isDirectory() ?
|
|
1112
|
+
a.isDirectory() ? wt(r, t, n + 1) : xt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
|
|
1105
1113
|
}
|
|
1106
1114
|
return t;
|
|
1107
|
-
},
|
|
1108
|
-
let t =
|
|
1115
|
+
}, Tt = (e) => {
|
|
1116
|
+
let t = yt(e);
|
|
1109
1117
|
return {
|
|
1110
|
-
dirs:
|
|
1118
|
+
dirs: bt(e, t),
|
|
1111
1119
|
fileCount: t.length
|
|
1112
1120
|
};
|
|
1113
|
-
},
|
|
1114
|
-
let t =
|
|
1121
|
+
}, Et = (e) => {
|
|
1122
|
+
let t = yt(e);
|
|
1115
1123
|
if (t.length === 0) return [];
|
|
1116
1124
|
let n = new D({
|
|
1117
1125
|
useInMemoryFileSystem: !0,
|
|
@@ -1120,8 +1128,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1120
1128
|
for (let e of t) try {
|
|
1121
1129
|
i.set(e, C(e, "utf8"));
|
|
1122
1130
|
} catch {}
|
|
1123
|
-
let a =
|
|
1124
|
-
for (let t of
|
|
1131
|
+
let a = lt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
|
|
1132
|
+
for (let t of wt(e)) try {
|
|
1125
1133
|
o.set(t, C(t, "utf8"));
|
|
1126
1134
|
} catch {}
|
|
1127
1135
|
for (let s of t) {
|
|
@@ -1134,28 +1142,30 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1134
1142
|
file: n.createSourceFile(`/scan${s.endsWith("x") ? ".tsx" : ".ts"}`, t, { overwrite: !0 }),
|
|
1135
1143
|
path: y(e, s),
|
|
1136
1144
|
serverOnly: a.has(s),
|
|
1137
|
-
testSources: o
|
|
1145
|
+
testSources: o,
|
|
1146
|
+
sources: i,
|
|
1147
|
+
absPath: s
|
|
1138
1148
|
};
|
|
1139
1149
|
} catch {
|
|
1140
1150
|
continue;
|
|
1141
1151
|
}
|
|
1142
|
-
for (let t of
|
|
1143
|
-
if (t.plugin !== void 0 &&
|
|
1152
|
+
for (let t of vt) {
|
|
1153
|
+
if (t.plugin !== void 0 && dt(c, t.plugin) || !t.match(c)) continue;
|
|
1144
1154
|
let n = r.get(t.id) ?? [];
|
|
1145
1155
|
n.push(y(e, s)), r.set(t.id, n);
|
|
1146
1156
|
}
|
|
1147
1157
|
}
|
|
1148
|
-
return
|
|
1158
|
+
return vt.filter((e) => r.has(e.id)).map((e) => ({
|
|
1149
1159
|
rule: e,
|
|
1150
1160
|
files: r.get(e.id)
|
|
1151
1161
|
})).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
|
|
1152
|
-
},
|
|
1153
|
-
let t =
|
|
1162
|
+
}, L = (e) => {
|
|
1163
|
+
let t = Tt(e);
|
|
1154
1164
|
return {
|
|
1155
1165
|
root: e,
|
|
1156
1166
|
scannedFiles: t.fileCount,
|
|
1157
1167
|
scannedDirs: t.dirs,
|
|
1158
|
-
findings:
|
|
1168
|
+
findings: Et(e).map(({ rule: e, files: t }) => ({
|
|
1159
1169
|
id: e.id,
|
|
1160
1170
|
scope: e.scope,
|
|
1161
1171
|
smell: e.smell,
|
|
@@ -1163,13 +1173,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1163
1173
|
files: t
|
|
1164
1174
|
}))
|
|
1165
1175
|
};
|
|
1166
|
-
},
|
|
1176
|
+
}, Dt = 3, Ot = .2, kt = 50, At = (e, t) => e >= kt && t > 0 && e / t >= Ot, jt = (e) => {
|
|
1167
1177
|
let t = /* @__PURE__ */ new Map();
|
|
1168
|
-
for (let n of
|
|
1178
|
+
for (let n of I(e)) try {
|
|
1169
1179
|
t.set(y(e, n), C(n, "utf8"));
|
|
1170
1180
|
} catch {}
|
|
1171
1181
|
let n = /* @__PURE__ */ new Map();
|
|
1172
|
-
for (let t of
|
|
1182
|
+
for (let t of Ct(e)) try {
|
|
1173
1183
|
n.set(y(e, t), C(t, "utf8"));
|
|
1174
1184
|
} catch {}
|
|
1175
1185
|
let r = Me({
|
|
@@ -1177,8 +1187,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1177
1187
|
envFiles: n
|
|
1178
1188
|
});
|
|
1179
1189
|
r !== void 0 && (console.log(""), console.log(Ne(r)));
|
|
1180
|
-
},
|
|
1181
|
-
let t =
|
|
1190
|
+
}, Mt = (e) => {
|
|
1191
|
+
let t = L(e);
|
|
1182
1192
|
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;
|
|
1183
1193
|
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;
|
|
1184
1194
|
console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
|
|
@@ -1189,24 +1199,24 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1189
1199
|
console.log(` [${e}]`);
|
|
1190
1200
|
let i = [...r].sort((e, t) => e.files.length - t.files.length);
|
|
1191
1201
|
for (let e of i) {
|
|
1192
|
-
if (
|
|
1202
|
+
if (At(e.files.length, t.scannedFiles)) {
|
|
1193
1203
|
let n = Math.round(e.files.length / t.scannedFiles * 100);
|
|
1194
1204
|
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.)");
|
|
1195
1205
|
continue;
|
|
1196
1206
|
}
|
|
1197
|
-
let r = e.files.slice(0,
|
|
1207
|
+
let r = e.files.slice(0, Dt).join(", "), i = e.files.length - Dt;
|
|
1198
1208
|
i > 0 && (n += i);
|
|
1199
1209
|
let a = i > 0 ? `, +${i} more` : "";
|
|
1200
1210
|
console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
|
|
1201
1211
|
}
|
|
1202
1212
|
}
|
|
1203
1213
|
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;
|
|
1204
|
-
},
|
|
1205
|
-
let t =
|
|
1214
|
+
}, Nt = "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.", Pt = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, Ft = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, It = (e) => {
|
|
1215
|
+
let t = Pt.exec(e)?.[1];
|
|
1206
1216
|
if (t !== void 0) return t === "ssr" ? "ssr" : null;
|
|
1207
|
-
let n =
|
|
1217
|
+
let n = Ft.exec(e)?.[1];
|
|
1208
1218
|
return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
|
|
1209
|
-
},
|
|
1219
|
+
}, R = async (e) => {
|
|
1210
1220
|
let t = v(e, "src", "pages");
|
|
1211
1221
|
if (!S(t)) return [];
|
|
1212
1222
|
let { pages: n, dirs: r } = await u(t), i = [];
|
|
@@ -1219,7 +1229,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1219
1229
|
continue;
|
|
1220
1230
|
}
|
|
1221
1231
|
if (c.test(n)) continue;
|
|
1222
|
-
let s =
|
|
1232
|
+
let s = It(n);
|
|
1223
1233
|
s !== null && i.push({
|
|
1224
1234
|
file: y(e, v(t, a.file)),
|
|
1225
1235
|
pattern: a.pattern,
|
|
@@ -1227,20 +1237,20 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1227
1237
|
});
|
|
1228
1238
|
}
|
|
1229
1239
|
return i;
|
|
1230
|
-
},
|
|
1240
|
+
}, Lt = 10, Rt = async (e) => {
|
|
1231
1241
|
console.log("");
|
|
1232
1242
|
let t = v(e, "src", "pages");
|
|
1233
1243
|
if (!S(t)) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
|
|
1234
|
-
let n = await
|
|
1244
|
+
let n = await R(e);
|
|
1235
1245
|
if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
|
|
1236
1246
|
console.log(`• renderMode:'spa' candidates (${n.length} page${n.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
|
|
1237
|
-
for (let e of n.slice(0,
|
|
1247
|
+
for (let e of n.slice(0, Lt)) {
|
|
1238
1248
|
let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
|
|
1239
1249
|
console.log(` ${e.file} (${e.pattern}) — ${t}`);
|
|
1240
1250
|
}
|
|
1241
|
-
let r = n.length -
|
|
1242
|
-
return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${
|
|
1243
|
-
},
|
|
1251
|
+
let r = n.length - Lt;
|
|
1252
|
+
return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${Nt}`), n.length;
|
|
1253
|
+
}, zt = /* @__PURE__ */ new Set([
|
|
1244
1254
|
"node_modules",
|
|
1245
1255
|
"dist",
|
|
1246
1256
|
"build",
|
|
@@ -1248,7 +1258,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1248
1258
|
".vite",
|
|
1249
1259
|
".framework",
|
|
1250
1260
|
".git"
|
|
1251
|
-
]),
|
|
1261
|
+
]), Bt = (e, t, n) => {
|
|
1252
1262
|
let r;
|
|
1253
1263
|
try {
|
|
1254
1264
|
r = w(e, { withFileTypes: !0 });
|
|
@@ -1256,11 +1266,11 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1256
1266
|
return;
|
|
1257
1267
|
}
|
|
1258
1268
|
for (let i of r) {
|
|
1259
|
-
if (
|
|
1269
|
+
if (zt.has(i.name)) continue;
|
|
1260
1270
|
let r = v(e, i.name);
|
|
1261
|
-
i.isDirectory() ?
|
|
1271
|
+
i.isDirectory() ? Bt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
|
|
1262
1272
|
}
|
|
1263
|
-
},
|
|
1273
|
+
}, Vt = (e, t) => {
|
|
1264
1274
|
let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
|
|
1265
1275
|
for (let i of t) {
|
|
1266
1276
|
let t;
|
|
@@ -1285,16 +1295,16 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1285
1295
|
}
|
|
1286
1296
|
}
|
|
1287
1297
|
return n;
|
|
1288
|
-
},
|
|
1298
|
+
}, Ht = (e, t) => {
|
|
1289
1299
|
let n = e.slice(t.length).split(x).slice(0, -1)[0];
|
|
1290
1300
|
return n !== void 0 && n.startsWith("[");
|
|
1291
|
-
},
|
|
1301
|
+
}, z = (e) => {
|
|
1292
1302
|
let t = [], n = v(e, "src", "pages"), r = v(e, "src");
|
|
1293
1303
|
if (!S(r)) return t;
|
|
1294
1304
|
let i = [];
|
|
1295
|
-
|
|
1305
|
+
Bt(r, e, i), i.sort();
|
|
1296
1306
|
let a = `${y(e, n)}${x}`;
|
|
1297
|
-
|
|
1307
|
+
Vt(e, i);
|
|
1298
1308
|
for (let n of i) {
|
|
1299
1309
|
let r = n.slice(n.lastIndexOf(x) + 1);
|
|
1300
1310
|
if (n.startsWith(a)) {
|
|
@@ -1310,7 +1320,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1310
1320
|
file: n
|
|
1311
1321
|
});
|
|
1312
1322
|
} catch {}
|
|
1313
|
-
|
|
1323
|
+
Ht(n, a) && t.push({
|
|
1314
1324
|
kind: "top-level-dynamic-route",
|
|
1315
1325
|
file: n
|
|
1316
1326
|
});
|
|
@@ -1322,35 +1332,35 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1322
1332
|
});
|
|
1323
1333
|
}
|
|
1324
1334
|
return t;
|
|
1325
|
-
},
|
|
1335
|
+
}, Ut = 10, B = (e, t, n) => {
|
|
1326
1336
|
if (t.length === 0) return;
|
|
1327
1337
|
console.log(`• ${e} (${t.length}):`);
|
|
1328
|
-
for (let e of t.slice(0,
|
|
1329
|
-
let r = t.length -
|
|
1338
|
+
for (let e of t.slice(0, Ut)) console.log(` ${e}`);
|
|
1339
|
+
let r = t.length - Ut;
|
|
1330
1340
|
r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
|
|
1331
|
-
},
|
|
1341
|
+
}, Wt = (e) => {
|
|
1332
1342
|
console.log("");
|
|
1333
1343
|
let t = v(e, "src", "pages");
|
|
1334
1344
|
if (!S(t)) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
|
|
1335
|
-
let n =
|
|
1345
|
+
let n = z(e);
|
|
1336
1346
|
if (n.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
|
|
1337
1347
|
let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
|
|
1338
|
-
return
|
|
1339
|
-
},
|
|
1340
|
-
let t =
|
|
1348
|
+
return B("will NEVER route — page.tsx outside src/pages/", r("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), B("pages with no test beside them", r("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), B("routes whose FIRST segment is dynamic — they claim every URL of that depth", r("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), B("pages with no `meta` export — they serve the APP name as their title", r("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). If such a page also has no real body — it redirects from an effect, say — the missing title is the symptom rather than the bug: on an `ssr` route the server answers 200 with an empty document and the redirect happens only once JavaScript runs. Move it into the loader (`redirect(...)`, a real 303). Advisory."), n.length;
|
|
1349
|
+
}, Gt = "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.", Kt = /* @__PURE__ */ new Map(), V = (e) => {
|
|
1350
|
+
let t = Kt.get(e);
|
|
1341
1351
|
if (t !== void 0) return t;
|
|
1342
1352
|
let n = (async () => {
|
|
1343
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
1353
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-D0E5x2Ue.js"), r = await t(e);
|
|
1344
1354
|
return {
|
|
1345
1355
|
files: r,
|
|
1346
1356
|
discovered: await n(r)
|
|
1347
1357
|
};
|
|
1348
1358
|
})();
|
|
1349
|
-
return
|
|
1350
|
-
},
|
|
1359
|
+
return Kt.set(e, n), n;
|
|
1360
|
+
}, qt = async (e) => {
|
|
1351
1361
|
let t;
|
|
1352
1362
|
try {
|
|
1353
|
-
let { files: n, discovered: r } = await
|
|
1363
|
+
let { files: n, discovered: r } = await V(e), i = r.tables.map((e) => {
|
|
1354
1364
|
let t = e;
|
|
1355
1365
|
return {
|
|
1356
1366
|
name: t.tableName,
|
|
@@ -1363,7 +1373,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1363
1373
|
return;
|
|
1364
1374
|
}
|
|
1365
1375
|
t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
|
|
1366
|
-
},
|
|
1376
|
+
}, Jt = (e) => e.map((e) => {
|
|
1367
1377
|
let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
|
|
1368
1378
|
name: e,
|
|
1369
1379
|
type: typeof t?.type == "string" ? t.type : ""
|
|
@@ -1373,21 +1383,21 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1373
1383
|
columns: n,
|
|
1374
1384
|
primaryKey: t.appliedPrimaryKey ?? []
|
|
1375
1385
|
};
|
|
1376
|
-
}),
|
|
1386
|
+
}), H = async (e) => {
|
|
1377
1387
|
try {
|
|
1378
|
-
let { discovered: t } = await
|
|
1379
|
-
return ve(
|
|
1388
|
+
let { discovered: t } = await V(e);
|
|
1389
|
+
return ve(Jt(t.tables));
|
|
1380
1390
|
} catch {
|
|
1381
1391
|
return null;
|
|
1382
1392
|
}
|
|
1383
|
-
},
|
|
1384
|
-
let t = await
|
|
1393
|
+
}, Yt = async (e) => {
|
|
1394
|
+
let t = await H(e);
|
|
1385
1395
|
if (t === null || t.tablesChecked === 0) return;
|
|
1386
1396
|
let n = be(t);
|
|
1387
1397
|
n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
|
|
1388
|
-
},
|
|
1398
|
+
}, U = async (e) => {
|
|
1389
1399
|
try {
|
|
1390
|
-
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await
|
|
1400
|
+
let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await V(e);
|
|
1391
1401
|
return we(n.events.map((e) => {
|
|
1392
1402
|
let n = e.descriptor, r = t(n.key);
|
|
1393
1403
|
return {
|
|
@@ -1400,19 +1410,19 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1400
1410
|
} catch {
|
|
1401
1411
|
return null;
|
|
1402
1412
|
}
|
|
1403
|
-
},
|
|
1404
|
-
let t = await
|
|
1413
|
+
}, Xt = async (e) => {
|
|
1414
|
+
let t = await U(e);
|
|
1405
1415
|
if (t === null || t.eventsChecked === 0) return;
|
|
1406
1416
|
let n = Te(t);
|
|
1407
1417
|
n !== null && (console.log(""), console.log(`• ${n}`));
|
|
1408
1418
|
let r = Ee(t);
|
|
1409
1419
|
r !== null && (console.log(""), console.log(`⚠️ ${r}`));
|
|
1410
|
-
},
|
|
1420
|
+
}, W = (e) => {
|
|
1411
1421
|
let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
|
|
1412
1422
|
return r === void 0 ? n : `${r}: ${n}`;
|
|
1413
|
-
},
|
|
1423
|
+
}, G = async (e) => {
|
|
1414
1424
|
try {
|
|
1415
|
-
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await
|
|
1425
|
+
let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await V(e)).discovered.serverOnlyLeaks, r = t(n);
|
|
1416
1426
|
return {
|
|
1417
1427
|
status: {
|
|
1418
1428
|
checked: !0,
|
|
@@ -1423,12 +1433,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1423
1433
|
} catch (e) {
|
|
1424
1434
|
return { status: {
|
|
1425
1435
|
checked: !1,
|
|
1426
|
-
reason:
|
|
1436
|
+
reason: W(e)
|
|
1427
1437
|
} };
|
|
1428
1438
|
}
|
|
1429
|
-
},
|
|
1439
|
+
}, K = async (r) => {
|
|
1430
1440
|
try {
|
|
1431
|
-
let { loadApiConfig: i } = await import("./dev-
|
|
1441
|
+
let { loadApiConfig: i } = await import("./dev-D0E5x2Ue.js"), { discovered: a } = await V(r), o = n(await i(r)), s = {
|
|
1432
1442
|
queries: a.queries,
|
|
1433
1443
|
mutations: a.mutations,
|
|
1434
1444
|
actions: a.actions,
|
|
@@ -1444,26 +1454,26 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1444
1454
|
} catch (e) {
|
|
1445
1455
|
return {
|
|
1446
1456
|
checked: !1,
|
|
1447
|
-
reason:
|
|
1457
|
+
reason: W(e),
|
|
1448
1458
|
defaultDeny: !0,
|
|
1449
1459
|
undecided: [],
|
|
1450
1460
|
openAccess: []
|
|
1451
1461
|
};
|
|
1452
1462
|
}
|
|
1453
|
-
},
|
|
1454
|
-
let t = await
|
|
1463
|
+
}, Zt = async (e) => {
|
|
1464
|
+
let t = await K(e);
|
|
1455
1465
|
if (!t.checked) return console.log(""), console.log("• access decisions: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
|
|
1456
1466
|
console.log(""), console.log(`access decisions · security.defaultDeny ${t.defaultDeny ? "ON" : "OFF"}`), console.log(` ✗ no access decision ${String(t.undecided.length).padStart(4)}`), console.log(` ✓ openAccess, declared on purpose ${String(t.openAccess.length).padStart(4)}`);
|
|
1457
1467
|
for (let e of t.openAccess) console.log(` ${e.tag} — ${e.openAccess ?? ""}`);
|
|
1458
1468
|
if (t.undecided.length === 0) return !1;
|
|
1459
1469
|
console.log("");
|
|
1460
1470
|
for (let e of t.undecided) console.log(` ✗ ${e.tag} (${e.kind})${e.file === void 0 ? "" : `\n ${e.file}`}`);
|
|
1461
|
-
return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"),
|
|
1462
|
-
},
|
|
1471
|
+
return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), Qt(), !0) : (console.log(" This app sets `security: { defaultDeny: false }`, so these BOOT — and each one is"), console.log(" callable by any authenticated session. Advisory here for that reason only."), Qt(), !1);
|
|
1472
|
+
}, Qt = () => {
|
|
1463
1473
|
console.log(" `guards: [{ scope: '…' }]` authority the SUBJECT holds globally"), console.log(" `guards: [{ action, resourceType, resource }]` authority per ROW / per resource"), console.log(" — backed by `defineResourcePolicy` + a tuple source you register. If your"), console.log(" relationships already live in your own tables (a `teamMembers` row), register"), console.log(" a tuple source over them; nothing is copied. Fail-closed without a resolver."), console.log(" Docs: Authentication → Authorization."), console.log(" `openAccess: '<why anyone may call it>'` deliberately unauthenticated");
|
|
1464
|
-
},
|
|
1474
|
+
}, q = async (e) => {
|
|
1465
1475
|
try {
|
|
1466
|
-
let { files: t } = await
|
|
1476
|
+
let { files: t } = await V(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
|
|
1467
1477
|
if (a.length === 0) return {
|
|
1468
1478
|
checked: !0,
|
|
1469
1479
|
unverified: [],
|
|
@@ -1490,14 +1500,14 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1490
1500
|
} catch (e) {
|
|
1491
1501
|
return {
|
|
1492
1502
|
checked: !1,
|
|
1493
|
-
reason:
|
|
1503
|
+
reason: W(e),
|
|
1494
1504
|
unverified: [],
|
|
1495
1505
|
open: [],
|
|
1496
1506
|
verified: 0
|
|
1497
1507
|
};
|
|
1498
1508
|
}
|
|
1499
|
-
},
|
|
1500
|
-
let t = await
|
|
1509
|
+
}, $t = async (e) => {
|
|
1510
|
+
let t = await q(e);
|
|
1501
1511
|
if (!t.checked) return console.log(""), console.log("• webhook verification: NOT CHECKED — the app's webhooks could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
|
|
1502
1512
|
if (t.verified + t.open.length + t.unverified.length === 0) return !1;
|
|
1503
1513
|
console.log(""), console.log("incoming webhook verification"), console.log(` ✓ signature / provider verified ${String(t.verified).padStart(4)}`), console.log(` ! deliberately public (none) ${String(t.open.length).padStart(4)}`), console.log(` ✗ nothing verifies the caller ${String(t.unverified.length).padStart(4)}`);
|
|
@@ -1506,13 +1516,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1506
1516
|
console.log("");
|
|
1507
1517
|
for (let e of t.unverified) console.log(` ✗ ${e.id}\n ${e.file}`);
|
|
1508
1518
|
return console.log(""), console.log(" These are public POSTs that run your application code, and `voltro dev` /"), console.log(" `voltro serve` REFUSE to mount them. Declare one of: `provider:` (a preset),"), console.log(" `signature:` (your own HMAC scheme), `verification: 'provider'` (your handler"), console.log(" verifies), or `verification: 'none'` (deliberately public)."), !0;
|
|
1509
|
-
},
|
|
1510
|
-
let { status: t, message: n } = await
|
|
1519
|
+
}, en = async (e) => {
|
|
1520
|
+
let { status: t, message: n } = await G(e);
|
|
1511
1521
|
return t.checked ? n !== void 0 && (console.log(""), console.log(`✗ [serverOnly] ${n}`), !0) : (console.log(""), console.log("• serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), console.log(" This audit is the automated check that a hash or a token cannot reach the wire, so"), console.log(" an app that leans on `.serverOnly()` should treat a persistent skip as a failure."), console.log(" `voltro doctor --json` reports it as `serverOnly.checked: false` for a CI gate."), !1);
|
|
1512
|
-
},
|
|
1513
|
-
hasWorkflows: async () => (await
|
|
1522
|
+
}, tn = async (e) => tt({
|
|
1523
|
+
hasWorkflows: async () => (await V(e)).discovered.workflows.length > 0,
|
|
1514
1524
|
openStore: async () => {
|
|
1515
|
-
let { loadApiConfig: t, buildStore: n } = await import("./dev-
|
|
1525
|
+
let { loadApiConfig: t, buildStore: n } = await import("./dev-D0E5x2Ue.js"), r = await t(e);
|
|
1516
1526
|
if (r === null) throw Error("no app.config.ts in this directory");
|
|
1517
1527
|
let i = await n(r, void 0);
|
|
1518
1528
|
return {
|
|
@@ -1526,13 +1536,13 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1526
1536
|
let { sweepStalledRuns: t } = await import("@voltro/workflow");
|
|
1527
1537
|
return t({ store: e });
|
|
1528
1538
|
}
|
|
1529
|
-
}),
|
|
1530
|
-
let t =
|
|
1539
|
+
}), nn = async (e) => {
|
|
1540
|
+
let t = rt(await tn(e));
|
|
1531
1541
|
t !== null && (console.log(""), console.log(t));
|
|
1532
|
-
},
|
|
1542
|
+
}, rn = async (e) => {
|
|
1533
1543
|
let t;
|
|
1534
1544
|
try {
|
|
1535
|
-
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await
|
|
1545
|
+
let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await V(e);
|
|
1536
1546
|
t = ke(r, i.workflows.map((e) => {
|
|
1537
1547
|
let t = e.definition, r = t.payloadSchema === void 0 ? {
|
|
1538
1548
|
required: [],
|
|
@@ -1552,18 +1562,18 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1552
1562
|
console.log("");
|
|
1553
1563
|
let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
|
|
1554
1564
|
console.log(`${r} ${n}`);
|
|
1555
|
-
},
|
|
1565
|
+
}, J = "voltro-authz-allowlist.txt", an = (e, t = (e) => {
|
|
1556
1566
|
try {
|
|
1557
1567
|
return C(e, "utf8");
|
|
1558
1568
|
} catch {
|
|
1559
1569
|
return "";
|
|
1560
1570
|
}
|
|
1561
|
-
}) => /* @__PURE__ */ new Set([...
|
|
1571
|
+
}) => /* @__PURE__ */ new Set([...ze, ...Be(I(e).map(t))]), Y = (e, t) => {
|
|
1562
1572
|
let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1563
1573
|
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];
|
|
1564
1574
|
}, X = async (e) => {
|
|
1565
1575
|
try {
|
|
1566
|
-
let { discovered: t } = await
|
|
1576
|
+
let { discovered: t } = await V(e), n = t.events.map((e) => ({
|
|
1567
1577
|
name: e.descriptor.name,
|
|
1568
1578
|
file: e.file
|
|
1569
1579
|
})).filter((e) => typeof e.name == "string");
|
|
@@ -1574,25 +1584,25 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1574
1584
|
} catch {
|
|
1575
1585
|
return "";
|
|
1576
1586
|
}
|
|
1577
|
-
}, a =
|
|
1587
|
+
}, a = I(e).filter((e) => !r.has(e)).map(i).join("\n");
|
|
1578
1588
|
return n.filter(({ name: e, file: t }) => {
|
|
1579
1589
|
if (a.includes(e)) return !1;
|
|
1580
|
-
let n =
|
|
1590
|
+
let n = Y(i(t), e);
|
|
1581
1591
|
return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
|
|
1582
1592
|
}).map((e) => e.name).sort();
|
|
1583
1593
|
} catch {
|
|
1584
1594
|
return [];
|
|
1585
1595
|
}
|
|
1586
|
-
},
|
|
1596
|
+
}, on = async (e) => {
|
|
1587
1597
|
let t = await X(e);
|
|
1588
1598
|
if (t.length !== 0) {
|
|
1589
1599
|
console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
|
|
1590
1600
|
for (let e of t) console.log(` ${e}`);
|
|
1591
1601
|
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.");
|
|
1592
1602
|
}
|
|
1593
|
-
},
|
|
1603
|
+
}, sn = async (e) => {
|
|
1594
1604
|
try {
|
|
1595
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
1605
|
+
let { loadApiConfig: t } = await import("./dev-D0E5x2Ue.js"), n = (await t(e))?.doctor?.scopeVocabulary;
|
|
1596
1606
|
if (typeof n != "string") return [];
|
|
1597
1607
|
let [r, i] = n.split("#");
|
|
1598
1608
|
if (r === void 0 || i === void 0) return [];
|
|
@@ -1609,7 +1619,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1609
1619
|
findings: []
|
|
1610
1620
|
});
|
|
1611
1621
|
try {
|
|
1612
|
-
let { offlineManifest: n, toInput: r } = await import("./checkCommand-
|
|
1622
|
+
let { offlineManifest: n, toInput: r } = await import("./checkCommand-DBpTSK8d.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-zuMGKVYX.js"), o = await n(e), s = r(o.manifest, o.tables), c = await sn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
|
|
1613
1623
|
if (l.length === 0) {
|
|
1614
1624
|
try {
|
|
1615
1625
|
let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-DgylYh9_.js");
|
|
@@ -1636,7 +1646,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1636
1646
|
} catch {
|
|
1637
1647
|
return t("app-not-importable");
|
|
1638
1648
|
}
|
|
1639
|
-
},
|
|
1649
|
+
}, cn = async (e) => {
|
|
1640
1650
|
let { findings: t, evaluated: n, notEvaluated: r } = await Z(e);
|
|
1641
1651
|
if (!n) {
|
|
1642
1652
|
if (console.log(""), r === "app-not-importable") {
|
|
@@ -1651,7 +1661,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1651
1661
|
for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
|
|
1652
1662
|
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}`);
|
|
1653
1663
|
}
|
|
1654
|
-
},
|
|
1664
|
+
}, ln = async (e) => {
|
|
1655
1665
|
let t = ["src/locales", "locales"], n, r = [];
|
|
1656
1666
|
for (let i of t) {
|
|
1657
1667
|
let t = v(e, i);
|
|
@@ -1663,7 +1673,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1663
1673
|
if (n === void 0 || r.length === 0) return;
|
|
1664
1674
|
let i = [];
|
|
1665
1675
|
try {
|
|
1666
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
1676
|
+
let { loadApiConfig: t } = await import("./dev-D0E5x2Ue.js");
|
|
1667
1677
|
i = (await t(e))?.locales ?? [];
|
|
1668
1678
|
} catch {
|
|
1669
1679
|
return;
|
|
@@ -1676,12 +1686,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1676
1686
|
console.log("");
|
|
1677
1687
|
for (let e of Fe(a, n)) console.log(e);
|
|
1678
1688
|
}
|
|
1679
|
-
},
|
|
1689
|
+
}, un = async (e) => {
|
|
1680
1690
|
let t = v(e, "src", "pages");
|
|
1681
1691
|
if (!S(t)) return !1;
|
|
1682
1692
|
let n = [];
|
|
1683
1693
|
try {
|
|
1684
|
-
let { walkPagesTree: e } = await import("./webDev-
|
|
1694
|
+
let { walkPagesTree: e } = await import("./webDev-BLKBO34K.js");
|
|
1685
1695
|
n = (await e(t)).pages.map((e) => e.pattern);
|
|
1686
1696
|
} catch {
|
|
1687
1697
|
return !1;
|
|
@@ -1691,10 +1701,10 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1691
1701
|
console.log("");
|
|
1692
1702
|
for (let e of Re(r)) console.log(e);
|
|
1693
1703
|
return r.problems.length > 0;
|
|
1694
|
-
},
|
|
1695
|
-
let { discovered: t } = await
|
|
1704
|
+
}, dn = async (e) => {
|
|
1705
|
+
let { discovered: t } = await V(e), n = [];
|
|
1696
1706
|
try {
|
|
1697
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
1707
|
+
let { loadApiConfig: t } = await import("./dev-D0E5x2Ue.js");
|
|
1698
1708
|
n = ((await t(e))?.plugins ?? []).map((e) => {
|
|
1699
1709
|
let t = (e.extendSchema?.tables ?? []).map((e) => {
|
|
1700
1710
|
let t = e;
|
|
@@ -1707,8 +1717,8 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1707
1717
|
plugin: e.name,
|
|
1708
1718
|
tables: t,
|
|
1709
1719
|
rpcTags: n,
|
|
1710
|
-
canDisableTables:
|
|
1711
|
-
canAlias:
|
|
1720
|
+
canDisableTables: fn.has(r),
|
|
1721
|
+
canAlias: pn.has(r)
|
|
1712
1722
|
};
|
|
1713
1723
|
});
|
|
1714
1724
|
} catch {
|
|
@@ -1720,18 +1730,18 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1720
1730
|
...t.mutations,
|
|
1721
1731
|
...t.actions,
|
|
1722
1732
|
...t.streams ?? []
|
|
1723
|
-
].map((e) => String(e.descriptor.name)), i =
|
|
1733
|
+
].map((e) => String(e.descriptor.name)), i = $e(n, {
|
|
1724
1734
|
tables: t.tables.map((e) => e.tableName),
|
|
1725
1735
|
rpcTags: r
|
|
1726
1736
|
});
|
|
1727
1737
|
console.log();
|
|
1728
|
-
for (let e of
|
|
1729
|
-
},
|
|
1738
|
+
for (let e of et(n, i)) console.log(e);
|
|
1739
|
+
}, fn = /* @__PURE__ */ new Set([
|
|
1730
1740
|
"@voltro/plugin-rbac",
|
|
1731
1741
|
"@voltro/plugin-audit",
|
|
1732
1742
|
"@voltro/plugin-notifications",
|
|
1733
1743
|
"aiFlows"
|
|
1734
|
-
]),
|
|
1744
|
+
]), pn = /* @__PURE__ */ new Set([
|
|
1735
1745
|
"aiFlows",
|
|
1736
1746
|
"@voltro/plugin-audit",
|
|
1737
1747
|
"@voltro/plugin-billing",
|
|
@@ -1748,7 +1758,7 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1748
1758
|
"@voltro/plugin-sso-saml",
|
|
1749
1759
|
"@voltro/plugin-storage",
|
|
1750
1760
|
"@voltro/plugin-versioning"
|
|
1751
|
-
]), Q = 20,
|
|
1761
|
+
]), Q = 20, mn = async (e) => {
|
|
1752
1762
|
let t = await $(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;
|
|
1753
1763
|
return {
|
|
1754
1764
|
executors: t.executorCount,
|
|
@@ -1762,14 +1772,14 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1762
1772
|
unchecked: t.result.unchecked
|
|
1763
1773
|
};
|
|
1764
1774
|
}, $ = async (e) => {
|
|
1765
|
-
let { discovered: t } = await
|
|
1775
|
+
let { discovered: t } = await V(e), n = /* @__PURE__ */ new Map(), r = (e) => {
|
|
1766
1776
|
if (!n.has(e)) try {
|
|
1767
1777
|
n.set(e, C(e, "utf8"));
|
|
1768
1778
|
} catch {
|
|
1769
1779
|
n.set(e, "");
|
|
1770
1780
|
}
|
|
1771
1781
|
return n.get(e);
|
|
1772
|
-
}, i =
|
|
1782
|
+
}, i = an(e, r), a = /* @__PURE__ */ new Set();
|
|
1773
1783
|
for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
|
|
1774
1784
|
let e = t.references?.();
|
|
1775
1785
|
e && a.add(e.tableName);
|
|
@@ -1801,12 +1811,12 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1801
1811
|
op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
|
|
1802
1812
|
...a?.table === void 0 ? {} : { table: a.table }
|
|
1803
1813
|
};
|
|
1804
|
-
})), s = v(e,
|
|
1805
|
-
for (let t of
|
|
1814
|
+
})), s = v(e, J), c = S(s) ? M(C(s, "utf8")) : void 0, l = !1;
|
|
1815
|
+
for (let t of I(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
|
|
1806
1816
|
l = !0;
|
|
1807
1817
|
break;
|
|
1808
1818
|
}
|
|
1809
|
-
let u =
|
|
1819
|
+
let u = Ke({
|
|
1810
1820
|
executors: o,
|
|
1811
1821
|
guardVocabulary: i,
|
|
1812
1822
|
tenantScopedTables: t.tenantScopedTables,
|
|
@@ -1823,46 +1833,46 @@ var le = /* @__PURE__ */ new Set([
|
|
|
1823
1833
|
executorCount: o.length,
|
|
1824
1834
|
allowPath: s
|
|
1825
1835
|
};
|
|
1826
|
-
},
|
|
1836
|
+
}, hn = async (e, t) => {
|
|
1827
1837
|
let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await $(e), l = n.counts;
|
|
1828
1838
|
console.log(`\nauthz scan · ${o} executor(s)`);
|
|
1829
|
-
let u = [...r].filter((e) => !
|
|
1839
|
+
let u = [...r].filter((e) => !ze.has(e)).sort();
|
|
1830
1840
|
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)}`);
|
|
1831
1841
|
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;
|
|
1832
|
-
if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${
|
|
1833
|
-
let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r =
|
|
1834
|
-
return oe(s,
|
|
1842
|
+
if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${J})`), 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) {
|
|
1843
|
+
let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Xe(e, t);
|
|
1844
|
+
return oe(s, Ye(e, t), "utf8"), console.log(`\n${J}: +${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;
|
|
1835
1845
|
}
|
|
1836
1846
|
if (n.unchecked.length === 0) return !1;
|
|
1837
1847
|
console.log("");
|
|
1838
1848
|
for (let e of n.unchecked.slice(0, Q)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
|
|
1839
|
-
return n.unchecked.length > Q && console.log(` … and ${n.unchecked.length - Q} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${
|
|
1840
|
-
},
|
|
1841
|
-
if (e.includes("--help") || e.includes("-h")) return console.log(
|
|
1849
|
+
return n.unchecked.length > Q && console.log(` … and ${n.unchecked.length - Q} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${J} 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 ${J}.`), 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);
|
|
1850
|
+
}, gn = async (e) => {
|
|
1851
|
+
if (e.includes("--help") || e.includes("-h")) return console.log(Gt), 0;
|
|
1842
1852
|
let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), o = b(n ?? process.cwd());
|
|
1843
1853
|
if (g(e)) return ee(), h({
|
|
1844
|
-
...
|
|
1845
|
-
spaCandidates: await
|
|
1846
|
-
pageConvention:
|
|
1854
|
+
...L(o),
|
|
1855
|
+
spaCandidates: await R(o),
|
|
1856
|
+
pageConvention: z(o),
|
|
1847
1857
|
fileTaxonomy: re(o).findings,
|
|
1848
|
-
junctionFks: (await
|
|
1849
|
-
eventDelivery: await
|
|
1858
|
+
junctionFks: (await H(o))?.issues ?? [],
|
|
1859
|
+
eventDelivery: await U(o),
|
|
1850
1860
|
duplicateInstances: i(o),
|
|
1851
1861
|
versionSkew: r(o) ?? null,
|
|
1852
|
-
serverOnly: (await
|
|
1853
|
-
accessDecisions: await
|
|
1854
|
-
webhookVerification: await
|
|
1855
|
-
authz: await
|
|
1862
|
+
serverOnly: (await G(o)).status,
|
|
1863
|
+
accessDecisions: await K(o),
|
|
1864
|
+
webhookVerification: await q(o),
|
|
1865
|
+
authz: await mn(o),
|
|
1856
1866
|
ungrantedScopes: await Z(o),
|
|
1857
1867
|
neverEmittedEvents: await X(o),
|
|
1858
|
-
stalledRuns: await
|
|
1868
|
+
stalledRuns: await tn(o)
|
|
1859
1869
|
}), 0;
|
|
1860
|
-
let c =
|
|
1861
|
-
|
|
1862
|
-
let l = await
|
|
1870
|
+
let c = st(o, t);
|
|
1871
|
+
Mt(o), jt(o), Wt(o), ne(o), await Rt(o), await rn(o), await nn(o), await qt(o), await Yt(o), await Xt(o), await dn(o), await cn(o), await on(o), await ln(o);
|
|
1872
|
+
let l = await en(o), u = await hn(o, e.includes("--write-authz-allowlist")), d = await Zt(o), f = await $t(o), p = await un(o), m = s(r(o));
|
|
1863
1873
|
m !== void 0 && (console.log(""), console.log(m));
|
|
1864
1874
|
let _ = a(i(o));
|
|
1865
1875
|
return _ !== void 0 && console.log(`\n${_}`), l || u || d || p || f ? Math.max(c, 1) : c;
|
|
1866
1876
|
};
|
|
1867
1877
|
//#endregion
|
|
1868
|
-
export {
|
|
1878
|
+
export { G as C, R as S, q as T, Rt as _, mn as a, Et as b, U as c, Tt as d, at as f, Wt as g, Mt as h, K as i, an as l, jt as m, ot as n, X as o, H as p, Nt as r, Y as s, Gt as t, L as u, gn as v, Z as w, z as x, st as y };
|