@rainy-updates/cli 0.6.0 → 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.
Files changed (55) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/bin/cli.js +12 -127
  3. package/dist/bin/dispatch.js +6 -0
  4. package/dist/bin/help.js +47 -0
  5. package/dist/bin/main.d.ts +1 -0
  6. package/dist/bin/main.js +126 -0
  7. package/dist/commands/audit/parser.js +36 -0
  8. package/dist/commands/audit/runner.js +17 -18
  9. package/dist/commands/bisect/oracle.js +18 -15
  10. package/dist/commands/bisect/runner.js +4 -3
  11. package/dist/commands/dashboard/parser.js +41 -0
  12. package/dist/commands/doctor/parser.js +44 -0
  13. package/dist/commands/ga/parser.js +39 -0
  14. package/dist/commands/ga/runner.js +10 -7
  15. package/dist/commands/health/parser.js +36 -0
  16. package/dist/commands/health/runner.js +5 -1
  17. package/dist/commands/hook/parser.d.ts +2 -0
  18. package/dist/commands/hook/parser.js +40 -0
  19. package/dist/commands/hook/runner.d.ts +2 -0
  20. package/dist/commands/hook/runner.js +174 -0
  21. package/dist/commands/licenses/parser.js +39 -0
  22. package/dist/commands/licenses/runner.js +5 -1
  23. package/dist/commands/resolve/graph/builder.js +5 -1
  24. package/dist/commands/resolve/parser.js +39 -0
  25. package/dist/commands/resolve/runner.js +5 -0
  26. package/dist/commands/review/parser.js +44 -0
  27. package/dist/commands/snapshot/parser.js +39 -0
  28. package/dist/commands/snapshot/runner.js +4 -1
  29. package/dist/commands/unused/parser.js +39 -0
  30. package/dist/commands/unused/runner.js +4 -1
  31. package/dist/commands/unused/scanner.d.ts +2 -1
  32. package/dist/commands/unused/scanner.js +60 -44
  33. package/dist/core/check.js +5 -1
  34. package/dist/core/init-ci.js +28 -26
  35. package/dist/core/options.d.ts +4 -1
  36. package/dist/core/options.js +57 -0
  37. package/dist/core/verification.js +8 -6
  38. package/dist/core/warm-cache.js +5 -1
  39. package/dist/generated/version.d.ts +1 -0
  40. package/dist/generated/version.js +2 -0
  41. package/dist/git/scope.d.ts +19 -0
  42. package/dist/git/scope.js +167 -0
  43. package/dist/index.d.ts +2 -1
  44. package/dist/index.js +1 -0
  45. package/dist/output/sarif.js +2 -8
  46. package/dist/pm/detect.d.ts +37 -0
  47. package/dist/pm/detect.js +133 -2
  48. package/dist/pm/install.d.ts +2 -1
  49. package/dist/pm/install.js +7 -5
  50. package/dist/rup +0 -0
  51. package/dist/types/index.d.ts +58 -0
  52. package/dist/ui/tui.js +152 -64
  53. package/dist/workspace/discover.d.ts +7 -1
  54. package/dist/workspace/discover.js +12 -3
  55. package/package.json +10 -5
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 { useState } from "react";
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 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
+ }
25
84
  function TuiApp({ items, title, subtitle, onComplete }) {
26
- const [cursorIndex, setCursorIndex] = useState(0);
27
- const [filterIndex, setFilterIndex] = useState(0);
28
- const [sortIndex, setSortIndex] = useState(0);
29
- const [groupIndex, setGroupIndex] = useState(0);
30
- const [tabIndex, setTabIndex] = useState(0);
31
- const [showHelp, setShowHelp] = useState(false);
32
- const [searchMode, setSearchMode] = useState(false);
33
- const [search, setSearch] = useState("");
34
- const [selectedIndices, setSelectedIndices] = useState(new Set(items.flatMap((item, index) => item.update.selectedByDefault === false ? [] : [index])));
35
- const activeFilter = FILTER_ORDER[filterIndex] ?? "all";
36
- const activeSort = SORT_ORDER[sortIndex] ?? "risk";
37
- const activeGroup = GROUP_ORDER[groupIndex] ?? "none";
38
- const activeTab = DETAIL_TABS[tabIndex] ?? "overview";
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, title, subtitle, 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
- setSearchMode(false);
53
- setSearch("");
54
- setCursorIndex(0);
118
+ dispatch({ type: "SET_SEARCH_MODE", active: false });
55
119
  return;
56
120
  }
