@launchsecure/launch-kit 0.0.39 → 0.0.41
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-Dd6IotOZ.css +1 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-DE0uje6k.css +32 -0
- package/dist/client/index.html +2 -2
- package/dist/council-client/assets/index-CGYusOCK.css +1 -0
- package/dist/council-client/assets/{index-jjBWyhry.js → index-DkTFX53U.js} +1 -1
- package/dist/council-client/index.html +2 -2
- package/dist/deck-client/assets/_baseUniq-mvYvzeEJ.js +1 -0
- package/dist/deck-client/assets/arc-CX4ylnp2.js +1 -0
- package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-BkR-5IRK.js +36 -0
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-CwAGy9lU.js → blockDiagram-DXYQGD6D-DVNQht7c.js} +2 -2
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-L_g_SS21.js → c4Diagram-AHTNJAMY-Cbq1rlG8.js} +2 -2
- package/dist/deck-client/assets/channel-B9GC-CLn.js +1 -0
- package/dist/deck-client/assets/chunk-4BX2VUAB-D58Co4lU.js +1 -0
- package/dist/deck-client/assets/{chunk-4TB4RGXK-Bk0FUbxU.js → chunk-4TB4RGXK-BYvhTm3d.js} +1 -1
- package/dist/deck-client/assets/chunk-55IACEB6-oWukUhYg.js +1 -0
- package/dist/deck-client/assets/chunk-EDXVE4YY-Cm58kVnZ.js +1 -0
- package/dist/deck-client/assets/{chunk-FMBD7UC4-DqOvWr1k.js → chunk-FMBD7UC4-Dg-i7kzi.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-1Kd7yK5u.js → chunk-OYMX7WX6-C72wigPl.js} +1 -1
- package/dist/deck-client/assets/chunk-QZHKN3VN-CLgeuAKw.js +1 -0
- package/dist/deck-client/assets/chunk-YZCP3GAM-HDDlJ5oI.js +1 -0
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CFBvYQ9j.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CFBvYQ9j.js +1 -0
- package/dist/deck-client/assets/clone-n-WQlAGe.js +1 -0
- package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CUXQKg2M.js +1 -0
- package/dist/deck-client/assets/dagre-KV5264BT-C5M-fVDc.js +4 -0
- package/dist/deck-client/assets/diagram-5BDNPKRD-CcVsQ0S8.js +10 -0
- package/dist/deck-client/assets/diagram-G4DWMVQ6-DJswXyep.js +24 -0
- package/dist/deck-client/assets/diagram-MMDJMWI5-CGT76fm1.js +43 -0
- package/dist/deck-client/assets/diagram-TYMM5635-BBsYUNN6.js +24 -0
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-aiv9GZnL.js → erDiagram-SMLLAGMA-DKWYEHQS.js} +2 -2
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-C6Fhvtsy.js → flowDiagram-DWJPFMVM-DLuDYIKT.js} +2 -2
- package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-B19b6Qtj.js +292 -0
- package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-BYLAfYVS.js +106 -0
- package/dist/deck-client/assets/graph-CfzQUfPh.js +1 -0
- package/dist/deck-client/assets/index-DlwdTgE_.js +892 -0
- package/dist/deck-client/assets/index-evAPhGvM.css +1 -0
- package/dist/deck-client/assets/infoDiagram-42DDH7IO-Dp3mUA9c.js +2 -0
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BwCUmUVt.js → ishikawaDiagram-UXIWVN3A-BhrNX_jI.js} +5 -5
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C6qoqJmJ.js → journeyDiagram-VCZTEJTY-B5lJI492.js} +2 -2
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-Dz1Tt3sA.js → kanban-definition-6JOO6SKY-D9-lmhQf.js} +2 -2
- package/dist/deck-client/assets/layout-CfIe_Su8.js +1 -0
- package/dist/deck-client/assets/linear-09ZFRoh_.js +1 -0
- package/dist/deck-client/assets/mermaid.core-BaQyIOvj.js +309 -0
- package/dist/deck-client/assets/min-CYwCzYaL.js +1 -0
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-CfXcK1qH.js → mindmap-definition-QFDTVHPH-CouFxf6C.js} +2 -2
- package/dist/deck-client/assets/pieDiagram-DEJITSTG-DMB1ufC0.js +30 -0
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-CXwvZ1i1.js → quadrantDiagram-34T5L4WZ-CBiOKudN.js} +2 -2
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cl6xm0fR.js → requirementDiagram-MS252O5E-BMc3GJkx.js} +2 -2
- package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-CxACUncm.js +10 -0
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-BC1MYBn6.js → sequenceDiagram-FGHM5R23-Ch-P3Mzc.js} +2 -2
- package/dist/deck-client/assets/stateDiagram-FHFEXIEX-Cy8n7Yzk.js +1 -0
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-C14VKCzi.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DKnITsD4.js → timeline-definition-GMOUNBTQ-C2V4sSkm.js} +2 -2
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-BdajXRrh.js → vennDiagram-DHZGUBPP-YOqt4VbE.js} +2 -2
- package/dist/deck-client/assets/wardley-RL74JXVD-Bxo5x40D.js +162 -0
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-B2hDCDl2.js → wardleyDiagram-NUSXRM2D-DW9SOqbx.js} +2 -2
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CvnYFs51.js → xychartDiagram-5P7HB3ND-D-rZvZOL.js} +2 -2
- package/dist/deck-client/index.html +2 -2
- package/dist/server/beacon-monitor-entry.js +106 -24
- package/dist/server/chart-serve.js +544 -247
- package/dist/server/cli.js +1016 -324
- package/dist/server/council-entry.js +23 -4
- package/dist/server/council-serve.js +22 -3
- package/dist/server/deck-mcp-entry.js +523 -125
- package/dist/server/deck-serve.js +326 -40
- package/dist/server/graph-mcp-entry.js +1160 -357
- package/dist/server/init-entry.js +17 -7
- package/dist/server/orbit-entry.js +91 -7
- package/dist/server/recall-entry.js +94 -12
- package/dist/server/rover-entry.js +1 -1
- package/package.json +1 -1
- package/scaffolds/ls-marketplace/plugins/kit/skills/comms/SKILL.md +34 -1
- package/scaffolds/ls-marketplace/plugins/kit/skills/gen-test/SKILL.md +126 -0
- package/scaffolds/statusline/statusline-mcp.sh +68 -19
- package/scaffolds/statusline/statusline-wrapper.sh +12 -9
- package/dist/chart-client/assets/index-ysGpLeOW.css +0 -1
- package/dist/client/assets/index-CMN3tlGP.css +0 -32
- package/dist/council-client/assets/index-ChmNX6bZ.css +0 -1
- package/dist/deck-client/assets/_baseUniq-DOrnEQMI.js +0 -1
- package/dist/deck-client/assets/arc-DOWK7V3m.js +0 -1
- package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-DPhzvk7q.js +0 -36
- package/dist/deck-client/assets/channel-DqiACUUq.js +0 -1
- package/dist/deck-client/assets/chunk-4BX2VUAB-RKm0LXpu.js +0 -1
- package/dist/deck-client/assets/chunk-55IACEB6-Cl3hja-M.js +0 -1
- package/dist/deck-client/assets/chunk-EDXVE4YY-CNIMQCV2.js +0 -1
- package/dist/deck-client/assets/chunk-QZHKN3VN-6_kraYpP.js +0 -1
- package/dist/deck-client/assets/chunk-YZCP3GAM-FgAwIWlo.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-D23cq2C3.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-D23cq2C3.js +0 -1
- package/dist/deck-client/assets/clone-C7jSigGq.js +0 -1
- package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CigVnnPr.js +0 -1
- package/dist/deck-client/assets/dagre-KV5264BT-DHZXTktX.js +0 -4
- package/dist/deck-client/assets/diagram-5BDNPKRD-H5k0eauU.js +0 -10
- package/dist/deck-client/assets/diagram-G4DWMVQ6-Bg3dFhSY.js +0 -24
- package/dist/deck-client/assets/diagram-MMDJMWI5-CQLC410N.js +0 -43
- package/dist/deck-client/assets/diagram-TYMM5635-DFTCHVkP.js +0 -24
- package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DSaGMPM4.js +0 -292
- package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-DMjL2Vix.js +0 -106
- package/dist/deck-client/assets/graph-B7Vn5lkK.js +0 -1
- package/dist/deck-client/assets/index-BD36e-tD.js +0 -1196
- package/dist/deck-client/assets/index-CGbNOpk9.css +0 -1
- package/dist/deck-client/assets/infoDiagram-42DDH7IO-mNi4iygG.js +0 -2
- package/dist/deck-client/assets/layout-CZTyRhOG.js +0 -1
- package/dist/deck-client/assets/linear--7n7iEvd.js +0 -1
- package/dist/deck-client/assets/min-Bh130DN8.js +0 -1
- package/dist/deck-client/assets/pieDiagram-DEJITSTG-DjVHLAVw.js +0 -30
- package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-BOH9sLyh.js +0 -10
- package/dist/deck-client/assets/stateDiagram-FHFEXIEX-kNp9bv8K.js +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-hRsAFc2t.js +0 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-BL802-su.js +0 -162
- /package/dist/chart-client/assets/{index-BlsuXuQ1.js → index-CrYM1-ac.js} +0 -0
- /package/dist/client/assets/{index-BA7BHBWT.js → index-BoIjawzY.js} +0 -0
|
@@ -148,20 +148,20 @@ __export(config_exports, {
|
|
|
148
148
|
loadConfig: () => loadConfig
|
|
149
149
|
});
|
|
150
150
|
function loadConfig(rootDir) {
|
|
151
|
-
const configPath = (0,
|
|
152
|
-
if (!(0,
|
|
151
|
+
const configPath = (0, import_node_path3.join)(rootDir, LAUNCHCHART_CONFIG_FILE);
|
|
152
|
+
if (!(0, import_node_fs3.existsSync)(configPath)) return {};
|
|
153
153
|
try {
|
|
154
|
-
return JSON.parse((0,
|
|
154
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(configPath, "utf-8"));
|
|
155
155
|
} catch {
|
|
156
156
|
return {};
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
-
var
|
|
159
|
+
var import_node_fs3, import_node_path3;
|
|
160
160
|
var init_config = __esm({
|
|
161
161
|
"src/server/graph/core/config.ts"() {
|
|
162
162
|
"use strict";
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
import_node_fs3 = require("node:fs");
|
|
164
|
+
import_node_path3 = require("node:path");
|
|
165
165
|
init_launch_kit_paths();
|
|
166
166
|
}
|
|
167
167
|
});
|
|
@@ -198,18 +198,18 @@ function detectConventionDirs(rootDir, extraConventionDirs = []) {
|
|
|
198
198
|
const conventionDirs = [...CONVENTION_DIRS_BUILTIN, ...extraConventionDirs];
|
|
199
199
|
const searchDirs = [
|
|
200
200
|
rootDir,
|
|
201
|
-
(0,
|
|
202
|
-
(0,
|
|
203
|
-
(0,
|
|
201
|
+
(0, import_node_path4.join)(rootDir, "src"),
|
|
202
|
+
(0, import_node_path4.join)(rootDir, "app"),
|
|
203
|
+
(0, import_node_path4.join)(rootDir, "lib")
|
|
204
204
|
];
|
|
205
205
|
for (const base of searchDirs) {
|
|
206
206
|
for (const convention of conventionDirs) {
|
|
207
|
-
const dir = (0,
|
|
208
|
-
if (!(0,
|
|
207
|
+
const dir = (0, import_node_path4.join)(base, convention);
|
|
208
|
+
if (!(0, import_node_fs4.existsSync)(dir)) continue;
|
|
209
209
|
try {
|
|
210
|
-
const stat = (0,
|
|
210
|
+
const stat = (0, import_node_fs4.statSync)(dir);
|
|
211
211
|
if (!stat.isDirectory()) continue;
|
|
212
|
-
const entries = (0,
|
|
212
|
+
const entries = (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
|
|
213
213
|
if (entries.length > 0) {
|
|
214
214
|
const relPath = dir.replace(rootDir + "/", "").replace(rootDir + "\\", "");
|
|
215
215
|
result.set(relPath, entries);
|
|
@@ -285,12 +285,12 @@ function extractModuleFromPath(id, extraTrivial, extraSkipSegments, extraGeneric
|
|
|
285
285
|
}
|
|
286
286
|
return "root";
|
|
287
287
|
}
|
|
288
|
-
var
|
|
288
|
+
var import_node_fs4, import_node_path4, CONVENTION_DIRS_BUILTIN, GENERIC_ROLE_NAMES_BUILTIN, SKIP_SEGMENTS_BUILTIN, TRIVIAL_GROUPS, cachedRootDir, cachedConventionDirs, moduleTagger;
|
|
289
289
|
var init_module_tagger = __esm({
|
|
290
290
|
"src/server/graph/taggers/module-tagger.ts"() {
|
|
291
291
|
"use strict";
|
|
292
|
-
|
|
293
|
-
|
|
292
|
+
import_node_fs4 = require("node:fs");
|
|
293
|
+
import_node_path4 = require("node:path");
|
|
294
294
|
CONVENTION_DIRS_BUILTIN = ["features", "modules", "domains", "areas"];
|
|
295
295
|
GENERIC_ROLE_NAMES_BUILTIN = /* @__PURE__ */ new Set([
|
|
296
296
|
// JS/TS
|
|
@@ -505,7 +505,7 @@ function loadCustomTaggers(registry, config, rootDir, disabled) {
|
|
|
505
505
|
for (const entry of config.taggers?.custom ?? []) {
|
|
506
506
|
if (disabled.has(entry.id)) continue;
|
|
507
507
|
try {
|
|
508
|
-
const absPath = (0,
|
|
508
|
+
const absPath = (0, import_node_path5.resolve)(rootDir, entry.path);
|
|
509
509
|
const mod = require(absPath);
|
|
510
510
|
const tagger = "default" in mod ? mod.default : mod;
|
|
511
511
|
const override = config.taggers?.trackUntagged?.[tagger.id];
|
|
@@ -526,11 +526,11 @@ function createTaggerRegistry(config, rootDir) {
|
|
|
526
526
|
loadCustomTaggers(registry, config, rootDir, disabled);
|
|
527
527
|
return registry;
|
|
528
528
|
}
|
|
529
|
-
var
|
|
529
|
+
var import_node_path5, TaggerRegistry, BUILTIN_TAGGERS;
|
|
530
530
|
var init_tagger_registry = __esm({
|
|
531
531
|
"src/server/graph/core/tagger-registry.ts"() {
|
|
532
532
|
"use strict";
|
|
533
|
-
|
|
533
|
+
import_node_path5 = require("node:path");
|
|
534
534
|
init_module_tagger();
|
|
535
535
|
init_screen_tagger();
|
|
536
536
|
TaggerRegistry = class {
|
|
@@ -558,42 +558,42 @@ var init_tagger_registry = __esm({
|
|
|
558
558
|
|
|
559
559
|
// src/server/graph/core/atomic-write.ts
|
|
560
560
|
function atomicWriteFileSync(filePath, content, encoding = "utf-8") {
|
|
561
|
-
(0,
|
|
561
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path6.dirname)(filePath), { recursive: true });
|
|
562
562
|
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
563
563
|
try {
|
|
564
|
-
(0,
|
|
565
|
-
(0,
|
|
564
|
+
(0, import_node_fs5.writeFileSync)(tmp, content, typeof content === "string" ? encoding : void 0);
|
|
565
|
+
(0, import_node_fs5.renameSync)(tmp, filePath);
|
|
566
566
|
} catch (e) {
|
|
567
567
|
try {
|
|
568
|
-
(0,
|
|
568
|
+
(0, import_node_fs5.unlinkSync)(tmp);
|
|
569
569
|
} catch {
|
|
570
570
|
}
|
|
571
571
|
throw e;
|
|
572
572
|
}
|
|
573
573
|
}
|
|
574
|
-
var
|
|
574
|
+
var import_node_fs5, import_node_path6;
|
|
575
575
|
var init_atomic_write = __esm({
|
|
576
576
|
"src/server/graph/core/atomic-write.ts"() {
|
|
577
577
|
"use strict";
|
|
578
|
-
|
|
579
|
-
|
|
578
|
+
import_node_fs5 = require("node:fs");
|
|
579
|
+
import_node_path6 = require("node:path");
|
|
580
580
|
}
|
|
581
581
|
});
|
|
582
582
|
|
|
583
583
|
// src/server/graph/core/tag-store.ts
|
|
584
584
|
function tagsFilePath(rootDir) {
|
|
585
|
-
return (0,
|
|
585
|
+
return (0, import_node_path7.join)(rootDir, GRAPHS_DIR, TAGS_FILENAME);
|
|
586
586
|
}
|
|
587
587
|
function readTagStore(rootDir) {
|
|
588
588
|
const filePath = tagsFilePath(rootDir);
|
|
589
|
-
if (!(0,
|
|
590
|
-
const stat = (0,
|
|
589
|
+
if (!(0, import_node_fs6.existsSync)(filePath)) return {};
|
|
590
|
+
const stat = (0, import_node_fs6.statSync)(filePath);
|
|
591
591
|
const cached = tagCache.get(filePath);
|
|
592
592
|
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
593
593
|
return cached.store;
|
|
594
594
|
}
|
|
595
595
|
try {
|
|
596
|
-
const content = (0,
|
|
596
|
+
const content = (0, import_node_fs6.readFileSync)(filePath, "utf-8");
|
|
597
597
|
const store = JSON.parse(content);
|
|
598
598
|
tagCache.set(filePath, { mtimeMs: stat.mtimeMs, store });
|
|
599
599
|
return store;
|
|
@@ -603,8 +603,8 @@ function readTagStore(rootDir) {
|
|
|
603
603
|
}
|
|
604
604
|
function writeTagStore(rootDir, store) {
|
|
605
605
|
const filePath = tagsFilePath(rootDir);
|
|
606
|
-
const dir = (0,
|
|
607
|
-
(0,
|
|
606
|
+
const dir = (0, import_node_path7.dirname)(filePath);
|
|
607
|
+
(0, import_node_fs6.mkdirSync)(dir, { recursive: true });
|
|
608
608
|
const cleaned = {};
|
|
609
609
|
for (const [nodeId, tags] of Object.entries(store)) {
|
|
610
610
|
if (Object.keys(tags).length > 0) {
|
|
@@ -629,12 +629,12 @@ function removeTag(rootDir, nodeId, key) {
|
|
|
629
629
|
}
|
|
630
630
|
writeTagStore(rootDir, store);
|
|
631
631
|
}
|
|
632
|
-
var
|
|
632
|
+
var import_node_fs6, import_node_path7, TAGS_FILENAME, GRAPHS_DIR, tagCache;
|
|
633
633
|
var init_tag_store = __esm({
|
|
634
634
|
"src/server/graph/core/tag-store.ts"() {
|
|
635
635
|
"use strict";
|
|
636
|
-
|
|
637
|
-
|
|
636
|
+
import_node_fs6 = require("node:fs");
|
|
637
|
+
import_node_path7 = require("node:path");
|
|
638
638
|
init_atomic_write();
|
|
639
639
|
TAGS_FILENAME = "tags.json";
|
|
640
640
|
GRAPHS_DIR = ".launchsecure/graphs";
|
|
@@ -644,16 +644,16 @@ var init_tag_store = __esm({
|
|
|
644
644
|
|
|
645
645
|
// src/server/graph/core/parse-worker-host.ts
|
|
646
646
|
function resolveWorkerPath() {
|
|
647
|
-
const sibling = (0,
|
|
648
|
-
if ((0,
|
|
649
|
-
const srcSibling = (0,
|
|
650
|
-
if ((0,
|
|
647
|
+
const sibling = (0, import_node_path8.join)(__dirname, "parse-worker-entry.js");
|
|
648
|
+
if ((0, import_node_fs7.existsSync)(sibling)) return sibling;
|
|
649
|
+
const srcSibling = (0, import_node_path8.join)(__dirname, "..", "..", "parse-worker-entry.ts");
|
|
650
|
+
if ((0, import_node_fs7.existsSync)(srcSibling)) return srcSibling;
|
|
651
651
|
throw new Error(
|
|
652
652
|
`parse-worker-entry not found. Build the chart server first (pnpm --filter @launchsecure/launch-kit build:server). Looked at: ${sibling} and ${srcSibling}`
|
|
653
653
|
);
|
|
654
654
|
}
|
|
655
655
|
function runParseInWorker(req) {
|
|
656
|
-
return new Promise((
|
|
656
|
+
return new Promise((resolve7, reject) => {
|
|
657
657
|
let workerPath;
|
|
658
658
|
try {
|
|
659
659
|
workerPath = resolveWorkerPath();
|
|
@@ -672,7 +672,7 @@ function runParseInWorker(req) {
|
|
|
672
672
|
};
|
|
673
673
|
worker.on("message", (reply) => {
|
|
674
674
|
if (reply.ok) {
|
|
675
|
-
finish(() =>
|
|
675
|
+
finish(() => resolve7({ results: reply.results, failedFiles: reply.failedFiles }));
|
|
676
676
|
} else {
|
|
677
677
|
const err2 = new Error(reply.error.message);
|
|
678
678
|
err2.name = reply.error.name;
|
|
@@ -689,12 +689,12 @@ function runParseInWorker(req) {
|
|
|
689
689
|
worker.postMessage(req);
|
|
690
690
|
});
|
|
691
691
|
}
|
|
692
|
-
var
|
|
692
|
+
var import_node_fs7, import_node_path8, import_node_worker_threads;
|
|
693
693
|
var init_parse_worker_host = __esm({
|
|
694
694
|
"src/server/graph/core/parse-worker-host.ts"() {
|
|
695
695
|
"use strict";
|
|
696
|
-
|
|
697
|
-
|
|
696
|
+
import_node_fs7 = require("node:fs");
|
|
697
|
+
import_node_path8 = require("node:path");
|
|
698
698
|
import_node_worker_threads = require("node:worker_threads");
|
|
699
699
|
}
|
|
700
700
|
});
|
|
@@ -752,17 +752,339 @@ function buildEffectsIndex(layerOutputs) {
|
|
|
752
752
|
return idx;
|
|
753
753
|
}
|
|
754
754
|
function writeEffectsIndex(rootDir, idx) {
|
|
755
|
-
const path2 = (0,
|
|
755
|
+
const path2 = (0, import_node_path9.join)(rootDir, LAUNCHSECURE_DIR, "graphs", "effects-index.json");
|
|
756
756
|
atomicWriteFileSync(path2, JSON.stringify(idx, null, 2) + "\n");
|
|
757
757
|
return path2;
|
|
758
758
|
}
|
|
759
|
-
var
|
|
759
|
+
var import_node_path9;
|
|
760
760
|
var init_effects_index = __esm({
|
|
761
761
|
"src/server/graph/core/effects-index.ts"() {
|
|
762
762
|
"use strict";
|
|
763
763
|
init_atomic_write();
|
|
764
|
-
|
|
764
|
+
import_node_path9 = require("node:path");
|
|
765
|
+
init_launch_kit_paths();
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
// src/server/graph/core/context/types.ts
|
|
770
|
+
function higherConfidence(a, b) {
|
|
771
|
+
return CONFIDENCE_RANK[a] >= CONFIDENCE_RANK[b] ? a : b;
|
|
772
|
+
}
|
|
773
|
+
function stateId(token) {
|
|
774
|
+
return `state:${token}`;
|
|
775
|
+
}
|
|
776
|
+
function actionId(key) {
|
|
777
|
+
return `action:${key}`;
|
|
778
|
+
}
|
|
779
|
+
function stateNode(token, label) {
|
|
780
|
+
return { id: stateId(token), type: CONTEXT_NODE_STATE, name: token, label };
|
|
781
|
+
}
|
|
782
|
+
function actionNode(key, name, meta = {}) {
|
|
783
|
+
return { id: actionId(key), type: CONTEXT_NODE_ACTION, name, ...meta };
|
|
784
|
+
}
|
|
785
|
+
function contextEdge(fromActionId, toStateId, type, confidence, origin) {
|
|
786
|
+
return { source: fromActionId, target: toStateId, type, label: `${type} (${origin})`, confidence, origin };
|
|
787
|
+
}
|
|
788
|
+
var CONTEXT_NODE_ACTION, CONTEXT_NODE_STATE, CONFIDENCE_RANK;
|
|
789
|
+
var init_types = __esm({
|
|
790
|
+
"src/server/graph/core/context/types.ts"() {
|
|
791
|
+
"use strict";
|
|
792
|
+
CONTEXT_NODE_ACTION = "action";
|
|
793
|
+
CONTEXT_NODE_STATE = "state";
|
|
794
|
+
CONFIDENCE_RANK = { low: 0, medium: 1, high: 2 };
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
// src/server/graph/core/context/providers/nextjs-prisma.ts
|
|
799
|
+
function isPublic(auth) {
|
|
800
|
+
if (!auth || auth.length === 0) return true;
|
|
801
|
+
return auth.every((a) => a === "public");
|
|
802
|
+
}
|
|
803
|
+
function notFoundEntity(consequence) {
|
|
804
|
+
if (!consequence) return null;
|
|
805
|
+
const m = consequence.match(/notFound\(\s*['"]([A-Za-z0-9_]+)['"]/);
|
|
806
|
+
return m ? m[1] : null;
|
|
807
|
+
}
|
|
808
|
+
var PRODUCING_METHODS, nextjsPrismaContextProvider;
|
|
809
|
+
var init_nextjs_prisma = __esm({
|
|
810
|
+
"src/server/graph/core/context/providers/nextjs-prisma.ts"() {
|
|
811
|
+
"use strict";
|
|
812
|
+
init_types();
|
|
813
|
+
PRODUCING_METHODS = /* @__PURE__ */ new Set(["create", "createMany", "upsert"]);
|
|
814
|
+
nextjsPrismaContextProvider = {
|
|
815
|
+
id: "nextjs-prisma",
|
|
816
|
+
detect(layerOutputs) {
|
|
817
|
+
const api = layerOutputs["api"];
|
|
818
|
+
return !!api && (api.nodes ?? []).some((n) => n.type === "endpoint");
|
|
819
|
+
},
|
|
820
|
+
derive(layerOutputs) {
|
|
821
|
+
const api = layerOutputs["api"];
|
|
822
|
+
const states = /* @__PURE__ */ new Map();
|
|
823
|
+
const actions = [];
|
|
824
|
+
const edges = [];
|
|
825
|
+
const seenEdge = /* @__PURE__ */ new Set();
|
|
826
|
+
const ensureState = (token, label) => {
|
|
827
|
+
const id = stateId(token);
|
|
828
|
+
if (!states.has(id)) states.set(id, stateNode(token, label));
|
|
829
|
+
return id;
|
|
830
|
+
};
|
|
831
|
+
const addEdge = (from, to, type, confidence, origin) => {
|
|
832
|
+
const key = `${from}|${type}|${to}`;
|
|
833
|
+
if (seenEdge.has(key)) return;
|
|
834
|
+
seenEdge.add(key);
|
|
835
|
+
edges.push(contextEdge(from, to, type, confidence, origin));
|
|
836
|
+
};
|
|
837
|
+
const endpoints = (api?.nodes ?? []).filter((n) => n.type === "endpoint");
|
|
838
|
+
for (const ep of endpoints) {
|
|
839
|
+
const aId = actionId(ep.id);
|
|
840
|
+
actions.push(actionNode(ep.id, ep.name, { methods: ep.methods ?? [], endpoint: ep.id }));
|
|
841
|
+
if (!isPublic(ep.auth)) {
|
|
842
|
+
addEdge(aId, ensureState("session", "Authenticated session"), "requires", "high", "auth-wrapper");
|
|
843
|
+
}
|
|
844
|
+
for (const op of ep.db_operations ?? []) {
|
|
845
|
+
const [model, method] = op.split(".");
|
|
846
|
+
if (!model || !PRODUCING_METHODS.has(method ?? "")) continue;
|
|
847
|
+
addEdge(aId, ensureState(model, `${model} record exists`), "produces", "medium", `db:${op}`);
|
|
848
|
+
}
|
|
849
|
+
for (const cond of ep.conditions ?? []) {
|
|
850
|
+
const entity = notFoundEntity(cond.consequence);
|
|
851
|
+
if (!entity) continue;
|
|
852
|
+
const token = entity.toLowerCase();
|
|
853
|
+
addEdge(aId, ensureState(token, `${token} record exists`), "requires", "low", "guard:notFound");
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return { nodes: [...actions, ...states.values()], edges };
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// src/server/graph/core/context/overlay.ts
|
|
863
|
+
function overlayPath(rootDir) {
|
|
864
|
+
return (0, import_node_path10.join)(rootDir, OVERLAY_FILE);
|
|
865
|
+
}
|
|
866
|
+
function loadOverlay(rootDir) {
|
|
867
|
+
const path2 = overlayPath(rootDir);
|
|
868
|
+
if (!(0, import_node_fs8.existsSync)(path2)) return null;
|
|
869
|
+
try {
|
|
870
|
+
return JSON.parse((0, import_node_fs8.readFileSync)(path2, "utf-8"));
|
|
871
|
+
} catch (e) {
|
|
872
|
+
process.stderr.write(`[launch-chart] failed to parse ${OVERLAY_FILE}: ${e.message}
|
|
873
|
+
`);
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function applyOverlayRejections(edges, rootDir) {
|
|
878
|
+
const overlay = loadOverlay(rootDir);
|
|
879
|
+
const rejects = overlay?.reject ?? [];
|
|
880
|
+
if (rejects.length === 0) return edges;
|
|
881
|
+
const rejectKeys = new Set(
|
|
882
|
+
rejects.map((r) => `${actionId(r.action)}|${r.type}|${stateId(r.state)}`)
|
|
883
|
+
);
|
|
884
|
+
return edges.filter((e) => !rejectKeys.has(`${e.source}|${e.type}|${e.target}`));
|
|
885
|
+
}
|
|
886
|
+
var import_node_fs8, import_node_path10, OVERLAY_FILE, overlayProvider;
|
|
887
|
+
var init_overlay = __esm({
|
|
888
|
+
"src/server/graph/core/context/overlay.ts"() {
|
|
889
|
+
"use strict";
|
|
890
|
+
import_node_fs8 = require("node:fs");
|
|
891
|
+
import_node_path10 = require("node:path");
|
|
892
|
+
init_types();
|
|
893
|
+
OVERLAY_FILE = ".launchchart.context.json";
|
|
894
|
+
overlayProvider = {
|
|
895
|
+
id: "overlay",
|
|
896
|
+
detect(_layerOutputs, rootDir) {
|
|
897
|
+
return (0, import_node_fs8.existsSync)(overlayPath(rootDir));
|
|
898
|
+
},
|
|
899
|
+
derive(_layerOutputs, rootDir) {
|
|
900
|
+
const overlay = loadOverlay(rootDir);
|
|
901
|
+
if (!overlay) return { nodes: [], edges: [] };
|
|
902
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
903
|
+
const edges = [];
|
|
904
|
+
const seenEdge = /* @__PURE__ */ new Set();
|
|
905
|
+
const ensureState = (token, label) => {
|
|
906
|
+
const id = stateId(token);
|
|
907
|
+
if (!nodes.has(id)) nodes.set(id, stateNode(token, label ?? `${token} (overlay)`));
|
|
908
|
+
return id;
|
|
909
|
+
};
|
|
910
|
+
const ensureAction = (key, name) => {
|
|
911
|
+
const id = actionId(key);
|
|
912
|
+
if (!nodes.has(id)) nodes.set(id, actionNode(key, name ?? key, { source: "overlay" }));
|
|
913
|
+
return id;
|
|
914
|
+
};
|
|
915
|
+
for (const s of overlay.states ?? []) ensureState(s.token, s.label);
|
|
916
|
+
for (const a of overlay.actions ?? []) ensureAction(a.id, a.name);
|
|
917
|
+
for (const e of overlay.edges ?? []) {
|
|
918
|
+
const aId = ensureAction(e.action);
|
|
919
|
+
const link = (token, type) => {
|
|
920
|
+
const sId = ensureState(token);
|
|
921
|
+
const key = `${aId}|${type}|${sId}`;
|
|
922
|
+
if (seenEdge.has(key)) return;
|
|
923
|
+
seenEdge.add(key);
|
|
924
|
+
edges.push(contextEdge(aId, sId, type, "high", "overlay"));
|
|
925
|
+
};
|
|
926
|
+
for (const token of e.requires ?? []) link(token, "requires");
|
|
927
|
+
for (const token of e.produces ?? []) link(token, "produces");
|
|
928
|
+
}
|
|
929
|
+
return { nodes: [...nodes.values()], edges };
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
// src/server/graph/core/context/registry.ts
|
|
936
|
+
function registerBuiltins2(registry, disabled) {
|
|
937
|
+
const builtins = [nextjsPrismaContextProvider, overlayProvider];
|
|
938
|
+
for (const provider of builtins) {
|
|
939
|
+
if (disabled.has(provider.id)) continue;
|
|
940
|
+
registry.register(provider);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function loadCustomProviders(registry, config, rootDir, disabled) {
|
|
944
|
+
for (const entry of config.context?.providers ?? []) {
|
|
945
|
+
try {
|
|
946
|
+
const absPath = (0, import_node_path11.resolve)(rootDir, entry.path);
|
|
947
|
+
const mod = require(absPath);
|
|
948
|
+
const provider = "default" in mod ? mod.default : mod;
|
|
949
|
+
if (disabled.has(provider.id)) continue;
|
|
950
|
+
registry.register(provider);
|
|
951
|
+
} catch (err2) {
|
|
952
|
+
process.stderr.write(`[launch-chart] failed to load custom context provider from ${entry.path}: ${err2}
|
|
953
|
+
`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function createContextProviderRegistry(config, rootDir) {
|
|
958
|
+
const registry = new ContextProviderRegistry();
|
|
959
|
+
const disabled = new Set(config.context?.disabled ?? []);
|
|
960
|
+
registerBuiltins2(registry, disabled);
|
|
961
|
+
loadCustomProviders(registry, config, rootDir, disabled);
|
|
962
|
+
return registry;
|
|
963
|
+
}
|
|
964
|
+
var import_node_path11, ContextProviderRegistry;
|
|
965
|
+
var init_registry = __esm({
|
|
966
|
+
"src/server/graph/core/context/registry.ts"() {
|
|
967
|
+
"use strict";
|
|
968
|
+
import_node_path11 = require("node:path");
|
|
969
|
+
init_nextjs_prisma();
|
|
970
|
+
init_overlay();
|
|
971
|
+
ContextProviderRegistry = class {
|
|
972
|
+
constructor() {
|
|
973
|
+
this.providers = [];
|
|
974
|
+
this.ids = /* @__PURE__ */ new Set();
|
|
975
|
+
}
|
|
976
|
+
register(provider) {
|
|
977
|
+
if (this.ids.has(provider.id)) {
|
|
978
|
+
throw new Error(`Duplicate context provider id: ${provider.id}`);
|
|
979
|
+
}
|
|
980
|
+
this.ids.add(provider.id);
|
|
981
|
+
this.providers.push(provider);
|
|
982
|
+
}
|
|
983
|
+
getAll() {
|
|
984
|
+
return [...this.providers];
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
// src/server/graph/core/context-map.ts
|
|
991
|
+
function buildContextMap(layerOutputs, rootDir) {
|
|
992
|
+
const generated = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
993
|
+
const config = loadConfig(rootDir);
|
|
994
|
+
const registry = createContextProviderRegistry(config, rootDir);
|
|
995
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
996
|
+
const edgesByKey = /* @__PURE__ */ new Map();
|
|
997
|
+
const providerStats = {};
|
|
998
|
+
const usedProviders = [];
|
|
999
|
+
for (const provider of registry.getAll()) {
|
|
1000
|
+
let detected = false;
|
|
1001
|
+
try {
|
|
1002
|
+
detected = provider.detect(layerOutputs, rootDir);
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
process.stderr.write(`[launch-chart] context provider "${provider.id}" detect failed: ${e.message}
|
|
1005
|
+
`);
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
if (!detected) continue;
|
|
1009
|
+
let contribution;
|
|
1010
|
+
try {
|
|
1011
|
+
contribution = provider.derive(layerOutputs, rootDir);
|
|
1012
|
+
} catch (e) {
|
|
1013
|
+
process.stderr.write(`[launch-chart] context provider "${provider.id}" derive failed: ${e.message}
|
|
1014
|
+
`);
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
usedProviders.push(provider.id);
|
|
1018
|
+
providerStats[provider.id] = { nodes: contribution.nodes.length, edges: contribution.edges.length };
|
|
1019
|
+
for (const node of contribution.nodes) {
|
|
1020
|
+
if (!nodesById.has(node.id)) nodesById.set(node.id, node);
|
|
1021
|
+
}
|
|
1022
|
+
for (const edge of contribution.edges) {
|
|
1023
|
+
const key = `${edge.source}|${edge.type}|${edge.target}`;
|
|
1024
|
+
const existing = edgesByKey.get(key);
|
|
1025
|
+
const edgeConf = edge.confidence ?? "low";
|
|
1026
|
+
if (!existing) {
|
|
1027
|
+
edgesByKey.set(key, { ...edge, providers: [provider.id] });
|
|
1028
|
+
} else {
|
|
1029
|
+
const existingConf = existing.confidence ?? "low";
|
|
1030
|
+
existing.confidence = higherConfidence(existingConf, edgeConf);
|
|
1031
|
+
const provs = existing.providers ?? [];
|
|
1032
|
+
if (!provs.includes(provider.id)) provs.push(provider.id);
|
|
1033
|
+
existing.providers = provs;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const merged = [...edgesByKey.values()];
|
|
1038
|
+
const edges = applyOverlayRejections(merged, rootDir);
|
|
1039
|
+
const rejectedCount = merged.length - edges.length;
|
|
1040
|
+
const flagged = edges.map((e) => ({
|
|
1041
|
+
source: e.source,
|
|
1042
|
+
target: e.target,
|
|
1043
|
+
type: e.type,
|
|
1044
|
+
label: e.label ?? `${e.type}`,
|
|
1045
|
+
confidence: e.confidence ?? "low"
|
|
1046
|
+
}));
|
|
1047
|
+
const nodes = [...nodesById.values()];
|
|
1048
|
+
const stateCount = nodes.filter((n) => n.type === CONTEXT_NODE_STATE).length;
|
|
1049
|
+
return {
|
|
1050
|
+
metadata: {
|
|
1051
|
+
generated,
|
|
1052
|
+
scope: "derived",
|
|
1053
|
+
layer: "context",
|
|
1054
|
+
source: "context-map-builder",
|
|
1055
|
+
derived: true,
|
|
1056
|
+
providers: usedProviders,
|
|
1057
|
+
provider_stats: providerStats,
|
|
1058
|
+
action_count: nodes.length - stateCount,
|
|
1059
|
+
state_count: stateCount,
|
|
1060
|
+
candidate_edge_count: edges.length,
|
|
1061
|
+
rejected_edge_count: rejectedCount,
|
|
1062
|
+
note: "Edges are candidates (see flagged_edges + confidence) from the context providers. Promote via the curation overlay."
|
|
1063
|
+
},
|
|
1064
|
+
nodes,
|
|
1065
|
+
edges,
|
|
1066
|
+
cross_refs: [],
|
|
1067
|
+
contradictions: [],
|
|
1068
|
+
warnings: [],
|
|
1069
|
+
flagged_edges: flagged
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
function writeContextMap(rootDir, output) {
|
|
1073
|
+
const path2 = (0, import_node_path12.join)(rootDir, LAUNCHSECURE_DIR, "graphs", "context.json");
|
|
1074
|
+
atomicWriteFileSync(path2, JSON.stringify(output, null, 2) + "\n");
|
|
1075
|
+
return path2;
|
|
1076
|
+
}
|
|
1077
|
+
var import_node_path12;
|
|
1078
|
+
var init_context_map = __esm({
|
|
1079
|
+
"src/server/graph/core/context-map.ts"() {
|
|
1080
|
+
"use strict";
|
|
1081
|
+
init_atomic_write();
|
|
1082
|
+
import_node_path12 = require("node:path");
|
|
765
1083
|
init_launch_kit_paths();
|
|
1084
|
+
init_config();
|
|
1085
|
+
init_registry();
|
|
1086
|
+
init_overlay();
|
|
1087
|
+
init_types();
|
|
766
1088
|
}
|
|
767
1089
|
});
|
|
768
1090
|
|
|
@@ -781,12 +1103,12 @@ function getFreshnessTracker(rootDir) {
|
|
|
781
1103
|
}
|
|
782
1104
|
return t;
|
|
783
1105
|
}
|
|
784
|
-
var
|
|
1106
|
+
var import_node_fs9, import_node_path13, SCHEMA_VERSION, MAX_PATCHED_FILES_TRACKED, DRIFTING_PATCH_THRESHOLD, HARD_ESCALATION_PATTERNS, FreshnessTracker, trackers;
|
|
785
1107
|
var init_freshness = __esm({
|
|
786
1108
|
"src/server/graph/core/freshness.ts"() {
|
|
787
1109
|
"use strict";
|
|
788
|
-
|
|
789
|
-
|
|
1110
|
+
import_node_fs9 = require("node:fs");
|
|
1111
|
+
import_node_path13 = require("node:path");
|
|
790
1112
|
init_launch_kit_paths();
|
|
791
1113
|
init_atomic_write();
|
|
792
1114
|
SCHEMA_VERSION = 1;
|
|
@@ -803,7 +1125,7 @@ var init_freshness = __esm({
|
|
|
803
1125
|
FreshnessTracker = class {
|
|
804
1126
|
constructor(rootDir) {
|
|
805
1127
|
this.dirty = false;
|
|
806
|
-
this.filePath = (0,
|
|
1128
|
+
this.filePath = (0, import_node_path13.join)(rootDir, LAUNCHSECURE_DIR, "graphs", ".freshness.json");
|
|
807
1129
|
this.snapshot = this.load() ?? {
|
|
808
1130
|
state: "fresh",
|
|
809
1131
|
lastFullRegenAt: null,
|
|
@@ -890,9 +1212,9 @@ var init_freshness = __esm({
|
|
|
890
1212
|
}
|
|
891
1213
|
}
|
|
892
1214
|
load() {
|
|
893
|
-
if (!(0,
|
|
1215
|
+
if (!(0, import_node_fs9.existsSync)(this.filePath)) return null;
|
|
894
1216
|
try {
|
|
895
|
-
const parsed = JSON.parse((0,
|
|
1217
|
+
const parsed = JSON.parse((0, import_node_fs9.readFileSync)(this.filePath, "utf-8"));
|
|
896
1218
|
if (parsed.schemaVersion !== SCHEMA_VERSION) return null;
|
|
897
1219
|
return {
|
|
898
1220
|
state: parsed.state,
|
|
@@ -914,22 +1236,22 @@ var init_freshness = __esm({
|
|
|
914
1236
|
|
|
915
1237
|
// src/server/graph/index.ts
|
|
916
1238
|
function getAvailableLayers(rootDir) {
|
|
917
|
-
const dir = (0,
|
|
918
|
-
if (!(0,
|
|
919
|
-
return (0,
|
|
1239
|
+
const dir = (0, import_node_path14.join)(rootDir, GRAPHS_DIR2);
|
|
1240
|
+
if (!(0, import_node_fs10.existsSync)(dir)) return [];
|
|
1241
|
+
return (0, import_node_fs10.readdirSync)(dir).filter((f) => f.endsWith(".json") && !f.startsWith(".") && !NON_LAYER_GRAPH_FILES.has(f)).map((f) => f.replace(".json", ""));
|
|
920
1242
|
}
|
|
921
1243
|
function graphsDir(rootDir) {
|
|
922
|
-
return (0,
|
|
1244
|
+
return (0, import_node_path14.join)(rootDir, GRAPHS_DIR2);
|
|
923
1245
|
}
|
|
924
1246
|
function graphFilePath(rootDir, layer) {
|
|
925
|
-
return (0,
|
|
1247
|
+
return (0, import_node_path14.join)(graphsDir(rootDir), `${layer}.json`);
|
|
926
1248
|
}
|
|
927
1249
|
function tagsFilePath2(rootDir) {
|
|
928
|
-
return (0,
|
|
1250
|
+
return (0, import_node_path14.join)(graphsDir(rootDir), "tags.json");
|
|
929
1251
|
}
|
|
930
1252
|
function getMtimeMs(filePath) {
|
|
931
|
-
if (!(0,
|
|
932
|
-
return (0,
|
|
1253
|
+
if (!(0, import_node_fs10.existsSync)(filePath)) return 0;
|
|
1254
|
+
return (0, import_node_fs10.statSync)(filePath).mtimeMs;
|
|
933
1255
|
}
|
|
934
1256
|
function invalidateCache(filePath) {
|
|
935
1257
|
graphCache.delete(filePath);
|
|
@@ -968,20 +1290,20 @@ function applyTags(graph, layer, rootDir) {
|
|
|
968
1290
|
}
|
|
969
1291
|
function readGraphRaw(rootDir, layer) {
|
|
970
1292
|
const filePath = graphFilePath(rootDir, layer);
|
|
971
|
-
if (!(0,
|
|
972
|
-
const stat = (0,
|
|
1293
|
+
if (!(0, import_node_fs10.existsSync)(filePath)) return null;
|
|
1294
|
+
const stat = (0, import_node_fs10.statSync)(filePath);
|
|
973
1295
|
const cached = graphCache.get(filePath);
|
|
974
1296
|
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
975
1297
|
return cached.graph;
|
|
976
1298
|
}
|
|
977
|
-
const content = (0,
|
|
1299
|
+
const content = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
|
|
978
1300
|
const graph = JSON.parse(content);
|
|
979
1301
|
graphCache.set(filePath, { mtimeMs: stat.mtimeMs, graph });
|
|
980
1302
|
return graph;
|
|
981
1303
|
}
|
|
982
1304
|
function readGraph(rootDir, layer) {
|
|
983
1305
|
const rawFilePath = graphFilePath(rootDir, layer);
|
|
984
|
-
if (!(0,
|
|
1306
|
+
if (!(0, import_node_fs10.existsSync)(rawFilePath)) return null;
|
|
985
1307
|
const rawMtime = getMtimeMs(rawFilePath);
|
|
986
1308
|
const tagsMtime = getMtimeMs(tagsFilePath2(rootDir));
|
|
987
1309
|
const cacheKey = `${rootDir}:${layer}`;
|
|
@@ -1005,7 +1327,7 @@ function readAllGraphs(rootDir) {
|
|
|
1005
1327
|
}
|
|
1006
1328
|
async function generateGraph(rootDir, layer) {
|
|
1007
1329
|
const dir = graphsDir(rootDir);
|
|
1008
|
-
(0,
|
|
1330
|
+
(0, import_node_fs10.mkdirSync)(dir, { recursive: true });
|
|
1009
1331
|
const { results, failedFiles } = await runParseInWorker({ rootDir, layer });
|
|
1010
1332
|
for (const result of results) {
|
|
1011
1333
|
const filePath = graphFilePath(rootDir, result.layer);
|
|
@@ -1016,13 +1338,13 @@ async function generateGraph(rootDir, layer) {
|
|
|
1016
1338
|
if (!layer) {
|
|
1017
1339
|
const producedLayers = new Set(results.map((r) => r.layer));
|
|
1018
1340
|
try {
|
|
1019
|
-
for (const f of (0,
|
|
1020
|
-
if (!f.endsWith(".json") || f === "tags.json" || f === "effects-index.json") continue;
|
|
1341
|
+
for (const f of (0, import_node_fs10.readdirSync)(dir)) {
|
|
1342
|
+
if (!f.endsWith(".json") || f === "tags.json" || f === "effects-index.json" || f === "context.json") continue;
|
|
1021
1343
|
const layerName = f.replace(/\.json$/, "");
|
|
1022
1344
|
if (producedLayers.has(layerName)) continue;
|
|
1023
|
-
const orphan = (0,
|
|
1345
|
+
const orphan = (0, import_node_path14.join)(dir, f);
|
|
1024
1346
|
try {
|
|
1025
|
-
(0,
|
|
1347
|
+
(0, import_node_fs10.unlinkSync)(orphan);
|
|
1026
1348
|
invalidateCache(orphan);
|
|
1027
1349
|
invalidateTaggedCache(rootDir, layerName);
|
|
1028
1350
|
process.stderr.write(`[launch-chart] removed orphan layer file: ${f} (no parser produced ${layerName} this run)
|
|
@@ -1033,22 +1355,31 @@ async function generateGraph(rootDir, layer) {
|
|
|
1033
1355
|
} catch {
|
|
1034
1356
|
}
|
|
1035
1357
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
if (existing) allLayers[layerName] = existing;
|
|
1046
|
-
}
|
|
1358
|
+
const allLayers = {};
|
|
1359
|
+
for (const r of results) allLayers[r.layer] = r.output;
|
|
1360
|
+
if (layer) {
|
|
1361
|
+
for (const f of (0, import_node_fs10.readdirSync)(dir)) {
|
|
1362
|
+
if (!f.endsWith(".json") || f.startsWith(".") || f === "tags.json" || f === "effects-index.json" || f === "context.json") continue;
|
|
1363
|
+
const layerName = f.replace(/\.json$/, "");
|
|
1364
|
+
if (allLayers[layerName]) continue;
|
|
1365
|
+
const existing = readGraphRaw(rootDir, layerName);
|
|
1366
|
+
if (existing) allLayers[layerName] = existing;
|
|
1047
1367
|
}
|
|
1368
|
+
}
|
|
1369
|
+
try {
|
|
1048
1370
|
const idx = buildEffectsIndex(allLayers);
|
|
1049
1371
|
writeEffectsIndex(rootDir, idx);
|
|
1050
1372
|
} catch (e) {
|
|
1051
1373
|
process.stderr.write(`[launch-chart] effects-index build failed: ${e.message}
|
|
1374
|
+
`);
|
|
1375
|
+
}
|
|
1376
|
+
try {
|
|
1377
|
+
const ctx = buildContextMap(allLayers, rootDir);
|
|
1378
|
+
const ctxPath = writeContextMap(rootDir, ctx);
|
|
1379
|
+
invalidateCache(ctxPath);
|
|
1380
|
+
invalidateTaggedCache(rootDir, "context");
|
|
1381
|
+
} catch (e) {
|
|
1382
|
+
process.stderr.write(`[launch-chart] context-map build failed: ${e.message}
|
|
1052
1383
|
`);
|
|
1053
1384
|
}
|
|
1054
1385
|
try {
|
|
@@ -1065,25 +1396,26 @@ async function generateGraph(rootDir, layer) {
|
|
|
1065
1396
|
return results;
|
|
1066
1397
|
}
|
|
1067
1398
|
function readEffectsIndex(rootDir) {
|
|
1068
|
-
const path2 = (0,
|
|
1069
|
-
if (!(0,
|
|
1399
|
+
const path2 = (0, import_node_path14.join)(rootDir, GRAPHS_DIR2, "effects-index.json");
|
|
1400
|
+
if (!(0, import_node_fs10.existsSync)(path2)) return null;
|
|
1070
1401
|
try {
|
|
1071
|
-
return JSON.parse((0,
|
|
1402
|
+
return JSON.parse((0, import_node_fs10.readFileSync)(path2, "utf-8"));
|
|
1072
1403
|
} catch {
|
|
1073
1404
|
return null;
|
|
1074
1405
|
}
|
|
1075
1406
|
}
|
|
1076
|
-
var
|
|
1407
|
+
var import_node_fs10, import_node_path14, GRAPHS_DIR2, NON_LAYER_GRAPH_FILES, graphCache, taggedCache;
|
|
1077
1408
|
var init_graph = __esm({
|
|
1078
1409
|
"src/server/graph/index.ts"() {
|
|
1079
1410
|
"use strict";
|
|
1080
|
-
|
|
1081
|
-
|
|
1411
|
+
import_node_fs10 = require("node:fs");
|
|
1412
|
+
import_node_path14 = require("node:path");
|
|
1082
1413
|
init_config();
|
|
1083
1414
|
init_tagger_registry();
|
|
1084
1415
|
init_tag_store();
|
|
1085
1416
|
init_parse_worker_host();
|
|
1086
1417
|
init_effects_index();
|
|
1418
|
+
init_context_map();
|
|
1087
1419
|
init_atomic_write();
|
|
1088
1420
|
init_freshness();
|
|
1089
1421
|
init_tag_store();
|
|
@@ -1097,12 +1429,12 @@ var init_graph = __esm({
|
|
|
1097
1429
|
// src/server/graph/core/walk.ts
|
|
1098
1430
|
function walk(dir, exts) {
|
|
1099
1431
|
const results = [];
|
|
1100
|
-
if (!(0,
|
|
1101
|
-
for (const entry of (0,
|
|
1102
|
-
const full = (0,
|
|
1432
|
+
if (!(0, import_node_fs11.existsSync)(dir)) return results;
|
|
1433
|
+
for (const entry of (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true })) {
|
|
1434
|
+
const full = (0, import_node_path15.join)(dir, entry.name);
|
|
1103
1435
|
if (entry.isDirectory()) {
|
|
1104
1436
|
results.push(...walk(full, exts));
|
|
1105
|
-
} else if (exts.includes((0,
|
|
1437
|
+
} else if (exts.includes((0, import_node_path15.extname)(entry.name))) {
|
|
1106
1438
|
results.push(full);
|
|
1107
1439
|
}
|
|
1108
1440
|
}
|
|
@@ -1110,25 +1442,25 @@ function walk(dir, exts) {
|
|
|
1110
1442
|
}
|
|
1111
1443
|
function walkWithIgnore(dir, exts, opts = {}) {
|
|
1112
1444
|
const results = [];
|
|
1113
|
-
if (!(0,
|
|
1445
|
+
if (!(0, import_node_fs11.existsSync)(dir)) return results;
|
|
1114
1446
|
const skip = opts.extraIgnore ? /* @__PURE__ */ new Set([...DEFAULT_IGNORE_DIRS, ...opts.extraIgnore]) : DEFAULT_IGNORE_DIRS;
|
|
1115
|
-
for (const entry of (0,
|
|
1447
|
+
for (const entry of (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true })) {
|
|
1116
1448
|
if (entry.isDirectory()) {
|
|
1117
1449
|
if (entry.name.startsWith(".")) continue;
|
|
1118
1450
|
if (skip.has(entry.name)) continue;
|
|
1119
|
-
results.push(...walkWithIgnore((0,
|
|
1120
|
-
} else if (exts.includes((0,
|
|
1121
|
-
results.push((0,
|
|
1451
|
+
results.push(...walkWithIgnore((0, import_node_path15.join)(dir, entry.name), exts, opts));
|
|
1452
|
+
} else if (exts.includes((0, import_node_path15.extname)(entry.name))) {
|
|
1453
|
+
results.push((0, import_node_path15.join)(dir, entry.name));
|
|
1122
1454
|
}
|
|
1123
1455
|
}
|
|
1124
1456
|
return results;
|
|
1125
1457
|
}
|
|
1126
|
-
var
|
|
1458
|
+
var import_node_fs11, import_node_path15, DEFAULT_IGNORE_DIRS;
|
|
1127
1459
|
var init_walk = __esm({
|
|
1128
1460
|
"src/server/graph/core/walk.ts"() {
|
|
1129
1461
|
"use strict";
|
|
1130
|
-
|
|
1131
|
-
|
|
1462
|
+
import_node_fs11 = require("node:fs");
|
|
1463
|
+
import_node_path15 = require("node:path");
|
|
1132
1464
|
DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
1133
1465
|
"node_modules",
|
|
1134
1466
|
"dist",
|
|
@@ -1141,9 +1473,9 @@ var init_walk = __esm({
|
|
|
1141
1473
|
|
|
1142
1474
|
// src/server/graph/core/resolve-paths.ts
|
|
1143
1475
|
function hasSqlFiles(dir) {
|
|
1144
|
-
if (!(0,
|
|
1476
|
+
if (!(0, import_node_fs12.existsSync)(dir)) return false;
|
|
1145
1477
|
try {
|
|
1146
|
-
return (0,
|
|
1478
|
+
return (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true }).some(
|
|
1147
1479
|
(e) => e.isFile() && e.name.endsWith(".sql")
|
|
1148
1480
|
);
|
|
1149
1481
|
} catch {
|
|
@@ -1151,27 +1483,27 @@ function hasSqlFiles(dir) {
|
|
|
1151
1483
|
}
|
|
1152
1484
|
}
|
|
1153
1485
|
function hasNestedMigrationSql(dir) {
|
|
1154
|
-
if (!(0,
|
|
1486
|
+
if (!(0, import_node_fs12.existsSync)(dir)) return false;
|
|
1155
1487
|
try {
|
|
1156
|
-
return (0,
|
|
1157
|
-
(e) => e.isDirectory() && (0,
|
|
1488
|
+
return (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true }).some(
|
|
1489
|
+
(e) => e.isDirectory() && (0, import_node_fs12.existsSync)((0, import_node_path16.join)(dir, e.name, "migration.sql"))
|
|
1158
1490
|
);
|
|
1159
1491
|
} catch {
|
|
1160
1492
|
return false;
|
|
1161
1493
|
}
|
|
1162
1494
|
}
|
|
1163
1495
|
function resolveDbFromDir(dir) {
|
|
1164
|
-
if (!(0,
|
|
1165
|
-
const schemaPath = (0,
|
|
1166
|
-
if ((0,
|
|
1167
|
-
const migrationsDir2 = (0,
|
|
1496
|
+
if (!(0, import_node_fs12.existsSync)(dir)) return { kind: "none", schemaPath: null, migrationsDir: null };
|
|
1497
|
+
const schemaPath = (0, import_node_path16.join)(dir, "schema.prisma");
|
|
1498
|
+
if ((0, import_node_fs12.existsSync)(schemaPath)) {
|
|
1499
|
+
const migrationsDir2 = (0, import_node_path16.join)(dir, "migrations");
|
|
1168
1500
|
return {
|
|
1169
1501
|
kind: "prisma",
|
|
1170
1502
|
schemaPath,
|
|
1171
|
-
migrationsDir: (0,
|
|
1503
|
+
migrationsDir: (0, import_node_fs12.existsSync)(migrationsDir2) ? migrationsDir2 : null
|
|
1172
1504
|
};
|
|
1173
1505
|
}
|
|
1174
|
-
const migrationsDir = (0,
|
|
1506
|
+
const migrationsDir = (0, import_node_path16.join)(dir, "migrations");
|
|
1175
1507
|
if (hasSqlFiles(migrationsDir) || hasNestedMigrationSql(migrationsDir)) {
|
|
1176
1508
|
return { kind: "sql-migrations", migrationsDir, schemaPath: null };
|
|
1177
1509
|
}
|
|
@@ -1182,33 +1514,33 @@ function resolveDbFromDir(dir) {
|
|
|
1182
1514
|
}
|
|
1183
1515
|
function detectDbConfig(rootDir, config) {
|
|
1184
1516
|
if (config.paths?.dbDir) {
|
|
1185
|
-
return resolveDbFromDir((0,
|
|
1517
|
+
return resolveDbFromDir((0, import_node_path16.join)(rootDir, config.paths.dbDir));
|
|
1186
1518
|
}
|
|
1187
|
-
const candidates = ["prisma", "supabase", "drizzle", (0,
|
|
1519
|
+
const candidates = ["prisma", "supabase", "drizzle", (0, import_node_path16.join)("db", "migrations"), "migrations"];
|
|
1188
1520
|
for (const c of candidates) {
|
|
1189
|
-
const dir = (0,
|
|
1521
|
+
const dir = (0, import_node_path16.join)(rootDir, c);
|
|
1190
1522
|
const resolved = resolveDbFromDir(dir);
|
|
1191
1523
|
if (resolved.kind !== "none") return resolved;
|
|
1192
1524
|
}
|
|
1193
1525
|
return { kind: "none", schemaPath: null, migrationsDir: null };
|
|
1194
1526
|
}
|
|
1195
1527
|
function detectDbDir(rootDir, config, dbConfig) {
|
|
1196
|
-
if (config.paths?.dbDir) return (0,
|
|
1197
|
-
if (dbConfig.kind === "prisma") return (0,
|
|
1528
|
+
if (config.paths?.dbDir) return (0, import_node_path16.join)(rootDir, config.paths.dbDir);
|
|
1529
|
+
if (dbConfig.kind === "prisma") return (0, import_node_path16.dirname)(dbConfig.schemaPath);
|
|
1198
1530
|
if (dbConfig.kind === "sql-migrations") return dbConfig.migrationsDir;
|
|
1199
1531
|
return null;
|
|
1200
1532
|
}
|
|
1201
1533
|
function dirHasTSFiles(dir) {
|
|
1202
|
-
if (!(0,
|
|
1534
|
+
if (!(0, import_node_fs12.existsSync)(dir)) return false;
|
|
1203
1535
|
try {
|
|
1204
1536
|
const stack = [dir];
|
|
1205
1537
|
while (stack.length > 0) {
|
|
1206
1538
|
const cur = stack.pop();
|
|
1207
|
-
const entries = (0,
|
|
1539
|
+
const entries = (0, import_node_fs12.readdirSync)(cur, { withFileTypes: true });
|
|
1208
1540
|
for (const e of entries) {
|
|
1209
1541
|
if (e.isFile() && (e.name.endsWith(".ts") || e.name.endsWith(".tsx"))) return true;
|
|
1210
1542
|
if (e.isDirectory() && !e.name.startsWith(".") && !DEFAULT_IGNORE_DIRS.has(e.name)) {
|
|
1211
|
-
stack.push((0,
|
|
1543
|
+
stack.push((0, import_node_path16.join)(cur, e.name));
|
|
1212
1544
|
}
|
|
1213
1545
|
}
|
|
1214
1546
|
}
|
|
@@ -1217,15 +1549,15 @@ function dirHasTSFiles(dir) {
|
|
|
1217
1549
|
return false;
|
|
1218
1550
|
}
|
|
1219
1551
|
function collectCodeBearingChildren(dir, extraSkip) {
|
|
1220
|
-
if (!(0,
|
|
1552
|
+
if (!(0, import_node_fs12.existsSync)(dir)) return [];
|
|
1221
1553
|
const out = [];
|
|
1222
1554
|
try {
|
|
1223
|
-
for (const entry of (0,
|
|
1555
|
+
for (const entry of (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true })) {
|
|
1224
1556
|
if (!entry.isDirectory()) continue;
|
|
1225
1557
|
if (entry.name.startsWith(".")) continue;
|
|
1226
1558
|
if (NON_SOURCE_DIRS.has(entry.name)) continue;
|
|
1227
1559
|
if (extraSkip?.has(entry.name)) continue;
|
|
1228
|
-
const full = (0,
|
|
1560
|
+
const full = (0, import_node_path16.join)(dir, entry.name);
|
|
1229
1561
|
if (dirHasTSFiles(full)) out.push(full);
|
|
1230
1562
|
}
|
|
1231
1563
|
} catch {
|
|
@@ -1237,7 +1569,7 @@ function detectSrcRoots(rootDir, srcDir, appDir, config) {
|
|
|
1237
1569
|
const roots2 = /* @__PURE__ */ new Set();
|
|
1238
1570
|
roots2.add(appDir);
|
|
1239
1571
|
for (const r of config.paths.srcRoots) {
|
|
1240
|
-
const abs = (0,
|
|
1572
|
+
const abs = (0, import_node_path16.isAbsolute)(r) ? r : (0, import_node_path16.resolve)(rootDir, r);
|
|
1241
1573
|
roots2.add(abs);
|
|
1242
1574
|
}
|
|
1243
1575
|
return [...roots2];
|
|
@@ -1246,7 +1578,7 @@ function detectSrcRoots(rootDir, srcDir, appDir, config) {
|
|
|
1246
1578
|
roots.add(appDir);
|
|
1247
1579
|
for (const c of collectCodeBearingChildren(srcDir)) roots.add(c);
|
|
1248
1580
|
if (srcDir !== rootDir) {
|
|
1249
|
-
const skipSrcWrapper = /* @__PURE__ */ new Set([(0,
|
|
1581
|
+
const skipSrcWrapper = /* @__PURE__ */ new Set([(0, import_node_path16.basename)(srcDir)]);
|
|
1250
1582
|
for (const c of collectCodeBearingChildren(rootDir, skipSrcWrapper)) roots.add(c);
|
|
1251
1583
|
}
|
|
1252
1584
|
return [...roots];
|
|
@@ -1257,10 +1589,10 @@ function detectConventionFiles(rootDir, srcDir) {
|
|
|
1257
1589
|
const dirs = srcDir === rootDir ? [rootDir] : [srcDir, rootDir];
|
|
1258
1590
|
for (const dir of dirs) {
|
|
1259
1591
|
for (const name of CONVENTION_NAMES) {
|
|
1260
|
-
const full = (0,
|
|
1261
|
-
if (!seen.has(full) && (0,
|
|
1592
|
+
const full = (0, import_node_path16.join)(dir, name);
|
|
1593
|
+
if (!seen.has(full) && (0, import_node_fs12.existsSync)(full)) {
|
|
1262
1594
|
try {
|
|
1263
|
-
if ((0,
|
|
1595
|
+
if ((0, import_node_fs12.statSync)(full).isFile()) {
|
|
1264
1596
|
seen.add(full);
|
|
1265
1597
|
out.push(full);
|
|
1266
1598
|
}
|
|
@@ -1275,34 +1607,34 @@ function resolveProjectPaths(rootDir, config) {
|
|
|
1275
1607
|
let srcDir;
|
|
1276
1608
|
let appDir;
|
|
1277
1609
|
if (config.paths?.appDir) {
|
|
1278
|
-
appDir = (0,
|
|
1279
|
-
srcDir = config.paths.srcDir ? (0,
|
|
1610
|
+
appDir = (0, import_node_path16.join)(rootDir, config.paths.appDir);
|
|
1611
|
+
srcDir = config.paths.srcDir ? (0, import_node_path16.join)(rootDir, config.paths.srcDir) : (0, import_node_path16.dirname)(appDir);
|
|
1280
1612
|
} else {
|
|
1281
|
-
const srcApp = (0,
|
|
1282
|
-
const rootApp = (0,
|
|
1283
|
-
if ((0,
|
|
1284
|
-
srcDir = (0,
|
|
1613
|
+
const srcApp = (0, import_node_path16.join)(rootDir, "src", "app");
|
|
1614
|
+
const rootApp = (0, import_node_path16.join)(rootDir, "app");
|
|
1615
|
+
if ((0, import_node_fs12.existsSync)(srcApp)) {
|
|
1616
|
+
srcDir = (0, import_node_path16.join)(rootDir, "src");
|
|
1285
1617
|
appDir = srcApp;
|
|
1286
|
-
} else if ((0,
|
|
1618
|
+
} else if ((0, import_node_fs12.existsSync)(rootApp)) {
|
|
1287
1619
|
srcDir = rootDir;
|
|
1288
1620
|
appDir = rootApp;
|
|
1289
1621
|
} else {
|
|
1290
1622
|
return null;
|
|
1291
1623
|
}
|
|
1292
1624
|
}
|
|
1293
|
-
const apiDir = (0,
|
|
1625
|
+
const apiDir = (0, import_node_path16.join)(appDir, "api");
|
|
1294
1626
|
const dbConfig = detectDbConfig(rootDir, config);
|
|
1295
1627
|
const dbDir = detectDbDir(rootDir, config, dbConfig);
|
|
1296
1628
|
const srcRoots = detectSrcRoots(rootDir, srcDir, appDir, config);
|
|
1297
1629
|
const conventionFiles = detectConventionFiles(rootDir, srcDir);
|
|
1298
1630
|
return { srcDir, appDir, apiDir, dbDir, srcRoots, conventionFiles, dbConfig };
|
|
1299
1631
|
}
|
|
1300
|
-
var
|
|
1632
|
+
var import_node_fs12, import_node_path16, NON_SOURCE_DIRS, CONVENTION_NAMES;
|
|
1301
1633
|
var init_resolve_paths = __esm({
|
|
1302
1634
|
"src/server/graph/core/resolve-paths.ts"() {
|
|
1303
1635
|
"use strict";
|
|
1304
|
-
|
|
1305
|
-
|
|
1636
|
+
import_node_fs12 = require("node:fs");
|
|
1637
|
+
import_node_path16 = require("node:path");
|
|
1306
1638
|
init_walk();
|
|
1307
1639
|
NON_SOURCE_DIRS = /* @__PURE__ */ new Set([
|
|
1308
1640
|
...DEFAULT_IGNORE_DIRS,
|
|
@@ -1401,8 +1733,8 @@ function getQuery(name) {
|
|
|
1401
1733
|
ensureInit();
|
|
1402
1734
|
const cached = queryCache.get(name);
|
|
1403
1735
|
if (cached) return cached;
|
|
1404
|
-
const scmPath = (0,
|
|
1405
|
-
const scm = (0,
|
|
1736
|
+
const scmPath = (0, import_node_path17.join)(queriesDir, `${name}.scm`);
|
|
1737
|
+
const scm = (0, import_node_fs13.readFileSync)(scmPath, "utf-8");
|
|
1406
1738
|
const query = tsxLanguage.query(scm);
|
|
1407
1739
|
queryCache.set(name, query);
|
|
1408
1740
|
return query;
|
|
@@ -1422,13 +1754,13 @@ function parseSource(absPath) {
|
|
|
1422
1754
|
ensureInit();
|
|
1423
1755
|
let content;
|
|
1424
1756
|
try {
|
|
1425
|
-
const stat = (0,
|
|
1757
|
+
const stat = (0, import_node_fs13.statSync)(absPath);
|
|
1426
1758
|
if (stat.size > MAX_PARSEABLE_BYTES) {
|
|
1427
1759
|
process.stderr.write(`[lc-extractor] skipping ${absPath}: ${stat.size} bytes exceeds max ${MAX_PARSEABLE_BYTES}
|
|
1428
1760
|
`);
|
|
1429
1761
|
return null;
|
|
1430
1762
|
}
|
|
1431
|
-
content = (0,
|
|
1763
|
+
content = (0, import_node_fs13.readFileSync)(absPath, "utf-8");
|
|
1432
1764
|
} catch (e) {
|
|
1433
1765
|
process.stderr.write(`[lc-extractor] read failed for ${absPath}: ${e instanceof Error ? e.message : String(e)}
|
|
1434
1766
|
`);
|
|
@@ -2462,18 +2794,18 @@ function collectUiLabels(root) {
|
|
|
2462
2794
|
visit(root);
|
|
2463
2795
|
return out;
|
|
2464
2796
|
}
|
|
2465
|
-
var
|
|
2797
|
+
var import_node_fs13, import_node_path17, tsxLanguage, parserInstance, TreeSitterCtor, initPromise, initialized, queriesDir, queryCache, MAX_PARSEABLE_BYTES, MAX_CONSECUTIVE_PARSE_FAILURES, consecutiveParseFailures, ParseCascadeError, PRISMA_MUTATION_METHODS_BUILTIN, SUPABASE_MUTATION_METHODS_BUILTIN, DB_IDENTIFIERS_FALLBACK, extraDbIdentifiers, extraMutationMethods, INLINE_AUTH_IMPORTS, EXEMPT_NAME_PATTERNS, PROTECT_NAME_PATTERNS, TRUST_AS_PROTECT_KEYS, TIMER_FNS, DOM_METHOD_NAMES, CLASSLIST_METHODS, STORAGE_OBJECTS, HISTORY_METHODS, LOCATION_METHODS, ASSIGN_DOM_PROPS, UI_LABEL_KEYS, UI_LABELS_MAX, NOTE_REGEX, NOTES_MAX;
|
|
2466
2798
|
var init_ts_extractor = __esm({
|
|
2467
2799
|
"src/server/graph/core/ts-extractor.ts"() {
|
|
2468
2800
|
"use strict";
|
|
2469
|
-
|
|
2470
|
-
|
|
2801
|
+
import_node_fs13 = require("node:fs");
|
|
2802
|
+
import_node_path17 = require("node:path");
|
|
2471
2803
|
init_parse_failure_cache();
|
|
2472
2804
|
initialized = false;
|
|
2473
2805
|
queriesDir = (() => {
|
|
2474
|
-
const srcPath = (0,
|
|
2806
|
+
const srcPath = (0, import_node_path17.join)((0, import_node_path17.dirname)(__filename), "..", "queries");
|
|
2475
2807
|
if (require("fs").existsSync(srcPath)) return srcPath;
|
|
2476
|
-
return (0,
|
|
2808
|
+
return (0, import_node_path17.join)((0, import_node_path17.dirname)(__filename), "graph", "queries");
|
|
2477
2809
|
})();
|
|
2478
2810
|
queryCache = /* @__PURE__ */ new Map();
|
|
2479
2811
|
MAX_PARSEABLE_BYTES = 2 * 1024 * 1024;
|
|
@@ -2607,25 +2939,25 @@ var init_ts_extractor = __esm({
|
|
|
2607
2939
|
|
|
2608
2940
|
// src/server/graph/parsers/ts/typescript-project.ts
|
|
2609
2941
|
function toNodeId(srcDir, rootDir, absPath) {
|
|
2610
|
-
const relFromSrc = (0,
|
|
2942
|
+
const relFromSrc = (0, import_node_path18.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
2611
2943
|
if (relFromSrc.startsWith("..")) {
|
|
2612
|
-
return (0,
|
|
2944
|
+
return (0, import_node_path18.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
2613
2945
|
}
|
|
2614
2946
|
return relFromSrc;
|
|
2615
2947
|
}
|
|
2616
2948
|
function resolveImport(srcDir, specifier) {
|
|
2617
2949
|
if (!specifier.startsWith("@/")) return null;
|
|
2618
2950
|
const rel = specifier.slice(2);
|
|
2619
|
-
const base = (0,
|
|
2620
|
-
for (const c of [base, base + ".ts", base + ".tsx", (0,
|
|
2621
|
-
if ((0,
|
|
2951
|
+
const base = (0, import_node_path18.join)(srcDir, rel);
|
|
2952
|
+
for (const c of [base, base + ".ts", base + ".tsx", (0, import_node_path18.join)(base, "index.ts"), (0, import_node_path18.join)(base, "index.tsx")]) {
|
|
2953
|
+
if ((0, import_node_fs14.existsSync)(c) && (0, import_node_fs14.statSync)(c).isFile()) return c;
|
|
2622
2954
|
}
|
|
2623
2955
|
return null;
|
|
2624
2956
|
}
|
|
2625
2957
|
function resolveRelativeImport(fromFile, specifier) {
|
|
2626
|
-
const base = (0,
|
|
2627
|
-
for (const c of [base, base + ".ts", base + ".tsx", (0,
|
|
2628
|
-
if ((0,
|
|
2958
|
+
const base = (0, import_node_path18.join)((0, import_node_path18.dirname)(fromFile), specifier);
|
|
2959
|
+
for (const c of [base, base + ".ts", base + ".tsx", (0, import_node_path18.join)(base, "index.ts"), (0, import_node_path18.join)(base, "index.tsx")]) {
|
|
2960
|
+
if ((0, import_node_fs14.existsSync)(c) && (0, import_node_fs14.statSync)(c).isFile()) return c;
|
|
2629
2961
|
}
|
|
2630
2962
|
return null;
|
|
2631
2963
|
}
|
|
@@ -2646,7 +2978,7 @@ function resolveBarrelMap(barrelAbsPath, parsedByPath, memo, visiting) {
|
|
|
2646
2978
|
const resolved = resolveRelativeImport(barrelAbsPath, re.from);
|
|
2647
2979
|
if (!resolved) continue;
|
|
2648
2980
|
if (re.isWildcard) {
|
|
2649
|
-
const targetBn = (0,
|
|
2981
|
+
const targetBn = (0, import_node_path18.basename)(resolved);
|
|
2650
2982
|
const targetIsBarrel = targetBn === "index.ts" || targetBn === "index.tsx";
|
|
2651
2983
|
if (targetIsBarrel) {
|
|
2652
2984
|
const nested = resolveBarrelMap(resolved, parsedByPath, memo, visiting);
|
|
@@ -2673,12 +3005,12 @@ function buildAllBarrelMaps(srcDir, parsedByPath) {
|
|
|
2673
3005
|
const barrels = /* @__PURE__ */ new Map();
|
|
2674
3006
|
const memo = /* @__PURE__ */ new Map();
|
|
2675
3007
|
for (const [absPath, parsed] of parsedByPath) {
|
|
2676
|
-
const bn = (0,
|
|
3008
|
+
const bn = (0, import_node_path18.basename)(absPath);
|
|
2677
3009
|
if (bn !== "index.ts" && bn !== "index.tsx") continue;
|
|
2678
3010
|
if (parsed.reExports.length === 0) continue;
|
|
2679
3011
|
const map = resolveBarrelMap(absPath, parsedByPath, memo, /* @__PURE__ */ new Set());
|
|
2680
3012
|
if (map.size > 0) {
|
|
2681
|
-
const barrelId = (0,
|
|
3013
|
+
const barrelId = (0, import_node_path18.relative)(srcDir, (0, import_node_path18.dirname)(absPath)).replace(/\\/g, "/");
|
|
2682
3014
|
barrels.set(barrelId, map);
|
|
2683
3015
|
}
|
|
2684
3016
|
}
|
|
@@ -2703,10 +3035,10 @@ function extractRoute(id) {
|
|
|
2703
3035
|
return route || "/";
|
|
2704
3036
|
}
|
|
2705
3037
|
function nameFromFilename(absPath) {
|
|
2706
|
-
return (0,
|
|
3038
|
+
return (0, import_node_path18.basename)(absPath, (0, import_node_path18.extname)(absPath)).replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^(\w)/, (_, c) => c.toUpperCase());
|
|
2707
3039
|
}
|
|
2708
3040
|
function filePathToAppRoute(appDir, absPath) {
|
|
2709
|
-
let route = ("/" + (0,
|
|
3041
|
+
let route = ("/" + (0, import_node_path18.relative)(appDir, absPath).replace(/\\/g, "/")).replace(/\/route\.tsx?$/, "");
|
|
2710
3042
|
route = route.replace(/\/\([^)]+\)/g, "");
|
|
2711
3043
|
route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, "*$1?");
|
|
2712
3044
|
route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
|
|
@@ -2954,7 +3286,7 @@ function generate(rootDir) {
|
|
|
2954
3286
|
const apiNodes = [];
|
|
2955
3287
|
const nodeIdSet = /* @__PURE__ */ new Set();
|
|
2956
3288
|
const routeToNodeId = /* @__PURE__ */ new Map();
|
|
2957
|
-
const fileSet = allDiscovered.filter((f) => !(0,
|
|
3289
|
+
const fileSet = allDiscovered.filter((f) => !(0, import_node_path18.basename)(f).startsWith("index."));
|
|
2958
3290
|
for (const absPath of fileSet) {
|
|
2959
3291
|
const id = toNodeId(srcDir, rootDir, absPath);
|
|
2960
3292
|
const type = classifyType(absPath, id);
|
|
@@ -3114,7 +3446,7 @@ function generate(rootDir) {
|
|
|
3114
3446
|
} catch {
|
|
3115
3447
|
continue;
|
|
3116
3448
|
}
|
|
3117
|
-
const externalId = (0,
|
|
3449
|
+
const externalId = (0, import_node_path18.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
3118
3450
|
const edgesFromThis = [];
|
|
3119
3451
|
const seen = /* @__PURE__ */ new Set();
|
|
3120
3452
|
for (const imp of parsed.imports) {
|
|
@@ -3456,12 +3788,12 @@ function generate(rootDir) {
|
|
|
3456
3788
|
}
|
|
3457
3789
|
return result;
|
|
3458
3790
|
}
|
|
3459
|
-
var
|
|
3791
|
+
var import_node_fs14, import_node_path18, HTTP_METHODS, CLASSIFICATION_TO_LAYER, typescriptProjectParser;
|
|
3460
3792
|
var init_typescript_project = __esm({
|
|
3461
3793
|
"src/server/graph/parsers/ts/typescript-project.ts"() {
|
|
3462
3794
|
"use strict";
|
|
3463
|
-
|
|
3464
|
-
|
|
3795
|
+
import_node_fs14 = require("node:fs");
|
|
3796
|
+
import_node_path18 = require("node:path");
|
|
3465
3797
|
init_config();
|
|
3466
3798
|
init_resolve_paths();
|
|
3467
3799
|
init_walk();
|
|
@@ -3583,7 +3915,7 @@ function parseEnums(content) {
|
|
|
3583
3915
|
}
|
|
3584
3916
|
function detect2(rootDir) {
|
|
3585
3917
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
3586
|
-
return paths?.dbConfig.kind === "prisma" && (0,
|
|
3918
|
+
return paths?.dbConfig.kind === "prisma" && (0, import_node_fs15.existsSync)(paths.dbConfig.schemaPath);
|
|
3587
3919
|
}
|
|
3588
3920
|
function generate2(rootDir) {
|
|
3589
3921
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
@@ -3600,7 +3932,7 @@ function generate2(rootDir) {
|
|
|
3600
3932
|
};
|
|
3601
3933
|
}
|
|
3602
3934
|
const schemaPath = paths.dbConfig.schemaPath;
|
|
3603
|
-
const content = (0,
|
|
3935
|
+
const content = (0, import_node_fs15.readFileSync)(schemaPath, "utf-8");
|
|
3604
3936
|
const { nodes: modelNodes, relations } = parseModels(content);
|
|
3605
3937
|
const enumNodes = parseEnums(content);
|
|
3606
3938
|
const allNodes = [...modelNodes, ...enumNodes];
|
|
@@ -3649,11 +3981,11 @@ function generate2(rootDir) {
|
|
|
3649
3981
|
}
|
|
3650
3982
|
};
|
|
3651
3983
|
}
|
|
3652
|
-
var
|
|
3984
|
+
var import_node_fs15, prismaSchemaParser;
|
|
3653
3985
|
var init_prisma_schema = __esm({
|
|
3654
3986
|
"src/server/graph/parsers/db/prisma-schema.ts"() {
|
|
3655
3987
|
"use strict";
|
|
3656
|
-
|
|
3988
|
+
import_node_fs15 = require("node:fs");
|
|
3657
3989
|
init_config();
|
|
3658
3990
|
init_resolve_paths();
|
|
3659
3991
|
prismaSchemaParser = {
|
|
@@ -3671,15 +4003,15 @@ function pgTypeToPrisma(pgType) {
|
|
|
3671
4003
|
return PG_TO_PRISMA[upper] ?? upper;
|
|
3672
4004
|
}
|
|
3673
4005
|
function discoverMigrationFiles(migrationsDir) {
|
|
3674
|
-
if (!(0,
|
|
4006
|
+
if (!(0, import_node_fs16.existsSync)(migrationsDir)) return [];
|
|
3675
4007
|
const out = [];
|
|
3676
|
-
const entries = (0,
|
|
4008
|
+
const entries = (0, import_node_fs16.readdirSync)(migrationsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
3677
4009
|
for (const entry of entries) {
|
|
3678
4010
|
if (entry.isDirectory()) {
|
|
3679
|
-
const sqlPath = (0,
|
|
3680
|
-
if ((0,
|
|
4011
|
+
const sqlPath = (0, import_node_path19.join)(migrationsDir, entry.name, "migration.sql");
|
|
4012
|
+
if ((0, import_node_fs16.existsSync)(sqlPath)) out.push(sqlPath);
|
|
3681
4013
|
} else if (entry.isFile() && entry.name.endsWith(".sql")) {
|
|
3682
|
-
out.push((0,
|
|
4014
|
+
out.push((0, import_node_path19.join)(migrationsDir, entry.name));
|
|
3683
4015
|
}
|
|
3684
4016
|
}
|
|
3685
4017
|
return out;
|
|
@@ -3699,7 +4031,7 @@ function parseMigrations(migrationsDir, dialect = postgresDialect) {
|
|
|
3699
4031
|
};
|
|
3700
4032
|
if (!migrationsDir) return state;
|
|
3701
4033
|
for (const sqlPath of discoverMigrationFiles(migrationsDir)) {
|
|
3702
|
-
const sql = (0,
|
|
4034
|
+
const sql = (0, import_node_fs16.readFileSync)(sqlPath, "utf-8");
|
|
3703
4035
|
let ast;
|
|
3704
4036
|
try {
|
|
3705
4037
|
ast = dialect.parse(sql);
|
|
@@ -4268,8 +4600,8 @@ function indexIsPrismaUncoverable(idx) {
|
|
|
4268
4600
|
return idx.hasPredicate || idx.hasExpressions || idx.method !== "btree";
|
|
4269
4601
|
}
|
|
4270
4602
|
function loadPrismaState(schemaPath) {
|
|
4271
|
-
if (!schemaPath || !(0,
|
|
4272
|
-
const content = (0,
|
|
4603
|
+
if (!schemaPath || !(0, import_node_fs16.existsSync)(schemaPath)) return null;
|
|
4604
|
+
const content = (0, import_node_fs16.readFileSync)(schemaPath, "utf-8");
|
|
4273
4605
|
const tables = /* @__PURE__ */ new Map();
|
|
4274
4606
|
const enums = /* @__PURE__ */ new Map();
|
|
4275
4607
|
const relations = [];
|
|
@@ -4676,7 +5008,7 @@ function generate3(rootDir) {
|
|
|
4676
5008
|
const migrationFiles = migrationsDir ? discoverMigrationFiles(migrationsDir) : [];
|
|
4677
5009
|
let migrationNodeCount = 0;
|
|
4678
5010
|
for (const sqlPath of migrationFiles) {
|
|
4679
|
-
const sql = (0,
|
|
5011
|
+
const sql = (0, import_node_fs16.readFileSync)(sqlPath, "utf-8");
|
|
4680
5012
|
const name = deriveMigrationName(sqlPath);
|
|
4681
5013
|
let ast;
|
|
4682
5014
|
try {
|
|
@@ -4740,12 +5072,12 @@ function generate3(rootDir) {
|
|
|
4740
5072
|
flagged_edges: flaggedEdges
|
|
4741
5073
|
};
|
|
4742
5074
|
}
|
|
4743
|
-
var
|
|
5075
|
+
var import_node_fs16, import_node_path19, import_pgsql_parser, PG_TO_PRISMA, postgresDialect, sqlMigrationsParser;
|
|
4744
5076
|
var init_sql_migrations = __esm({
|
|
4745
5077
|
"src/server/graph/parsers/db/sql-migrations.ts"() {
|
|
4746
5078
|
"use strict";
|
|
4747
|
-
|
|
4748
|
-
|
|
5079
|
+
import_node_fs16 = require("node:fs");
|
|
5080
|
+
import_node_path19 = require("node:path");
|
|
4749
5081
|
import_pgsql_parser = require("pgsql-parser");
|
|
4750
5082
|
init_config();
|
|
4751
5083
|
init_resolve_paths();
|
|
@@ -5069,18 +5401,18 @@ var init_fetch_resolver = __esm({
|
|
|
5069
5401
|
|
|
5070
5402
|
// src/server/graph/parsers/crosslayer/api-annotations.ts
|
|
5071
5403
|
function toNodeId2(srcDir, rootDir, absPath) {
|
|
5072
|
-
const relFromSrc = (0,
|
|
5404
|
+
const relFromSrc = (0, import_node_path20.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
5073
5405
|
if (relFromSrc.startsWith("..")) {
|
|
5074
|
-
return (0,
|
|
5406
|
+
return (0, import_node_path20.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
5075
5407
|
}
|
|
5076
5408
|
return relFromSrc;
|
|
5077
5409
|
}
|
|
5078
|
-
var
|
|
5410
|
+
var import_node_fs17, import_node_path20, API_ANNOTATION_RE, apiAnnotationsParser;
|
|
5079
5411
|
var init_api_annotations = __esm({
|
|
5080
5412
|
"src/server/graph/parsers/crosslayer/api-annotations.ts"() {
|
|
5081
5413
|
"use strict";
|
|
5082
|
-
|
|
5083
|
-
|
|
5414
|
+
import_node_fs17 = require("node:fs");
|
|
5415
|
+
import_node_path20 = require("node:path");
|
|
5084
5416
|
init_api_route_matching();
|
|
5085
5417
|
init_config();
|
|
5086
5418
|
init_resolve_paths();
|
|
@@ -5127,7 +5459,7 @@ var init_api_annotations = __esm({
|
|
|
5127
5459
|
const flaggedEdges = [];
|
|
5128
5460
|
const seenEdge = /* @__PURE__ */ new Set();
|
|
5129
5461
|
for (const absPath of files) {
|
|
5130
|
-
const content = (0,
|
|
5462
|
+
const content = (0, import_node_fs17.readFileSync)(absPath, "utf-8");
|
|
5131
5463
|
const sourceId = toNodeId2(srcDir, rootDir, absPath);
|
|
5132
5464
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
5133
5465
|
let match;
|
|
@@ -5174,18 +5506,18 @@ var init_api_annotations = __esm({
|
|
|
5174
5506
|
|
|
5175
5507
|
// src/server/graph/parsers/crosslayer/url-literal-scanner.ts
|
|
5176
5508
|
function toNodeId3(srcDir, rootDir, absPath) {
|
|
5177
|
-
const relFromSrc = (0,
|
|
5509
|
+
const relFromSrc = (0, import_node_path21.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
5178
5510
|
if (relFromSrc.startsWith("..")) {
|
|
5179
|
-
return (0,
|
|
5511
|
+
return (0, import_node_path21.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
5180
5512
|
}
|
|
5181
5513
|
return relFromSrc;
|
|
5182
5514
|
}
|
|
5183
|
-
var
|
|
5515
|
+
var import_node_fs18, import_node_path21, URL_LITERAL_RE, urlLiteralScannerParser;
|
|
5184
5516
|
var init_url_literal_scanner = __esm({
|
|
5185
5517
|
"src/server/graph/parsers/crosslayer/url-literal-scanner.ts"() {
|
|
5186
5518
|
"use strict";
|
|
5187
|
-
|
|
5188
|
-
|
|
5519
|
+
import_node_fs18 = require("node:fs");
|
|
5520
|
+
import_node_path21 = require("node:path");
|
|
5189
5521
|
init_api_route_matching();
|
|
5190
5522
|
init_config();
|
|
5191
5523
|
init_resolve_paths();
|
|
@@ -5231,7 +5563,7 @@ var init_url_literal_scanner = __esm({
|
|
|
5231
5563
|
for (const absPath of files) {
|
|
5232
5564
|
const sourceId = toNodeId3(srcDir, rootDir, absPath);
|
|
5233
5565
|
if (!uiNodeIds.has(sourceId)) continue;
|
|
5234
|
-
const content = (0,
|
|
5566
|
+
const content = (0, import_node_fs18.readFileSync)(absPath, "utf-8");
|
|
5235
5567
|
let match;
|
|
5236
5568
|
URL_LITERAL_RE.lastIndex = 0;
|
|
5237
5569
|
while ((match = URL_LITERAL_RE.exec(content)) !== null) {
|
|
@@ -5296,21 +5628,21 @@ function extractEnumValues(rootDir) {
|
|
|
5296
5628
|
const schemaPaths = [];
|
|
5297
5629
|
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath) {
|
|
5298
5630
|
schemaPaths.push(paths.dbConfig.schemaPath);
|
|
5299
|
-
schemaPaths.push((0,
|
|
5631
|
+
schemaPaths.push((0, import_node_path22.join)((0, import_node_path22.dirname)(paths.dbConfig.schemaPath), "schema"));
|
|
5300
5632
|
} else {
|
|
5301
|
-
schemaPaths.push((0,
|
|
5302
|
-
schemaPaths.push((0,
|
|
5633
|
+
schemaPaths.push((0, import_node_path22.join)(rootDir, "prisma", "schema.prisma"));
|
|
5634
|
+
schemaPaths.push((0, import_node_path22.join)(rootDir, "prisma", "schema"));
|
|
5303
5635
|
}
|
|
5304
5636
|
let content = "";
|
|
5305
5637
|
for (const p of schemaPaths) {
|
|
5306
|
-
if ((0,
|
|
5638
|
+
if ((0, import_node_fs19.existsSync)(p)) {
|
|
5307
5639
|
try {
|
|
5308
|
-
const stat = (0,
|
|
5640
|
+
const stat = (0, import_node_fs19.statSync)(p);
|
|
5309
5641
|
if (stat.isFile()) {
|
|
5310
|
-
content = (0,
|
|
5642
|
+
content = (0, import_node_fs19.readFileSync)(p, "utf-8");
|
|
5311
5643
|
} else if (stat.isDirectory()) {
|
|
5312
|
-
const files = (0,
|
|
5313
|
-
content = files.map((f) => (0,
|
|
5644
|
+
const files = (0, import_node_fs19.readdirSync)(p).filter((f) => f.endsWith(".prisma"));
|
|
5645
|
+
content = files.map((f) => (0, import_node_fs19.readFileSync)((0, import_node_path22.join)(p, f), "utf-8")).join("\n");
|
|
5314
5646
|
}
|
|
5315
5647
|
} catch {
|
|
5316
5648
|
continue;
|
|
@@ -5468,27 +5800,27 @@ function extractSeedData(rootDir) {
|
|
|
5468
5800
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
5469
5801
|
const candidates = [];
|
|
5470
5802
|
if (paths?.dbDir) {
|
|
5471
|
-
candidates.push((0,
|
|
5472
|
-
candidates.push((0,
|
|
5803
|
+
candidates.push((0, import_node_path22.join)(paths.dbDir, "seed.ts"));
|
|
5804
|
+
candidates.push((0, import_node_path22.join)(paths.dbDir, "seed.js"));
|
|
5473
5805
|
} else {
|
|
5474
|
-
candidates.push((0,
|
|
5475
|
-
candidates.push((0,
|
|
5806
|
+
candidates.push((0, import_node_path22.join)(rootDir, "prisma", "seed.ts"));
|
|
5807
|
+
candidates.push((0, import_node_path22.join)(rootDir, "prisma", "seed.js"));
|
|
5476
5808
|
}
|
|
5477
|
-
const baseRoots = paths?.srcRoots ?? [(0,
|
|
5809
|
+
const baseRoots = paths?.srcRoots ?? [(0, import_node_path22.join)(rootDir, "src")];
|
|
5478
5810
|
for (const root of baseRoots) {
|
|
5479
|
-
candidates.push((0,
|
|
5811
|
+
candidates.push((0, import_node_path22.join)(root, "server", "lib", "system-tags.ts"));
|
|
5480
5812
|
}
|
|
5481
5813
|
const seedFiles = candidates.filter((p) => {
|
|
5482
5814
|
try {
|
|
5483
|
-
return (0,
|
|
5815
|
+
return (0, import_node_fs19.existsSync)(p) && (0, import_node_fs19.statSync)(p).isFile();
|
|
5484
5816
|
} catch {
|
|
5485
5817
|
return false;
|
|
5486
5818
|
}
|
|
5487
5819
|
});
|
|
5488
5820
|
const useTreeSitter = tryLoadTreeSitter();
|
|
5489
5821
|
for (const filePath of seedFiles) {
|
|
5490
|
-
const content = (0,
|
|
5491
|
-
const relPath = (0,
|
|
5822
|
+
const content = (0, import_node_fs19.readFileSync)(filePath, "utf-8");
|
|
5823
|
+
const relPath = (0, import_node_path22.relative)(rootDir, filePath);
|
|
5492
5824
|
const seeded = detectSeededArrays(content, relPath);
|
|
5493
5825
|
let astRoot = null;
|
|
5494
5826
|
if (useTreeSitter && parseCode) {
|
|
@@ -5582,11 +5914,11 @@ function extractSeedData(rootDir) {
|
|
|
5582
5914
|
return { nodes, edges };
|
|
5583
5915
|
}
|
|
5584
5916
|
function walkDir(dir, exts) {
|
|
5585
|
-
if (!(0,
|
|
5917
|
+
if (!(0, import_node_fs19.existsSync)(dir)) return [];
|
|
5586
5918
|
const results = [];
|
|
5587
|
-
for (const entry of (0,
|
|
5919
|
+
for (const entry of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
|
|
5588
5920
|
if (entry.name === "node_modules" || entry.name === ".next" || entry.name === "dist") continue;
|
|
5589
|
-
const full = (0,
|
|
5921
|
+
const full = (0, import_node_path22.join)(dir, entry.name);
|
|
5590
5922
|
if (entry.isDirectory()) results.push(...walkDir(full, exts));
|
|
5591
5923
|
else if (exts.some((ext) => entry.name.endsWith(ext))) results.push(full);
|
|
5592
5924
|
}
|
|
@@ -5595,7 +5927,7 @@ function walkDir(dir, exts) {
|
|
|
5595
5927
|
function extractConstants(rootDir) {
|
|
5596
5928
|
const nodes = [];
|
|
5597
5929
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
5598
|
-
const roots = paths?.srcRoots ?? [(0,
|
|
5930
|
+
const roots = paths?.srcRoots ?? [(0, import_node_path22.join)(rootDir, "src")];
|
|
5599
5931
|
const seenFile = /* @__PURE__ */ new Set();
|
|
5600
5932
|
const allFiles = [];
|
|
5601
5933
|
for (const root of roots) {
|
|
@@ -5608,8 +5940,8 @@ function extractConstants(rootDir) {
|
|
|
5608
5940
|
}
|
|
5609
5941
|
if (allFiles.length === 0) return { nodes };
|
|
5610
5942
|
for (const filePath of allFiles) {
|
|
5611
|
-
const content = (0,
|
|
5612
|
-
const relPath = (0,
|
|
5943
|
+
const content = (0, import_node_fs19.readFileSync)(filePath, "utf-8");
|
|
5944
|
+
const relPath = (0, import_node_path22.relative)(rootDir, filePath);
|
|
5613
5945
|
const constArrayRe = /export\s+const\s+([A-Z][A-Z_0-9]+)\s*(?::[^=]+)?\s*=\s*\[/g;
|
|
5614
5946
|
let cm;
|
|
5615
5947
|
while ((cm = constArrayRe.exec(content)) !== null) {
|
|
@@ -5643,13 +5975,13 @@ function extractConstants(rootDir) {
|
|
|
5643
5975
|
}
|
|
5644
5976
|
function detect4(rootDir) {
|
|
5645
5977
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
5646
|
-
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath && (0,
|
|
5978
|
+
if (paths?.dbConfig.kind === "prisma" && paths.dbConfig.schemaPath && (0, import_node_fs19.existsSync)(paths.dbConfig.schemaPath)) {
|
|
5647
5979
|
return true;
|
|
5648
5980
|
}
|
|
5649
5981
|
if (paths?.dbDir) {
|
|
5650
|
-
if ((0,
|
|
5982
|
+
if ((0, import_node_fs19.existsSync)((0, import_node_path22.join)(paths.dbDir, "seed.ts")) || (0, import_node_fs19.existsSync)((0, import_node_path22.join)(paths.dbDir, "seed.js"))) return true;
|
|
5651
5983
|
}
|
|
5652
|
-
return (0,
|
|
5984
|
+
return (0, import_node_fs19.existsSync)((0, import_node_path22.join)(rootDir, "prisma", "schema.prisma")) || (0, import_node_fs19.existsSync)((0, import_node_path22.join)(rootDir, "prisma", "seed.ts"));
|
|
5653
5985
|
}
|
|
5654
5986
|
function generate4(rootDir) {
|
|
5655
5987
|
const enumResult = extractEnumValues(rootDir);
|
|
@@ -5716,12 +6048,12 @@ function generate4(rootDir) {
|
|
|
5716
6048
|
}
|
|
5717
6049
|
};
|
|
5718
6050
|
}
|
|
5719
|
-
var
|
|
6051
|
+
var import_node_fs19, import_node_path22, parseCode, SHARED_MODELS, DB_MODELS, staticValuesParser;
|
|
5720
6052
|
var init_static_values = __esm({
|
|
5721
6053
|
"src/server/graph/parsers/static/static-values.ts"() {
|
|
5722
6054
|
"use strict";
|
|
5723
|
-
|
|
5724
|
-
|
|
6055
|
+
import_node_fs19 = require("node:fs");
|
|
6056
|
+
import_node_path22 = require("node:path");
|
|
5725
6057
|
init_config();
|
|
5726
6058
|
init_resolve_paths();
|
|
5727
6059
|
parseCode = null;
|
|
@@ -5821,12 +6153,12 @@ function collectStaticRefsRegex(content, valueLookup, allValues) {
|
|
|
5821
6153
|
}
|
|
5822
6154
|
return refs;
|
|
5823
6155
|
}
|
|
5824
|
-
var
|
|
6156
|
+
var import_node_fs20, import_node_path23, MIN_VALUE_LENGTH, SKIP_VALUES, staticRefScannerParser;
|
|
5825
6157
|
var init_static_ref_scanner = __esm({
|
|
5826
6158
|
"src/server/graph/parsers/crosslayer/static-ref-scanner.ts"() {
|
|
5827
6159
|
"use strict";
|
|
5828
|
-
|
|
5829
|
-
|
|
6160
|
+
import_node_fs20 = require("node:fs");
|
|
6161
|
+
import_node_path23 = require("node:path");
|
|
5830
6162
|
init_config();
|
|
5831
6163
|
init_resolve_paths();
|
|
5832
6164
|
init_walk();
|
|
@@ -5919,11 +6251,11 @@ var init_static_ref_scanner = __esm({
|
|
|
5919
6251
|
const seen = /* @__PURE__ */ new Set();
|
|
5920
6252
|
let filesScanned = 0;
|
|
5921
6253
|
for (const absPath of files) {
|
|
5922
|
-
const relFromSrc = (0,
|
|
5923
|
-
const sourceId = relFromSrc.startsWith("..") ? (0,
|
|
6254
|
+
const relFromSrc = (0, import_node_path23.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
6255
|
+
const sourceId = relFromSrc.startsWith("..") ? (0, import_node_path23.relative)(rootDir, absPath).replace(/\\/g, "/") : relFromSrc;
|
|
5924
6256
|
const sourceLayer = uiNodeIds.has(sourceId) ? "ui" : apiNodeIds.has(sourceId) ? "api" : null;
|
|
5925
6257
|
if (!sourceLayer) continue;
|
|
5926
|
-
const content = (0,
|
|
6258
|
+
const content = (0, import_node_fs20.readFileSync)(absPath, "utf-8");
|
|
5927
6259
|
filesScanned++;
|
|
5928
6260
|
let fileRefs;
|
|
5929
6261
|
if (parseCode2) {
|
|
@@ -5968,9 +6300,9 @@ var init_static_ref_scanner = __esm({
|
|
|
5968
6300
|
|
|
5969
6301
|
// src/server/graph/parsers/crosslayer/middleware-gates.ts
|
|
5970
6302
|
function toNodeId4(srcDir, rootDir, absPath) {
|
|
5971
|
-
const relFromSrc = (0,
|
|
6303
|
+
const relFromSrc = (0, import_node_path24.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
5972
6304
|
if (relFromSrc.startsWith("..")) {
|
|
5973
|
-
return (0,
|
|
6305
|
+
return (0, import_node_path24.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
5974
6306
|
}
|
|
5975
6307
|
return relFromSrc;
|
|
5976
6308
|
}
|
|
@@ -5990,11 +6322,11 @@ function collectTargets(apiOutput, uiOutput) {
|
|
|
5990
6322
|
}
|
|
5991
6323
|
return out;
|
|
5992
6324
|
}
|
|
5993
|
-
var
|
|
6325
|
+
var import_node_path24, middlewareGatesParser;
|
|
5994
6326
|
var init_middleware_gates = __esm({
|
|
5995
6327
|
"src/server/graph/parsers/crosslayer/middleware-gates.ts"() {
|
|
5996
6328
|
"use strict";
|
|
5997
|
-
|
|
6329
|
+
import_node_path24 = require("node:path");
|
|
5998
6330
|
init_ts_extractor();
|
|
5999
6331
|
init_config();
|
|
6000
6332
|
init_resolve_paths();
|
|
@@ -6215,7 +6547,7 @@ var init_call_resolver = __esm({
|
|
|
6215
6547
|
function isMultiLayerParser(p) {
|
|
6216
6548
|
return "layers" in p && Array.isArray(p.layers);
|
|
6217
6549
|
}
|
|
6218
|
-
function
|
|
6550
|
+
function registerBuiltins3(registry, disabled) {
|
|
6219
6551
|
const builtins = [
|
|
6220
6552
|
typescriptProjectParser,
|
|
6221
6553
|
prismaSchemaParser,
|
|
@@ -6236,7 +6568,7 @@ function registerBuiltins2(registry, disabled) {
|
|
|
6236
6568
|
function loadCustomParsers(registry, config, rootDir, disabled) {
|
|
6237
6569
|
for (const entry of config.parsers?.custom ?? []) {
|
|
6238
6570
|
try {
|
|
6239
|
-
const absPath = (0,
|
|
6571
|
+
const absPath = (0, import_node_path25.resolve)(rootDir, entry.path);
|
|
6240
6572
|
const mod = require(absPath);
|
|
6241
6573
|
const parser = "default" in mod ? mod.default : mod;
|
|
6242
6574
|
if (disabled.has(parser.id)) continue;
|
|
@@ -6259,15 +6591,15 @@ function loadCustomParsers(registry, config, rootDir, disabled) {
|
|
|
6259
6591
|
function createRegistry(config, rootDir) {
|
|
6260
6592
|
const registry = new ParserRegistry();
|
|
6261
6593
|
const disabled = new Set(config.parsers?.disabled ?? []);
|
|
6262
|
-
|
|
6594
|
+
registerBuiltins3(registry, disabled);
|
|
6263
6595
|
loadCustomParsers(registry, config, rootDir, disabled);
|
|
6264
6596
|
return registry;
|
|
6265
6597
|
}
|
|
6266
|
-
var
|
|
6598
|
+
var import_node_path25, ParserRegistry;
|
|
6267
6599
|
var init_parser_registry = __esm({
|
|
6268
6600
|
"src/server/graph/core/parser-registry.ts"() {
|
|
6269
6601
|
"use strict";
|
|
6270
|
-
|
|
6602
|
+
import_node_path25 = require("node:path");
|
|
6271
6603
|
init_typescript_project();
|
|
6272
6604
|
init_prisma_schema();
|
|
6273
6605
|
init_sql_migrations();
|
|
@@ -6356,9 +6688,9 @@ function collectSourceFiles(rootDir) {
|
|
|
6356
6688
|
return out;
|
|
6357
6689
|
}
|
|
6358
6690
|
function toNodeId5(rootDir, srcDir, absPath) {
|
|
6359
|
-
const relFromSrc = (0,
|
|
6691
|
+
const relFromSrc = (0, import_node_path26.relative)(srcDir, absPath).replace(/\\/g, "/");
|
|
6360
6692
|
if (relFromSrc.startsWith("..")) {
|
|
6361
|
-
return (0,
|
|
6693
|
+
return (0, import_node_path26.relative)(rootDir, absPath).replace(/\\/g, "/");
|
|
6362
6694
|
}
|
|
6363
6695
|
return relFromSrc;
|
|
6364
6696
|
}
|
|
@@ -6446,18 +6778,18 @@ function collectDeclaredEnvKeys(rootDir) {
|
|
|
6446
6778
|
const files = [];
|
|
6447
6779
|
let entries = [];
|
|
6448
6780
|
try {
|
|
6449
|
-
entries = (0,
|
|
6781
|
+
entries = (0, import_node_fs21.readdirSync)(rootDir);
|
|
6450
6782
|
} catch {
|
|
6451
6783
|
return { keys, files };
|
|
6452
6784
|
}
|
|
6453
6785
|
for (const name of entries) {
|
|
6454
6786
|
if (!name.startsWith(".env")) continue;
|
|
6455
|
-
const abs = (0,
|
|
6456
|
-
if (!(0,
|
|
6787
|
+
const abs = (0, import_node_path26.join)(rootDir, name);
|
|
6788
|
+
if (!(0, import_node_fs21.existsSync)(abs)) continue;
|
|
6457
6789
|
files.push(name);
|
|
6458
6790
|
let content = "";
|
|
6459
6791
|
try {
|
|
6460
|
-
content = (0,
|
|
6792
|
+
content = (0, import_node_fs21.readFileSync)(abs, "utf-8");
|
|
6461
6793
|
} catch {
|
|
6462
6794
|
continue;
|
|
6463
6795
|
}
|
|
@@ -6611,12 +6943,12 @@ function checkHardcodedUrlFallback(rootDir, core) {
|
|
|
6611
6943
|
}
|
|
6612
6944
|
return core.buildReport("security", "hardcoded_url_fallback", findings);
|
|
6613
6945
|
}
|
|
6614
|
-
var
|
|
6946
|
+
var import_node_fs21, import_node_path26, SECRET_KEY_RE, SECRET_KEY_ALLOWLIST, FRAMEWORK_ENV_KEYS;
|
|
6615
6947
|
var init_audit_security = __esm({
|
|
6616
6948
|
"src/server/graph/core/audit-security.ts"() {
|
|
6617
6949
|
"use strict";
|
|
6618
|
-
|
|
6619
|
-
|
|
6950
|
+
import_node_fs21 = require("node:fs");
|
|
6951
|
+
import_node_path26 = require("node:path");
|
|
6620
6952
|
init_ts_extractor();
|
|
6621
6953
|
init_config();
|
|
6622
6954
|
init_resolve_paths();
|
|
@@ -6678,10 +7010,10 @@ var init_audit_security = __esm({
|
|
|
6678
7010
|
|
|
6679
7011
|
// src/server/graph/core/audit-core.ts
|
|
6680
7012
|
function readGraphFile(rootDir, layer) {
|
|
6681
|
-
const filePath = (0,
|
|
6682
|
-
if (!(0,
|
|
7013
|
+
const filePath = (0, import_node_path27.join)(rootDir, LAUNCHSECURE_DIR, "graphs", `${layer}.json`);
|
|
7014
|
+
if (!(0, import_node_fs22.existsSync)(filePath)) return null;
|
|
6683
7015
|
try {
|
|
6684
|
-
return JSON.parse((0,
|
|
7016
|
+
return JSON.parse((0, import_node_fs22.readFileSync)(filePath, "utf-8"));
|
|
6685
7017
|
} catch {
|
|
6686
7018
|
return null;
|
|
6687
7019
|
}
|
|
@@ -6723,20 +7055,20 @@ function checkUnprotectedRoutes(rootDir) {
|
|
|
6723
7055
|
const findings = [];
|
|
6724
7056
|
const api = readGraphFile(rootDir, "api");
|
|
6725
7057
|
if (!api) return buildSkipped("api", "unprotected_routes", "no api graph");
|
|
6726
|
-
const routePermsPath = (0,
|
|
6727
|
-
if (!(0,
|
|
7058
|
+
const routePermsPath = (0, import_node_path27.join)(rootDir, "src", "config", "route-permissions.ts");
|
|
7059
|
+
if (!(0, import_node_fs22.existsSync)(routePermsPath)) {
|
|
6728
7060
|
return buildSkipped(
|
|
6729
7061
|
"api",
|
|
6730
7062
|
"unprotected_routes",
|
|
6731
7063
|
`no src/config/route-permissions.ts \u2014 this check needs a centralized ROUTE_PERMISSIONS inventory to compare endpoints against`
|
|
6732
7064
|
);
|
|
6733
7065
|
}
|
|
6734
|
-
const routePermsContent = (0,
|
|
7066
|
+
const routePermsContent = (0, import_node_fs22.readFileSync)(routePermsPath, "utf-8");
|
|
6735
7067
|
const registeredRoutes = /* @__PURE__ */ new Set();
|
|
6736
7068
|
const routeEntryRe = /path:\s*'([^']+)'/g;
|
|
6737
|
-
let
|
|
6738
|
-
while ((
|
|
6739
|
-
registeredRoutes.add(
|
|
7069
|
+
let rm2;
|
|
7070
|
+
while ((rm2 = routeEntryRe.exec(routePermsContent)) !== null) {
|
|
7071
|
+
registeredRoutes.add(rm2[1].replace(/:(\w+)/g, "[$1]"));
|
|
6740
7072
|
}
|
|
6741
7073
|
for (const node of api.nodes) {
|
|
6742
7074
|
if (node.type !== "endpoint") continue;
|
|
@@ -6816,15 +7148,15 @@ function checkUnenforcedPermissions(rootDir) {
|
|
|
6816
7148
|
`no seed_permission nodes \u2014 this project either has no seed permissions or hasn't tagged them in seed.ts`
|
|
6817
7149
|
);
|
|
6818
7150
|
}
|
|
6819
|
-
const routePermsPath = (0,
|
|
6820
|
-
if (!(0,
|
|
7151
|
+
const routePermsPath = (0, import_node_path27.join)(rootDir, "src", "config", "route-permissions.ts");
|
|
7152
|
+
if (!(0, import_node_fs22.existsSync)(routePermsPath)) {
|
|
6821
7153
|
return buildSkipped(
|
|
6822
7154
|
"static",
|
|
6823
7155
|
"unenforced_permissions",
|
|
6824
7156
|
`no src/config/route-permissions.ts to compare seed permissions against`
|
|
6825
7157
|
);
|
|
6826
7158
|
}
|
|
6827
|
-
const routePermsContent = (0,
|
|
7159
|
+
const routePermsContent = (0, import_node_fs22.readFileSync)(routePermsPath, "utf-8");
|
|
6828
7160
|
for (const perm of permissions) {
|
|
6829
7161
|
const regex = new RegExp(`permission:\\s*['"]${perm.key}['"]`);
|
|
6830
7162
|
if (!regex.test(routePermsContent)) {
|
|
@@ -6860,9 +7192,9 @@ function checkHardcodedValues(rootDir) {
|
|
|
6860
7192
|
const seen = /* @__PURE__ */ new Set();
|
|
6861
7193
|
for (const node of api.nodes) {
|
|
6862
7194
|
if (node.type !== "endpoint") continue;
|
|
6863
|
-
const filePath = (0,
|
|
6864
|
-
if (!(0,
|
|
6865
|
-
const content = (0,
|
|
7195
|
+
const filePath = (0, import_node_path27.join)(rootDir, "src", node.id);
|
|
7196
|
+
if (!(0, import_node_fs22.existsSync)(filePath)) continue;
|
|
7197
|
+
const content = (0, import_node_fs22.readFileSync)(filePath, "utf-8");
|
|
6866
7198
|
let m;
|
|
6867
7199
|
allCapsRe.lastIndex = 0;
|
|
6868
7200
|
while ((m = allCapsRe.exec(content)) !== null) {
|
|
@@ -6981,12 +7313,12 @@ function formatAsPrompt(reports) {
|
|
|
6981
7313
|
if (lines.length === 0) return "No audit findings.";
|
|
6982
7314
|
return lines.join("\n");
|
|
6983
7315
|
}
|
|
6984
|
-
var
|
|
7316
|
+
var import_node_fs22, import_node_path27, CHECKS;
|
|
6985
7317
|
var init_audit_core = __esm({
|
|
6986
7318
|
"src/server/graph/core/audit-core.ts"() {
|
|
6987
7319
|
"use strict";
|
|
6988
|
-
|
|
6989
|
-
|
|
7320
|
+
import_node_fs22 = require("node:fs");
|
|
7321
|
+
import_node_path27 = require("node:path");
|
|
6990
7322
|
init_launch_kit_paths();
|
|
6991
7323
|
init_audit_security();
|
|
6992
7324
|
CHECKS = {
|
|
@@ -7025,16 +7357,16 @@ function randomPort() {
|
|
|
7025
7357
|
function findProjectRoot(startDir) {
|
|
7026
7358
|
let dir = startDir;
|
|
7027
7359
|
for (let i = 0; i < 8; i++) {
|
|
7028
|
-
const graphsDir2 =
|
|
7029
|
-
if (
|
|
7030
|
-
const parent =
|
|
7360
|
+
const graphsDir2 = import_node_path28.default.join(dir, LAUNCHSECURE_DIR, "graphs");
|
|
7361
|
+
if (import_node_fs23.default.existsSync(import_node_path28.default.join(graphsDir2, "ui.json")) || import_node_fs23.default.existsSync(import_node_path28.default.join(graphsDir2, "api.json")) || import_node_fs23.default.existsSync(import_node_path28.default.join(graphsDir2, "db.json"))) return dir;
|
|
7362
|
+
const parent = import_node_path28.default.dirname(dir);
|
|
7031
7363
|
if (parent === dir) break;
|
|
7032
7364
|
dir = parent;
|
|
7033
7365
|
}
|
|
7034
7366
|
dir = startDir;
|
|
7035
7367
|
for (let i = 0; i < 8; i++) {
|
|
7036
|
-
if (
|
|
7037
|
-
const parent =
|
|
7368
|
+
if (import_node_fs23.default.existsSync(import_node_path28.default.join(dir, ".git"))) return dir;
|
|
7369
|
+
const parent = import_node_path28.default.dirname(dir);
|
|
7038
7370
|
if (parent === dir) break;
|
|
7039
7371
|
dir = parent;
|
|
7040
7372
|
}
|
|
@@ -7043,7 +7375,7 @@ function findProjectRoot(startDir) {
|
|
|
7043
7375
|
function resolveRequestRoot(url, monorepoRoot, projects) {
|
|
7044
7376
|
const projectParam = url.searchParams.get("project");
|
|
7045
7377
|
if (!projectParam || projects.length === 0) return monorepoRoot;
|
|
7046
|
-
const resolved =
|
|
7378
|
+
const resolved = import_node_path28.default.resolve(monorepoRoot, projectParam);
|
|
7047
7379
|
if (!resolved.startsWith(monorepoRoot)) {
|
|
7048
7380
|
throw new Error("Project path outside monorepo root");
|
|
7049
7381
|
}
|
|
@@ -7094,16 +7426,16 @@ async function buildMergedGraph(root) {
|
|
|
7094
7426
|
};
|
|
7095
7427
|
}
|
|
7096
7428
|
function serveStatic(res, filePath) {
|
|
7097
|
-
if (!
|
|
7098
|
-
const ext =
|
|
7429
|
+
if (!import_node_fs23.default.existsSync(filePath) || !import_node_fs23.default.statSync(filePath).isFile()) return false;
|
|
7430
|
+
const ext = import_node_path28.default.extname(filePath).toLowerCase();
|
|
7099
7431
|
const mime = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
7100
7432
|
res.writeHead(200, { "Content-Type": mime, "Cache-Control": "no-cache" });
|
|
7101
|
-
|
|
7433
|
+
import_node_fs23.default.createReadStream(filePath).pipe(res);
|
|
7102
7434
|
return true;
|
|
7103
7435
|
}
|
|
7104
7436
|
function serveIndex(res, clientDir) {
|
|
7105
|
-
const indexPath =
|
|
7106
|
-
if (!
|
|
7437
|
+
const indexPath = import_node_path28.default.join(clientDir, "index.html");
|
|
7438
|
+
if (!import_node_fs23.default.existsSync(indexPath)) {
|
|
7107
7439
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
7108
7440
|
res.end(`LaunchChart client bundle not found at ${clientDir}. Run 'npm run build:chart-client'.`);
|
|
7109
7441
|
return;
|
|
@@ -7111,14 +7443,14 @@ function serveIndex(res, clientDir) {
|
|
|
7111
7443
|
serveStatic(res, indexPath);
|
|
7112
7444
|
}
|
|
7113
7445
|
function tryListen(server, port) {
|
|
7114
|
-
return new Promise((
|
|
7446
|
+
return new Promise((resolve7, reject) => {
|
|
7115
7447
|
const onError = (err2) => {
|
|
7116
7448
|
server.off("listening", onListening);
|
|
7117
7449
|
reject(err2);
|
|
7118
7450
|
};
|
|
7119
7451
|
const onListening = () => {
|
|
7120
7452
|
server.off("error", onError);
|
|
7121
|
-
|
|
7453
|
+
resolve7(port);
|
|
7122
7454
|
};
|
|
7123
7455
|
server.once("error", onError);
|
|
7124
7456
|
server.once("listening", onListening);
|
|
@@ -7155,7 +7487,7 @@ async function startChartServer(opts = {}) {
|
|
|
7155
7487
|
}
|
|
7156
7488
|
return { port: existing.port, url: existing.url };
|
|
7157
7489
|
}
|
|
7158
|
-
const clientDir = opts.clientDir ??
|
|
7490
|
+
const clientDir = opts.clientDir ?? import_node_path28.default.join(__dirname, "..", "chart-client");
|
|
7159
7491
|
const rootConfig = loadConfig(projectRoot);
|
|
7160
7492
|
const projects = rootConfig.projects ?? [];
|
|
7161
7493
|
const server = import_node_http.default.createServer((req, res) => {
|
|
@@ -7171,11 +7503,11 @@ async function startChartServer(opts = {}) {
|
|
|
7171
7503
|
}
|
|
7172
7504
|
if (req.method === "GET" && url2.pathname === "/api/projects") {
|
|
7173
7505
|
const projectList = projects.length > 0 ? projects.map((p) => {
|
|
7174
|
-
const absRoot =
|
|
7175
|
-
const hasGraphs =
|
|
7176
|
-
const hasNextConfig =
|
|
7506
|
+
const absRoot = import_node_path28.default.resolve(projectRoot, p.root);
|
|
7507
|
+
const hasGraphs = import_node_fs23.default.existsSync(import_node_path28.default.join(absRoot, LAUNCHSECURE_DIR, "graphs"));
|
|
7508
|
+
const hasNextConfig = import_node_fs23.default.existsSync(import_node_path28.default.join(absRoot, "next.config.ts")) || import_node_fs23.default.existsSync(import_node_path28.default.join(absRoot, "next.config.js")) || import_node_fs23.default.existsSync(import_node_path28.default.join(absRoot, "next.config.mjs"));
|
|
7177
7509
|
return { name: p.name, root: p.root, hasGraphs, hasNextConfig };
|
|
7178
|
-
}) : [{ name:
|
|
7510
|
+
}) : [{ name: import_node_path28.default.basename(projectRoot), root: ".", hasGraphs: import_node_fs23.default.existsSync(import_node_path28.default.join(projectRoot, LAUNCHSECURE_DIR, "graphs")), hasNextConfig: true }];
|
|
7179
7511
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7180
7512
|
res.end(JSON.stringify({ projects: projectList, monorepoRoot: projectRoot }));
|
|
7181
7513
|
return;
|
|
@@ -7221,20 +7553,20 @@ async function startChartServer(opts = {}) {
|
|
|
7221
7553
|
}
|
|
7222
7554
|
if (req.method === "GET" && url2.pathname === "/api/file-content") {
|
|
7223
7555
|
const relPath = url2.searchParams.get("path");
|
|
7224
|
-
if (!relPath || relPath.includes("..") ||
|
|
7556
|
+
if (!relPath || relPath.includes("..") || import_node_path28.default.isAbsolute(relPath)) {
|
|
7225
7557
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7226
7558
|
res.end(JSON.stringify({ error: "Invalid path" }));
|
|
7227
7559
|
return;
|
|
7228
7560
|
}
|
|
7229
|
-
const filePath =
|
|
7230
|
-
if (!filePath.startsWith(reqRoot) || !
|
|
7561
|
+
const filePath = import_node_path28.default.join(reqRoot, relPath);
|
|
7562
|
+
if (!filePath.startsWith(reqRoot) || !import_node_fs23.default.existsSync(filePath) || !import_node_fs23.default.statSync(filePath).isFile()) {
|
|
7231
7563
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
7232
7564
|
res.end(JSON.stringify({ error: "File not found" }));
|
|
7233
7565
|
return;
|
|
7234
7566
|
}
|
|
7235
|
-
const ext =
|
|
7567
|
+
const ext = import_node_path28.default.extname(filePath).toLowerCase();
|
|
7236
7568
|
const langMap = { ".ts": "typescript", ".tsx": "tsx", ".js": "javascript", ".jsx": "jsx", ".prisma": "prisma", ".json": "json", ".css": "css" };
|
|
7237
|
-
const content =
|
|
7569
|
+
const content = import_node_fs23.default.readFileSync(filePath, "utf-8");
|
|
7238
7570
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7239
7571
|
res.end(JSON.stringify({ content, language: langMap[ext] ?? "text", path: relPath }));
|
|
7240
7572
|
return;
|
|
@@ -7289,8 +7621,8 @@ async function startChartServer(opts = {}) {
|
|
|
7289
7621
|
const newConfig = JSON.parse(body);
|
|
7290
7622
|
const existingConfig = loadConfig(reqRoot);
|
|
7291
7623
|
const merged = { ...existingConfig, ...newConfig };
|
|
7292
|
-
const configPath =
|
|
7293
|
-
|
|
7624
|
+
const configPath = import_node_path28.default.join(reqRoot, LAUNCHCHART_CONFIG_FILE);
|
|
7625
|
+
import_node_fs23.default.writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
7294
7626
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7295
7627
|
res.end(JSON.stringify({ ok: true }));
|
|
7296
7628
|
} catch (err2) {
|
|
@@ -7323,8 +7655,8 @@ async function startChartServer(opts = {}) {
|
|
|
7323
7655
|
const taggerConfig = JSON.parse(body);
|
|
7324
7656
|
const config2 = loadConfig(reqRoot);
|
|
7325
7657
|
config2.taggers = taggerConfig;
|
|
7326
|
-
const configPath =
|
|
7327
|
-
|
|
7658
|
+
const configPath = import_node_path28.default.join(reqRoot, LAUNCHCHART_CONFIG_FILE);
|
|
7659
|
+
import_node_fs23.default.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
7328
7660
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7329
7661
|
res.end(JSON.stringify({ ok: true }));
|
|
7330
7662
|
} catch (err2) {
|
|
@@ -7394,7 +7726,7 @@ async function startChartServer(opts = {}) {
|
|
|
7394
7726
|
dbDir: !!config2.paths?.dbDir,
|
|
7395
7727
|
srcRoots: !!(config2.paths?.srcRoots && config2.paths.srcRoots.length > 0)
|
|
7396
7728
|
};
|
|
7397
|
-
const relFromRoot = (abs) =>
|
|
7729
|
+
const relFromRoot = (abs) => import_node_path28.default.relative(reqRoot, abs) || ".";
|
|
7398
7730
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7399
7731
|
res.end(JSON.stringify({
|
|
7400
7732
|
projectRoot: reqRoot,
|
|
@@ -7416,19 +7748,19 @@ async function startChartServer(opts = {}) {
|
|
|
7416
7748
|
}
|
|
7417
7749
|
if (req.method === "GET" && url2.pathname === "/api/browse-dir") {
|
|
7418
7750
|
const browsePath = url2.searchParams.get("path") || projectRoot;
|
|
7419
|
-
const abs =
|
|
7420
|
-
const twoUp =
|
|
7751
|
+
const abs = import_node_path28.default.resolve(browsePath);
|
|
7752
|
+
const twoUp = import_node_path28.default.resolve(projectRoot, "..", "..");
|
|
7421
7753
|
if (!abs.startsWith(twoUp)) {
|
|
7422
7754
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
7423
7755
|
res.end(JSON.stringify({ ok: false, error: "Path outside allowed range" }));
|
|
7424
7756
|
return;
|
|
7425
7757
|
}
|
|
7426
7758
|
try {
|
|
7427
|
-
const entries =
|
|
7759
|
+
const entries = import_node_fs23.default.readdirSync(abs, { withFileTypes: true });
|
|
7428
7760
|
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();
|
|
7429
|
-
const parent = abs !== twoUp ?
|
|
7761
|
+
const parent = abs !== twoUp ? import_node_path28.default.dirname(abs) : null;
|
|
7430
7762
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
7431
|
-
res.end(JSON.stringify({ current: abs, parent, dirs, relative:
|
|
7763
|
+
res.end(JSON.stringify({ current: abs, parent, dirs, relative: import_node_path28.default.relative(projectRoot, abs) || "." }));
|
|
7432
7764
|
} catch (err2) {
|
|
7433
7765
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
7434
7766
|
res.end(JSON.stringify({ ok: false, error: String(err2) }));
|
|
@@ -7454,8 +7786,8 @@ async function startChartServer(opts = {}) {
|
|
|
7454
7786
|
const { projects: newProjects } = JSON.parse(body);
|
|
7455
7787
|
const config2 = loadConfig(projectRoot);
|
|
7456
7788
|
config2.projects = newProjects.length > 0 ? newProjects : void 0;
|
|
7457
|
-
const configPath =
|
|
7458
|
-
|
|
7789
|
+
const configPath = import_node_path28.default.join(projectRoot, LAUNCHCHART_CONFIG_FILE);
|
|
7790
|
+
import_node_fs23.default.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
7459
7791
|
projects.length = 0;
|
|
7460
7792
|
if (config2.projects) projects.push(...config2.projects);
|
|
7461
7793
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -7468,7 +7800,7 @@ async function startChartServer(opts = {}) {
|
|
|
7468
7800
|
return;
|
|
7469
7801
|
}
|
|
7470
7802
|
if (url2.pathname !== "/") {
|
|
7471
|
-
const staticPath =
|
|
7803
|
+
const staticPath = import_node_path28.default.join(clientDir, url2.pathname);
|
|
7472
7804
|
if (serveStatic(res, staticPath)) return;
|
|
7473
7805
|
}
|
|
7474
7806
|
serveIndex(res, clientDir);
|
|
@@ -7528,13 +7860,13 @@ function runServeCli(argv) {
|
|
|
7528
7860
|
process.exit(1);
|
|
7529
7861
|
});
|
|
7530
7862
|
}
|
|
7531
|
-
var import_node_http,
|
|
7863
|
+
var import_node_http, import_node_fs23, import_node_path28, MAX_PORT_SCAN, MIME_TYPES;
|
|
7532
7864
|
var init_chart_serve = __esm({
|
|
7533
7865
|
"src/server/chart-serve.ts"() {
|
|
7534
7866
|
"use strict";
|
|
7535
7867
|
import_node_http = __toESM(require("node:http"));
|
|
7536
|
-
|
|
7537
|
-
|
|
7868
|
+
import_node_fs23 = __toESM(require("node:fs"));
|
|
7869
|
+
import_node_path28 = __toESM(require("node:path"));
|
|
7538
7870
|
init_launch_kit_paths();
|
|
7539
7871
|
init_graph();
|
|
7540
7872
|
init_freshness();
|
|
@@ -7563,9 +7895,9 @@ function emptyRegistry() {
|
|
|
7563
7895
|
return { version: 1, worktrees: {} };
|
|
7564
7896
|
}
|
|
7565
7897
|
function readRegistry() {
|
|
7566
|
-
if (!(0,
|
|
7898
|
+
if (!(0, import_node_fs24.existsSync)(REGISTRY_PATH)) return emptyRegistry();
|
|
7567
7899
|
try {
|
|
7568
|
-
const parsed = JSON.parse((0,
|
|
7900
|
+
const parsed = JSON.parse((0, import_node_fs24.readFileSync)(REGISTRY_PATH, "utf-8"));
|
|
7569
7901
|
if (parsed?.version === 1 && parsed.worktrees && typeof parsed.worktrees === "object") {
|
|
7570
7902
|
return parsed;
|
|
7571
7903
|
}
|
|
@@ -7576,17 +7908,17 @@ function readRegistry() {
|
|
|
7576
7908
|
function listWorktrees() {
|
|
7577
7909
|
return Object.values(readRegistry().worktrees);
|
|
7578
7910
|
}
|
|
7579
|
-
var
|
|
7580
|
-
var
|
|
7911
|
+
var import_node_fs24, import_node_os3, import_node_path29, REGISTRY_DIR, REGISTRY_PATH, LOCK_PATH;
|
|
7912
|
+
var init_registry2 = __esm({
|
|
7581
7913
|
"src/server/orbit/registry.ts"() {
|
|
7582
7914
|
"use strict";
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7915
|
+
import_node_fs24 = require("node:fs");
|
|
7916
|
+
import_node_os3 = require("node:os");
|
|
7917
|
+
import_node_path29 = require("node:path");
|
|
7586
7918
|
init_launch_kit_paths();
|
|
7587
|
-
REGISTRY_DIR = (0,
|
|
7588
|
-
REGISTRY_PATH = (0,
|
|
7589
|
-
LOCK_PATH = (0,
|
|
7919
|
+
REGISTRY_DIR = (0, import_node_path29.join)((0, import_node_os3.homedir)(), LAUNCHSECURE_DIR, "orbit");
|
|
7920
|
+
REGISTRY_PATH = (0, import_node_path29.join)(REGISTRY_DIR, "state.json");
|
|
7921
|
+
LOCK_PATH = (0, import_node_path29.join)(REGISTRY_DIR, "state.json.lock");
|
|
7590
7922
|
}
|
|
7591
7923
|
});
|
|
7592
7924
|
|
|
@@ -7606,7 +7938,7 @@ function resolveWorktreeRoot(slug, monorepoRoot) {
|
|
|
7606
7938
|
function resolveWorktreeOrProjectRoot(args, monorepoRoot) {
|
|
7607
7939
|
const projectRoot = typeof args.project_root === "string" ? args.project_root.trim() : "";
|
|
7608
7940
|
if (projectRoot) {
|
|
7609
|
-
return (0,
|
|
7941
|
+
return (0, import_node_path30.isAbsolute)(projectRoot) ? projectRoot : (0, import_node_path30.resolve)(monorepoRoot, projectRoot);
|
|
7610
7942
|
}
|
|
7611
7943
|
const worktree = typeof args.worktree === "string" ? args.worktree.trim() : "";
|
|
7612
7944
|
if (worktree) {
|
|
@@ -7614,12 +7946,12 @@ function resolveWorktreeOrProjectRoot(args, monorepoRoot) {
|
|
|
7614
7946
|
}
|
|
7615
7947
|
return null;
|
|
7616
7948
|
}
|
|
7617
|
-
var
|
|
7949
|
+
var import_node_path30, WORKTREE_PARAM_DESCRIPTION, PROJECT_ROOT_PARAM_DESCRIPTION;
|
|
7618
7950
|
var init_worktree = __esm({
|
|
7619
7951
|
"src/server/lib/worktree.ts"() {
|
|
7620
7952
|
"use strict";
|
|
7621
|
-
|
|
7622
|
-
|
|
7953
|
+
import_node_path30 = require("node:path");
|
|
7954
|
+
init_registry2();
|
|
7623
7955
|
WORKTREE_PARAM_DESCRIPTION = "Optional orbit worktree slug (from `launch-orbit create`). Resolves to the worktree's path via the orbit registry (~/.launchsecure/orbit/state.json). Lets you query a worktree's state from a Claude Code session pinned to the main repo. Superseded by `project_root`.";
|
|
7624
7956
|
PROJECT_ROOT_PARAM_DESCRIPTION = "Optional explicit project root. Accepts an absolute path or a path relative to the monorepo root. Escape hatch when `worktree` doesn't fit. Takes precedence over all other root args.";
|
|
7625
7957
|
}
|
|
@@ -7632,7 +7964,7 @@ function listProjects(monorepoRoot) {
|
|
|
7632
7964
|
return entries.map((p) => ({
|
|
7633
7965
|
name: p.name,
|
|
7634
7966
|
root: p.root,
|
|
7635
|
-
absoluteRoot: (0,
|
|
7967
|
+
absoluteRoot: (0, import_node_path31.resolve)(monorepoRoot, p.root)
|
|
7636
7968
|
}));
|
|
7637
7969
|
}
|
|
7638
7970
|
function resolveProject(name, projects) {
|
|
@@ -7660,11 +7992,11 @@ function resolveRequestRoot2(args, monorepoRoot) {
|
|
|
7660
7992
|
const project = typeof args.project === "string" ? args.project : void 0;
|
|
7661
7993
|
return resolveProjectRoot(project, monorepoRoot);
|
|
7662
7994
|
}
|
|
7663
|
-
var
|
|
7995
|
+
var import_node_path31, WORKTREE_PARAM_DESCRIPTION2, PROJECT_ROOT_PARAM_DESCRIPTION2, PROJECT_PARAM_DESCRIPTION;
|
|
7664
7996
|
var init_projects = __esm({
|
|
7665
7997
|
"src/server/graph/core/projects.ts"() {
|
|
7666
7998
|
"use strict";
|
|
7667
|
-
|
|
7999
|
+
import_node_path31 = require("node:path");
|
|
7668
8000
|
init_worktree();
|
|
7669
8001
|
init_config();
|
|
7670
8002
|
WORKTREE_PARAM_DESCRIPTION2 = WORKTREE_PARAM_DESCRIPTION;
|
|
@@ -7804,13 +8136,247 @@ var init_graph_cli = __esm({
|
|
|
7804
8136
|
}
|
|
7805
8137
|
});
|
|
7806
8138
|
|
|
8139
|
+
// src/server/graph/core/context/snippet.ts
|
|
8140
|
+
function toExportName(name) {
|
|
8141
|
+
const words = name.replace(/^action:/, "").replace(/^state:/, "").split(/[^A-Za-z0-9]+/).filter(Boolean).filter((w) => w.toLowerCase() !== "api");
|
|
8142
|
+
if (words.length === 0) return "snippet";
|
|
8143
|
+
const camel = words.map((w, i) => i === 0 ? w.charAt(0).toLowerCase() + w.slice(1) : w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
8144
|
+
return /^[0-9]/.test(camel) ? `s${camel}` : camel;
|
|
8145
|
+
}
|
|
8146
|
+
function scaffoldSnippet(input) {
|
|
8147
|
+
const exportName = toExportName(input.name || input.nodeId);
|
|
8148
|
+
const arr = (xs) => `[${xs.map((x) => `'${x}'`).join(", ")}]`;
|
|
8149
|
+
const code = `import { type Page } from '@playwright/test';
|
|
8150
|
+
|
|
8151
|
+
/**
|
|
8152
|
+
* Snippet: ${input.name}
|
|
8153
|
+
* Scaffolded by launch-chart \`scaffold_snippet\` \u2014 conforms to the Snippet shape.
|
|
8154
|
+
* requires/produces are derived from the context graph. TODO(you):
|
|
8155
|
+
* 1. fill \`params\` from the endpoint's request/validation schema;
|
|
8156
|
+
* 2. implement \`run()\` with real Playwright steps (navigate, fill, click, assert).
|
|
8157
|
+
*/
|
|
8158
|
+
export const ${exportName} = {
|
|
8159
|
+
id: '${input.nodeId}',
|
|
8160
|
+
requires: ${arr(input.requires)},
|
|
8161
|
+
produces: ${arr(input.produces)},
|
|
8162
|
+
params: [], // TODO: e.g. ['name', 'color'] \u2014 from the endpoint schema
|
|
8163
|
+
async run(page: Page, params: Record<string, unknown>) {
|
|
8164
|
+
// TODO: implement with Playwright.
|
|
8165
|
+
// e.g. await page.goto('/...'); await page.fill('#...', params.x); await page.click('text=Save');
|
|
8166
|
+
},
|
|
8167
|
+
};
|
|
8168
|
+
`;
|
|
8169
|
+
return { exportName, code };
|
|
8170
|
+
}
|
|
8171
|
+
var init_snippet = __esm({
|
|
8172
|
+
"src/server/graph/core/context/snippet.ts"() {
|
|
8173
|
+
"use strict";
|
|
8174
|
+
}
|
|
8175
|
+
});
|
|
8176
|
+
|
|
8177
|
+
// src/server/graph/core/context/snippet-registry.ts
|
|
8178
|
+
function parseStringArray(src, key) {
|
|
8179
|
+
const m = src.match(new RegExp(`${key}\\s*:\\s*\\[([^\\]]*)\\]`));
|
|
8180
|
+
if (!m) return [];
|
|
8181
|
+
return [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]);
|
|
8182
|
+
}
|
|
8183
|
+
function parseSnippetFile(content, file) {
|
|
8184
|
+
const idM = content.match(/\bid\s*:\s*['"]([^'"]+)['"]/);
|
|
8185
|
+
if (!idM) return null;
|
|
8186
|
+
const exportM = content.match(/export\s+const\s+(\w+)/);
|
|
8187
|
+
return {
|
|
8188
|
+
id: idM[1],
|
|
8189
|
+
exportName: exportM ? exportM[1] : "default",
|
|
8190
|
+
file,
|
|
8191
|
+
requires: parseStringArray(content, "requires"),
|
|
8192
|
+
produces: parseStringArray(content, "produces"),
|
|
8193
|
+
params: parseStringArray(content, "params")
|
|
8194
|
+
};
|
|
8195
|
+
}
|
|
8196
|
+
function loadSnippetIndex(rootDir, snippetsDir = DEFAULT_SNIPPETS_DIR) {
|
|
8197
|
+
const dir = (0, import_node_path32.join)(rootDir, snippetsDir);
|
|
8198
|
+
const index = /* @__PURE__ */ new Map();
|
|
8199
|
+
if (!(0, import_node_fs25.existsSync)(dir)) return index;
|
|
8200
|
+
for (const f of (0, import_node_fs25.readdirSync)(dir)) {
|
|
8201
|
+
if (!f.endsWith(".ts") && !f.endsWith(".tsx")) continue;
|
|
8202
|
+
const full = (0, import_node_path32.join)(dir, f);
|
|
8203
|
+
try {
|
|
8204
|
+
if (!(0, import_node_fs25.statSync)(full).isFile()) continue;
|
|
8205
|
+
const entry = parseSnippetFile((0, import_node_fs25.readFileSync)(full, "utf-8"), (0, import_node_path32.join)(snippetsDir, f));
|
|
8206
|
+
if (entry) index.set(entry.id, entry);
|
|
8207
|
+
} catch {
|
|
8208
|
+
}
|
|
8209
|
+
}
|
|
8210
|
+
return index;
|
|
8211
|
+
}
|
|
8212
|
+
var import_node_fs25, import_node_path32, DEFAULT_SNIPPETS_DIR;
|
|
8213
|
+
var init_snippet_registry = __esm({
|
|
8214
|
+
"src/server/graph/core/context/snippet-registry.ts"() {
|
|
8215
|
+
"use strict";
|
|
8216
|
+
import_node_fs25 = require("node:fs");
|
|
8217
|
+
import_node_path32 = require("node:path");
|
|
8218
|
+
DEFAULT_SNIPPETS_DIR = "tests/snippets";
|
|
8219
|
+
}
|
|
8220
|
+
});
|
|
8221
|
+
|
|
8222
|
+
// src/server/graph/core/context/test-plan.ts
|
|
8223
|
+
function buildTestPlan(graph, targetId, snippets) {
|
|
8224
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
8225
|
+
for (const n of graph.nodes) nodesById.set(n.id, n);
|
|
8226
|
+
const requiresOf = /* @__PURE__ */ new Map();
|
|
8227
|
+
const producesOf = /* @__PURE__ */ new Map();
|
|
8228
|
+
const producersOf = /* @__PURE__ */ new Map();
|
|
8229
|
+
for (const e of graph.edges) {
|
|
8230
|
+
if (e.type === "requires") {
|
|
8231
|
+
(requiresOf.get(e.source) ?? requiresOf.set(e.source, /* @__PURE__ */ new Set()).get(e.source)).add(e.target);
|
|
8232
|
+
} else if (e.type === "produces") {
|
|
8233
|
+
(producesOf.get(e.source) ?? producesOf.set(e.source, /* @__PURE__ */ new Set()).get(e.source)).add(e.target);
|
|
8234
|
+
(producersOf.get(e.target) ?? producersOf.set(e.target, /* @__PURE__ */ new Set()).get(e.target)).add(e.source);
|
|
8235
|
+
}
|
|
8236
|
+
}
|
|
8237
|
+
const pickProducer = (producers) => {
|
|
8238
|
+
const withSnippet = producers.filter((p) => snippets.has(p)).sort();
|
|
8239
|
+
return withSnippet.length ? withSnippet[0] : [...producers].sort()[0];
|
|
8240
|
+
};
|
|
8241
|
+
const order = [];
|
|
8242
|
+
const done = /* @__PURE__ */ new Set();
|
|
8243
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
8244
|
+
const gaps = [];
|
|
8245
|
+
const allRequired = /* @__PURE__ */ new Set();
|
|
8246
|
+
const visit = (actionId2) => {
|
|
8247
|
+
if (done.has(actionId2)) return;
|
|
8248
|
+
if (visiting.has(actionId2)) {
|
|
8249
|
+
gaps.push({ type: "cycle", action: actionId2, detail: `dependency cycle through ${actionId2}` });
|
|
8250
|
+
return;
|
|
8251
|
+
}
|
|
8252
|
+
visiting.add(actionId2);
|
|
8253
|
+
for (const state of requiresOf.get(actionId2) ?? []) {
|
|
8254
|
+
allRequired.add(state);
|
|
8255
|
+
const producers = [...producersOf.get(state) ?? []];
|
|
8256
|
+
if (producers.length === 0) {
|
|
8257
|
+
gaps.push({
|
|
8258
|
+
type: "no_producer",
|
|
8259
|
+
state: stripState(state),
|
|
8260
|
+
action: actionId2,
|
|
8261
|
+
detail: `${actionId2} requires "${stripState(state)}" but no action produces it`
|
|
8262
|
+
});
|
|
8263
|
+
continue;
|
|
8264
|
+
}
|
|
8265
|
+
visit(pickProducer(producers));
|
|
8266
|
+
}
|
|
8267
|
+
visiting.delete(actionId2);
|
|
8268
|
+
done.add(actionId2);
|
|
8269
|
+
order.push(actionId2);
|
|
8270
|
+
};
|
|
8271
|
+
visit(targetId);
|
|
8272
|
+
const steps = order.map((actionId2) => {
|
|
8273
|
+
const node = nodesById.get(actionId2);
|
|
8274
|
+
const entry = snippets.get(actionId2) ?? null;
|
|
8275
|
+
if (!entry) {
|
|
8276
|
+
gaps.push({
|
|
8277
|
+
type: "no_snippet",
|
|
8278
|
+
action: actionId2,
|
|
8279
|
+
detail: `no snippet bound to ${actionId2} \u2014 scaffold one with scaffold_snippet`
|
|
8280
|
+
});
|
|
8281
|
+
}
|
|
8282
|
+
const satisfies = [...producesOf.get(actionId2) ?? []].filter((s) => allRequired.has(s)).map(stripState).sort();
|
|
8283
|
+
return {
|
|
8284
|
+
action: actionId2,
|
|
8285
|
+
name: String(node?.name ?? actionId2),
|
|
8286
|
+
satisfies,
|
|
8287
|
+
snippet: entry ? { id: entry.id, exportName: entry.exportName, file: entry.file, params: entry.params } : null
|
|
8288
|
+
};
|
|
8289
|
+
});
|
|
8290
|
+
return { target: targetId, steps, gaps, runnable: gaps.length === 0 };
|
|
8291
|
+
}
|
|
8292
|
+
var stripState;
|
|
8293
|
+
var init_test_plan = __esm({
|
|
8294
|
+
"src/server/graph/core/context/test-plan.ts"() {
|
|
8295
|
+
"use strict";
|
|
8296
|
+
stripState = (s) => s.replace(/^state:/, "");
|
|
8297
|
+
}
|
|
8298
|
+
});
|
|
8299
|
+
|
|
8300
|
+
// src/server/graph/core/context/spec-gen.ts
|
|
8301
|
+
function toEnvRef(name) {
|
|
8302
|
+
return `process.env.${name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase()}`;
|
|
8303
|
+
}
|
|
8304
|
+
function classify(name) {
|
|
8305
|
+
const n = name.toLowerCase();
|
|
8306
|
+
if (/pass|secret|token|credential|apikey|api_key|_key$|^key$/.test(n)) return { kind: "secret" };
|
|
8307
|
+
if (/email|slug|(^|_)id$|uuid|userid|orgid|projectid/.test(n)) return { kind: "real" };
|
|
8308
|
+
if (/desc|summary|body|message|content|note/.test(n)) return { kind: "faker", faker: "faker.lorem.sentence()" };
|
|
8309
|
+
if (/colou?r/.test(n)) return { kind: "faker", faker: "faker.color.rgb({ format: 'hex' })" };
|
|
8310
|
+
if (/icon/.test(n)) return { kind: "faker", faker: "faker.helpers.arrayElement(['rocket','bug','star','flag','zap','heart'])" };
|
|
8311
|
+
if (/name|title|label/.test(n)) return { kind: "faker", faker: "faker.word.noun()" };
|
|
8312
|
+
if (/url|link|href/.test(n)) return { kind: "faker", faker: "faker.internet.url()" };
|
|
8313
|
+
if (/count|qty|quantity|amount|number|price/.test(n)) return { kind: "faker", faker: "String(faker.number.int({ min: 1, max: 100 }))" };
|
|
8314
|
+
return { kind: "faker", faker: "faker.lorem.word()" };
|
|
8315
|
+
}
|
|
8316
|
+
function relImport(specDir, file) {
|
|
8317
|
+
let r = (0, import_node_path33.relative)(specDir, file).replace(/\.tsx?$/, "");
|
|
8318
|
+
if (!r.startsWith(".")) r = `./${r}`;
|
|
8319
|
+
return r;
|
|
8320
|
+
}
|
|
8321
|
+
function generateSpec(plan, opts = {}) {
|
|
8322
|
+
const specDir = opts.specDir ?? "tests/generated";
|
|
8323
|
+
const fixtures = opts.fixtures ?? {};
|
|
8324
|
+
const missing = /* @__PURE__ */ new Set();
|
|
8325
|
+
let usesFaker = false;
|
|
8326
|
+
const bound = plan.steps.filter((s) => s.snippet);
|
|
8327
|
+
const unbound = plan.steps.filter((s) => !s.snippet);
|
|
8328
|
+
const importLines = /* @__PURE__ */ new Set();
|
|
8329
|
+
for (const s of bound) {
|
|
8330
|
+
importLines.add(`import { ${s.snippet.exportName} } from '${relImport(specDir, s.snippet.file)}';`);
|
|
8331
|
+
}
|
|
8332
|
+
const callLines = bound.map((s) => {
|
|
8333
|
+
const params = s.snippet.params.map((p) => {
|
|
8334
|
+
if (Object.prototype.hasOwnProperty.call(fixtures, p)) {
|
|
8335
|
+
return ` ${p}: ${JSON.stringify(fixtures[p])},`;
|
|
8336
|
+
}
|
|
8337
|
+
const c = classify(p);
|
|
8338
|
+
if (c.kind === "secret") return ` ${p}: ${toEnvRef(p)},`;
|
|
8339
|
+
if (c.kind === "faker") {
|
|
8340
|
+
usesFaker = true;
|
|
8341
|
+
return ` ${p}: ${c.faker},`;
|
|
8342
|
+
}
|
|
8343
|
+
missing.add(p);
|
|
8344
|
+
return ` ${p}: undefined, // TODO: real value (seeded ${p})`;
|
|
8345
|
+
});
|
|
8346
|
+
const block = params.length ? `{
|
|
8347
|
+
${params.join("\n")}
|
|
8348
|
+
}` : "{}";
|
|
8349
|
+
return ` await ${s.snippet.exportName}.run(page, ${block});`;
|
|
8350
|
+
});
|
|
8351
|
+
const warnings = unbound.map(
|
|
8352
|
+
(s) => ` // \u26A0 GAP: no snippet for ${s.action} \u2014 scaffold_snippet then re-plan.`
|
|
8353
|
+
);
|
|
8354
|
+
const header = [`import { test } from '@playwright/test';`];
|
|
8355
|
+
if (usesFaker) header.push(`import { faker } from '@faker-js/faker';`);
|
|
8356
|
+
const code = `${header.join("\n")}
|
|
8357
|
+
${[...importLines].join("\n")}
|
|
8358
|
+
|
|
8359
|
+
test(${JSON.stringify(opts.title ?? `plan: ${plan.target}`)}, async ({ page }) => {
|
|
8360
|
+
${[...warnings, ...callLines].join("\n")}
|
|
8361
|
+
});
|
|
8362
|
+
`;
|
|
8363
|
+
return { code, missingFixtures: [...missing], complete: plan.runnable && missing.size === 0 };
|
|
8364
|
+
}
|
|
8365
|
+
var import_node_path33;
|
|
8366
|
+
var init_spec_gen = __esm({
|
|
8367
|
+
"src/server/graph/core/context/spec-gen.ts"() {
|
|
8368
|
+
"use strict";
|
|
8369
|
+
import_node_path33 = require("node:path");
|
|
8370
|
+
}
|
|
8371
|
+
});
|
|
8372
|
+
|
|
7807
8373
|
// src/server/graph/core/language-detection.ts
|
|
7808
8374
|
function walkForExtensions(dir, extCounts, depth = 0) {
|
|
7809
8375
|
if (depth > 10) return;
|
|
7810
|
-
if (!(0,
|
|
8376
|
+
if (!(0, import_node_fs26.existsSync)(dir)) return;
|
|
7811
8377
|
let entries;
|
|
7812
8378
|
try {
|
|
7813
|
-
entries = (0,
|
|
8379
|
+
entries = (0, import_node_fs26.readdirSync)(dir, { withFileTypes: true });
|
|
7814
8380
|
} catch {
|
|
7815
8381
|
return;
|
|
7816
8382
|
}
|
|
@@ -7818,9 +8384,9 @@ function walkForExtensions(dir, extCounts, depth = 0) {
|
|
|
7818
8384
|
if (entry.name.startsWith(".") && entry.isDirectory()) continue;
|
|
7819
8385
|
if (entry.isDirectory()) {
|
|
7820
8386
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
7821
|
-
walkForExtensions((0,
|
|
8387
|
+
walkForExtensions((0, import_node_path34.join)(dir, entry.name), extCounts, depth + 1);
|
|
7822
8388
|
} else {
|
|
7823
|
-
const ext = (0,
|
|
8389
|
+
const ext = (0, import_node_path34.extname)(entry.name).toLowerCase();
|
|
7824
8390
|
if (ext && EXTENSION_TO_LANGUAGE[ext]) {
|
|
7825
8391
|
extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
|
|
7826
8392
|
}
|
|
@@ -7859,12 +8425,12 @@ function detectLanguages(rootDir, supportedLanguages) {
|
|
|
7859
8425
|
});
|
|
7860
8426
|
return results;
|
|
7861
8427
|
}
|
|
7862
|
-
var
|
|
8428
|
+
var import_node_fs26, import_node_path34, EXTENSION_TO_LANGUAGE, IGNORE_DIRS, AUXILIARY_LANGUAGES;
|
|
7863
8429
|
var init_language_detection = __esm({
|
|
7864
8430
|
"src/server/graph/core/language-detection.ts"() {
|
|
7865
8431
|
"use strict";
|
|
7866
|
-
|
|
7867
|
-
|
|
8432
|
+
import_node_fs26 = require("node:fs");
|
|
8433
|
+
import_node_path34 = require("node:path");
|
|
7868
8434
|
init_launch_kit_paths();
|
|
7869
8435
|
EXTENSION_TO_LANGUAGE = {
|
|
7870
8436
|
// Web / Frontend
|
|
@@ -7986,7 +8552,7 @@ __export(watcher_exports, {
|
|
|
7986
8552
|
function isIgnoredPath(rel) {
|
|
7987
8553
|
if (rel.startsWith(GRAPHS_RELATIVE)) return true;
|
|
7988
8554
|
if (rel.endsWith(".lock") || rel.endsWith(".log")) return true;
|
|
7989
|
-
for (const part of rel.split(
|
|
8555
|
+
for (const part of rel.split(import_node_path35.sep)) {
|
|
7990
8556
|
if (IGNORE_SEGMENTS.has(part)) return true;
|
|
7991
8557
|
}
|
|
7992
8558
|
return false;
|
|
@@ -8028,7 +8594,7 @@ function startGraphWatcher(rootDir, opts = {}) {
|
|
|
8028
8594
|
regenerating = false;
|
|
8029
8595
|
}
|
|
8030
8596
|
}
|
|
8031
|
-
const watcher = (0,
|
|
8597
|
+
const watcher = (0, import_node_fs27.watch)(rootDir, { recursive: true }, (event, filename) => {
|
|
8032
8598
|
if (!filename) return;
|
|
8033
8599
|
const rel = filename.toString();
|
|
8034
8600
|
if (process.env.LAUNCH_CHART_WATCH_TRACE === "1") {
|
|
@@ -8061,12 +8627,12 @@ function startGraphWatcher(rootDir, opts = {}) {
|
|
|
8061
8627
|
freshness: () => getFreshnessTracker(rootDir).get()
|
|
8062
8628
|
};
|
|
8063
8629
|
}
|
|
8064
|
-
var
|
|
8630
|
+
var import_node_fs27, import_node_path35, IGNORE_SEGMENTS, TRIGGER_EXTENSIONS, GRAPHS_RELATIVE;
|
|
8065
8631
|
var init_watcher = __esm({
|
|
8066
8632
|
"src/server/graph/core/watcher.ts"() {
|
|
8067
8633
|
"use strict";
|
|
8068
|
-
|
|
8069
|
-
|
|
8634
|
+
import_node_fs27 = require("node:fs");
|
|
8635
|
+
import_node_path35 = require("node:path");
|
|
8070
8636
|
init_launch_kit_paths();
|
|
8071
8637
|
init_graph();
|
|
8072
8638
|
init_freshness();
|
|
@@ -8095,7 +8661,7 @@ var init_watcher = __esm({
|
|
|
8095
8661
|
".prisma",
|
|
8096
8662
|
".sql"
|
|
8097
8663
|
]);
|
|
8098
|
-
GRAPHS_RELATIVE = (0,
|
|
8664
|
+
GRAPHS_RELATIVE = (0, import_node_path35.join)(LAUNCHSECURE_DIR, "graphs");
|
|
8099
8665
|
}
|
|
8100
8666
|
});
|
|
8101
8667
|
|
|
@@ -8828,12 +9394,12 @@ function handleReadGraph(args) {
|
|
|
8828
9394
|
return okJson(result);
|
|
8829
9395
|
}
|
|
8830
9396
|
function nodeToFilePath(rootDir, layer, nodeId) {
|
|
8831
|
-
if (layer === "ui" || layer === "api") return (0,
|
|
8832
|
-
if (layer === "db") return (0,
|
|
8833
|
-
const withSrc = (0,
|
|
8834
|
-
if ((0,
|
|
8835
|
-
const direct = (0,
|
|
8836
|
-
if ((0,
|
|
9397
|
+
if (layer === "ui" || layer === "api") return (0, import_node_path36.join)(rootDir, "src", nodeId);
|
|
9398
|
+
if (layer === "db") return (0, import_node_path36.join)(rootDir, "prisma", "schema.prisma");
|
|
9399
|
+
const withSrc = (0, import_node_path36.join)(rootDir, "src", nodeId);
|
|
9400
|
+
if ((0, import_node_fs28.existsSync)(withSrc)) return withSrc;
|
|
9401
|
+
const direct = (0, import_node_path36.join)(rootDir, nodeId);
|
|
9402
|
+
if ((0, import_node_fs28.existsSync)(direct)) return direct;
|
|
8837
9403
|
return null;
|
|
8838
9404
|
}
|
|
8839
9405
|
function handleInspectNode(args) {
|
|
@@ -9101,11 +9667,11 @@ function handleGrepNodes(args) {
|
|
|
9101
9667
|
let filesSearched = 0;
|
|
9102
9668
|
let truncated = false;
|
|
9103
9669
|
for (const [filePath, nodeId] of filePaths) {
|
|
9104
|
-
if (!(0,
|
|
9670
|
+
if (!(0, import_node_fs28.existsSync)(filePath)) continue;
|
|
9105
9671
|
filesSearched++;
|
|
9106
9672
|
let content;
|
|
9107
9673
|
try {
|
|
9108
|
-
content = (0,
|
|
9674
|
+
content = (0, import_node_fs28.readFileSync)(filePath, "utf-8");
|
|
9109
9675
|
} catch {
|
|
9110
9676
|
continue;
|
|
9111
9677
|
}
|
|
@@ -9324,11 +9890,11 @@ function handleStartChartServer(args) {
|
|
|
9324
9890
|
});
|
|
9325
9891
|
}
|
|
9326
9892
|
const entryPath = process.argv[1];
|
|
9327
|
-
const logDir = (0,
|
|
9328
|
-
(0,
|
|
9329
|
-
const logPath = (0,
|
|
9330
|
-
const out = (0,
|
|
9331
|
-
const err2 = (0,
|
|
9893
|
+
const logDir = (0, import_node_path36.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR);
|
|
9894
|
+
(0, import_node_fs28.mkdirSync)(logDir, { recursive: true });
|
|
9895
|
+
const logPath = (0, import_node_path36.join)(logDir, "launch-chart.log");
|
|
9896
|
+
const out = (0, import_node_fs28.openSync)(logPath, "a");
|
|
9897
|
+
const err2 = (0, import_node_fs28.openSync)(logPath, "a");
|
|
9332
9898
|
const portArgs = args.port ? ["--port", String(args.port)] : [];
|
|
9333
9899
|
const child = (0, import_node_child_process2.spawn)(process.execPath, [entryPath, "serve", ...portArgs], {
|
|
9334
9900
|
detached: true,
|
|
@@ -9506,6 +10072,96 @@ function handleDriftReport(args) {
|
|
|
9506
10072
|
items: page
|
|
9507
10073
|
});
|
|
9508
10074
|
}
|
|
10075
|
+
function handleGenerateSpec(args) {
|
|
10076
|
+
const __resolved = resolveOrErr(args);
|
|
10077
|
+
if ("content" in __resolved) return __resolved;
|
|
10078
|
+
const { rootDir } = __resolved;
|
|
10079
|
+
const target = typeof args.target === "string" ? args.target.trim() : "";
|
|
10080
|
+
if (!target) return err("target is required (a context action node id).");
|
|
10081
|
+
const snippetsDir = typeof args.snippets_dir === "string" && args.snippets_dir.trim() ? args.snippets_dir.trim() : DEFAULT_SNIPPETS_DIR;
|
|
10082
|
+
const specDir = typeof args.spec_dir === "string" && args.spec_dir.trim() ? args.spec_dir.trim() : "tests/generated";
|
|
10083
|
+
const fixtures = args.fixtures && typeof args.fixtures === "object" ? args.fixtures : {};
|
|
10084
|
+
const title = typeof args.title === "string" ? args.title : void 0;
|
|
10085
|
+
const g = readGraph(rootDir, "context");
|
|
10086
|
+
if (!g) return err("No `context` layer found. Run generate_graph first to build context.json.");
|
|
10087
|
+
let node = g.nodes.find((n) => n.id === target);
|
|
10088
|
+
if (!node && !target.startsWith("action:") && !target.startsWith("state:")) {
|
|
10089
|
+
node = g.nodes.find((n) => n.id === `action:${target}`) ?? g.nodes.find((n) => n.id === `state:${target}`);
|
|
10090
|
+
}
|
|
10091
|
+
if (!node) return err(`No context node matching "${target}". Use read_graph layer:context to list nodes.`);
|
|
10092
|
+
const snippets = loadSnippetIndex(rootDir, snippetsDir);
|
|
10093
|
+
const plan = buildTestPlan(g, node.id, snippets);
|
|
10094
|
+
const spec = generateSpec(plan, { title: title ?? String(node.name), specDir, fixtures });
|
|
10095
|
+
return okJson({
|
|
10096
|
+
target: node.id,
|
|
10097
|
+
spec_dir: specDir,
|
|
10098
|
+
suggested_path: `${specDir}/${String(node.name).replace(/[^A-Za-z0-9]+/g, "-").replace(/^-|-$/g, "") || "spec"}.spec.ts`,
|
|
10099
|
+
complete: spec.complete,
|
|
10100
|
+
missing_fixtures: spec.missingFixtures,
|
|
10101
|
+
plan_gaps: plan.gaps,
|
|
10102
|
+
code: spec.code,
|
|
10103
|
+
next: spec.complete ? "Write `code` to suggested_path and run `playwright test` (needs @faker-js/faker installed + the secret env vars set)." : "Supply `fixtures` for missing_fixtures (real seeded values), then regenerate."
|
|
10104
|
+
});
|
|
10105
|
+
}
|
|
10106
|
+
function handleTestPlan(args) {
|
|
10107
|
+
const __resolved = resolveOrErr(args);
|
|
10108
|
+
if ("content" in __resolved) return __resolved;
|
|
10109
|
+
const { rootDir } = __resolved;
|
|
10110
|
+
const target = typeof args.target === "string" ? args.target.trim() : "";
|
|
10111
|
+
if (!target) return err("target is required (a context action node id).");
|
|
10112
|
+
const snippetsDir = typeof args.snippets_dir === "string" && args.snippets_dir.trim() ? args.snippets_dir.trim() : DEFAULT_SNIPPETS_DIR;
|
|
10113
|
+
const g = readGraph(rootDir, "context");
|
|
10114
|
+
if (!g) return err("No `context` layer found. Run generate_graph first to build context.json.");
|
|
10115
|
+
let node = g.nodes.find((n) => n.id === target);
|
|
10116
|
+
if (!node && !target.startsWith("action:") && !target.startsWith("state:")) {
|
|
10117
|
+
node = g.nodes.find((n) => n.id === `action:${target}`) ?? g.nodes.find((n) => n.id === `state:${target}`);
|
|
10118
|
+
}
|
|
10119
|
+
if (!node) return err(`No context node matching "${target}". Use read_graph layer:context to list nodes.`);
|
|
10120
|
+
const snippets = loadSnippetIndex(rootDir, snippetsDir);
|
|
10121
|
+
const plan = buildTestPlan(g, node.id, snippets);
|
|
10122
|
+
return okJson({
|
|
10123
|
+
...plan,
|
|
10124
|
+
snippets_dir: snippetsDir,
|
|
10125
|
+
snippets_found: snippets.size,
|
|
10126
|
+
next: plan.runnable ? "Runnable: execute the snippets in `steps` order, threading produced state forward." : "Not yet runnable \u2014 resolve `gaps` (scaffold_snippet for missing bindings; add a producer for unmet states)."
|
|
10127
|
+
});
|
|
10128
|
+
}
|
|
10129
|
+
function handleScaffoldSnippet(args) {
|
|
10130
|
+
const __resolved = resolveOrErr(args);
|
|
10131
|
+
if ("content" in __resolved) return __resolved;
|
|
10132
|
+
const { rootDir } = __resolved;
|
|
10133
|
+
const target = typeof args.target === "string" ? args.target.trim() : "";
|
|
10134
|
+
if (!target) return err('target is required (a context node id, e.g. "action:app/api/.../tags/route.ts").');
|
|
10135
|
+
const g = readGraph(rootDir, "context");
|
|
10136
|
+
if (!g) return err("No `context` layer found. Run generate_graph first to build context.json.");
|
|
10137
|
+
let node = g.nodes.find((n) => n.id === target);
|
|
10138
|
+
if (!node && !target.startsWith("action:") && !target.startsWith("state:")) {
|
|
10139
|
+
node = g.nodes.find((n) => n.id === `action:${target}`) ?? g.nodes.find((n) => n.id === `state:${target}`);
|
|
10140
|
+
}
|
|
10141
|
+
if (!node) return err(`No context node matching "${target}". Use read_graph layer:context to list nodes.`);
|
|
10142
|
+
const out = g.edges.filter((e) => e.source === node.id);
|
|
10143
|
+
const tokensOf = (type) => [...new Set(out.filter((e) => e.type === type).map((e) => String(e.target).replace(/^state:/, "")))].sort();
|
|
10144
|
+
const requires = tokensOf("requires");
|
|
10145
|
+
const produces = tokensOf("produces");
|
|
10146
|
+
const { exportName, code } = scaffoldSnippet({
|
|
10147
|
+
nodeId: node.id,
|
|
10148
|
+
name: String(node.name ?? node.id),
|
|
10149
|
+
requires,
|
|
10150
|
+
produces
|
|
10151
|
+
});
|
|
10152
|
+
const requiresDetail = out.filter((e) => e.type === "requires").map((e) => ({ state: String(e.target).replace(/^state:/, ""), confidence: e.confidence ?? "low", origin: e.origin ?? null }));
|
|
10153
|
+
return okJson({
|
|
10154
|
+
target: node.id,
|
|
10155
|
+
name: node.name ?? node.id,
|
|
10156
|
+
requires,
|
|
10157
|
+
produces,
|
|
10158
|
+
requires_detail: requiresDetail,
|
|
10159
|
+
export_name: exportName,
|
|
10160
|
+
suggested_path: `tests/snippets/${exportName}.ts`,
|
|
10161
|
+
snippet: code,
|
|
10162
|
+
next: "Fill `params` from the endpoint request/validation schema and implement run() with Playwright, then write the file to suggested_path."
|
|
10163
|
+
});
|
|
10164
|
+
}
|
|
9509
10165
|
function handleWhoUses(args) {
|
|
9510
10166
|
const __resolved = resolveOrErr(args);
|
|
9511
10167
|
if ("content" in __resolved) return __resolved;
|
|
@@ -9751,20 +10407,20 @@ function handleDetectProjectStack() {
|
|
|
9751
10407
|
if (ref.type === "references_api") stats.references_api++;
|
|
9752
10408
|
}
|
|
9753
10409
|
}
|
|
9754
|
-
const srcDir = (0,
|
|
9755
|
-
if ((0,
|
|
10410
|
+
const srcDir = (0, import_node_path36.join)(rootDir, "src");
|
|
10411
|
+
if ((0, import_node_fs28.existsSync)(srcDir)) {
|
|
9756
10412
|
const scanDir = (dir) => {
|
|
9757
|
-
if (!(0,
|
|
9758
|
-
for (const entry of (0,
|
|
10413
|
+
if (!(0, import_node_fs28.existsSync)(dir)) return;
|
|
10414
|
+
for (const entry of (0, import_node_fs28.readdirSync)(dir, { withFileTypes: true })) {
|
|
9759
10415
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
9760
|
-
const full = (0,
|
|
10416
|
+
const full = (0, import_node_path36.join)(dir, entry.name);
|
|
9761
10417
|
if (entry.isDirectory()) {
|
|
9762
10418
|
scanDir(full);
|
|
9763
10419
|
continue;
|
|
9764
10420
|
}
|
|
9765
|
-
if (![".ts", ".tsx"].includes((0,
|
|
10421
|
+
if (![".ts", ".tsx"].includes((0, import_node_path36.extname)(entry.name))) continue;
|
|
9766
10422
|
try {
|
|
9767
|
-
const content = (0,
|
|
10423
|
+
const content = (0, import_node_fs28.readFileSync)(full, "utf-8");
|
|
9768
10424
|
const matches = content.match(/@api\s+(GET|POST|PUT|DELETE|PATCH)\s+\/\S+/g);
|
|
9769
10425
|
if (matches) stats.annotations += matches.length;
|
|
9770
10426
|
} catch {
|
|
@@ -9783,7 +10439,7 @@ function handleDetectProjectStack() {
|
|
|
9783
10439
|
name: p.name,
|
|
9784
10440
|
root: p.root,
|
|
9785
10441
|
absolute_root: p.absoluteRoot,
|
|
9786
|
-
has_graph: (0,
|
|
10442
|
+
has_graph: (0, import_node_fs28.existsSync)((0, import_node_path36.join)(p.absoluteRoot, LAUNCHSECURE_DIR, "graphs"))
|
|
9787
10443
|
}));
|
|
9788
10444
|
return okJson({
|
|
9789
10445
|
languages,
|
|
@@ -9913,6 +10569,18 @@ async function handleMessage(msg) {
|
|
|
9913
10569
|
respond(id ?? null, withFreshnessMeta(handleWhoUses(args), args));
|
|
9914
10570
|
return;
|
|
9915
10571
|
}
|
|
10572
|
+
if (toolName === "scaffold_snippet") {
|
|
10573
|
+
respond(id ?? null, withFreshnessMeta(handleScaffoldSnippet(args), args));
|
|
10574
|
+
return;
|
|
10575
|
+
}
|
|
10576
|
+
if (toolName === "test_plan") {
|
|
10577
|
+
respond(id ?? null, withFreshnessMeta(handleTestPlan(args), args));
|
|
10578
|
+
return;
|
|
10579
|
+
}
|
|
10580
|
+
if (toolName === "generate_spec") {
|
|
10581
|
+
respond(id ?? null, withFreshnessMeta(handleGenerateSpec(args), args));
|
|
10582
|
+
return;
|
|
10583
|
+
}
|
|
9916
10584
|
if (toolName === "trace_path") {
|
|
9917
10585
|
respond(id ?? null, withFreshnessMeta(handleTracePath(args), args));
|
|
9918
10586
|
return;
|
|
@@ -9981,16 +10649,20 @@ function startGraphMcpServer() {
|
|
|
9981
10649
|
process.stderr.write(`[launchsecure-graph] MCP server started (cwd: ${process.cwd()})
|
|
9982
10650
|
`);
|
|
9983
10651
|
}
|
|
9984
|
-
var
|
|
10652
|
+
var import_node_fs28, import_node_path36, import_node_child_process2, import_node_os4, SERVER_INFO, TOOLS, MINIMAL_STRIP_FIELDS, COMPACT_SCHEMA, COMPACT_NODE_KNOWN_KEYS, DEEP_FIELDS, NOTE_KIND_CATEGORY, EST_CHARS_PER_NODE_FULL, EST_CHARS_PER_NODE_MIN, EST_CHARS_PER_EDGE, DEFAULT_EST_NODE_FULL, DEFAULT_EST_NODE_MIN, DEFAULT_EST_EDGE, NEIGHBORHOOD_BUDGET_CHARS, MAX_FILTER_EDGES, BATCH_BUDGET_CHARS, watcherHandle;
|
|
9985
10653
|
var init_graph_mcp = __esm({
|
|
9986
10654
|
"src/server/graph-mcp.ts"() {
|
|
9987
10655
|
"use strict";
|
|
9988
|
-
|
|
9989
|
-
|
|
10656
|
+
import_node_fs28 = require("node:fs");
|
|
10657
|
+
import_node_path36 = require("node:path");
|
|
9990
10658
|
import_node_child_process2 = require("node:child_process");
|
|
9991
|
-
|
|
10659
|
+
import_node_os4 = require("node:os");
|
|
9992
10660
|
init_launch_kit_paths();
|
|
9993
10661
|
init_graph();
|
|
10662
|
+
init_snippet();
|
|
10663
|
+
init_snippet_registry();
|
|
10664
|
+
init_test_plan();
|
|
10665
|
+
init_spec_gen();
|
|
9994
10666
|
init_lockfile();
|
|
9995
10667
|
init_config();
|
|
9996
10668
|
init_parser_registry();
|
|
@@ -10375,6 +11047,53 @@ USE THIS FOR: "are there unsafe migrations on this branch", "audit the migration
|
|
|
10375
11047
|
required: ["from", "to"]
|
|
10376
11048
|
}
|
|
10377
11049
|
},
|
|
11050
|
+
{
|
|
11051
|
+
name: "scaffold_snippet",
|
|
11052
|
+
description: 'Scaffold a standardised test "snippet" for a context-graph action/state node. Returns a ready-to-complete snippet file: requires/produces are filled from the context graph (certain); params + the run() body are left as TODOs for you to fill from the endpoint source.\n\nUSE THIS FOR: turning a context `action` (e.g. "create tag", "pay via UPI") into a reusable Playwright snippet in the project\'s standard shape, instead of hand-writing the requires/produces wiring. Requires the `context` layer (run generate_graph first).',
|
|
11053
|
+
inputSchema: {
|
|
11054
|
+
type: "object",
|
|
11055
|
+
properties: {
|
|
11056
|
+
target: { type: "string", description: 'Context node id (e.g. "action:app/api/orgs/[orgSlug]/tags/route.ts") or the bare key (the "action:"/"state:" prefix is added if missing).' },
|
|
11057
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
11058
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
11059
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
11060
|
+
},
|
|
11061
|
+
required: ["target"]
|
|
11062
|
+
}
|
|
11063
|
+
},
|
|
11064
|
+
{
|
|
11065
|
+
name: "test_plan",
|
|
11066
|
+
description: 'Assemble the ordered snippet chain needed to exercise a target action. Walks the context graph\'s requires/produces backward from the target (prerequisites first, target last), binds each step to its snippet (from tests/snippets/), and flags gaps \u2014 a required state nothing produces, an action with no snippet, or a cycle.\n\nUSE THIS FOR: "what\'s the test plan for creating a tag", "what setup does action X need". Returns { target, steps[], gaps[], runnable }. Requires the `context` layer (run generate_graph first). When runnable is false, the gaps tell you which snippets to scaffold next.',
|
|
11067
|
+
inputSchema: {
|
|
11068
|
+
type: "object",
|
|
11069
|
+
properties: {
|
|
11070
|
+
target: { type: "string", description: 'Context action node id (e.g. "action:app/api/orgs/[orgSlug]/tags/route.ts") or the bare key.' },
|
|
11071
|
+
snippets_dir: { type: "string", description: `Snippets directory relative to project root. Default "${DEFAULT_SNIPPETS_DIR}".` },
|
|
11072
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
11073
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
11074
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
11075
|
+
},
|
|
11076
|
+
required: ["target"]
|
|
11077
|
+
}
|
|
11078
|
+
},
|
|
11079
|
+
{
|
|
11080
|
+
name: "generate_spec",
|
|
11081
|
+
description: 'Generate a runnable Playwright .spec.ts from a target action: builds the test_plan, then emits imports + ordered `await snippet.run(page, {...})` calls. Params are auto-classified \u2014 secrets \u2192 process.env, free-form (name/color/icon/description) \u2192 faker.*, and state-bound (email/slug/id) \u2192 TODO + returned in missingFixtures. Supply those via `fixtures`.\n\nUSE THIS FOR: "generate the test for creating a tag". Returns { code, missingFixtures, complete }. complete:true means every state-bound param had a fixture (secrets via env + faker auto). Requires the `context` layer + snippets.',
|
|
11082
|
+
inputSchema: {
|
|
11083
|
+
type: "object",
|
|
11084
|
+
properties: {
|
|
11085
|
+
target: { type: "string", description: "Context action node id (or bare key)." },
|
|
11086
|
+
fixtures: { type: "object", description: 'param name -> value for state-bound params (e.g. {"email":"...","orgSlug":"..."}). Overrides classification.', additionalProperties: true },
|
|
11087
|
+
title: { type: "string", description: "Test title. Defaults to the target." },
|
|
11088
|
+
spec_dir: { type: "string", description: 'Where the spec will live (for import paths). Default "tests/generated".' },
|
|
11089
|
+
snippets_dir: { type: "string", description: `Snippets directory. Default "${DEFAULT_SNIPPETS_DIR}".` },
|
|
11090
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
11091
|
+
worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
|
|
11092
|
+
project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
|
|
11093
|
+
},
|
|
11094
|
+
required: ["target"]
|
|
11095
|
+
}
|
|
11096
|
+
},
|
|
10378
11097
|
{
|
|
10379
11098
|
name: "auth_coverage_report",
|
|
10380
11099
|
description: 'Aggregate every API endpoint by its auth[] wrapper(s) \u2014 surfaces endpoints with empty auth, groups by module, shows which auth strategies dominate. Computed from api.json endpoint auth field (100% populated).\n\nUSE THIS FOR: "are there unauthenticated endpoints", "which auth wrappers are in use", "audit auth strategy consistency across modules". Paginated. Returns { total, by_strategy: {strategy: count}, unauthenticated: [endpoint_ids], by_module: {module: {total, by_strategy}} } plus the paginated endpoint list.',
|
|
@@ -10641,8 +11360,92 @@ Example: blast_points(node_id: "server/auth/middleware.ts", hops: 2) \u2192 retu
|
|
|
10641
11360
|
|
|
10642
11361
|
// src/server/graph-mcp-entry.ts
|
|
10643
11362
|
init_lockfile();
|
|
11363
|
+
|
|
11364
|
+
// src/server/prune-npx-cache.ts
|
|
11365
|
+
var import_node_fs2 = require("node:fs");
|
|
11366
|
+
var import_node_os2 = require("node:os");
|
|
11367
|
+
var import_node_path2 = require("node:path");
|
|
11368
|
+
var import_node_util = require("node:util");
|
|
11369
|
+
var PKG = "@launchsecure/launch-kit";
|
|
11370
|
+
var readFileAsync = (0, import_node_util.promisify)(import_node_fs2.readFile);
|
|
11371
|
+
var readdirAsync = (0, import_node_util.promisify)(import_node_fs2.readdir);
|
|
11372
|
+
var rmAsync = (0, import_node_util.promisify)(import_node_fs2.rm);
|
|
11373
|
+
var realpathAsync = (0, import_node_util.promisify)(import_node_fs2.realpath);
|
|
11374
|
+
function npxCacheRoot() {
|
|
11375
|
+
const cache = process.env.npm_config_cache;
|
|
11376
|
+
if (cache && cache.trim()) return (0, import_node_path2.join)(cache, "_npx");
|
|
11377
|
+
if (process.platform === "win32") {
|
|
11378
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
11379
|
+
if (localAppData) return (0, import_node_path2.join)(localAppData, "npm-cache", "_npx");
|
|
11380
|
+
}
|
|
11381
|
+
return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".npm", "_npx");
|
|
11382
|
+
}
|
|
11383
|
+
function ownVersion() {
|
|
11384
|
+
let dir = __dirname;
|
|
11385
|
+
for (let i = 0; i < 8; i++) {
|
|
11386
|
+
try {
|
|
11387
|
+
const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.join)(dir, "package.json"), "utf8"));
|
|
11388
|
+
if (pkg && pkg.name === PKG) return typeof pkg.version === "string" ? pkg.version : null;
|
|
11389
|
+
} catch {
|
|
11390
|
+
}
|
|
11391
|
+
const parent = (0, import_node_path2.dirname)(dir);
|
|
11392
|
+
if (parent === dir) break;
|
|
11393
|
+
dir = parent;
|
|
11394
|
+
}
|
|
11395
|
+
return null;
|
|
11396
|
+
}
|
|
11397
|
+
async function pruneStaleNpxCache() {
|
|
11398
|
+
try {
|
|
11399
|
+
const version = ownVersion();
|
|
11400
|
+
if (!version) return;
|
|
11401
|
+
const root = npxCacheRoot();
|
|
11402
|
+
let hashes;
|
|
11403
|
+
try {
|
|
11404
|
+
hashes = await readdirAsync(root);
|
|
11405
|
+
} catch {
|
|
11406
|
+
return;
|
|
11407
|
+
}
|
|
11408
|
+
const selfPath = await realpathAsync(process.argv[1] || __dirname).catch(() => __dirname);
|
|
11409
|
+
let reaped = 0;
|
|
11410
|
+
for (const hash of hashes) {
|
|
11411
|
+
const dir = (0, import_node_path2.join)(root, hash);
|
|
11412
|
+
if (selfPath === dir || selfPath.startsWith(dir + import_node_path2.sep)) continue;
|
|
11413
|
+
let ver;
|
|
11414
|
+
try {
|
|
11415
|
+
const lkPkg = JSON.parse(
|
|
11416
|
+
await readFileAsync((0, import_node_path2.join)(dir, "node_modules", PKG, "package.json"), "utf8")
|
|
11417
|
+
);
|
|
11418
|
+
ver = lkPkg.version;
|
|
11419
|
+
} catch {
|
|
11420
|
+
continue;
|
|
11421
|
+
}
|
|
11422
|
+
if (ver === version) continue;
|
|
11423
|
+
try {
|
|
11424
|
+
const top = JSON.parse(await readFileAsync((0, import_node_path2.join)(dir, "package.json"), "utf8"));
|
|
11425
|
+
const spec = top?.dependencies?.[PKG];
|
|
11426
|
+
if (typeof spec === "string" && spec.startsWith("file:")) continue;
|
|
11427
|
+
} catch {
|
|
11428
|
+
}
|
|
11429
|
+
try {
|
|
11430
|
+
await rmAsync(dir, { recursive: true, force: true });
|
|
11431
|
+
reaped++;
|
|
11432
|
+
} catch {
|
|
11433
|
+
}
|
|
11434
|
+
}
|
|
11435
|
+
if (reaped > 0) {
|
|
11436
|
+
process.stderr.write(
|
|
11437
|
+
`[launch-kit] npx-cache: reaped ${reaped} stale copy(ies), kept v${version}
|
|
11438
|
+
`
|
|
11439
|
+
);
|
|
11440
|
+
}
|
|
11441
|
+
} catch {
|
|
11442
|
+
}
|
|
11443
|
+
}
|
|
11444
|
+
|
|
11445
|
+
// src/server/graph-mcp-entry.ts
|
|
10644
11446
|
async function main() {
|
|
10645
11447
|
setProjectRoot(process.cwd());
|
|
11448
|
+
void pruneStaleNpxCache();
|
|
10646
11449
|
const argv = process.argv.slice(2);
|
|
10647
11450
|
const subcommand = argv[0];
|
|
10648
11451
|
if (subcommand === "serve") {
|