@alimaandev/ripple 0.3.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 +50 -23
- package/dist/bin.js +80 -16
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-OOERV74D.js → chunk-LDCFNORX.js} +12 -3
- package/dist/chunk-LDCFNORX.js.map +1 -0
- package/dist/index.d.ts +43 -27
- package/dist/index.js +1 -1
- package/package.json +5 -2
- package/dist/chunk-OOERV74D.js.map +0 -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
|
|
232
|
-
| `diff` | `-b, --base <ref>` | Git ref to diff against (default: `origin/main`, `main`, `HEAD~1`) |
|
|
233
|
-
| `diff` | `-g, --gate <level>` | Blocking level: `medium`, `high`, or `critical` (default: `high`) |
|
|
234
|
-
| `diff` | `-
|
|
235
|
-
| `diff` | `-
|
|
236
|
-
| `diff` |
|
|
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
|
+
| `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` | `-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
|
|
@@ -321,6 +339,10 @@ defaults — every field is optional.
|
|
|
321
339
|
"cycleMembership": 0.1
|
|
322
340
|
},
|
|
323
341
|
"thresholds": { "medium": 30, "high": 55, "critical": 80 }
|
|
342
|
+
},
|
|
343
|
+
"diff": {
|
|
344
|
+
"base": "origin/main",
|
|
345
|
+
"gate": "high"
|
|
324
346
|
}
|
|
325
347
|
}
|
|
326
348
|
```
|
|
@@ -333,9 +355,14 @@ defaults — every field is optional.
|
|
|
333
355
|
| `tsconfigPath` | `tsconfig.json` | tsconfig to read for `paths` |
|
|
334
356
|
| `risk.weights` | see example | Risk signal weights |
|
|
335
357
|
| `risk.thresholds` | 30 / 55 / 80 | Score thresholds for MEDIUM / HIGH / CRITICAL |
|
|
358
|
+
| `diff.base` | `origin/main` → `main` → `HEAD~1` | Git ref to diff against when `--base` is not given |
|
|
359
|
+
| `diff.gate` | `high` | Blocking level when `--gate` is not given |
|
|
336
360
|
|
|
337
361
|
`node_modules` is always excluded, regardless of config.
|
|
338
362
|
|
|
363
|
+
The `diff` block sets command defaults only — `--base`/`--gate` flags always
|
|
364
|
+
win over the config file, which wins over the built-in defaults.
|
|
365
|
+
|
|
339
366
|
## Risk scoring
|
|
340
367
|
|
|
341
368
|
The score is a weighted sum of seven signals, normalized to 0–1. Affected
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONFIG, rippleConfigSchema, formatSchemaIssues } from './chunk-
|
|
2
|
+
import { DEFAULT_CONFIG, rippleConfigSchema, formatSchemaIssues } from './chunk-LDCFNORX.js';
|
|
3
3
|
import { CommanderError, Command } from 'commander';
|
|
4
4
|
import { createJiti } from 'jiti';
|
|
5
5
|
import path8 from 'path';
|
|
@@ -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,24 +1861,26 @@ 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(
|
|
1837
|
-
const gate = options.gate ?? "high";
|
|
1876
|
+
const tracker = createStageTracker(format === "terminal");
|
|
1838
1877
|
const started = Date.now();
|
|
1878
|
+
const context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1879
|
+
const gate = options.gate ?? context.config.diff.gate ?? "high";
|
|
1880
|
+
const base = options.base ?? context.config.diff.base;
|
|
1839
1881
|
tracker.next("Resolving git changes");
|
|
1840
|
-
const changed = changedFiles(ctx2.cwd,
|
|
1882
|
+
const changed = changedFiles(ctx2.cwd, base);
|
|
1841
1883
|
tracker.next("Analyzing changed files");
|
|
1842
|
-
const context = await loadProjectContext(ctx2.cwd, options.config);
|
|
1843
1884
|
const { graph, entryPoints } = await runPipeline(context);
|
|
1844
1885
|
const byRel = /* @__PURE__ */ new Map();
|
|
1845
1886
|
const skipped = [];
|
|
@@ -1866,9 +1907,9 @@ async function diffCommand(options, ctx2) {
|
|
|
1866
1907
|
tracker.done();
|
|
1867
1908
|
const results = [...byRel.values()].sort((a, b) => b.risk.score - a.risk.score);
|
|
1868
1909
|
const counts = countLevels(results);
|
|
1869
|
-
const blocked = results.some((result) =>
|
|
1910
|
+
const blocked = results.some((result) => LEVEL_INDEX2[result.risk.level] >= GATE_INDEX2[gate]);
|
|
1870
1911
|
const durationMs = Date.now() - started;
|
|
1871
|
-
if (
|
|
1912
|
+
if (format === "json") {
|
|
1872
1913
|
ctx2.writer.write(
|
|
1873
1914
|
serializeJson(
|
|
1874
1915
|
buildDiffJsonReport(
|
|
@@ -1885,6 +1926,18 @@ async function diffCommand(options, ctx2) {
|
|
|
1885
1926
|
);
|
|
1886
1927
|
return blocked ? ExitCode.Failure : ExitCode.Success;
|
|
1887
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
|
+
}
|
|
1888
1941
|
renderDiffReport(
|
|
1889
1942
|
{
|
|
1890
1943
|
cwd: ctx2.cwd,
|
|
@@ -1927,7 +1980,7 @@ function renderDiffReport(input, writer) {
|
|
|
1927
1980
|
writer.writeLine();
|
|
1928
1981
|
writer.writeLine(sectionHeader(`Risk analysis (${results.length} files)`, style));
|
|
1929
1982
|
for (const result of results) {
|
|
1930
|
-
const risky =
|
|
1983
|
+
const risky = LEVEL_INDEX2[result.risk.level] >= GATE_INDEX2[gate];
|
|
1931
1984
|
const fileIcon = risky ? icon("cross") : icon("tick");
|
|
1932
1985
|
const fileIconText = style.color ? risky ? chalk.red(fileIcon) : chalk.green(fileIcon) : fileIcon;
|
|
1933
1986
|
const score = result.risk.score.toFixed(1);
|
|
@@ -2010,7 +2063,7 @@ function createProgram(ctx2) {
|
|
|
2010
2063
|
throw new ExitError(exitCode2);
|
|
2011
2064
|
}
|
|
2012
2065
|
});
|
|
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(
|
|
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(
|
|
2014
2067
|
"-g, --gate <level>",
|
|
2015
2068
|
"risk level that blocks the gate (medium | high | critical)",
|
|
2016
2069
|
parseGateLevel
|
|
@@ -2018,6 +2071,7 @@ function createProgram(ctx2) {
|
|
|
2018
2071
|
const exitCode2 = await diffCommand(
|
|
2019
2072
|
{
|
|
2020
2073
|
json: Boolean(options.json),
|
|
2074
|
+
format: options.format ?? (options.json ? "json" : "terminal"),
|
|
2021
2075
|
verbose: Boolean(options.verbose),
|
|
2022
2076
|
...options.base !== void 0 ? { base: options.base } : {},
|
|
2023
2077
|
...options.gate !== void 0 ? { gate: options.gate } : {},
|
|
@@ -2092,6 +2146,16 @@ function parseGateLevel(value) {
|
|
|
2092
2146
|
`expected one of medium | high | critical, got "${value}"`
|
|
2093
2147
|
);
|
|
2094
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
|
+
}
|
|
2095
2159
|
function printError(error, ctx2) {
|
|
2096
2160
|
if (error instanceof RippleError) {
|
|
2097
2161
|
ctx2.errorWriter.writeLine(`\u2716 ${error.message}`);
|
|
@@ -2125,7 +2189,7 @@ async function run({ argv, ctx: ctx2 }) {
|
|
|
2125
2189
|
|
|
2126
2190
|
// package.json
|
|
2127
2191
|
var package_default = {
|
|
2128
|
-
version: "0.
|
|
2192
|
+
version: "0.5.0"};
|
|
2129
2193
|
|
|
2130
2194
|
// src/cli/bin.ts
|
|
2131
2195
|
var ctx = {
|