57
121
  if (key.return) {
58
- setSearchMode(false);
59
- setCursorIndex(0);
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
- setSearch((value) => value.slice(0, -1));
64
- setCursorIndex(0);
127
+ dispatch({ type: "BACKSPACE_SEARCH" });
65
128
  return;
66
129
  }
67
130
  if (input && !key.ctrl && !key.meta) {
68
- setSearch((value) => value + input);
69
- setCursorIndex(0);
131
+ dispatch({ type: "APPEND_SEARCH", value: input });
70
132
  }
71
133
  return;
72
134
  }
73
135
  if (input === "/") {
74
- setSearchMode(true);
136
+ dispatch({ type: "SET_SEARCH_MODE", active: true });
75
137
  return;
76
138
  }
77
139
  if (input === "?") {
78
- setShowHelp((value) => !value);
140
+ dispatch({ type: "TOGGLE_HELP" });
79
141
  return;
80
142
  }
81
143
  if (key.escape && showHelp) {
82
- setShowHelp(false);
144
+ dispatch({ type: "SET_HELP", active: false });
83
145
  return;
84
146
  }
85
147
  if (key.leftArrow) {
86
- setFilterIndex((prev) => Math.max(0, prev - 1));
87
- setCursorIndex(0);
148
+ dispatch({
149
+ type: "MOVE_FILTER",
150
+ direction: -1,
151
+ max: FILTER_ORDER.length - 1,
152
+ });
88
153
  }
89
154
  if (key.rightArrow) {
90
- setFilterIndex((prev) => Math.min(FILTER_ORDER.length - 1, prev + 1));
91
- setCursorIndex(0);
155
+ dispatch({
156
+ type: "MOVE_FILTER",
157
+ direction: 1,
158
+ max: FILTER_ORDER.length - 1,
159
+ });
92
160
  }
93
161
  if (key.upArrow) {
94
- setCursorIndex((prev) => Math.max(0, prev - 1));
162
+ dispatch({
163
+ type: "MOVE_CURSOR",
164
+ direction: -1,
165
+ max: itemRows.length - 1,
166
+ });
95
167
  }
96
168
  if (key.downArrow) {
97
- setCursorIndex((prev) => Math.min(itemRows.length - 1, Math.max(0, prev + 1)));
169
+ dispatch({ type: "MOVE_CURSOR", direction: 1, max: itemRows.length - 1 });
98
170
  }
99
171
  if (input === "o") {
100
- setSortIndex((prev) => (prev + 1) % SORT_ORDER.length);
101
- setCursorIndex(0);
172
+ dispatch({ type: "CYCLE_SORT", max: SORT_ORDER.length });
102
173
  }
103
174
  if (input === "g") {
104
- setGroupIndex((prev) => (prev + 1) % GROUP_ORDER.length);
105
- setCursorIndex(0);
175
+ dispatch({ type: "CYCLE_GROUP", max: GROUP_ORDER.length });
106
176
  }
107
177
  if (key.tab) {
108
- setTabIndex((prev) => (prev + 1) % DETAIL_TABS.length);
178
+ dispatch({ type: "CYCLE_TAB", max: DETAIL_TABS.length });
109
179
  }
110
180
  if (input === "a") {
111
- setSelectedIndices((prev) => addVisible(prev, itemRows));
181
+ dispatch({
182
+ type: "SET_SELECTED",
183
+ indices: addVisible(selectedIndices, itemRows),
184
+ });
112
185
  }
113
186
  if (input === "n") {
114
- setSelectedIndices((prev) => removeVisible(prev, itemRows));
187
+ dispatch({
188
+ type: "SET_SELECTED",
189
+ indices: removeVisible(selectedIndices, itemRows),
190
+ });
115
191
  }
116
192
  if (input === "s") {
117
- setSelectedIndices((prev) => selectSafe(prev, itemRows, items));
193
+ dispatch({
194
+ type: "SET_SELECTED",
195
+ indices: selectSafe(selectedIndices, itemRows, items),
196
+ });
118
197
  }
