@alimaandev/ripple 0.1.1 → 0.3.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 +163 -57
- package/dist/bin.js +490 -119
- package/dist/bin.js.map +1 -1
- package/package.json +3 -1
package/dist/bin.js
CHANGED
|
@@ -6,10 +6,13 @@ import path8 from 'path';
|
|
|
6
6
|
import fs from 'fs/promises';
|
|
7
7
|
import fs2 from 'fs';
|
|
8
8
|
import { Project, ts, SyntaxKind } from 'ts-morph';
|
|
9
|
-
import ora from 'ora';
|
|
10
|
-
import treeify from 'treeify';
|
|
11
9
|
import chalk from 'chalk';
|
|
10
|
+
import figures from 'figures';
|
|
11
|
+
import treeify from 'treeify';
|
|
12
|
+
import boxen from 'boxen';
|
|
12
13
|
import fg from 'fast-glob';
|
|
14
|
+
import ora from 'ora';
|
|
15
|
+
import { execFileSync } from 'child_process';
|
|
13
16
|
|
|
14
17
|
// src/output/writer.ts
|
|
15
18
|
var TerminalWriter = class {
|
|
@@ -266,8 +269,8 @@ async function loadProjectContext(cwd, explicitConfigPath) {
|
|
|
266
269
|
function cycleMemberPaths(graph) {
|
|
267
270
|
const members = /* @__PURE__ */ new Set();
|
|
268
271
|
for (const cycle of graph.cycles) {
|
|
269
|
-
for (const
|
|
270
|
-
members.add(
|
|
272
|
+
for (const path13 of cycle.members) {
|
|
273
|
+
members.add(path13);
|
|
271
274
|
}
|
|
272
275
|
}
|
|
273
276
|
return members;
|
|
@@ -673,24 +676,79 @@ function analyzeFile(input) {
|
|
|
673
676
|
maxDepth: reachedDepth
|
|
674
677
|
};
|
|
675
678
|
}
|
|
676
|
-
function
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
679
|
+
function dim(text, style) {
|
|
680
|
+
return style.color ? chalk.dim(text) : text;
|
|
681
|
+
}
|
|
682
|
+
function strong(text, style) {
|
|
683
|
+
return style.color ? chalk.bold(text) : text;
|
|
684
|
+
}
|
|
685
|
+
var RISK_COLORS = {
|
|
686
|
+
LOW: (text) => chalk.green(text),
|
|
687
|
+
MEDIUM: (text) => chalk.yellow(text),
|
|
688
|
+
HIGH: (text) => chalk.magenta(text),
|
|
689
|
+
CRITICAL: (text) => chalk.red.bold(text)
|
|
690
|
+
};
|
|
691
|
+
function riskColor(level) {
|
|
692
|
+
return RISK_COLORS[level];
|
|
693
|
+
}
|
|
694
|
+
function riskBadge(level, style) {
|
|
695
|
+
return style.color ? RISK_COLORS[level](level) : level;
|
|
696
|
+
}
|
|
697
|
+
function confidenceText(percent, style) {
|
|
698
|
+
const text = `${percent}%`;
|
|
699
|
+
if (!style.color) return text;
|
|
700
|
+
if (percent >= 90) return chalk.green(text);
|
|
701
|
+
if (percent >= 70) return chalk.yellow(text);
|
|
702
|
+
return chalk.red(text);
|
|
703
|
+
}
|
|
704
|
+
function formatDuration(ms) {
|
|
705
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
706
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
707
|
+
}
|
|
708
|
+
function pluralize(count, noun, plural = `${noun}s`) {
|
|
709
|
+
return `${count} ${count === 1 ? noun : plural}`;
|
|
710
|
+
}
|
|
711
|
+
function icon(name) {
|
|
712
|
+
return figures[name];
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/ui/progress.ts
|
|
716
|
+
var noop = {
|
|
717
|
+
next: () => {
|
|
718
|
+
},
|
|
719
|
+
done: () => {
|
|
681
720
|
}
|
|
682
|
-
|
|
721
|
+
};
|
|
722
|
+
function createStageTracker(enabled) {
|
|
723
|
+
if (!enabled || !process.stdout.isTTY) return noop;
|
|
724
|
+
let current;
|
|
725
|
+
let from = 0;
|
|
726
|
+
const stamp = (label, ms) => {
|
|
727
|
+
process.stdout.write(
|
|
728
|
+
`${chalk.green(icon("tick"))} ${label} ${chalk.dim(formatDuration(ms))}
|
|
729
|
+
`
|
|
730
|
+
);
|
|
731
|
+
};
|
|
683
732
|
return {
|
|
684
|
-
|
|
685
|
-
|
|
733
|
+
next(label) {
|
|
734
|
+
if (current) stamp(current, Date.now() - from);
|
|
735
|
+
current = label;
|
|
736
|
+
from = Date.now();
|
|
686
737
|
},
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
738
|
+
done() {
|
|
739
|
+
if (current) stamp(current, Date.now() - from);
|
|
740
|
+
current = void 0;
|
|
690
741
|
}
|
|
691
742
|
};
|
|
692
743
|
}
|
|
693
744
|
|
|
745
|
+
// src/ui/color.ts
|
|
746
|
+
function resolveColor(requested) {
|
|
747
|
+
if (requested === false) return false;
|
|
748
|
+
const noColor = process.env.NO_COLOR;
|
|
749
|
+
return noColor === void 0 || noColor === "";
|
|
750
|
+
}
|
|
751
|
+
|
|
694
752
|
// src/formatter/table.ts
|
|
695
753
|
function renderTable(columns, rows) {
|
|
696
754
|
const widths = columns.map((column, index) => {
|
|
@@ -728,50 +786,53 @@ function renderTree(nodes) {
|
|
|
728
786
|
}
|
|
729
787
|
return treeify.asTree(root, false, false).trimEnd();
|
|
730
788
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
LOW: (text) => chalk.green(text),
|
|
745
|
-
MEDIUM: (text) => chalk.yellow(text),
|
|
746
|
-
HIGH: (text) => chalk.magenta(text),
|
|
747
|
-
CRITICAL: (text) => chalk.red.bold(text)
|
|
748
|
-
};
|
|
749
|
-
function riskBadge(level, style) {
|
|
750
|
-
return style.color ? RISK_COLORS[level](level) : level;
|
|
751
|
-
}
|
|
752
|
-
function confidenceText(percent, style) {
|
|
753
|
-
const text = `${percent}%`;
|
|
754
|
-
if (!style.color) return text;
|
|
755
|
-
if (percent >= 90) return chalk.green(text);
|
|
756
|
-
if (percent >= 70) return chalk.yellow(text);
|
|
757
|
-
return chalk.red(text);
|
|
789
|
+
var DIVIDER_WIDTH = 38;
|
|
790
|
+
function brandHeader(options) {
|
|
791
|
+
const { label, version, style } = options;
|
|
792
|
+
if (!style.color) {
|
|
793
|
+
return `ripple \u2014 ${label}${version ? ` \xB7 v${version}` : ""}`;
|
|
794
|
+
}
|
|
795
|
+
const tagline = `${icon("pointer")} ${label}${version ? ` \xB7 v${version}` : ""}`;
|
|
796
|
+
return boxen(` ${tagline}`, {
|
|
797
|
+
padding: { left: 1, right: 1 },
|
|
798
|
+
title: "ripple",
|
|
799
|
+
borderStyle: "round",
|
|
800
|
+
borderColor: "cyan"
|
|
801
|
+
});
|
|
758
802
|
}
|
|
759
|
-
function
|
|
760
|
-
if (
|
|
761
|
-
return `${(
|
|
803
|
+
function sectionHeader(title, style) {
|
|
804
|
+
if (!style.color) return title;
|
|
805
|
+
return `${chalk.dim("\u2500".repeat(DIVIDER_WIDTH))}
|
|
806
|
+
${chalk.bold(title)}`;
|
|
762
807
|
}
|
|
763
|
-
|
|
764
|
-
|
|
808
|
+
|
|
809
|
+
// src/ui/gauge.ts
|
|
810
|
+
var CELLS = 10;
|
|
811
|
+
var FILL = "\u2588";
|
|
812
|
+
var EMPTY = "\u2591";
|
|
813
|
+
function riskGauge(score, level, style) {
|
|
814
|
+
if (!style.color) return "";
|
|
815
|
+
const filled = Math.max(0, Math.min(CELLS, Math.round(score / 100 * CELLS)));
|
|
816
|
+
return riskColor(level)(FILL.repeat(filled) + EMPTY.repeat(CELLS - filled));
|
|
765
817
|
}
|
|
766
818
|
|
|
767
819
|
// src/output/report.ts
|
|
768
820
|
var DEFAULT_LIST_LIMIT = 20;
|
|
769
821
|
var textStyle = (options) => ({ color: options.color });
|
|
770
822
|
function fileLabel(filePath, depth, inCycle, style) {
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
return
|
|
823
|
+
const parts = [dim(filePath, style)];
|
|
824
|
+
if (depth >= 0) parts.push(dim(`\xB7 depth ${depth}`, style));
|
|
825
|
+
if (inCycle) parts.push(dim("\xB7 cycle", style));
|
|
826
|
+
return parts.join(" ");
|
|
827
|
+
}
|
|
828
|
+
function impactLine(result, style) {
|
|
829
|
+
const { summary } = result;
|
|
830
|
+
const parts = [];
|
|
831
|
+
if (summary.routes > 0) parts.push(pluralize(summary.routes, "route"));
|
|
832
|
+
if (summary.components > 0) parts.push(pluralize(summary.components, "component"));
|
|
833
|
+
if (summary.tests > 0) parts.push(pluralize(summary.tests, "test"));
|
|
834
|
+
if (summary.utilities > 0) parts.push(pluralize(summary.utilities, "utility"));
|
|
835
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : dim("none", style);
|
|
775
836
|
}
|
|
776
837
|
function buildAffectedTree(result, cwd, style) {
|
|
777
838
|
const { affected, graph } = result;
|
|
@@ -811,29 +872,32 @@ function renderAnalyzeReport(result, options, writer) {
|
|
|
811
872
|
const style = textStyle(options);
|
|
812
873
|
const { summary, risk } = result;
|
|
813
874
|
const file = displayPath(result.targetPath, options.cwd);
|
|
814
|
-
writer.writeLine(
|
|
875
|
+
writer.writeLine(brandHeader({ label: "impact analysis", version: options.version, style }));
|
|
815
876
|
writer.writeLine();
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
);
|
|
877
|
+
const score = risk.score.toFixed(1);
|
|
878
|
+
const gauge = riskGauge(risk.score, risk.level, style);
|
|
879
|
+
const rows = [
|
|
880
|
+
["File", file],
|
|
881
|
+
["Risk", `${riskBadge(risk.level, style)} \xB7 ${score}/100${gauge ? ` ${gauge}` : ""}`],
|
|
882
|
+
["Impact", impactLine(result, style)],
|
|
883
|
+
["Affected", pluralize(summary.affectedFiles, "file")],
|
|
884
|
+
["Max depth", String(summary.maxDepth)],
|
|
885
|
+
["Confidence", confidenceText(summary.confidence, style)],
|
|
886
|
+
["Duration", dim(formatDuration(result.durationMs), style)]
|
|
887
|
+
];
|
|
888
|
+
const width = Math.max(...rows.map(([key]) => key.length));
|
|
889
|
+
for (const [key, value] of rows) {
|
|
890
|
+
writer.writeLine(`${key.padEnd(width)} ${value}`);
|
|
891
|
+
}
|
|
830
892
|
writer.writeLine();
|
|
831
|
-
writer.writeLine(
|
|
893
|
+
writer.writeLine(sectionHeader("Top impact", style));
|
|
832
894
|
for (const area of summary.topImpact) {
|
|
833
|
-
writer.writeLine(
|
|
895
|
+
writer.writeLine(
|
|
896
|
+
`${icon("bullet")} ${strong(area.label, style)} ${dim(`(${area.count})`, style)}`
|
|
897
|
+
);
|
|
834
898
|
}
|
|
835
899
|
writer.writeLine();
|
|
836
|
-
writer.writeLine(
|
|
900
|
+
writer.writeLine(sectionHeader("Circular dependencies", style));
|
|
837
901
|
if (result.graph.cycles.length === 0) {
|
|
838
902
|
writer.writeLine(dim("none", style));
|
|
839
903
|
} else {
|
|
@@ -841,13 +905,13 @@ function renderAnalyzeReport(result, options, writer) {
|
|
|
841
905
|
const chain = cycle.path.map((key) => {
|
|
842
906
|
const node = result.graph.nodes.get(key);
|
|
843
907
|
return displayPath(node?.path ?? key, options.cwd);
|
|
844
|
-
}).join(
|
|
845
|
-
writer.writeLine(
|
|
908
|
+
}).join(` ${icon("arrowRight")} `);
|
|
909
|
+
writer.writeLine(`${icon("circle")} ${chain}`);
|
|
846
910
|
}
|
|
847
911
|
}
|
|
848
912
|
writer.writeLine();
|
|
849
913
|
if (summary.affectedFiles > 0) {
|
|
850
|
-
writer.writeLine(
|
|
914
|
+
writer.writeLine(sectionHeader(`Affected files (${summary.affectedFiles})`, style));
|
|
851
915
|
const tree = buildAffectedTree(result, options.cwd, style);
|
|
852
916
|
writer.writeLine(renderTree(tree));
|
|
853
917
|
const limit = options.listLimit ?? DEFAULT_LIST_LIMIT;
|
|
@@ -859,22 +923,27 @@ function renderAnalyzeReport(result, options, writer) {
|
|
|
859
923
|
writer.writeLine();
|
|
860
924
|
}
|
|
861
925
|
if (options.verbose) {
|
|
862
|
-
writer.writeLine(
|
|
926
|
+
writer.writeLine(sectionHeader("Risk factors", style));
|
|
863
927
|
const columns = [
|
|
864
928
|
{ header: "Factor" },
|
|
865
929
|
{ header: "Weight" },
|
|
866
930
|
{ header: "Signal" },
|
|
867
931
|
{ header: "Points" }
|
|
868
932
|
];
|
|
869
|
-
const
|
|
933
|
+
const rowsVerbose = risk.factors.map((factor) => [
|
|
870
934
|
factor.label,
|
|
871
935
|
factor.weight.toFixed(2),
|
|
872
936
|
factor.value.toFixed(2),
|
|
873
937
|
factor.contribution.toFixed(2)
|
|
874
938
|
]);
|
|
875
|
-
writer.writeLine(renderTable(columns,
|
|
939
|
+
writer.writeLine(renderTable(columns, rowsVerbose));
|
|
876
940
|
writer.writeLine();
|
|
877
941
|
}
|
|
942
|
+
if (style.color) {
|
|
943
|
+
writer.writeLine(
|
|
944
|
+
dim(`${icon("info")} Tip: run "ripple doctor" to check project health`, style)
|
|
945
|
+
);
|
|
946
|
+
}
|
|
878
947
|
}
|
|
879
948
|
function buildAnalyzeJsonReport(result, version, cwd) {
|
|
880
949
|
const realPath = (key) => result.graph.nodes.get(key)?.path ?? key;
|
|
@@ -898,7 +967,7 @@ function buildAnalyzeJsonReport(result, version, cwd) {
|
|
|
898
967
|
function renderGraphReport(input, options, writer) {
|
|
899
968
|
const style = textStyle(options);
|
|
900
969
|
const { stats } = input;
|
|
901
|
-
writer.writeLine(
|
|
970
|
+
writer.writeLine(brandHeader({ label: "dependency graph", version: options.version, style }));
|
|
902
971
|
writer.writeLine();
|
|
903
972
|
writer.writeLine(
|
|
904
973
|
renderKeyValue([
|
|
@@ -911,26 +980,26 @@ function renderGraphReport(input, options, writer) {
|
|
|
911
980
|
);
|
|
912
981
|
writer.writeLine();
|
|
913
982
|
if (input.forward) {
|
|
914
|
-
writer.writeLine(
|
|
915
|
-
writer.writeLine(renderTree(input.forward.map((
|
|
983
|
+
writer.writeLine(sectionHeader("Dependants (forward)", style));
|
|
984
|
+
writer.writeLine(renderTree(input.forward.map((path13) => ({ label: path13 }))));
|
|
916
985
|
writer.writeLine();
|
|
917
986
|
}
|
|
918
987
|
if (input.reverse) {
|
|
919
|
-
writer.writeLine(
|
|
920
|
-
writer.writeLine(renderTree(input.reverse.map((
|
|
988
|
+
writer.writeLine(sectionHeader("Dependents (reverse)", style));
|
|
989
|
+
writer.writeLine(renderTree(input.reverse.map((path13) => ({ label: path13 }))));
|
|
921
990
|
writer.writeLine();
|
|
922
991
|
}
|
|
923
|
-
|
|
924
|
-
|
|
992
|
+
writer.writeLine(sectionHeader("Circular dependencies", style));
|
|
993
|
+
if (input.cycles.length === 0) {
|
|
994
|
+
writer.writeLine(dim("none", style));
|
|
995
|
+
} else {
|
|
925
996
|
for (const cycle of input.cycles) {
|
|
926
997
|
const chain = cycle.path.map((key) => {
|
|
927
998
|
const node = input.nodes.get(key);
|
|
928
999
|
return displayPath(node?.path ?? key, input.cwd);
|
|
929
|
-
}).join(
|
|
930
|
-
writer.writeLine(
|
|
1000
|
+
}).join(` ${icon("arrowRight")} `);
|
|
1001
|
+
writer.writeLine(`${icon("circle")} ${chain}`);
|
|
931
1002
|
}
|
|
932
|
-
} else {
|
|
933
|
-
writer.writeLine(dim("No circular dependencies found.", style));
|
|
934
1003
|
}
|
|
935
1004
|
}
|
|
936
1005
|
function buildGraphJsonReport(input, version) {
|
|
@@ -1209,8 +1278,8 @@ function findCycles(graph) {
|
|
|
1209
1278
|
}
|
|
1210
1279
|
const members = [...component].sort();
|
|
1211
1280
|
const componentSet = new Set(component);
|
|
1212
|
-
const
|
|
1213
|
-
cycles.push({ members, path:
|
|
1281
|
+
const path13 = representativeCyclePath(graph, component, componentSet);
|
|
1282
|
+
cycles.push({ members, path: path13 ?? [members[0], members[0]] });
|
|
1214
1283
|
}
|
|
1215
1284
|
cycles.sort(
|
|
1216
1285
|
(a, b) => a.members.length - b.members.length || a.members[0].localeCompare(b.members[0])
|
|
@@ -1219,14 +1288,14 @@ function findCycles(graph) {
|
|
|
1219
1288
|
}
|
|
1220
1289
|
function representativeCyclePath(graph, component, componentSet) {
|
|
1221
1290
|
const inPath = /* @__PURE__ */ new Set();
|
|
1222
|
-
const
|
|
1291
|
+
const path13 = [];
|
|
1223
1292
|
let current = component[0];
|
|
1224
1293
|
for (let steps = 0; steps <= component.length * 2 && current !== void 0; steps++) {
|
|
1225
1294
|
const firstOccurrence = inPath.has(current);
|
|
1226
1295
|
if (firstOccurrence) {
|
|
1227
|
-
const start =
|
|
1296
|
+
const start = path13.indexOf(current);
|
|
1228
1297
|
if (start === -1) return void 0;
|
|
1229
|
-
const cycle =
|
|
1298
|
+
const cycle = path13.slice(start);
|
|
1230
1299
|
if (cycle.length > 1 || graph.forward.get(current)?.has(current)) {
|
|
1231
1300
|
cycle.push(current);
|
|
1232
1301
|
return cycle;
|
|
@@ -1234,7 +1303,7 @@ function representativeCyclePath(graph, component, componentSet) {
|
|
|
1234
1303
|
return void 0;
|
|
1235
1304
|
}
|
|
1236
1305
|
inPath.add(current);
|
|
1237
|
-
|
|
1306
|
+
path13.push(current);
|
|
1238
1307
|
const next = [...graph.forward.get(current) ?? []].find(
|
|
1239
1308
|
(candidate) => componentSet.has(candidate)
|
|
1240
1309
|
);
|
|
@@ -1413,11 +1482,13 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1413
1482
|
if (!await pathExists(targetPath)) {
|
|
1414
1483
|
throw new RippleError(`File not found: ${fileArg}`, ExitCode.NotFound);
|
|
1415
1484
|
}
|
|
1416
|
-
const
|
|
1417
|
-
|
|
1485
|
+
const tracker = createStageTracker(!options.json);
|
|
1486
|
+
tracker.next("Loading config");
|
|
1418
1487
|
const started = Date.now();
|
|
1419
1488
|
const context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1489
|
+
tracker.next("Building dependency graph");
|
|
1420
1490
|
const { graph, entryPoints, durationMs: pipelineMs } = await runPipeline(context);
|
|
1491
|
+
tracker.next("Analyzing impact");
|
|
1421
1492
|
const completed = Date.now();
|
|
1422
1493
|
const result = analyzeFile({
|
|
1423
1494
|
graph,
|
|
@@ -1428,7 +1499,7 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1428
1499
|
durationMs: pipelineMs + (completed - started),
|
|
1429
1500
|
...options.depth !== void 0 ? { maxDepth: options.depth } : {}
|
|
1430
1501
|
});
|
|
1431
|
-
|
|
1502
|
+
tracker.done();
|
|
1432
1503
|
if (options.json) {
|
|
1433
1504
|
const report = buildAnalyzeJsonReport(result, ctx2.version, ctx2.cwd);
|
|
1434
1505
|
ctx2.writer.write(serializeJson(report));
|
|
@@ -1438,8 +1509,9 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1438
1509
|
result,
|
|
1439
1510
|
{
|
|
1440
1511
|
cwd: ctx2.cwd,
|
|
1441
|
-
color: options.color
|
|
1442
|
-
verbose: options.verbose
|
|
1512
|
+
color: resolveColor(options.color),
|
|
1513
|
+
verbose: options.verbose,
|
|
1514
|
+
version: ctx2.version
|
|
1443
1515
|
},
|
|
1444
1516
|
ctx2.writer
|
|
1445
1517
|
);
|
|
@@ -1468,11 +1540,12 @@ function walk(graph, startKey, edges, maxDepth, cwd) {
|
|
|
1468
1540
|
return entries.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path)).map((entry) => entry.path);
|
|
1469
1541
|
}
|
|
1470
1542
|
async function graphCommand(fileArg, options, ctx2) {
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1543
|
+
const tracker = createStageTracker(!options.json);
|
|
1544
|
+
tracker.next("Loading config");
|
|
1473
1545
|
const cwd = ctx2.cwd;
|
|
1474
1546
|
try {
|
|
1475
1547
|
const context = await loadProjectContext(cwd, options.config);
|
|
1548
|
+
tracker.next("Building graph");
|
|
1476
1549
|
const { graph } = await runPipeline(context);
|
|
1477
1550
|
const input = {
|
|
1478
1551
|
stats: graph.stats,
|
|
@@ -1481,13 +1554,19 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1481
1554
|
cwd
|
|
1482
1555
|
};
|
|
1483
1556
|
if (!fileArg) {
|
|
1557
|
+
tracker.done();
|
|
1484
1558
|
if (options.json) {
|
|
1485
1559
|
const report = buildGraphJsonReport(input, ctx2.version);
|
|
1486
1560
|
ctx2.writer.write(serializeJson(report));
|
|
1487
1561
|
} else {
|
|
1488
1562
|
renderGraphReport(
|
|
1489
1563
|
input,
|
|
1490
|
-
{
|
|
1564
|
+
{
|
|
1565
|
+
cwd,
|
|
1566
|
+
color: resolveColor(options.color),
|
|
1567
|
+
verbose: options.verbose,
|
|
1568
|
+
version: ctx2.version
|
|
1569
|
+
},
|
|
1491
1570
|
ctx2.writer
|
|
1492
1571
|
);
|
|
1493
1572
|
}
|
|
@@ -1497,6 +1576,7 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1497
1576
|
const { key } = requireNode(graph, targetPath, context.rootDir);
|
|
1498
1577
|
const dependants = walk(graph, key, graph.forward, options.depth, cwd);
|
|
1499
1578
|
const dependents = walk(graph, key, graph.reverse, options.depth, cwd);
|
|
1579
|
+
tracker.done();
|
|
1500
1580
|
if (options.json) {
|
|
1501
1581
|
const report = buildGraphJsonReport(
|
|
1502
1582
|
{
|
|
@@ -1513,17 +1593,38 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1513
1593
|
forward: options.reverse ? void 0 : dependants,
|
|
1514
1594
|
reverse: options.reverse ? dependents : void 0
|
|
1515
1595
|
},
|
|
1516
|
-
{ cwd, color: options.color
|
|
1596
|
+
{ cwd, color: resolveColor(options.color), verbose: options.verbose, version: ctx2.version },
|
|
1517
1597
|
ctx2.writer
|
|
1518
1598
|
);
|
|
1519
1599
|
}
|
|
1520
1600
|
return ExitCode.Success;
|
|
1521
1601
|
} finally {
|
|
1522
|
-
|
|
1602
|
+
tracker.done();
|
|
1523
1603
|
}
|
|
1524
1604
|
}
|
|
1605
|
+
function createSpinner(enabled) {
|
|
1606
|
+
if (!enabled || !process.stdout.isTTY) {
|
|
1607
|
+
return { start: () => {
|
|
1608
|
+
}, stop: () => {
|
|
1609
|
+
} };
|
|
1610
|
+
}
|
|
1611
|
+
let instance;
|
|
1612
|
+
return {
|
|
1613
|
+
start(text) {
|
|
1614
|
+
instance = ora(text).start();
|
|
1615
|
+
},
|
|
1616
|
+
stop() {
|
|
1617
|
+
instance?.stop();
|
|
1618
|
+
instance = void 0;
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// src/commands/doctor.ts
|
|
1525
1624
|
async function doctorCommand(options, ctx2) {
|
|
1526
|
-
const
|
|
1625
|
+
const color = resolveColor(options.color);
|
|
1626
|
+
const style = { color };
|
|
1627
|
+
const spinner = createSpinner(color);
|
|
1527
1628
|
spinner.start("Running diagnostics\u2026");
|
|
1528
1629
|
const results = [];
|
|
1529
1630
|
const check = (label, state, detail) => {
|
|
@@ -1548,7 +1649,7 @@ async function doctorCommand(options, ctx2) {
|
|
|
1548
1649
|
context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1549
1650
|
} catch (error) {
|
|
1550
1651
|
check("ripple config", "fail", error instanceof Error ? error.message : String(error));
|
|
1551
|
-
renderResults(results, ctx2);
|
|
1652
|
+
renderResults(results, ctx2, color);
|
|
1552
1653
|
return ExitCode.InvalidConfig;
|
|
1553
1654
|
}
|
|
1554
1655
|
if (context.tsconfig) {
|
|
@@ -1597,29 +1698,39 @@ async function doctorCommand(options, ctx2) {
|
|
|
1597
1698
|
} finally {
|
|
1598
1699
|
spinner.stop();
|
|
1599
1700
|
}
|
|
1600
|
-
renderResults(results, ctx2);
|
|
1701
|
+
renderResults(results, ctx2, color);
|
|
1601
1702
|
const failed = results.some((check2) => check2.state === "fail");
|
|
1602
1703
|
const warned = results.some((check2) => check2.state === "warn");
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
);
|
|
1704
|
+
const verdict = failed ? { icon: icon("cross"), text: "Diagnoses found problems.", color: chalk.red } : warned ? { icon: icon("warning"), text: "Diagnoses passed with warnings.", color: chalk.yellow } : { icon: icon("tick"), text: "All checks passed.", color: chalk.green };
|
|
1705
|
+
const verdictIcon = style.color ? verdict.color(verdict.icon) : verdict.icon;
|
|
1706
|
+
ctx2.writer.writeLine(`${verdictIcon} ${verdict.text}`);
|
|
1606
1707
|
return failed ? ExitCode.Failure : ExitCode.Success;
|
|
1607
1708
|
}
|
|
1608
|
-
function renderResults(results, ctx2) {
|
|
1709
|
+
function renderResults(results, ctx2, color) {
|
|
1710
|
+
const style = { color };
|
|
1711
|
+
ctx2.writer.writeLine(brandHeader({ label: "project health check", version: ctx2.version, style }));
|
|
1712
|
+
ctx2.writer.writeLine();
|
|
1713
|
+
const width = Math.max(...results.map((check) => check.label.length));
|
|
1609
1714
|
for (const check of results) {
|
|
1610
|
-
const
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1715
|
+
const stateIcon = check.state === "ok" ? icon("tick") : check.state === "warn" ? icon("warning") : icon("cross");
|
|
1716
|
+
const colored = check.state === "ok" ? chalk.green : check.state === "warn" ? chalk.yellow : chalk.red;
|
|
1717
|
+
const mark = style.color ? colored(stateIcon) : stateIcon;
|
|
1718
|
+
const detail = check.detail ? ` ${dim(check.detail, style)}` : "";
|
|
1719
|
+
ctx2.writer.writeLine(`${mark} ${check.label.padEnd(width)}${detail}`);
|
|
1613
1720
|
}
|
|
1614
1721
|
ctx2.writer.writeLine();
|
|
1615
1722
|
}
|
|
1616
1723
|
var INIT_FILENAME = "ripple.config.json";
|
|
1617
1724
|
async function initCommand(options, ctx2) {
|
|
1725
|
+
const style = { color: resolveColor(options.color) };
|
|
1618
1726
|
const target = path8.join(ctx2.cwd, INIT_FILENAME);
|
|
1619
1727
|
try {
|
|
1620
1728
|
await fs.access(target);
|
|
1621
1729
|
if (!options.force) {
|
|
1622
|
-
|
|
1730
|
+
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
|
+
);
|
|
1623
1734
|
return ExitCode.Failure;
|
|
1624
1735
|
}
|
|
1625
1736
|
} catch {
|
|
@@ -1630,16 +1741,237 @@ async function initCommand(options, ctx2) {
|
|
|
1630
1741
|
try {
|
|
1631
1742
|
await fs.writeFile(target, `${JSON.stringify(payload, null, 2)}
|
|
1632
1743
|
`, "utf8");
|
|
1633
|
-
|
|
1634
|
-
ctx2.writer.writeLine(
|
|
1744
|
+
const mark = style.color ? chalk.green(icon("tick")) : icon("tick");
|
|
1745
|
+
ctx2.writer.writeLine(`${mark} Wrote ${INIT_FILENAME}`);
|
|
1746
|
+
ctx2.writer.writeLine(
|
|
1747
|
+
` ${dim("Edit it to tune include globs, aliases and risk weights.", style)}`
|
|
1748
|
+
);
|
|
1635
1749
|
return ExitCode.Success;
|
|
1636
1750
|
} catch (error) {
|
|
1751
|
+
const mark = style.color ? chalk.red(icon("cross")) : icon("cross");
|
|
1637
1752
|
ctx2.writer.writeLine(
|
|
1638
|
-
|
|
1753
|
+
`${mark} Could not write ${INIT_FILENAME}: ${error instanceof Error ? error.message : String(error)}`
|
|
1639
1754
|
);
|
|
1640
1755
|
return ExitCode.Failure;
|
|
1641
1756
|
}
|
|
1642
1757
|
}
|
|
1758
|
+
var DEFAULT_BASES = ["origin/main", "main", "HEAD~1"];
|
|
1759
|
+
function git(args, cwd) {
|
|
1760
|
+
return execFileSync("git", args, {
|
|
1761
|
+
cwd,
|
|
1762
|
+
encoding: "utf8",
|
|
1763
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1764
|
+
}).trim();
|
|
1765
|
+
}
|
|
1766
|
+
function refExists(ref, cwd) {
|
|
1767
|
+
try {
|
|
1768
|
+
const out = git(["rev-parse", "--verify", ref], cwd);
|
|
1769
|
+
return out.length > 0;
|
|
1770
|
+
} catch {
|
|
1771
|
+
return false;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
function resolveBase(cwd, requested) {
|
|
1775
|
+
if (requested) {
|
|
1776
|
+
if (!refExists(requested, cwd)) {
|
|
1777
|
+
throw new RippleError(`Git ref not found: ${requested}`, ExitCode.NotFound);
|
|
1778
|
+
}
|
|
1779
|
+
return requested;
|
|
1780
|
+
}
|
|
1781
|
+
for (const candidate of DEFAULT_BASES) {
|
|
1782
|
+
if (refExists(candidate, cwd)) return candidate;
|
|
1783
|
+
}
|
|
1784
|
+
throw new RippleError(
|
|
1785
|
+
"No git base ref found (tried origin/main, main, HEAD~1). Pass --base <ref> explicitly.",
|
|
1786
|
+
ExitCode.NotFound
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
function changedFiles(cwd, base) {
|
|
1790
|
+
const baseLabel = resolveBase(cwd, base);
|
|
1791
|
+
let baseCommit = baseLabel;
|
|
1792
|
+
try {
|
|
1793
|
+
const mergeBase = git(["merge-base", baseLabel, "HEAD"], cwd);
|
|
1794
|
+
if (mergeBase) baseCommit = mergeBase;
|
|
1795
|
+
} catch {
|
|
1796
|
+
}
|
|
1797
|
+
const files = /* @__PURE__ */ new Set();
|
|
1798
|
+
const tracked = git(["diff", "--name-only", "--diff-filter=ACMRT", baseCommit], cwd);
|
|
1799
|
+
for (const line of tracked.split("\n")) {
|
|
1800
|
+
if (line.trim()) files.add(line.trim());
|
|
1801
|
+
}
|
|
1802
|
+
const untracked = git(["ls-files", "--others", "--exclude-standard"], cwd);
|
|
1803
|
+
for (const line of untracked.split("\n")) {
|
|
1804
|
+
if (line.trim()) files.add(line.trim());
|
|
1805
|
+
}
|
|
1806
|
+
return { baseLabel, files: [...files].sort() };
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// src/commands/diff.ts
|
|
1810
|
+
var LEVEL_INDEX = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
1811
|
+
var GATE_INDEX = { medium: 1, high: 2, critical: 3 };
|
|
1812
|
+
var VERDICT_NOTE = {
|
|
1813
|
+
medium: "no MEDIUM or worse",
|
|
1814
|
+
high: "no HIGH or CRITICAL",
|
|
1815
|
+
critical: "no CRITICAL"
|
|
1816
|
+
};
|
|
1817
|
+
function countLevels(results) {
|
|
1818
|
+
const counts = { low: 0, medium: 0, high: 0, critical: 0 };
|
|
1819
|
+
for (const result of results) {
|
|
1820
|
+
counts[result.risk.level.toLowerCase()] += 1;
|
|
1821
|
+
}
|
|
1822
|
+
return counts;
|
|
1823
|
+
}
|
|
1824
|
+
function riskySummary(counts, gate) {
|
|
1825
|
+
const gateIndex = GATE_INDEX[gate];
|
|
1826
|
+
const parts = [];
|
|
1827
|
+
if (LEVEL_INDEX.CRITICAL >= gateIndex && counts.critical > 0)
|
|
1828
|
+
parts.push(`${counts.critical} CRITICAL`);
|
|
1829
|
+
if (LEVEL_INDEX.HIGH >= gateIndex && counts.high > 0) parts.push(`${counts.high} HIGH`);
|
|
1830
|
+
if (LEVEL_INDEX.MEDIUM >= gateIndex && counts.medium > 0) parts.push(`${counts.medium} MEDIUM`);
|
|
1831
|
+
if (LEVEL_INDEX.LOW >= gateIndex && counts.low > 0) parts.push(`${counts.low} LOW`);
|
|
1832
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : "none";
|
|
1833
|
+
}
|
|
1834
|
+
async function diffCommand(options, ctx2) {
|
|
1835
|
+
const style = { color: resolveColor(options.color) };
|
|
1836
|
+
const tracker = createStageTracker(!options.json);
|
|
1837
|
+
const gate = options.gate ?? "high";
|
|
1838
|
+
const started = Date.now();
|
|
1839
|
+
tracker.next("Resolving git changes");
|
|
1840
|
+
const changed = changedFiles(ctx2.cwd, options.base);
|
|
1841
|
+
tracker.next("Analyzing changed files");
|
|
1842
|
+
const context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1843
|
+
const { graph, entryPoints } = await runPipeline(context);
|
|
1844
|
+
const byRel = /* @__PURE__ */ new Map();
|
|
1845
|
+
const skipped = [];
|
|
1846
|
+
for (const rel of changed.files) {
|
|
1847
|
+
const abs = path8.resolve(ctx2.cwd, rel);
|
|
1848
|
+
const key = pathKey(abs, context.rootDir);
|
|
1849
|
+
if (!graph.nodes.has(key)) {
|
|
1850
|
+
skipped.push(rel);
|
|
1851
|
+
continue;
|
|
1852
|
+
}
|
|
1853
|
+
byRel.set(
|
|
1854
|
+
rel,
|
|
1855
|
+
analyzeFile({
|
|
1856
|
+
graph,
|
|
1857
|
+
context,
|
|
1858
|
+
entryPoints,
|
|
1859
|
+
targetKey: key,
|
|
1860
|
+
targetPath: abs,
|
|
1861
|
+
durationMs: 0,
|
|
1862
|
+
...options.depth !== void 0 ? { maxDepth: options.depth } : {}
|
|
1863
|
+
})
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
tracker.done();
|
|
1867
|
+
const results = [...byRel.values()].sort((a, b) => b.risk.score - a.risk.score);
|
|
1868
|
+
const counts = countLevels(results);
|
|
1869
|
+
const blocked = results.some((result) => LEVEL_INDEX[result.risk.level] >= GATE_INDEX[gate]);
|
|
1870
|
+
const durationMs = Date.now() - started;
|
|
1871
|
+
if (options.json) {
|
|
1872
|
+
ctx2.writer.write(
|
|
1873
|
+
serializeJson(
|
|
1874
|
+
buildDiffJsonReport(
|
|
1875
|
+
changed.baseLabel,
|
|
1876
|
+
changed.files,
|
|
1877
|
+
byRel,
|
|
1878
|
+
gate,
|
|
1879
|
+
blocked,
|
|
1880
|
+
counts,
|
|
1881
|
+
durationMs,
|
|
1882
|
+
ctx2.version
|
|
1883
|
+
)
|
|
1884
|
+
)
|
|
1885
|
+
);
|
|
1886
|
+
return blocked ? ExitCode.Failure : ExitCode.Success;
|
|
1887
|
+
}
|
|
1888
|
+
renderDiffReport(
|
|
1889
|
+
{
|
|
1890
|
+
cwd: ctx2.cwd,
|
|
1891
|
+
baseLabel: changed.baseLabel,
|
|
1892
|
+
files: changed.files,
|
|
1893
|
+
results,
|
|
1894
|
+
skipped,
|
|
1895
|
+
counts,
|
|
1896
|
+
gate,
|
|
1897
|
+
blocked,
|
|
1898
|
+
durationMs,
|
|
1899
|
+
style
|
|
1900
|
+
},
|
|
1901
|
+
ctx2.writer
|
|
1902
|
+
);
|
|
1903
|
+
return blocked ? ExitCode.Failure : ExitCode.Success;
|
|
1904
|
+
}
|
|
1905
|
+
function renderDiffReport(input, writer) {
|
|
1906
|
+
const { cwd, baseLabel, files, results, skipped, counts, gate, blocked, durationMs, style } = input;
|
|
1907
|
+
writer.writeLine(brandHeader({ label: `diff vs ${baseLabel}`, style }));
|
|
1908
|
+
writer.writeLine();
|
|
1909
|
+
const rows = [
|
|
1910
|
+
["Changed", pluralize(files.length, "file")],
|
|
1911
|
+
["Source", pluralize(results.length, "file")],
|
|
1912
|
+
["Duration", dim(formatDuration(durationMs), style)]
|
|
1913
|
+
];
|
|
1914
|
+
const width = Math.max(...rows.map(([key]) => key.length));
|
|
1915
|
+
for (const [key, value] of rows) {
|
|
1916
|
+
writer.writeLine(`${key.padEnd(width)} ${value}`);
|
|
1917
|
+
}
|
|
1918
|
+
writer.writeLine();
|
|
1919
|
+
const mark = blocked ? icon("cross") : icon("tick");
|
|
1920
|
+
const color = blocked ? chalk.red : chalk.green;
|
|
1921
|
+
const markText = style.color ? color(mark) : mark;
|
|
1922
|
+
const verdict = blocked ? "Gate blocked" : "Gate passed";
|
|
1923
|
+
const verdictText = style.color ? color(strong(verdict, style)) : verdict;
|
|
1924
|
+
writer.writeLine(
|
|
1925
|
+
`${markText} ${verdictText} \u2014 ${riskySummary(counts, gate)} ${dim(`(${VERDICT_NOTE[gate]})`, style)}`
|
|
1926
|
+
);
|
|
1927
|
+
writer.writeLine();
|
|
1928
|
+
writer.writeLine(sectionHeader(`Risk analysis (${results.length} files)`, style));
|
|
1929
|
+
for (const result of results) {
|
|
1930
|
+
const risky = LEVEL_INDEX[result.risk.level] >= GATE_INDEX[gate];
|
|
1931
|
+
const fileIcon = risky ? icon("cross") : icon("tick");
|
|
1932
|
+
const fileIconText = style.color ? risky ? chalk.red(fileIcon) : chalk.green(fileIcon) : fileIcon;
|
|
1933
|
+
const score = result.risk.score.toFixed(1);
|
|
1934
|
+
const gauge = riskGauge(result.risk.score, result.risk.level, style);
|
|
1935
|
+
const file = displayPath(result.targetPath, cwd);
|
|
1936
|
+
writer.writeLine(
|
|
1937
|
+
`${fileIconText} ${file} ${riskBadge(result.risk.level, style)} \xB7 ${score}/100${gauge ? ` ${gauge}` : ""}`
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
for (const rel of skipped) {
|
|
1941
|
+
writer.writeLine(
|
|
1942
|
+
`${dim(icon("ellipsis"), style)} ${dim(rel, style)} ${dim("(not a source file)", style)}`
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
writer.writeLine();
|
|
1946
|
+
}
|
|
1947
|
+
function buildDiffJsonReport(baseLabel, changedFileNames, byRel, gate, blocked, counts, durationMs, version) {
|
|
1948
|
+
const files = [];
|
|
1949
|
+
for (const rel of changedFileNames) {
|
|
1950
|
+
const result = byRel.get(rel);
|
|
1951
|
+
if (result) {
|
|
1952
|
+
files.push({
|
|
1953
|
+
file: rel,
|
|
1954
|
+
analyzed: true,
|
|
1955
|
+
risk: result.risk,
|
|
1956
|
+
affectedFiles: result.summary.affectedFiles,
|
|
1957
|
+
targetInCycle: result.targetInCycle
|
|
1958
|
+
});
|
|
1959
|
+
} else {
|
|
1960
|
+
files.push({ file: rel, analyzed: false });
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
files.sort((a, b) => (b.risk?.score ?? -1) - (a.risk?.score ?? -1));
|
|
1964
|
+
return {
|
|
1965
|
+
tool: "ripple",
|
|
1966
|
+
version,
|
|
1967
|
+
command: "diff",
|
|
1968
|
+
base: baseLabel,
|
|
1969
|
+
changedFiles: changedFileNames.length,
|
|
1970
|
+
files,
|
|
1971
|
+
gate: { level: gate, blocked, counts },
|
|
1972
|
+
durationMs
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1643
1975
|
|
|
1644
1976
|
// src/cli/program.ts
|
|
1645
1977
|
var PROGRAM_NAME = "ripple";
|
|
@@ -1678,6 +2010,27 @@ function createProgram(ctx2) {
|
|
|
1678
2010
|
throw new ExitError(exitCode2);
|
|
1679
2011
|
}
|
|
1680
2012
|
});
|
|
2013
|
+
program.command("diff").description("Analyze changed files since a base ref and gate risky changes").option("-j, --json", "emit a machine-readable JSON report").option("-v, --verbose", "include extra sections").option("-b, --base <ref>", "git ref to diff against (default: origin/main, main, HEAD~1)").option(
|
|
2014
|
+
"-g, --gate <level>",
|
|
2015
|
+
"risk level that blocks the gate (medium | high | critical)",
|
|
2016
|
+
parseGateLevel
|
|
2017
|
+
).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) => {
|
|
2018
|
+
const exitCode2 = await diffCommand(
|
|
2019
|
+
{
|
|
2020
|
+
json: Boolean(options.json),
|
|
2021
|
+
verbose: Boolean(options.verbose),
|
|
2022
|
+
...options.base !== void 0 ? { base: options.base } : {},
|
|
2023
|
+
...options.gate !== void 0 ? { gate: options.gate } : {},
|
|
2024
|
+
...options.depth !== void 0 ? { depth: options.depth } : {},
|
|
2025
|
+
...options.config !== void 0 ? { config: options.config } : {},
|
|
2026
|
+
color: options.color
|
|
2027
|
+
},
|
|
2028
|
+
ctx2
|
|
2029
|
+
);
|
|
2030
|
+
if (exitCode2 !== ExitCode.Success) {
|
|
2031
|
+
throw new ExitError(exitCode2);
|
|
2032
|
+
}
|
|
2033
|
+
});
|
|
1681
2034
|
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) => {
|
|
1682
2035
|
const exitCode2 = await doctorCommand(
|
|
1683
2036
|
{
|
|
@@ -1691,8 +2044,11 @@ function createProgram(ctx2) {
|
|
|
1691
2044
|
throw new ExitError(exitCode2);
|
|
1692
2045
|
}
|
|
1693
2046
|
});
|
|
1694
|
-
program.command("init").description("Create a ripple.config.json with the default settings").option("-f, --force", "overwrite an existing config file").action(async (options) => {
|
|
1695
|
-
const exitCode2 = await initCommand(
|
|
2047
|
+
program.command("init").description("Create a ripple.config.json with the default settings").option("-f, --force", "overwrite an existing config file").option("--no-color", "disable ANSI colors").action(async (options) => {
|
|
2048
|
+
const exitCode2 = await initCommand(
|
|
2049
|
+
{ force: Boolean(options.force), color: options.color },
|
|
2050
|
+
ctx2
|
|
2051
|
+
);
|
|
1696
2052
|
if (exitCode2 !== ExitCode.Success) {
|
|
1697
2053
|
throw new ExitError(exitCode2);
|
|
1698
2054
|
}
|
|
@@ -1700,6 +2056,11 @@ function createProgram(ctx2) {
|
|
|
1700
2056
|
program.command("version").description("Print the Ripple version").action(() => {
|
|
1701
2057
|
ctx2.writer.writeLine(ctx2.version);
|
|
1702
2058
|
});
|
|
2059
|
+
program.addHelpText(
|
|
2060
|
+
"beforeAll",
|
|
2061
|
+
`ripple \u2014 dependency impact analysis for TypeScript and JavaScript
|
|
2062
|
+
`
|
|
2063
|
+
);
|
|
1703
2064
|
return program;
|
|
1704
2065
|
}
|
|
1705
2066
|
var ExitError = class extends Error {
|
|
@@ -1721,6 +2082,16 @@ function parsePositiveInt(value) {
|
|
|
1721
2082
|
}
|
|
1722
2083
|
return parsed;
|
|
1723
2084
|
}
|
|
2085
|
+
function parseGateLevel(value) {
|
|
2086
|
+
if (value === "medium" || value === "high" || value === "critical") {
|
|
2087
|
+
return value;
|
|
2088
|
+
}
|
|
2089
|
+
throw new CommanderError(
|
|
2090
|
+
1,
|
|
2091
|
+
"commander.invalidArgument",
|
|
2092
|
+
`expected one of medium | high | critical, got "${value}"`
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
1724
2095
|
function printError(error, ctx2) {
|
|
1725
2096
|
if (error instanceof RippleError) {
|
|
1726
2097
|
ctx2.errorWriter.writeLine(`\u2716 ${error.message}`);
|
|
@@ -1754,7 +2125,7 @@ async function run({ argv, ctx: ctx2 }) {
|
|
|
1754
2125
|
|
|
1755
2126
|
// package.json
|
|
1756
2127
|
var package_default = {
|
|
1757
|
-
version: "0.
|
|
2128
|
+
version: "0.3.0"};
|
|
1758
2129
|
|
|
1759
2130
|
// src/cli/bin.ts
|
|
1760
2131
|
var ctx = {
|