@rainy-updates/cli 0.6.0 → 0.6.2
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 +111 -0
- package/README.md +12 -0
- package/dist/bin/cli.js +12 -127
- package/dist/bin/dispatch.js +6 -0
- package/dist/bin/help.js +48 -0
- package/dist/bin/main.d.ts +1 -0
- package/dist/bin/main.js +126 -0
- package/dist/commands/audit/parser.js +36 -0
- package/dist/commands/audit/runner.js +17 -18
- package/dist/commands/bisect/oracle.js +21 -17
- package/dist/commands/bisect/runner.js +4 -3
- package/dist/commands/dashboard/parser.js +41 -0
- package/dist/commands/dashboard/runner.js +3 -0
- package/dist/commands/doctor/parser.js +44 -0
- package/dist/commands/ga/parser.js +39 -0
- package/dist/commands/ga/runner.js +73 -9
- package/dist/commands/health/parser.js +36 -0
- 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 +5 -1
- package/dist/commands/resolve/graph/builder.js +5 -1
- package/dist/commands/resolve/parser.js +39 -0
- package/dist/commands/resolve/runner.js +5 -0
- package/dist/commands/review/parser.js +44 -0
- package/dist/commands/snapshot/parser.js +39 -0
- package/dist/commands/snapshot/runner.js +4 -1
- package/dist/commands/unused/parser.js +39 -0
- package/dist/commands/unused/runner.js +4 -1
- package/dist/commands/unused/scanner.d.ts +2 -1
- package/dist/commands/unused/scanner.js +60 -44
- package/dist/core/check.js +5 -1
- package/dist/core/doctor/findings.js +4 -4
- package/dist/core/init-ci.js +28 -26
- package/dist/core/options.d.ts +4 -1
- package/dist/core/options.js +57 -0
- package/dist/core/verification.js +11 -9
- package/dist/core/warm-cache.js +5 -1
- 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/sarif.js +2 -8
- package/dist/pm/detect.d.ts +37 -0
- package/dist/pm/detect.js +133 -2
- package/dist/pm/install.d.ts +2 -1
- package/dist/pm/install.js +7 -5
- package/dist/rup +0 -0
- package/dist/types/index.d.ts +59 -1
- package/dist/ui/dashboard-state.d.ts +7 -0
- package/dist/ui/dashboard-state.js +44 -0
- package/dist/ui/tui.d.ts +3 -0
- package/dist/ui/tui.js +311 -111
- package/dist/utils/shell.d.ts +6 -0
- package/dist/utils/shell.js +18 -0
- package/dist/workspace/discover.d.ts +7 -1
- package/dist/workspace/discover.js +12 -3
- package/package.json +16 -8
package/dist/ui/tui.js
CHANGED
|
@@ -1,178 +1,286 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import { Box, render, Text, useInput } from "ink";
|
|
4
|
-
|
|
5
|
-
"all",
|
|
6
|
-
"security",
|
|
7
|
-
"risky",
|
|
8
|
-
"major",
|
|
9
|
-
"peer-conflict",
|
|
10
|
-
"license",
|
|
11
|
-
"unused",
|
|
12
|
-
"blocked",
|
|
13
|
-
];
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, render, Text, useInput, useStdout } from "ink";
|
|
4
|
+
import { DETAIL_TABS, FILTER_ORDER, } from "./dashboard-state.js";
|
|
14
5
|
const SORT_ORDER = ["risk", "advisories", "diff", "name", "workspace"];
|
|
15
6
|
const GROUP_ORDER = ["none", "workspace", "scope", "risk", "decision"];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
7
|
+
function tuiReducer(state, action) {
|
|
8
|
+
switch (action.type) {
|
|
9
|
+
case "SET_SEARCH_MODE":
|
|
10
|
+
return {
|
|
11
|
+
...state,
|
|
12
|
+
searchMode: action.active,
|
|
13
|
+
...(action.active ? {} : { search: "", cursorIndex: 0 }),
|
|
14
|
+
};
|
|
15
|
+
case "APPEND_SEARCH":
|
|
16
|
+
return { ...state, search: state.search + action.value, cursorIndex: 0 };
|
|
17
|
+
case "BACKSPACE_SEARCH":
|
|
18
|
+
return { ...state, search: state.search.slice(0, -1), cursorIndex: 0 };
|
|
19
|
+
case "TOGGLE_HELP":
|
|
20
|
+
return { ...state, showHelp: !state.showHelp };
|
|
21
|
+
case "SET_HELP":
|
|
22
|
+
return { ...state, showHelp: action.active };
|
|
23
|
+
case "MOVE_FILTER":
|
|
24
|
+
return {
|
|
25
|
+
...state,
|
|
26
|
+
filterIndex: Math.min(action.max, Math.max(0, state.filterIndex + action.direction)),
|
|
27
|
+
cursorIndex: 0,
|
|
28
|
+
};
|
|
29
|
+
case "MOVE_CURSOR":
|
|
30
|
+
return {
|
|
31
|
+
...state,
|
|
32
|
+
cursorIndex: Math.min(action.max, Math.max(0, state.cursorIndex + action.direction)),
|
|
33
|
+
};
|
|
34
|
+
case "CYCLE_SORT":
|
|
35
|
+
return {
|
|
36
|
+
...state,
|
|
37
|
+
sortIndex: (state.sortIndex + 1) % action.max,
|
|
38
|
+
cursorIndex: 0,
|
|
39
|
+
};
|
|
40
|
+
case "CYCLE_GROUP":
|
|
41
|
+
return {
|
|
42
|
+
...state,
|
|
43
|
+
groupIndex: (state.groupIndex + 1) % action.max,
|
|
44
|
+
cursorIndex: 0,
|
|
45
|
+
};
|
|
46
|
+
case "CYCLE_TAB": {
|
|
47
|
+
const next = (state.tabIndex + action.direction + action.max) % action.max;
|
|
48
|
+
return { ...state, tabIndex: next };
|
|
49
|
+
}
|
|
50
|
+
case "SET_SELECTED":
|
|
51
|
+
return { ...state, selectedIndices: action.indices };
|
|
52
|
+
case "TOGGLE_SELECTED": {
|
|
53
|
+
const next = new Set(state.selectedIndices);
|
|
54
|
+
if (next.has(action.index))
|
|
55
|
+
next.delete(action.index);
|
|
56
|
+
else
|
|
57
|
+
next.add(action.index);
|
|
58
|
+
return { ...state, selectedIndices: next };
|
|
59
|
+
}
|
|
60
|
+
default:
|
|
61
|
+
return state;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function TuiApp({ items, title, subtitle, initialFilter = "all", initialTab = "overview", onComplete, }) {
|
|
65
|
+
const { stdout } = useStdout();
|
|
66
|
+
const { columns: stdoutWidth = 160, rows: stdoutHeight = 32 } = stdout;
|
|
67
|
+
const [state, dispatch] = React.useReducer(tuiReducer, undefined, () => ({
|
|
68
|
+
cursorIndex: 0,
|
|
69
|
+
filterIndex: Math.max(0, FILTER_ORDER.indexOf(initialFilter)),
|
|
70
|
+
sortIndex: 0,
|
|
71
|
+
groupIndex: 0,
|
|
72
|
+
tabIndex: Math.max(0, DETAIL_TABS.indexOf(initialTab)),
|
|
73
|
+
showHelp: false,
|
|
74
|
+
searchMode: false,
|
|
75
|
+
search: "",
|
|
76
|
+
selectedIndices: new Set(items.flatMap((item, index) => item.update.selectedByDefault === false ? [] : [index])),
|
|
77
|
+
}));
|
|
78
|
+
const activeFilter = FILTER_ORDER[state.filterIndex] ?? "all";
|
|
79
|
+
const activeSort = SORT_ORDER[state.sortIndex] ?? "risk";
|
|
80
|
+
const activeGroup = GROUP_ORDER[state.groupIndex] ?? "none";
|
|
81
|
+
const activeTab = DETAIL_TABS[state.tabIndex] ?? "overview";
|
|
39
82
|
const visibleRows = buildVisibleRows(items, {
|
|
40
83
|
filter: activeFilter,
|
|
41
84
|
sort: activeSort,
|
|
42
85
|
group: activeGroup,
|
|
43
|
-
search,
|
|
86
|
+
search: state.search,
|
|
44
87
|
});
|
|
45
88
|
const itemRows = visibleRows.filter((row) => row.kind === "item" && typeof row.index === "number");
|
|
46
|
-
const boundedCursor = Math.min(cursorIndex, Math.max(0, itemRows.length - 1));
|
|
89
|
+
const boundedCursor = Math.min(state.cursorIndex, Math.max(0, itemRows.length - 1));
|
|
47
90
|
const focusedIndex = itemRows[boundedCursor]?.index ?? 0;
|
|
48
91
|
const focusedItem = items[focusedIndex];
|
|
92
|
+
const visibleMetrics = summarizeVisibleItems(itemRows, items, state.selectedIndices);
|
|
93
|
+
const renderWindow = createRenderWindow({
|
|
94
|
+
visibleRows,
|
|
95
|
+
focusedIndex,
|
|
96
|
+
stdoutHeight,
|
|
97
|
+
});
|
|
98
|
+
const rowPositionByIndex = createRowPositionMap(itemRows);
|
|
99
|
+
const layout = createDashboardLayout(stdoutWidth);
|
|
100
|
+
const platformLabel = process.platform === "win32" ? "windows" : "unix";
|
|
101
|
+
const selectedItems = items.filter((_, index) => state.selectedIndices.has(index));
|
|
49
102
|
useInput((input, key) => {
|
|
50
|
-
if (searchMode) {
|
|
103
|
+
if (state.searchMode) {
|
|
51
104
|
if (key.escape) {
|
|
52
|
-
|
|
53
|
-
setSearch("");
|
|
54
|
-
setCursorIndex(0);
|
|
105
|
+
dispatch({ type: "SET_SEARCH_MODE", active: false });
|
|
55
106
|
return;
|
|
56
107
|
}
|
|
57
108
|
if (key.return) {
|
|
58
|
-
|
|
59
|
-
setCursorIndex(0);
|
|
109
|
+
dispatch({ type: "SET_SEARCH_MODE", active: false });
|
|
60
110
|
return;
|
|
61
111
|
}
|
|
62
112
|
if (key.backspace || key.delete) {
|
|
63
|
-
|
|
64
|
-
setCursorIndex(0);
|
|
113
|
+
dispatch({ type: "BACKSPACE_SEARCH" });
|
|
65
114
|
return;
|
|
66
115
|
}
|
|
67
116
|
if (input && !key.ctrl && !key.meta) {
|
|
68
|
-
|
|
69
|
-
setCursorIndex(0);
|
|
117
|
+
dispatch({ type: "APPEND_SEARCH", value: input });
|
|
70
118
|
}
|
|
71
119
|
return;
|
|
72
120
|
}
|
|
73
121
|
if (input === "/") {
|
|
74
|
-
|
|
122
|
+
dispatch({ type: "SET_SEARCH_MODE", active: true });
|
|
75
123
|
return;
|
|
76
124
|
}
|
|
77
125
|
if (input === "?") {
|
|
78
|
-
|
|
126
|
+
dispatch({ type: "TOGGLE_HELP" });
|
|
79
127
|
return;
|
|
80
128
|
}
|
|
81
|
-
if (key.escape && showHelp) {
|
|
82
|
-
|
|
129
|
+
if (key.escape && state.showHelp) {
|
|
130
|
+
dispatch({ type: "SET_HELP", active: false });
|
|
83
131
|
return;
|
|
84
132
|
}
|
|
85
|
-
if (key.leftArrow) {
|
|
86
|
-
|
|
87
|
-
|
|
133
|
+
if (key.leftArrow || input === "h") {
|
|
134
|
+
dispatch({
|
|
135
|
+
type: "MOVE_FILTER",
|
|
136
|
+
direction: -1,
|
|
137
|
+
max: FILTER_ORDER.length - 1,
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
88
140
|
}
|
|
89
|
-
if (key.rightArrow) {
|
|
90
|
-
|
|
91
|
-
|
|
141
|
+
if (key.rightArrow || input === "l") {
|
|
142
|
+
dispatch({
|
|
143
|
+
type: "MOVE_FILTER",
|
|
144
|
+
direction: 1,
|
|
145
|
+
max: FILTER_ORDER.length - 1,
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
92
148
|
}
|
|
93
|
-
if (key.upArrow) {
|
|
94
|
-
|
|
149
|
+
if (key.upArrow || input === "k") {
|
|
150
|
+
dispatch({
|
|
151
|
+
type: "MOVE_CURSOR",
|
|
152
|
+
direction: -1,
|
|
153
|
+
max: itemRows.length - 1,
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
95
156
|
}
|
|
96
|
-
if (key.downArrow) {
|
|
97
|
-
|
|
157
|
+
if (key.downArrow || input === "j") {
|
|
158
|
+
dispatch({
|
|
159
|
+
type: "MOVE_CURSOR",
|
|
160
|
+
direction: 1,
|
|
161
|
+
max: itemRows.length - 1,
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
98
164
|
}
|
|
99
165
|
if (input === "o") {
|
|
100
|
-
|
|
101
|
-
|
|
166
|
+
dispatch({ type: "CYCLE_SORT", max: SORT_ORDER.length });
|
|
167
|
+
return;
|
|
102
168
|
}
|
|
103
169
|
if (input === "g") {
|
|
104
|
-
|
|
105
|
-
|
|
170
|
+
dispatch({ type: "CYCLE_GROUP", max: GROUP_ORDER.length });
|
|
171
|
+
return;
|
|
106
172
|
}
|
|
107
173
|
if (key.tab) {
|
|
108
|
-
|
|
174
|
+
dispatch({
|
|
175
|
+
type: "CYCLE_TAB",
|
|
176
|
+
direction: key.shift ? -1 : 1,
|
|
177
|
+
max: DETAIL_TABS.length,
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
109
180
|
}
|
|
110
181
|
if (input === "a") {
|
|
111
|
-
|
|
182
|
+
dispatch({
|
|
183
|
+
type: "SET_SELECTED",
|
|
184
|
+
indices: addVisible(state.selectedIndices, itemRows),
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
112
187
|
}
|
|
113
188
|
if (input === "n") {
|
|
114
|
-
|
|
189
|
+
dispatch({
|
|
190
|
+
type: "SET_SELECTED",
|
|
191
|
+
indices: removeVisible(state.selectedIndices, itemRows),
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
115
194
|
}
|
|
116
195
|
if (input === "s") {
|
|
117
|
-
|
|
196
|
+
dispatch({
|
|
197
|
+
type: "SET_SELECTED",
|
|
198
|
+
indices: selectSafe(state.selectedIndices, itemRows, items),
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
118
201
|
}
|
|
119
202
|
if (input === "b") {
|
|
120
|
-
|
|
203
|
+
dispatch({
|
|
204
|
+
type: "SET_SELECTED",
|
|
205
|
+
indices: clearBlocked(state.selectedIndices, itemRows, items),
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
121
208
|
}
|
|
122
|
-
if (input === "
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
next.delete(focusedIndex);
|
|
127
|
-
else
|
|
128
|
-
next.add(focusedIndex);
|
|
129
|
-
return next;
|
|
209
|
+
if (input === "x") {
|
|
210
|
+
dispatch({
|
|
211
|
+
type: "SET_SELECTED",
|
|
212
|
+
indices: selectActionable(state.selectedIndices, itemRows, items),
|
|
130
213
|
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (input === " ") {
|
|
217
|
+
dispatch({ type: "TOGGLE_SELECTED", index: focusedIndex });
|
|
218
|
+
return;
|
|
131
219
|
}
|
|
132
|
-
if (input === "q" || key.escape) {
|
|
133
|
-
onComplete(
|
|
220
|
+
if (input === "q" || (key.escape && !state.showHelp)) {
|
|
221
|
+
onComplete(selectedItems);
|
|
134
222
|
return;
|
|
135
223
|
}
|
|
136
224
|
if (key.return) {
|
|
137
|
-
onComplete(
|
|
225
|
+
onComplete(selectedItems);
|
|
138
226
|
}
|
|
139
227
|
});
|
|
140
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
228
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(DashboardHeader, { title: title, subtitle: subtitle, platformLabel: platformLabel, metrics: visibleMetrics }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(FilterRail, { width: layout.railWidth, filterIndex: state.filterIndex, search: state.search, searchMode: state.searchMode, activeSort: activeSort, activeGroup: activeGroup, activeTab: activeTab }), _jsx(QueuePanel, { width: layout.queueWidth, items: items, visibleRows: visibleRows, itemRows: itemRows, renderWindow: renderWindow, rowPositionByIndex: rowPositionByIndex, boundedCursor: boundedCursor, selectedIndices: state.selectedIndices }), _jsx(DecisionPanel, { width: layout.detailWidth, activeTab: activeTab, focusedItem: focusedItem })] }), _jsx(ActionBar, {}), state.showHelp ? _jsx(HelpPanel, {}) : null] }));
|
|
229
|
+
}
|
|
230
|
+
function DashboardHeader({ title, subtitle, platformLabel, metrics, }) {
|
|
231
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, color: "cyan", children: title ?? "Rainy Dashboard" }), _jsx(Text, { color: "gray", children: subtitle ??
|
|
232
|
+
"Check detects, doctor summarizes, dashboard decides, upgrade applies." }), _jsxs(Text, { color: "gray", children: [platformLabel, " keys: arrows or hjkl, Tab changes panel, / search, ? help, Enter confirm"] }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsxs(Text, { children: ["visible ", metrics.total, " | selected ", metrics.selected, " | actionable", " ", metrics.actionable, " | blocked ", metrics.blocked, " | security", " ", metrics.security] }) })] }));
|
|
233
|
+
}
|
|
234
|
+
function FilterRail({ width, filterIndex, search, searchMode, activeSort, activeGroup, activeTab, }) {
|
|
235
|
+
return (_jsxs(Box, { width: width, 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] })] })] }));
|
|
236
|
+
}
|
|
237
|
+
function QueuePanel({ width, items, visibleRows, itemRows, renderWindow, rowPositionByIndex, boundedCursor, selectedIndices, }) {
|
|
238
|
+
return (_jsxs(Box, { marginLeft: 1, width: width, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsxs(Text, { bold: true, children: ["Review Queue", renderWindow.start > 0 ? " [more above]" : "", renderWindow.end < visibleRows.length ? " [more below]" : ""] }), itemRows.length === 0 ? (_jsx(Text, { color: "gray", children: "No review candidates match this view." })) : (renderWindow.rows.map((row) => (_jsx(QueueRow, { row: row, items: items, rowPositionByIndex: rowPositionByIndex, boundedCursor: boundedCursor, selectedIndices: selectedIndices }, row.kind === "group" ? `group:${row.label}` : `${items[row.index ?? 0]?.update.packagePath}:${items[row.index ?? 0]?.update.name}`))))] }));
|
|
239
|
+
}
|
|
240
|
+
function QueueRow({ row, items, rowPositionByIndex, boundedCursor, selectedIndices, }) {
|
|
241
|
+
if (row.kind === "group") {
|
|
242
|
+
return (_jsx(Text, { bold: true, color: "gray", children: row.label }));
|
|
243
|
+
}
|
|
244
|
+
const index = row.index ?? 0;
|
|
245
|
+
const item = items[index];
|
|
246
|
+
const update = item.update;
|
|
247
|
+
const decision = update.decisionState ?? deriveDecision(item);
|
|
248
|
+
const itemPosition = rowPositionByIndex.get(index) ?? -1;
|
|
249
|
+
const isFocused = itemPosition === boundedCursor;
|
|
250
|
+
const isSelected = selectedIndices.has(index);
|
|
251
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: isFocused ? "cyan" : "gray", children: [isFocused ? ">" : " ", " ", isSelected ? "[x]" : "[ ]", " "] }), _jsx(Box, { width: 24, children: _jsx(Text, { bold: isFocused, children: truncate(update.name, 22) }) }), _jsx(Box, { width: 10, children: _jsx(Text, { color: diffColor(update.diffType), children: update.diffType }) }), _jsx(Box, { width: 11, children: _jsx(Text, { color: riskColor(update.riskLevel), children: update.riskLevel ?? "low" }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: decisionColor(decision), children: truncate(decision, 10) }) }), _jsx(Box, { width: 7, children: _jsx(Text, { color: decisionColor(decision), children: update.riskScore ?? "--" }) }), _jsxs(Text, { color: "gray", children: [truncate(update.fromRange, 12), " ", "->", " "] }), _jsx(Text, { color: "green", children: truncate(update.toVersionResolved, 12) })] }));
|
|
252
|
+
}
|
|
253
|
+
function DecisionPanel({ width, activeTab, focusedItem, }) {
|
|
254
|
+
return (_jsxs(Box, { marginLeft: 1, width: width, 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." }))] }));
|
|
255
|
+
}
|
|
256
|
+
function ActionBar() {
|
|
257
|
+
return (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", children: "A add visible | N clear visible | S safe | X actionable | B clear blocked | Space toggle | Q finish" }) }));
|
|
258
|
+
}
|
|
259
|
+
function HelpPanel() {
|
|
260
|
+
return (_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 filters for queue slices, search with /, and switch panels with Tab." }), _jsx(Text, { color: "gray", children: "The queue is windowed around the focused package for faster rendering in large workspaces." }), _jsx(Text, { color: "gray", children: "Actionable items can be bulk-selected with X. Blocked items stay easy to clear with B." })] }));
|
|
154
261
|
}
|
|
155
262
|
function renderTab(item, tab) {
|
|
156
263
|
const update = item.update;
|
|
157
264
|
if (tab === "risk") {
|
|
158
|
-
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
|
|
265
|
+
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:", " ", truncate(update.recommendedAction ?? "Safe to keep in the selected set.", 80)] }), update.riskReasons && update.riskReasons.length > 0 ? (update.riskReasons.slice(0, 5).map((reason) => (_jsxs(Text, { color: "gray", children: ["- ", truncate(reason, 48)] }, reason)))) : (_jsx(Text, { color: "gray", children: "No elevated risk reasons." }))] }));
|
|
159
266
|
}
|
|
160
267
|
if (tab === "security") {
|
|
161
|
-
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
|
|
268
|
+
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: ["- ", truncate(`${advisory.severity} ${advisory.cveId}: ${advisory.title}`, 48)] }, `${advisory.packageName}:${advisory.cveId}`)))) : (_jsx(Text, { color: "gray", children: "No security advisories detected." }))] }));
|
|
162
269
|
}
|
|
163
270
|
if (tab === "peer") {
|
|
164
|
-
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
|
|
271
|
+
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: ["- ", truncate(`${conflict.requester} requires ${conflict.peer} ${conflict.requiredRange}`, 48)] }, `${conflict.requester}:${conflict.peer}`)))) : (_jsx(Text, { color: "gray", children: "No peer conflicts detected." }))] }));
|
|
165
272
|
}
|
|
166
273
|
if (tab === "license") {
|
|
167
|
-
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"] })] }));
|
|
274
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["license status: ", update.licenseStatus ?? "allowed"] }), _jsxs(Text, { children: ["repository: ", truncate(update.repository ?? "unavailable", 48)] }), _jsxs(Text, { children: ["homepage: ", truncate(update.homepage ?? "unavailable", 48)] })] }));
|
|
168
275
|
}
|
|
169
276
|
if (tab === "health") {
|
|
170
277
|
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"] })] }));
|
|
171
278
|
}
|
|
172
279
|
if (tab === "changelog") {
|
|
173
|
-
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: update.releaseNotesSummary?.title ?? "Release notes unavailable" }), _jsx(Text, { color: "gray", children: update.releaseNotesSummary?.excerpt ??
|
|
280
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: truncate(update.releaseNotesSummary?.title ?? "Release notes unavailable", 48) }), _jsx(Text, { color: "gray", children: truncate(update.releaseNotesSummary?.excerpt ??
|
|
281
|
+
"Run review with changelog support or inspect the repository manually.", 96) })] }));
|
|
174
282
|
}
|
|
175
|
-
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."] })] }));
|
|
283
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: update.name }), _jsxs(Text, { color: "gray", children: ["package: ", truncate(update.packagePath, 48)] }), _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:", " ", truncate(update.recommendedAction ?? "Safe to keep in the selected set.", 80)] })] }));
|
|
176
284
|
}
|
|
177
285
|
function buildVisibleRows(items, config) {
|
|
178
286
|
const filtered = items
|
|
@@ -181,7 +289,11 @@ function buildVisibleRows(items, config) {
|
|
|
181
289
|
.filter(({ item }) => matchesSearch(item, config.search))
|
|
182
290
|
.sort((left, right) => compareItems(left.item, right.item, config.sort));
|
|
183
291
|
if (config.group === "none") {
|
|
184
|
-
return filtered.map(({ item, index }) => ({
|
|
292
|
+
return filtered.map(({ item, index }) => ({
|
|
293
|
+
kind: "item",
|
|
294
|
+
label: item.update.name,
|
|
295
|
+
index,
|
|
296
|
+
}));
|
|
185
297
|
}
|
|
186
298
|
const rows = [];
|
|
187
299
|
let currentGroup = "";
|
|
@@ -191,15 +303,20 @@ function buildVisibleRows(items, config) {
|
|
|
191
303
|
currentGroup = nextGroup;
|
|
192
304
|
rows.push({ kind: "group", label: nextGroup });
|
|
193
305
|
}
|
|
194
|
-
rows.push({
|
|
306
|
+
rows.push({
|
|
307
|
+
kind: "item",
|
|
308
|
+
label: entry.item.update.name,
|
|
309
|
+
index: entry.index,
|
|
310
|
+
});
|
|
195
311
|
}
|
|
196
312
|
return rows;
|
|
197
313
|
}
|
|
198
314
|
function matchesFilter(item, filter) {
|
|
199
315
|
if (filter === "security")
|
|
200
316
|
return item.advisories.length > 0;
|
|
201
|
-
if (filter === "risky")
|
|
317
|
+
if (filter === "risky") {
|
|
202
318
|
return item.update.riskLevel === "critical" || item.update.riskLevel === "high";
|
|
319
|
+
}
|
|
203
320
|
if (filter === "major")
|
|
204
321
|
return item.update.diffType === "major";
|
|
205
322
|
if (filter === "peer-conflict")
|
|
@@ -208,8 +325,9 @@ function matchesFilter(item, filter) {
|
|
|
208
325
|
return item.update.licenseStatus === "denied";
|
|
209
326
|
if (filter === "unused")
|
|
210
327
|
return item.unusedIssues.length > 0;
|
|
211
|
-
if (filter === "blocked")
|
|
328
|
+
if (filter === "blocked") {
|
|
212
329
|
return (item.update.decisionState ?? deriveDecision(item)) === "blocked";
|
|
330
|
+
}
|
|
213
331
|
return true;
|
|
214
332
|
}
|
|
215
333
|
function matchesSearch(item, search) {
|
|
@@ -221,7 +339,7 @@ function matchesSearch(item, search) {
|
|
|
221
339
|
}
|
|
222
340
|
function compareItems(left, right, sort) {
|
|
223
341
|
if (sort === "advisories") {
|
|
224
|
-
const byAdvisories =
|
|
342
|
+
const byAdvisories = right.advisories.length - left.advisories.length;
|
|
225
343
|
if (byAdvisories !== 0)
|
|
226
344
|
return byAdvisories;
|
|
227
345
|
}
|
|
@@ -256,8 +374,9 @@ function groupLabel(item, group) {
|
|
|
256
374
|
}
|
|
257
375
|
if (group === "risk")
|
|
258
376
|
return item.update.riskLevel ?? "low";
|
|
259
|
-
if (group === "decision")
|
|
377
|
+
if (group === "decision") {
|
|
260
378
|
return item.update.decisionState ?? deriveDecision(item);
|
|
379
|
+
}
|
|
261
380
|
return "all";
|
|
262
381
|
}
|
|
263
382
|
function addVisible(selected, rows) {
|
|
@@ -275,7 +394,8 @@ function removeVisible(selected, rows) {
|
|
|
275
394
|
function selectSafe(selected, rows, items) {
|
|
276
395
|
const next = new Set(selected);
|
|
277
396
|
for (const row of rows) {
|
|
278
|
-
if ((items[row.index]?.update.decisionState ??
|
|
397
|
+
if ((items[row.index]?.update.decisionState ??
|
|
398
|
+
deriveDecision(items[row.index])) === "safe") {
|
|
279
399
|
next.add(row.index);
|
|
280
400
|
}
|
|
281
401
|
}
|
|
@@ -284,14 +404,27 @@ function selectSafe(selected, rows, items) {
|
|
|
284
404
|
function clearBlocked(selected, rows, items) {
|
|
285
405
|
const next = new Set(selected);
|
|
286
406
|
for (const row of rows) {
|
|
287
|
-
if ((items[row.index]?.update.decisionState ??
|
|
407
|
+
if ((items[row.index]?.update.decisionState ??
|
|
408
|
+
deriveDecision(items[row.index])) === "blocked") {
|
|
288
409
|
next.delete(row.index);
|
|
289
410
|
}
|
|
290
411
|
}
|
|
291
412
|
return next;
|
|
292
413
|
}
|
|
414
|
+
function selectActionable(selected, rows, items) {
|
|
415
|
+
const next = new Set(selected);
|
|
416
|
+
for (const row of rows) {
|
|
417
|
+
const decision = items[row.index]?.update.decisionState ??
|
|
418
|
+
deriveDecision(items[row.index]);
|
|
419
|
+
if (decision === "actionable" || decision === "review") {
|
|
420
|
+
next.add(row.index);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return next;
|
|
424
|
+
}
|
|
293
425
|
function deriveDecision(item) {
|
|
294
|
-
if (item.update.peerConflictSeverity === "error" ||
|
|
426
|
+
if (item.update.peerConflictSeverity === "error" ||
|
|
427
|
+
item.update.licenseStatus === "denied") {
|
|
295
428
|
return "blocked";
|
|
296
429
|
}
|
|
297
430
|
if ((item.update.advisoryCount ?? 0) > 0 || item.update.riskLevel === "critical") {
|
|
@@ -354,9 +487,76 @@ function decisionColor(label) {
|
|
|
354
487
|
return "green";
|
|
355
488
|
}
|
|
356
489
|
}
|
|
490
|
+
function clamp(value, min, max) {
|
|
491
|
+
return Math.min(max, Math.max(min, value));
|
|
492
|
+
}
|
|
493
|
+
function createDashboardLayout(stdoutWidth) {
|
|
494
|
+
const railWidth = clamp(Math.floor(stdoutWidth * 0.2), 24, 30);
|
|
495
|
+
const detailWidth = clamp(Math.floor(stdoutWidth * 0.26), 36, 54);
|
|
496
|
+
const queueWidth = Math.max(48, stdoutWidth - railWidth - detailWidth - 8);
|
|
497
|
+
return { railWidth, detailWidth, queueWidth };
|
|
498
|
+
}
|
|
499
|
+
function truncate(value, maxLength) {
|
|
500
|
+
if (value.length <= maxLength)
|
|
501
|
+
return value;
|
|
502
|
+
if (maxLength <= 1)
|
|
503
|
+
return value.slice(0, maxLength);
|
|
504
|
+
return `${value.slice(0, Math.max(1, maxLength - 1))}…`;
|
|
505
|
+
}
|
|
506
|
+
function createRowPositionMap(rows) {
|
|
507
|
+
const map = new Map();
|
|
508
|
+
rows.forEach((row, position) => {
|
|
509
|
+
map.set(row.index, position);
|
|
510
|
+
});
|
|
511
|
+
return map;
|
|
512
|
+
}
|
|
513
|
+
function createRenderWindow(config) {
|
|
514
|
+
const maxRows = clamp(config.stdoutHeight - 16, 8, 18);
|
|
515
|
+
if (config.visibleRows.length <= maxRows) {
|
|
516
|
+
return {
|
|
517
|
+
rows: config.visibleRows,
|
|
518
|
+
start: 0,
|
|
519
|
+
end: config.visibleRows.length,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const focusedRow = Math.max(0, config.visibleRows.findIndex((row) => row.kind === "item" && row.index === config.focusedIndex));
|
|
523
|
+
let start = Math.max(0, focusedRow - Math.floor(maxRows / 2));
|
|
524
|
+
let end = Math.min(config.visibleRows.length, start + maxRows);
|
|
525
|
+
start = Math.max(0, end - maxRows);
|
|
526
|
+
return {
|
|
527
|
+
rows: config.visibleRows.slice(start, end),
|
|
528
|
+
start,
|
|
529
|
+
end,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function summarizeVisibleItems(rows, items, selected) {
|
|
533
|
+
let actionable = 0;
|
|
534
|
+
let blocked = 0;
|
|
535
|
+
let security = 0;
|
|
536
|
+
let selectedCount = 0;
|
|
537
|
+
for (const row of rows) {
|
|
538
|
+
const item = items[row.index];
|
|
539
|
+
const decision = item.update.decisionState ?? deriveDecision(item);
|
|
540
|
+
if (selected.has(row.index))
|
|
541
|
+
selectedCount += 1;
|
|
542
|
+
if (decision === "actionable" || decision === "review")
|
|
543
|
+
actionable += 1;
|
|
544
|
+
if (decision === "blocked")
|
|
545
|
+
blocked += 1;
|
|
546
|
+
if (item.advisories.length > 0)
|
|
547
|
+
security += 1;
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
total: rows.length,
|
|
551
|
+
selected: selectedCount,
|
|
552
|
+
actionable,
|
|
553
|
+
blocked,
|
|
554
|
+
security,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
357
557
|
export async function runTui(items, options) {
|
|
358
558
|
return new Promise((resolve) => {
|
|
359
|
-
const { unmount } = render(_jsx(TuiApp, { items: items, title: options?.title, subtitle: options?.subtitle, onComplete: (selected) => {
|
|
559
|
+
const { unmount } = render(_jsx(TuiApp, { items: items, title: options?.title, subtitle: options?.subtitle, initialFilter: options?.initialFilter, initialTab: options?.initialTab, onComplete: (selected) => {
|
|
360
560
|
unmount();
|
|
361
561
|
resolve(selected);
|
|
362
562
|
} }));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function buildShellInvocation(command, runtimePlatform = process.platform, env = process.env) {
|
|
2
|
+
if (runtimePlatform === "win32") {
|
|
3
|
+
const shell = env.COMSPEC?.trim() || "cmd.exe";
|
|
4
|
+
const args = ["/d", "/s", "/c", command];
|
|
5
|
+
return {
|
|
6
|
+
shell,
|
|
7
|
+
args,
|
|
8
|
+
display: [shell, ...args].join(" "),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const shell = env.SHELL?.trim() || "sh";
|
|
12
|
+
const args = ["-lc", command];
|
|
13
|
+
return {
|
|
14
|
+
shell,
|
|
15
|
+
args,
|
|
16
|
+
display: [shell, ...args].join(" "),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -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[]>;
|