119
198
  if (input === "b") {
120
- setSelectedIndices((prev) => clearBlocked(prev, itemRows, items));
199
+ dispatch({
200
+ type: "SET_SELECTED",
201
+ indices: clearBlocked(selectedIndices, itemRows, items),
202
+ });
121
203
  }
122
204
  if (input === " ") {
123
- setSelectedIndices((prev) => {
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)));
@@ -138,7 +213,7 @@ function TuiApp({ items, title, subtitle, onComplete }) {
138
213
  }
139
214
  });
140
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 ??
141
- "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) => {
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) => {
142
217
  if (row.kind === "group") {
143
218
  return (_jsx(Text, { bold: true, color: "gray", children: row.label }, `group:${row.label}`));
144
219
  }
@@ -150,18 +225,18 @@ function TuiApp({ items, title, subtitle, onComplete }) {
150
225
  const isFocused = itemPosition === boundedCursor;
151
226
  const isSelected = selectedIndices.has(index);
152
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}`));
153
- }))] }), _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] }));
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] }));
154
229
  }
155
230
  function renderTab(item, tab) {
156
231
  const update = item.update;
157
232
  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 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." }))] }));
159
234
  }
160
235
  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, " ", 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." }))] }));
162
237
  }
163
238
  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, " 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." }))] }));
165
240
  }
166
241
  if (tab === "license") {
167
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"] })] }));
@@ -170,9 +245,10 @@ function renderTab(item, tab) {
170
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"] })] }));
171
246
  }
172
247
  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 ?? "Run review with changelog support or inspect the repository manually." })] }));
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." })] }));
174
250
  }
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."] })] }));
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."] })] }));
176
252
  }
177
253
  function buildVisibleRows(items, config) {
178
254
  const filtered = items
@@ -181,7 +257,11 @@ function buildVisibleRows(items, config) {
181
257
  .filter(({ item }) => matchesSearch(item, config.search))
182
258
  .sort((left, right) => compareItems(left.item, right.item, config.sort));
183
259
  if (config.group === "none") {
184
- return filtered.map(({ item, index }) => ({ kind: "item", label: item.update.name, index }));
260
+ return filtered.map(({ item, index }) => ({
261
+ kind: "item",
262
+ label: item.update.name,
263
+ index,
264
+ }));
185
265
  }
186
266
  const rows = [];
187
267
  let currentGroup = "";
@@ -191,7 +271,11 @@ function buildVisibleRows(items, config) {
191
271
  currentGroup = nextGroup;
192
272
  rows.push({ kind: "group", label: nextGroup });
193
273
  }
194
- rows.push({ kind: "item", label: entry.item.update.name, index: entry.index });
274
+ rows.push({
275
+ kind: "item",
276
+ label: entry.item.update.name,
277
+ index: entry.index,
278
+ });
195
279
  }
196
280
  return rows;
197
281
  }
@@ -199,7 +283,7 @@ function matchesFilter(item, filter) {
199
283
  if (filter === "security")
200
284
  return item.advisories.length > 0;
201
285
  if (filter === "risky")
202
- return item.update.riskLevel === "critical" || item.update.riskLevel === "high";
286
+ return (item.update.riskLevel === "critical" || item.update.riskLevel === "high");
203
287
  if (filter === "major")
204
288
  return item.update.diffType === "major";
205
289
  if (filter === "peer-conflict")
@@ -275,7 +359,8 @@ function removeVisible(selected, rows) {
275
359
  function selectSafe(selected, rows, items) {
276
360
  const next = new Set(selected);
277
361
  for (const row of rows) {
278
- if ((items[row.index]?.update.decisionState ?? deriveDecision(items[row.index])) === "safe") {
362
+ if ((items[row.index]?.update.decisionState ??
363
+ deriveDecision(items[row.index])) === "safe") {
279
364
  next.add(row.index);
280
365
  }
281
366
  }
@@ -284,17 +369,20 @@ function selectSafe(selected, rows, items) {
284
369
  function clearBlocked(selected, rows, items) {
285
370
  const next = new Set(selected);
286
371
  for (const row of rows) {
287
- if ((items[row.index]?.update.decisionState ?? deriveDecision(items[row.index])) === "blocked") {
372
+ if ((items[row.index]?.update.decisionState ??
373
+ deriveDecision(items[row.index])) === "blocked") {
288
374
  next.delete(row.index);
289
375
  }
290
376
  }
291
377
  return next;
292
378
  }
293
379
  function deriveDecision(item) {
294
- if (item.update.peerConflictSeverity === "error" || item.update.licenseStatus === "denied") {
380
+ if (item.update.peerConflictSeverity === "error" ||
381
+ item.update.licenseStatus === "denied") {
295
382
  return "blocked";
296
383
  }
297
- if ((item.update.advisoryCount ?? 0) > 0 || item.update.riskLevel === "critical") {
384
+ if ((item.update.advisoryCount ?? 0) > 0 ||
385
+ item.update.riskLevel === "critical") {
298
386
  return "actionable";
299
387
  }
300
388
  if (item.update.riskLevel === "high" || item.update.diffType === "major") {
@@ -1 +1,7 @@
1
- export declare function discoverPackageDirs(cwd: string, workspaceMode: boolean): Promise<string[]>;
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[]>;
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { scopePackageDirsByGit } from "../git/scope.js";
2
3
  const HARD_IGNORE_DIRS = new Set([
3
4
  "node_modules",
4
5
  ".git",
@@ -8,9 +9,13 @@ const HARD_IGNORE_DIRS = new Set([
8
9
  "coverage",
9
10
  ]);
10
11
  const MAX_DISCOVERED_DIRS = 20000;
11
- export async function discoverPackageDirs(cwd, workspaceMode) {
12
+ export async function discoverPackageDirs(cwd, workspaceMode, options = {}) {
12
13
  if (!workspaceMode) {
13
- return [cwd];
14
+ const scoped = await scopePackageDirsByGit(cwd, [cwd], options.git ?? {}, {
15
+ includeKinds: options.includeKinds,
16
+ includeDependents: options.includeDependents,
17
+ });
18
+ return scoped.packageDirs;
14
19
  }
15
20
  const roots = new Set([cwd]);
16
21
  const patterns = [
@@ -40,7 +45,11 @@ export async function discoverPackageDirs(cwd, workspaceMode) {
40
45
  existing.push(dir);
41
46
  }
42
47
  }
43
- return existing.sort();
48
+ const scoped = await scopePackageDirsByGit(cwd, existing.sort(), options.git ?? {}, {
49
+ includeKinds: options.includeKinds,
50
+ includeDependents: options.includeDependents,
51
+ });
52
+ return scoped.packageDirs;
44
53
  }
45
54
  async function packageFileExists(packageJsonPath) {
46
55
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainy-updates/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
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,
@@ -30,6 +30,7 @@
30
30
  "renovate"
31
31
  ],
32
32
  "bin": {
33
+ "cli": "./dist/bin/cli.js",
33
34
  "rainy-updates": "./dist/bin/cli.js",
34
35
  "rainy-up": "./dist/bin/cli.js",
35
36
  "rup": "./dist/bin/cli.js"
@@ -51,10 +52,13 @@
51
52
  ],
52
53
  "scripts": {
53
54
  "clean": "rm -rf dist",
54
- "build": "tsc -p tsconfig.build.json",
55
- "build:exe": "bun build ./src/bin/cli.ts --compile --outfile dist/rup",
56
- "typecheck": "tsc --noEmit",
57
- "test": "bun test",
55
+ "version:sync": "bun scripts/sync-version.mjs",
56
+ "version:check": "bun scripts/sync-version.mjs --check",
57
+ "version": "bun run version:sync",
58
+ "build": "bun run version:sync && tsc -p tsconfig.build.json",
59
+ "build:exe": "bun run version:sync && bun build ./src/bin/cli.ts --compile --outfile dist/rup",
60
+ "typecheck": "bun run version:check && tsc --noEmit",
61
+ "test": "bun run version:check && bun test",
58
62
  "lint": "bunx biome check src tests",
59
63
  "check": "bun run typecheck && bun test",
60
64
  "bench:fixtures": "bun run scripts/generate-benchmark-fixtures.mjs",
@@ -86,6 +90,7 @@
86
90
  },
87
91
  "dependencies": {
88
92
  "ink": "^6.8.0",
93
+ "oxc-parser": "^0.115.0",
89
94
  "react": "^19.2.4",
90
95
  "zod": "^4.3.6"
91
96
  }