@alimaandev/ripple 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -67
- package/dist/bin.js +423 -62
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-LDCFNORX.js → chunk-CFH4UW2E.js} +8 -4
- package/dist/chunk-CFH4UW2E.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1 -1
- package/package.json +13 -6
- package/ripple.schema.json +123 -0
- package/dist/chunk-LDCFNORX.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { rippleConfigSchema, DEFAULT_CONFIG, formatSchemaIssues } from './chunk-CFH4UW2E.js';
|
|
3
3
|
import { CommanderError, Command } from 'commander';
|
|
4
4
|
import { createJiti } from 'jiti';
|
|
5
5
|
import path8 from 'path';
|
|
@@ -9,9 +9,9 @@ import { Project, ts, SyntaxKind } from 'ts-morph';
|
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import figures from 'figures';
|
|
11
11
|
import treeify from 'treeify';
|
|
12
|
-
import boxen from 'boxen';
|
|
13
12
|
import fg from 'fast-glob';
|
|
14
13
|
import ora from 'ora';
|
|
14
|
+
import picomatch from 'picomatch';
|
|
15
15
|
import { execFileSync } from 'child_process';
|
|
16
16
|
|
|
17
17
|
// src/output/writer.ts
|
|
@@ -507,7 +507,7 @@ function reverseTraverse(graph, targetKey, options) {
|
|
|
507
507
|
const { maxDepth, categorize } = options;
|
|
508
508
|
const affected = /* @__PURE__ */ new Map();
|
|
509
509
|
const visited = /* @__PURE__ */ new Set([targetKey]);
|
|
510
|
-
const
|
|
510
|
+
const cycleMembers2 = cycleMemberPaths(graph);
|
|
511
511
|
if (!graph.reverse.has(targetKey)) {
|
|
512
512
|
return { affected, maxDepth: 0 };
|
|
513
513
|
}
|
|
@@ -529,7 +529,7 @@ function reverseTraverse(graph, targetKey, options) {
|
|
|
529
529
|
depth: current.depth,
|
|
530
530
|
direct: current.depth === 1,
|
|
531
531
|
categories: categorize(filePath),
|
|
532
|
-
inCycle:
|
|
532
|
+
inCycle: cycleMembers2.has(current.key)
|
|
533
533
|
});
|
|
534
534
|
if (maxDepth === void 0 || current.depth < maxDepth) {
|
|
535
535
|
for (const importer of graph.reverse.get(current.key) ?? []) {
|
|
@@ -786,24 +786,17 @@ function renderTree(nodes) {
|
|
|
786
786
|
}
|
|
787
787
|
return treeify.asTree(root, false, false).trimEnd();
|
|
788
788
|
}
|
|
789
|
-
var DIVIDER_WIDTH = 38;
|
|
790
789
|
function brandHeader(options) {
|
|
791
790
|
const { label, version, style } = options;
|
|
792
791
|
if (!style.color) {
|
|
793
792
|
return `ripple \u2014 ${label}${version ? ` \xB7 v${version}` : ""}`;
|
|
794
793
|
}
|
|
795
|
-
const
|
|
796
|
-
return
|
|
797
|
-
padding: { left: 1, right: 1 },
|
|
798
|
-
title: "ripple",
|
|
799
|
-
borderStyle: "round",
|
|
800
|
-
borderColor: "cyan"
|
|
801
|
-
});
|
|
794
|
+
const title = chalk.bold.cyan(`ripple \xB7 ${label}`);
|
|
795
|
+
return `${title}${version ? ` ${chalk.dim(`\xB7 v${version}`)}` : ""}`;
|
|
802
796
|
}
|
|
803
797
|
function sectionHeader(title, style) {
|
|
804
798
|
if (!style.color) return title;
|
|
805
|
-
return `${chalk.dim("\
|
|
806
|
-
${chalk.bold(title)}`;
|
|
799
|
+
return `${chalk.dim("\u203A")} ${chalk.bold(title)}`;
|
|
807
800
|
}
|
|
808
801
|
|
|
809
802
|
// src/ui/gauge.ts
|
|
@@ -1392,7 +1385,7 @@ function buildGraph(project, filePaths, context) {
|
|
|
1392
1385
|
const reverse = /* @__PURE__ */ new Map();
|
|
1393
1386
|
const external = /* @__PURE__ */ new Map();
|
|
1394
1387
|
let parsedCount = 0;
|
|
1395
|
-
let
|
|
1388
|
+
let internalEdges2 = 0;
|
|
1396
1389
|
let unresolvedEdges = 0;
|
|
1397
1390
|
let externalEdges = 0;
|
|
1398
1391
|
for (const parsed of parsedFiles) {
|
|
@@ -1412,7 +1405,7 @@ function buildGraph(project, filePaths, context) {
|
|
|
1412
1405
|
parsed.imports.map((decl) => decl.raw),
|
|
1413
1406
|
context
|
|
1414
1407
|
);
|
|
1415
|
-
|
|
1408
|
+
internalEdges2 += internal.length;
|
|
1416
1409
|
unresolvedEdges += unresolved.length;
|
|
1417
1410
|
externalEdges += externalSpecifiers.length;
|
|
1418
1411
|
for (const target of internal) {
|
|
@@ -1437,7 +1430,7 @@ function buildGraph(project, filePaths, context) {
|
|
|
1437
1430
|
stats: {
|
|
1438
1431
|
files: parsedFiles.length,
|
|
1439
1432
|
parsed: parsedCount,
|
|
1440
|
-
internalEdges,
|
|
1433
|
+
internalEdges: internalEdges2,
|
|
1441
1434
|
unresolvedEdges,
|
|
1442
1435
|
externalEdges,
|
|
1443
1436
|
cycles: 0
|
|
@@ -1518,6 +1511,150 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1518
1511
|
return ExitCode.Success;
|
|
1519
1512
|
}
|
|
1520
1513
|
|
|
1514
|
+
// src/formatter/graph-export.ts
|
|
1515
|
+
var escapeDot = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1516
|
+
var escapeMermaid = (value) => value.replace(/#/g, "#35;").replace(/"/g, "#quot;");
|
|
1517
|
+
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1518
|
+
function sortedKeys(input) {
|
|
1519
|
+
return [...input.nodes.keys()].sort((a, b) => {
|
|
1520
|
+
const left = input.nodes.get(a) ?? a;
|
|
1521
|
+
const right = input.nodes.get(b) ?? b;
|
|
1522
|
+
return left.localeCompare(right);
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
var nodeLabel = (input, key) => displayPath(input.nodes.get(key) ?? key, input.cwd);
|
|
1526
|
+
function cycleMembers(input) {
|
|
1527
|
+
const present = /* @__PURE__ */ new Set();
|
|
1528
|
+
for (const cycle of input.cycles) {
|
|
1529
|
+
for (const member of cycle.members) {
|
|
1530
|
+
if (input.nodes.has(member)) present.add(member);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return [...present].sort((a, b) => nodeLabel(input, a).localeCompare(nodeLabel(input, b)));
|
|
1534
|
+
}
|
|
1535
|
+
function internalEdges(input) {
|
|
1536
|
+
const pairs = [];
|
|
1537
|
+
for (const [source, targets] of input.edges) {
|
|
1538
|
+
if (!input.nodes.has(source)) continue;
|
|
1539
|
+
for (const target of targets) {
|
|
1540
|
+
if (!input.nodes.has(target)) continue;
|
|
1541
|
+
pairs.push([source, target]);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return pairs.sort((a, b) => {
|
|
1545
|
+
const left = nodeLabel(input, a[0]).localeCompare(nodeLabel(input, b[0]));
|
|
1546
|
+
return left !== 0 ? left : nodeLabel(input, a[1]).localeCompare(nodeLabel(input, b[1]));
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
function renderMermaidGraph(input) {
|
|
1550
|
+
const keys = sortedKeys(input);
|
|
1551
|
+
const idByKey = new Map(keys.map((key, index) => [key, `n${index}`]));
|
|
1552
|
+
const lines = ["flowchart LR"];
|
|
1553
|
+
lines.push(" classDef cycle fill:#4a2525,stroke:#f85149,color:#f0b3b3;");
|
|
1554
|
+
for (const key of keys) {
|
|
1555
|
+
lines.push(` ${idByKey.get(key)}["${escapeMermaid(nodeLabel(input, key))}"]`);
|
|
1556
|
+
}
|
|
1557
|
+
for (const [source, target] of internalEdges(input)) {
|
|
1558
|
+
lines.push(` ${idByKey.get(source)} --> ${idByKey.get(target)}`);
|
|
1559
|
+
}
|
|
1560
|
+
const members = cycleMembers(input).map((key) => idByKey.get(key)).filter((id) => id !== void 0);
|
|
1561
|
+
if (members.length > 0) {
|
|
1562
|
+
lines.push(` class ${members.join(",")} cycle;`);
|
|
1563
|
+
}
|
|
1564
|
+
return `${lines.join("\n")}
|
|
1565
|
+
`;
|
|
1566
|
+
}
|
|
1567
|
+
function renderDotGraph(input) {
|
|
1568
|
+
const lines = [
|
|
1569
|
+
"digraph ripple {",
|
|
1570
|
+
" rankdir=LR;",
|
|
1571
|
+
' node [shape=box, style="rounded,filled", fillcolor="#161b22", color="#30363d", fontcolor="#e6edf3"];',
|
|
1572
|
+
' edge [color="#8b949e"];'
|
|
1573
|
+
];
|
|
1574
|
+
for (const key of sortedKeys(input)) {
|
|
1575
|
+
const label = nodeLabel(input, key);
|
|
1576
|
+
lines.push(` "${escapeDot(label)}" [label="${escapeDot(label)}"];`);
|
|
1577
|
+
}
|
|
1578
|
+
for (const [source, target] of internalEdges(input)) {
|
|
1579
|
+
lines.push(
|
|
1580
|
+
` "${escapeDot(nodeLabel(input, source))}" -> "${escapeDot(nodeLabel(input, target))}";`
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
for (const key of cycleMembers(input)) {
|
|
1584
|
+
const label = nodeLabel(input, key);
|
|
1585
|
+
lines.push(` "${escapeDot(label)}" [color="#f85149", penwidth=1.5];`);
|
|
1586
|
+
}
|
|
1587
|
+
lines.push("}");
|
|
1588
|
+
return `${lines.join("\n")}
|
|
1589
|
+
`;
|
|
1590
|
+
}
|
|
1591
|
+
function renderHtmlGraph(input) {
|
|
1592
|
+
const keys = sortedKeys(input);
|
|
1593
|
+
const edges = internalEdges(input);
|
|
1594
|
+
const members = cycleMembers(input);
|
|
1595
|
+
const cycleLines = input.cycles.map(
|
|
1596
|
+
(cycle) => cycle.path.filter((key) => input.nodes.has(key)).map((key) => escapeHtml(nodeLabel(input, key))).join(" → ")
|
|
1597
|
+
).filter((line) => line.length > 0);
|
|
1598
|
+
const edgeRows = edges.map(
|
|
1599
|
+
([source, target]) => ` <tr><td>${escapeHtml(nodeLabel(input, source))}</td><td>→</td><td>${escapeHtml(nodeLabel(input, target))}</td></tr>`
|
|
1600
|
+
).join("\n");
|
|
1601
|
+
const cycleRows = cycleLines.length > 0 ? cycleLines.map((line) => ` <li><code>${line}</code></li>`).join("\n") : " <li><em>none</em></li>";
|
|
1602
|
+
return `<!DOCTYPE html>
|
|
1603
|
+
<html lang="en">
|
|
1604
|
+
<head>
|
|
1605
|
+
<meta charset="utf-8">
|
|
1606
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1607
|
+
<title>Ripple \u2014 dependency graph</title>
|
|
1608
|
+
<style>
|
|
1609
|
+
:root { color-scheme: dark; }
|
|
1610
|
+
body { margin: 0; padding: 2rem; background: #0d1117; color: #e6edf3;
|
|
1611
|
+
font: 15px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
1612
|
+
h1 { font-size: 1.4rem; margin: 0 0 .25rem; }
|
|
1613
|
+
h2 { font-size: 1.05rem; margin: 2rem 0 .5rem; color: #79c0ff; }
|
|
1614
|
+
code, pre { font-family: SFMono-Regular, Consolas, monospace; }
|
|
1615
|
+
pre { background: #161b22; border: 1px solid #30363d; border-radius: 8px;
|
|
1616
|
+
padding: 1rem; overflow-x: auto; font-size: .85rem; }
|
|
1617
|
+
table { border-collapse: collapse; width: 100%; font-size: .9rem; }
|
|
1618
|
+
th, td { text-align: left; padding: .35rem .6rem; border-bottom: 1px solid #21262d; }
|
|
1619
|
+
th { color: #8b949e; font-weight: 600; }
|
|
1620
|
+
.stats { display: flex; gap: 1.5rem; margin-top: 1rem; }
|
|
1621
|
+
.stat b { display: block; font-size: 1.6rem; color: #79c0ff; }
|
|
1622
|
+
.stat span { color: #8b949e; font-size: .8rem; }
|
|
1623
|
+
.muted { color: #8b949e; }
|
|
1624
|
+
a { color: #79c0ff; }
|
|
1625
|
+
</style>
|
|
1626
|
+
</head>
|
|
1627
|
+
<body>
|
|
1628
|
+
<h1>Ripple dependency graph</h1>
|
|
1629
|
+
<p class="muted">Exported with <code>ripple graph --format html</code> \u2014 deterministic and offline.</p>
|
|
1630
|
+
<div class="stats">
|
|
1631
|
+
<div class="stat"><b>${keys.length}</b><span>files</span></div>
|
|
1632
|
+
<div class="stat"><b>${edges.length}</b><span>edges</span></div>
|
|
1633
|
+
<div class="stat"><b>${input.cycles.length}</b><span>cycle groups</span></div>
|
|
1634
|
+
</div>
|
|
1635
|
+
|
|
1636
|
+
<h2>Circular dependencies</h2>
|
|
1637
|
+
<ul>${cycleRows}</ul>
|
|
1638
|
+
<p class="muted">Cycle members: ${members.map(escapeHtml).join(", ")}</p>
|
|
1639
|
+
|
|
1640
|
+
<h2>Graph</h2>
|
|
1641
|
+
<pre><code class="language-mermaid">${escapeHtml(renderMermaidGraph(input)).trimEnd()}</code></pre>
|
|
1642
|
+
<p class="muted">Render this diagram with the
|
|
1643
|
+
<a href="https://mermaid.live">Mermaid live editor</a> or a tool that
|
|
1644
|
+
understands Mermaid (GitHub markdown, docs sites, IDEs).</p>
|
|
1645
|
+
|
|
1646
|
+
<h2>Edges (${edges.length})</h2>
|
|
1647
|
+
<table>
|
|
1648
|
+
<thead><tr><th>Source</th><th></th><th>Target</th></tr></thead>
|
|
1649
|
+
<tbody>
|
|
1650
|
+
${edgeRows}
|
|
1651
|
+
</tbody>
|
|
1652
|
+
</table>
|
|
1653
|
+
</body>
|
|
1654
|
+
</html>
|
|
1655
|
+
`;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1521
1658
|
// src/commands/graph.ts
|
|
1522
1659
|
function walk(graph, startKey, edges, maxDepth, cwd) {
|
|
1523
1660
|
const seen = /* @__PURE__ */ new Set([startKey]);
|
|
@@ -1539,8 +1676,36 @@ function walk(graph, startKey, edges, maxDepth, cwd) {
|
|
|
1539
1676
|
}
|
|
1540
1677
|
return entries.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path)).map((entry) => entry.path);
|
|
1541
1678
|
}
|
|
1679
|
+
function reachableSubgraph(graph, startKey, reverse, maxDepth, cwd) {
|
|
1680
|
+
const edges = reverse ? graph.reverse : graph.forward;
|
|
1681
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1682
|
+
const visited = /* @__PURE__ */ new Set([startKey]);
|
|
1683
|
+
const queue = [{ key: startKey, depth: 0 }];
|
|
1684
|
+
const collectedEdges = /* @__PURE__ */ new Map();
|
|
1685
|
+
while (queue.length > 0) {
|
|
1686
|
+
const current = queue.shift();
|
|
1687
|
+
if (!current) continue;
|
|
1688
|
+
nodes.set(current.key, graph.nodes.get(current.key)?.path ?? current.key);
|
|
1689
|
+
if (maxDepth !== void 0 && current.depth >= maxDepth) continue;
|
|
1690
|
+
const targets = /* @__PURE__ */ new Set();
|
|
1691
|
+
for (const candidate of edges.get(current.key) ?? []) {
|
|
1692
|
+
targets.add(candidate);
|
|
1693
|
+
if (!visited.has(candidate)) {
|
|
1694
|
+
visited.add(candidate);
|
|
1695
|
+
queue.push({ key: candidate, depth: current.depth + 1 });
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
if (targets.size > 0) collectedEdges.set(current.key, targets);
|
|
1699
|
+
}
|
|
1700
|
+
return { nodes, edges: collectedEdges, cycles: graph.cycles, cwd };
|
|
1701
|
+
}
|
|
1702
|
+
function writeExport(format, input, writer) {
|
|
1703
|
+
const rendered = format === "mermaid" ? renderMermaidGraph(input) : format === "dot" ? renderDotGraph(input) : renderHtmlGraph(input);
|
|
1704
|
+
writer.write(rendered);
|
|
1705
|
+
}
|
|
1542
1706
|
async function graphCommand(fileArg, options, ctx2) {
|
|
1543
|
-
const
|
|
1707
|
+
const format = options.format ?? (options.json ? "json" : "terminal");
|
|
1708
|
+
const tracker = createStageTracker(format === "terminal");
|
|
1544
1709
|
tracker.next("Loading config");
|
|
1545
1710
|
const cwd = ctx2.cwd;
|
|
1546
1711
|
try {
|
|
@@ -1553,9 +1718,24 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1553
1718
|
nodes: graph.nodes,
|
|
1554
1719
|
cwd
|
|
1555
1720
|
};
|
|
1721
|
+
if (format === "mermaid" || format === "dot" || format === "html") {
|
|
1722
|
+
let exportInput;
|
|
1723
|
+
if (fileArg) {
|
|
1724
|
+
const targetPath2 = resolveTargetFile(fileArg, cwd);
|
|
1725
|
+
const { key: key2 } = requireNode(graph, targetPath2, context.rootDir);
|
|
1726
|
+
exportInput = reachableSubgraph(graph, key2, options.reverse, options.depth, cwd);
|
|
1727
|
+
} else {
|
|
1728
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1729
|
+
for (const [key2, node] of graph.nodes) nodes.set(key2, node.path);
|
|
1730
|
+
exportInput = { nodes, edges: graph.forward, cycles: graph.cycles, cwd };
|
|
1731
|
+
}
|
|
1732
|
+
tracker.done();
|
|
1733
|
+
writeExport(format, exportInput, ctx2.writer);
|
|
1734
|
+
return ExitCode.Success;
|
|
1735
|
+
}
|
|
1556
1736
|
if (!fileArg) {
|
|
1557
1737
|
tracker.done();
|
|
1558
|
-
if (
|
|
1738
|
+
if (format === "json") {
|
|
1559
1739
|
const report = buildGraphJsonReport(input, ctx2.version);
|
|
1560
1740
|
ctx2.writer.write(serializeJson(report));
|
|
1561
1741
|
} else {
|
|
@@ -1577,7 +1757,7 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1577
1757
|
const dependants = walk(graph, key, graph.forward, options.depth, cwd);
|
|
1578
1758
|
const dependents = walk(graph, key, graph.reverse, options.depth, cwd);
|
|
1579
1759
|
tracker.done();
|
|
1580
|
-
if (
|
|
1760
|
+
if (format === "json") {
|
|
1581
1761
|
const report = buildGraphJsonReport(
|
|
1582
1762
|
{
|
|
1583
1763
|
...input,
|
|
@@ -1720,29 +1900,72 @@ function renderResults(results, ctx2, color) {
|
|
|
1720
1900
|
}
|
|
1721
1901
|
ctx2.writer.writeLine();
|
|
1722
1902
|
}
|
|
1723
|
-
var
|
|
1903
|
+
var SCHEMA_REF = "./node_modules/@alimaandev/ripple/ripple.schema.json";
|
|
1904
|
+
function jsonPayload(full) {
|
|
1905
|
+
const payload = full ? { ...DEFAULT_CONFIG } : { include: DEFAULT_CONFIG.include };
|
|
1906
|
+
return { ...payload, $schema: SCHEMA_REF };
|
|
1907
|
+
}
|
|
1908
|
+
function tsValue(value, indent) {
|
|
1909
|
+
if (value === void 0) return "undefined";
|
|
1910
|
+
if (Array.isArray(value)) {
|
|
1911
|
+
if (value.length === 0) return "[]";
|
|
1912
|
+
const items = value.map((item) => tsValue(item, `${indent} `)).join(`,
|
|
1913
|
+
${indent} `);
|
|
1914
|
+
return `[
|
|
1915
|
+
${indent} ${items}
|
|
1916
|
+
${indent}]`;
|
|
1917
|
+
}
|
|
1918
|
+
if (typeof value === "object" && value !== null) {
|
|
1919
|
+
const entries = Object.entries(value).filter(
|
|
1920
|
+
([, entry]) => entry !== void 0
|
|
1921
|
+
);
|
|
1922
|
+
if (entries.length === 0) return "{}";
|
|
1923
|
+
const items = entries.map(([key, entry]) => `${key}: ${tsValue(entry, `${indent} `)}`).join(`,
|
|
1924
|
+
${indent} `);
|
|
1925
|
+
return `{
|
|
1926
|
+
${indent} ${items}
|
|
1927
|
+
${indent}}`;
|
|
1928
|
+
}
|
|
1929
|
+
return JSON.stringify(value);
|
|
1930
|
+
}
|
|
1931
|
+
function tsPayload(full) {
|
|
1932
|
+
if (!full) {
|
|
1933
|
+
return `import type { RippleConfig } from "@alimaandev/ripple";
|
|
1934
|
+
|
|
1935
|
+
const config: RippleConfig = {
|
|
1936
|
+
include: ${JSON.stringify(DEFAULT_CONFIG.include)},
|
|
1937
|
+
};
|
|
1938
|
+
|
|
1939
|
+
export default config;
|
|
1940
|
+
`;
|
|
1941
|
+
}
|
|
1942
|
+
const body = tsValue({ ...DEFAULT_CONFIG }, " ");
|
|
1943
|
+
return `import type { RippleConfig } from "@alimaandev/ripple";
|
|
1944
|
+
|
|
1945
|
+
const config: RippleConfig = ${body};
|
|
1946
|
+
|
|
1947
|
+
export default config;
|
|
1948
|
+
`;
|
|
1949
|
+
}
|
|
1724
1950
|
async function initCommand(options, ctx2) {
|
|
1725
1951
|
const style = { color: resolveColor(options.color) };
|
|
1726
|
-
const
|
|
1952
|
+
const fileName = options.ts ? "ripple.config.ts" : "ripple.config.json";
|
|
1953
|
+
const target = path8.join(ctx2.cwd, fileName);
|
|
1727
1954
|
try {
|
|
1728
1955
|
await fs.access(target);
|
|
1729
1956
|
if (!options.force) {
|
|
1730
1957
|
const mark = style.color ? chalk.red(icon("cross")) : icon("cross");
|
|
1731
|
-
ctx2.writer.writeLine(
|
|
1732
|
-
`${mark} ${INIT_FILENAME} already exists. Pass --force to overwrite it.`
|
|
1733
|
-
);
|
|
1958
|
+
ctx2.writer.writeLine(`${mark} ${fileName} already exists. Pass --force to overwrite it.`);
|
|
1734
1959
|
return ExitCode.Failure;
|
|
1735
1960
|
}
|
|
1736
1961
|
} catch {
|
|
1737
1962
|
}
|
|
1738
|
-
const
|
|
1739
|
-
|
|
1740
|
-
};
|
|
1963
|
+
const content = options.ts ? tsPayload(Boolean(options.full)) : `${JSON.stringify(jsonPayload(Boolean(options.full)), null, 2)}
|
|
1964
|
+
`;
|
|
1741
1965
|
try {
|
|
1742
|
-
await fs.writeFile(target,
|
|
1743
|
-
`, "utf8");
|
|
1966
|
+
await fs.writeFile(target, content, "utf8");
|
|
1744
1967
|
const mark = style.color ? chalk.green(icon("tick")) : icon("tick");
|
|
1745
|
-
ctx2.writer.writeLine(`${mark} Wrote ${
|
|
1968
|
+
ctx2.writer.writeLine(`${mark} Wrote ${fileName}`);
|
|
1746
1969
|
ctx2.writer.writeLine(
|
|
1747
1970
|
` ${dim("Edit it to tune include globs, aliases and risk weights.", style)}`
|
|
1748
1971
|
);
|
|
@@ -1750,7 +1973,7 @@ async function initCommand(options, ctx2) {
|
|
|
1750
1973
|
} catch (error) {
|
|
1751
1974
|
const mark = style.color ? chalk.red(icon("cross")) : icon("cross");
|
|
1752
1975
|
ctx2.writer.writeLine(
|
|
1753
|
-
`${mark} Could not write ${
|
|
1976
|
+
`${mark} Could not write ${fileName}: ${error instanceof Error ? error.message : String(error)}`
|
|
1754
1977
|
);
|
|
1755
1978
|
return ExitCode.Failure;
|
|
1756
1979
|
}
|
|
@@ -1806,9 +2029,48 @@ function changedFiles(cwd, base) {
|
|
|
1806
2029
|
return { baseLabel, files: [...files].sort() };
|
|
1807
2030
|
}
|
|
1808
2031
|
|
|
1809
|
-
// src/
|
|
2032
|
+
// src/formatter/annotations.ts
|
|
1810
2033
|
var LEVEL_INDEX = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
1811
2034
|
var GATE_INDEX = { medium: 1, high: 2, critical: 3 };
|
|
2035
|
+
function escapeProperty(value) {
|
|
2036
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
2037
|
+
}
|
|
2038
|
+
function escapeMessage(value) {
|
|
2039
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
2040
|
+
}
|
|
2041
|
+
function renderAnnotation(annotation) {
|
|
2042
|
+
const props = annotation.file ? ` file=${escapeProperty(annotation.file)},title=${escapeProperty(annotation.title)}` : ` title=${escapeProperty(annotation.title)}`;
|
|
2043
|
+
return `::${annotation.level}${props}::${escapeMessage(annotation.message)}`;
|
|
2044
|
+
}
|
|
2045
|
+
function buildFileAnnotations(files, gate) {
|
|
2046
|
+
return files.map(({ file, result }) => {
|
|
2047
|
+
const blocked = LEVEL_INDEX[result.risk.level] >= GATE_INDEX[gate];
|
|
2048
|
+
return {
|
|
2049
|
+
level: blocked ? "error" : "warning",
|
|
2050
|
+
file,
|
|
2051
|
+
title: `Ripple ${result.risk.level}`,
|
|
2052
|
+
message: `${result.risk.level} risk (score ${result.risk.score}) \u2014 ${result.summary.affectedFiles} affected ${result.summary.affectedFiles === 1 ? "file" : "files"}`
|
|
2053
|
+
};
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
function buildGateAnnotation(blocked, gate, counts) {
|
|
2057
|
+
const gateIndex = GATE_INDEX[gate];
|
|
2058
|
+
const parts = [];
|
|
2059
|
+
if (LEVEL_INDEX.CRITICAL >= gateIndex && counts.critical > 0)
|
|
2060
|
+
parts.push(`${counts.critical} CRITICAL`);
|
|
2061
|
+
if (LEVEL_INDEX.HIGH >= gateIndex && counts.high > 0) parts.push(`${counts.high} HIGH`);
|
|
2062
|
+
if (LEVEL_INDEX.MEDIUM >= gateIndex && counts.medium > 0) parts.push(`${counts.medium} MEDIUM`);
|
|
2063
|
+
const atGate = parts.length > 0 ? parts.join(" \xB7 ") : "none";
|
|
2064
|
+
return {
|
|
2065
|
+
level: "notice",
|
|
2066
|
+
title: `Ripple diff ${gate} gate`,
|
|
2067
|
+
message: blocked ? `Gate blocked: ${atGate} at or above ${gate}` : `Gate passed: nothing at ${gate} or above`
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
// src/commands/diff.ts
|
|
2072
|
+
var LEVEL_INDEX2 = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
2073
|
+
var GATE_INDEX2 = { medium: 1, high: 2, critical: 3 };
|
|
1812
2074
|
var VERDICT_NOTE = {
|
|
1813
2075
|
medium: "no MEDIUM or worse",
|
|
1814
2076
|
high: "no HIGH or CRITICAL",
|
|
@@ -1821,19 +2083,26 @@ function countLevels(results) {
|
|
|
1821
2083
|
}
|
|
1822
2084
|
return counts;
|
|
1823
2085
|
}
|
|
2086
|
+
function isAllowedByDiffAllowlist(relPath, patterns) {
|
|
2087
|
+
if (patterns.length === 0) return false;
|
|
2088
|
+
const rel = toPosix(relPath);
|
|
2089
|
+
const matcher = picomatch(patterns.map(toPosix), { dot: true });
|
|
2090
|
+
return matcher(rel);
|
|
2091
|
+
}
|
|
1824
2092
|
function riskySummary(counts, gate) {
|
|
1825
|
-
const gateIndex =
|
|
2093
|
+
const gateIndex = GATE_INDEX2[gate];
|
|
1826
2094
|
const parts = [];
|
|
1827
|
-
if (
|
|
2095
|
+
if (LEVEL_INDEX2.CRITICAL >= gateIndex && counts.critical > 0)
|
|
1828
2096
|
parts.push(`${counts.critical} CRITICAL`);
|
|
1829
|
-
if (
|
|
1830
|
-
if (
|
|
1831
|
-
if (
|
|
2097
|
+
if (LEVEL_INDEX2.HIGH >= gateIndex && counts.high > 0) parts.push(`${counts.high} HIGH`);
|
|
2098
|
+
if (LEVEL_INDEX2.MEDIUM >= gateIndex && counts.medium > 0) parts.push(`${counts.medium} MEDIUM`);
|
|
2099
|
+
if (LEVEL_INDEX2.LOW >= gateIndex && counts.low > 0) parts.push(`${counts.low} LOW`);
|
|
1832
2100
|
return parts.length > 0 ? parts.join(" \xB7 ") : "none";
|
|
1833
2101
|
}
|
|
1834
2102
|
async function diffCommand(options, ctx2) {
|
|
2103
|
+
const format = options.format ?? (options.json ? "json" : "terminal");
|
|
1835
2104
|
const style = { color: resolveColor(options.color) };
|
|
1836
|
-
const tracker = createStageTracker(
|
|
2105
|
+
const tracker = createStageTracker(format === "terminal");
|
|
1837
2106
|
const started = Date.now();
|
|
1838
2107
|
const context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1839
2108
|
const gate = options.gate ?? context.config.diff.gate ?? "high";
|
|
@@ -1865,20 +2134,29 @@ async function diffCommand(options, ctx2) {
|
|
|
1865
2134
|
);
|
|
1866
2135
|
}
|
|
1867
2136
|
tracker.done();
|
|
1868
|
-
const
|
|
1869
|
-
const
|
|
1870
|
-
const
|
|
2137
|
+
const allowPatterns = [...options.allow ?? [], ...context.config.diff.allow ?? []];
|
|
2138
|
+
const allowedByRel = /* @__PURE__ */ new Map();
|
|
2139
|
+
for (const rel of byRel.keys()) {
|
|
2140
|
+
allowedByRel.set(rel, isAllowedByDiffAllowlist(rel, allowPatterns));
|
|
2141
|
+
}
|
|
2142
|
+
const entries = [...byRel.entries()].map(([rel, result]) => ({ rel, result, allowed: allowedByRel.get(rel) ?? false })).sort((a, b) => b.result.risk.score - a.result.risk.score);
|
|
2143
|
+
const gateResults = entries.filter((entry) => !entry.allowed).map((entry) => entry.result);
|
|
2144
|
+
const counts = countLevels(gateResults);
|
|
2145
|
+
const allowedCount = entries.filter((entry) => entry.allowed).length;
|
|
2146
|
+
const blocked = gateResults.some((result) => LEVEL_INDEX2[result.risk.level] >= GATE_INDEX2[gate]);
|
|
1871
2147
|
const durationMs = Date.now() - started;
|
|
1872
|
-
if (
|
|
2148
|
+
if (format === "json") {
|
|
1873
2149
|
ctx2.writer.write(
|
|
1874
2150
|
serializeJson(
|
|
1875
2151
|
buildDiffJsonReport(
|
|
1876
2152
|
changed.baseLabel,
|
|
1877
2153
|
changed.files,
|
|
1878
2154
|
byRel,
|
|
2155
|
+
allowedByRel,
|
|
1879
2156
|
gate,
|
|
1880
2157
|
blocked,
|
|
1881
2158
|
counts,
|
|
2159
|
+
allowedCount,
|
|
1882
2160
|
durationMs,
|
|
1883
2161
|
ctx2.version
|
|
1884
2162
|
)
|
|
@@ -1886,14 +2164,27 @@ async function diffCommand(options, ctx2) {
|
|
|
1886
2164
|
);
|
|
1887
2165
|
return blocked ? ExitCode.Failure : ExitCode.Success;
|
|
1888
2166
|
}
|
|
2167
|
+
if (format === "github") {
|
|
2168
|
+
const annotations = [
|
|
2169
|
+
...buildFileAnnotations(
|
|
2170
|
+
[...byRel.entries()].filter(([rel]) => !(allowedByRel.get(rel) ?? false)).map(([file, result]) => ({ file, result })),
|
|
2171
|
+
gate
|
|
2172
|
+
),
|
|
2173
|
+
buildGateAnnotation(blocked, gate, counts)
|
|
2174
|
+
];
|
|
2175
|
+
ctx2.writer.write(`${annotations.map(renderAnnotation).join("\n")}
|
|
2176
|
+
`);
|
|
2177
|
+
return blocked ? ExitCode.Failure : ExitCode.Success;
|
|
2178
|
+
}
|
|
1889
2179
|
renderDiffReport(
|
|
1890
2180
|
{
|
|
1891
2181
|
cwd: ctx2.cwd,
|
|
1892
2182
|
baseLabel: changed.baseLabel,
|
|
1893
2183
|
files: changed.files,
|
|
1894
|
-
|
|
2184
|
+
entries,
|
|
1895
2185
|
skipped,
|
|
1896
2186
|
counts,
|
|
2187
|
+
allowedCount,
|
|
1897
2188
|
gate,
|
|
1898
2189
|
blocked,
|
|
1899
2190
|
durationMs,
|
|
@@ -1904,12 +2195,25 @@ async function diffCommand(options, ctx2) {
|
|
|
1904
2195
|
return blocked ? ExitCode.Failure : ExitCode.Success;
|
|
1905
2196
|
}
|
|
1906
2197
|
function renderDiffReport(input, writer) {
|
|
1907
|
-
const {
|
|
2198
|
+
const {
|
|
2199
|
+
cwd,
|
|
2200
|
+
baseLabel,
|
|
2201
|
+
files,
|
|
2202
|
+
entries,
|
|
2203
|
+
skipped,
|
|
2204
|
+
counts,
|
|
2205
|
+
allowedCount,
|
|
2206
|
+
gate,
|
|
2207
|
+
blocked,
|
|
2208
|
+
durationMs,
|
|
2209
|
+
style
|
|
2210
|
+
} = input;
|
|
1908
2211
|
writer.writeLine(brandHeader({ label: `diff vs ${baseLabel}`, style }));
|
|
1909
2212
|
writer.writeLine();
|
|
1910
2213
|
const rows = [
|
|
1911
2214
|
["Changed", pluralize(files.length, "file")],
|
|
1912
|
-
["Source", pluralize(
|
|
2215
|
+
["Source", pluralize(entries.length, "file")],
|
|
2216
|
+
["Allowed", pluralize(allowedCount, "file")],
|
|
1913
2217
|
["Duration", dim(formatDuration(durationMs), style)]
|
|
1914
2218
|
];
|
|
1915
2219
|
const width = Math.max(...rows.map(([key]) => key.length));
|
|
@@ -1926,16 +2230,17 @@ function renderDiffReport(input, writer) {
|
|
|
1926
2230
|
`${markText} ${verdictText} \u2014 ${riskySummary(counts, gate)} ${dim(`(${VERDICT_NOTE[gate]})`, style)}`
|
|
1927
2231
|
);
|
|
1928
2232
|
writer.writeLine();
|
|
1929
|
-
writer.writeLine(sectionHeader(`Risk analysis (${
|
|
1930
|
-
for (const result of
|
|
1931
|
-
const risky =
|
|
2233
|
+
writer.writeLine(sectionHeader(`Risk analysis (${entries.length} files)`, style));
|
|
2234
|
+
for (const { result, allowed } of entries) {
|
|
2235
|
+
const risky = LEVEL_INDEX2[result.risk.level] >= GATE_INDEX2[gate];
|
|
1932
2236
|
const fileIcon = risky ? icon("cross") : icon("tick");
|
|
1933
2237
|
const fileIconText = style.color ? risky ? chalk.red(fileIcon) : chalk.green(fileIcon) : fileIcon;
|
|
1934
2238
|
const score = result.risk.score.toFixed(1);
|
|
1935
2239
|
const gauge = riskGauge(result.risk.score, result.risk.level, style);
|
|
1936
2240
|
const file = displayPath(result.targetPath, cwd);
|
|
2241
|
+
const allowedTag = allowed ? ` ${dim("(allowed)", style)}` : "";
|
|
1937
2242
|
writer.writeLine(
|
|
1938
|
-
`${fileIconText} ${file} ${riskBadge(result.risk.level, style)} \xB7 ${score}/100${gauge ? ` ${gauge}` : ""}`
|
|
2243
|
+
`${fileIconText} ${file} ${riskBadge(result.risk.level, style)} \xB7 ${score}/100${gauge ? ` ${gauge}` : ""}${allowedTag}`
|
|
1939
2244
|
);
|
|
1940
2245
|
}
|
|
1941
2246
|
for (const rel of skipped) {
|
|
@@ -1945,7 +2250,7 @@ function renderDiffReport(input, writer) {
|
|
|
1945
2250
|
}
|
|
1946
2251
|
writer.writeLine();
|
|
1947
2252
|
}
|
|
1948
|
-
function buildDiffJsonReport(baseLabel, changedFileNames, byRel, gate, blocked, counts, durationMs, version) {
|
|
2253
|
+
function buildDiffJsonReport(baseLabel, changedFileNames, byRel, allowedByRel, gate, blocked, counts, allowedCount, durationMs, version) {
|
|
1949
2254
|
const files = [];
|
|
1950
2255
|
for (const rel of changedFileNames) {
|
|
1951
2256
|
const result = byRel.get(rel);
|
|
@@ -1955,7 +2260,8 @@ function buildDiffJsonReport(baseLabel, changedFileNames, byRel, gate, blocked,
|
|
|
1955
2260
|
analyzed: true,
|
|
1956
2261
|
risk: result.risk,
|
|
1957
2262
|
affectedFiles: result.summary.affectedFiles,
|
|
1958
|
-
targetInCycle: result.targetInCycle
|
|
2263
|
+
targetInCycle: result.targetInCycle,
|
|
2264
|
+
allowed: allowedByRel.get(rel) ?? false
|
|
1959
2265
|
});
|
|
1960
2266
|
} else {
|
|
1961
2267
|
files.push({ file: rel, analyzed: false });
|
|
@@ -1969,16 +2275,29 @@ function buildDiffJsonReport(baseLabel, changedFileNames, byRel, gate, blocked,
|
|
|
1969
2275
|
base: baseLabel,
|
|
1970
2276
|
changedFiles: changedFileNames.length,
|
|
1971
2277
|
files,
|
|
1972
|
-
gate: { level: gate, blocked, counts },
|
|
2278
|
+
gate: { level: gate, blocked, counts, allowed: allowedCount },
|
|
1973
2279
|
durationMs
|
|
1974
2280
|
};
|
|
1975
2281
|
}
|
|
1976
2282
|
|
|
1977
2283
|
// src/cli/program.ts
|
|
1978
2284
|
var PROGRAM_NAME = "ripple";
|
|
2285
|
+
function helpExamples(examples) {
|
|
2286
|
+
const lines = examples.map((example) => ` ${example}`);
|
|
2287
|
+
return `
|
|
2288
|
+
Examples:
|
|
2289
|
+
${lines.join("\n")}
|
|
2290
|
+
`;
|
|
2291
|
+
}
|
|
1979
2292
|
function createProgram(ctx2) {
|
|
1980
2293
|
const program = new Command().name(PROGRAM_NAME).description("Dependency impact analysis for TypeScript and JavaScript projects.").version(ctx2.version, "-V, --version", "print the Ripple version").showHelpAfterError();
|
|
1981
|
-
program.command("analyze <file>").description("Analyze the impact of changing a file").
|
|
2294
|
+
program.command("analyze <file>").description("Analyze the impact of changing a file").addHelpText(
|
|
2295
|
+
"after",
|
|
2296
|
+
helpExamples([
|
|
2297
|
+
"ripple analyze src/authentication/login.ts",
|
|
2298
|
+
"ripple analyze src/index.ts --depth 3"
|
|
2299
|
+
])
|
|
2300
|
+
).option("-j, --json", "emit a machine-readable JSON report").option("-v, --verbose", "include the risk factor breakdown").option("-d, --depth <number>", "cap the reverse traversal depth", parsePositiveInt).option("-c, --config <path>", "path to a ripple config file").option("--no-color", "disable ANSI colors").action(async (file, options) => {
|
|
1982
2301
|
const exitCode2 = await analyzeCommand(
|
|
1983
2302
|
file,
|
|
1984
2303
|
{
|
|
@@ -1994,11 +2313,23 @@ function createProgram(ctx2) {
|
|
|
1994
2313
|
throw new ExitError(exitCode2);
|
|
1995
2314
|
}
|
|
1996
2315
|
});
|
|
1997
|
-
program.command("graph [file]").description("Show project graph stats, or the dependency tree of one file").
|
|
2316
|
+
program.command("graph [file]").description("Show project graph stats, or the dependency tree of one file").addHelpText(
|
|
2317
|
+
"after",
|
|
2318
|
+
helpExamples([
|
|
2319
|
+
"ripple graph",
|
|
2320
|
+
"ripple graph src/index.ts --reverse --depth 3",
|
|
2321
|
+
"ripple graph --format mermaid"
|
|
2322
|
+
])
|
|
2323
|
+
).option("-j, --json", "emit a machine-readable JSON report").option(
|
|
2324
|
+
"-f, --format <format>",
|
|
2325
|
+
"output format: terminal | json | mermaid | dot | html",
|
|
2326
|
+
parseGraphFormat
|
|
2327
|
+
).option("-v, --verbose", "include extra sections").option("-r, --reverse", "show dependents (what imports the file) instead of dependants").option("-d, --depth <number>", "cap the tree depth", parsePositiveInt).option("-c, --config <path>", "path to a ripple config file").option("--no-color", "disable ANSI colors").action(async (file, options) => {
|
|
1998
2328
|
const exitCode2 = await graphCommand(
|
|
1999
2329
|
file,
|
|
2000
2330
|
{
|
|
2001
2331
|
json: Boolean(options.json),
|
|
2332
|
+
format: options.format ?? (options.json ? "json" : "terminal"),
|
|
2002
2333
|
verbose: Boolean(options.verbose),
|
|
2003
2334
|
reverse: Boolean(options.reverse),
|
|
2004
2335
|
...options.depth !== void 0 ? { depth: options.depth } : {},
|
|
@@ -2011,18 +2342,23 @@ function createProgram(ctx2) {
|
|
|
2011
2342
|
throw new ExitError(exitCode2);
|
|
2012
2343
|
}
|
|
2013
2344
|
});
|
|
2014
|
-
program.command("diff").description("Analyze changed files since a base ref and gate risky changes").
|
|
2345
|
+
program.command("diff").description("Analyze changed files since a base ref and gate risky changes").addHelpText(
|
|
2346
|
+
"after",
|
|
2347
|
+
helpExamples(["ripple diff --base main --gate critical", "ripple diff --format github"])
|
|
2348
|
+
).option("-j, --json", "emit a machine-readable JSON report").option("-f, --format <format>", "output format: terminal | json | github", parseDiffFormat).option("-v, --verbose", "include extra sections").option("-b, --base <ref>", "git ref to diff against (default: origin/main, main, HEAD~1)").option(
|
|
2015
2349
|
"-g, --gate <level>",
|
|
2016
2350
|
"risk level that blocks the gate (medium | high | critical)",
|
|
2017
2351
|
parseGateLevel
|
|
2018
|
-
).option("-d, --depth <number>", "cap the reverse traversal depth", parsePositiveInt).option("-c, --config <path>", "path to a ripple config file").option("--no-color", "disable ANSI colors").action(async (options) => {
|
|
2352
|
+
).option("-d, --depth <number>", "cap the reverse traversal depth", parsePositiveInt).option("-a, --allow <glob...>", "changed files matching these globs never block the gate").option("-c, --config <path>", "path to a ripple config file").option("--no-color", "disable ANSI colors").action(async (options) => {
|
|
2019
2353
|
const exitCode2 = await diffCommand(
|
|
2020
2354
|
{
|
|
2021
2355
|
json: Boolean(options.json),
|
|
2356
|
+
format: options.format ?? (options.json ? "json" : "terminal"),
|
|
2022
2357
|
verbose: Boolean(options.verbose),
|
|
2023
2358
|
...options.base !== void 0 ? { base: options.base } : {},
|
|
2024
2359
|
...options.gate !== void 0 ? { gate: options.gate } : {},
|
|
2025
2360
|
...options.depth !== void 0 ? { depth: options.depth } : {},
|
|
2361
|
+
...options.allow !== void 0 ? { allow: options.allow } : {},
|
|
2026
2362
|
...options.config !== void 0 ? { config: options.config } : {},
|
|
2027
2363
|
color: options.color
|
|
2028
2364
|
},
|
|
@@ -2032,7 +2368,7 @@ function createProgram(ctx2) {
|
|
|
2032
2368
|
throw new ExitError(exitCode2);
|
|
2033
2369
|
}
|
|
2034
2370
|
});
|
|
2035
|
-
program.command("doctor").description("Check the project and environment for Ripple readiness").option("-v, --verbose", "print detailed diagnostics").option("-c, --config <path>", "path to a ripple config file").option("--no-color", "disable ANSI colors").action(async (options) => {
|
|
2371
|
+
program.command("doctor").description("Check the project and environment for Ripple readiness").addHelpText("after", helpExamples(["ripple doctor", "ripple doctor -v"])).option("-v, --verbose", "print detailed diagnostics").option("-c, --config <path>", "path to a ripple config file").option("--no-color", "disable ANSI colors").action(async (options) => {
|
|
2036
2372
|
const exitCode2 = await doctorCommand(
|
|
2037
2373
|
{
|
|
2038
2374
|
verbose: Boolean(options.verbose),
|
|
@@ -2045,16 +2381,21 @@ function createProgram(ctx2) {
|
|
|
2045
2381
|
throw new ExitError(exitCode2);
|
|
2046
2382
|
}
|
|
2047
2383
|
});
|
|
2048
|
-
program.command("init").description("Create a ripple.config
|
|
2384
|
+
program.command("init").description("Create a ripple.config with the default settings").addHelpText("after", helpExamples(["ripple init --force", "ripple init --ts --full"])).option("-f, --force", "overwrite an existing config file").option("--full", "write every built-in default instead of a minimal config").option("--ts", "write a typed ripple.config.ts instead of JSON").option("--no-color", "disable ANSI colors").action(async (options) => {
|
|
2049
2385
|
const exitCode2 = await initCommand(
|
|
2050
|
-
{
|
|
2386
|
+
{
|
|
2387
|
+
force: Boolean(options.force),
|
|
2388
|
+
full: Boolean(options.full),
|
|
2389
|
+
ts: Boolean(options.ts),
|
|
2390
|
+
color: options.color
|
|
2391
|
+
},
|
|
2051
2392
|
ctx2
|
|
2052
2393
|
);
|
|
2053
2394
|
if (exitCode2 !== ExitCode.Success) {
|
|
2054
2395
|
throw new ExitError(exitCode2);
|
|
2055
2396
|
}
|
|
2056
2397
|
});
|
|
2057
|
-
program.command("version").description("Print the Ripple version").action(() => {
|
|
2398
|
+
program.command("version").description("Print the Ripple version").addHelpText("after", helpExamples(["ripple version"])).action(() => {
|
|
2058
2399
|
ctx2.writer.writeLine(ctx2.version);
|
|
2059
2400
|
});
|
|
2060
2401
|
program.addHelpText(
|
|
@@ -2093,6 +2434,26 @@ function parseGateLevel(value) {
|
|
|
2093
2434
|
`expected one of medium | high | critical, got "${value}"`
|
|
2094
2435
|
);
|
|
2095
2436
|
}
|
|
2437
|
+
function parseDiffFormat(value) {
|
|
2438
|
+
if (value === "terminal" || value === "json" || value === "github") {
|
|
2439
|
+
return value;
|
|
2440
|
+
}
|
|
2441
|
+
throw new CommanderError(
|
|
2442
|
+
1,
|
|
2443
|
+
"commander.invalidArgument",
|
|
2444
|
+
`expected one of terminal | json | github, got "${value}"`
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
function parseGraphFormat(value) {
|
|
2448
|
+
if (value === "terminal" || value === "json" || value === "mermaid" || value === "dot" || value === "html") {
|
|
2449
|
+
return value;
|
|
2450
|
+
}
|
|
2451
|
+
throw new CommanderError(
|
|
2452
|
+
1,
|
|
2453
|
+
"commander.invalidArgument",
|
|
2454
|
+
`expected one of terminal | json | mermaid | dot | html, got "${value}"`
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2096
2457
|
function printError(error, ctx2) {
|
|
2097
2458
|
if (error instanceof RippleError) {
|
|
2098
2459
|
ctx2.errorWriter.writeLine(`\u2716 ${error.message}`);
|
|
@@ -2126,7 +2487,7 @@ async function run({ argv, ctx: ctx2 }) {
|
|
|
2126
2487
|
|
|
2127
2488
|
// package.json
|
|
2128
2489
|
var package_default = {
|
|
2129
|
-
version: "0.
|
|
2490
|
+
version: "0.6.0"};
|
|
2130
2491
|
|
|
2131
2492
|
// src/cli/bin.ts
|
|
2132
2493
|
var ctx = {
|