@launchsecure/launch-kit 0.0.17 → 0.0.18
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/dist/chart-client/assets/index--120d9P9.css +1 -0
- package/dist/chart-client/assets/index-D7x8nz-H.js +441 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-Bf8zdL3x.css +32 -0
- package/dist/client/assets/index-Ds9UP_cj.js +291 -0
- package/dist/client/index.html +2 -2
- package/dist/council-client/assets/index-CofZh7pS.css +1 -0
- package/dist/council-client/assets/index-Dc41S-R2.js +198 -0
- package/dist/council-client/index.html +21 -0
- package/dist/deck-client/assets/{_baseUniq-BbqvoK-V.js → _baseUniq-2gclQXo7.js} +1 -1
- package/dist/deck-client/assets/{arc-CMtYsIZt.js → arc-DcMY5Wm0.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-BEN5hESa.js → architectureDiagram-Q4EWVU46-B8iirmmJ.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BV4CZ6k8.js → blockDiagram-DXYQGD6D-B4JBLjmJ.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-fLcBXqdD.js → c4Diagram-AHTNJAMY-CojrJAk8.js} +1 -1
- package/dist/deck-client/assets/channel-ERh5jKXV.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-BO_19zwB.js → chunk-4BX2VUAB-Bmb_BMDo.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-iYegd5fu.js → chunk-4TB4RGXK-CumBy8qe.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-DM3QwYFL.js → chunk-55IACEB6-Ka8Hb1wD.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-DGznOul1.js → chunk-EDXVE4YY-B3sIPiQo.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-DsANJqYW.js → chunk-FMBD7UC4-C1tYkaqu.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-6PGH1F7d.js → chunk-OYMX7WX6-D7Wacbky.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-Dihf0Uq7.js → chunk-QZHKN3VN-ChXI0vO3.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-Cali2wW5.js → chunk-YZCP3GAM-BXhiqf8u.js} +1 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CMi1Gaev.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CMi1Gaev.js +1 -0
- package/dist/deck-client/assets/clone-DfWhlD4X.js +1 -0
- package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-DsRY4vbI.js → cose-bilkent-S5V4N54A-Bqp3p68D.js} +1 -1
- package/dist/deck-client/assets/cytoscape.esm-BQk4lpUV.js +331 -0
- package/dist/deck-client/assets/{dagre-KV5264BT-DJIKE_pI.js → dagre-KV5264BT-BS-rtyhZ.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD-Ckgli1SP.js → diagram-5BDNPKRD-BIrj9YGI.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-CozcDzae.js → diagram-G4DWMVQ6-noHWPIg4.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-xVSwW3f_.js → diagram-MMDJMWI5-C2qHxvqV.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-CJeZVY-P.js → diagram-TYMM5635-BytnGQr-.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-j4wjAERH.js → erDiagram-SMLLAGMA-BfK5m2YQ.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-CVLZ1efi.js → flowDiagram-DWJPFMVM-Cq925G1Z.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-CcIJ7pkP.js → ganttDiagram-T4ZO3ILL-DhhHPAmj.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-BZRhQX-a.js → gitGraphDiagram-UUTBAWPF-B3Lc0h9q.js} +1 -1
- package/dist/deck-client/assets/{graph-D0l25xfo.js → graph-RTawgVWm.js} +1 -1
- package/dist/deck-client/assets/index-765AIQ9z.css +1 -0
- package/dist/deck-client/assets/{index-BXcoHWVM.js → index-BfIfJXmS.js} +93 -93
- package/dist/deck-client/assets/{infoDiagram-42DDH7IO-BLwgxnYT.js → infoDiagram-42DDH7IO-BlR584kX.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BfOLoWv3.js → ishikawaDiagram-UXIWVN3A-DygKoNGY.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-CPuL6C9h.js → journeyDiagram-VCZTEJTY-BnaiYp9N.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-D3uf7_tx.js → kanban-definition-6JOO6SKY-BQBUBzJC.js} +1 -1
- package/dist/deck-client/assets/{layout-CzToiXdK.js → layout-DeZ8HI1T.js} +1 -1
- package/dist/deck-client/assets/{linear-BU36t460.js → linear-C6roLi_9.js} +1 -1
- package/dist/deck-client/assets/{min-DX_q-lqP.js → min-CbUksbuI.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-Ccty4O16.js → mindmap-definition-QFDTVHPH-iNxV62yN.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DVjsvH19.js → pieDiagram-DEJITSTG-DHVA0jaG.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-DtOXFVW9.js → quadrantDiagram-34T5L4WZ-DBeKKLUQ.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BbO_kKg6.js → requirementDiagram-MS252O5E-CBwITx7p.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-qbbj-CmC.js → sankeyDiagram-XADWPNL6-BtE-1YTU.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-JNKZAgfQ.js → sequenceDiagram-FGHM5R23-DN96yPP2.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-dtFalcNx.js → stateDiagram-FHFEXIEX-VUkKC2uJ.js} +1 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CA0IjulK.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-Dpp5nqSJ.js → timeline-definition-GMOUNBTQ-oUeZhRns.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-D8qEutX7.js → vennDiagram-DHZGUBPP-D87fK90n.js} +1 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-DYbYcpDp.js +162 -0
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-Bxl9X3CK.js → wardleyDiagram-NUSXRM2D-Ca_i0QRA.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-DYcvxLhi.js → xychartDiagram-5P7HB3ND-CUOJVIvq.js} +1 -1
- package/dist/deck-client/index.html +2 -2
- package/dist/server/chart-serve.js +258 -273
- package/dist/server/cli.js +305 -713
- package/dist/server/council-entry.js +1418 -0
- package/dist/server/council-serve.js +1039 -0
- package/dist/server/deck-server/deck-mcp-entry.js +0 -0
- package/dist/server/fb-wizard.js +0 -0
- package/dist/server/graph-mcp-entry.js +268 -701
- package/dist/server/server/cli.js +0 -0
- package/dist/server/server/fb-wizard.js +0 -0
- package/dist/server/server/graph-mcp-entry.js +0 -0
- package/package.json +17 -17
- package/dist/chart-client/assets/index-BUhuLBaw.js +0 -441
- package/dist/chart-client/assets/index-CWRZxjqR.css +0 -1
- package/dist/client/assets/index-CAAipH3V.js +0 -291
- package/dist/client/assets/index-DtbN793z.css +0 -32
- package/dist/deck-client/assets/channel-Nf-B3Qor.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-3i3-miMR.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-3i3-miMR.js +0 -1
- package/dist/deck-client/assets/clone-DXBuQlG8.js +0 -1
- package/dist/deck-client/assets/cytoscape.esm-BiciSPf8.js +0 -331
- package/dist/deck-client/assets/index-Cdh-f3-c.css +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CviYYulW.js +0 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-BwMqiNcL.js +0 -162
|
@@ -188,6 +188,56 @@ var init_resolve_paths = __esm({
|
|
|
188
188
|
}
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
+
// src/server/graph/core/walk.ts
|
|
192
|
+
function walk(dir, exts) {
|
|
193
|
+
const results = [];
|
|
194
|
+
if (!(0, import_node_fs4.existsSync)(dir)) return results;
|
|
195
|
+
for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
|
|
196
|
+
const full = (0, import_node_path4.join)(dir, entry.name);
|
|
197
|
+
if (entry.isDirectory()) {
|
|
198
|
+
results.push(...walk(full, exts));
|
|
199
|
+
} else if (exts.includes((0, import_node_path4.extname)(entry.name))) {
|
|
200
|
+
results.push(full);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return results;
|
|
204
|
+
}
|
|
205
|
+
function walkWithIgnore(dir, exts, opts = {}) {
|
|
206
|
+
const results = [];
|
|
207
|
+
if (!(0, import_node_fs4.existsSync)(dir)) return results;
|
|
208
|
+
const skip = opts.extraIgnore ? /* @__PURE__ */ new Set([...DEFAULT_IGNORE_DIRS, ...opts.extraIgnore]) : DEFAULT_IGNORE_DIRS;
|
|
209
|
+
for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
|
|
210
|
+
if (entry.isDirectory()) {
|
|
211
|
+
if (skip.has(entry.name)) continue;
|
|
212
|
+
results.push(...walkWithIgnore((0, import_node_path4.join)(dir, entry.name), exts, opts));
|
|
213
|
+
} else if (exts.includes((0, import_node_path4.extname)(entry.name))) {
|
|
214
|
+
results.push((0, import_node_path4.join)(dir, entry.name));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return results;
|
|
218
|
+
}
|
|
219
|
+
var import_node_fs4, import_node_path4, DEFAULT_IGNORE_DIRS;
|
|
220
|
+
var init_walk = __esm({
|
|
221
|
+
"src/server/graph/core/walk.ts"() {
|
|
222
|
+
"use strict";
|
|
223
|
+
import_node_fs4 = require("node:fs");
|
|
224
|
+
import_node_path4 = require("node:path");
|
|
225
|
+
DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
226
|
+
"node_modules",
|
|
227
|
+
".git",
|
|
228
|
+
".next",
|
|
229
|
+
".launchsecure",
|
|
230
|
+
".claude",
|
|
231
|
+
"dist",
|
|
232
|
+
"build",
|
|
233
|
+
"out",
|
|
234
|
+
".turbo",
|
|
235
|
+
".vercel",
|
|
236
|
+
"coverage"
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
191
241
|
// src/server/graph/core/ts-extractor.ts
|
|
192
242
|
var ts_extractor_exports = {};
|
|
193
243
|
__export(ts_extractor_exports, {
|
|
@@ -224,15 +274,15 @@ function getQuery(name) {
|
|
|
224
274
|
ensureInit();
|
|
225
275
|
const cached = queryCache.get(name);
|
|
226
276
|
if (cached) return cached;
|
|
227
|
-
const scmPath = (0,
|
|
228
|
-
const scm = (0,
|
|
277
|
+
const scmPath = (0, import_node_path5.join)(queriesDir, `${name}.scm`);
|
|
278
|
+
const scm = (0, import_node_fs5.readFileSync)(scmPath, "utf-8");
|
|
229
279
|
const query = tsxLanguage.query(scm);
|
|
230
280
|
queryCache.set(name, query);
|
|
231
281
|
return query;
|
|
232
282
|
}
|
|
233
283
|
function parseSource(absPath) {
|
|
234
284
|
ensureInit();
|
|
235
|
-
const content = (0,
|
|
285
|
+
const content = (0, import_node_fs5.readFileSync)(absPath, "utf-8");
|
|
236
286
|
return parserInstance.parse(content);
|
|
237
287
|
}
|
|
238
288
|
function parseCodeTS(code) {
|
|
@@ -697,17 +747,17 @@ function extractDeep(absPath) {
|
|
|
697
747
|
}
|
|
698
748
|
return { elements, stateVars, conditions, variables, responses, params };
|
|
699
749
|
}
|
|
700
|
-
var
|
|
750
|
+
var import_node_fs5, import_node_path5, tsxLanguage, parserInstance, initPromise, initialized, queriesDir, queryCache, PRISMA_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods;
|
|
701
751
|
var init_ts_extractor = __esm({
|
|
702
752
|
"src/server/graph/core/ts-extractor.ts"() {
|
|
703
753
|
"use strict";
|
|
704
|
-
|
|
705
|
-
|
|
754
|
+
import_node_fs5 = require("node:fs");
|
|
755
|
+
import_node_path5 = require("node:path");
|
|
706
756
|
initialized = false;
|
|
707
757
|
queriesDir = (() => {
|
|
708
|
-
const srcPath = (0,
|
|
758
|
+
const srcPath = (0, import_node_path5.join)((0, import_node_path5.dirname)(__filename), "..", "queries");
|
|
709
759
|
if (require("fs").existsSync(srcPath)) return srcPath;
|
|
710
|
-
return (0,
|
|
760
|
+
return (0, import_node_path5.join)((0, import_node_path5.dirname)(__filename), "graph", "queries");
|
|
711
761
|
})();
|
|
712
762
|
queryCache = /* @__PURE__ */ new Map();
|
|
713
763
|
PRISMA_MUTATION_METHODS_BUILTIN = [
|
|
@@ -728,48 +778,22 @@ var init_ts_extractor = __esm({
|
|
|
728
778
|
});
|
|
729
779
|
|
|
730
780
|
// src/server/graph/parsers/ts/typescript-project.ts
|
|
731
|
-
function walk(dir, exts) {
|
|
732
|
-
const results = [];
|
|
733
|
-
if (!(0, import_node_fs5.existsSync)(dir)) return results;
|
|
734
|
-
for (const entry of (0, import_node_fs5.readdirSync)(dir, { withFileTypes: true })) {
|
|
735
|
-
const full = (0, import_node_path5.join)(dir, entry.name);
|
|
736
|
-
if (entry.isDirectory()) {
|
|
737
|
-
results.push(...walk(full, exts));
|
|
738
|
-
} else if (exts.includes((0, import_node_path5.extname)(entry.name))) {
|
|
739
|
-
results.push(full);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
return results;
|
|
743
|
-
}
|
|
744
|
-
function walkWithIgnore(dir, exts, ignoreDirs) {
|
|
745
|
-
const results = [];
|
|
746
|
-
if (!(0, import_node_fs5.existsSync)(dir)) return results;
|
|
747
|
-
for (const entry of (0, import_node_fs5.readdirSync)(dir, { withFileTypes: true })) {
|
|
748
|
-
if (entry.isDirectory()) {
|
|
749
|
-
if (ignoreDirs.has(entry.name)) continue;
|
|
750
|
-
results.push(...walkWithIgnore((0, import_node_path5.join)(dir, entry.name), exts, ignoreDirs));
|
|
751
|
-
} else if (exts.includes((0, import_node_path5.extname)(entry.name))) {
|
|
752
|
-
results.push((0, import_node_path5.join)(dir, entry.name));
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return results;
|
|
756
|
-
}
|
|
757
781
|
function toNodeId(srcDir, absPath) {
|
|
758
|
-
return (0,
|
|
782
|
+
return (0, import_node_path6.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
759
783
|
}
|
|
760
784
|
function resolveImport(srcDir, specifier) {
|
|
761
785
|
if (!specifier.startsWith("@/")) return null;
|
|
762
786
|
const rel = specifier.slice(2);
|
|
763
|
-
const base = (0,
|
|
764
|
-
for (const c of [base, base + ".ts", base + ".tsx", (0,
|
|
765
|
-
if ((0,
|
|
787
|
+
const base = (0, import_node_path6.join)(srcDir, rel);
|
|
788
|
+
for (const c of [base, base + ".ts", base + ".tsx", (0, import_node_path6.join)(base, "index.ts"), (0, import_node_path6.join)(base, "index.tsx")]) {
|
|
789
|
+
if ((0, import_node_fs6.existsSync)(c) && (0, import_node_fs6.statSync)(c).isFile()) return c;
|
|
766
790
|
}
|
|
767
791
|
return null;
|
|
768
792
|
}
|
|
769
793
|
function resolveRelativeImport(fromFile, specifier) {
|
|
770
|
-
const base = (0,
|
|
771
|
-
for (const c of [base, base + ".ts", base + ".tsx", (0,
|
|
772
|
-
if ((0,
|
|
794
|
+
const base = (0, import_node_path6.join)((0, import_node_path6.dirname)(fromFile), specifier);
|
|
795
|
+
for (const c of [base, base + ".ts", base + ".tsx", (0, import_node_path6.join)(base, "index.ts"), (0, import_node_path6.join)(base, "index.tsx")]) {
|
|
796
|
+
if ((0, import_node_fs6.existsSync)(c) && (0, import_node_fs6.statSync)(c).isFile()) return c;
|
|
773
797
|
}
|
|
774
798
|
return null;
|
|
775
799
|
}
|
|
@@ -790,7 +814,7 @@ function resolveBarrelMap(barrelAbsPath, parsedByPath, memo, visiting) {
|
|
|
790
814
|
const resolved = resolveRelativeImport(barrelAbsPath, re.from);
|
|
791
815
|
if (!resolved) continue;
|
|
792
816
|
if (re.isWildcard) {
|
|
793
|
-
const targetBn = (0,
|
|
817
|
+
const targetBn = (0, import_node_path6.basename)(resolved);
|
|
794
818
|
const targetIsBarrel = targetBn === "index.ts" || targetBn === "index.tsx";
|
|
795
819
|
if (targetIsBarrel) {
|
|
796
820
|
const nested = resolveBarrelMap(resolved, parsedByPath, memo, visiting);
|
|
@@ -817,12 +841,12 @@ function buildAllBarrelMaps(srcDir, parsedByPath) {
|
|
|
817
841
|
const barrels = /* @__PURE__ */ new Map();
|
|
818
842
|
const memo = /* @__PURE__ */ new Map();
|
|
819
843
|
for (const [absPath, parsed] of parsedByPath) {
|
|
820
|
-
const bn = (0,
|
|
844
|
+
const bn = (0, import_node_path6.basename)(absPath);
|
|
821
845
|
if (bn !== "index.ts" && bn !== "index.tsx") continue;
|
|
822
846
|
if (parsed.reExports.length === 0) continue;
|
|
823
847
|
const map = resolveBarrelMap(absPath, parsedByPath, memo, /* @__PURE__ */ new Set());
|
|
824
848
|
if (map.size > 0) {
|
|
825
|
-
const barrelId = (0,
|
|
849
|
+
const barrelId = (0, import_node_path6.relative)(srcDir, (0, import_node_path6.dirname)(absPath)).replace(/\\/g, "/");
|
|
826
850
|
barrels.set(barrelId, map);
|
|
827
851
|
}
|
|
828
852
|
}
|
|
@@ -843,10 +867,10 @@ function extractRoute(id) {
|
|
|
843
867
|
return route || "/";
|
|
844
868
|
}
|
|
845
869
|
function nameFromFilename(absPath) {
|
|
846
|
-
return (0,
|
|
870
|
+
return (0, import_node_path6.basename)(absPath, (0, import_node_path6.extname)(absPath)).replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^(\w)/, (_, c) => c.toUpperCase());
|
|
847
871
|
}
|
|
848
872
|
function filePathToApiRoute(apiDir, absPath) {
|
|
849
|
-
let route = "/" + (0,
|
|
873
|
+
let route = "/" + (0, import_node_path6.relative)(apiDir, absPath).replace(/\\/g, "/").replace(/\/route\.tsx?$/, "");
|
|
850
874
|
route = route.replace(/\[([^\]]+)\]/g, ":$1");
|
|
851
875
|
route = route.replace(/\/+/g, "/");
|
|
852
876
|
if (route === "/") return "/api";
|
|
@@ -1030,12 +1054,9 @@ function extractEdges(srcDir, absPath, sourceId, parsed, nodeIdSet, barrelMaps,
|
|
|
1030
1054
|
}
|
|
1031
1055
|
return { edges, flagged };
|
|
1032
1056
|
}
|
|
1033
|
-
function hasNextConfig(rootDir) {
|
|
1034
|
-
return (0, import_node_fs5.existsSync)((0, import_node_path5.join)(rootDir, "next.config.ts")) || (0, import_node_fs5.existsSync)((0, import_node_path5.join)(rootDir, "next.config.js")) || (0, import_node_fs5.existsSync)((0, import_node_path5.join)(rootDir, "next.config.mjs"));
|
|
1035
|
-
}
|
|
1036
1057
|
function detect(rootDir) {
|
|
1037
1058
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
1038
|
-
return paths !== null
|
|
1059
|
+
return paths !== null;
|
|
1039
1060
|
}
|
|
1040
1061
|
function generate(rootDir) {
|
|
1041
1062
|
const config = loadConfig(rootDir);
|
|
@@ -1043,10 +1064,10 @@ function generate(rootDir) {
|
|
|
1043
1064
|
const srcDir = paths.srcDir;
|
|
1044
1065
|
const apiDir = paths.apiDir;
|
|
1045
1066
|
const appFiles = walk(paths.appDir, [".tsx", ".ts"]);
|
|
1046
|
-
const clientFiles = walk((0,
|
|
1047
|
-
const serverFiles = walk((0,
|
|
1048
|
-
const libFiles = walk((0,
|
|
1049
|
-
const configFiles = walk((0,
|
|
1067
|
+
const clientFiles = walk((0, import_node_path6.join)(srcDir, "client"), [".tsx", ".ts"]);
|
|
1068
|
+
const serverFiles = walk((0, import_node_path6.join)(srcDir, "server"), [".ts", ".tsx"]);
|
|
1069
|
+
const libFiles = walk((0, import_node_path6.join)(srcDir, "lib"), [".ts", ".tsx"]);
|
|
1070
|
+
const configFiles = walk((0, import_node_path6.join)(srcDir, "config"), [".ts", ".tsx"]);
|
|
1050
1071
|
const allDiscovered = [...appFiles, ...clientFiles, ...serverFiles, ...libFiles, ...configFiles];
|
|
1051
1072
|
const parsedByPath = /* @__PURE__ */ new Map();
|
|
1052
1073
|
for (const absPath of allDiscovered) {
|
|
@@ -1057,7 +1078,7 @@ function generate(rootDir) {
|
|
|
1057
1078
|
const apiNodes = [];
|
|
1058
1079
|
const nodeIdSet = /* @__PURE__ */ new Set();
|
|
1059
1080
|
const routeToNodeId = /* @__PURE__ */ new Map();
|
|
1060
|
-
const fileSet = allDiscovered.filter((f) => !(0,
|
|
1081
|
+
const fileSet = allDiscovered.filter((f) => !(0, import_node_path6.basename)(f).startsWith("index."));
|
|
1061
1082
|
for (const absPath of fileSet) {
|
|
1062
1083
|
const id = toNodeId(srcDir, absPath);
|
|
1063
1084
|
const type = classifyType(absPath, id);
|
|
@@ -1074,7 +1095,7 @@ function generate(rootDir) {
|
|
|
1074
1095
|
const dbCalls = extractDbCallsTS(absPath);
|
|
1075
1096
|
const authWrappers = extractAuthWrappersTS(absPath);
|
|
1076
1097
|
const deep = extractDeep(absPath);
|
|
1077
|
-
const routePath = (0,
|
|
1098
|
+
const routePath = (0, import_node_fs6.existsSync)(apiDir) ? filePathToApiRoute(apiDir, absPath) : `/api/${id.replace(/\/route\.tsx?$/, "")}`;
|
|
1078
1099
|
const mutations = dbCalls.filter((c) => c.isMutation);
|
|
1079
1100
|
const mutates = mutations.length > 0;
|
|
1080
1101
|
const authStrategy = [...authWrappers];
|
|
@@ -1151,20 +1172,7 @@ function generate(rootDir) {
|
|
|
1151
1172
|
});
|
|
1152
1173
|
}
|
|
1153
1174
|
const externalScanned = new Set(allDiscovered.map((f) => f.replace(/\\/g, "/")));
|
|
1154
|
-
const
|
|
1155
|
-
"node_modules",
|
|
1156
|
-
".next",
|
|
1157
|
-
"dist",
|
|
1158
|
-
".launchsecure",
|
|
1159
|
-
".git",
|
|
1160
|
-
"src",
|
|
1161
|
-
"coverage",
|
|
1162
|
-
".turbo",
|
|
1163
|
-
"build",
|
|
1164
|
-
"out",
|
|
1165
|
-
".vercel"
|
|
1166
|
-
]);
|
|
1167
|
-
const externalCandidates = walkWithIgnore(rootDir, [".ts", ".tsx"], IGNORE_DIRS2);
|
|
1175
|
+
const externalCandidates = walkWithIgnore(rootDir, [".ts", ".tsx"], { extraIgnore: /* @__PURE__ */ new Set(["src"]) });
|
|
1168
1176
|
for (const absPath of externalCandidates) {
|
|
1169
1177
|
const normalized = absPath.replace(/\\/g, "/");
|
|
1170
1178
|
if (externalScanned.has(normalized)) continue;
|
|
@@ -1174,7 +1182,7 @@ function generate(rootDir) {
|
|
|
1174
1182
|
} catch {
|
|
1175
1183
|
continue;
|
|
1176
1184
|
}
|
|
1177
|
-
const externalId = (0,
|
|
1185
|
+
const externalId = (0, import_node_path6.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
1178
1186
|
const edgesFromThis = [];
|
|
1179
1187
|
const seen = /* @__PURE__ */ new Set();
|
|
1180
1188
|
for (const imp of parsed.imports) {
|
|
@@ -1370,14 +1378,15 @@ function generate(rootDir) {
|
|
|
1370
1378
|
}
|
|
1371
1379
|
return result;
|
|
1372
1380
|
}
|
|
1373
|
-
var
|
|
1381
|
+
var import_node_fs6, import_node_path6, HTTP_METHODS, CLASSIFICATION_TO_LAYER, typescriptProjectParser;
|
|
1374
1382
|
var init_typescript_project = __esm({
|
|
1375
1383
|
"src/server/graph/parsers/ts/typescript-project.ts"() {
|
|
1376
1384
|
"use strict";
|
|
1377
|
-
|
|
1378
|
-
|
|
1385
|
+
import_node_fs6 = require("node:fs");
|
|
1386
|
+
import_node_path6 = require("node:path");
|
|
1379
1387
|
init_config();
|
|
1380
1388
|
init_resolve_paths();
|
|
1389
|
+
init_walk();
|
|
1381
1390
|
init_ts_extractor();
|
|
1382
1391
|
HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
|
|
1383
1392
|
CLASSIFICATION_TO_LAYER = {
|
|
@@ -1493,11 +1502,11 @@ function parseEnums(content) {
|
|
|
1493
1502
|
return nodes;
|
|
1494
1503
|
}
|
|
1495
1504
|
function detect2(rootDir) {
|
|
1496
|
-
return (0,
|
|
1505
|
+
return (0, import_node_fs7.existsSync)((0, import_node_path7.join)(rootDir, "prisma", "schema.prisma"));
|
|
1497
1506
|
}
|
|
1498
1507
|
function generate2(rootDir) {
|
|
1499
|
-
const schemaPath = (0,
|
|
1500
|
-
const content = (0,
|
|
1508
|
+
const schemaPath = (0, import_node_path7.join)(rootDir, "prisma", "schema.prisma");
|
|
1509
|
+
const content = (0, import_node_fs7.readFileSync)(schemaPath, "utf-8");
|
|
1501
1510
|
const { nodes: modelNodes, relations } = parseModels(content);
|
|
1502
1511
|
const enumNodes = parseEnums(content);
|
|
1503
1512
|
const allNodes = [...modelNodes, ...enumNodes];
|
|
@@ -1546,12 +1555,12 @@ function generate2(rootDir) {
|
|
|
1546
1555
|
}
|
|
1547
1556
|
};
|
|
1548
1557
|
}
|
|
1549
|
-
var
|
|
1558
|
+
var import_node_fs7, import_node_path7, prismaSchemaParser;
|
|
1550
1559
|
var init_prisma_schema = __esm({
|
|
1551
1560
|
"src/server/graph/parsers/db/prisma-schema.ts"() {
|
|
1552
1561
|
"use strict";
|
|
1553
|
-
|
|
1554
|
-
|
|
1562
|
+
import_node_fs7 = require("node:fs");
|
|
1563
|
+
import_node_path7 = require("node:path");
|
|
1555
1564
|
prismaSchemaParser = {
|
|
1556
1565
|
id: "prisma-schema",
|
|
1557
1566
|
layer: "db",
|
|
@@ -1685,19 +1694,19 @@ function parseUniqueIndex(sql, state) {
|
|
|
1685
1694
|
}
|
|
1686
1695
|
}
|
|
1687
1696
|
function parseMigrations(rootDir) {
|
|
1688
|
-
const migrationsDir = (0,
|
|
1697
|
+
const migrationsDir = (0, import_node_path8.join)(rootDir, "prisma", "migrations");
|
|
1689
1698
|
const state = {
|
|
1690
1699
|
tables: /* @__PURE__ */ new Map(),
|
|
1691
1700
|
enums: /* @__PURE__ */ new Map(),
|
|
1692
1701
|
fks: [],
|
|
1693
1702
|
uniqueIndexes: /* @__PURE__ */ new Map()
|
|
1694
1703
|
};
|
|
1695
|
-
if (!(0,
|
|
1696
|
-
const dirs = (0,
|
|
1704
|
+
if (!(0, import_node_fs8.existsSync)(migrationsDir)) return state;
|
|
1705
|
+
const dirs = (0, import_node_fs8.readdirSync)(migrationsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
1697
1706
|
for (const dir of dirs) {
|
|
1698
|
-
const sqlPath = (0,
|
|
1699
|
-
if (!(0,
|
|
1700
|
-
const sql = (0,
|
|
1707
|
+
const sqlPath = (0, import_node_path8.join)(migrationsDir, dir, "migration.sql");
|
|
1708
|
+
if (!(0, import_node_fs8.existsSync)(sqlPath)) continue;
|
|
1709
|
+
const sql = (0, import_node_fs8.readFileSync)(sqlPath, "utf-8");
|
|
1701
1710
|
parseCreateEnum(sql, state);
|
|
1702
1711
|
parseCreateTable(sql, state);
|
|
1703
1712
|
parseAlterTable(sql, state);
|
|
@@ -1708,9 +1717,9 @@ function parseMigrations(rootDir) {
|
|
|
1708
1717
|
return state;
|
|
1709
1718
|
}
|
|
1710
1719
|
function loadPrismaState(rootDir) {
|
|
1711
|
-
const schemaPath = (0,
|
|
1712
|
-
if (!(0,
|
|
1713
|
-
const content = (0,
|
|
1720
|
+
const schemaPath = (0, import_node_path8.join)(rootDir, "prisma", "schema.prisma");
|
|
1721
|
+
if (!(0, import_node_fs8.existsSync)(schemaPath)) return null;
|
|
1722
|
+
const content = (0, import_node_fs8.readFileSync)(schemaPath, "utf-8");
|
|
1714
1723
|
const tables = /* @__PURE__ */ new Map();
|
|
1715
1724
|
const enums = /* @__PURE__ */ new Map();
|
|
1716
1725
|
const relations = [];
|
|
@@ -1875,9 +1884,9 @@ function verify(sqlState, prisma) {
|
|
|
1875
1884
|
return { contradictions, flaggedEdges };
|
|
1876
1885
|
}
|
|
1877
1886
|
function detect3(rootDir) {
|
|
1878
|
-
const migrationsDir = (0,
|
|
1879
|
-
if (!(0,
|
|
1880
|
-
return (0,
|
|
1887
|
+
const migrationsDir = (0, import_node_path8.join)(rootDir, "prisma", "migrations");
|
|
1888
|
+
if (!(0, import_node_fs8.existsSync)(migrationsDir)) return false;
|
|
1889
|
+
return (0, import_node_fs8.readdirSync)(migrationsDir, { withFileTypes: true }).some((d) => d.isDirectory() && (0, import_node_fs8.existsSync)((0, import_node_path8.join)(migrationsDir, d.name, "migration.sql")));
|
|
1881
1890
|
}
|
|
1882
1891
|
function generate3(rootDir) {
|
|
1883
1892
|
const sqlState = parseMigrations(rootDir);
|
|
@@ -1944,12 +1953,12 @@ function generate3(rootDir) {
|
|
|
1944
1953
|
flagged_edges: flaggedEdges
|
|
1945
1954
|
};
|
|
1946
1955
|
}
|
|
1947
|
-
var
|
|
1956
|
+
var import_node_fs8, import_node_path8, PG_TO_PRISMA, sqlMigrationsParser;
|
|
1948
1957
|
var init_sql_migrations = __esm({
|
|
1949
1958
|
"src/server/graph/parsers/db/sql-migrations.ts"() {
|
|
1950
1959
|
"use strict";
|
|
1951
|
-
|
|
1952
|
-
|
|
1960
|
+
import_node_fs8 = require("node:fs");
|
|
1961
|
+
import_node_path8 = require("node:path");
|
|
1953
1962
|
PG_TO_PRISMA = {
|
|
1954
1963
|
"TEXT": "String",
|
|
1955
1964
|
"VARCHAR": "String",
|
|
@@ -2212,28 +2221,28 @@ var init_fetch_resolver = __esm({
|
|
|
2212
2221
|
|
|
2213
2222
|
// src/server/graph/parsers/crosslayer/api-annotations.ts
|
|
2214
2223
|
function walk2(dir, exts) {
|
|
2215
|
-
if (!(0,
|
|
2224
|
+
if (!(0, import_node_fs9.existsSync)(dir)) return [];
|
|
2216
2225
|
const results = [];
|
|
2217
|
-
for (const entry of (0,
|
|
2226
|
+
for (const entry of (0, import_node_fs9.readdirSync)(dir, { withFileTypes: true })) {
|
|
2218
2227
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2219
|
-
const full = (0,
|
|
2228
|
+
const full = (0, import_node_path9.join)(dir, entry.name);
|
|
2220
2229
|
if (entry.isDirectory()) {
|
|
2221
2230
|
results.push(...walk2(full, exts));
|
|
2222
|
-
} else if (exts.includes((0,
|
|
2231
|
+
} else if (exts.includes((0, import_node_path9.extname)(entry.name))) {
|
|
2223
2232
|
results.push(full);
|
|
2224
2233
|
}
|
|
2225
2234
|
}
|
|
2226
2235
|
return results;
|
|
2227
2236
|
}
|
|
2228
2237
|
function toNodeId2(srcDir, absPath) {
|
|
2229
|
-
return (0,
|
|
2238
|
+
return (0, import_node_path9.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2230
2239
|
}
|
|
2231
|
-
var
|
|
2240
|
+
var import_node_fs9, import_node_path9, API_ANNOTATION_RE, apiAnnotationsParser;
|
|
2232
2241
|
var init_api_annotations = __esm({
|
|
2233
2242
|
"src/server/graph/parsers/crosslayer/api-annotations.ts"() {
|
|
2234
2243
|
"use strict";
|
|
2235
|
-
|
|
2236
|
-
|
|
2244
|
+
import_node_fs9 = require("node:fs");
|
|
2245
|
+
import_node_path9 = require("node:path");
|
|
2237
2246
|
init_api_route_matching();
|
|
2238
2247
|
API_ANNOTATION_RE = /@api\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\/\S+)/g;
|
|
2239
2248
|
apiAnnotationsParser = {
|
|
@@ -2241,7 +2250,7 @@ var init_api_annotations = __esm({
|
|
|
2241
2250
|
layer: "crosslayer",
|
|
2242
2251
|
concern: "api-binding",
|
|
2243
2252
|
detect(rootDir) {
|
|
2244
|
-
return (0,
|
|
2253
|
+
return (0, import_node_fs9.existsSync)((0, import_node_path9.join)(rootDir, "src"));
|
|
2245
2254
|
},
|
|
2246
2255
|
generate(rootDir, layerOutputs) {
|
|
2247
2256
|
const apiOutput = layerOutputs.get("api");
|
|
@@ -2252,13 +2261,13 @@ var init_api_annotations = __esm({
|
|
|
2252
2261
|
const uiNodeIds = new Set(uiOutput?.nodes.map((n) => n.id) ?? []);
|
|
2253
2262
|
const apiRoutes = loadApiRoutesFromOutput(apiOutput);
|
|
2254
2263
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
2255
|
-
const srcDir = (0,
|
|
2264
|
+
const srcDir = (0, import_node_path9.join)(rootDir, "src");
|
|
2256
2265
|
const files = walk2(srcDir, [".ts", ".tsx"]);
|
|
2257
2266
|
const crossRefs = [];
|
|
2258
2267
|
const flaggedEdges = [];
|
|
2259
2268
|
const seen = /* @__PURE__ */ new Set();
|
|
2260
2269
|
for (const absPath of files) {
|
|
2261
|
-
const content = (0,
|
|
2270
|
+
const content = (0, import_node_fs9.readFileSync)(absPath, "utf-8");
|
|
2262
2271
|
const sourceId = toNodeId2(srcDir, absPath);
|
|
2263
2272
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
2264
2273
|
let match;
|
|
@@ -2305,28 +2314,28 @@ var init_api_annotations = __esm({
|
|
|
2305
2314
|
|
|
2306
2315
|
// src/server/graph/parsers/crosslayer/url-literal-scanner.ts
|
|
2307
2316
|
function walk3(dir, exts) {
|
|
2308
|
-
if (!(0,
|
|
2317
|
+
if (!(0, import_node_fs10.existsSync)(dir)) return [];
|
|
2309
2318
|
const results = [];
|
|
2310
|
-
for (const entry of (0,
|
|
2319
|
+
for (const entry of (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true })) {
|
|
2311
2320
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2312
|
-
const full = (0,
|
|
2321
|
+
const full = (0, import_node_path10.join)(dir, entry.name);
|
|
2313
2322
|
if (entry.isDirectory()) {
|
|
2314
2323
|
results.push(...walk3(full, exts));
|
|
2315
|
-
} else if (exts.includes((0,
|
|
2324
|
+
} else if (exts.includes((0, import_node_path10.extname)(entry.name))) {
|
|
2316
2325
|
results.push(full);
|
|
2317
2326
|
}
|
|
2318
2327
|
}
|
|
2319
2328
|
return results;
|
|
2320
2329
|
}
|
|
2321
2330
|
function toNodeId3(srcDir, absPath) {
|
|
2322
|
-
return (0,
|
|
2331
|
+
return (0, import_node_path10.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2323
2332
|
}
|
|
2324
|
-
var
|
|
2333
|
+
var import_node_fs10, import_node_path10, URL_LITERAL_RE, urlLiteralScannerParser;
|
|
2325
2334
|
var init_url_literal_scanner = __esm({
|
|
2326
2335
|
"src/server/graph/parsers/crosslayer/url-literal-scanner.ts"() {
|
|
2327
2336
|
"use strict";
|
|
2328
|
-
|
|
2329
|
-
|
|
2337
|
+
import_node_fs10 = require("node:fs");
|
|
2338
|
+
import_node_path10 = require("node:path");
|
|
2330
2339
|
init_api_route_matching();
|
|
2331
2340
|
init_config();
|
|
2332
2341
|
init_resolve_paths();
|
|
@@ -2350,7 +2359,7 @@ var init_url_literal_scanner = __esm({
|
|
|
2350
2359
|
const apiPathMap = buildApiPathMap(apiRoutes);
|
|
2351
2360
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
2352
2361
|
const srcDir = paths.srcDir;
|
|
2353
|
-
const clientDir = (0,
|
|
2362
|
+
const clientDir = (0, import_node_path10.join)(srcDir, "client");
|
|
2354
2363
|
const files = [
|
|
2355
2364
|
...walk3(clientDir, [".ts", ".tsx"]),
|
|
2356
2365
|
...walk3(paths.appDir, [".ts", ".tsx"])
|
|
@@ -2360,7 +2369,7 @@ var init_url_literal_scanner = __esm({
|
|
|
2360
2369
|
for (const absPath of files) {
|
|
2361
2370
|
const sourceId = toNodeId3(srcDir, absPath);
|
|
2362
2371
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
2363
|
-
const content = (0,
|
|
2372
|
+
const content = (0, import_node_fs10.readFileSync)(absPath, "utf-8");
|
|
2364
2373
|
let match;
|
|
2365
2374
|
URL_LITERAL_RE.lastIndex = 0;
|
|
2366
2375
|
while ((match = URL_LITERAL_RE.exec(content)) !== null) {
|
|
@@ -2422,19 +2431,19 @@ function extractEnumValues(rootDir) {
|
|
|
2422
2431
|
const nodes = [];
|
|
2423
2432
|
const edges = [];
|
|
2424
2433
|
const schemaPaths = [
|
|
2425
|
-
(0,
|
|
2426
|
-
(0,
|
|
2434
|
+
(0, import_node_path11.join)(rootDir, "prisma", "schema.prisma"),
|
|
2435
|
+
(0, import_node_path11.join)(rootDir, "prisma", "schema")
|
|
2427
2436
|
];
|
|
2428
2437
|
let content = "";
|
|
2429
2438
|
for (const p of schemaPaths) {
|
|
2430
|
-
if ((0,
|
|
2439
|
+
if ((0, import_node_fs11.existsSync)(p)) {
|
|
2431
2440
|
try {
|
|
2432
|
-
const stat = (0,
|
|
2441
|
+
const stat = (0, import_node_fs11.statSync)(p);
|
|
2433
2442
|
if (stat.isFile()) {
|
|
2434
|
-
content = (0,
|
|
2443
|
+
content = (0, import_node_fs11.readFileSync)(p, "utf-8");
|
|
2435
2444
|
} else if (stat.isDirectory()) {
|
|
2436
|
-
const files = (0,
|
|
2437
|
-
content = files.map((f) => (0,
|
|
2445
|
+
const files = (0, import_node_fs11.readdirSync)(p).filter((f) => f.endsWith(".prisma"));
|
|
2446
|
+
content = files.map((f) => (0, import_node_fs11.readFileSync)((0, import_node_path11.join)(p, f), "utf-8")).join("\n");
|
|
2438
2447
|
}
|
|
2439
2448
|
} catch {
|
|
2440
2449
|
continue;
|
|
@@ -2508,7 +2517,7 @@ function extractStringArrayFromNode(node) {
|
|
|
2508
2517
|
return values;
|
|
2509
2518
|
}
|
|
2510
2519
|
function findArrayDecl(root, varName) {
|
|
2511
|
-
function
|
|
2520
|
+
function walk4(node) {
|
|
2512
2521
|
if (node.type === "variable_declarator") {
|
|
2513
2522
|
const nameNode = node.childForFieldName("name");
|
|
2514
2523
|
const valueNode = node.childForFieldName("value");
|
|
@@ -2521,12 +2530,12 @@ function findArrayDecl(root, varName) {
|
|
|
2521
2530
|
}
|
|
2522
2531
|
}
|
|
2523
2532
|
for (const child of node.namedChildren) {
|
|
2524
|
-
const found =
|
|
2533
|
+
const found = walk4(child);
|
|
2525
2534
|
if (found) return found;
|
|
2526
2535
|
}
|
|
2527
2536
|
return null;
|
|
2528
2537
|
}
|
|
2529
|
-
return
|
|
2538
|
+
return walk4(root);
|
|
2530
2539
|
}
|
|
2531
2540
|
function extractObjectPropsRegex(objStr) {
|
|
2532
2541
|
const props = {};
|
|
@@ -2590,14 +2599,14 @@ function extractSeedData(rootDir) {
|
|
|
2590
2599
|
const nodes = [];
|
|
2591
2600
|
const edges = [];
|
|
2592
2601
|
const seedFiles = [
|
|
2593
|
-
(0,
|
|
2594
|
-
(0,
|
|
2595
|
-
(0,
|
|
2596
|
-
].filter(
|
|
2602
|
+
(0, import_node_path11.join)(rootDir, "prisma", "seed.ts"),
|
|
2603
|
+
(0, import_node_path11.join)(rootDir, "prisma", "seed.js"),
|
|
2604
|
+
(0, import_node_path11.join)(rootDir, "src", "server", "lib", "system-tags.ts")
|
|
2605
|
+
].filter(import_node_fs11.existsSync);
|
|
2597
2606
|
const useTreeSitter = tryLoadTreeSitter();
|
|
2598
2607
|
for (const filePath of seedFiles) {
|
|
2599
|
-
const content = (0,
|
|
2600
|
-
const relPath = (0,
|
|
2608
|
+
const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
|
|
2609
|
+
const relPath = (0, import_node_path11.relative)(rootDir, filePath);
|
|
2601
2610
|
const seeded = detectSeededArrays(content, relPath);
|
|
2602
2611
|
let astRoot = null;
|
|
2603
2612
|
if (useTreeSitter && parseCode) {
|
|
@@ -2691,11 +2700,11 @@ function extractSeedData(rootDir) {
|
|
|
2691
2700
|
return { nodes, edges };
|
|
2692
2701
|
}
|
|
2693
2702
|
function walkDir(dir, exts) {
|
|
2694
|
-
if (!(0,
|
|
2703
|
+
if (!(0, import_node_fs11.existsSync)(dir)) return [];
|
|
2695
2704
|
const results = [];
|
|
2696
|
-
for (const entry of (0,
|
|
2705
|
+
for (const entry of (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true })) {
|
|
2697
2706
|
if (entry.name === "node_modules" || entry.name === ".next" || entry.name === "dist") continue;
|
|
2698
|
-
const full = (0,
|
|
2707
|
+
const full = (0, import_node_path11.join)(dir, entry.name);
|
|
2699
2708
|
if (entry.isDirectory()) results.push(...walkDir(full, exts));
|
|
2700
2709
|
else if (exts.some((ext) => entry.name.endsWith(ext))) results.push(full);
|
|
2701
2710
|
}
|
|
@@ -2703,11 +2712,11 @@ function walkDir(dir, exts) {
|
|
|
2703
2712
|
}
|
|
2704
2713
|
function extractConstants(rootDir) {
|
|
2705
2714
|
const nodes = [];
|
|
2706
|
-
const srcDir = (0,
|
|
2707
|
-
if (!(0,
|
|
2715
|
+
const srcDir = (0, import_node_path11.join)(rootDir, "src");
|
|
2716
|
+
if (!(0, import_node_fs11.existsSync)(srcDir)) return { nodes };
|
|
2708
2717
|
for (const filePath of walkDir(srcDir, [".ts", ".tsx"])) {
|
|
2709
|
-
const content = (0,
|
|
2710
|
-
const relPath = (0,
|
|
2718
|
+
const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
|
|
2719
|
+
const relPath = (0, import_node_path11.relative)(rootDir, filePath);
|
|
2711
2720
|
const constArrayRe = /export\s+const\s+([A-Z][A-Z_0-9]+)\s*(?::[^=]+)?\s*=\s*\[/g;
|
|
2712
2721
|
let cm;
|
|
2713
2722
|
while ((cm = constArrayRe.exec(content)) !== null) {
|
|
@@ -2740,7 +2749,7 @@ function extractConstants(rootDir) {
|
|
|
2740
2749
|
return { nodes };
|
|
2741
2750
|
}
|
|
2742
2751
|
function detect4(rootDir) {
|
|
2743
|
-
return (0,
|
|
2752
|
+
return (0, import_node_fs11.existsSync)((0, import_node_path11.join)(rootDir, "prisma", "schema.prisma")) || (0, import_node_fs11.existsSync)((0, import_node_path11.join)(rootDir, "prisma", "seed.ts"));
|
|
2744
2753
|
}
|
|
2745
2754
|
function generate4(rootDir) {
|
|
2746
2755
|
const enumResult = extractEnumValues(rootDir);
|
|
@@ -2807,12 +2816,12 @@ function generate4(rootDir) {
|
|
|
2807
2816
|
}
|
|
2808
2817
|
};
|
|
2809
2818
|
}
|
|
2810
|
-
var
|
|
2819
|
+
var import_node_fs11, import_node_path11, parseCode, SHARED_MODELS, DB_MODELS, staticValuesParser;
|
|
2811
2820
|
var init_static_values = __esm({
|
|
2812
2821
|
"src/server/graph/parsers/static/static-values.ts"() {
|
|
2813
2822
|
"use strict";
|
|
2814
|
-
|
|
2815
|
-
|
|
2823
|
+
import_node_fs11 = require("node:fs");
|
|
2824
|
+
import_node_path11 = require("node:path");
|
|
2816
2825
|
parseCode = null;
|
|
2817
2826
|
SHARED_MODELS = /* @__PURE__ */ new Set(["permission", "role", "tag"]);
|
|
2818
2827
|
DB_MODELS = /* @__PURE__ */ new Set(["subscriptionPlan", "providerDefinition", "pipelineMasterTemplate"]);
|
|
@@ -2826,23 +2835,6 @@ var init_static_values = __esm({
|
|
|
2826
2835
|
});
|
|
2827
2836
|
|
|
2828
2837
|
// src/server/graph/parsers/crosslayer/static-ref-scanner.ts
|
|
2829
|
-
function walk4(dir, exts) {
|
|
2830
|
-
if (!(0, import_node_fs11.existsSync)(dir)) return [];
|
|
2831
|
-
const results = [];
|
|
2832
|
-
function recurse(d) {
|
|
2833
|
-
for (const entry of (0, import_node_fs11.readdirSync)(d, { withFileTypes: true })) {
|
|
2834
|
-
const full = (0, import_node_path11.join)(d, entry.name);
|
|
2835
|
-
if (entry.isDirectory()) {
|
|
2836
|
-
if (entry.name === "node_modules" || entry.name === ".next" || entry.name === "dist") continue;
|
|
2837
|
-
recurse(full);
|
|
2838
|
-
} else if (exts.some((ext) => entry.name.endsWith(ext))) {
|
|
2839
|
-
results.push(full);
|
|
2840
|
-
}
|
|
2841
|
-
}
|
|
2842
|
-
}
|
|
2843
|
-
recurse(dir);
|
|
2844
|
-
return results;
|
|
2845
|
-
}
|
|
2846
2838
|
function isInCommentOrType(node) {
|
|
2847
2839
|
let current = node.parent;
|
|
2848
2840
|
while (current) {
|
|
@@ -2927,14 +2919,15 @@ function collectStaticRefsRegex(content, valueLookup, allValues) {
|
|
|
2927
2919
|
}
|
|
2928
2920
|
return refs;
|
|
2929
2921
|
}
|
|
2930
|
-
var
|
|
2922
|
+
var import_node_fs12, import_node_path12, MIN_VALUE_LENGTH, SKIP_VALUES, staticRefScannerParser;
|
|
2931
2923
|
var init_static_ref_scanner = __esm({
|
|
2932
2924
|
"src/server/graph/parsers/crosslayer/static-ref-scanner.ts"() {
|
|
2933
2925
|
"use strict";
|
|
2934
|
-
|
|
2935
|
-
|
|
2926
|
+
import_node_fs12 = require("node:fs");
|
|
2927
|
+
import_node_path12 = require("node:path");
|
|
2936
2928
|
init_config();
|
|
2937
2929
|
init_resolve_paths();
|
|
2930
|
+
init_walk();
|
|
2938
2931
|
MIN_VALUE_LENGTH = 4;
|
|
2939
2932
|
SKIP_VALUES = /* @__PURE__ */ new Set([
|
|
2940
2933
|
"true",
|
|
@@ -2993,11 +2986,11 @@ var init_static_ref_scanner = __esm({
|
|
|
2993
2986
|
if (!paths) return { cross_refs: [], flagged_edges: [], warnings: [] };
|
|
2994
2987
|
const srcDir = paths.srcDir;
|
|
2995
2988
|
const files = [
|
|
2996
|
-
...
|
|
2997
|
-
...
|
|
2998
|
-
...
|
|
2999
|
-
...
|
|
3000
|
-
...
|
|
2989
|
+
...walkWithIgnore((0, import_node_path12.join)(srcDir, "client"), [".ts", ".tsx"]),
|
|
2990
|
+
...walkWithIgnore(paths.appDir, [".ts", ".tsx"]),
|
|
2991
|
+
...walkWithIgnore((0, import_node_path12.join)(srcDir, "server"), [".ts", ".tsx"]),
|
|
2992
|
+
...walkWithIgnore((0, import_node_path12.join)(srcDir, "lib"), [".ts", ".tsx"]),
|
|
2993
|
+
...walkWithIgnore((0, import_node_path12.join)(srcDir, "config"), [".ts", ".tsx"])
|
|
3001
2994
|
];
|
|
3002
2995
|
const uiOutput = layerOutputs.get("ui");
|
|
3003
2996
|
const apiOutput = layerOutputs.get("api");
|
|
@@ -3015,10 +3008,10 @@ var init_static_ref_scanner = __esm({
|
|
|
3015
3008
|
const seen = /* @__PURE__ */ new Set();
|
|
3016
3009
|
let filesScanned = 0;
|
|
3017
3010
|
for (const absPath of files) {
|
|
3018
|
-
const sourceId = (0,
|
|
3011
|
+
const sourceId = (0, import_node_path12.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
3019
3012
|
const sourceLayer = uiNodeIds.has(sourceId) ? "ui" : apiNodeIds.has(sourceId) ? "api" : null;
|
|
3020
3013
|
if (!sourceLayer) continue;
|
|
3021
|
-
const content = (0,
|
|
3014
|
+
const content = (0, import_node_fs12.readFileSync)(absPath, "utf-8");
|
|
3022
3015
|
filesScanned++;
|
|
3023
3016
|
let fileRefs;
|
|
3024
3017
|
if (parseCode2) {
|
|
@@ -3084,7 +3077,7 @@ function registerBuiltins(registry, disabled) {
|
|
|
3084
3077
|
function loadCustomParsers(registry, config, rootDir, disabled) {
|
|
3085
3078
|
for (const entry of config.parsers?.custom ?? []) {
|
|
3086
3079
|
try {
|
|
3087
|
-
const absPath = (0,
|
|
3080
|
+
const absPath = (0, import_node_path13.resolve)(rootDir, entry.path);
|
|
3088
3081
|
const mod = require(absPath);
|
|
3089
3082
|
const parser = "default" in mod ? mod.default : mod;
|
|
3090
3083
|
if (disabled.has(parser.id)) continue;
|
|
@@ -3111,11 +3104,11 @@ function createRegistry(config, rootDir) {
|
|
|
3111
3104
|
loadCustomParsers(registry, config, rootDir, disabled);
|
|
3112
3105
|
return registry;
|
|
3113
3106
|
}
|
|
3114
|
-
var
|
|
3107
|
+
var import_node_path13, ParserRegistry;
|
|
3115
3108
|
var init_parser_registry = __esm({
|
|
3116
3109
|
"src/server/graph/core/parser-registry.ts"() {
|
|
3117
3110
|
"use strict";
|
|
3118
|
-
|
|
3111
|
+
import_node_path13 = require("node:path");
|
|
3119
3112
|
init_typescript_project();
|
|
3120
3113
|
init_prisma_schema();
|
|
3121
3114
|
init_sql_migrations();
|
|
@@ -3286,10 +3279,10 @@ var init_merge = __esm({
|
|
|
3286
3279
|
|
|
3287
3280
|
// src/server/graph/core/graph-builder.ts
|
|
3288
3281
|
function readGraphFromDisk(rootDir, layer) {
|
|
3289
|
-
const filePath = (0,
|
|
3290
|
-
if (!(0,
|
|
3282
|
+
const filePath = (0, import_node_path14.join)(rootDir, ".launchsecure", "graphs", `${layer}.json`);
|
|
3283
|
+
if (!(0, import_node_fs13.existsSync)(filePath)) return null;
|
|
3291
3284
|
try {
|
|
3292
|
-
return JSON.parse((0,
|
|
3285
|
+
return JSON.parse((0, import_node_fs13.readFileSync)(filePath, "utf-8"));
|
|
3293
3286
|
} catch {
|
|
3294
3287
|
return null;
|
|
3295
3288
|
}
|
|
@@ -3385,12 +3378,12 @@ function generateAll(rootDir) {
|
|
|
3385
3378
|
const extras = [...byLayer.keys()].filter((l) => !wellKnownOrder.includes(l)).sort();
|
|
3386
3379
|
return [...wellKnownOrder, ...extras].map((l) => byLayer.get(l)).filter((r) => !!r);
|
|
3387
3380
|
}
|
|
3388
|
-
var
|
|
3381
|
+
var import_node_fs13, import_node_path14;
|
|
3389
3382
|
var init_graph_builder = __esm({
|
|
3390
3383
|
"src/server/graph/core/graph-builder.ts"() {
|
|
3391
3384
|
"use strict";
|
|
3392
|
-
|
|
3393
|
-
|
|
3385
|
+
import_node_fs13 = require("node:fs");
|
|
3386
|
+
import_node_path14 = require("node:path");
|
|
3394
3387
|
init_config();
|
|
3395
3388
|
init_parser_registry();
|
|
3396
3389
|
init_merge();
|
|
@@ -3429,18 +3422,18 @@ function detectConventionDirs(rootDir, extraConventionDirs = []) {
|
|
|
3429
3422
|
const conventionDirs = [...CONVENTION_DIRS_BUILTIN, ...extraConventionDirs];
|
|
3430
3423
|
const searchDirs = [
|
|
3431
3424
|
rootDir,
|
|
3432
|
-
(0,
|
|
3433
|
-
(0,
|
|
3434
|
-
(0,
|
|
3425
|
+
(0, import_node_path15.join)(rootDir, "src"),
|
|
3426
|
+
(0, import_node_path15.join)(rootDir, "app"),
|
|
3427
|
+
(0, import_node_path15.join)(rootDir, "lib")
|
|
3435
3428
|
];
|
|
3436
3429
|
for (const base of searchDirs) {
|
|
3437
3430
|
for (const convention of conventionDirs) {
|
|
3438
|
-
const dir = (0,
|
|
3439
|
-
if (!(0,
|
|
3431
|
+
const dir = (0, import_node_path15.join)(base, convention);
|
|
3432
|
+
if (!(0, import_node_fs14.existsSync)(dir)) continue;
|
|
3440
3433
|
try {
|
|
3441
|
-
const stat = (0,
|
|
3434
|
+
const stat = (0, import_node_fs14.statSync)(dir);
|
|
3442
3435
|
if (!stat.isDirectory()) continue;
|
|
3443
|
-
const entries = (0,
|
|
3436
|
+
const entries = (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
|
|
3444
3437
|
if (entries.length > 0) {
|
|
3445
3438
|
const relPath = dir.replace(rootDir + "/", "").replace(rootDir + "\\", "");
|
|
3446
3439
|
result.set(relPath, entries);
|
|
@@ -3512,12 +3505,12 @@ function extractModuleFromPath(id, extraTrivial, extraSkipSegments) {
|
|
|
3512
3505
|
}
|
|
3513
3506
|
return "root";
|
|
3514
3507
|
}
|
|
3515
|
-
var
|
|
3508
|
+
var import_node_fs14, import_node_path15, CONVENTION_DIRS_BUILTIN, GENERIC_ROLE_NAMES_BUILTIN, SKIP_SEGMENTS_BUILTIN, TRIVIAL_GROUPS, cachedRootDir, cachedConventionDirs, moduleTagger;
|
|
3516
3509
|
var init_module_tagger = __esm({
|
|
3517
3510
|
"src/server/graph/taggers/module-tagger.ts"() {
|
|
3518
3511
|
"use strict";
|
|
3519
|
-
|
|
3520
|
-
|
|
3512
|
+
import_node_fs14 = require("node:fs");
|
|
3513
|
+
import_node_path15 = require("node:path");
|
|
3521
3514
|
CONVENTION_DIRS_BUILTIN = ["features", "modules", "domains", "areas"];
|
|
3522
3515
|
GENERIC_ROLE_NAMES_BUILTIN = /* @__PURE__ */ new Set([
|
|
3523
3516
|
// JS/TS
|
|
@@ -3729,7 +3722,7 @@ function loadCustomTaggers(registry, config, rootDir, disabled) {
|
|
|
3729
3722
|
for (const entry of config.taggers?.custom ?? []) {
|
|
3730
3723
|
if (disabled.has(entry.id)) continue;
|
|
3731
3724
|
try {
|
|
3732
|
-
const absPath = (0,
|
|
3725
|
+
const absPath = (0, import_node_path16.resolve)(rootDir, entry.path);
|
|
3733
3726
|
const mod = require(absPath);
|
|
3734
3727
|
const tagger = "default" in mod ? mod.default : mod;
|
|
3735
3728
|
const override = config.taggers?.trackUntagged?.[tagger.id];
|
|
@@ -3750,11 +3743,11 @@ function createTaggerRegistry(config, rootDir) {
|
|
|
3750
3743
|
loadCustomTaggers(registry, config, rootDir, disabled);
|
|
3751
3744
|
return registry;
|
|
3752
3745
|
}
|
|
3753
|
-
var
|
|
3746
|
+
var import_node_path16, TaggerRegistry, BUILTIN_TAGGERS;
|
|
3754
3747
|
var init_tagger_registry = __esm({
|
|
3755
3748
|
"src/server/graph/core/tagger-registry.ts"() {
|
|
3756
3749
|
"use strict";
|
|
3757
|
-
|
|
3750
|
+
import_node_path16 = require("node:path");
|
|
3758
3751
|
init_module_tagger();
|
|
3759
3752
|
init_screen_tagger();
|
|
3760
3753
|
TaggerRegistry = class {
|
|
@@ -3782,18 +3775,18 @@ var init_tagger_registry = __esm({
|
|
|
3782
3775
|
|
|
3783
3776
|
// src/server/graph/core/tag-store.ts
|
|
3784
3777
|
function tagsFilePath(rootDir) {
|
|
3785
|
-
return (0,
|
|
3778
|
+
return (0, import_node_path17.join)(rootDir, GRAPHS_DIR, TAGS_FILENAME);
|
|
3786
3779
|
}
|
|
3787
3780
|
function readTagStore(rootDir) {
|
|
3788
3781
|
const filePath = tagsFilePath(rootDir);
|
|
3789
|
-
if (!(0,
|
|
3790
|
-
const stat = (0,
|
|
3782
|
+
if (!(0, import_node_fs15.existsSync)(filePath)) return {};
|
|
3783
|
+
const stat = (0, import_node_fs15.statSync)(filePath);
|
|
3791
3784
|
const cached = tagCache.get(filePath);
|
|
3792
3785
|
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
3793
3786
|
return cached.store;
|
|
3794
3787
|
}
|
|
3795
3788
|
try {
|
|
3796
|
-
const content = (0,
|
|
3789
|
+
const content = (0, import_node_fs15.readFileSync)(filePath, "utf-8");
|
|
3797
3790
|
const store = JSON.parse(content);
|
|
3798
3791
|
tagCache.set(filePath, { mtimeMs: stat.mtimeMs, store });
|
|
3799
3792
|
return store;
|
|
@@ -3803,15 +3796,15 @@ function readTagStore(rootDir) {
|
|
|
3803
3796
|
}
|
|
3804
3797
|
function writeTagStore(rootDir, store) {
|
|
3805
3798
|
const filePath = tagsFilePath(rootDir);
|
|
3806
|
-
const dir = (0,
|
|
3807
|
-
(0,
|
|
3799
|
+
const dir = (0, import_node_path17.dirname)(filePath);
|
|
3800
|
+
(0, import_node_fs15.mkdirSync)(dir, { recursive: true });
|
|
3808
3801
|
const cleaned = {};
|
|
3809
3802
|
for (const [nodeId, tags] of Object.entries(store)) {
|
|
3810
3803
|
if (Object.keys(tags).length > 0) {
|
|
3811
3804
|
cleaned[nodeId] = tags;
|
|
3812
3805
|
}
|
|
3813
3806
|
}
|
|
3814
|
-
(0,
|
|
3807
|
+
(0, import_node_fs15.writeFileSync)(filePath, JSON.stringify(cleaned, null, 2) + "\n", "utf-8");
|
|
3815
3808
|
tagCache.delete(filePath);
|
|
3816
3809
|
}
|
|
3817
3810
|
function setTag(rootDir, nodeId, key, value) {
|
|
@@ -3829,12 +3822,12 @@ function removeTag(rootDir, nodeId, key) {
|
|
|
3829
3822
|
}
|
|
3830
3823
|
writeTagStore(rootDir, store);
|
|
3831
3824
|
}
|
|
3832
|
-
var
|
|
3825
|
+
var import_node_fs15, import_node_path17, TAGS_FILENAME, GRAPHS_DIR, tagCache;
|
|
3833
3826
|
var init_tag_store = __esm({
|
|
3834
3827
|
"src/server/graph/core/tag-store.ts"() {
|
|
3835
3828
|
"use strict";
|
|
3836
|
-
|
|
3837
|
-
|
|
3829
|
+
import_node_fs15 = require("node:fs");
|
|
3830
|
+
import_node_path17 = require("node:path");
|
|
3838
3831
|
TAGS_FILENAME = "tags.json";
|
|
3839
3832
|
GRAPHS_DIR = ".launchsecure/graphs";
|
|
3840
3833
|
tagCache = /* @__PURE__ */ new Map();
|
|
@@ -3843,22 +3836,22 @@ var init_tag_store = __esm({
|
|
|
3843
3836
|
|
|
3844
3837
|
// src/server/graph/index.ts
|
|
3845
3838
|
function getAvailableLayers(rootDir) {
|
|
3846
|
-
const dir = (0,
|
|
3847
|
-
if (!(0,
|
|
3848
|
-
return (0,
|
|
3839
|
+
const dir = (0, import_node_path18.join)(rootDir, GRAPHS_DIR2);
|
|
3840
|
+
if (!(0, import_node_fs16.existsSync)(dir)) return [];
|
|
3841
|
+
return (0, import_node_fs16.readdirSync)(dir).filter((f) => f.endsWith(".json") && f !== "tags.json").map((f) => f.replace(".json", ""));
|
|
3849
3842
|
}
|
|
3850
3843
|
function graphsDir(rootDir) {
|
|
3851
|
-
return (0,
|
|
3844
|
+
return (0, import_node_path18.join)(rootDir, GRAPHS_DIR2);
|
|
3852
3845
|
}
|
|
3853
3846
|
function graphFilePath(rootDir, layer) {
|
|
3854
|
-
return (0,
|
|
3847
|
+
return (0, import_node_path18.join)(graphsDir(rootDir), `${layer}.json`);
|
|
3855
3848
|
}
|
|
3856
3849
|
function tagsFilePath2(rootDir) {
|
|
3857
|
-
return (0,
|
|
3850
|
+
return (0, import_node_path18.join)(graphsDir(rootDir), "tags.json");
|
|
3858
3851
|
}
|
|
3859
3852
|
function getMtimeMs(filePath) {
|
|
3860
|
-
if (!(0,
|
|
3861
|
-
return (0,
|
|
3853
|
+
if (!(0, import_node_fs16.existsSync)(filePath)) return 0;
|
|
3854
|
+
return (0, import_node_fs16.statSync)(filePath).mtimeMs;
|
|
3862
3855
|
}
|
|
3863
3856
|
function invalidateCache(filePath) {
|
|
3864
3857
|
graphCache.delete(filePath);
|
|
@@ -3897,20 +3890,20 @@ function applyTags(graph, layer, rootDir) {
|
|
|
3897
3890
|
}
|
|
3898
3891
|
function readGraphRaw(rootDir, layer) {
|
|
3899
3892
|
const filePath = graphFilePath(rootDir, layer);
|
|
3900
|
-
if (!(0,
|
|
3901
|
-
const stat = (0,
|
|
3893
|
+
if (!(0, import_node_fs16.existsSync)(filePath)) return null;
|
|
3894
|
+
const stat = (0, import_node_fs16.statSync)(filePath);
|
|
3902
3895
|
const cached = graphCache.get(filePath);
|
|
3903
3896
|
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
3904
3897
|
return cached.graph;
|
|
3905
3898
|
}
|
|
3906
|
-
const content = (0,
|
|
3899
|
+
const content = (0, import_node_fs16.readFileSync)(filePath, "utf-8");
|
|
3907
3900
|
const graph = JSON.parse(content);
|
|
3908
3901
|
graphCache.set(filePath, { mtimeMs: stat.mtimeMs, graph });
|
|
3909
3902
|
return graph;
|
|
3910
3903
|
}
|
|
3911
3904
|
function readGraph(rootDir, layer) {
|
|
3912
3905
|
const rawFilePath = graphFilePath(rootDir, layer);
|
|
3913
|
-
if (!(0,
|
|
3906
|
+
if (!(0, import_node_fs16.existsSync)(rawFilePath)) return null;
|
|
3914
3907
|
const rawMtime = getMtimeMs(rawFilePath);
|
|
3915
3908
|
const tagsMtime = getMtimeMs(tagsFilePath2(rootDir));
|
|
3916
3909
|
const cacheKey = `${rootDir}:${layer}`;
|
|
@@ -3940,22 +3933,22 @@ async function generateGraph(rootDir, layer) {
|
|
|
3940
3933
|
mutationMethods: config.parsers?.patterns?.mutationMethods
|
|
3941
3934
|
});
|
|
3942
3935
|
const dir = graphsDir(rootDir);
|
|
3943
|
-
(0,
|
|
3936
|
+
(0, import_node_fs16.mkdirSync)(dir, { recursive: true });
|
|
3944
3937
|
const results = layer ? [generateLayer(rootDir, layer)].filter((r) => r !== null) : generateAll(rootDir);
|
|
3945
3938
|
for (const result of results) {
|
|
3946
3939
|
const filePath = graphFilePath(rootDir, result.layer);
|
|
3947
|
-
(0,
|
|
3940
|
+
(0, import_node_fs16.writeFileSync)(filePath, JSON.stringify(result.output, null, 2) + "\n", "utf-8");
|
|
3948
3941
|
invalidateCache(filePath);
|
|
3949
3942
|
invalidateTaggedCache(rootDir, result.layer);
|
|
3950
3943
|
}
|
|
3951
3944
|
return results;
|
|
3952
3945
|
}
|
|
3953
|
-
var
|
|
3946
|
+
var import_node_fs16, import_node_path18, GRAPHS_DIR2, graphCache, taggedCache;
|
|
3954
3947
|
var init_graph = __esm({
|
|
3955
3948
|
"src/server/graph/index.ts"() {
|
|
3956
3949
|
"use strict";
|
|
3957
|
-
|
|
3958
|
-
|
|
3950
|
+
import_node_fs16 = require("node:fs");
|
|
3951
|
+
import_node_path18 = require("node:path");
|
|
3959
3952
|
init_graph_builder();
|
|
3960
3953
|
init_config();
|
|
3961
3954
|
init_tagger_registry();
|
|
@@ -3970,10 +3963,10 @@ var init_graph = __esm({
|
|
|
3970
3963
|
|
|
3971
3964
|
// src/server/graph/core/audit-core.ts
|
|
3972
3965
|
function readGraphFile(rootDir, layer) {
|
|
3973
|
-
const filePath = (0,
|
|
3974
|
-
if (!(0,
|
|
3966
|
+
const filePath = (0, import_node_path19.join)(rootDir, ".launchsecure", "graphs", `${layer}.json`);
|
|
3967
|
+
if (!(0, import_node_fs17.existsSync)(filePath)) return null;
|
|
3975
3968
|
try {
|
|
3976
|
-
return JSON.parse((0,
|
|
3969
|
+
return JSON.parse((0, import_node_fs17.readFileSync)(filePath, "utf-8"));
|
|
3977
3970
|
} catch {
|
|
3978
3971
|
return null;
|
|
3979
3972
|
}
|
|
@@ -4017,10 +4010,10 @@ function checkUnprotectedRoutes(rootDir) {
|
|
|
4017
4010
|
const api = readGraphFile(rootDir, "api");
|
|
4018
4011
|
const staticGraph = readGraphFile(rootDir, "static");
|
|
4019
4012
|
if (!api) return buildReport("api", "unprotected_routes", findings);
|
|
4020
|
-
const routePermsPath = (0,
|
|
4013
|
+
const routePermsPath = (0, import_node_path19.join)(rootDir, "src", "config", "route-permissions.ts");
|
|
4021
4014
|
let routePermsContent = "";
|
|
4022
|
-
if ((0,
|
|
4023
|
-
routePermsContent = (0,
|
|
4015
|
+
if ((0, import_node_fs17.existsSync)(routePermsPath)) {
|
|
4016
|
+
routePermsContent = (0, import_node_fs17.readFileSync)(routePermsPath, "utf-8");
|
|
4024
4017
|
}
|
|
4025
4018
|
const registeredRoutes = /* @__PURE__ */ new Set();
|
|
4026
4019
|
const routeEntryRe = /path:\s*'([^']+)'/g;
|
|
@@ -4097,10 +4090,10 @@ function checkUnenforcedPermissions(rootDir) {
|
|
|
4097
4090
|
const staticGraph = readGraphFile(rootDir, "static");
|
|
4098
4091
|
if (!staticGraph) return buildReport("static", "unenforced_permissions", findings);
|
|
4099
4092
|
const permissions = staticGraph.nodes.filter((n) => n.type === "seed_permission").map((n) => ({ id: n.id, key: n.value, name: n.name }));
|
|
4100
|
-
const routePermsPath = (0,
|
|
4093
|
+
const routePermsPath = (0, import_node_path19.join)(rootDir, "src", "config", "route-permissions.ts");
|
|
4101
4094
|
let routePermsContent = "";
|
|
4102
|
-
if ((0,
|
|
4103
|
-
routePermsContent = (0,
|
|
4095
|
+
if ((0, import_node_fs17.existsSync)(routePermsPath)) {
|
|
4096
|
+
routePermsContent = (0, import_node_fs17.readFileSync)(routePermsPath, "utf-8");
|
|
4104
4097
|
}
|
|
4105
4098
|
for (const perm of permissions) {
|
|
4106
4099
|
const regex = new RegExp(`permission:\\s*['"]${perm.key}['"]`);
|
|
@@ -4130,9 +4123,9 @@ function checkHardcodedValues(rootDir) {
|
|
|
4130
4123
|
const seen = /* @__PURE__ */ new Set();
|
|
4131
4124
|
for (const node of api.nodes) {
|
|
4132
4125
|
if (node.type !== "endpoint") continue;
|
|
4133
|
-
const filePath = (0,
|
|
4134
|
-
if (!(0,
|
|
4135
|
-
const content = (0,
|
|
4126
|
+
const filePath = (0, import_node_path19.join)(rootDir, "src", node.id);
|
|
4127
|
+
if (!(0, import_node_fs17.existsSync)(filePath)) continue;
|
|
4128
|
+
const content = (0, import_node_fs17.readFileSync)(filePath, "utf-8");
|
|
4136
4129
|
let m;
|
|
4137
4130
|
allCapsRe.lastIndex = 0;
|
|
4138
4131
|
while ((m = allCapsRe.exec(content)) !== null) {
|
|
@@ -4226,12 +4219,12 @@ function formatAsPrompt(reports) {
|
|
|
4226
4219
|
if (lines.length === 0) return "No audit findings.";
|
|
4227
4220
|
return lines.join("\n");
|
|
4228
4221
|
}
|
|
4229
|
-
var
|
|
4222
|
+
var import_node_fs17, import_node_path19, CHECKS;
|
|
4230
4223
|
var init_audit_core = __esm({
|
|
4231
4224
|
"src/server/graph/core/audit-core.ts"() {
|
|
4232
4225
|
"use strict";
|
|
4233
|
-
|
|
4234
|
-
|
|
4226
|
+
import_node_fs17 = require("node:fs");
|
|
4227
|
+
import_node_path19 = require("node:path");
|
|
4235
4228
|
CHECKS = {
|
|
4236
4229
|
db: {
|
|
4237
4230
|
schema_drift: checkSchemaDrift,
|
|
@@ -4263,16 +4256,16 @@ function randomPort() {
|
|
|
4263
4256
|
function findProjectRoot(startDir) {
|
|
4264
4257
|
let dir = startDir;
|
|
4265
4258
|
for (let i = 0; i < 8; i++) {
|
|
4266
|
-
const graphsDir2 =
|
|
4267
|
-
if (
|
|
4268
|
-
const parent =
|
|
4259
|
+
const graphsDir2 = import_node_path20.default.join(dir, ".launchsecure", "graphs");
|
|
4260
|
+
if (import_node_fs18.default.existsSync(import_node_path20.default.join(graphsDir2, "ui.json")) || import_node_fs18.default.existsSync(import_node_path20.default.join(graphsDir2, "api.json")) || import_node_fs18.default.existsSync(import_node_path20.default.join(graphsDir2, "db.json"))) return dir;
|
|
4261
|
+
const parent = import_node_path20.default.dirname(dir);
|
|
4269
4262
|
if (parent === dir) break;
|
|
4270
4263
|
dir = parent;
|
|
4271
4264
|
}
|
|
4272
4265
|
dir = startDir;
|
|
4273
4266
|
for (let i = 0; i < 8; i++) {
|
|
4274
|
-
if (
|
|
4275
|
-
const parent =
|
|
4267
|
+
if (import_node_fs18.default.existsSync(import_node_path20.default.join(dir, ".git"))) return dir;
|
|
4268
|
+
const parent = import_node_path20.default.dirname(dir);
|
|
4276
4269
|
if (parent === dir) break;
|
|
4277
4270
|
dir = parent;
|
|
4278
4271
|
}
|
|
@@ -4281,7 +4274,7 @@ function findProjectRoot(startDir) {
|
|
|
4281
4274
|
function resolveRequestRoot(url, monorepoRoot, projects) {
|
|
4282
4275
|
const projectParam = url.searchParams.get("project");
|
|
4283
4276
|
if (!projectParam || projects.length === 0) return monorepoRoot;
|
|
4284
|
-
const resolved =
|
|
4277
|
+
const resolved = import_node_path20.default.resolve(monorepoRoot, projectParam);
|
|
4285
4278
|
if (!resolved.startsWith(monorepoRoot)) {
|
|
4286
4279
|
throw new Error("Project path outside monorepo root");
|
|
4287
4280
|
}
|
|
@@ -4332,16 +4325,16 @@ async function buildMergedGraph(root) {
|
|
|
4332
4325
|
};
|
|
4333
4326
|
}
|
|
4334
4327
|
function serveStatic(res, filePath) {
|
|
4335
|
-
if (!
|
|
4336
|
-
const ext =
|
|
4328
|
+
if (!import_node_fs18.default.existsSync(filePath) || !import_node_fs18.default.statSync(filePath).isFile()) return false;
|
|
4329
|
+
const ext = import_node_path20.default.extname(filePath).toLowerCase();
|
|
4337
4330
|
const mime = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
4338
4331
|
res.writeHead(200, { "Content-Type": mime, "Cache-Control": "no-cache" });
|
|
4339
|
-
|
|
4332
|
+
import_node_fs18.default.createReadStream(filePath).pipe(res);
|
|
4340
4333
|
return true;
|
|
4341
4334
|
}
|
|
4342
4335
|
function serveIndex(res, clientDir) {
|
|
4343
|
-
const indexPath =
|
|
4344
|
-
if (!
|
|
4336
|
+
const indexPath = import_node_path20.default.join(clientDir, "index.html");
|
|
4337
|
+
if (!import_node_fs18.default.existsSync(indexPath)) {
|
|
4345
4338
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
4346
4339
|
res.end(`LaunchChart client bundle not found at ${clientDir}. Run 'npm run build:chart-client'.`);
|
|
4347
4340
|
return;
|
|
@@ -4393,7 +4386,7 @@ async function startChartServer(opts = {}) {
|
|
|
4393
4386
|
}
|
|
4394
4387
|
return { port: existing.port, url: existing.url };
|
|
4395
4388
|
}
|
|
4396
|
-
const clientDir = opts.clientDir ??
|
|
4389
|
+
const clientDir = opts.clientDir ?? import_node_path20.default.join(__dirname, "..", "chart-client");
|
|
4397
4390
|
const rootConfig = loadConfig(projectRoot);
|
|
4398
4391
|
const projects = rootConfig.projects ?? [];
|
|
4399
4392
|
const server = import_node_http.default.createServer((req, res) => {
|
|
@@ -4409,11 +4402,11 @@ async function startChartServer(opts = {}) {
|
|
|
4409
4402
|
}
|
|
4410
4403
|
if (req.method === "GET" && url2.pathname === "/api/projects") {
|
|
4411
4404
|
const projectList = projects.length > 0 ? projects.map((p) => {
|
|
4412
|
-
const absRoot =
|
|
4413
|
-
const hasGraphs =
|
|
4414
|
-
const
|
|
4415
|
-
return { name: p.name, root: p.root, hasGraphs, hasNextConfig
|
|
4416
|
-
}) : [{ name:
|
|
4405
|
+
const absRoot = import_node_path20.default.resolve(projectRoot, p.root);
|
|
4406
|
+
const hasGraphs = import_node_fs18.default.existsSync(import_node_path20.default.join(absRoot, ".launchsecure", "graphs"));
|
|
4407
|
+
const hasNextConfig = import_node_fs18.default.existsSync(import_node_path20.default.join(absRoot, "next.config.ts")) || import_node_fs18.default.existsSync(import_node_path20.default.join(absRoot, "next.config.js")) || import_node_fs18.default.existsSync(import_node_path20.default.join(absRoot, "next.config.mjs"));
|
|
4408
|
+
return { name: p.name, root: p.root, hasGraphs, hasNextConfig };
|
|
4409
|
+
}) : [{ name: import_node_path20.default.basename(projectRoot), root: ".", hasGraphs: import_node_fs18.default.existsSync(import_node_path20.default.join(projectRoot, ".launchsecure", "graphs")), hasNextConfig: true }];
|
|
4417
4410
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4418
4411
|
res.end(JSON.stringify({ projects: projectList, monorepoRoot: projectRoot }));
|
|
4419
4412
|
return;
|
|
@@ -4459,20 +4452,20 @@ async function startChartServer(opts = {}) {
|
|
|
4459
4452
|
}
|
|
4460
4453
|
if (req.method === "GET" && url2.pathname === "/api/file-content") {
|
|
4461
4454
|
const relPath = url2.searchParams.get("path");
|
|
4462
|
-
if (!relPath || relPath.includes("..") ||
|
|
4455
|
+
if (!relPath || relPath.includes("..") || import_node_path20.default.isAbsolute(relPath)) {
|
|
4463
4456
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4464
4457
|
res.end(JSON.stringify({ error: "Invalid path" }));
|
|
4465
4458
|
return;
|
|
4466
4459
|
}
|
|
4467
|
-
const filePath =
|
|
4468
|
-
if (!filePath.startsWith(reqRoot) || !
|
|
4460
|
+
const filePath = import_node_path20.default.join(reqRoot, relPath);
|
|
4461
|
+
if (!filePath.startsWith(reqRoot) || !import_node_fs18.default.existsSync(filePath) || !import_node_fs18.default.statSync(filePath).isFile()) {
|
|
4469
4462
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
4470
4463
|
res.end(JSON.stringify({ error: "File not found" }));
|
|
4471
4464
|
return;
|
|
4472
4465
|
}
|
|
4473
|
-
const ext =
|
|
4466
|
+
const ext = import_node_path20.default.extname(filePath).toLowerCase();
|
|
4474
4467
|
const langMap = { ".ts": "typescript", ".tsx": "tsx", ".js": "javascript", ".jsx": "jsx", ".prisma": "prisma", ".json": "json", ".css": "css" };
|
|
4475
|
-
const content =
|
|
4468
|
+
const content = import_node_fs18.default.readFileSync(filePath, "utf-8");
|
|
4476
4469
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4477
4470
|
res.end(JSON.stringify({ content, language: langMap[ext] ?? "text", path: relPath }));
|
|
4478
4471
|
return;
|
|
@@ -4514,8 +4507,8 @@ async function startChartServer(opts = {}) {
|
|
|
4514
4507
|
req.on("end", () => {
|
|
4515
4508
|
try {
|
|
4516
4509
|
const newConfig = JSON.parse(body);
|
|
4517
|
-
const configPath =
|
|
4518
|
-
|
|
4510
|
+
const configPath = import_node_path20.default.join(reqRoot, ".launchchart.json");
|
|
4511
|
+
import_node_fs18.default.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
|
|
4519
4512
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4520
4513
|
res.end(JSON.stringify({ ok: true }));
|
|
4521
4514
|
} catch (err2) {
|
|
@@ -4548,8 +4541,8 @@ async function startChartServer(opts = {}) {
|
|
|
4548
4541
|
const taggerConfig = JSON.parse(body);
|
|
4549
4542
|
const config2 = loadConfig(reqRoot);
|
|
4550
4543
|
config2.taggers = taggerConfig;
|
|
4551
|
-
const configPath =
|
|
4552
|
-
|
|
4544
|
+
const configPath = import_node_path20.default.join(reqRoot, ".launchchart.json");
|
|
4545
|
+
import_node_fs18.default.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
4553
4546
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4554
4547
|
res.end(JSON.stringify({ ok: true }));
|
|
4555
4548
|
} catch (err2) {
|
|
@@ -4622,10 +4615,10 @@ async function startChartServer(opts = {}) {
|
|
|
4622
4615
|
res.end(JSON.stringify({
|
|
4623
4616
|
projectRoot: reqRoot,
|
|
4624
4617
|
detected: paths ? {
|
|
4625
|
-
srcDir:
|
|
4626
|
-
appDir:
|
|
4627
|
-
apiDir:
|
|
4628
|
-
dbDir: paths.dbDir ?
|
|
4618
|
+
srcDir: import_node_path20.default.relative(reqRoot, paths.srcDir) || ".",
|
|
4619
|
+
appDir: import_node_path20.default.relative(reqRoot, paths.appDir),
|
|
4620
|
+
apiDir: import_node_path20.default.relative(reqRoot, paths.apiDir),
|
|
4621
|
+
dbDir: paths.dbDir ? import_node_path20.default.relative(reqRoot, paths.dbDir) : null
|
|
4629
4622
|
} : null,
|
|
4630
4623
|
overrides,
|
|
4631
4624
|
isOverride: overrides.appDir
|
|
@@ -4634,19 +4627,19 @@ async function startChartServer(opts = {}) {
|
|
|
4634
4627
|
}
|
|
4635
4628
|
if (req.method === "GET" && url2.pathname === "/api/browse-dir") {
|
|
4636
4629
|
const browsePath = url2.searchParams.get("path") || projectRoot;
|
|
4637
|
-
const abs =
|
|
4638
|
-
const twoUp =
|
|
4630
|
+
const abs = import_node_path20.default.resolve(browsePath);
|
|
4631
|
+
const twoUp = import_node_path20.default.resolve(projectRoot, "..", "..");
|
|
4639
4632
|
if (!abs.startsWith(twoUp)) {
|
|
4640
4633
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
4641
4634
|
res.end(JSON.stringify({ ok: false, error: "Path outside allowed range" }));
|
|
4642
4635
|
return;
|
|
4643
4636
|
}
|
|
4644
4637
|
try {
|
|
4645
|
-
const entries =
|
|
4638
|
+
const entries = import_node_fs18.default.readdirSync(abs, { withFileTypes: true });
|
|
4646
4639
|
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules" && e.name !== "dist" && e.name !== ".next").map((e) => e.name).sort();
|
|
4647
|
-
const parent = abs !== twoUp ?
|
|
4640
|
+
const parent = abs !== twoUp ? import_node_path20.default.dirname(abs) : null;
|
|
4648
4641
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4649
|
-
res.end(JSON.stringify({ current: abs, parent, dirs, relative:
|
|
4642
|
+
res.end(JSON.stringify({ current: abs, parent, dirs, relative: import_node_path20.default.relative(projectRoot, abs) || "." }));
|
|
4650
4643
|
} catch (err2) {
|
|
4651
4644
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4652
4645
|
res.end(JSON.stringify({ ok: false, error: String(err2) }));
|
|
@@ -4672,8 +4665,8 @@ async function startChartServer(opts = {}) {
|
|
|
4672
4665
|
const { projects: newProjects } = JSON.parse(body);
|
|
4673
4666
|
const config2 = loadConfig(projectRoot);
|
|
4674
4667
|
config2.projects = newProjects.length > 0 ? newProjects : void 0;
|
|
4675
|
-
const configPath =
|
|
4676
|
-
|
|
4668
|
+
const configPath = import_node_path20.default.join(projectRoot, ".launchchart.json");
|
|
4669
|
+
import_node_fs18.default.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
4677
4670
|
projects.length = 0;
|
|
4678
4671
|
if (config2.projects) projects.push(...config2.projects);
|
|
4679
4672
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -4686,7 +4679,7 @@ async function startChartServer(opts = {}) {
|
|
|
4686
4679
|
return;
|
|
4687
4680
|
}
|
|
4688
4681
|
if (url2.pathname !== "/") {
|
|
4689
|
-
const staticPath =
|
|
4682
|
+
const staticPath = import_node_path20.default.join(clientDir, url2.pathname);
|
|
4690
4683
|
if (serveStatic(res, staticPath)) return;
|
|
4691
4684
|
}
|
|
4692
4685
|
serveIndex(res, clientDir);
|
|
@@ -4746,13 +4739,13 @@ function runServeCli(argv) {
|
|
|
4746
4739
|
process.exit(1);
|
|
4747
4740
|
});
|
|
4748
4741
|
}
|
|
4749
|
-
var import_node_http,
|
|
4742
|
+
var import_node_http, import_node_fs18, import_node_path20, MAX_PORT_SCAN, MIME_TYPES;
|
|
4750
4743
|
var init_chart_serve = __esm({
|
|
4751
4744
|
"src/server/chart-serve.ts"() {
|
|
4752
4745
|
"use strict";
|
|
4753
4746
|
import_node_http = __toESM(require("node:http"));
|
|
4754
|
-
|
|
4755
|
-
|
|
4747
|
+
import_node_fs18 = __toESM(require("node:fs"));
|
|
4748
|
+
import_node_path20 = __toESM(require("node:path"));
|
|
4756
4749
|
init_graph();
|
|
4757
4750
|
init_lockfile();
|
|
4758
4751
|
init_config();
|
|
@@ -4774,214 +4767,6 @@ var init_chart_serve = __esm({
|
|
|
4774
4767
|
}
|
|
4775
4768
|
});
|
|
4776
4769
|
|
|
4777
|
-
// src/server/blast-radius-builder.ts
|
|
4778
|
-
function loadDefaults(rootDir) {
|
|
4779
|
-
const filePath = (0, import_node_path20.join)(rootDir, ".launchsecure", "blast-radius-defaults.json");
|
|
4780
|
-
try {
|
|
4781
|
-
if (import_node_fs18.default.existsSync(filePath)) {
|
|
4782
|
-
const raw = import_node_fs18.default.readFileSync(filePath, "utf-8");
|
|
4783
|
-
return JSON.parse(raw);
|
|
4784
|
-
}
|
|
4785
|
-
} catch {
|
|
4786
|
-
}
|
|
4787
|
-
return FALLBACK_DEFAULTS;
|
|
4788
|
-
}
|
|
4789
|
-
function generateAcceptance(node, inspect) {
|
|
4790
|
-
const criteria = [];
|
|
4791
|
-
const t = node.type?.toLowerCase() ?? "";
|
|
4792
|
-
if (t === "endpoint" || t === "mcp-tool") {
|
|
4793
|
-
const methods = inspect?.methods ?? [];
|
|
4794
|
-
const path3 = inspect?.path ?? node.id;
|
|
4795
|
-
if (methods.length > 0) {
|
|
4796
|
-
criteria.push(`${methods.join("/")} ${path3} still returns correct responses for authorized users`);
|
|
4797
|
-
} else {
|
|
4798
|
-
criteria.push(`${path3} still responds correctly`);
|
|
4799
|
-
}
|
|
4800
|
-
if (inspect?.auth && inspect.auth.includes("withAuth")) {
|
|
4801
|
-
criteria.push("Authentication and authorization still enforced");
|
|
4802
|
-
}
|
|
4803
|
-
if (inspect?.db_models && inspect.db_models.length > 0) {
|
|
4804
|
-
criteria.push(`DB operations on ${inspect.db_models.join(", ")} still work correctly`);
|
|
4805
|
-
}
|
|
4806
|
-
} else if (t === "page" || t === "component" || t === "layout") {
|
|
4807
|
-
criteria.push(`${node.name} renders without errors`);
|
|
4808
|
-
if (inspect?.stateVars && inspect.stateVars.length > 0) {
|
|
4809
|
-
criteria.push("State management still works correctly");
|
|
4810
|
-
}
|
|
4811
|
-
if (inspect?.elements && inspect.elements.length > 5) {
|
|
4812
|
-
criteria.push("All child components render correctly");
|
|
4813
|
-
}
|
|
4814
|
-
} else if (t === "table" || t === "enum") {
|
|
4815
|
-
criteria.push(`${node.name} schema unchanged or migration applies cleanly`);
|
|
4816
|
-
criteria.push("Existing queries against this table still work");
|
|
4817
|
-
} else if (t === "hook") {
|
|
4818
|
-
criteria.push(`${node.name} returns expected shape`);
|
|
4819
|
-
if (inspect?.stateVars && inspect.stateVars.length > 0) {
|
|
4820
|
-
criteria.push(`State variables [${inspect.stateVars.map((s) => s.name).join(", ")}] still returned`);
|
|
4821
|
-
}
|
|
4822
|
-
} else if (t === "context") {
|
|
4823
|
-
criteria.push(`${node.name} provides correct context to consumers`);
|
|
4824
|
-
} else if (t === "lib" || t === "config" || t === "types") {
|
|
4825
|
-
criteria.push(`${node.name} exports still conform to expected interface`);
|
|
4826
|
-
} else if (t === "seed" || t === "seed_role" || t === "seed_permission") {
|
|
4827
|
-
criteria.push("Seed runs without errors");
|
|
4828
|
-
criteria.push("Expected rows created in database");
|
|
4829
|
-
} else {
|
|
4830
|
-
criteria.push("Verify no regression");
|
|
4831
|
-
}
|
|
4832
|
-
return criteria;
|
|
4833
|
-
}
|
|
4834
|
-
function buildManifest(input) {
|
|
4835
|
-
const { mode, title, description, subtitle, blastResults, createNodes, inspectData, defaults } = input;
|
|
4836
|
-
const nodeMap = /* @__PURE__ */ new Map();
|
|
4837
|
-
const centerNodeIds = /* @__PURE__ */ new Set();
|
|
4838
|
-
for (const result of blastResults) {
|
|
4839
|
-
centerNodeIds.add(result.center.id);
|
|
4840
|
-
for (const node of result.affected) {
|
|
4841
|
-
const existing = nodeMap.get(node.id);
|
|
4842
|
-
if (!existing || node.hop < existing.hop) {
|
|
4843
|
-
nodeMap.set(node.id, node);
|
|
4844
|
-
}
|
|
4845
|
-
}
|
|
4846
|
-
}
|
|
4847
|
-
for (const id of centerNodeIds) {
|
|
4848
|
-
nodeMap.delete(id);
|
|
4849
|
-
}
|
|
4850
|
-
const manifestNodes = [];
|
|
4851
|
-
for (const result of blastResults) {
|
|
4852
|
-
const c = result.center;
|
|
4853
|
-
if (manifestNodes.some((n) => n.id === c.id)) continue;
|
|
4854
|
-
const inspect = inspectData[c.id];
|
|
4855
|
-
manifestNodes.push({
|
|
4856
|
-
id: c.id,
|
|
4857
|
-
name: c.name,
|
|
4858
|
-
layer: c.layer,
|
|
4859
|
-
ring: "modify",
|
|
4860
|
-
type: c.type,
|
|
4861
|
-
reason: `Direct change target`,
|
|
4862
|
-
acceptance: generateAcceptance(
|
|
4863
|
-
{ id: c.id, name: c.name, type: c.type, layer: c.layer, hop: 0 },
|
|
4864
|
-
inspect
|
|
4865
|
-
)
|
|
4866
|
-
});
|
|
4867
|
-
}
|
|
4868
|
-
for (const [, node] of nodeMap) {
|
|
4869
|
-
const ring = node.hop <= 1 ? "modify" : "ripple";
|
|
4870
|
-
const inspect = inspectData[node.id];
|
|
4871
|
-
const reason = node.hop <= 1 ? `Directly depends on changed node` : `Indirect dependency (${node.hop} hops away)`;
|
|
4872
|
-
manifestNodes.push({
|
|
4873
|
-
id: node.id,
|
|
4874
|
-
name: node.name,
|
|
4875
|
-
layer: node.layer,
|
|
4876
|
-
ring,
|
|
4877
|
-
type: node.type,
|
|
4878
|
-
reason,
|
|
4879
|
-
acceptance: generateAcceptance(node, inspect)
|
|
4880
|
-
});
|
|
4881
|
-
}
|
|
4882
|
-
for (const cn of createNodes) {
|
|
4883
|
-
manifestNodes.push({
|
|
4884
|
-
id: cn.id,
|
|
4885
|
-
name: cn.name,
|
|
4886
|
-
layer: cn.layer,
|
|
4887
|
-
ring: "create",
|
|
4888
|
-
type: cn.type ?? "unknown",
|
|
4889
|
-
reason: cn.reason,
|
|
4890
|
-
acceptance: cn.acceptance ?? ["Verify implementation matches spec"]
|
|
4891
|
-
});
|
|
4892
|
-
}
|
|
4893
|
-
const layerIds = /* @__PURE__ */ new Set();
|
|
4894
|
-
for (const n of manifestNodes) {
|
|
4895
|
-
layerIds.add(n.layer);
|
|
4896
|
-
}
|
|
4897
|
-
const layers = [];
|
|
4898
|
-
for (const id of layerIds) {
|
|
4899
|
-
const def = defaults.layers[id];
|
|
4900
|
-
if (def) {
|
|
4901
|
-
layers.push({ id, name: def.name, icon: def.icon, color: def.color });
|
|
4902
|
-
} else {
|
|
4903
|
-
layers.push({ id, name: id, icon: "box", color: "#cbd5e1" });
|
|
4904
|
-
}
|
|
4905
|
-
}
|
|
4906
|
-
const edgeSet = /* @__PURE__ */ new Set();
|
|
4907
|
-
const edges = [];
|
|
4908
|
-
const allNodeIds = new Set(manifestNodes.map((n) => n.id));
|
|
4909
|
-
for (const cId of centerNodeIds) {
|
|
4910
|
-
for (const result of blastResults) {
|
|
4911
|
-
for (const affected of result.affected) {
|
|
4912
|
-
if (affected.hop === 1 && result.center.id === cId && allNodeIds.has(affected.id)) {
|
|
4913
|
-
const key = `${cId}->${affected.id}`;
|
|
4914
|
-
if (!edgeSet.has(key)) {
|
|
4915
|
-
edgeSet.add(key);
|
|
4916
|
-
edges.push({ source: cId, target: affected.id });
|
|
4917
|
-
}
|
|
4918
|
-
}
|
|
4919
|
-
}
|
|
4920
|
-
}
|
|
4921
|
-
}
|
|
4922
|
-
for (const result of blastResults) {
|
|
4923
|
-
if (result.edges) {
|
|
4924
|
-
for (const edge of result.edges) {
|
|
4925
|
-
if (allNodeIds.has(edge.source) && allNodeIds.has(edge.target)) {
|
|
4926
|
-
const key = `${edge.source}->${edge.target}`;
|
|
4927
|
-
if (!edgeSet.has(key)) {
|
|
4928
|
-
edgeSet.add(key);
|
|
4929
|
-
edges.push({ source: edge.source, target: edge.target });
|
|
4930
|
-
}
|
|
4931
|
-
}
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4935
|
-
for (const cn of createNodes) {
|
|
4936
|
-
edges.push({ source: "center", target: cn.id });
|
|
4937
|
-
if (cn.connects_to) {
|
|
4938
|
-
for (const targetId of cn.connects_to) {
|
|
4939
|
-
if (allNodeIds.has(targetId) || createNodes.some((c) => c.id === targetId)) {
|
|
4940
|
-
const key = `${cn.id}->${targetId}`;
|
|
4941
|
-
if (!edgeSet.has(key)) {
|
|
4942
|
-
edgeSet.add(key);
|
|
4943
|
-
edges.push({ source: cn.id, target: targetId });
|
|
4944
|
-
}
|
|
4945
|
-
}
|
|
4946
|
-
}
|
|
4947
|
-
}
|
|
4948
|
-
}
|
|
4949
|
-
return {
|
|
4950
|
-
mode,
|
|
4951
|
-
title,
|
|
4952
|
-
subtitle,
|
|
4953
|
-
layers,
|
|
4954
|
-
rings: defaults.rings,
|
|
4955
|
-
center: { name: title, description },
|
|
4956
|
-
nodes: manifestNodes,
|
|
4957
|
-
edges
|
|
4958
|
-
};
|
|
4959
|
-
}
|
|
4960
|
-
var import_node_fs18, import_node_path20, FALLBACK_DEFAULTS;
|
|
4961
|
-
var init_blast_radius_builder = __esm({
|
|
4962
|
-
"src/server/blast-radius-builder.ts"() {
|
|
4963
|
-
"use strict";
|
|
4964
|
-
import_node_fs18 = __toESM(require("node:fs"));
|
|
4965
|
-
import_node_path20 = require("node:path");
|
|
4966
|
-
FALLBACK_DEFAULTS = {
|
|
4967
|
-
rings: [
|
|
4968
|
-
{ id: "modify", name: "Modify", color: "#ff6b00" },
|
|
4969
|
-
{ id: "ripple", name: "Ripple (verify)", color: "#ffff00" },
|
|
4970
|
-
{ id: "create", name: "Create", color: "#00ff00" }
|
|
4971
|
-
],
|
|
4972
|
-
layers: {
|
|
4973
|
-
db: { name: "Database", icon: "database", color: "#cbd5e1" },
|
|
4974
|
-
api: { name: "API", icon: "server", color: "#cbd5e1" },
|
|
4975
|
-
middleware: { name: "Middleware", icon: "shield", color: "#cbd5e1" },
|
|
4976
|
-
ui: { name: "UI", icon: "layout-dashboard", color: "#cbd5e1" },
|
|
4977
|
-
config: { name: "Config / Seed", icon: "settings", color: "#cbd5e1" },
|
|
4978
|
-
shared: { name: "Shared Types", icon: "box", color: "#cbd5e1" }
|
|
4979
|
-
},
|
|
4980
|
-
center: { color: "#ff0000" }
|
|
4981
|
-
};
|
|
4982
|
-
}
|
|
4983
|
-
});
|
|
4984
|
-
|
|
4985
4770
|
// src/server/graph/core/language-detection.ts
|
|
4986
4771
|
function walkForExtensions(dir, extCounts, depth = 0) {
|
|
4987
4772
|
if (depth > 10) return;
|
|
@@ -5375,144 +5160,6 @@ function handleBlastPoints(args) {
|
|
|
5375
5160
|
}
|
|
5376
5161
|
});
|
|
5377
5162
|
}
|
|
5378
|
-
function handleGenerateBlastRadius(args) {
|
|
5379
|
-
const rootDir = process.cwd();
|
|
5380
|
-
const mode = args.mode ?? "structural";
|
|
5381
|
-
const title = args.title;
|
|
5382
|
-
const description = args.description ?? title;
|
|
5383
|
-
const subtitle = args.subtitle;
|
|
5384
|
-
const hops = args.hops ?? 2;
|
|
5385
|
-
const defaults = loadDefaults(rootDir);
|
|
5386
|
-
let centerNodeIds = [];
|
|
5387
|
-
if (mode === "structural") {
|
|
5388
|
-
const nodeId = args.node_id;
|
|
5389
|
-
if (!nodeId) return err("structural mode requires node_id");
|
|
5390
|
-
centerNodeIds = [nodeId];
|
|
5391
|
-
} else {
|
|
5392
|
-
centerNodeIds = args.center_nodes ?? [];
|
|
5393
|
-
if (centerNodeIds.length === 0) return err("feature mode requires center_nodes[]");
|
|
5394
|
-
}
|
|
5395
|
-
const createNodes = args.create_nodes ?? [];
|
|
5396
|
-
const blastResults = [];
|
|
5397
|
-
for (const nodeId of centerNodeIds) {
|
|
5398
|
-
let targetLayer;
|
|
5399
|
-
const graphs = readAllGraphs(rootDir);
|
|
5400
|
-
for (const [layer, graph2] of Object.entries(graphs)) {
|
|
5401
|
-
if (graph2 && graph2.nodes.some((n) => n.id === nodeId)) {
|
|
5402
|
-
targetLayer = layer;
|
|
5403
|
-
break;
|
|
5404
|
-
}
|
|
5405
|
-
}
|
|
5406
|
-
if (!targetLayer) continue;
|
|
5407
|
-
const graph = readGraph(rootDir, targetLayer);
|
|
5408
|
-
if (!graph) continue;
|
|
5409
|
-
const center = graph.nodes.find((n) => n.id === nodeId);
|
|
5410
|
-
if (!center) continue;
|
|
5411
|
-
const result2 = reverseNeighborhood(graph, nodeId, hops, "reverse");
|
|
5412
|
-
const affected = [];
|
|
5413
|
-
for (const [id, { node, hop }] of result2.nodes) {
|
|
5414
|
-
if (id === nodeId) continue;
|
|
5415
|
-
const tags = node.tags;
|
|
5416
|
-
affected.push({ id: node.id, name: node.name, type: node.type, layer: targetLayer, hop, module: tags?.module });
|
|
5417
|
-
}
|
|
5418
|
-
const otherLayers = getAvailableLayers(rootDir).filter((l) => l !== targetLayer && l !== "static");
|
|
5419
|
-
for (const otherLayer of otherLayers) {
|
|
5420
|
-
const otherGraph = readGraph(rootDir, otherLayer);
|
|
5421
|
-
if (!otherGraph) continue;
|
|
5422
|
-
for (const edge of otherGraph.edges) {
|
|
5423
|
-
if (edge.target === nodeId || edge.source === nodeId) {
|
|
5424
|
-
const dependentId = edge.target === nodeId ? edge.source : edge.target;
|
|
5425
|
-
if (affected.some((a) => a.id === dependentId)) continue;
|
|
5426
|
-
const depNode = otherGraph.nodes.find((n) => n.id === dependentId);
|
|
5427
|
-
if (depNode) {
|
|
5428
|
-
const tags = depNode.tags;
|
|
5429
|
-
affected.push({ id: depNode.id, name: depNode.name, type: depNode.type, layer: otherLayer, hop: 1, module: tags?.module });
|
|
5430
|
-
}
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5433
|
-
}
|
|
5434
|
-
const centerTags = center.tags;
|
|
5435
|
-
const edges = result2.edges.map((e) => ({ source: e.source, target: e.target }));
|
|
5436
|
-
blastResults.push({
|
|
5437
|
-
center: { id: center.id, name: center.name, type: center.type, layer: targetLayer, module: centerTags?.module },
|
|
5438
|
-
affected,
|
|
5439
|
-
edges
|
|
5440
|
-
});
|
|
5441
|
-
}
|
|
5442
|
-
if (blastResults.length === 0) {
|
|
5443
|
-
return err(`None of the center nodes were found in any graph layer: ${centerNodeIds.join(", ")}`);
|
|
5444
|
-
}
|
|
5445
|
-
const inspectData = {};
|
|
5446
|
-
const allAffectedIds = /* @__PURE__ */ new Set();
|
|
5447
|
-
for (const r of blastResults) {
|
|
5448
|
-
allAffectedIds.add(r.center.id);
|
|
5449
|
-
for (const a of r.affected) allAffectedIds.add(a.id);
|
|
5450
|
-
}
|
|
5451
|
-
const allGraphs = readAllGraphs(rootDir);
|
|
5452
|
-
for (const id of allAffectedIds) {
|
|
5453
|
-
for (const [, graph] of Object.entries(allGraphs)) {
|
|
5454
|
-
if (!graph) continue;
|
|
5455
|
-
const node = graph.nodes.find((n) => n.id === id);
|
|
5456
|
-
if (node) {
|
|
5457
|
-
inspectData[id] = {
|
|
5458
|
-
type: node.type,
|
|
5459
|
-
name: node.name,
|
|
5460
|
-
methods: node.methods,
|
|
5461
|
-
path: node.path ?? node.handler,
|
|
5462
|
-
auth: node.auth,
|
|
5463
|
-
db_models: node.db_models
|
|
5464
|
-
};
|
|
5465
|
-
break;
|
|
5466
|
-
}
|
|
5467
|
-
}
|
|
5468
|
-
}
|
|
5469
|
-
const manifest = buildManifest({
|
|
5470
|
-
mode,
|
|
5471
|
-
title,
|
|
5472
|
-
description,
|
|
5473
|
-
subtitle,
|
|
5474
|
-
blastResults,
|
|
5475
|
-
createNodes,
|
|
5476
|
-
inspectData,
|
|
5477
|
-
defaults
|
|
5478
|
-
});
|
|
5479
|
-
const pushToDeck = args.push_to_deck;
|
|
5480
|
-
const session = args.session;
|
|
5481
|
-
let deckResult;
|
|
5482
|
-
if (pushToDeck) {
|
|
5483
|
-
if (!session) return err("push_to_deck requires a session name");
|
|
5484
|
-
const deckLockPath = (0, import_node_path22.join)(rootDir, ".launchsecure", "launch-deck.lock");
|
|
5485
|
-
if (!(0, import_node_fs20.existsSync)(deckLockPath)) {
|
|
5486
|
-
deckResult = { pushed: false, reason: "Deck server not running (no lock file). Push manually via deck tool." };
|
|
5487
|
-
} else {
|
|
5488
|
-
try {
|
|
5489
|
-
const lock = JSON.parse((0, import_node_fs20.readFileSync)(deckLockPath, "utf-8"));
|
|
5490
|
-
const deckUrl = lock.url;
|
|
5491
|
-
const body = JSON.stringify({
|
|
5492
|
-
session,
|
|
5493
|
-
mode: "show",
|
|
5494
|
-
blocks: [{ type: "blast-radius", label: title, manifest }]
|
|
5495
|
-
});
|
|
5496
|
-
(0, import_node_child_process2.execFileSync)("curl", [
|
|
5497
|
-
"-s",
|
|
5498
|
-
"-X",
|
|
5499
|
-
"POST",
|
|
5500
|
-
deckUrl + "/api/deck",
|
|
5501
|
-
"-H",
|
|
5502
|
-
"Content-Type: application/json",
|
|
5503
|
-
"-d",
|
|
5504
|
-
body
|
|
5505
|
-
], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] });
|
|
5506
|
-
deckResult = { pushed: true, session, url: deckUrl };
|
|
5507
|
-
} catch (e) {
|
|
5508
|
-
deckResult = { pushed: false, reason: `Failed to push to deck: ${e}` };
|
|
5509
|
-
}
|
|
5510
|
-
}
|
|
5511
|
-
}
|
|
5512
|
-
const result = { ...manifest };
|
|
5513
|
-
if (deckResult) result._deck = deckResult;
|
|
5514
|
-
return okJson(result);
|
|
5515
|
-
}
|
|
5516
5163
|
function layerSummary(graph) {
|
|
5517
5164
|
const typeCounts = {};
|
|
5518
5165
|
const moduleCounts = {};
|
|
@@ -6214,10 +5861,6 @@ async function handleMessage(msg) {
|
|
|
6214
5861
|
respond(id ?? null, handleBlastPoints(args));
|
|
6215
5862
|
return;
|
|
6216
5863
|
}
|
|
6217
|
-
if (toolName === "generate_blast_radius") {
|
|
6218
|
-
respond(id ?? null, handleGenerateBlastRadius(args));
|
|
6219
|
-
return;
|
|
6220
|
-
}
|
|
6221
5864
|
respondError(id ?? null, -32601, `Unknown tool: ${toolName}`);
|
|
6222
5865
|
return;
|
|
6223
5866
|
}
|
|
@@ -6262,7 +5905,6 @@ var init_graph_mcp = __esm({
|
|
|
6262
5905
|
import_node_child_process2 = require("node:child_process");
|
|
6263
5906
|
import_node_os2 = require("node:os");
|
|
6264
5907
|
init_graph();
|
|
6265
|
-
init_blast_radius_builder();
|
|
6266
5908
|
init_lockfile();
|
|
6267
5909
|
init_config();
|
|
6268
5910
|
init_parser_registry();
|
|
@@ -6579,81 +6221,6 @@ Example: blast_points(node_id: "server/auth/middleware.ts", hops: 2) \u2192 retu
|
|
|
6579
6221
|
},
|
|
6580
6222
|
required: ["node_id"]
|
|
6581
6223
|
}
|
|
6582
|
-
},
|
|
6583
|
-
{
|
|
6584
|
-
name: "generate_blast_radius",
|
|
6585
|
-
description: `Generate a complete BlastRadiusManifest from graph data \u2014 ready to push to deck.
|
|
6586
|
-
|
|
6587
|
-
Two modes:
|
|
6588
|
-
- **Structural**: single node changed \u2192 auto-discover what's affected via reverse BFS
|
|
6589
|
-
Example: generate_blast_radius({ mode: "structural", node_id: "CommentChannel", title: "CommentChannel refactor" })
|
|
6590
|
-
- **Feature**: new feature \u2192 multiple starting nodes + new nodes to create
|
|
6591
|
-
Example: generate_blast_radius({ mode: "feature", title: "Client Role", description: "...", center_nodes: ["CommentChannel", "ProjectMember"], create_nodes: [{ id: "ChannelMember", name: "ChannelMember table", layer: "db", reason: "..." }] })
|
|
6592
|
-
|
|
6593
|
-
Output is a BlastRadiusManifest JSON that passes directly to the deck tool's blast-radius block.
|
|
6594
|
-
Reads ring/layer/center colors from .launchsecure/blast-radius-defaults.json.
|
|
6595
|
-
Auto-generates acceptance criteria per node using inspect_node AST data.`,
|
|
6596
|
-
inputSchema: {
|
|
6597
|
-
type: "object",
|
|
6598
|
-
properties: {
|
|
6599
|
-
mode: {
|
|
6600
|
-
type: "string",
|
|
6601
|
-
enum: ["structural", "feature"],
|
|
6602
|
-
description: '"structural" = single node changed. "feature" = new feature with multiple nodes.'
|
|
6603
|
-
},
|
|
6604
|
-
title: {
|
|
6605
|
-
type: "string",
|
|
6606
|
-
description: "Title for the blast radius (shown in center node and header)."
|
|
6607
|
-
},
|
|
6608
|
-
description: {
|
|
6609
|
-
type: "string",
|
|
6610
|
-
description: "Description of the change or feature."
|
|
6611
|
-
},
|
|
6612
|
-
subtitle: {
|
|
6613
|
-
type: "string",
|
|
6614
|
-
description: "Optional subtitle shown above title in the viz."
|
|
6615
|
-
},
|
|
6616
|
-
node_id: {
|
|
6617
|
-
type: "string",
|
|
6618
|
-
description: "Structural mode only: the node being changed."
|
|
6619
|
-
},
|
|
6620
|
-
center_nodes: {
|
|
6621
|
-
type: "array",
|
|
6622
|
-
items: { type: "string" },
|
|
6623
|
-
description: "Feature mode: existing graph node IDs that are the starting points for traversal."
|
|
6624
|
-
},
|
|
6625
|
-
create_nodes: {
|
|
6626
|
-
type: "array",
|
|
6627
|
-
items: {
|
|
6628
|
-
type: "object",
|
|
6629
|
-
properties: {
|
|
6630
|
-
id: { type: "string" },
|
|
6631
|
-
name: { type: "string" },
|
|
6632
|
-
layer: { type: "string" },
|
|
6633
|
-
type: { type: "string" },
|
|
6634
|
-
reason: { type: "string" },
|
|
6635
|
-
acceptance: { type: "array", items: { type: "string" } },
|
|
6636
|
-
connects_to: { type: "array", items: { type: "string" }, description: "IDs of existing nodes this new node has FK/relationship edges to." }
|
|
6637
|
-
},
|
|
6638
|
-
required: ["id", "name", "layer", "reason"]
|
|
6639
|
-
},
|
|
6640
|
-
description: "Feature mode: new nodes that need to be created (not in graph yet)."
|
|
6641
|
-
},
|
|
6642
|
-
hops: {
|
|
6643
|
-
type: "number",
|
|
6644
|
-
description: "Max hops for traversal. Default 2. Hop 1 = modify ring, hop 2+ = ripple ring."
|
|
6645
|
-
},
|
|
6646
|
-
push_to_deck: {
|
|
6647
|
-
type: "boolean",
|
|
6648
|
-
description: "If true, pushes the manifest directly to LaunchDeck browser (requires deck server running). Default false."
|
|
6649
|
-
},
|
|
6650
|
-
session: {
|
|
6651
|
-
type: "string",
|
|
6652
|
-
description: "Session name for the deck tab. Required when push_to_deck is true."
|
|
6653
|
-
}
|
|
6654
|
-
},
|
|
6655
|
-
required: ["title"]
|
|
6656
|
-
}
|
|
6657
6224
|
}
|
|
6658
6225
|
];
|
|
6659
6226
|
COMPACT_SCHEMA = {
|