@alimaandev/ripple 0.1.0 → 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 +51 -38
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +239 -177
- package/dist/bin.js.map +1 -1
- package/dist/chunk-OOERV74D.js +66 -0
- package/dist/chunk-OOERV74D.js.map +1 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/package.json +15 -1
package/dist/bin.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { DEFAULT_CONFIG, rippleConfigSchema, formatSchemaIssues } from './chunk-OOERV74D.js';
|
|
2
3
|
import { CommanderError, Command } from 'commander';
|
|
3
4
|
import { createJiti } from 'jiti';
|
|
4
5
|
import path8 from 'path';
|
|
5
6
|
import fs from 'fs/promises';
|
|
6
7
|
import fs2 from 'fs';
|
|
7
8
|
import { Project, ts, SyntaxKind } from 'ts-morph';
|
|
8
|
-
import { z } from 'zod';
|
|
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 {
|
|
@@ -183,66 +185,6 @@ function substituteAlias(specifier, aliases) {
|
|
|
183
185
|
return void 0;
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
// src/config/defaults.ts
|
|
187
|
-
var DEFAULT_CONFIG = {
|
|
188
|
-
include: ["**/*.{ts,tsx,js,jsx}"],
|
|
189
|
-
ignore: ["node_modules", "dist", "build", "coverage", ".next", "out", ".git"],
|
|
190
|
-
aliases: {},
|
|
191
|
-
tsconfigPath: "tsconfig.json",
|
|
192
|
-
risk: {
|
|
193
|
-
weights: {
|
|
194
|
-
affectedFiles: 0.3,
|
|
195
|
-
entryPoint: 0.15,
|
|
196
|
-
sharedUtility: 0.15,
|
|
197
|
-
publicExports: 0.1,
|
|
198
|
-
tests: 0.1,
|
|
199
|
-
routes: 0.1,
|
|
200
|
-
cycleMembership: 0.1
|
|
201
|
-
},
|
|
202
|
-
thresholds: {
|
|
203
|
-
medium: 30,
|
|
204
|
-
high: 55,
|
|
205
|
-
critical: 80
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
// src/config/schema.ts
|
|
211
|
-
var fraction = z.number().min(0).max(1);
|
|
212
|
-
var score = z.number().min(0).max(100);
|
|
213
|
-
var riskWeightsSchema = z.object({
|
|
214
|
-
affectedFiles: fraction.default(DEFAULT_CONFIG.risk.weights.affectedFiles),
|
|
215
|
-
entryPoint: fraction.default(DEFAULT_CONFIG.risk.weights.entryPoint),
|
|
216
|
-
sharedUtility: fraction.default(DEFAULT_CONFIG.risk.weights.sharedUtility),
|
|
217
|
-
publicExports: fraction.default(DEFAULT_CONFIG.risk.weights.publicExports),
|
|
218
|
-
tests: fraction.default(DEFAULT_CONFIG.risk.weights.tests),
|
|
219
|
-
routes: fraction.default(DEFAULT_CONFIG.risk.weights.routes),
|
|
220
|
-
cycleMembership: fraction.default(DEFAULT_CONFIG.risk.weights.cycleMembership)
|
|
221
|
-
}).strict();
|
|
222
|
-
var riskThresholdsSchema = z.object({
|
|
223
|
-
medium: score.default(DEFAULT_CONFIG.risk.thresholds.medium),
|
|
224
|
-
high: score.default(DEFAULT_CONFIG.risk.thresholds.high),
|
|
225
|
-
critical: score.default(DEFAULT_CONFIG.risk.thresholds.critical)
|
|
226
|
-
}).strict();
|
|
227
|
-
var riskConfigSchema = z.object({
|
|
228
|
-
weights: riskWeightsSchema.default(DEFAULT_CONFIG.risk.weights),
|
|
229
|
-
thresholds: riskThresholdsSchema.default(DEFAULT_CONFIG.risk.thresholds)
|
|
230
|
-
}).strict();
|
|
231
|
-
var rippleConfigSchema = z.object({
|
|
232
|
-
include: z.array(z.string().min(1)).min(1).default(DEFAULT_CONFIG.include),
|
|
233
|
-
ignore: z.array(z.string()).default(DEFAULT_CONFIG.ignore),
|
|
234
|
-
aliases: z.record(z.string(), z.string()).default(DEFAULT_CONFIG.aliases),
|
|
235
|
-
tsconfigPath: z.string().min(1).default(DEFAULT_CONFIG.tsconfigPath),
|
|
236
|
-
risk: riskConfigSchema.default(DEFAULT_CONFIG.risk)
|
|
237
|
-
}).strict();
|
|
238
|
-
function formatSchemaIssues(error) {
|
|
239
|
-
return error.issues.map((issue) => {
|
|
240
|
-
const path12 = issue.path.join(".");
|
|
241
|
-
const location = path12 === "" ? "config" : `config.${path12}`;
|
|
242
|
-
return `${location}: ${issue.message}`;
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
188
|
// src/config/loader.ts
|
|
247
189
|
var CONFIG_CANDIDATES = [
|
|
248
190
|
"ripple.config.ts",
|
|
@@ -600,10 +542,10 @@ function reverseTraverse(graph, targetKey, options) {
|
|
|
600
542
|
}
|
|
601
543
|
|
|
602
544
|
// src/risk/levels.ts
|
|
603
|
-
function scoreToLevel(
|
|
604
|
-
if (
|
|
605
|
-
if (
|
|
606
|
-
if (
|
|
545
|
+
function scoreToLevel(score, thresholds) {
|
|
546
|
+
if (score >= thresholds.critical) return "CRITICAL";
|
|
547
|
+
if (score >= thresholds.high) return "HIGH";
|
|
548
|
+
if (score >= thresholds.medium) return "MEDIUM";
|
|
607
549
|
return "LOW";
|
|
608
550
|
}
|
|
609
551
|
function clamp(value, min, max) {
|
|
@@ -684,14 +626,14 @@ function scoreRisk(input) {
|
|
|
684
626
|
contribution: Math.round(weight * value * 100 * 100) / 100
|
|
685
627
|
};
|
|
686
628
|
});
|
|
687
|
-
const
|
|
629
|
+
const score = clamp(
|
|
688
630
|
Math.round(factors.reduce((sum, factor) => sum + factor.contribution, 0) * 100) / 100,
|
|
689
631
|
0,
|
|
690
632
|
100
|
|
691
633
|
);
|
|
692
634
|
return {
|
|
693
|
-
score
|
|
694
|
-
level: scoreToLevel(
|
|
635
|
+
score,
|
|
636
|
+
level: scoreToLevel(score, input.riskConfig.thresholds),
|
|
695
637
|
factors
|
|
696
638
|
};
|
|
697
639
|
}
|
|
@@ -733,24 +675,79 @@ function analyzeFile(input) {
|
|
|
733
675
|
maxDepth: reachedDepth
|
|
734
676
|
};
|
|
735
677
|
}
|
|
736
|
-
function
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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: () => {
|
|
741
719
|
}
|
|
742
|
-
|
|
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
|
+
};
|
|
743
731
|
return {
|
|
744
|
-
|
|
745
|
-
|
|
732
|
+
next(label) {
|
|
733
|
+
if (current) stamp(current, Date.now() - from);
|
|
734
|
+
current = label;
|
|
735
|
+
from = Date.now();
|
|
746
736
|
},
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
737
|
+
done() {
|
|
738
|
+
if (current) stamp(current, Date.now() - from);
|
|
739
|
+
current = void 0;
|
|
750
740
|
}
|
|
751
741
|
};
|
|
752
742
|
}
|
|
753
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
|
+
|
|
754
751
|
// src/formatter/table.ts
|
|
755
752
|
function renderTable(columns, rows) {
|
|
756
753
|
const widths = columns.map((column, index) => {
|
|
@@ -788,50 +785,53 @@ function renderTree(nodes) {
|
|
|
788
785
|
}
|
|
789
786
|
return treeify.asTree(root, false, false).trimEnd();
|
|
790
787
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
return
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
LOW: (text) => chalk.green(text),
|
|
805
|
-
MEDIUM: (text) => chalk.yellow(text),
|
|
806
|
-
HIGH: (text) => chalk.magenta(text),
|
|
807
|
-
CRITICAL: (text) => chalk.red.bold(text)
|
|
808
|
-
};
|
|
809
|
-
function riskBadge(level, style) {
|
|
810
|
-
return style.color ? RISK_COLORS[level](level) : level;
|
|
811
|
-
}
|
|
812
|
-
function confidenceText(percent, style) {
|
|
813
|
-
const text = `${percent}%`;
|
|
814
|
-
if (!style.color) return text;
|
|
815
|
-
if (percent >= 90) return chalk.green(text);
|
|
816
|
-
if (percent >= 70) return chalk.yellow(text);
|
|
817
|
-
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
|
+
});
|
|
818
801
|
}
|
|
819
|
-
function
|
|
820
|
-
if (
|
|
821
|
-
return `${(
|
|
802
|
+
function sectionHeader(title, style) {
|
|
803
|
+
if (!style.color) return title;
|
|
804
|
+
return `${chalk.dim("\u2500".repeat(DIVIDER_WIDTH))}
|
|
805
|
+
${chalk.bold(title)}`;
|
|
822
806
|
}
|
|
823
|
-
|
|
824
|
-
|
|
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));
|
|
825
816
|
}
|
|
826
817
|
|
|
827
818
|
// src/output/report.ts
|
|
828
819
|
var DEFAULT_LIST_LIMIT = 20;
|
|
829
820
|
var textStyle = (options) => ({ color: options.color });
|
|
830
821
|
function fileLabel(filePath, depth, inCycle, style) {
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
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);
|
|
835
835
|
}
|
|
836
836
|
function buildAffectedTree(result, cwd, style) {
|
|
837
837
|
const { affected, graph } = result;
|
|
@@ -871,29 +871,32 @@ function renderAnalyzeReport(result, options, writer) {
|
|
|
871
871
|
const style = textStyle(options);
|
|
872
872
|
const { summary, risk } = result;
|
|
873
873
|
const file = displayPath(result.targetPath, options.cwd);
|
|
874
|
-
writer.writeLine(
|
|
874
|
+
writer.writeLine(brandHeader({ label: "impact analysis", version: options.version, style }));
|
|
875
875
|
writer.writeLine();
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
);
|
|
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
|
+
}
|
|
890
891
|
writer.writeLine();
|
|
891
|
-
writer.writeLine(
|
|
892
|
+
writer.writeLine(sectionHeader("Top impact", style));
|
|
892
893
|
for (const area of summary.topImpact) {
|
|
893
|
-
writer.writeLine(
|
|
894
|
+
writer.writeLine(
|
|
895
|
+
`${icon("bullet")} ${strong(area.label, style)} ${dim(`(${area.count})`, style)}`
|
|
896
|
+
);
|
|
894
897
|
}
|
|
895
898
|
writer.writeLine();
|
|
896
|
-
writer.writeLine(
|
|
899
|
+
writer.writeLine(sectionHeader("Circular dependencies", style));
|
|
897
900
|
if (result.graph.cycles.length === 0) {
|
|
898
901
|
writer.writeLine(dim("none", style));
|
|
899
902
|
} else {
|
|
@@ -901,13 +904,13 @@ function renderAnalyzeReport(result, options, writer) {
|
|
|
901
904
|
const chain = cycle.path.map((key) => {
|
|
902
905
|
const node = result.graph.nodes.get(key);
|
|
903
906
|
return displayPath(node?.path ?? key, options.cwd);
|
|
904
|
-
}).join(
|
|
905
|
-
writer.writeLine(
|
|
907
|
+
}).join(` ${icon("arrowRight")} `);
|
|
908
|
+
writer.writeLine(`${icon("circle")} ${chain}`);
|
|
906
909
|
}
|
|
907
910
|
}
|
|
908
911
|
writer.writeLine();
|
|
909
912
|
if (summary.affectedFiles > 0) {
|
|
910
|
-
writer.writeLine(
|
|
913
|
+
writer.writeLine(sectionHeader(`Affected files (${summary.affectedFiles})`, style));
|
|
911
914
|
const tree = buildAffectedTree(result, options.cwd, style);
|
|
912
915
|
writer.writeLine(renderTree(tree));
|
|
913
916
|
const limit = options.listLimit ?? DEFAULT_LIST_LIMIT;
|
|
@@ -919,22 +922,27 @@ function renderAnalyzeReport(result, options, writer) {
|
|
|
919
922
|
writer.writeLine();
|
|
920
923
|
}
|
|
921
924
|
if (options.verbose) {
|
|
922
|
-
writer.writeLine(
|
|
925
|
+
writer.writeLine(sectionHeader("Risk factors", style));
|
|
923
926
|
const columns = [
|
|
924
927
|
{ header: "Factor" },
|
|
925
928
|
{ header: "Weight" },
|
|
926
929
|
{ header: "Signal" },
|
|
927
930
|
{ header: "Points" }
|
|
928
931
|
];
|
|
929
|
-
const
|
|
932
|
+
const rowsVerbose = risk.factors.map((factor) => [
|
|
930
933
|
factor.label,
|
|
931
934
|
factor.weight.toFixed(2),
|
|
932
935
|
factor.value.toFixed(2),
|
|
933
936
|
factor.contribution.toFixed(2)
|
|
934
937
|
]);
|
|
935
|
-
writer.writeLine(renderTable(columns,
|
|
938
|
+
writer.writeLine(renderTable(columns, rowsVerbose));
|
|
936
939
|
writer.writeLine();
|
|
937
940
|
}
|
|
941
|
+
if (style.color) {
|
|
942
|
+
writer.writeLine(
|
|
943
|
+
dim(`${icon("info")} Tip: run "ripple doctor" to check project health`, style)
|
|
944
|
+
);
|
|
945
|
+
}
|
|
938
946
|
}
|
|
939
947
|
function buildAnalyzeJsonReport(result, version, cwd) {
|
|
940
948
|
const realPath = (key) => result.graph.nodes.get(key)?.path ?? key;
|
|
@@ -958,7 +966,7 @@ function buildAnalyzeJsonReport(result, version, cwd) {
|
|
|
958
966
|
function renderGraphReport(input, options, writer) {
|
|
959
967
|
const style = textStyle(options);
|
|
960
968
|
const { stats } = input;
|
|
961
|
-
writer.writeLine(
|
|
969
|
+
writer.writeLine(brandHeader({ label: "dependency graph", version: options.version, style }));
|
|
962
970
|
writer.writeLine();
|
|
963
971
|
writer.writeLine(
|
|
964
972
|
renderKeyValue([
|
|
@@ -971,26 +979,26 @@ function renderGraphReport(input, options, writer) {
|
|
|
971
979
|
);
|
|
972
980
|
writer.writeLine();
|
|
973
981
|
if (input.forward) {
|
|
974
|
-
writer.writeLine(
|
|
982
|
+
writer.writeLine(sectionHeader("Dependants (forward)", style));
|
|
975
983
|
writer.writeLine(renderTree(input.forward.map((path12) => ({ label: path12 }))));
|
|
976
984
|
writer.writeLine();
|
|
977
985
|
}
|
|
978
986
|
if (input.reverse) {
|
|
979
|
-
writer.writeLine(
|
|
987
|
+
writer.writeLine(sectionHeader("Dependents (reverse)", style));
|
|
980
988
|
writer.writeLine(renderTree(input.reverse.map((path12) => ({ label: path12 }))));
|
|
981
989
|
writer.writeLine();
|
|
982
990
|
}
|
|
983
|
-
|
|
984
|
-
|
|
991
|
+
writer.writeLine(sectionHeader("Circular dependencies", style));
|
|
992
|
+
if (input.cycles.length === 0) {
|
|
993
|
+
writer.writeLine(dim("none", style));
|
|
994
|
+
} else {
|
|
985
995
|
for (const cycle of input.cycles) {
|
|
986
996
|
const chain = cycle.path.map((key) => {
|
|
987
997
|
const node = input.nodes.get(key);
|
|
988
998
|
return displayPath(node?.path ?? key, input.cwd);
|
|
989
|
-
}).join(
|
|
990
|
-
writer.writeLine(
|
|
999
|
+
}).join(` ${icon("arrowRight")} `);
|
|
1000
|
+
writer.writeLine(`${icon("circle")} ${chain}`);
|
|
991
1001
|
}
|
|
992
|
-
} else {
|
|
993
|
-
writer.writeLine(dim("No circular dependencies found.", style));
|
|
994
1002
|
}
|
|
995
1003
|
}
|
|
996
1004
|
function buildGraphJsonReport(input, version) {
|
|
@@ -1473,11 +1481,13 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1473
1481
|
if (!await pathExists(targetPath)) {
|
|
1474
1482
|
throw new RippleError(`File not found: ${fileArg}`, ExitCode.NotFound);
|
|
1475
1483
|
}
|
|
1476
|
-
const
|
|
1477
|
-
|
|
1484
|
+
const tracker = createStageTracker(!options.json);
|
|
1485
|
+
tracker.next("Loading config");
|
|
1478
1486
|
const started = Date.now();
|
|
1479
1487
|
const context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1488
|
+
tracker.next("Building dependency graph");
|
|
1480
1489
|
const { graph, entryPoints, durationMs: pipelineMs } = await runPipeline(context);
|
|
1490
|
+
tracker.next("Analyzing impact");
|
|
1481
1491
|
const completed = Date.now();
|
|
1482
1492
|
const result = analyzeFile({
|
|
1483
1493
|
graph,
|
|
@@ -1488,7 +1498,7 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1488
1498
|
durationMs: pipelineMs + (completed - started),
|
|
1489
1499
|
...options.depth !== void 0 ? { maxDepth: options.depth } : {}
|
|
1490
1500
|
});
|
|
1491
|
-
|
|
1501
|
+
tracker.done();
|
|
1492
1502
|
if (options.json) {
|
|
1493
1503
|
const report = buildAnalyzeJsonReport(result, ctx2.version, ctx2.cwd);
|
|
1494
1504
|
ctx2.writer.write(serializeJson(report));
|
|
@@ -1498,8 +1508,9 @@ async function analyzeCommand(fileArg, options, ctx2) {
|
|
|
1498
1508
|
result,
|
|
1499
1509
|
{
|
|
1500
1510
|
cwd: ctx2.cwd,
|
|
1501
|
-
color: options.color
|
|
1502
|
-
verbose: options.verbose
|
|
1511
|
+
color: resolveColor(options.color),
|
|
1512
|
+
verbose: options.verbose,
|
|
1513
|
+
version: ctx2.version
|
|
1503
1514
|
},
|
|
1504
1515
|
ctx2.writer
|
|
1505
1516
|
);
|
|
@@ -1528,11 +1539,12 @@ function walk(graph, startKey, edges, maxDepth, cwd) {
|
|
|
1528
1539
|
return entries.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path)).map((entry) => entry.path);
|
|
1529
1540
|
}
|
|
1530
1541
|
async function graphCommand(fileArg, options, ctx2) {
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1542
|
+
const tracker = createStageTracker(!options.json);
|
|
1543
|
+
tracker.next("Loading config");
|
|
1533
1544
|
const cwd = ctx2.cwd;
|
|
1534
1545
|
try {
|
|
1535
1546
|
const context = await loadProjectContext(cwd, options.config);
|
|
1547
|
+
tracker.next("Building graph");
|
|
1536
1548
|
const { graph } = await runPipeline(context);
|
|
1537
1549
|
const input = {
|
|
1538
1550
|
stats: graph.stats,
|
|
@@ -1541,13 +1553,19 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1541
1553
|
cwd
|
|
1542
1554
|
};
|
|
1543
1555
|
if (!fileArg) {
|
|
1556
|
+
tracker.done();
|
|
1544
1557
|
if (options.json) {
|
|
1545
1558
|
const report = buildGraphJsonReport(input, ctx2.version);
|
|
1546
1559
|
ctx2.writer.write(serializeJson(report));
|
|
1547
1560
|
} else {
|
|
1548
1561
|
renderGraphReport(
|
|
1549
1562
|
input,
|
|
1550
|
-
{
|
|
1563
|
+
{
|
|
1564
|
+
cwd,
|
|
1565
|
+
color: resolveColor(options.color),
|
|
1566
|
+
verbose: options.verbose,
|
|
1567
|
+
version: ctx2.version
|
|
1568
|
+
},
|
|
1551
1569
|
ctx2.writer
|
|
1552
1570
|
);
|
|
1553
1571
|
}
|
|
@@ -1557,6 +1575,7 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1557
1575
|
const { key } = requireNode(graph, targetPath, context.rootDir);
|
|
1558
1576
|
const dependants = walk(graph, key, graph.forward, options.depth, cwd);
|
|
1559
1577
|
const dependents = walk(graph, key, graph.reverse, options.depth, cwd);
|
|
1578
|
+
tracker.done();
|
|
1560
1579
|
if (options.json) {
|
|
1561
1580
|
const report = buildGraphJsonReport(
|
|
1562
1581
|
{
|
|
@@ -1573,17 +1592,38 @@ async function graphCommand(fileArg, options, ctx2) {
|
|
|
1573
1592
|
forward: options.reverse ? void 0 : dependants,
|
|
1574
1593
|
reverse: options.reverse ? dependents : void 0
|
|
1575
1594
|
},
|
|
1576
|
-
{ cwd, color: options.color
|
|
1595
|
+
{ cwd, color: resolveColor(options.color), verbose: options.verbose, version: ctx2.version },
|
|
1577
1596
|
ctx2.writer
|
|
1578
1597
|
);
|
|
1579
1598
|
}
|
|
1580
1599
|
return ExitCode.Success;
|
|
1581
1600
|
} finally {
|
|
1582
|
-
|
|
1601
|
+
tracker.done();
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
function createSpinner(enabled) {
|
|
1605
|
+
if (!enabled || !process.stdout.isTTY) {
|
|
1606
|
+
return { start: () => {
|
|
1607
|
+
}, stop: () => {
|
|
1608
|
+
} };
|
|
1583
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
|
+
};
|
|
1584
1620
|
}
|
|
1621
|
+
|
|
1622
|
+
// src/commands/doctor.ts
|
|
1585
1623
|
async function doctorCommand(options, ctx2) {
|
|
1586
|
-
const
|
|
1624
|
+
const color = resolveColor(options.color);
|
|
1625
|
+
const style = { color };
|
|
1626
|
+
const spinner = createSpinner(color);
|
|
1587
1627
|
spinner.start("Running diagnostics\u2026");
|
|
1588
1628
|
const results = [];
|
|
1589
1629
|
const check = (label, state, detail) => {
|
|
@@ -1608,7 +1648,7 @@ async function doctorCommand(options, ctx2) {
|
|
|
1608
1648
|
context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1609
1649
|
} catch (error) {
|
|
1610
1650
|
check("ripple config", "fail", error instanceof Error ? error.message : String(error));
|
|
1611
|
-
renderResults(results, ctx2);
|
|
1651
|
+
renderResults(results, ctx2, color);
|
|
1612
1652
|
return ExitCode.InvalidConfig;
|
|
1613
1653
|
}
|
|
1614
1654
|
if (context.tsconfig) {
|
|
@@ -1657,29 +1697,39 @@ async function doctorCommand(options, ctx2) {
|
|
|
1657
1697
|
} finally {
|
|
1658
1698
|
spinner.stop();
|
|
1659
1699
|
}
|
|
1660
|
-
renderResults(results, ctx2);
|
|
1700
|
+
renderResults(results, ctx2, color);
|
|
1661
1701
|
const failed = results.some((check2) => check2.state === "fail");
|
|
1662
1702
|
const warned = results.some((check2) => check2.state === "warn");
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
);
|
|
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}`);
|
|
1666
1706
|
return failed ? ExitCode.Failure : ExitCode.Success;
|
|
1667
1707
|
}
|
|
1668
|
-
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));
|
|
1669
1713
|
for (const check of results) {
|
|
1670
|
-
const
|
|
1671
|
-
const
|
|
1672
|
-
|
|
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}`);
|
|
1673
1719
|
}
|
|
1674
1720
|
ctx2.writer.writeLine();
|
|
1675
1721
|
}
|
|
1676
1722
|
var INIT_FILENAME = "ripple.config.json";
|
|
1677
1723
|
async function initCommand(options, ctx2) {
|
|
1724
|
+
const style = { color: resolveColor(options.color) };
|
|
1678
1725
|
const target = path8.join(ctx2.cwd, INIT_FILENAME);
|
|
1679
1726
|
try {
|
|
1680
1727
|
await fs.access(target);
|
|
1681
1728
|
if (!options.force) {
|
|
1682
|
-
|
|
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
|
+
);
|
|
1683
1733
|
return ExitCode.Failure;
|
|
1684
1734
|
}
|
|
1685
1735
|
} catch {
|
|
@@ -1690,12 +1740,16 @@ async function initCommand(options, ctx2) {
|
|
|
1690
1740
|
try {
|
|
1691
1741
|
await fs.writeFile(target, `${JSON.stringify(payload, null, 2)}
|
|
1692
1742
|
`, "utf8");
|
|
1693
|
-
|
|
1694
|
-
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
|
+
);
|
|
1695
1748
|
return ExitCode.Success;
|
|
1696
1749
|
} catch (error) {
|
|
1750
|
+
const mark = style.color ? chalk.red(icon("cross")) : icon("cross");
|
|
1697
1751
|
ctx2.writer.writeLine(
|
|
1698
|
-
|
|
1752
|
+
`${mark} Could not write ${INIT_FILENAME}: ${error instanceof Error ? error.message : String(error)}`
|
|
1699
1753
|
);
|
|
1700
1754
|
return ExitCode.Failure;
|
|
1701
1755
|
}
|
|
@@ -1751,8 +1805,11 @@ function createProgram(ctx2) {
|
|
|
1751
1805
|
throw new ExitError(exitCode2);
|
|
1752
1806
|
}
|
|
1753
1807
|
});
|
|
1754
|
-
program.command("init").description("Create a ripple.config.json with the default settings").option("-f, --force", "overwrite an existing config file").action(async (options) => {
|
|
1755
|
-
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
|
+
);
|
|
1756
1813
|
if (exitCode2 !== ExitCode.Success) {
|
|
1757
1814
|
throw new ExitError(exitCode2);
|
|
1758
1815
|
}
|
|
@@ -1760,6 +1817,11 @@ function createProgram(ctx2) {
|
|
|
1760
1817
|
program.command("version").description("Print the Ripple version").action(() => {
|
|
1761
1818
|
ctx2.writer.writeLine(ctx2.version);
|
|
1762
1819
|
});
|
|
1820
|
+
program.addHelpText(
|
|
1821
|
+
"beforeAll",
|
|
1822
|
+
`ripple \u2014 dependency impact analysis for TypeScript and JavaScript
|
|
1823
|
+
`
|
|
1824
|
+
);
|
|
1763
1825
|
return program;
|
|
1764
1826
|
}
|
|
1765
1827
|
var ExitError = class extends Error {
|
|
@@ -1814,7 +1876,7 @@ async function run({ argv, ctx: ctx2 }) {
|
|
|
1814
1876
|
|
|
1815
1877
|
// package.json
|
|
1816
1878
|
var package_default = {
|
|
1817
|
-
version: "0.
|
|
1879
|
+
version: "0.2.0"};
|
|
1818
1880
|
|
|
1819
1881
|
// src/cli/bin.ts
|
|
1820
1882
|
var ctx = {
|