depwire-cli 0.9.24 → 0.9.26
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/{chunk-ORGAO3HT.js → chunk-B2KGFBZL.js} +59 -1
- package/dist/{chunk-QHVWDUSX.js → chunk-YYY5TNG7.js} +1446 -1
- package/dist/index.js +482 -36
- package/dist/mcpb-entry.js +2 -2
- package/dist/sdk.d.ts +49 -1
- package/dist/sdk.js +3 -1
- package/dist/viz/public/arc.js +185 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
stashChanges,
|
|
18
18
|
updateFileInGraph,
|
|
19
19
|
watchProject
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-B2KGFBZL.js";
|
|
21
21
|
import {
|
|
22
22
|
SimulationEngine,
|
|
23
23
|
analyzeDeadCode,
|
|
@@ -29,14 +29,15 @@ import {
|
|
|
29
29
|
getHealthTrend,
|
|
30
30
|
getImpact,
|
|
31
31
|
parseProject,
|
|
32
|
+
scanSecurity,
|
|
32
33
|
searchSymbols
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-YYY5TNG7.js";
|
|
34
35
|
|
|
35
36
|
// src/index.ts
|
|
36
37
|
import { Command } from "commander";
|
|
37
|
-
import { resolve as
|
|
38
|
-
import { writeFileSync, readFileSync as
|
|
39
|
-
import { fileURLToPath as
|
|
38
|
+
import { resolve as resolve3, dirname as dirname4, join as join5 } from "path";
|
|
39
|
+
import { writeFileSync, readFileSync as readFileSync3, existsSync } from "fs";
|
|
40
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
40
41
|
|
|
41
42
|
// src/graph/serializer.ts
|
|
42
43
|
import { DirectedGraph } from "graphology";
|
|
@@ -305,10 +306,10 @@ async function findAvailablePort(startPort) {
|
|
|
305
306
|
const net = await import("net");
|
|
306
307
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
307
308
|
const testPort = startPort + attempt;
|
|
308
|
-
const isAvailable = await new Promise((
|
|
309
|
-
const server = net.createServer().once("error", () =>
|
|
309
|
+
const isAvailable = await new Promise((resolve4) => {
|
|
310
|
+
const server = net.createServer().once("error", () => resolve4(false)).once("listening", () => {
|
|
310
311
|
server.close();
|
|
311
|
-
|
|
312
|
+
resolve4(true);
|
|
312
313
|
}).listen(testPort, "127.0.0.1");
|
|
313
314
|
});
|
|
314
315
|
if (isAvailable) {
|
|
@@ -349,13 +350,13 @@ async function startTemporalServer(snapshots, projectRoot, preferredPort = 3334)
|
|
|
349
350
|
console.log(" (Could not open browser automatically)");
|
|
350
351
|
});
|
|
351
352
|
});
|
|
352
|
-
await new Promise((
|
|
353
|
+
await new Promise((resolve4, reject) => {
|
|
353
354
|
server.on("error", reject);
|
|
354
355
|
process.on("SIGINT", () => {
|
|
355
356
|
console.log("\n\nShutting down temporal server...");
|
|
356
357
|
server.close(() => {
|
|
357
358
|
console.log("Server stopped");
|
|
358
|
-
|
|
359
|
+
resolve4();
|
|
359
360
|
process.exit(0);
|
|
360
361
|
});
|
|
361
362
|
});
|
|
@@ -502,13 +503,269 @@ async function trackCommand(command, version = "unknown") {
|
|
|
502
503
|
// src/commands/whatif.ts
|
|
503
504
|
import { resolve } from "path";
|
|
504
505
|
import chalk from "chalk";
|
|
506
|
+
|
|
507
|
+
// src/viz/whatif-server.ts
|
|
508
|
+
import express2 from "express";
|
|
509
|
+
import open2 from "open";
|
|
510
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
511
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
512
|
+
|
|
513
|
+
// src/viz/generate-whatif-html.ts
|
|
514
|
+
function generateWhatIfHtml(currentVizData, simulatedVizData, simulationResult, operation, target) {
|
|
515
|
+
const { healthDelta, diff } = simulationResult;
|
|
516
|
+
const deltaSign = healthDelta.delta >= 0 ? "+" : "";
|
|
517
|
+
const deltaLabel = healthDelta.delta === 0 ? "unchanged" : healthDelta.improved ? `${deltaSign}${healthDelta.delta} \u2713 improved` : `${healthDelta.delta} \u2717 degraded`;
|
|
518
|
+
const deltaColor = healthDelta.delta === 0 ? "#fbbf24" : healthDelta.improved ? "#4ade80" : "#f87171";
|
|
519
|
+
const opBadge = operation !== "none" ? `<span style="background:${deltaColor};color:#000;padding:4px 12px;border-radius:4px;font-weight:700;font-size:13px;text-transform:uppercase;margin-left:12px;">${operation} ${target}</span>` : "";
|
|
520
|
+
const brokenImportsHtml = diff.brokenImports.length > 0 ? `<details style="margin-top:16px;background:#16213e;border:1px solid #2a2a4a;border-radius:8px;padding:12px 16px;">
|
|
521
|
+
<summary style="cursor:pointer;color:#f87171;font-weight:600;font-size:14px;">Broken Imports (${diff.brokenImports.length})</summary>
|
|
522
|
+
<ul style="margin:8px 0 0 16px;padding:0;list-style:none;">
|
|
523
|
+
${diff.brokenImports.map((bi) => `<li style="color:#e0e0e0;font-size:13px;padding:4px 0;font-family:monospace;">${bi.file} \u2192 <span style="color:#f87171;">${bi.importedSymbol}</span></li>`).join("")}
|
|
524
|
+
</ul>
|
|
525
|
+
</details>` : "";
|
|
526
|
+
const currentDataJson = JSON.stringify(currentVizData);
|
|
527
|
+
const simulatedDataJson = JSON.stringify(simulatedVizData);
|
|
528
|
+
return `<!DOCTYPE html>
|
|
529
|
+
<html lang="en">
|
|
530
|
+
<head>
|
|
531
|
+
<meta charset="UTF-8">
|
|
532
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
533
|
+
<title>Depwire \u2014 What If Simulation</title>
|
|
534
|
+
<link rel="stylesheet" href="/style.css">
|
|
535
|
+
<style>
|
|
536
|
+
body { overflow: auto; height: auto; }
|
|
537
|
+
.whatif-header {
|
|
538
|
+
background: #16213e;
|
|
539
|
+
border-bottom: 1px solid #2a2a4a;
|
|
540
|
+
padding: 16px 24px;
|
|
541
|
+
display: flex;
|
|
542
|
+
align-items: center;
|
|
543
|
+
gap: 16px;
|
|
544
|
+
}
|
|
545
|
+
.whatif-header h1 {
|
|
546
|
+
margin: 0;
|
|
547
|
+
font-size: 20px;
|
|
548
|
+
font-weight: 600;
|
|
549
|
+
background: linear-gradient(135deg, #4a9eff, #7c3aed);
|
|
550
|
+
-webkit-background-clip: text;
|
|
551
|
+
-webkit-text-fill-color: transparent;
|
|
552
|
+
background-clip: text;
|
|
553
|
+
}
|
|
554
|
+
.health-banner {
|
|
555
|
+
background: #0f1729;
|
|
556
|
+
border: 1px solid #2a2a4a;
|
|
557
|
+
border-radius: 8px;
|
|
558
|
+
padding: 16px 24px;
|
|
559
|
+
margin: 16px 24px;
|
|
560
|
+
display: flex;
|
|
561
|
+
align-items: center;
|
|
562
|
+
gap: 32px;
|
|
563
|
+
flex-wrap: wrap;
|
|
564
|
+
}
|
|
565
|
+
.health-score {
|
|
566
|
+
font-size: 22px;
|
|
567
|
+
font-weight: 700;
|
|
568
|
+
}
|
|
569
|
+
.health-stat {
|
|
570
|
+
font-size: 14px;
|
|
571
|
+
color: #a0a0a0;
|
|
572
|
+
}
|
|
573
|
+
.health-stat strong {
|
|
574
|
+
color: #e0e0e0;
|
|
575
|
+
font-size: 18px;
|
|
576
|
+
}
|
|
577
|
+
.panels {
|
|
578
|
+
display: flex;
|
|
579
|
+
flex-direction: row;
|
|
580
|
+
gap: 0;
|
|
581
|
+
width: 100%;
|
|
582
|
+
height: calc(100vh - 180px);
|
|
583
|
+
min-height: 400px;
|
|
584
|
+
}
|
|
585
|
+
.panel {
|
|
586
|
+
flex: 1;
|
|
587
|
+
min-width: 0;
|
|
588
|
+
display: flex;
|
|
589
|
+
flex-direction: column;
|
|
590
|
+
border-right: 1px solid #2a2a4a;
|
|
591
|
+
overflow: hidden;
|
|
592
|
+
position: relative;
|
|
593
|
+
}
|
|
594
|
+
.panel:last-child { border-right: none; }
|
|
595
|
+
.panel-label {
|
|
596
|
+
background: #16213e;
|
|
597
|
+
padding: 8px 16px;
|
|
598
|
+
font-size: 13px;
|
|
599
|
+
font-weight: 600;
|
|
600
|
+
color: #a0a0a0;
|
|
601
|
+
border-bottom: 1px solid #2a2a4a;
|
|
602
|
+
display: flex;
|
|
603
|
+
justify-content: space-between;
|
|
604
|
+
flex-shrink: 0;
|
|
605
|
+
}
|
|
606
|
+
.panel-diagram {
|
|
607
|
+
flex: 1;
|
|
608
|
+
overflow: hidden;
|
|
609
|
+
position: relative;
|
|
610
|
+
}
|
|
611
|
+
.panel-diagram svg {
|
|
612
|
+
display: block;
|
|
613
|
+
width: 100%;
|
|
614
|
+
height: 100%;
|
|
615
|
+
}
|
|
616
|
+
.broken-section {
|
|
617
|
+
padding: 0 24px 24px;
|
|
618
|
+
}
|
|
619
|
+
</style>
|
|
620
|
+
</head>
|
|
621
|
+
<body>
|
|
622
|
+
<div class="whatif-header">
|
|
623
|
+
<h1>depwire \u2014 What If Simulation</h1>
|
|
624
|
+
${opBadge}
|
|
625
|
+
</div>
|
|
626
|
+
|
|
627
|
+
<div class="health-banner">
|
|
628
|
+
<div class="health-score" style="color:${deltaColor}">
|
|
629
|
+
Health Score: ${healthDelta.before} \u2192 ${healthDelta.after}
|
|
630
|
+
<span style="font-size:16px;margin-left:8px;">(${deltaLabel})</span>
|
|
631
|
+
</div>
|
|
632
|
+
<div class="health-stat"><strong>${diff.affectedNodes.length}</strong> Affected Nodes</div>
|
|
633
|
+
<div class="health-stat"><strong>${diff.brokenImports.length}</strong> Broken Imports</div>
|
|
634
|
+
<div class="health-stat"><strong>${diff.removedEdges.length}</strong> Removed Edges</div>
|
|
635
|
+
</div>
|
|
636
|
+
|
|
637
|
+
<div class="panels">
|
|
638
|
+
<div class="panel">
|
|
639
|
+
<div class="panel-label">
|
|
640
|
+
<span>Current</span>
|
|
641
|
+
<span>${currentVizData.stats.totalFiles} files</span>
|
|
642
|
+
</div>
|
|
643
|
+
<div class="panel-diagram" id="arc-diagram-current">
|
|
644
|
+
<svg id="svg-current"></svg>
|
|
645
|
+
</div>
|
|
646
|
+
<div class="tooltip" id="tooltip-current"></div>
|
|
647
|
+
</div>
|
|
648
|
+
<div class="panel">
|
|
649
|
+
<div class="panel-label">
|
|
650
|
+
<span>After ${operation !== "none" ? operation.toUpperCase() : "\u2014"}</span>
|
|
651
|
+
<span>${simulatedVizData.stats.totalFiles} files</span>
|
|
652
|
+
</div>
|
|
653
|
+
<div class="panel-diagram" id="arc-diagram-simulated">
|
|
654
|
+
<svg id="svg-simulated"></svg>
|
|
655
|
+
</div>
|
|
656
|
+
<div class="tooltip" id="tooltip-simulated"></div>
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
659
|
+
|
|
660
|
+
<div class="broken-section">
|
|
661
|
+
${brokenImportsHtml}
|
|
662
|
+
</div>
|
|
663
|
+
|
|
664
|
+
<script>window.__depwireWhatIf = true;</script>
|
|
665
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
|
|
666
|
+
<script src="/arc.js"></script>
|
|
667
|
+
<script>
|
|
668
|
+
const currentData = ${currentDataJson};
|
|
669
|
+
const simulatedData = ${simulatedDataJson};
|
|
670
|
+
|
|
671
|
+
const left = window.createArcDiagram('arc-diagram-current', 'svg-current', 'tooltip-current', currentData);
|
|
672
|
+
const right = window.createArcDiagram('arc-diagram-simulated', 'svg-simulated', 'tooltip-simulated', simulatedData);
|
|
673
|
+
|
|
674
|
+
left.render();
|
|
675
|
+
right.render();
|
|
676
|
+
|
|
677
|
+
window.addEventListener('resize', () => {
|
|
678
|
+
left.render();
|
|
679
|
+
right.render();
|
|
680
|
+
});
|
|
681
|
+
</script>
|
|
682
|
+
</body>
|
|
683
|
+
</html>`;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/viz/whatif-server.ts
|
|
687
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
688
|
+
var __dirname2 = dirname2(__filename2);
|
|
689
|
+
async function findAvailablePort2(startPort, maxAttempts = 10) {
|
|
690
|
+
const net = await import("net");
|
|
691
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
692
|
+
const testPort = startPort + attempt;
|
|
693
|
+
const isAvailable = await new Promise((resolve4) => {
|
|
694
|
+
const server = net.createServer();
|
|
695
|
+
server.once("error", () => {
|
|
696
|
+
resolve4(false);
|
|
697
|
+
});
|
|
698
|
+
server.once("listening", () => {
|
|
699
|
+
server.close();
|
|
700
|
+
resolve4(true);
|
|
701
|
+
});
|
|
702
|
+
server.listen(testPort, "127.0.0.1");
|
|
703
|
+
});
|
|
704
|
+
if (isAvailable) {
|
|
705
|
+
if (attempt > 0) {
|
|
706
|
+
console.error(`Port ${startPort} in use, using port ${testPort} instead`);
|
|
707
|
+
}
|
|
708
|
+
return testPort;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
throw new Error(`No available ports found between ${startPort} and ${startPort + maxAttempts - 1}`);
|
|
712
|
+
}
|
|
713
|
+
async function serveWhatIfViz(currentVizData, simulatedVizData, simulationResult, operation, target) {
|
|
714
|
+
const availablePort = await findAvailablePort2(3335);
|
|
715
|
+
const app = express2();
|
|
716
|
+
app.get("/", (_req, res) => {
|
|
717
|
+
const html = generateWhatIfHtml(currentVizData, simulatedVizData, simulationResult, operation, target);
|
|
718
|
+
res.type("html").send(html);
|
|
719
|
+
});
|
|
720
|
+
app.get("/favicon.ico", (_req, res) => {
|
|
721
|
+
res.sendFile(join3(__dirname2, "..", "..", "icon.png"));
|
|
722
|
+
});
|
|
723
|
+
const publicDir = join3(__dirname2, "viz", "public");
|
|
724
|
+
app.use(express2.static(publicDir));
|
|
725
|
+
app.get("/api/graph", (_req, res) => {
|
|
726
|
+
res.json(currentVizData);
|
|
727
|
+
});
|
|
728
|
+
app.get("/api/current", (_req, res) => {
|
|
729
|
+
res.json(currentVizData);
|
|
730
|
+
});
|
|
731
|
+
app.get("/api/simulated", (_req, res) => {
|
|
732
|
+
res.json(simulatedVizData);
|
|
733
|
+
});
|
|
734
|
+
app.get("/api/result", (_req, res) => {
|
|
735
|
+
res.json(simulationResult);
|
|
736
|
+
});
|
|
737
|
+
const server = app.listen(availablePort, "127.0.0.1", () => {
|
|
738
|
+
const url = `http://127.0.0.1:${availablePort}`;
|
|
739
|
+
console.error(`
|
|
740
|
+
Opening What If UI at ${url}`);
|
|
741
|
+
console.error("Press Ctrl+C to stop\n");
|
|
742
|
+
open2(url);
|
|
743
|
+
});
|
|
744
|
+
process.on("SIGINT", () => {
|
|
745
|
+
console.error("\nShutting down What If server...");
|
|
746
|
+
server.close(() => {
|
|
747
|
+
process.exit(0);
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/commands/whatif.ts
|
|
505
753
|
async function whatif(dir, options) {
|
|
506
754
|
if (!options.simulate) {
|
|
507
|
-
|
|
508
|
-
console.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
console.
|
|
755
|
+
const projectRoot2 = dir === "." ? findProjectRoot() : resolve(dir);
|
|
756
|
+
console.error(`Parsing project: ${projectRoot2}`);
|
|
757
|
+
const parsedFiles2 = await parseProject(projectRoot2);
|
|
758
|
+
const graph2 = buildGraph(parsedFiles2);
|
|
759
|
+
console.error(`Built graph: ${graph2.order} symbols, ${graph2.size} edges`);
|
|
760
|
+
const vizData = prepareVizData(graph2, projectRoot2);
|
|
761
|
+
const emptyResult = {
|
|
762
|
+
action: { type: "delete", target: "" },
|
|
763
|
+
originalGraph: { nodeCount: graph2.order, edgeCount: graph2.size, healthScore: 0 },
|
|
764
|
+
simulatedGraph: { nodeCount: graph2.order, edgeCount: graph2.size, healthScore: 0 },
|
|
765
|
+
diff: { addedEdges: [], removedEdges: [], affectedNodes: [], brokenImports: [], circularDepsIntroduced: [], circularDepsResolved: [] },
|
|
766
|
+
healthDelta: { before: 0, after: 0, delta: 0, improved: false, dimensionChanges: [] }
|
|
767
|
+
};
|
|
768
|
+
await serveWhatIfViz(vizData, vizData, emptyResult, "none", "");
|
|
512
769
|
return;
|
|
513
770
|
}
|
|
514
771
|
const validActions = ["move", "delete", "rename", "split", "merge"];
|
|
@@ -522,11 +779,11 @@ async function whatif(dir, options) {
|
|
|
522
779
|
}
|
|
523
780
|
const action = buildAction(options);
|
|
524
781
|
const projectRoot = dir === "." ? findProjectRoot() : resolve(dir);
|
|
525
|
-
console.
|
|
782
|
+
console.error(`Parsing project: ${projectRoot}`);
|
|
526
783
|
const parsedFiles = await parseProject(projectRoot);
|
|
527
784
|
const graph = buildGraph(parsedFiles);
|
|
528
|
-
console.
|
|
529
|
-
console.
|
|
785
|
+
console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
786
|
+
console.error("");
|
|
530
787
|
const engine = new SimulationEngine(graph);
|
|
531
788
|
try {
|
|
532
789
|
const result = engine.simulate(action);
|
|
@@ -631,18 +888,198 @@ function formatAction(action) {
|
|
|
631
888
|
}
|
|
632
889
|
}
|
|
633
890
|
|
|
891
|
+
// src/commands/security.ts
|
|
892
|
+
import { resolve as resolve2, dirname as dirname3, join as join4 } from "path";
|
|
893
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
894
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
895
|
+
|
|
896
|
+
// src/security/reporter.ts
|
|
897
|
+
import chalk2 from "chalk";
|
|
898
|
+
var SEVERITY_COLORS = {
|
|
899
|
+
critical: chalk2.red.bold,
|
|
900
|
+
high: chalk2.red,
|
|
901
|
+
medium: chalk2.yellow,
|
|
902
|
+
low: chalk2.blue,
|
|
903
|
+
info: chalk2.dim
|
|
904
|
+
};
|
|
905
|
+
var SEVERITY_LABELS = {
|
|
906
|
+
critical: "CRITICAL",
|
|
907
|
+
high: "HIGH",
|
|
908
|
+
medium: "MEDIUM",
|
|
909
|
+
low: "LOW",
|
|
910
|
+
info: "INFO"
|
|
911
|
+
};
|
|
912
|
+
function formatTable(result, elapsedMs) {
|
|
913
|
+
const lines = [];
|
|
914
|
+
const sep = "\u2500".repeat(62);
|
|
915
|
+
lines.push("");
|
|
916
|
+
lines.push(chalk2.bold("Depwire Security Scan"));
|
|
917
|
+
lines.push("");
|
|
918
|
+
const summaryParts = [
|
|
919
|
+
result.summary.critical > 0 ? chalk2.red.bold(`${result.summary.critical} Critical`) : null,
|
|
920
|
+
result.summary.high > 0 ? chalk2.red(`${result.summary.high} High`) : null,
|
|
921
|
+
result.summary.medium > 0 ? chalk2.yellow(`${result.summary.medium} Medium`) : null,
|
|
922
|
+
result.summary.low > 0 ? chalk2.blue(`${result.summary.low} Low`) : null,
|
|
923
|
+
result.summary.info > 0 ? chalk2.dim(`${result.summary.info} Info`) : null
|
|
924
|
+
].filter(Boolean);
|
|
925
|
+
if (summaryParts.length > 0) {
|
|
926
|
+
lines.push(`\u250C${sep}\u2510`);
|
|
927
|
+
lines.push(`\u2502 ${summaryParts.join(" \u2502 ")} \u2502`);
|
|
928
|
+
lines.push(`\u2514${sep}\u2518`);
|
|
929
|
+
} else {
|
|
930
|
+
lines.push(chalk2.green.bold(" No security findings detected."));
|
|
931
|
+
}
|
|
932
|
+
lines.push("");
|
|
933
|
+
const severityOrder = ["critical", "high", "medium", "low", "info"];
|
|
934
|
+
for (const severity of severityOrder) {
|
|
935
|
+
const group = result.findings.filter((f) => f.severity === severity);
|
|
936
|
+
if (group.length === 0) continue;
|
|
937
|
+
const colorFn = SEVERITY_COLORS[severity];
|
|
938
|
+
lines.push(colorFn(SEVERITY_LABELS[severity]));
|
|
939
|
+
for (const finding of group) {
|
|
940
|
+
lines.push(` ${colorFn(`[${finding.id}]`)} ${finding.title}`);
|
|
941
|
+
lines.push(` File: ${finding.file}${finding.line ? `:${finding.line}` : ""}`);
|
|
942
|
+
lines.push(` ${chalk2.dim(finding.description)}`);
|
|
943
|
+
lines.push(` ${chalk2.dim("Fix:")} ${finding.suggestedFix}`);
|
|
944
|
+
if (finding.graphReachability?.elevatedBy) {
|
|
945
|
+
lines.push(` ${chalk2.magenta("\u2191 Elevated:")} ${finding.graphReachability.elevatedBy}`);
|
|
946
|
+
}
|
|
947
|
+
lines.push("");
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const elapsed = (elapsedMs / 1e3).toFixed(1);
|
|
951
|
+
lines.push(chalk2.dim(`Scanned ${result.filesScanned} files in ${elapsed}s`));
|
|
952
|
+
lines.push(chalk2.dim("Run with --format json for machine output"));
|
|
953
|
+
lines.push(chalk2.dim("Run with --format sarif for GitHub Security integration"));
|
|
954
|
+
lines.push("");
|
|
955
|
+
return lines.join("\n");
|
|
956
|
+
}
|
|
957
|
+
function formatJSON(result) {
|
|
958
|
+
return JSON.stringify(result, null, 2);
|
|
959
|
+
}
|
|
960
|
+
function formatSARIF(result, version) {
|
|
961
|
+
const rules = result.findings.map((f) => ({
|
|
962
|
+
id: f.id,
|
|
963
|
+
shortDescription: { text: f.title },
|
|
964
|
+
fullDescription: { text: f.description },
|
|
965
|
+
help: { text: f.suggestedFix },
|
|
966
|
+
properties: {
|
|
967
|
+
severity: f.severity,
|
|
968
|
+
vulnerabilityClass: f.vulnerabilityClass
|
|
969
|
+
}
|
|
970
|
+
}));
|
|
971
|
+
const uniqueRules = Array.from(
|
|
972
|
+
new Map(rules.map((r) => [r.id, r])).values()
|
|
973
|
+
);
|
|
974
|
+
const results = result.findings.map((f) => {
|
|
975
|
+
let level;
|
|
976
|
+
if (f.severity === "critical" || f.severity === "high") level = "error";
|
|
977
|
+
else if (f.severity === "medium") level = "warning";
|
|
978
|
+
else level = "note";
|
|
979
|
+
const sarifResult = {
|
|
980
|
+
ruleId: f.id,
|
|
981
|
+
level,
|
|
982
|
+
message: { text: `${f.title}: ${f.description}` },
|
|
983
|
+
locations: [
|
|
984
|
+
{
|
|
985
|
+
physicalLocation: {
|
|
986
|
+
artifactLocation: { uri: f.file },
|
|
987
|
+
region: f.line ? { startLine: f.line } : void 0
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
]
|
|
991
|
+
};
|
|
992
|
+
return sarifResult;
|
|
993
|
+
});
|
|
994
|
+
const sarif = {
|
|
995
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
996
|
+
version: "2.1.0",
|
|
997
|
+
runs: [
|
|
998
|
+
{
|
|
999
|
+
tool: {
|
|
1000
|
+
driver: {
|
|
1001
|
+
name: "depwire",
|
|
1002
|
+
version,
|
|
1003
|
+
rules: uniqueRules
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
results
|
|
1007
|
+
}
|
|
1008
|
+
]
|
|
1009
|
+
};
|
|
1010
|
+
return JSON.stringify(sarif, null, 2);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/commands/security.ts
|
|
1014
|
+
var __filename3 = fileURLToPath3(import.meta.url);
|
|
1015
|
+
var __dirname3 = dirname3(__filename3);
|
|
1016
|
+
function getVersion() {
|
|
1017
|
+
try {
|
|
1018
|
+
let dir = __dirname3;
|
|
1019
|
+
for (let i = 0; i < 5; i++) {
|
|
1020
|
+
const pkgPath = join4(dir, "package.json");
|
|
1021
|
+
try {
|
|
1022
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
1023
|
+
if (pkg.name === "depwire-cli") return pkg.version;
|
|
1024
|
+
} catch {
|
|
1025
|
+
}
|
|
1026
|
+
dir = dirname3(dir);
|
|
1027
|
+
}
|
|
1028
|
+
} catch {
|
|
1029
|
+
}
|
|
1030
|
+
return "0.0.0";
|
|
1031
|
+
}
|
|
1032
|
+
var SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"];
|
|
1033
|
+
async function securityCommand(dir, options) {
|
|
1034
|
+
const projectRoot = dir === "." ? findProjectRoot() : resolve2(dir);
|
|
1035
|
+
console.error(`Scanning: ${projectRoot}`);
|
|
1036
|
+
const startTime = Date.now();
|
|
1037
|
+
const parsedFiles = await parseProject(projectRoot);
|
|
1038
|
+
console.error(`Parsed ${parsedFiles.length} files`);
|
|
1039
|
+
const graph = buildGraph(parsedFiles);
|
|
1040
|
+
console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
1041
|
+
const result = await scanSecurity(projectRoot, graph, {
|
|
1042
|
+
target: options.target,
|
|
1043
|
+
classes: options.class,
|
|
1044
|
+
format: options.format || "table",
|
|
1045
|
+
graphAware: true
|
|
1046
|
+
});
|
|
1047
|
+
const elapsedMs = Date.now() - startTime;
|
|
1048
|
+
const format = options.format || "table";
|
|
1049
|
+
if (format === "json") {
|
|
1050
|
+
console.log(formatJSON(result));
|
|
1051
|
+
} else if (format === "sarif") {
|
|
1052
|
+
console.log(formatSARIF(result, getVersion()));
|
|
1053
|
+
} else {
|
|
1054
|
+
console.log(formatTable(result, elapsedMs));
|
|
1055
|
+
}
|
|
1056
|
+
if (options.failOn) {
|
|
1057
|
+
const threshold = options.failOn;
|
|
1058
|
+
const thresholdIdx = SEVERITY_ORDER.indexOf(threshold);
|
|
1059
|
+
if (thresholdIdx >= 0) {
|
|
1060
|
+
const hasFindings = result.findings.some(
|
|
1061
|
+
(f) => SEVERITY_ORDER.indexOf(f.severity) <= thresholdIdx
|
|
1062
|
+
);
|
|
1063
|
+
if (hasFindings) {
|
|
1064
|
+
console.error(`Findings at or above ${threshold} severity detected \u2014 exiting with code 1`);
|
|
1065
|
+
process.exit(1);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
634
1071
|
// src/index.ts
|
|
635
|
-
var
|
|
636
|
-
var
|
|
637
|
-
var packageJsonPath =
|
|
638
|
-
var packageJson = JSON.parse(
|
|
1072
|
+
var __filename4 = fileURLToPath4(import.meta.url);
|
|
1073
|
+
var __dirname4 = dirname4(__filename4);
|
|
1074
|
+
var packageJsonPath = join5(__dirname4, "../package.json");
|
|
1075
|
+
var packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
|
|
639
1076
|
var program = new Command();
|
|
640
1077
|
program.name("depwire").description("Code cross-reference graph builder for TypeScript projects").version(packageJson.version);
|
|
641
1078
|
program.command("parse").description("Parse a TypeScript project and build dependency graph").argument("[directory]", "Project directory to parse (defaults to current directory or auto-detected project root)").option("-o, --output <path>", "Output JSON file path", "depwire-output.json").option("--pretty", "Pretty-print JSON output").option("--stats", "Print summary statistics").option("--exclude <patterns...>", 'Glob patterns to exclude (e.g., "**/*.test.*" "dist/**")').option("--verbose", "Show detailed parsing progress").action(async (directory, options) => {
|
|
642
1079
|
trackCommand("parse", packageJson.version);
|
|
643
1080
|
const startTime = Date.now();
|
|
644
1081
|
try {
|
|
645
|
-
const projectRoot = directory ?
|
|
1082
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
646
1083
|
console.log(`Parsing project: ${projectRoot}`);
|
|
647
1084
|
const parsedFiles = await parseProject(projectRoot, {
|
|
648
1085
|
exclude: options.exclude,
|
|
@@ -681,12 +1118,12 @@ Orphan Files (no cross-references): ${summary.orphanFiles.length}`);
|
|
|
681
1118
|
program.command("query").description("Query impact analysis for a symbol").argument("<directory>", "Project directory").argument("<symbol-name>", "Symbol name to query").action(async (directory, symbolName) => {
|
|
682
1119
|
trackCommand("query", packageJson.version);
|
|
683
1120
|
try {
|
|
684
|
-
const projectRoot =
|
|
1121
|
+
const projectRoot = resolve3(directory);
|
|
685
1122
|
const cacheFile = "depwire-output.json";
|
|
686
1123
|
let graph;
|
|
687
1124
|
if (existsSync(cacheFile)) {
|
|
688
1125
|
console.log("Loading from cache...");
|
|
689
|
-
const json = JSON.parse(
|
|
1126
|
+
const json = JSON.parse(readFileSync3(cacheFile, "utf-8"));
|
|
690
1127
|
graph = importFromJSON(json);
|
|
691
1128
|
} else {
|
|
692
1129
|
console.log("Parsing project...");
|
|
@@ -730,7 +1167,7 @@ Total Transitive Dependents: ${impact.transitiveDependents.length}`);
|
|
|
730
1167
|
program.command("viz").description("Launch interactive arc diagram visualization").argument("[directory]", "Project directory to visualize (defaults to current directory or auto-detected project root)").option("-p, --port <number>", "Server port", "3333").option("--no-open", "Don't auto-open browser").option("--exclude <patterns...>", 'Glob patterns to exclude (e.g., "**/*.test.*" "dist/**")').option("--verbose", "Show detailed parsing progress").action(async (directory, options) => {
|
|
731
1168
|
trackCommand("viz", packageJson.version);
|
|
732
1169
|
try {
|
|
733
|
-
const projectRoot = directory ?
|
|
1170
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
734
1171
|
console.log(`Parsing project: ${projectRoot}`);
|
|
735
1172
|
const parsedFiles = await parseProject(projectRoot, {
|
|
736
1173
|
exclude: options.exclude,
|
|
@@ -753,7 +1190,7 @@ program.command("viz").description("Launch interactive arc diagram visualization
|
|
|
753
1190
|
program.command("temporal").description("Visualize how the dependency graph evolved over git history").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--commits <number>", "Number of commits to sample", "20").option("--strategy <type>", "Sampling strategy: even, weekly, monthly", "even").option("-p, --port <number>", "Server port", "3334").option("--output <path>", "Save snapshots to custom path (default: .depwire/temporal/)").option("--verbose", "Show progress for each commit being parsed").option("--stats", "Show summary statistics at end").action(async (directory, options) => {
|
|
754
1191
|
trackCommand("temporal", packageJson.version);
|
|
755
1192
|
try {
|
|
756
|
-
const projectRoot = directory ?
|
|
1193
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
757
1194
|
await runTemporalAnalysis(projectRoot, {
|
|
758
1195
|
commits: parseInt(options.commits, 10),
|
|
759
1196
|
strategy: options.strategy,
|
|
@@ -773,11 +1210,11 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
|
|
|
773
1210
|
const state = createEmptyState();
|
|
774
1211
|
let projectRootToConnect = null;
|
|
775
1212
|
if (directory) {
|
|
776
|
-
projectRootToConnect =
|
|
1213
|
+
projectRootToConnect = resolve3(directory);
|
|
777
1214
|
} else {
|
|
778
1215
|
const detectedRoot = findProjectRoot();
|
|
779
1216
|
const cwd = process.cwd();
|
|
780
|
-
if (detectedRoot !== cwd || existsSync(
|
|
1217
|
+
if (detectedRoot !== cwd || existsSync(join5(cwd, "package.json")) || existsSync(join5(cwd, "tsconfig.json")) || existsSync(join5(cwd, "go.mod")) || existsSync(join5(cwd, "pyproject.toml")) || existsSync(join5(cwd, "setup.py")) || existsSync(join5(cwd, ".git"))) {
|
|
781
1218
|
projectRootToConnect = detectedRoot;
|
|
782
1219
|
}
|
|
783
1220
|
}
|
|
@@ -834,8 +1271,8 @@ program.command("docs").description("Generate comprehensive codebase documentati
|
|
|
834
1271
|
trackCommand("docs", packageJson.version);
|
|
835
1272
|
const startTime = Date.now();
|
|
836
1273
|
try {
|
|
837
|
-
const projectRoot = directory ?
|
|
838
|
-
const outputDir = options.output ?
|
|
1274
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
1275
|
+
const outputDir = options.output ? resolve3(options.output) : join5(projectRoot, ".depwire");
|
|
839
1276
|
const includeList = options.include.split(",").map((s) => s.trim());
|
|
840
1277
|
const onlyList = options.only ? options.only.split(",").map((s) => s.trim()) : void 0;
|
|
841
1278
|
if (options.gitignore === void 0 && !existsSyncNode(outputDir)) {
|
|
@@ -897,16 +1334,16 @@ async function promptGitignore() {
|
|
|
897
1334
|
input: process.stdin,
|
|
898
1335
|
output: process.stdout
|
|
899
1336
|
});
|
|
900
|
-
return new Promise((
|
|
1337
|
+
return new Promise((resolve4) => {
|
|
901
1338
|
rl.question("Add .depwire/ to .gitignore? [Y/n] ", (answer) => {
|
|
902
1339
|
rl.close();
|
|
903
1340
|
const normalized = answer.trim().toLowerCase();
|
|
904
|
-
|
|
1341
|
+
resolve4(normalized === "" || normalized === "y" || normalized === "yes");
|
|
905
1342
|
});
|
|
906
1343
|
});
|
|
907
1344
|
}
|
|
908
1345
|
function addToGitignore(projectRoot, pattern) {
|
|
909
|
-
const gitignorePath =
|
|
1346
|
+
const gitignorePath = join5(projectRoot, ".gitignore");
|
|
910
1347
|
try {
|
|
911
1348
|
let content = "";
|
|
912
1349
|
if (existsSyncNode(gitignorePath)) {
|
|
@@ -931,7 +1368,7 @@ ${pattern}
|
|
|
931
1368
|
program.command("health").description("Analyze dependency architecture health (0-100 score)").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--json", "Output as JSON").option("--verbose", "Show detailed breakdown").action(async (directory, options) => {
|
|
932
1369
|
trackCommand("health", packageJson.version);
|
|
933
1370
|
try {
|
|
934
|
-
const projectRoot = directory ?
|
|
1371
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
935
1372
|
const startTime = Date.now();
|
|
936
1373
|
const parsedFiles = await parseProject(projectRoot);
|
|
937
1374
|
const graph = buildGraph(parsedFiles);
|
|
@@ -955,7 +1392,7 @@ program.command("health").description("Analyze dependency architecture health (0
|
|
|
955
1392
|
program.command("dead-code").description("Identify dead code - symbols defined but never referenced").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--confidence <level>", "Minimum confidence level to show: high, medium, low (default: medium)", "medium").option("--json", "Output as JSON (for CI/automation)").option("--verbose", "Show detailed info for each dead symbol").option("--stats", "Show summary statistics").option("--include-tests", "Include test files in analysis").option("--include-low", "Shortcut for --confidence low").option("--debug", "Show debug information (exclusion stats)").action(async (directory, options) => {
|
|
956
1393
|
trackCommand("dead-code", packageJson.version);
|
|
957
1394
|
try {
|
|
958
|
-
const projectRoot = directory ?
|
|
1395
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
959
1396
|
const startTime = Date.now();
|
|
960
1397
|
const parsedFiles = await parseProject(projectRoot);
|
|
961
1398
|
const graph = buildGraph(parsedFiles);
|
|
@@ -991,4 +1428,13 @@ program.command("whatif").description("Simulate architectural changes before tou
|
|
|
991
1428
|
process.exit(1);
|
|
992
1429
|
}
|
|
993
1430
|
});
|
|
1431
|
+
program.command("security").description("Scan codebase for security vulnerabilities (deterministic, no API key required)").argument("[directory]", "Project directory to scan (defaults to current directory or auto-detected project root)").option("--target <file>", "Scan a single file instead of the whole repo").option("--class <classes...>", "Only run specific vulnerability class checks").option("--format <format>", "Output format: table (default), json, sarif", "table").option("--fail-on <level>", "Exit with code 1 if findings at this severity or above").action(async (directory, options) => {
|
|
1432
|
+
trackCommand("security", packageJson.version);
|
|
1433
|
+
try {
|
|
1434
|
+
await securityCommand(directory || ".", options);
|
|
1435
|
+
} catch (err) {
|
|
1436
|
+
console.error("Error running security scan:", err);
|
|
1437
|
+
process.exit(1);
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
994
1440
|
program.parse();
|
package/dist/mcpb-entry.js
CHANGED
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
startMcpServer,
|
|
5
5
|
updateFileInGraph,
|
|
6
6
|
watchProject
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-B2KGFBZL.js";
|
|
8
8
|
import {
|
|
9
9
|
buildGraph,
|
|
10
10
|
parseProject
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-YYY5TNG7.js";
|
|
12
12
|
|
|
13
13
|
// src/mcpb-entry.ts
|
|
14
14
|
import { resolve } from "path";
|