@alimaandev/ripple 0.1.1 → 0.2.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 +38 -34
- package/dist/bin.js +231 -109
- package/dist/bin.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -44,13 +44,12 @@ exactly how a change propagates through your codebase:
|
|
|
44
44
|
```text
|
|
45
45
|
$ ripple analyze src/authentication/login.ts
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Confidence 100%
|
|
47
|
+
File src/authentication/login.ts
|
|
48
|
+
Risk MEDIUM · 34.7/100 ███░░░░░░░
|
|
49
|
+
Impact 4 routes · 2 components · 1 test
|
|
50
|
+
Affected 7 files
|
|
51
|
+
Max depth 2
|
|
52
|
+
Confidence 100%
|
|
54
53
|
```
|
|
55
54
|
|
|
56
55
|
No black box.
|
|
@@ -117,30 +116,32 @@ ripple analyze src/authentication/login.ts
|
|
|
117
116
|
Expected output:
|
|
118
117
|
|
|
119
118
|
```text
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
Top
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
119
|
+
╭ ripple ────────────────────────────╮
|
|
120
|
+
│ ❯ impact analysis · v0.2.0 │
|
|
121
|
+
╰────────────────────────────────────╯
|
|
122
|
+
|
|
123
|
+
File src/authentication/login.ts
|
|
124
|
+
Risk MEDIUM · 34.7/100 ███░░░░░░░
|
|
125
|
+
Impact 4 routes · 2 components · 1 test
|
|
126
|
+
Affected 7 files
|
|
127
|
+
Max depth 2
|
|
128
|
+
Confidence 100%
|
|
129
|
+
|
|
130
|
+
────────────────────────────────────
|
|
131
|
+
Top impact
|
|
132
|
+
● Dashboard (2)
|
|
133
|
+
● Admin (1)
|
|
134
|
+
● Index TS (1)
|
|
135
|
+
|
|
136
|
+
────────────────────────────────────
|
|
137
|
+
Circular dependencies
|
|
138
|
+
◯ src/circular/a.ts → src/circular/c.ts → src/circular/b.ts → src/circular/a.ts
|
|
139
|
+
|
|
140
|
+
────────────────────────────────────
|
|
141
|
+
Affected files (7)
|
|
142
|
+
├─ src/admin/page.tsx · depth 1
|
|
143
|
+
├─ src/dashboard/page.tsx · depth 1
|
|
144
|
+
└─ src/main.ts · depth 2
|
|
144
145
|
```
|
|
145
146
|
|
|
146
147
|
### See more of the project
|
|
@@ -153,7 +154,9 @@ ripple graph
|
|
|
153
154
|
```
|
|
154
155
|
|
|
155
156
|
```text
|
|
156
|
-
|
|
157
|
+
╭ ripple ────────────────────────────╮
|
|
158
|
+
│ ❯ dependency graph · v0.2.0 │
|
|
159
|
+
╰────────────────────────────────────╯
|
|
157
160
|
|
|
158
161
|
Files 25
|
|
159
162
|
Edges 28
|
|
@@ -161,8 +164,9 @@ External 4
|
|
|
161
164
|
Unresolved 0
|
|
162
165
|
Circular groups 1
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
────────────────────────────────────
|
|
168
|
+
Circular dependencies
|
|
169
|
+
◯ src/circular/a.ts → src/circular/c.ts → src/circular/b.ts → src/circular/a.ts
|
|
166
170
|
```
|
|
167
171
|
|
|
168
172
|
Point it at a file to see its dependency tree — forwards (what it imports)
|
package/dist/bin.js
CHANGED
|
@@ -6,10 +6,12 @@ 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';
|
|
13
15
|
|
|
14
16
|
// src/output/writer.ts
|
|
15
17
|
var TerminalWriter = class {
|
|
@@ -673,24 +675,79 @@ function analyzeFile(input) {
|
|
|
673
675
|
maxDepth: reachedDepth
|
|
674
676
|
};
|
|
675
677
|
}
|
|
676
|
-
function
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
678
|
+
function dim(text, style) {
|
|
679
|
+
return style.color ? chalk.dim(text) : text;
|
|
680
|
+
}
|
|
681
|
+
function strong(text, style) {
|
|
682
|
+
return style.color ? chalk.bold(text) : text;
|
|
683
|
+
}
|
|
684
|
+
var RISK_COLORS = {
|
|
685
|
+
LOW: (text) => chalk.green(text),
|
|
686
|
+
MEDIUM: (text) => chalk.yellow(text),
|
|
687
|
+
HIGH: (text) => chalk.magenta(text),
|
|
688
|
+
CRITICAL: (text) => chalk.red.bold(text)
|
|
689
|
+
};
|
|
690
|
+
function riskColor(level) {
|
|
691
|
+
return RISK_COLORS[level];
|
|
692
|
+
}
|
|
693
|
+
function riskBadge(level, style) {
|
|
694
|
+
return style.color ? RISK_COLORS[level](level) : level;
|
|
695
|
+
}
|
|
696
|
+
function confidenceText(percent, style) {
|
|
697
|
+
const text = `${percent}%`;
|
|
698
|
+
if (!style.color) return text;
|
|
699
|
+
if (percent >= 90) return chalk.green(text);
|
|
700
|
+
if (percent >= 70) return chalk.yellow(text);
|
|
701
|
+
return chalk.red(text);
|
|
702
|
+
}
|
|
703
|
+
function formatDuration(ms) {
|
|
704
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
705
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
706
|
+
}
|
|
707
|
+
function pluralize(count, noun, plural = `${noun}s`) {
|
|
708
|
+
return `${count} ${count === 1 ? noun : plural}`;
|
|
709
|
+
}
|
|
710
|
+
function icon(name) {
|
|
711
|
+
return figures[name];
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/ui/progress.ts
|
|
715
|
+
var noop = {
|
|
716
|
+
next: () => {
|
|
717
|
+
},
|
|
718
|
+
done: () => {
|
|
681
719
|
}
|
|
682
|
-
|
|
720
|
+
};
|
|
721
|
+
function createStageTracker(enabled) {
|
|
722
|
+
if (!enabled || !process.stdout.isTTY) return noop;
|
|
723
|
+
let current;
|
|
724
|
+
let from = 0;
|
|
725
|
+
const stamp = (label, ms) => {
|
|
726
|
+
process.stdout.write(
|
|
727
|
+
`${chalk.green(icon("tick"))} ${label} ${chalk.dim(formatDuration(ms))}
|
|
728
|
+
`
|
|
729
|
+
);
|
|
730
|
+
};
|
|
683
731
|
return {
|
|
684
|
-
|
|
685
|
-
|
|
732
|
+
next(label) {
|
|
733
|
+
if (current) stamp(current, Date.now() - from);
|
|
734
|
+
current = label;
|
|
735
|
+
from = Date.now();
|
|
686
736
|
},
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
737
|
+
done() {
|
|
738
|
+
if (current) stamp(current, Date.now() - from);
|
|
739
|
+
current = void 0;
|
|
690
740
|
}
|
|
691
741
|
};
|
|
692
742
|
}
|
|
693
743
|
|
|
744
|
+
// src/ui/color.ts
|
|
745
|
+
function resolveColor(requested) {
|
|
746
|
+
if (requested === false) return false;
|
|
747
|
+
const noColor = process.env.NO_COLOR;
|
|
748
|
+
return noColor === void 0 || noColor === "";
|
|
749
|
+
}
|
|
750
|
+
|
|
694
751
|
// src/formatter/table.ts
|
|
695
752
|
function renderTable(columns, rows) {
|
|
696
753
|
const widths = columns.map((column, index) => {
|
|
@@ -728,50 +785,53 @@ function renderTree(nodes) {
|
|
|
728
785
|
}
|
|
729
786
|
return treeify.asTree(root, false, false).trimEnd();
|
|
730
787
|
}
|
|
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);
|
|
788
|
+
var DIVIDER_WIDTH = 38;
|
|
789
|
+
function brandHeader(options) {
|
|
790
|
+
const { label, version, style } = options;
|
|
791
|
+
if (!style.color) {
|
|
792
|
+
return `ripple \u2014 ${label}${version ? ` \xB7 v${version}` : ""}`;
|
|
793
|
+
}
|
|
794
|
+
const tagline = `${icon("pointer")} ${label}${version ? ` \xB7 v${version}` : ""}`;
|
|
795
|
+
return boxen(` ${tagline}`, {
|
|
796
|
+
padding: { left: 1, right: 1 },
|
|
797
|
+
title: "ripple",
|
|
798
|
+
borderStyle: "round",
|
|
799
|
+
borderColor: "cyan"
|
|
800
|
+
});
|
|
758
801
|
}
|
|
759
|
-
function
|
|
760
|
-
if (
|
|
761
|
-
return `${(
|
|
802
|
+
function sectionHeader(title, style) {
|
|
803
|
+
if (!style.color) return title;
|
|
804
|
+
return `${chalk.dim("\u2500".repeat(DIVIDER_WIDTH))}
|
|
805
|
+
${chalk.bold(title)}`;
|
|
762
806
|
}
|
|
763
|
-
|
|
764
|
-
|
|
807
|
+
|
|
808
|
+
// src/ui/gauge.ts
|
|
809
|
+
var CELLS = 10;
|
|
810
|
+
var FILL = "\u2588";
|
|
811
|
+
var EMPTY = "\u2591";
|
|
812
|
+
function riskGauge(score, level, style) {
|
|
813
|
+
if (!style.color) return "";
|
|
814
|
+
const filled = Math.max(0, Math.min(CELLS, Math.round(score / 100 * CELLS)));
|
|
815
|
+
return riskColor(level)(FILL.repeat(filled) + EMPTY.repeat(CELLS - filled));
|
|
765
816
|
}
|
|
766
817
|
|
|
767
818
|
// src/output/report.ts
|
|
768
819
|
var DEFAULT_LIST_LIMIT = 20;
|
|
769
820
|
var textStyle = (options) => ({ color: options.color });
|
|
770
821
|
function fileLabel(filePath, depth, inCycle, style) {
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
return
|
|
822
|
+
const parts = [dim(filePath, style)];
|
|
823
|
+
if (depth >= 0) parts.push(dim(`\xB7 depth ${depth}`, style));
|
|
824
|
+
if (inCycle) parts.push(dim("\xB7 cycle", style));
|
|
825
|
+
return parts.join(" ");
|
|
826
|
+
}
|
|
827
|
+
function impactLine(result, style) {
|
|
828
|
+
const { summary } = result;
|
|
829
|
+
const parts = [];
|
|
830
|
+
if (summary.routes > 0) parts.push(pluralize(summary.routes, "route"));
|
|
831
|
+
if (summary.components > 0) parts.push(pluralize(summary.components, "component"));
|
|
832
|
+
if (summary.tests > 0) parts.push(pluralize(summary.tests, "test"));
|
|
833
|
+
if (summary.utilities > 0) parts.push(pluralize(summary.utilities, "utility"));
|
|
834
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : dim("none", style);
|
|
775
835
|
}
|
|
776
836
|
function buildAffectedTree(result, cwd, style) {
|
|
777
837
|
const { affected, graph } = result;
|
|
@@ -811,29 +871,32 @@ function renderAnalyzeReport(result, options, writer) {
|
|
|
811
871
|
const style = textStyle(options);
|
|
812
872
|
const { summary, risk } = result;
|
|
813
873
|
const file = displayPath(result.targetPath, options.cwd);
|
|
814
|
-
writer.writeLine(
|
|
874
|
+
writer.writeLine(brandHeader({ label: "impact analysis", version: options.version, style }));
|
|
815
875
|
writer.writeLine();
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
);
|
|
876
|
+
const score = risk.score.toFixed(1);
|
|
877
|
+
const gauge = riskGauge(risk.score, risk.level, style);
|
|
878
|
+
const rows = [
|
|
879
|
+
["File", file],
|
|
880
|
+
["Risk", `${riskBadge(risk.level, style)} \xB7 ${score}/100${gauge ? ` ${gauge}` : ""}`],
|
|
881
|
+
["Impact", impactLine(result, style)],
|
|
882
|
+
["Affected", pluralize(summary.affectedFiles, "file")],
|
|
883
|
+
["Max depth", String(summary.maxDepth)],
|
|
884
|
+
["Confidence", confidenceText(summary.confidence, style)],
|
|
885
|
+
["Duration", dim(formatDuration(result.durationMs), style)]
|
|
886
|
+
];
|
|
887
|
+
const width = Math.max(...rows.map(([key]) => key.length));
|
|
888
|
+
for (const [key, value] of rows) {
|
|
889
|
+
writer.writeLine(`${key.padEnd(width)} ${value}`);
|
|
890
|
+
}
|
|
830
891
|
writer.writeLine();
|
|
831
|
-
writer.writeLine(
|
|
892
|
+
writer.writeLine(sectionHeader("Top impact", style));
|
|
832
893
|
for (const area of summary.topImpact) {
|
|
833
|
-
writer.writeLine(
|
|
894
|
+
writer.writeLine(
|
|
895
|
+
`${icon("bullet")} ${strong(area.label, style)} ${dim(`(${area.count})`, style)}`
|
|
896
|
+
);
|
|
834
897
|
}
|
|
835
898
|
writer.writeLine();
|
|
836
|
-
writer.writeLine(
|
|
899
|
+
writer.writeLine(sectionHeader("Circular dependencies", style));
|
|
837
900
|
if (result.graph.cycles.length === 0) {
|
|
838
901
|
writer.writeLine(dim("none", style));
|
|
839
902
|
} else {
|
|
@@ -841,13 +904,13 @@ function renderAnalyzeReport(result, options, writer) {
|
|
|
841
904
|
const chain = cycle.path.map((key) => {
|
|
842
905
|
const node = result.graph.nodes.get(key);
|
|
843
906
|
return displayPath(node?.path ?? key, options.cwd);
|
|
844
|
-
}).join(
|
|
845
|
-
writer.writeLine(
|
|
907
|
+
}).join(` ${icon("arrowRight")} `);
|
|
908
|
+
writer.writeLine(`${icon("circle")} ${chain}`);
|
|
846
909
|
}
|
|
847
910
|
}
|
|
848
911
|
writer.writeLine();
|
|
849
912
|
if (summary.affectedFiles > 0) {
|
|
850
|
-
writer.writeLine(
|
|
913
|
+
writer.writeLine(sectionHeader(`Affected files (${summary.affectedFiles})`, style));
|
|
851
914
|
const tree = buildAffectedTree(result, options.cwd, style);
|
|
852
915
|
writer.writeLine(renderTree(tree));
|
|
853
916
|
const limit = options.listLimit ?? DEFAULT_LIST_LIMIT;
|
|
@@ -859,22 +922,27 @@ function renderAnalyzeReport(result, options, writer) {
|
|
|
859
922
|
writer.writeLine();
|
|
860
923
|
}
|
|
861
924
|
if (options.verbose) {
|
|
862
|
-
writer.writeLine(
|
|
925
|
+
writer.writeLine(sectionHeader("Risk factors", style));
|
|
863
926
|
const columns = [
|
|
864
927
|
{ header: "Factor" },
|
|
865
928
|
{ header: "Weight" },
|
|
866
929
|
{ header: "Signal" },
|
|
867
930
|
{ header: "Points" }
|
|
868
931
|
];
|
|
869
|
-
const
|
|
932
|
+
const rowsVerbose = risk.factors.map((factor) => [
|
|
870
933
|
factor.label,
|
|
871
934
|
factor.weight.toFixed(2),
|
|
872
935
|
factor.value.toFixed(2),
|
|
873
936
|
factor.contribution.toFixed(2)
|
|
874
937
|
]);
|
|
875
|
-
writer.writeLine(renderTable(columns,
|
|
938
|
+
writer.writeLine(renderTable(columns, rowsVerbose));
|
|
876
939
|
writer.writeLine();
|
|
877
940
|
}
|
|
941
|
+
if (style.color) {
|
|
942
|
+
writer.writeLine(
|
|
943
|
+
dim(`${icon("info")} Tip: run "ripple doctor" to check project health`, style)
|
|
944
|
+
);
|
|
945
|
+
}
|
|
878
946
|
}
|
|
879
947
|
function buildAnalyzeJsonReport(result, version, cwd) {
|
|
880
948
|
const realPath = (key) => result.graph.nodes.get(key)?.path ?? key;
|
|
@@ -898,7 +966,7 @@ function buildAnalyzeJsonReport(result, version, cwd) {
|
|
|
898
966
|
function renderGraphReport(input, options, writer) {
|
|
899
967
|
const style = textStyle(options);
|
|
900
968
|
const { stats } = input;
|
|
901
|
-
writer.writeLine(
|
|
969
|
+
writer.writeLine(brandHeader({ label: "dependency graph", version: options.version, style }));
|
|
902
970
|
writer.writeLine();
|
|
903
971
|
writer.writeLine(
|
|
904
972
|
renderKeyValue([
|
|
@@ -911,26 +979,26 @@ function renderGraphReport(input, options, writer) {
|
|
|
911
979
|
);
|
|
912
980
|
writer.writeLine();
|
|
913
981
|
if (input.forward) {
|
|
914
|
-
writer.writeLine(
|
|
982
|
+
writer.writeLine(sectionHeader("Dependants (forward)", style));
|
|
915
983
|
writer.writeLine(renderTree(input.forward.map((path12) => ({ label: path12 }))));
|
|
916
984
|
writer.writeLine();
|
|
917
985
|
}
|
|
918
986
|
if (input.reverse) {
|
|
919
|
-
writer.writeLine(
|
|
987
|
+
writer.writeLine(sectionHeader("Dependents (reverse)", style));
|
|
920
988
|
writer.writeLine(renderTree(input.reverse.map((path12) => ({ label: path12 }))));
|
|
921
989
|
writer.writeLine();
|
|
922
990
|
}
|
|
923
|
-
|
|
924
|
-
|
|
991
|
+
writer.writeLine(sectionHeader("Circular dependencies", style));
|
|
992
|
+
if (input.cycles.length === 0) {
|
|
993
|
+
writer.writeLine(dim("none", style));
|
|
994
|
+
} else {
|
|
925
995
|
for (const cycle of input.cycles) {
|
|
926
996
|
const chain = cycle.path.map((key) => {
|
|
927
997
|
const node = input.nodes.get(key);
|
|
928
998
|
return displayPath(node?.path ?? key, input.cwd);
|
|
929
|
-
}).join(
|
|
930
|
-
writer.writeLine(
|
|
999
|
+
}).join(` ${icon("arrowRight")} `);
|
|
1000
|
+
writer.writeLine(`${icon("circle")} ${chain}`);
|
|
931
1001
|
}
|
|
932
|
-
} else {
|
|
933
|
-
writer.writeLine(dim("No circular dependencies found.", style));
|
|
934
1002
|
}
|
|
935
1003
|
}
|
|
936
1004
|
function buildGraphJsonReport(input, version) {
|
|
@@ -1413,11 +1481,13 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1413
1481
|
if (!await pathExists(targetPath)) {
|
|
1414
1482
|
throw new RippleError(`File not found: ${fileArg}`, ExitCode.NotFound);
|
|
1415
1483
|
}
|
|
1416
|
-
const
|
|
1417
|
-
|
|
1484
|
+
const tracker = createStageTracker(!options.json);
|
|
1485
|
+
tracker.next("Loading config");
|
|
1418
1486
|
const started = Date.now();
|
|
1419
1487
|
const context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1488
|
+
tracker.next("Building dependency graph");
|
|
1420
1489
|
const { graph, entryPoints, durationMs: pipelineMs } = await runPipeline(context);
|
|
1490
|
+
tracker.next("Analyzing impact");
|
|
1421
1491
|
const completed = Date.now();
|
|
1422
1492
|
const result = analyzeFile({
|
|
1423
1493
|
graph,
|
|
@@ -1428,7 +1498,7 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1428
1498
|
durationMs: pipelineMs + (completed - started),
|
|
1429
1499
|
...options.depth !== void 0 ? { maxDepth: options.depth } : {}
|
|
1430
1500
|
});
|
|
1431
|
-
|
|
1501
|
+
tracker.done();
|
|
1432
1502
|
if (options.json) {
|
|
1433
1503
|
const report = buildAnalyzeJsonReport(result, ctx2.version, ctx2.cwd);
|
|
1434
1504
|
ctx2.writer.write(serializeJson(report));
|
|
@@ -1438,8 +1508,9 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1438
1508
|
result,
|
|
1439
1509
|
{
|
|
1440
1510
|
cwd: ctx2.cwd,
|
|
1441
|
-
color: options.color
|
|
1442
|
-
verbose: options.verbose
|
|
1511
|
+
color: resolveColor(options.color),
|
|
1512
|
+
verbose: options.verbose,
|
|
1513
|
+
version: ctx2.version
|
|
1443
1514
|
},
|
|
1444
1515
|
ctx2.writer
|
|
1445
1516
|
);
|
|
@@ -1468,11 +1539,12 @@ function walk(graph, startKey, edges, maxDepth, cwd) {
|
|
|
1468
1539
|
return entries.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path)).map((entry) => entry.path);
|
|
1469
1540
|
}
|
|
1470
1541
|
async function graphCommand(fileArg, options, ctx2) {
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1542
|
+
const tracker = createStageTracker(!options.json);
|
|
1543
|
+
tracker.next("Loading config");
|
|
1473
1544
|
const cwd = ctx2.cwd;
|
|
1474
1545
|
try {
|
|
1475
1546
|
const context = await loadProjectContext(cwd, options.config);
|
|
1547
|
+
tracker.next("Building graph");
|
|
1476
1548
|
const { graph } = await runPipeline(context);
|
|
1477
1549
|
const input = {
|
|
1478
1550
|
stats: graph.stats,
|
|
@@ -1481,13 +1553,19 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1481
1553
|
cwd
|
|
1482
1554
|
};
|
|
1483
1555
|
if (!fileArg) {
|
|
1556
|
+
tracker.done();
|
|
1484
1557
|
if (options.json) {
|
|
1485
1558
|
const report = buildGraphJsonReport(input, ctx2.version);
|
|
1486
1559
|
ctx2.writer.write(serializeJson(report));
|
|
1487
1560
|
} else {
|
|
1488
1561
|
renderGraphReport(
|
|
1489
1562
|
input,
|
|
1490
|
-
{
|
|
1563
|
+
{
|
|
1564
|
+
cwd,
|
|
1565
|
+
color: resolveColor(options.color),
|
|
1566
|
+
verbose: options.verbose,
|
|
1567
|
+
version: ctx2.version
|
|
1568
|
+
},
|
|
1491
1569
|
ctx2.writer
|
|
1492
1570
|
);
|
|
1493
1571
|
}
|
|
@@ -1497,6 +1575,7 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1497
1575
|
const { key } = requireNode(graph, targetPath, context.rootDir);
|
|
1498
1576
|
const dependants = walk(graph, key, graph.forward, options.depth, cwd);
|
|
1499
1577
|
const dependents = walk(graph, key, graph.reverse, options.depth, cwd);
|
|
1578
|
+
tracker.done();
|
|
1500
1579
|
if (options.json) {
|
|
1501
1580
|
const report = buildGraphJsonReport(
|
|
1502
1581
|
{
|
|
@@ -1513,17 +1592,38 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1513
1592
|
forward: options.reverse ? void 0 : dependants,
|
|
1514
1593
|
reverse: options.reverse ? dependents : void 0
|
|
1515
1594
|
},
|
|
1516
|
-
{ cwd, color: options.color
|
|
1595
|
+
{ cwd, color: resolveColor(options.color), verbose: options.verbose, version: ctx2.version },
|
|
1517
1596
|
ctx2.writer
|
|
1518
1597
|
);
|
|
1519
1598
|
}
|
|
1520
1599
|
return ExitCode.Success;
|
|
1521
1600
|
} finally {
|
|
1522
|
-
|
|
1601
|
+
tracker.done();
|
|
1523
1602
|
}
|
|
1524
1603
|
}
|
|
1604
|
+
function createSpinner(enabled) {
|
|
1605
|
+
if (!enabled || !process.stdout.isTTY) {
|
|
1606
|
+
return { start: () => {
|
|
1607
|
+
}, stop: () => {
|
|
1608
|
+
} };
|
|
1609
|
+
}
|
|
1610
|
+
let instance;
|
|
1611
|
+
return {
|
|
1612
|
+
start(text) {
|
|
1613
|
+
instance = ora(text).start();
|
|
1614
|
+
},
|
|
1615
|
+
stop() {
|
|
1616
|
+
instance?.stop();
|
|
1617
|
+
instance = void 0;
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// src/commands/doctor.ts
|
|
1525
1623
|
async function doctorCommand(options, ctx2) {
|
|
1526
|
-
const
|
|
1624
|
+
const color = resolveColor(options.color);
|
|
1625
|
+
const style = { color };
|
|
1626
|
+
const spinner = createSpinner(color);
|
|
1527
1627
|
spinner.start("Running diagnostics\u2026");
|
|
1528
1628
|
const results = [];
|
|
1529
1629
|
const check = (label, state, detail) => {
|
|
@@ -1548,7 +1648,7 @@ async function doctorCommand(options, ctx2) {
|
|
|
1548
1648
|
context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1549
1649
|
} catch (error) {
|
|
1550
1650
|
check("ripple config", "fail", error instanceof Error ? error.message : String(error));
|
|
1551
|
-
renderResults(results, ctx2);
|
|
1651
|
+
renderResults(results, ctx2, color);
|
|
1552
1652
|
return ExitCode.InvalidConfig;
|
|
1553
1653
|
}
|
|
1554
1654
|
if (context.tsconfig) {
|
|
@@ -1597,29 +1697,39 @@ async function doctorCommand(options, ctx2) {
|
|
|
1597
1697
|
} finally {
|
|
1598
1698
|
spinner.stop();
|
|
1599
1699
|
}
|
|
1600
|
-
renderResults(results, ctx2);
|
|
1700
|
+
renderResults(results, ctx2, color);
|
|
1601
1701
|
const failed = results.some((check2) => check2.state === "fail");
|
|
1602
1702
|
const warned = results.some((check2) => check2.state === "warn");
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
);
|
|
1703
|
+
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 };
|
|
1704
|
+
const verdictIcon = style.color ? verdict.color(verdict.icon) : verdict.icon;
|
|
1705
|
+
ctx2.writer.writeLine(`${verdictIcon} ${verdict.text}`);
|
|
1606
1706
|
return failed ? ExitCode.Failure : ExitCode.Success;
|
|
1607
1707
|
}
|
|
1608
|
-
function renderResults(results, ctx2) {
|
|
1708
|
+
function renderResults(results, ctx2, color) {
|
|
1709
|
+
const style = { color };
|
|
1710
|
+
ctx2.writer.writeLine(brandHeader({ label: "project health check", version: ctx2.version, style }));
|
|
1711
|
+
ctx2.writer.writeLine();
|
|
1712
|
+
const width = Math.max(...results.map((check) => check.label.length));
|
|
1609
1713
|
for (const check of results) {
|
|
1610
|
-
const
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1714
|
+
const stateIcon = check.state === "ok" ? icon("tick") : check.state === "warn" ? icon("warning") : icon("cross");
|
|
1715
|
+
const colored = check.state === "ok" ? chalk.green : check.state === "warn" ? chalk.yellow : chalk.red;
|
|
1716
|
+
const mark = style.color ? colored(stateIcon) : stateIcon;
|
|
1717
|
+
const detail = check.detail ? ` ${dim(check.detail, style)}` : "";
|
|
1718
|
+
ctx2.writer.writeLine(`${mark} ${check.label.padEnd(width)}${detail}`);
|
|
1613
1719
|
}
|
|
1614
1720
|
ctx2.writer.writeLine();
|
|
1615
1721
|
}
|
|
1616
1722
|
var INIT_FILENAME = "ripple.config.json";
|
|
1617
1723
|
async function initCommand(options, ctx2) {
|
|
1724
|
+
const style = { color: resolveColor(options.color) };
|
|
1618
1725
|
const target = path8.join(ctx2.cwd, INIT_FILENAME);
|
|
1619
1726
|
try {
|
|
1620
1727
|
await fs.access(target);
|
|
1621
1728
|
if (!options.force) {
|
|
1622
|
-
|
|
1729
|
+
const mark = style.color ? chalk.red(icon("cross")) : icon("cross");
|
|
1730
|
+
ctx2.writer.writeLine(
|
|
1731
|
+
`${mark} ${INIT_FILENAME} already exists. Pass --force to overwrite it.`
|
|
1732
|
+
);
|
|
1623
1733
|
return ExitCode.Failure;
|
|
1624
1734
|
}
|
|
1625
1735
|
} catch {
|
|
@@ -1630,12 +1740,16 @@ async function initCommand(options, ctx2) {
|
|
|
1630
1740
|
try {
|
|
1631
1741
|
await fs.writeFile(target, `${JSON.stringify(payload, null, 2)}
|
|
1632
1742
|
`, "utf8");
|
|
1633
|
-
|
|
1634
|
-
ctx2.writer.writeLine(
|
|
1743
|
+
const mark = style.color ? chalk.green(icon("tick")) : icon("tick");
|
|
1744
|
+
ctx2.writer.writeLine(`${mark} Wrote ${INIT_FILENAME}`);
|
|
1745
|
+
ctx2.writer.writeLine(
|
|
1746
|
+
` ${dim("Edit it to tune include globs, aliases and risk weights.", style)}`
|
|
1747
|
+
);
|
|
1635
1748
|
return ExitCode.Success;
|
|
1636
1749
|
} catch (error) {
|
|
1750
|
+
const mark = style.color ? chalk.red(icon("cross")) : icon("cross");
|
|
1637
1751
|
ctx2.writer.writeLine(
|
|
1638
|
-
|
|
1752
|
+
`${mark} Could not write ${INIT_FILENAME}: ${error instanceof Error ? error.message : String(error)}`
|
|
1639
1753
|
);
|
|
1640
1754
|
return ExitCode.Failure;
|
|
1641
1755
|
}
|
|
@@ -1691,8 +1805,11 @@ function createProgram(ctx2) {
|
|
|
1691
1805
|
throw new ExitError(exitCode2);
|
|
1692
1806
|
}
|
|
1693
1807
|
});
|
|
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(
|
|
1808
|
+
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) => {
|
|
1809
|
+
const exitCode2 = await initCommand(
|
|
1810
|
+
{ force: Boolean(options.force), color: options.color },
|
|
1811
|
+
ctx2
|
|
1812
|
+
);
|
|
1696
1813
|
if (exitCode2 !== ExitCode.Success) {
|
|
1697
1814
|
throw new ExitError(exitCode2);
|
|
1698
1815
|
}
|
|
@@ -1700,6 +1817,11 @@ function createProgram(ctx2) {
|
|
|
1700
1817
|
program.command("version").description("Print the Ripple version").action(() => {
|
|
1701
1818
|
ctx2.writer.writeLine(ctx2.version);
|
|
1702
1819
|
});
|
|
1820
|
+
program.addHelpText(
|
|
1821
|
+
"beforeAll",
|
|
1822
|
+
`ripple \u2014 dependency impact analysis for TypeScript and JavaScript
|
|
1823
|
+
`
|
|
1824
|
+
);
|
|
1703
1825
|
return program;
|
|
1704
1826
|
}
|
|
1705
1827
|
var ExitError = class extends Error {
|
|
@@ -1754,7 +1876,7 @@ async function run({ argv, ctx: ctx2 }) {
|
|
|
1754
1876
|
|
|
1755
1877
|
// package.json
|
|
1756
1878
|
var package_default = {
|
|
1757
|
-
version: "0.
|
|
1879
|
+
version: "0.2.0"};
|
|
1758
1880
|
|
|
1759
1881
|
// src/cli/bin.ts
|
|
1760
1882
|
var ctx = {
|