@rainy-updates/cli 0.5.2-rc.2 → 0.5.3
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/CHANGELOG.md +107 -0
- package/README.md +105 -22
- package/dist/bin/cli.js +124 -9
- package/dist/cache/cache.d.ts +1 -0
- package/dist/cache/cache.js +9 -2
- package/dist/commands/audit/runner.js +8 -1
- package/dist/commands/audit/sources/index.js +8 -1
- package/dist/commands/doctor/parser.d.ts +2 -0
- package/dist/commands/doctor/parser.js +92 -0
- package/dist/commands/doctor/runner.d.ts +2 -0
- package/dist/commands/doctor/runner.js +13 -0
- package/dist/commands/resolve/runner.js +3 -0
- package/dist/commands/review/parser.d.ts +2 -0
- package/dist/commands/review/parser.js +174 -0
- package/dist/commands/review/runner.d.ts +2 -0
- package/dist/commands/review/runner.js +30 -0
- package/dist/config/loader.d.ts +3 -0
- package/dist/core/check.js +64 -5
- package/dist/core/errors.d.ts +11 -0
- package/dist/core/errors.js +6 -0
- package/dist/core/options.d.ts +8 -1
- package/dist/core/options.js +43 -0
- package/dist/core/review-model.d.ts +5 -0
- package/dist/core/review-model.js +372 -0
- package/dist/core/summary.js +11 -2
- package/dist/core/upgrade.d.ts +1 -0
- package/dist/core/upgrade.js +27 -21
- package/dist/core/warm-cache.js +28 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/output/format.d.ts +4 -1
- package/dist/output/format.js +41 -3
- package/dist/output/github.js +6 -1
- package/dist/output/sarif.js +14 -0
- package/dist/registry/npm.d.ts +22 -0
- package/dist/registry/npm.js +33 -4
- package/dist/risk/index.d.ts +3 -0
- package/dist/risk/index.js +24 -0
- package/dist/risk/scorer.d.ts +3 -0
- package/dist/risk/scorer.js +114 -0
- package/dist/risk/signals/fresh-package.d.ts +3 -0
- package/dist/risk/signals/fresh-package.js +22 -0
- package/dist/risk/signals/install-scripts.d.ts +3 -0
- package/dist/risk/signals/install-scripts.js +10 -0
- package/dist/risk/signals/maintainer-churn.d.ts +3 -0
- package/dist/risk/signals/maintainer-churn.js +11 -0
- package/dist/risk/signals/metadata.d.ts +3 -0
- package/dist/risk/signals/metadata.js +18 -0
- package/dist/risk/signals/mutable-source.d.ts +3 -0
- package/dist/risk/signals/mutable-source.js +24 -0
- package/dist/risk/signals/typosquat.d.ts +3 -0
- package/dist/risk/signals/typosquat.js +70 -0
- package/dist/risk/types.d.ts +15 -0
- package/dist/risk/types.js +1 -0
- package/dist/types/index.d.ts +85 -0
- package/dist/ui/tui.d.ts +0 -4
- package/dist/ui/tui.js +103 -21
- package/package.json +10 -2
package/dist/ui/tui.js
CHANGED
|
@@ -1,44 +1,126 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import { render, Text,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import { Box, render, Text, useInput } from "ink";
|
|
4
|
+
const FILTER_ORDER = [
|
|
5
|
+
"all",
|
|
6
|
+
"security",
|
|
7
|
+
"risky",
|
|
8
|
+
"major",
|
|
9
|
+
];
|
|
10
|
+
function VersionDiff({ from, to }) {
|
|
11
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: from }), _jsxs(Text, { color: "gray", children: [" ", " -> ", " "] }), _jsx(Text, { color: "green", children: to })] }));
|
|
12
|
+
}
|
|
13
|
+
function riskColor(level) {
|
|
14
|
+
switch (level) {
|
|
15
|
+
case "critical":
|
|
16
|
+
return "red";
|
|
17
|
+
case "high":
|
|
18
|
+
return "yellow";
|
|
19
|
+
case "medium":
|
|
20
|
+
return "cyan";
|
|
21
|
+
default:
|
|
22
|
+
return "green";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function diffColor(level) {
|
|
26
|
+
switch (level) {
|
|
27
|
+
case "major":
|
|
28
|
+
return "red";
|
|
29
|
+
case "minor":
|
|
30
|
+
return "yellow";
|
|
31
|
+
case "patch":
|
|
32
|
+
return "green";
|
|
33
|
+
default:
|
|
34
|
+
return "cyan";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function decisionLabel(update) {
|
|
38
|
+
if (update.peerConflictSeverity === "error" || update.licenseStatus === "denied") {
|
|
39
|
+
return "blocked";
|
|
40
|
+
}
|
|
41
|
+
if (update.advisoryCount && update.advisoryCount > 0) {
|
|
42
|
+
return "actionable";
|
|
43
|
+
}
|
|
44
|
+
if (update.riskLevel === "critical" || update.riskLevel === "high") {
|
|
45
|
+
return "review";
|
|
46
|
+
}
|
|
47
|
+
return "safe";
|
|
48
|
+
}
|
|
49
|
+
function decisionColor(label) {
|
|
50
|
+
switch (label) {
|
|
51
|
+
case "blocked":
|
|
52
|
+
return "red";
|
|
53
|
+
case "actionable":
|
|
54
|
+
return "yellow";
|
|
55
|
+
case "review":
|
|
56
|
+
return "cyan";
|
|
57
|
+
default:
|
|
58
|
+
return "green";
|
|
59
|
+
}
|
|
11
60
|
}
|
|
12
61
|
function TuiApp({ updates, onComplete }) {
|
|
13
62
|
const [cursorIndex, setCursorIndex] = useState(0);
|
|
14
|
-
const [
|
|
63
|
+
const [filterIndex, setFilterIndex] = useState(0);
|
|
64
|
+
const [selectedIndices, setSelectedIndices] = useState(new Set(updates.map((_, index) => index)));
|
|
65
|
+
const activeFilter = FILTER_ORDER[filterIndex] ?? "all";
|
|
66
|
+
const filteredIndices = updates
|
|
67
|
+
.map((update, index) => ({ update, index }))
|
|
68
|
+
.filter(({ update }) => {
|
|
69
|
+
if (activeFilter === "security")
|
|
70
|
+
return (update.advisoryCount ?? 0) > 0;
|
|
71
|
+
if (activeFilter === "risky") {
|
|
72
|
+
return update.riskLevel === "critical" || update.riskLevel === "high";
|
|
73
|
+
}
|
|
74
|
+
if (activeFilter === "major")
|
|
75
|
+
return update.diffType === "major";
|
|
76
|
+
return true;
|
|
77
|
+
})
|
|
78
|
+
.map(({ index }) => index);
|
|
79
|
+
const boundedCursor = Math.min(cursorIndex, Math.max(0, filteredIndices.length - 1));
|
|
80
|
+
const focusedIndex = filteredIndices[boundedCursor] ?? 0;
|
|
81
|
+
const focusedUpdate = updates[focusedIndex];
|
|
15
82
|
useInput((input, key) => {
|
|
83
|
+
if (key.leftArrow) {
|
|
84
|
+
setFilterIndex((prev) => Math.max(0, prev - 1));
|
|
85
|
+
setCursorIndex(0);
|
|
86
|
+
}
|
|
87
|
+
if (key.rightArrow) {
|
|
88
|
+
setFilterIndex((prev) => Math.min(FILTER_ORDER.length - 1, prev + 1));
|
|
89
|
+
setCursorIndex(0);
|
|
90
|
+
}
|
|
16
91
|
if (key.upArrow) {
|
|
17
92
|
setCursorIndex((prev) => Math.max(0, prev - 1));
|
|
18
93
|
}
|
|
19
94
|
if (key.downArrow) {
|
|
20
|
-
setCursorIndex((prev) => Math.min(
|
|
95
|
+
setCursorIndex((prev) => Math.min(filteredIndices.length - 1, Math.max(0, prev + 1)));
|
|
96
|
+
}
|
|
97
|
+
if (input === "a") {
|
|
98
|
+
setSelectedIndices(new Set(filteredIndices));
|
|
99
|
+
}
|
|
100
|
+
if (input === "n") {
|
|
101
|
+
setSelectedIndices(new Set());
|
|
21
102
|
}
|
|
22
103
|
if (input === " ") {
|
|
23
104
|
setSelectedIndices((prev) => {
|
|
24
105
|
const next = new Set(prev);
|
|
25
|
-
if (next.has(
|
|
26
|
-
next.delete(
|
|
106
|
+
if (next.has(focusedIndex))
|
|
107
|
+
next.delete(focusedIndex);
|
|
27
108
|
else
|
|
28
|
-
next.add(
|
|
109
|
+
next.add(focusedIndex);
|
|
29
110
|
return next;
|
|
30
111
|
});
|
|
31
112
|
}
|
|
32
113
|
if (key.return) {
|
|
33
|
-
|
|
34
|
-
onComplete(selected);
|
|
114
|
+
onComplete(updates.filter((_, index) => selectedIndices.has(index)));
|
|
35
115
|
}
|
|
36
116
|
});
|
|
37
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
117
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Rainy Review Queue" }), _jsx(Text, { color: "gray", children: "Detect with check, summarize with doctor, decide here in review, then apply with upgrade." }), _jsx(Text, { color: "gray", children: "Left/Right filter Up/Down move Space toggle A select visible N clear Enter confirm" }), _jsx(Box, { marginTop: 1, children: FILTER_ORDER.map((filter, index) => (_jsx(Box, { marginRight: 2, children: _jsxs(Text, { color: index === filterIndex ? "cyan" : "gray", children: ["[", filter, "]"] }) }, filter))) }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsxs(Box, { width: 72, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Review Queue" }), filteredIndices.length === 0 ? (_jsx(Text, { color: "gray", children: "No review candidates match this filter." })) : (filteredIndices.map((index, visibleIndex) => {
|
|
118
|
+
const update = updates[index];
|
|
119
|
+
const isFocused = visibleIndex === boundedCursor;
|
|
120
|
+
const isSelected = selectedIndices.has(index);
|
|
121
|
+
const decision = decisionLabel(update);
|
|
122
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: isFocused ? "cyan" : "gray", children: [isFocused ? ">" : " ", " ", isSelected ? "[x]" : "[ ]", " "] }), _jsx(Box, { width: 22, children: _jsx(Text, { bold: isFocused, children: update.name }) }), _jsx(Box, { width: 10, children: _jsx(Text, { color: diffColor(update.diffType), children: update.diffType }) }), _jsx(Box, { width: 18, children: _jsx(Text, { color: riskColor(update.riskLevel), children: update.riskLevel ?? update.impactScore?.rank ?? "low" }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: decisionColor(decision), children: decision }) }), _jsx(Box, { width: 10, children: _jsx(Text, { color: decisionColor(decision), children: typeof update.riskScore === "number" ? update.riskScore : "--" }) }), _jsx(VersionDiff, { from: update.fromRange, to: update.toVersionResolved })] }, `${update.packagePath}:${update.name}`));
|
|
123
|
+
}))] }), _jsxs(Box, { marginLeft: 1, width: 46, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Decision Panel" }), focusedUpdate ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: focusedUpdate.name }), _jsxs(Text, { color: "gray", children: ["package: ", focusedUpdate.packagePath] }), _jsxs(Text, { children: ["state:", " ", _jsx(Text, { color: decisionColor(decisionLabel(focusedUpdate)), children: decisionLabel(focusedUpdate) })] }), _jsxs(Text, { children: ["diff: ", _jsx(Text, { color: diffColor(focusedUpdate.diffType), children: focusedUpdate.diffType })] }), _jsxs(Text, { children: ["risk: ", _jsx(Text, { color: riskColor(focusedUpdate.riskLevel), children: focusedUpdate.riskLevel ?? focusedUpdate.impactScore?.rank ?? "low" })] }), _jsxs(Text, { children: ["risk score: ", focusedUpdate.riskScore ?? 0] }), _jsxs(Text, { children: ["impact score: ", focusedUpdate.impactScore?.score ?? 0] }), _jsxs(Text, { children: ["advisories: ", focusedUpdate.advisoryCount ?? 0] }), _jsxs(Text, { children: ["peer: ", focusedUpdate.peerConflictSeverity ?? "none"] }), _jsxs(Text, { children: ["license: ", focusedUpdate.licenseStatus ?? "allowed"] }), _jsxs(Text, { children: ["health: ", focusedUpdate.healthStatus ?? "healthy"] }), _jsxs(Text, { children: ["action:", " ", _jsx(Text, { color: decisionColor(decisionLabel(focusedUpdate)), children: focusedUpdate.recommendedAction ?? "Safe to keep in the review queue." })] }), focusedUpdate.homepage ? (_jsxs(Text, { color: "blue", children: ["homepage: ", focusedUpdate.homepage] })) : (_jsx(Text, { color: "gray", children: "homepage: unavailable" })), focusedUpdate.riskReasons && focusedUpdate.riskReasons.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Reasons" }), focusedUpdate.riskReasons.slice(0, 4).map((reason) => (_jsxs(Text, { color: "gray", children: ["- ", reason] }, reason)))] })) : (_jsx(Text, { color: "gray", children: "No elevated risk reasons." }))] })) : (_jsx(Text, { color: "gray", children: "No review candidate selected." }))] })] }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsxs(Text, { color: "gray", children: [selectedIndices.size, " selected for apply of ", updates.length, ". Filter: ", activeFilter, ". Enter confirms the review decision set."] }) })] }));
|
|
42
124
|
}
|
|
43
125
|
export async function runTui(updates) {
|
|
44
126
|
return new Promise((resolve) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainy-updates/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "The fastest DevOps-first dependency CLI. Checks, audits, upgrades, bisects, and automates npm/pnpm dependencies in CI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -56,7 +56,15 @@
|
|
|
56
56
|
"test": "bun test",
|
|
57
57
|
"lint": "bunx biome check src tests",
|
|
58
58
|
"check": "bun run typecheck && bun test",
|
|
59
|
-
"
|
|
59
|
+
"bench:fixtures": "node scripts/generate-benchmark-fixtures.mjs",
|
|
60
|
+
"bench:check": "bun run build && bun run bench:fixtures && node scripts/benchmark.mjs single-100 check cold 3 && node scripts/benchmark.mjs single-100 check warm 3",
|
|
61
|
+
"bench:review": "bun run build && bun run bench:fixtures && node scripts/benchmark.mjs single-100 review cold 3 && node scripts/benchmark.mjs single-100 review warm 3",
|
|
62
|
+
"bench:resolve": "bun run build && bun run bench:fixtures && node scripts/benchmark.mjs single-100 resolve cold 3 && node scripts/benchmark.mjs single-100 resolve warm 3",
|
|
63
|
+
"bench:ci": "bun run build && bun run bench:fixtures && node scripts/benchmark.mjs mono-1000 ci cold 3 && node scripts/benchmark.mjs mono-1000 ci warm 3",
|
|
64
|
+
"perf:smoke": "node scripts/perf-smoke.mjs && RAINY_UPDATES_PERF_SCENARIO=resolve node scripts/perf-smoke.mjs && RAINY_UPDATES_PERF_SCENARIO=ci node scripts/perf-smoke.mjs",
|
|
65
|
+
"perf:check": "node scripts/perf-smoke.mjs",
|
|
66
|
+
"perf:resolve": "RAINY_UPDATES_PERF_SCENARIO=resolve node scripts/perf-smoke.mjs",
|
|
67
|
+
"perf:ci": "RAINY_UPDATES_PERF_SCENARIO=ci node scripts/perf-smoke.mjs",
|
|
60
68
|
"test:prod": "node dist/bin/cli.js --help && node dist/bin/cli.js --version",
|
|
61
69
|
"prepublishOnly": "bun run check && bun run build && bun run test:prod"
|
|
62
70
|
},
|