@alimaandev/ripple 0.4.0 → 0.5.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 +40 -22
- package/dist/bin.js +75 -12
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ Everything below is real output against a shipped example project in `tests/fixt
|
|
|
97
97
|
```bash
|
|
98
98
|
npm install -g @alimaandev/ripple
|
|
99
99
|
|
|
100
|
-
ripple version # → 0.
|
|
100
|
+
ripple version # → 0.5.0
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
Or install from source:
|
|
@@ -212,28 +212,29 @@ ripple <command> [options]
|
|
|
212
212
|
|
|
213
213
|
### Options
|
|
214
214
|
|
|
215
|
-
| Command | Flag | Description
|
|
216
|
-
| --------- | --------------------- |
|
|
217
|
-
| `analyze` | `-j, --json` | Emit the JSON report instead of the terminal report
|
|
218
|
-
| `analyze` | `-v, --verbose` | Include the risk-factor point breakdown
|
|
219
|
-
| `analyze` | `-d, --depth <n>` | Cap the reverse traversal at `n` levels
|
|
220
|
-
| `analyze` | `-c, --config <path>` | Use a specific config file
|
|
221
|
-
| `analyze` | `--no-color` | Disable ANSI colors
|
|
222
|
-
| `graph` | `-j, --json` | Emit the JSON report
|
|
223
|
-
| `graph` | `-r, --reverse` | Show dependents (what imports this file) instead of dependencies
|
|
224
|
-
| `graph` | `-d, --depth <n>` | Cap tree depth
|
|
225
|
-
| `graph` | `-c, --config <path>` | Use a specific config file
|
|
226
|
-
| `graph` | `--no-color` | Disable ANSI colors
|
|
227
|
-
| `doctor` | `-c, --config <path>` | Use a specific config file
|
|
228
|
-
| `doctor` | `-v, --verbose` | Verbose output
|
|
229
|
-
| `doctor` | `--no-color` | Disable ANSI colors
|
|
230
|
-
| `init` | `-f, --force` | Overwrite an existing config
|
|
231
|
-
| `diff` | `-j, --json` | Emit the JSON report instead of the terminal report
|
|
215
|
+
| Command | Flag | Description |
|
|
216
|
+
| --------- | --------------------- | ---------------------------------------------------------------------------------------------- |
|
|
217
|
+
| `analyze` | `-j, --json` | Emit the JSON report instead of the terminal report |
|
|
218
|
+
| `analyze` | `-v, --verbose` | Include the risk-factor point breakdown |
|
|
219
|
+
| `analyze` | `-d, --depth <n>` | Cap the reverse traversal at `n` levels |
|
|
220
|
+
| `analyze` | `-c, --config <path>` | Use a specific config file |
|
|
221
|
+
| `analyze` | `--no-color` | Disable ANSI colors |
|
|
222
|
+
| `graph` | `-j, --json` | Emit the JSON report |
|
|
223
|
+
| `graph` | `-r, --reverse` | Show dependents (what imports this file) instead of dependencies |
|
|
224
|
+
| `graph` | `-d, --depth <n>` | Cap tree depth |
|
|
225
|
+
| `graph` | `-c, --config <path>` | Use a specific config file |
|
|
226
|
+
| `graph` | `--no-color` | Disable ANSI colors |
|
|
227
|
+
| `doctor` | `-c, --config <path>` | Use a specific config file |
|
|
228
|
+
| `doctor` | `-v, --verbose` | Verbose output |
|
|
229
|
+
| `doctor` | `--no-color` | Disable ANSI colors |
|
|
230
|
+
| `init` | `-f, --force` | Overwrite an existing config |
|
|
231
|
+
| `diff` | `-j, --json` | Emit the JSON report instead of the terminal report |
|
|
232
232
|
| `diff` | `-b, --base <ref>` | Git ref to diff against (default: `origin/main`, `main`, `HEAD~1`, or `diff.base` from config) |
|
|
233
|
-
| `diff` | `-g, --gate <level>` | Blocking level: `medium`, `high`, or `critical` (default: `high`, or `diff.gate` from config)
|
|
234
|
-
| `diff` | `-
|
|
235
|
-
| `diff` | `-
|
|
236
|
-
| `diff` |
|
|
233
|
+
| `diff` | `-g, --gate <level>` | Blocking level: `medium`, `high`, or `critical` (default: `high`, or `diff.gate` from config) |
|
|
234
|
+
| `diff` | `-f, --format <fmt>` | Output: `terminal`, `json`, or `github` (workflow annotations) |
|
|
235
|
+
| `diff` | `-d, --depth <n>` | Cap reverse traversal per file |
|
|
236
|
+
| `diff` | `-c, --config <path>` | Use a specific config file |
|
|
237
|
+
| `diff` | `--no-color` | Disable ANSI colors |
|
|
237
238
|
|
|
238
239
|
### `ripple doctor`
|
|
239
240
|
|
|
@@ -298,6 +299,23 @@ Risk analysis (2 files)
|
|
|
298
299
|
Run `ripple diff` locally before opening a PR, and in CI as the same gate —
|
|
299
300
|
identical code, identical verdict.
|
|
300
301
|
|
|
302
|
+
```bash
|
|
303
|
+
ripple diff --format github
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`--format github` emits GitHub Actions workflow commands. Each risky change
|
|
307
|
+
shows as a warning or error annotation right on the PR's Files tab —
|
|
308
|
+
`::error` when a file blocks the gate, `::warning` otherwise — plus a
|
|
309
|
+
`::notice` line with the gate verdict. The exit code still carries the
|
|
310
|
+
verdict, so the job fails when the gate blocks:
|
|
311
|
+
|
|
312
|
+
```yaml
|
|
313
|
+
- run: npx @alimaandev/ripple diff --format github
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
(`--json` is shorthand for `--format json`; the format flag also accepts
|
|
317
|
+
`terminal`, the default.)
|
|
318
|
+
|
|
301
319
|
## Configuration
|
|
302
320
|
|
|
303
321
|
Ripple discovers `ripple.config.ts`, `.js`, `.cjs`, `.mjs`, or `.json` in the
|
package/dist/bin.js
CHANGED
|
@@ -1806,9 +1806,48 @@ function changedFiles(cwd, base) {
|
|
|
1806
1806
|
return { baseLabel, files: [...files].sort() };
|
|
1807
1807
|
}
|
|
1808
1808
|
|
|
1809
|
-
// src/
|
|
1809
|
+
// src/formatter/annotations.ts
|
|
1810
1810
|
var LEVEL_INDEX = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
1811
1811
|
var GATE_INDEX = { medium: 1, high: 2, critical: 3 };
|
|
1812
|
+
function escapeProperty(value) {
|
|
1813
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
1814
|
+
}
|
|
1815
|
+
function escapeMessage(value) {
|
|
1816
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
1817
|
+
}
|
|
1818
|
+
function renderAnnotation(annotation) {
|
|
1819
|
+
const props = annotation.file ? ` file=${escapeProperty(annotation.file)},title=${escapeProperty(annotation.title)}` : ` title=${escapeProperty(annotation.title)}`;
|
|
1820
|
+
return `::${annotation.level}${props}::${escapeMessage(annotation.message)}`;
|
|
1821
|
+
}
|
|
1822
|
+
function buildFileAnnotations(files, gate) {
|
|
1823
|
+
return files.map(({ file, result }) => {
|
|
1824
|
+
const blocked = LEVEL_INDEX[result.risk.level] >= GATE_INDEX[gate];
|
|
1825
|
+
return {
|
|
1826
|
+
level: blocked ? "error" : "warning",
|
|
1827
|
+
file,
|
|
1828
|
+
title: `Ripple ${result.risk.level}`,
|
|
1829
|
+
message: `${result.risk.level} risk (score ${result.risk.score}) \u2014 ${result.summary.affectedFiles} affected ${result.summary.affectedFiles === 1 ? "file" : "files"}`
|
|
1830
|
+
};
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
function buildGateAnnotation(blocked, gate, counts) {
|
|
1834
|
+
const gateIndex = GATE_INDEX[gate];
|
|
1835
|
+
const parts = [];
|
|
1836
|
+
if (LEVEL_INDEX.CRITICAL >= gateIndex && counts.critical > 0)
|
|
1837
|
+
parts.push(`${counts.critical} CRITICAL`);
|
|
1838
|
+
if (LEVEL_INDEX.HIGH >= gateIndex && counts.high > 0) parts.push(`${counts.high} HIGH`);
|
|
1839
|
+
if (LEVEL_INDEX.MEDIUM >= gateIndex && counts.medium > 0) parts.push(`${counts.medium} MEDIUM`);
|
|
1840
|
+
const atGate = parts.length > 0 ? parts.join(" \xB7 ") : "none";
|
|
1841
|
+
return {
|
|
1842
|
+
level: "notice",
|
|
1843
|
+
title: `Ripple diff ${gate} gate`,
|
|
1844
|
+
message: blocked ? `Gate blocked: ${atGate} at or above ${gate}` : `Gate passed: nothing at ${gate} or above`
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// src/commands/diff.ts
|
|
1849
|
+
var LEVEL_INDEX2 = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
1850
|
+
var GATE_INDEX2 = { medium: 1, high: 2, critical: 3 };
|
|
1812
1851
|
var VERDICT_NOTE = {
|
|
1813
1852
|
medium: "no MEDIUM or worse",
|
|
1814
1853
|
high: "no HIGH or CRITICAL",
|
|
@@ -1822,18 +1861,19 @@ function countLevels(results) {
|
|
|
1822
1861
|
return counts;
|
|
1823
1862
|
}
|
|
1824
1863
|
function riskySummary(counts, gate) {
|
|
1825
|
-
const gateIndex =
|
|
1864
|
+
const gateIndex = GATE_INDEX2[gate];
|
|
1826
1865
|
const parts = [];
|
|
1827
|
-
if (
|
|
1866
|
+
if (LEVEL_INDEX2.CRITICAL >= gateIndex && counts.critical > 0)
|
|
1828
1867
|
parts.push(`${counts.critical} CRITICAL`);
|
|
1829
|
-
if (
|
|
1830
|
-
if (
|
|
1831
|
-
if (
|
|
1868
|
+
if (LEVEL_INDEX2.HIGH >= gateIndex && counts.high > 0) parts.push(`${counts.high} HIGH`);
|
|
1869
|
+
if (LEVEL_INDEX2.MEDIUM >= gateIndex && counts.medium > 0) parts.push(`${counts.medium} MEDIUM`);
|
|
1870
|
+
if (LEVEL_INDEX2.LOW >= gateIndex && counts.low > 0) parts.push(`${counts.low} LOW`);
|
|
1832
1871
|
return parts.length > 0 ? parts.join(" \xB7 ") : "none";
|
|
1833
1872
|
}
|
|
1834
1873
|
async function diffCommand(options, ctx2) {
|
|
1874
|
+
const format = options.format ?? (options.json ? "json" : "terminal");
|
|
1835
1875
|
const style = { color: resolveColor(options.color) };
|
|
1836
|
-
const tracker = createStageTracker(
|
|
1876
|
+
const tracker = createStageTracker(format === "terminal");
|
|
1837
1877
|
const started = Date.now();
|
|
1838
1878
|
const context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1839
1879
|
const gate = options.gate ?? context.config.diff.gate ?? "high";
|
|
@@ -1867,9 +1907,9 @@ async function diffCommand(options, ctx2) {
|
|
|
1867
1907
|
tracker.done();
|
|
1868
1908
|
const results = [...byRel.values()].sort((a, b) => b.risk.score - a.risk.score);
|
|
1869
1909
|
const counts = countLevels(results);
|
|
1870
|
-
const blocked = results.some((result) =>
|
|
1910
|
+
const blocked = results.some((result) => LEVEL_INDEX2[result.risk.level] >= GATE_INDEX2[gate]);
|
|
1871
1911
|
const durationMs = Date.now() - started;
|
|
1872
|
-
if (
|
|
1912
|
+
if (format === "json") {
|
|
1873
1913
|
ctx2.writer.write(
|
|
1874
1914
|
serializeJson(
|
|
1875
1915
|
buildDiffJsonReport(
|
|
@@ -1886,6 +1926,18 @@ async function diffCommand(options, ctx2) {
|
|
|
1886
1926
|
);
|
|
1887
1927
|
return blocked ? ExitCode.Failure : ExitCode.Success;
|
|
1888
1928
|
}
|
|
1929
|
+
if (format === "github") {
|
|
1930
|
+
const annotations = [
|
|
1931
|
+
...buildFileAnnotations(
|
|
1932
|
+
[...byRel.entries()].map(([file, result]) => ({ file, result })),
|
|
1933
|
+
gate
|
|
1934
|
+
),
|
|
1935
|
+
buildGateAnnotation(blocked, gate, counts)
|
|
1936
|
+
];
|
|
1937
|
+
ctx2.writer.write(`${annotations.map(renderAnnotation).join("\n")}
|
|
1938
|
+
`);
|
|
1939
|
+
return blocked ? ExitCode.Failure : ExitCode.Success;
|
|
1940
|
+
}
|
|
1889
1941
|
renderDiffReport(
|
|
1890
1942
|
{
|
|
1891
1943
|
cwd: ctx2.cwd,
|
|
@@ -1928,7 +1980,7 @@ function renderDiffReport(input, writer) {
|
|
|
1928
1980
|
writer.writeLine();
|
|
1929
1981
|
writer.writeLine(sectionHeader(`Risk analysis (${results.length} files)`, style));
|
|
1930
1982
|
for (const result of results) {
|
|
1931
|
-
const risky =
|
|
1983
|
+
const risky = LEVEL_INDEX2[result.risk.level] >= GATE_INDEX2[gate];
|
|
1932
1984
|
const fileIcon = risky ? icon("cross") : icon("tick");
|
|
1933
1985
|
const fileIconText = style.color ? risky ? chalk.red(fileIcon) : chalk.green(fileIcon) : fileIcon;
|
|
1934
1986
|
const score = result.risk.score.toFixed(1);
|
|
@@ -2011,7 +2063,7 @@ function createProgram(ctx2) {
|
|
|
2011
2063
|
throw new ExitError(exitCode2);
|
|
2012
2064
|
}
|
|
2013
2065
|
});
|
|
2014
|
-
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(
|
|
2066
|
+
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("-f, --format <format>", "output format: terminal | json | github", parseDiffFormat).option("-v, --verbose", "include extra sections").option("-b, --base <ref>", "git ref to diff against (default: origin/main, main, HEAD~1)").option(
|
|
2015
2067
|
"-g, --gate <level>",
|
|
2016
2068
|
"risk level that blocks the gate (medium | high | critical)",
|
|
2017
2069
|
parseGateLevel
|
|
@@ -2019,6 +2071,7 @@ function createProgram(ctx2) {
|
|
|
2019
2071
|
const exitCode2 = await diffCommand(
|
|
2020
2072
|
{
|
|
2021
2073
|
json: Boolean(options.json),
|
|
2074
|
+
format: options.format ?? (options.json ? "json" : "terminal"),
|
|
2022
2075
|
verbose: Boolean(options.verbose),
|
|
2023
2076
|
...options.base !== void 0 ? { base: options.base } : {},
|
|
2024
2077
|
...options.gate !== void 0 ? { gate: options.gate } : {},
|
|
@@ -2093,6 +2146,16 @@ function parseGateLevel(value) {
|
|
|
2093
2146
|
`expected one of medium | high | critical, got "${value}"`
|
|
2094
2147
|
);
|
|
2095
2148
|
}
|
|
2149
|
+
function parseDiffFormat(value) {
|
|
2150
|
+
if (value === "terminal" || value === "json" || value === "github") {
|
|
2151
|
+
return value;
|
|
2152
|
+
}
|
|
2153
|
+
throw new CommanderError(
|
|
2154
|
+
1,
|
|
2155
|
+
"commander.invalidArgument",
|
|
2156
|
+
`expected one of terminal | json | github, got "${value}"`
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2096
2159
|
function printError(error, ctx2) {
|
|
2097
2160
|
if (error instanceof RippleError) {
|
|
2098
2161
|
ctx2.errorWriter.writeLine(`\u2716 ${error.message}`);
|
|
@@ -2126,7 +2189,7 @@ async function run({ argv, ctx: ctx2 }) {
|
|
|
2126
2189
|
|
|
2127
2190
|
// package.json
|
|
2128
2191
|
var package_default = {
|
|
2129
|
-
version: "0.
|
|
2192
|
+
version: "0.5.0"};
|
|
2130
2193
|
|
|
2131
2194
|
// src/cli/bin.ts
|
|
2132
2195
|
var ctx = {
|