@rainy-updates/cli 0.5.7 → 0.6.1
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 +134 -0
- package/README.md +90 -31
- package/dist/bin/cli.js +11 -126
- package/dist/bin/dispatch.js +35 -32
- package/dist/bin/help.js +79 -2
- package/dist/bin/main.d.ts +1 -0
- package/dist/bin/main.js +126 -0
- package/dist/cache/cache.js +13 -11
- package/dist/commands/audit/parser.js +38 -2
- package/dist/commands/audit/runner.js +41 -61
- package/dist/commands/audit/targets.js +13 -13
- package/dist/commands/bisect/oracle.js +31 -11
- package/dist/commands/bisect/parser.js +3 -3
- package/dist/commands/bisect/runner.js +16 -8
- package/dist/commands/changelog/fetcher.js +11 -5
- package/dist/commands/dashboard/parser.js +144 -1
- package/dist/commands/dashboard/runner.d.ts +2 -2
- package/dist/commands/dashboard/runner.js +67 -37
- package/dist/commands/doctor/parser.js +53 -4
- package/dist/commands/doctor/runner.js +2 -2
- package/dist/commands/ga/parser.js +43 -4
- package/dist/commands/ga/runner.js +22 -13
- package/dist/commands/health/parser.js +38 -2
- package/dist/commands/health/runner.js +5 -1
- package/dist/commands/hook/parser.d.ts +2 -0
- package/dist/commands/hook/parser.js +40 -0
- package/dist/commands/hook/runner.d.ts +2 -0
- package/dist/commands/hook/runner.js +174 -0
- package/dist/commands/licenses/parser.js +39 -0
- package/dist/commands/licenses/runner.js +9 -5
- package/dist/commands/resolve/graph/builder.js +5 -1
- package/dist/commands/resolve/parser.js +39 -0
- package/dist/commands/resolve/runner.js +14 -4
- package/dist/commands/review/parser.js +101 -4
- package/dist/commands/review/runner.js +31 -5
- package/dist/commands/snapshot/parser.js +39 -0
- package/dist/commands/snapshot/runner.js +21 -18
- package/dist/commands/snapshot/store.d.ts +0 -12
- package/dist/commands/snapshot/store.js +26 -38
- package/dist/commands/unused/parser.js +39 -0
- package/dist/commands/unused/runner.js +10 -8
- package/dist/commands/unused/scanner.d.ts +2 -1
- package/dist/commands/unused/scanner.js +65 -52
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.js +2 -5
- package/dist/config/policy.js +20 -11
- package/dist/core/analysis/run-silenced.js +0 -1
- package/dist/core/artifacts.js +6 -5
- package/dist/core/baseline.js +3 -5
- package/dist/core/check.js +7 -3
- package/dist/core/ci.js +52 -1
- package/dist/core/decision-plan.d.ts +14 -0
- package/dist/core/decision-plan.js +107 -0
- package/dist/core/doctor/result.js +8 -5
- package/dist/core/fix-pr-batch.js +38 -28
- package/dist/core/fix-pr.js +27 -24
- package/dist/core/init-ci.js +34 -28
- package/dist/core/options.d.ts +4 -1
- package/dist/core/options.js +152 -4
- package/dist/core/review-model.js +3 -0
- package/dist/core/summary.js +6 -0
- package/dist/core/upgrade.js +64 -2
- package/dist/core/verification.d.ts +2 -0
- package/dist/core/verification.js +108 -0
- package/dist/core/warm-cache.js +7 -3
- package/dist/generated/version.d.ts +1 -0
- package/dist/generated/version.js +2 -0
- package/dist/git/scope.d.ts +19 -0
- package/dist/git/scope.js +167 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/output/format.js +15 -0
- package/dist/output/github.js +6 -0
- package/dist/output/sarif.js +12 -18
- package/dist/parsers/package-json.js +2 -4
- package/dist/pm/detect.d.ts +40 -1
- package/dist/pm/detect.js +152 -9
- package/dist/pm/install.d.ts +3 -1
- package/dist/pm/install.js +18 -17
- package/dist/registry/npm.js +34 -76
- package/dist/rup +0 -0
- package/dist/types/index.d.ts +134 -5
- package/dist/ui/tui.d.ts +4 -1
- package/dist/ui/tui.js +156 -67
- package/dist/utils/io.js +5 -6
- package/dist/utils/lockfile.js +24 -19
- package/dist/utils/runtime-paths.d.ts +4 -0
- package/dist/utils/runtime-paths.js +35 -0
- package/dist/utils/runtime.d.ts +7 -0
- package/dist/utils/runtime.js +32 -0
- package/dist/workspace/discover.d.ts +7 -1
- package/dist/workspace/discover.js +67 -54
- package/package.json +24 -19
- package/dist/ui/dashboard/DashboardTUI.d.ts +0 -6
- package/dist/ui/dashboard/DashboardTUI.js +0 -34
- package/dist/ui/dashboard/components/DetailPanel.d.ts +0 -4
- package/dist/ui/dashboard/components/DetailPanel.js +0 -30
- package/dist/ui/dashboard/components/Footer.d.ts +0 -4
- package/dist/ui/dashboard/components/Footer.js +0 -9
- package/dist/ui/dashboard/components/Header.d.ts +0 -4
- package/dist/ui/dashboard/components/Header.js +0 -12
- package/dist/ui/dashboard/components/Sidebar.d.ts +0 -4
- package/dist/ui/dashboard/components/Sidebar.js +0 -23
- package/dist/ui/dashboard/store.d.ts +0 -34
- package/dist/ui/dashboard/store.js +0 -148
package/dist/ui/tui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import React from "react";
|
|
3
3
|
import { Box, render, Text, useInput } from "ink";
|
|
4
4
|
const FILTER_ORDER = [
|
|
5
5
|
"all",
|
|
@@ -22,20 +22,86 @@ const DETAIL_TABS = [
|
|
|
22
22
|
"health",
|
|
23
23
|
"changelog",
|
|
24
24
|
];
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
function tuiReducer(state, action) {
|
|
26
|
+
switch (action.type) {
|
|
27
|
+
case "SET_SEARCH_MODE":
|
|
28
|
+
return {
|
|
29
|
+
...state,
|
|
30
|
+
searchMode: action.active,
|
|
31
|
+
...(action.active ? {} : { search: "", cursorIndex: 0 }),
|
|
32
|
+
};
|
|
33
|
+
case "SET_SEARCH":
|
|
34
|
+
return { ...state, search: action.value, cursorIndex: 0 };
|
|
35
|
+
case "APPEND_SEARCH":
|
|
36
|
+
return { ...state, search: state.search + action.value, cursorIndex: 0 };
|
|
37
|
+
case "BACKSPACE_SEARCH":
|
|
38
|
+
return { ...state, search: state.search.slice(0, -1), cursorIndex: 0 };
|
|
39
|
+
case "TOGGLE_HELP":
|
|
40
|
+
return { ...state, showHelp: !state.showHelp };
|
|
41
|
+
case "SET_HELP":
|
|
42
|
+
return { ...state, showHelp: action.active };
|
|
43
|
+
case "MOVE_FILTER":
|
|
44
|
+
return {
|
|
45
|
+
...state,
|
|
46
|
+
filterIndex: Math.min(action.max, Math.max(0, state.filterIndex + action.direction)),
|
|
47
|
+
cursorIndex: 0,
|
|
48
|
+
};
|
|
49
|
+
case "MOVE_CURSOR":
|
|
50
|
+
return {
|
|
51
|
+
...state,
|
|
52
|
+
cursorIndex: Math.min(action.max, Math.max(0, state.cursorIndex + action.direction)),
|
|
53
|
+
};
|
|
54
|
+
case "RESET_CURSOR":
|
|
55
|
+
return { ...state, cursorIndex: 0 };
|
|
56
|
+
case "CYCLE_SORT":
|
|
57
|
+
return {
|
|
58
|
+
...state,
|
|
59
|
+
sortIndex: (state.sortIndex + 1) % action.max,
|
|
60
|
+
cursorIndex: 0,
|
|
61
|
+
};
|
|
62
|
+
case "CYCLE_GROUP":
|
|
63
|
+
return {
|
|
64
|
+
...state,
|
|
65
|
+
groupIndex: (state.groupIndex + 1) % action.max,
|
|
66
|
+
cursorIndex: 0,
|
|
67
|
+
};
|
|
68
|
+
case "CYCLE_TAB":
|
|
69
|
+
return { ...state, tabIndex: (state.tabIndex + 1) % action.max };
|
|
70
|
+
case "SET_SELECTED":
|
|
71
|
+
return { ...state, selectedIndices: action.indices };
|
|
72
|
+
case "TOGGLE_SELECTED": {
|
|
73
|
+
const next = new Set(state.selectedIndices);
|
|
74
|
+
if (next.has(action.index))
|
|
75
|
+
next.delete(action.index);
|
|
76
|
+
else
|
|
77
|
+
next.add(action.index);
|
|
78
|
+
return { ...state, selectedIndices: next };
|
|
79
|
+
}
|
|
80
|
+
default:
|
|
81
|
+
return state;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function TuiApp({ items, title, subtitle, onComplete }) {
|
|
85
|
+
const [state, dispatch] = React.useReducer(tuiReducer, undefined, () => ({
|
|
86
|
+
cursorIndex: 0,
|
|
87
|
+
filterIndex: 0,
|
|
88
|
+
sortIndex: 0,
|
|
89
|
+
groupIndex: 0,
|
|
90
|
+
tabIndex: 0,
|
|
91
|
+
showHelp: false,
|
|
92
|
+
searchMode: false,
|
|
93
|
+
search: "",
|
|
94
|
+
selectedIndices: new Set(items.flatMap((item, index) => item.update.selectedByDefault === false ? [] : [index])),
|
|
95
|
+
}));
|
|
96
|
+
const activeFilter = FILTER_ORDER[state.filterIndex] ?? "all";
|
|
97
|
+
const activeSort = SORT_ORDER[state.sortIndex] ?? "risk";
|
|
98
|
+
const activeGroup = GROUP_ORDER[state.groupIndex] ?? "none";
|
|
99
|
+
const activeTab = DETAIL_TABS[state.tabIndex] ?? "overview";
|
|
100
|
+
const searchMode = state.searchMode;
|
|
101
|
+
const search = state.search;
|
|
102
|
+
const showHelp = state.showHelp;
|
|
103
|
+
const selectedIndices = state.selectedIndices;
|
|
104
|
+
const filterIndex = state.filterIndex;
|
|
39
105
|
const visibleRows = buildVisibleRows(items, {
|
|
40
106
|
filter: activeFilter,
|
|
41
107
|
sort: activeSort,
|
|
@@ -43,91 +109,100 @@ function TuiApp({ items, onComplete }) {
|
|
|
43
109
|
search,
|
|
44
110
|
});
|
|
45
111
|
const itemRows = visibleRows.filter((row) => row.kind === "item" && typeof row.index === "number");
|
|
46
|
-
const boundedCursor = Math.min(cursorIndex, Math.max(0, itemRows.length - 1));
|
|
112
|
+
const boundedCursor = Math.min(state.cursorIndex, Math.max(0, itemRows.length - 1));
|
|
47
113
|
const focusedIndex = itemRows[boundedCursor]?.index ?? 0;
|
|
48
114
|
const focusedItem = items[focusedIndex];
|
|
49
115
|
useInput((input, key) => {
|
|
50
116
|
if (searchMode) {
|
|
51
117
|
if (key.escape) {
|
|
52
|
-
|
|
53
|
-
setSearch("");
|
|
54
|
-
setCursorIndex(0);
|
|
118
|
+
dispatch({ type: "SET_SEARCH_MODE", active: false });
|
|
55
119
|
return;
|
|
56
120
|
}
|
|
57
121
|
if (key.return) {
|
|
58
|
-
|
|
59
|
-
|
|
122
|
+
dispatch({ type: "SET_SEARCH_MODE", active: false });
|
|
123
|
+
dispatch({ type: "SET_SEARCH", value: search }); // keeps search but exits mode
|
|
60
124
|
return;
|
|
61
125
|
}
|
|
62
126
|
if (key.backspace || key.delete) {
|
|
63
|
-
|
|
64
|
-
setCursorIndex(0);
|
|
127
|
+
dispatch({ type: "BACKSPACE_SEARCH" });
|
|
65
128
|
return;
|
|
66
129
|
}
|
|
67
130
|
if (input && !key.ctrl && !key.meta) {
|
|
68
|
-
|
|
69
|
-
setCursorIndex(0);
|
|
131
|
+
dispatch({ type: "APPEND_SEARCH", value: input });
|
|
70
132
|
}
|
|
71
133
|
return;
|
|
72
134
|
}
|
|
73
135
|
if (input === "/") {
|
|
74
|
-
|
|
136
|
+
dispatch({ type: "SET_SEARCH_MODE", active: true });
|
|
75
137
|
return;
|
|
76
138
|
}
|
|
77
139
|
if (input === "?") {
|
|
78
|
-
|
|
140
|
+
dispatch({ type: "TOGGLE_HELP" });
|
|
79
141
|
return;
|
|
80
142
|
}
|
|
81
143
|
if (key.escape && showHelp) {
|
|
82
|
-
|
|
144
|
+
dispatch({ type: "SET_HELP", active: false });
|
|
83
145
|
return;
|
|
84
146
|
}
|
|
85
147
|
if (key.leftArrow) {
|
|
86
|
-
|
|
87
|
-
|
|
148
|
+
dispatch({
|
|
149
|
+
type: "MOVE_FILTER",
|
|
150
|
+
direction: -1,
|
|
151
|
+
max: FILTER_ORDER.length - 1,
|
|
152
|
+
});
|
|
88
153
|
}
|
|
89
154
|
if (key.rightArrow) {
|
|
90
|
-
|
|
91
|
-
|
|
155
|
+
dispatch({
|
|
156
|
+
type: "MOVE_FILTER",
|
|
157
|
+
direction: 1,
|
|
158
|
+
max: FILTER_ORDER.length - 1,
|
|
159
|
+
});
|
|
92
160
|
}
|
|
93
161
|
if (key.upArrow) {
|
|
94
|
-
|
|
162
|
+
dispatch({
|
|
163
|
+
type: "MOVE_CURSOR",
|
|
164
|
+
direction: -1,
|
|
165
|
+
max: itemRows.length - 1,
|
|
166
|
+
});
|
|
95
167
|
}
|
|
96
168
|
if (key.downArrow) {
|
|
97
|
-
|
|
169
|
+
dispatch({ type: "MOVE_CURSOR", direction: 1, max: itemRows.length - 1 });
|
|
98
170
|
}
|
|
99
171
|
if (input === "o") {
|
|
100
|
-
|
|
101
|
-
setCursorIndex(0);
|
|
172
|
+
dispatch({ type: "CYCLE_SORT", max: SORT_ORDER.length });
|
|
102
173
|
}
|
|
103
174
|
if (input === "g") {
|
|
104
|
-
|
|
105
|
-
setCursorIndex(0);
|
|
175
|
+
dispatch({ type: "CYCLE_GROUP", max: GROUP_ORDER.length });
|
|
106
176
|
}
|
|
107
177
|
if (key.tab) {
|
|
108
|
-
|
|
178
|
+
dispatch({ type: "CYCLE_TAB", max: DETAIL_TABS.length });
|
|
109
179
|
}
|
|
110
180
|
if (input === "a") {
|
|
111
|
-
|
|
181
|
+
dispatch({
|
|
182
|
+
type: "SET_SELECTED",
|
|
183
|
+
indices: addVisible(selectedIndices, itemRows),
|
|
184
|
+
});
|
|
112
185
|
}
|
|
113
186
|
if (input === "n") {
|
|
114
|
-
|
|
187
|
+
dispatch({
|
|
188
|
+
type: "SET_SELECTED",
|
|
189
|
+
indices: removeVisible(selectedIndices, itemRows),
|
|
190
|
+
});
|
|
115
191
|
}
|
|
116
192
|
if (input === "s") {
|
|
117
|
-
|
|
193
|
+
dispatch({
|
|
194
|
+
type: "SET_SELECTED",
|
|
195
|
+
indices: selectSafe(selectedIndices, itemRows, items),
|
|
196
|
+
});
|
|
118
197
|
}
|
|
119
198
|
if (input === "b") {
|
|
120
|
-
|
|
199
|
+
dispatch({
|
|
200
|
+
type: "SET_SELECTED",
|
|
201
|
+
indices: clearBlocked(selectedIndices, itemRows, items),
|
|
202
|
+
});
|
|
121
203
|
}
|
|
122
204
|
if (input === " ") {
|
|
123
|
-
|
|
124
|
-
const next = new Set(prev);
|
|
125
|
-
if (next.has(focusedIndex))
|
|
126
|
-
next.delete(focusedIndex);
|
|
127
|
-
else
|
|
128
|
-
next.add(focusedIndex);
|
|
129
|
-
return next;
|
|
130
|
-
});
|
|
205
|
+
dispatch({ type: "TOGGLE_SELECTED", index: focusedIndex });
|
|
131
206
|
}
|
|
132
207
|
if (input === "q" || key.escape) {
|
|
133
208
|
onComplete(items.filter((_, index) => selectedIndices.has(index)));
|
|
@@ -137,7 +212,8 @@ function TuiApp({ items, onComplete }) {
|
|
|
137
212
|
onComplete(items.filter((_, index) => selectedIndices.has(index)));
|
|
138
213
|
}
|
|
139
214
|
});
|
|
140
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children:
|
|
215
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: title ?? "Rainy Dashboard" }), _jsx(Text, { color: "gray", children: subtitle ??
|
|
216
|
+
"Check detects, doctor summarizes, dashboard decides, upgrade applies." }), _jsx(Text, { color: "gray", children: "Filters: \u2190/\u2192 Sort: o Group: g Tabs: Tab Search: / Help: ? Space: toggle Enter: confirm" }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsxs(Box, { width: 24, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Filter Rail" }), FILTER_ORDER.map((filter, index) => (_jsxs(Text, { color: index === filterIndex ? "cyan" : "gray", children: [index === filterIndex ? ">" : " ", " ", filter] }, filter))), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Search" }), _jsx(Text, { color: searchMode ? "cyan" : "gray", children: searchMode ? `/${search}` : search ? `/${search}` : "inactive" })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Modes" }), _jsxs(Text, { color: "gray", children: ["sort: ", activeSort] }), _jsxs(Text, { color: "gray", children: ["group: ", activeGroup] }), _jsxs(Text, { color: "gray", children: ["tab: ", activeTab] })] })] }), _jsxs(Box, { marginLeft: 1, width: 82, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Review Queue" }), itemRows.length === 0 ? (_jsx(Text, { color: "gray", children: "No review candidates match this view." })) : (visibleRows.map((row, visibleIndex) => {
|
|
141
217
|
if (row.kind === "group") {
|
|
142
218
|
return (_jsx(Text, { bold: true, color: "gray", children: row.label }, `group:${row.label}`));
|
|
143
219
|
}
|
|
@@ -149,18 +225,18 @@ function TuiApp({ items, onComplete }) {
|
|
|
149
225
|
const isFocused = itemPosition === boundedCursor;
|
|
150
226
|
const isSelected = selectedIndices.has(index);
|
|
151
227
|
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: 14, children: _jsx(Text, { color: diffColor(update.diffType), children: update.diffType }) }), _jsx(Box, { width: 14, children: _jsx(Text, { color: riskColor(update.riskLevel), children: update.riskLevel ?? "low" }) }), _jsx(Box, { width: 14, children: _jsx(Text, { color: decisionColor(decision), children: decision }) }), _jsx(Box, { width: 10, children: _jsx(Text, { color: decisionColor(decision), children: update.riskScore ?? "--" }) }), _jsx(Text, { color: "gray", children: update.fromRange }), _jsx(Text, { color: "gray", children: " \u2192 " }), _jsx(Text, { color: "green", children: update.toVersionResolved })] }, `${update.packagePath}:${update.name}`));
|
|
152
|
-
}))] }), _jsxs(Box, { marginLeft: 1, width: 54, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Decision Panel" }), _jsxs(Text, { color: "gray", children: ["tab: ", activeTab] }), focusedItem ? renderTab(focusedItem, activeTab) : _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 of ", items.length, ". view=", activeFilter, " sort=", activeSort, " group=", activeGroup] }) }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", children: "A select visible
|
|
228
|
+
}))] }), _jsxs(Box, { marginLeft: 1, width: 54, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Decision Panel" }), _jsxs(Text, { color: "gray", children: ["tab: ", activeTab] }), focusedItem ? (renderTab(focusedItem, activeTab)) : (_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 of ", items.length, ". view=", activeFilter, " ", "sort=", activeSort, " group=", activeGroup] }) }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", children: "A select visible N clear visible S select safe B clear blocked Q finish Esc clears search/help" }) }), showHelp ? (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Help" }), _jsx(Text, { color: "gray", children: "Use review as the decision center. Search packages with / and inspect details with Tab." }), _jsx(Text, { color: "gray", children: "Blocked items default to deselected. Safe items can be bulk-selected with S." })] })) : null] }));
|
|
153
229
|
}
|
|
154
230
|
function renderTab(item, tab) {
|
|
155
231
|
const update = item.update;
|
|
156
232
|
if (tab === "risk") {
|
|
157
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["state: ", _jsx(Text, { color: decisionColor(update.decisionState ?? deriveDecision(item)), children: update.decisionState ?? deriveDecision(item) })] }), _jsxs(Text, { children: ["policy: ", _jsx(Text, { color: decisionColor(policyToDecision(update.policyAction)), children: update.policyAction ?? "allow" })] }), _jsxs(Text, { children: ["risk score: ", update.riskScore ?? 0] }), _jsxs(Text, { children: ["impact score: ", update.impactScore?.score ?? 0] }), _jsxs(Text, { children: ["recommended action: ", update.recommendedAction ?? "Safe to keep in the selected set."] }), update.riskReasons && update.riskReasons.length > 0 ? update.riskReasons.slice(0, 5).map((reason) => (_jsxs(Text, { color: "gray", children: ["- ", reason] }, reason))) : _jsx(Text, { color: "gray", children: "No elevated risk reasons." })] }));
|
|
233
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["state:", " ", _jsx(Text, { color: decisionColor(update.decisionState ?? deriveDecision(item)), children: update.decisionState ?? deriveDecision(item) })] }), _jsxs(Text, { children: ["policy:", " ", _jsx(Text, { color: decisionColor(policyToDecision(update.policyAction)), children: update.policyAction ?? "allow" })] }), _jsxs(Text, { children: ["risk score: ", update.riskScore ?? 0] }), _jsxs(Text, { children: ["impact score: ", update.impactScore?.score ?? 0] }), _jsxs(Text, { children: ["recommended action:", " ", update.recommendedAction ?? "Safe to keep in the selected set."] }), update.riskReasons && update.riskReasons.length > 0 ? (update.riskReasons.slice(0, 5).map((reason) => (_jsxs(Text, { color: "gray", children: ["- ", reason] }, reason)))) : (_jsx(Text, { color: "gray", children: "No elevated risk reasons." }))] }));
|
|
158
234
|
}
|
|
159
235
|
if (tab === "security") {
|
|
160
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["advisories: ", item.advisories.length] }), item.advisories.length > 0 ? item.advisories.slice(0, 4).map((advisory) => (_jsxs(Text, { color: "gray", children: ["- ", advisory.severity, " ", advisory.cveId, ": ", advisory.title] }, `${advisory.packageName}:${advisory.cveId}`))) : _jsx(Text, { color: "gray", children: "No security advisories detected." })] }));
|
|
236
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["advisories: ", item.advisories.length] }), item.advisories.length > 0 ? (item.advisories.slice(0, 4).map((advisory) => (_jsxs(Text, { color: "gray", children: ["- ", advisory.severity, " ", advisory.cveId, ": ", advisory.title] }, `${advisory.packageName}:${advisory.cveId}`)))) : (_jsx(Text, { color: "gray", children: "No security advisories detected." }))] }));
|
|
161
237
|
}
|
|
162
238
|
if (tab === "peer") {
|
|
163
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["peer status: ", update.peerConflictSeverity ?? "none"] }), item.peerConflicts.length > 0 ? item.peerConflicts.slice(0, 4).map((conflict) => (_jsxs(Text, { color: "gray", children: ["- ", conflict.requester, " requires ", conflict.peer, " ", conflict.requiredRange] }, `${conflict.requester}:${conflict.peer}`))) : _jsx(Text, { color: "gray", children: "No peer conflicts detected." })] }));
|
|
239
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["peer status: ", update.peerConflictSeverity ?? "none"] }), item.peerConflicts.length > 0 ? (item.peerConflicts.slice(0, 4).map((conflict) => (_jsxs(Text, { color: "gray", children: ["- ", conflict.requester, " requires ", conflict.peer, " ", conflict.requiredRange] }, `${conflict.requester}:${conflict.peer}`)))) : (_jsx(Text, { color: "gray", children: "No peer conflicts detected." }))] }));
|
|
164
240
|
}
|
|
165
241
|
if (tab === "license") {
|
|
166
242
|
return (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["license status: ", update.licenseStatus ?? "allowed"] }), _jsxs(Text, { children: ["repository: ", update.repository ?? "unavailable"] }), _jsxs(Text, { children: ["homepage: ", update.homepage ?? "unavailable"] })] }));
|
|
@@ -169,9 +245,10 @@ function renderTab(item, tab) {
|
|
|
169
245
|
return (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["health: ", update.healthStatus ?? "healthy"] }), _jsxs(Text, { children: ["maintainers: ", update.maintainerCount ?? "unknown"] }), _jsxs(Text, { children: ["publish age days: ", update.publishAgeDays ?? "unknown"] }), _jsxs(Text, { children: ["maintainer churn: ", update.maintainerChurn ?? "unknown"] })] }));
|
|
170
246
|
}
|
|
171
247
|
if (tab === "changelog") {
|
|
172
|
-
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: update.releaseNotesSummary?.title ?? "Release notes unavailable" }), _jsx(Text, { color: "gray", children: update.releaseNotesSummary?.excerpt ??
|
|
248
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: update.releaseNotesSummary?.title ?? "Release notes unavailable" }), _jsx(Text, { color: "gray", children: update.releaseNotesSummary?.excerpt ??
|
|
249
|
+
"Run review with changelog support or inspect the repository manually." })] }));
|
|
173
250
|
}
|
|
174
|
-
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: update.name }), _jsxs(Text, { color: "gray", children: ["package: ", update.packagePath] }), _jsxs(Text, { children: ["state: ", _jsx(Text, { color: decisionColor(update.decisionState ?? deriveDecision(item)), children: update.decisionState ?? deriveDecision(item) })] }), _jsxs(Text, { children: ["diff: ", _jsx(Text, { color: diffColor(update.diffType), children: update.diffType })] }), _jsxs(Text, { children: ["risk: ", _jsx(Text, { color: riskColor(update.riskLevel), children: update.riskLevel ?? "low" })] }), _jsxs(Text, { children: ["policy: ", update.policyAction ?? "allow"] }), _jsxs(Text, { children: ["workspace: ", update.workspaceGroup ?? "root"] }), _jsxs(Text, { children: ["group: ", update.groupKey ?? "none"] }), _jsxs(Text, { children: ["action: ", update.recommendedAction ?? "Safe to keep in the selected set."] })] }));
|
|
251
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: update.name }), _jsxs(Text, { color: "gray", children: ["package: ", update.packagePath] }), _jsxs(Text, { children: ["state:", " ", _jsx(Text, { color: decisionColor(update.decisionState ?? deriveDecision(item)), children: update.decisionState ?? deriveDecision(item) })] }), _jsxs(Text, { children: ["diff: ", _jsx(Text, { color: diffColor(update.diffType), children: update.diffType })] }), _jsxs(Text, { children: ["risk:", " ", _jsx(Text, { color: riskColor(update.riskLevel), children: update.riskLevel ?? "low" })] }), _jsxs(Text, { children: ["policy: ", update.policyAction ?? "allow"] }), _jsxs(Text, { children: ["workspace: ", update.workspaceGroup ?? "root"] }), _jsxs(Text, { children: ["group: ", update.groupKey ?? "none"] }), _jsxs(Text, { children: ["action:", " ", update.recommendedAction ?? "Safe to keep in the selected set."] })] }));
|
|
175
252
|
}
|
|
176
253
|
function buildVisibleRows(items, config) {
|
|
177
254
|
const filtered = items
|
|
@@ -180,7 +257,11 @@ function buildVisibleRows(items, config) {
|
|
|
180
257
|
.filter(({ item }) => matchesSearch(item, config.search))
|
|
181
258
|
.sort((left, right) => compareItems(left.item, right.item, config.sort));
|
|
182
259
|
if (config.group === "none") {
|
|
183
|
-
return filtered.map(({ item, index }) => ({
|
|
260
|
+
return filtered.map(({ item, index }) => ({
|
|
261
|
+
kind: "item",
|
|
262
|
+
label: item.update.name,
|
|
263
|
+
index,
|
|
264
|
+
}));
|
|
184
265
|
}
|
|
185
266
|
const rows = [];
|
|
186
267
|
let currentGroup = "";
|
|
@@ -190,7 +271,11 @@ function buildVisibleRows(items, config) {
|
|
|
190
271
|
currentGroup = nextGroup;
|
|
191
272
|
rows.push({ kind: "group", label: nextGroup });
|
|
192
273
|
}
|
|
193
|
-
rows.push({
|
|
274
|
+
rows.push({
|
|
275
|
+
kind: "item",
|
|
276
|
+
label: entry.item.update.name,
|
|
277
|
+
index: entry.index,
|
|
278
|
+
});
|
|
194
279
|
}
|
|
195
280
|
return rows;
|
|
196
281
|
}
|
|
@@ -198,7 +283,7 @@ function matchesFilter(item, filter) {
|
|
|
198
283
|
if (filter === "security")
|
|
199
284
|
return item.advisories.length > 0;
|
|
200
285
|
if (filter === "risky")
|
|
201
|
-
return item.update.riskLevel === "critical" || item.update.riskLevel === "high";
|
|
286
|
+
return (item.update.riskLevel === "critical" || item.update.riskLevel === "high");
|
|
202
287
|
if (filter === "major")
|
|
203
288
|
return item.update.diffType === "major";
|
|
204
289
|
if (filter === "peer-conflict")
|
|
@@ -274,7 +359,8 @@ function removeVisible(selected, rows) {
|
|
|
274
359
|
function selectSafe(selected, rows, items) {
|
|
275
360
|
const next = new Set(selected);
|
|
276
361
|
for (const row of rows) {
|
|
277
|
-
if ((items[row.index]?.update.decisionState ??
|
|
362
|
+
if ((items[row.index]?.update.decisionState ??
|
|
363
|
+
deriveDecision(items[row.index])) === "safe") {
|
|
278
364
|
next.add(row.index);
|
|
279
365
|
}
|
|
280
366
|
}
|
|
@@ -283,17 +369,20 @@ function selectSafe(selected, rows, items) {
|
|
|
283
369
|
function clearBlocked(selected, rows, items) {
|
|
284
370
|
const next = new Set(selected);
|
|
285
371
|
for (const row of rows) {
|
|
286
|
-
if ((items[row.index]?.update.decisionState ??
|
|
372
|
+
if ((items[row.index]?.update.decisionState ??
|
|
373
|
+
deriveDecision(items[row.index])) === "blocked") {
|
|
287
374
|
next.delete(row.index);
|
|
288
375
|
}
|
|
289
376
|
}
|
|
290
377
|
return next;
|
|
291
378
|
}
|
|
292
379
|
function deriveDecision(item) {
|
|
293
|
-
if (item.update.peerConflictSeverity === "error" ||
|
|
380
|
+
if (item.update.peerConflictSeverity === "error" ||
|
|
381
|
+
item.update.licenseStatus === "denied") {
|
|
294
382
|
return "blocked";
|
|
295
383
|
}
|
|
296
|
-
if ((item.update.advisoryCount ?? 0) > 0 ||
|
|
384
|
+
if ((item.update.advisoryCount ?? 0) > 0 ||
|
|
385
|
+
item.update.riskLevel === "critical") {
|
|
297
386
|
return "actionable";
|
|
298
387
|
}
|
|
299
388
|
if (item.update.riskLevel === "high" || item.update.diffType === "major") {
|
|
@@ -353,9 +442,9 @@ function decisionColor(label) {
|
|
|
353
442
|
return "green";
|
|
354
443
|
}
|
|
355
444
|
}
|
|
356
|
-
export async function runTui(items) {
|
|
445
|
+
export async function runTui(items, options) {
|
|
357
446
|
return new Promise((resolve) => {
|
|
358
|
-
const { unmount } = render(_jsx(TuiApp, { items: items, onComplete: (selected) => {
|
|
447
|
+
const { unmount } = render(_jsx(TuiApp, { items: items, title: options?.title, subtitle: options?.subtitle, onComplete: (selected) => {
|
|
359
448
|
unmount();
|
|
360
449
|
resolve(selected);
|
|
361
450
|
} }));
|
package/dist/utils/io.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, rename } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
3
|
export async function writeFileAtomic(filePath, content) {
|
|
5
4
|
const dir = path.dirname(filePath);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
await
|
|
9
|
-
await
|
|
5
|
+
const tempFile = path.join(dir, `.tmp-${path.basename(filePath)}-${crypto.randomUUID()}`);
|
|
6
|
+
await mkdir(dir, { recursive: true });
|
|
7
|
+
await Bun.write(tempFile, content);
|
|
8
|
+
await rename(tempFile, filePath);
|
|
10
9
|
}
|
package/dist/utils/lockfile.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const LOCKFILE_NAMES = [
|
|
3
|
+
"package-lock.json",
|
|
4
|
+
"npm-shrinkwrap.json",
|
|
5
|
+
"pnpm-lock.yaml",
|
|
6
|
+
"yarn.lock",
|
|
7
|
+
"bun.lock",
|
|
8
|
+
];
|
|
5
9
|
export async function captureLockfileSnapshot(cwd) {
|
|
6
10
|
const snapshot = new Map();
|
|
7
11
|
for (const name of LOCKFILE_NAMES) {
|
|
8
12
|
const filePath = path.join(cwd, name);
|
|
9
|
-
|
|
10
|
-
const content = await fs.readFile(filePath);
|
|
11
|
-
snapshot.set(filePath, hashBuffer(content));
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
snapshot.set(filePath, null);
|
|
15
|
-
}
|
|
13
|
+
snapshot.set(filePath, await readLockfileHash(filePath));
|
|
16
14
|
}
|
|
17
15
|
return snapshot;
|
|
18
16
|
}
|
|
@@ -20,14 +18,7 @@ export async function changedLockfiles(cwd, before) {
|
|
|
20
18
|
const changed = [];
|
|
21
19
|
for (const name of LOCKFILE_NAMES) {
|
|
22
20
|
const filePath = path.join(cwd, name);
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const content = await fs.readFile(filePath);
|
|
26
|
-
current = hashBuffer(content);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
current = null;
|
|
30
|
-
}
|
|
21
|
+
const current = await readLockfileHash(filePath);
|
|
31
22
|
if ((before.get(filePath) ?? null) !== current) {
|
|
32
23
|
changed.push(filePath);
|
|
33
24
|
}
|
|
@@ -39,6 +30,20 @@ export function validateLockfileMode(mode, install) {
|
|
|
39
30
|
throw new Error("--lockfile-mode update requires --install to update lockfiles deterministically.");
|
|
40
31
|
}
|
|
41
32
|
}
|
|
33
|
+
async function readLockfileHash(filePath) {
|
|
34
|
+
try {
|
|
35
|
+
const file = Bun.file(filePath);
|
|
36
|
+
if (!(await file.exists()))
|
|
37
|
+
return null;
|
|
38
|
+
const content = await file.bytes();
|
|
39
|
+
return hashBuffer(content);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
42
45
|
function hashBuffer(value) {
|
|
43
|
-
|
|
46
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
47
|
+
hasher.update(value);
|
|
48
|
+
return hasher.digest("hex");
|
|
44
49
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getRuntimeCwd, readEnv } from "./runtime.js";
|
|
4
|
+
function resolveHomeDir() {
|
|
5
|
+
return readEnv("HOME") ?? readEnv("USERPROFILE") ?? getRuntimeCwd();
|
|
6
|
+
}
|
|
7
|
+
export function getHomeDir() {
|
|
8
|
+
return resolveHomeDir();
|
|
9
|
+
}
|
|
10
|
+
export function getCacheDir(appName = "rainy-updates") {
|
|
11
|
+
return path.join(readEnv("XDG_CACHE_HOME") ?? path.join(resolveHomeDir(), ".cache"), appName);
|
|
12
|
+
}
|
|
13
|
+
export function getTempDir() {
|
|
14
|
+
return (readEnv("BUN_TMPDIR") ??
|
|
15
|
+
readEnv("TMPDIR") ??
|
|
16
|
+
readEnv("TMP") ??
|
|
17
|
+
readEnv("TEMP") ??
|
|
18
|
+
"/tmp");
|
|
19
|
+
}
|
|
20
|
+
export async function createTempDir(prefix) {
|
|
21
|
+
const baseDir = getTempDir();
|
|
22
|
+
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
23
|
+
const candidate = path.join(baseDir, `${prefix}${crypto.randomUUID()}`);
|
|
24
|
+
try {
|
|
25
|
+
await mkdir(candidate, { recursive: false });
|
|
26
|
+
return candidate;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") {
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Unable to create temp directory for prefix ${prefix}`);
|
|
35
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function getRuntimeCwd(): string;
|
|
2
|
+
export declare function getRuntimeArgv(): string[];
|
|
3
|
+
export declare function readEnv(name: string): string | undefined;
|
|
4
|
+
export declare function writeStdout(message: string): void;
|
|
5
|
+
export declare function writeStderr(message: string): void;
|
|
6
|
+
export declare function setRuntimeExitCode(code: number): void;
|
|
7
|
+
export declare function exitProcess(code: number): never;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function getRuntimeCwd() {
|
|
2
|
+
return process.cwd();
|
|
3
|
+
}
|
|
4
|
+
export function getRuntimeArgv() {
|
|
5
|
+
if (typeof process !== "undefined" && Array.isArray(process.argv)) {
|
|
6
|
+
return process.argv.slice(2);
|
|
7
|
+
}
|
|
8
|
+
if (typeof Bun !== "undefined" && Array.isArray(Bun.argv)) {
|
|
9
|
+
return Bun.argv.slice(2);
|
|
10
|
+
}
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
export function readEnv(name) {
|
|
14
|
+
if (typeof Bun !== "undefined") {
|
|
15
|
+
const value = Bun.env[name];
|
|
16
|
+
if (value)
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
return process.env[name];
|
|
20
|
+
}
|
|
21
|
+
export function writeStdout(message) {
|
|
22
|
+
process.stdout.write(message);
|
|
23
|
+
}
|
|
24
|
+
export function writeStderr(message) {
|
|
25
|
+
process.stderr.write(message);
|
|
26
|
+
}
|
|
27
|
+
export function setRuntimeExitCode(code) {
|
|
28
|
+
process.exitCode = code;
|
|
29
|
+
}
|
|
30
|
+
export function exitProcess(code) {
|
|
31
|
+
process.exit(code);
|
|
32
|
+
}
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { DependencyKind } from "../types/index.js";
|
|
2
|
+
import { type GitScopeOptions } from "../git/scope.js";
|
|
3
|
+
export declare function discoverPackageDirs(cwd: string, workspaceMode: boolean, options?: {
|
|
4
|
+
git?: GitScopeOptions;
|
|
5
|
+
includeKinds?: DependencyKind[];
|
|
6
|
+
includeDependents?: boolean;
|
|
7
|
+
}): Promise<string[]>;
|