@rainy-updates/cli 0.5.4 → 0.5.6
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 +84 -0
- package/README.md +5 -0
- package/dist/bin/cli.js +37 -1
- package/dist/commands/audit/runner.js +43 -26
- package/dist/commands/dashboard/parser.d.ts +2 -0
- package/dist/commands/dashboard/parser.js +59 -0
- package/dist/commands/dashboard/runner.d.ts +2 -0
- package/dist/commands/dashboard/runner.js +47 -0
- package/dist/commands/doctor/parser.js +6 -0
- package/dist/commands/ga/parser.d.ts +2 -0
- package/dist/commands/ga/parser.js +50 -0
- package/dist/commands/ga/runner.d.ts +2 -0
- package/dist/commands/ga/runner.js +129 -0
- package/dist/commands/resolve/runner.js +7 -3
- package/dist/commands/review/parser.js +6 -0
- package/dist/commands/review/runner.js +4 -3
- package/dist/core/analysis-bundle.d.ts +4 -0
- package/dist/core/analysis-bundle.js +241 -0
- package/dist/core/artifacts.d.ts +3 -0
- package/dist/core/artifacts.js +48 -0
- package/dist/core/check.js +6 -1
- package/dist/core/options.d.ts +7 -1
- package/dist/core/options.js +14 -0
- package/dist/core/review-model.js +51 -177
- package/dist/core/summary.js +13 -0
- package/dist/output/format.js +15 -0
- package/dist/output/github.js +8 -0
- package/dist/output/sarif.js +12 -0
- package/dist/types/index.d.ts +92 -0
- package/dist/ui/dashboard/DashboardTUI.d.ts +6 -0
- package/dist/ui/dashboard/DashboardTUI.js +34 -0
- package/dist/ui/dashboard/components/DetailPanel.d.ts +4 -0
- package/dist/ui/dashboard/components/DetailPanel.js +30 -0
- package/dist/ui/dashboard/components/Footer.d.ts +4 -0
- package/dist/ui/dashboard/components/Footer.js +9 -0
- package/dist/ui/dashboard/components/Header.d.ts +4 -0
- package/dist/ui/dashboard/components/Header.js +12 -0
- package/dist/ui/dashboard/components/Sidebar.d.ts +4 -0
- package/dist/ui/dashboard/components/Sidebar.js +23 -0
- package/dist/ui/dashboard/store.d.ts +34 -0
- package/dist/ui/dashboard/store.js +148 -0
- package/dist/ui/tui.d.ts +2 -2
- package/dist/ui/tui.js +310 -79
- package/package.json +1 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { useDashboardStore } from "../store.js";
|
|
5
|
+
// A heavily memoized single row
|
|
6
|
+
const DependencyRow = React.memo(({ update, isActive }) => {
|
|
7
|
+
return (_jsxs(Box, { paddingX: 1, width: "100%", children: [_jsx(Box, { width: 2, children: _jsx(Text, { color: "cyan", children: isActive ? "> " : " " }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: isActive ? "white" : "gray", bold: isActive, children: update.name }) }), _jsx(Box, { width: 15, justifyContent: "flex-end", children: _jsx(Text, { dimColor: true, children: update.fromRange }) }), _jsx(Box, { width: 3, justifyContent: "center", children: _jsx(Text, { dimColor: true, children: "\u2192" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { color: "green", children: update.toRange }) })] }));
|
|
8
|
+
});
|
|
9
|
+
DependencyRow.displayName = "DependencyRow";
|
|
10
|
+
function SidebarComponent() {
|
|
11
|
+
const updates = useDashboardStore((s) => s.updates);
|
|
12
|
+
const selectedIndex = useDashboardStore((s) => s.selectedIndex);
|
|
13
|
+
// Simple windowing: in a real robust TUI we'd calculate terminal height
|
|
14
|
+
// For now we'll just slice the array based on a fixed viewport (e.g., 20 items)
|
|
15
|
+
const windowSize = 20;
|
|
16
|
+
const start = Math.max(0, Math.min(selectedIndex - windowSize / 2, updates.length - windowSize));
|
|
17
|
+
const visibleUpdates = updates.slice(start, start + windowSize);
|
|
18
|
+
return (_jsxs(Box, { width: "50%", flexDirection: "column", borderStyle: "single", borderColor: "gray", height: windowSize + 2, children: [visibleUpdates.map((update, i) => {
|
|
19
|
+
const actualIndex = start + i;
|
|
20
|
+
return (_jsx(DependencyRow, { update: update, index: actualIndex, isActive: actualIndex === selectedIndex }, `${update.name}-${update.toRange}`));
|
|
21
|
+
}), updates.length === 0 && (_jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "No updates found." }) }))] }));
|
|
22
|
+
}
|
|
23
|
+
export const Sidebar = React.memo(SidebarComponent);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DashboardOptions, CheckResult, PackageUpdate } from "../../types/index.js";
|
|
2
|
+
export interface DashboardState {
|
|
3
|
+
selectedIndex: number;
|
|
4
|
+
view: "dependencies" | "security" | "health";
|
|
5
|
+
modal: "none" | "resolving" | "auditing" | "applying";
|
|
6
|
+
updates: PackageUpdate[];
|
|
7
|
+
summary: CheckResult["summary"];
|
|
8
|
+
options: DashboardOptions;
|
|
9
|
+
error?: string;
|
|
10
|
+
shouldApply: boolean;
|
|
11
|
+
}
|
|
12
|
+
type Listener = () => void;
|
|
13
|
+
declare class DashboardStore {
|
|
14
|
+
private state;
|
|
15
|
+
private listeners;
|
|
16
|
+
constructor(initialState: DashboardState);
|
|
17
|
+
getState: () => DashboardState;
|
|
18
|
+
setState: (partial: Partial<DashboardState> | ((state: DashboardState) => Partial<DashboardState>)) => void;
|
|
19
|
+
subscribe: (listener: Listener) => () => boolean;
|
|
20
|
+
private emit;
|
|
21
|
+
}
|
|
22
|
+
export declare function initStore(options: DashboardOptions, initialResult: CheckResult): DashboardStore;
|
|
23
|
+
export declare function useDashboardStore<T>(selector: (state: DashboardState) => T): T;
|
|
24
|
+
export declare const dashboardActions: {
|
|
25
|
+
moveCursorUp: () => void;
|
|
26
|
+
moveCursorDown: () => void;
|
|
27
|
+
setView: (view: DashboardState["view"]) => void;
|
|
28
|
+
setModal: (modal: DashboardState["modal"]) => void;
|
|
29
|
+
setShouldApply: (shouldApply: boolean) => void;
|
|
30
|
+
runResolveAction: () => Promise<void>;
|
|
31
|
+
runAuditAction: () => Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
export declare function getStore(): DashboardStore | null;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import { runResolve } from "../../commands/resolve/runner.js";
|
|
3
|
+
import { runAudit } from "../../commands/audit/runner.js";
|
|
4
|
+
class DashboardStore {
|
|
5
|
+
state;
|
|
6
|
+
listeners = new Set();
|
|
7
|
+
constructor(initialState) {
|
|
8
|
+
this.state = initialState;
|
|
9
|
+
}
|
|
10
|
+
getState = () => this.state;
|
|
11
|
+
setState = (partial) => {
|
|
12
|
+
const changes = typeof partial === "function" ? partial(this.state) : partial;
|
|
13
|
+
this.state = { ...this.state, ...changes };
|
|
14
|
+
this.emit();
|
|
15
|
+
};
|
|
16
|
+
subscribe = (listener) => {
|
|
17
|
+
this.listeners.add(listener);
|
|
18
|
+
return () => this.listeners.delete(listener);
|
|
19
|
+
};
|
|
20
|
+
emit() {
|
|
21
|
+
for (const listener of this.listeners) {
|
|
22
|
+
listener();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Global singleton per run
|
|
27
|
+
let store = null;
|
|
28
|
+
export function initStore(options, initialResult) {
|
|
29
|
+
if (!store) {
|
|
30
|
+
store = new DashboardStore({
|
|
31
|
+
selectedIndex: 0,
|
|
32
|
+
view: options.view ?? "dependencies",
|
|
33
|
+
modal: "none",
|
|
34
|
+
updates: initialResult.updates,
|
|
35
|
+
summary: initialResult.summary,
|
|
36
|
+
options,
|
|
37
|
+
shouldApply: false,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return store;
|
|
41
|
+
}
|
|
42
|
+
// Hook to use the store in components, taking a selector to prevent unnecessary re-renders
|
|
43
|
+
export function useDashboardStore(selector) {
|
|
44
|
+
if (!store)
|
|
45
|
+
throw new Error("Store not initialized");
|
|
46
|
+
// Custom equality check could be added, but returning primitive/stable references from selector works best
|
|
47
|
+
return useSyncExternalStore(store.subscribe, () => selector(store.getState()));
|
|
48
|
+
}
|
|
49
|
+
// Export actions to modify state without re-rendering the caller
|
|
50
|
+
export const dashboardActions = {
|
|
51
|
+
moveCursorUp: () => {
|
|
52
|
+
store?.setState((s) => ({
|
|
53
|
+
selectedIndex: Math.max(0, s.selectedIndex - 1),
|
|
54
|
+
}));
|
|
55
|
+
},
|
|
56
|
+
moveCursorDown: () => {
|
|
57
|
+
store?.setState((s) => ({
|
|
58
|
+
selectedIndex: Math.min(s.updates.length - 1, s.selectedIndex + 1),
|
|
59
|
+
}));
|
|
60
|
+
},
|
|
61
|
+
setView: (view) => {
|
|
62
|
+
store?.setState({ view, selectedIndex: 0 }); // reset cursor on view change
|
|
63
|
+
},
|
|
64
|
+
setModal: (modal) => {
|
|
65
|
+
store?.setState({ modal });
|
|
66
|
+
},
|
|
67
|
+
setShouldApply: (shouldApply) => {
|
|
68
|
+
store?.setState({ shouldApply });
|
|
69
|
+
},
|
|
70
|
+
runResolveAction: async () => {
|
|
71
|
+
if (!store)
|
|
72
|
+
return;
|
|
73
|
+
const s = store.getState();
|
|
74
|
+
store.setState({ modal: "resolving" });
|
|
75
|
+
try {
|
|
76
|
+
const resolveOpts = {
|
|
77
|
+
cwd: s.options.cwd,
|
|
78
|
+
workspace: s.options.workspace,
|
|
79
|
+
afterUpdate: true,
|
|
80
|
+
safe: true,
|
|
81
|
+
concurrency: s.options.concurrency,
|
|
82
|
+
registryTimeoutMs: s.options.registryTimeoutMs,
|
|
83
|
+
cacheTtlSeconds: s.options.cacheTtlSeconds,
|
|
84
|
+
silent: true,
|
|
85
|
+
};
|
|
86
|
+
const result = await runResolve(resolveOpts);
|
|
87
|
+
// Update updates array with the conflict severity
|
|
88
|
+
const updatedUpdates = s.updates.map((update) => {
|
|
89
|
+
const hasError = result.conflicts.some((c) => c.requester === update.name && c.severity === "error");
|
|
90
|
+
const hasWarning = result.conflicts.some((c) => c.requester === update.name && c.severity === "warning");
|
|
91
|
+
const severity = (hasError ? "error" : hasWarning ? "warning" : "none");
|
|
92
|
+
return { ...update, peerConflictSeverity: severity };
|
|
93
|
+
});
|
|
94
|
+
store.setState({ updates: updatedUpdates });
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
store.setState({ error: String(err) });
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
store.setState({ modal: "none" });
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
runAuditAction: async () => {
|
|
104
|
+
if (!store)
|
|
105
|
+
return;
|
|
106
|
+
const s = store.getState();
|
|
107
|
+
store.setState({ modal: "auditing" });
|
|
108
|
+
try {
|
|
109
|
+
const auditOpts = {
|
|
110
|
+
cwd: s.options.cwd,
|
|
111
|
+
workspace: s.options.workspace,
|
|
112
|
+
fix: false,
|
|
113
|
+
dryRun: false,
|
|
114
|
+
commit: false,
|
|
115
|
+
packageManager: "auto",
|
|
116
|
+
reportFormat: "summary",
|
|
117
|
+
sourceMode: "auto",
|
|
118
|
+
concurrency: s.options.concurrency,
|
|
119
|
+
registryTimeoutMs: s.options.registryTimeoutMs,
|
|
120
|
+
silent: true,
|
|
121
|
+
};
|
|
122
|
+
const result = await runAudit(auditOpts);
|
|
123
|
+
// Map advisories back to updates
|
|
124
|
+
const updatedUpdates = s.updates.map((update) => {
|
|
125
|
+
const pkgSummary = result.packages.find((p) => p.packageName === update.name);
|
|
126
|
+
if (pkgSummary) {
|
|
127
|
+
return {
|
|
128
|
+
...update,
|
|
129
|
+
riskLevel: pkgSummary.severity,
|
|
130
|
+
advisoryCount: pkgSummary.advisoryCount,
|
|
131
|
+
toRange: pkgSummary.patchedVersion || update.toRange, // suggest the patch!
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return update;
|
|
135
|
+
});
|
|
136
|
+
store.setState({ updates: updatedUpdates });
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
store.setState({ error: String(err) });
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
store.setState({ modal: "none" });
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
export function getStore() {
|
|
147
|
+
return store;
|
|
148
|
+
}
|
package/dist/ui/tui.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function runTui(
|
|
1
|
+
import type { ReviewItem } from "../types/index.js";
|
|
2
|
+
export declare function runTui(items: ReviewItem[]): Promise<ReviewItem[]>;
|
package/dist/ui/tui.js
CHANGED
|
@@ -6,9 +6,307 @@ const FILTER_ORDER = [
|
|
|
6
6
|
"security",
|
|
7
7
|
"risky",
|
|
8
8
|
"major",
|
|
9
|
+
"peer-conflict",
|
|
10
|
+
"license",
|
|
11
|
+
"unused",
|
|
12
|
+
"blocked",
|
|
9
13
|
];
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
const SORT_ORDER = ["risk", "advisories", "diff", "name", "workspace"];
|
|
15
|
+
const GROUP_ORDER = ["none", "workspace", "scope", "risk", "decision"];
|
|
16
|
+
const DETAIL_TABS = [
|
|
17
|
+
"overview",
|
|
18
|
+
"risk",
|
|
19
|
+
"security",
|
|
20
|
+
"peer",
|
|
21
|
+
"license",
|
|
22
|
+
"health",
|
|
23
|
+
"changelog",
|
|
24
|
+
];
|
|
25
|
+
function TuiApp({ items, 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";
|
|
39
|
+
const visibleRows = buildVisibleRows(items, {
|
|
40
|
+
filter: activeFilter,
|
|
41
|
+
sort: activeSort,
|
|
42
|
+
group: activeGroup,
|
|
43
|
+
search,
|
|
44
|
+
});
|
|
45
|
+
const itemRows = visibleRows.filter((row) => row.kind === "item" && typeof row.index === "number");
|
|
46
|
+
const boundedCursor = Math.min(cursorIndex, Math.max(0, itemRows.length - 1));
|
|
47
|
+
const focusedIndex = itemRows[boundedCursor]?.index ?? 0;
|
|
48
|
+
const focusedItem = items[focusedIndex];
|
|
49
|
+
useInput((input, key) => {
|
|
50
|
+
if (searchMode) {
|
|
51
|
+
if (key.escape) {
|
|
52
|
+
setSearchMode(false);
|
|
53
|
+
setSearch("");
|
|
54
|
+
setCursorIndex(0);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (key.return) {
|
|
58
|
+
setSearchMode(false);
|
|
59
|
+
setCursorIndex(0);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (key.backspace || key.delete) {
|
|
63
|
+
setSearch((value) => value.slice(0, -1));
|
|
64
|
+
setCursorIndex(0);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (input && !key.ctrl && !key.meta) {
|
|
68
|
+
setSearch((value) => value + input);
|
|
69
|
+
setCursorIndex(0);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (input === "/") {
|
|
74
|
+
setSearchMode(true);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (input === "?") {
|
|
78
|
+
setShowHelp((value) => !value);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (key.escape && showHelp) {
|
|
82
|
+
setShowHelp(false);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (key.leftArrow) {
|
|
86
|
+
setFilterIndex((prev) => Math.max(0, prev - 1));
|
|
87
|
+
setCursorIndex(0);
|
|
88
|
+
}
|
|
89
|
+
if (key.rightArrow) {
|
|
90
|
+
setFilterIndex((prev) => Math.min(FILTER_ORDER.length - 1, prev + 1));
|
|
91
|
+
setCursorIndex(0);
|
|
92
|
+
}
|
|
93
|
+
if (key.upArrow) {
|
|
94
|
+
setCursorIndex((prev) => Math.max(0, prev - 1));
|
|
95
|
+
}
|
|
96
|
+
if (key.downArrow) {
|
|
97
|
+
setCursorIndex((prev) => Math.min(itemRows.length - 1, Math.max(0, prev + 1)));
|
|
98
|
+
}
|
|
99
|
+
if (input === "o") {
|
|
100
|
+
setSortIndex((prev) => (prev + 1) % SORT_ORDER.length);
|
|
101
|
+
setCursorIndex(0);
|
|
102
|
+
}
|
|
103
|
+
if (input === "g") {
|
|
104
|
+
setGroupIndex((prev) => (prev + 1) % GROUP_ORDER.length);
|
|
105
|
+
setCursorIndex(0);
|
|
106
|
+
}
|
|
107
|
+
if (key.tab) {
|
|
108
|
+
setTabIndex((prev) => (prev + 1) % DETAIL_TABS.length);
|
|
109
|
+
}
|
|
110
|
+
if (input === "a") {
|
|
111
|
+
setSelectedIndices((prev) => addVisible(prev, itemRows));
|
|
112
|
+
}
|
|
113
|
+
if (input === "n") {
|
|
114
|
+
setSelectedIndices((prev) => removeVisible(prev, itemRows));
|
|
115
|
+
}
|
|
116
|
+
if (input === "s") {
|
|
117
|
+
setSelectedIndices((prev) => selectSafe(prev, itemRows, items));
|
|
118
|
+
}
|
|
119
|
+
if (input === "b") {
|
|
120
|
+
setSelectedIndices((prev) => clearBlocked(prev, itemRows, items));
|
|
121
|
+
}
|
|
122
|
+
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
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (input === "q" || key.escape) {
|
|
133
|
+
onComplete(items.filter((_, index) => selectedIndices.has(index)));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (key.return) {
|
|
137
|
+
onComplete(items.filter((_, index) => selectedIndices.has(index)));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Rainy Review Queue" }), _jsx(Text, { color: "gray", children: "Detect with check, summarize with doctor, decide here in review, then apply with upgrade." }), _jsx(Text, { color: "gray", children: "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
|
+
if (row.kind === "group") {
|
|
142
|
+
return (_jsx(Text, { bold: true, color: "gray", children: row.label }, `group:${row.label}`));
|
|
143
|
+
}
|
|
144
|
+
const index = row.index ?? 0;
|
|
145
|
+
const item = items[index];
|
|
146
|
+
const update = item.update;
|
|
147
|
+
const decision = update.decisionState ?? deriveDecision(item);
|
|
148
|
+
const itemPosition = itemRows.findIndex((candidate) => candidate.index === index);
|
|
149
|
+
const isFocused = itemPosition === boundedCursor;
|
|
150
|
+
const isSelected = selectedIndices.has(index);
|
|
151
|
+
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 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
|
+
}
|
|
154
|
+
function renderTab(item, tab) {
|
|
155
|
+
const update = item.update;
|
|
156
|
+
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." })] }));
|
|
158
|
+
}
|
|
159
|
+
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." })] }));
|
|
161
|
+
}
|
|
162
|
+
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." })] }));
|
|
164
|
+
}
|
|
165
|
+
if (tab === "license") {
|
|
166
|
+
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"] })] }));
|
|
167
|
+
}
|
|
168
|
+
if (tab === "health") {
|
|
169
|
+
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
|
+
}
|
|
171
|
+
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 ?? "Run review with changelog support or inspect the repository manually." })] }));
|
|
173
|
+
}
|
|
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."] })] }));
|
|
175
|
+
}
|
|
176
|
+
function buildVisibleRows(items, config) {
|
|
177
|
+
const filtered = items
|
|
178
|
+
.map((item, index) => ({ item, index }))
|
|
179
|
+
.filter(({ item }) => matchesFilter(item, config.filter))
|
|
180
|
+
.filter(({ item }) => matchesSearch(item, config.search))
|
|
181
|
+
.sort((left, right) => compareItems(left.item, right.item, config.sort));
|
|
182
|
+
if (config.group === "none") {
|
|
183
|
+
return filtered.map(({ item, index }) => ({ kind: "item", label: item.update.name, index }));
|
|
184
|
+
}
|
|
185
|
+
const rows = [];
|
|
186
|
+
let currentGroup = "";
|
|
187
|
+
for (const entry of filtered) {
|
|
188
|
+
const nextGroup = groupLabel(entry.item, config.group);
|
|
189
|
+
if (nextGroup !== currentGroup) {
|
|
190
|
+
currentGroup = nextGroup;
|
|
191
|
+
rows.push({ kind: "group", label: nextGroup });
|
|
192
|
+
}
|
|
193
|
+
rows.push({ kind: "item", label: entry.item.update.name, index: entry.index });
|
|
194
|
+
}
|
|
195
|
+
return rows;
|
|
196
|
+
}
|
|
197
|
+
function matchesFilter(item, filter) {
|
|
198
|
+
if (filter === "security")
|
|
199
|
+
return item.advisories.length > 0;
|
|
200
|
+
if (filter === "risky")
|
|
201
|
+
return item.update.riskLevel === "critical" || item.update.riskLevel === "high";
|
|
202
|
+
if (filter === "major")
|
|
203
|
+
return item.update.diffType === "major";
|
|
204
|
+
if (filter === "peer-conflict")
|
|
205
|
+
return item.peerConflicts.length > 0;
|
|
206
|
+
if (filter === "license")
|
|
207
|
+
return item.update.licenseStatus === "denied";
|
|
208
|
+
if (filter === "unused")
|
|
209
|
+
return item.unusedIssues.length > 0;
|
|
210
|
+
if (filter === "blocked")
|
|
211
|
+
return (item.update.decisionState ?? deriveDecision(item)) === "blocked";
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
function matchesSearch(item, search) {
|
|
215
|
+
if (!search.trim())
|
|
216
|
+
return true;
|
|
217
|
+
const query = search.toLowerCase();
|
|
218
|
+
return (item.update.name.toLowerCase().includes(query) ||
|
|
219
|
+
item.update.packagePath.toLowerCase().includes(query));
|
|
220
|
+
}
|
|
221
|
+
function compareItems(left, right, sort) {
|
|
222
|
+
if (sort === "advisories") {
|
|
223
|
+
const byAdvisories = (right.advisories.length ?? 0) - (left.advisories.length ?? 0);
|
|
224
|
+
if (byAdvisories !== 0)
|
|
225
|
+
return byAdvisories;
|
|
226
|
+
}
|
|
227
|
+
if (sort === "diff") {
|
|
228
|
+
const byDiff = diffWeight(right.update.diffType) - diffWeight(left.update.diffType);
|
|
229
|
+
if (byDiff !== 0)
|
|
230
|
+
return byDiff;
|
|
231
|
+
}
|
|
232
|
+
if (sort === "workspace") {
|
|
233
|
+
const byWorkspace = (left.update.workspaceGroup ?? left.update.packagePath).localeCompare(right.update.workspaceGroup ?? right.update.packagePath);
|
|
234
|
+
if (byWorkspace !== 0)
|
|
235
|
+
return byWorkspace;
|
|
236
|
+
}
|
|
237
|
+
if (sort === "name") {
|
|
238
|
+
const byName = left.update.name.localeCompare(right.update.name);
|
|
239
|
+
if (byName !== 0)
|
|
240
|
+
return byName;
|
|
241
|
+
}
|
|
242
|
+
const byRisk = (right.update.riskScore ?? 0) - (left.update.riskScore ?? 0);
|
|
243
|
+
if (byRisk !== 0)
|
|
244
|
+
return byRisk;
|
|
245
|
+
return left.update.name.localeCompare(right.update.name);
|
|
246
|
+
}
|
|
247
|
+
function groupLabel(item, group) {
|
|
248
|
+
if (group === "workspace")
|
|
249
|
+
return item.update.workspaceGroup ?? "root";
|
|
250
|
+
if (group === "scope") {
|
|
251
|
+
if (item.update.name.startsWith("@")) {
|
|
252
|
+
return item.update.name.split("/")[0] ?? "unscoped";
|
|
253
|
+
}
|
|
254
|
+
return "unscoped";
|
|
255
|
+
}
|
|
256
|
+
if (group === "risk")
|
|
257
|
+
return item.update.riskLevel ?? "low";
|
|
258
|
+
if (group === "decision")
|
|
259
|
+
return item.update.decisionState ?? deriveDecision(item);
|
|
260
|
+
return "all";
|
|
261
|
+
}
|
|
262
|
+
function addVisible(selected, rows) {
|
|
263
|
+
const next = new Set(selected);
|
|
264
|
+
for (const row of rows)
|
|
265
|
+
next.add(row.index);
|
|
266
|
+
return next;
|
|
267
|
+
}
|
|
268
|
+
function removeVisible(selected, rows) {
|
|
269
|
+
const next = new Set(selected);
|
|
270
|
+
for (const row of rows)
|
|
271
|
+
next.delete(row.index);
|
|
272
|
+
return next;
|
|
273
|
+
}
|
|
274
|
+
function selectSafe(selected, rows, items) {
|
|
275
|
+
const next = new Set(selected);
|
|
276
|
+
for (const row of rows) {
|
|
277
|
+
if ((items[row.index]?.update.decisionState ?? deriveDecision(items[row.index])) === "safe") {
|
|
278
|
+
next.add(row.index);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return next;
|
|
282
|
+
}
|
|
283
|
+
function clearBlocked(selected, rows, items) {
|
|
284
|
+
const next = new Set(selected);
|
|
285
|
+
for (const row of rows) {
|
|
286
|
+
if ((items[row.index]?.update.decisionState ?? deriveDecision(items[row.index])) === "blocked") {
|
|
287
|
+
next.delete(row.index);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return next;
|
|
291
|
+
}
|
|
292
|
+
function deriveDecision(item) {
|
|
293
|
+
if (item.update.peerConflictSeverity === "error" || item.update.licenseStatus === "denied") {
|
|
294
|
+
return "blocked";
|
|
295
|
+
}
|
|
296
|
+
if ((item.update.advisoryCount ?? 0) > 0 || item.update.riskLevel === "critical") {
|
|
297
|
+
return "actionable";
|
|
298
|
+
}
|
|
299
|
+
if (item.update.riskLevel === "high" || item.update.diffType === "major") {
|
|
300
|
+
return "review";
|
|
301
|
+
}
|
|
302
|
+
return "safe";
|
|
303
|
+
}
|
|
304
|
+
function policyToDecision(value) {
|
|
305
|
+
if (value === "block")
|
|
306
|
+
return "blocked";
|
|
307
|
+
if (value === "review")
|
|
308
|
+
return "review";
|
|
309
|
+
return "safe";
|
|
12
310
|
}
|
|
13
311
|
function riskColor(level) {
|
|
14
312
|
switch (level) {
|
|
@@ -34,17 +332,14 @@ function diffColor(level) {
|
|
|
34
332
|
return "cyan";
|
|
35
333
|
}
|
|
36
334
|
}
|
|
37
|
-
function
|
|
38
|
-
if (
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return "review";
|
|
46
|
-
}
|
|
47
|
-
return "safe";
|
|
335
|
+
function diffWeight(level) {
|
|
336
|
+
if (level === "major")
|
|
337
|
+
return 4;
|
|
338
|
+
if (level === "minor")
|
|
339
|
+
return 3;
|
|
340
|
+
if (level === "patch")
|
|
341
|
+
return 2;
|
|
342
|
+
return 1;
|
|
48
343
|
}
|
|
49
344
|
function decisionColor(label) {
|
|
50
345
|
switch (label) {
|
|
@@ -58,73 +353,9 @@ function decisionColor(label) {
|
|
|
58
353
|
return "green";
|
|
59
354
|
}
|
|
60
355
|
}
|
|
61
|
-
function
|
|
62
|
-
const [cursorIndex, setCursorIndex] = useState(0);
|
|
63
|
-
const [filterIndex, setFilterIndex] = useState(0);
|
|
64
|
-
const [selectedIndices, setSelectedIndices] = useState(new Set(updates.map((_, index) => index)));
|
|
65
|
-
const activeFilter = FILTER_ORDER[filterIndex] ?? "all";
|
|
66
|
-
const filteredIndices = updates
|
|
67
|
-
.map((update, index) => ({ update, index }))
|
|
68
|
-
.filter(({ update }) => {
|
|
69
|
-
if (activeFilter === "security")
|
|
70
|
-
return (update.advisoryCount ?? 0) > 0;
|
|
71
|
-
if (activeFilter === "risky") {
|
|
72
|
-
return update.riskLevel === "critical" || update.riskLevel === "high";
|
|
73
|
-
}
|
|
74
|
-
if (activeFilter === "major")
|
|
75
|
-
return update.diffType === "major";
|
|
76
|
-
return true;
|
|
77
|
-
})
|
|
78
|
-
.map(({ index }) => index);
|
|
79
|
-
const boundedCursor = Math.min(cursorIndex, Math.max(0, filteredIndices.length - 1));
|
|
80
|
-
const focusedIndex = filteredIndices[boundedCursor] ?? 0;
|
|
81
|
-
const focusedUpdate = updates[focusedIndex];
|
|
82
|
-
useInput((input, key) => {
|
|
83
|
-
if (key.leftArrow) {
|
|
84
|
-
setFilterIndex((prev) => Math.max(0, prev - 1));
|
|
85
|
-
setCursorIndex(0);
|
|
86
|
-
}
|
|
87
|
-
if (key.rightArrow) {
|
|
88
|
-
setFilterIndex((prev) => Math.min(FILTER_ORDER.length - 1, prev + 1));
|
|
89
|
-
setCursorIndex(0);
|
|
90
|
-
}
|
|
91
|
-
if (key.upArrow) {
|
|
92
|
-
setCursorIndex((prev) => Math.max(0, prev - 1));
|
|
93
|
-
}
|
|
94
|
-
if (key.downArrow) {
|
|
95
|
-
setCursorIndex((prev) => Math.min(filteredIndices.length - 1, Math.max(0, prev + 1)));
|
|
96
|
-
}
|
|
97
|
-
if (input === "a") {
|
|
98
|
-
setSelectedIndices(new Set(filteredIndices));
|
|
99
|
-
}
|
|
100
|
-
if (input === "n") {
|
|
101
|
-
setSelectedIndices(new Set());
|
|
102
|
-
}
|
|
103
|
-
if (input === " ") {
|
|
104
|
-
setSelectedIndices((prev) => {
|
|
105
|
-
const next = new Set(prev);
|
|
106
|
-
if (next.has(focusedIndex))
|
|
107
|
-
next.delete(focusedIndex);
|
|
108
|
-
else
|
|
109
|
-
next.add(focusedIndex);
|
|
110
|
-
return next;
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
if (key.return) {
|
|
114
|
-
onComplete(updates.filter((_, index) => selectedIndices.has(index)));
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Rainy Review Queue" }), _jsx(Text, { color: "gray", children: "Detect with check, summarize with doctor, decide here in review, then apply with upgrade." }), _jsx(Text, { color: "gray", children: "Left/Right filter Up/Down move Space toggle A select visible N clear Enter confirm" }), _jsx(Box, { marginTop: 1, children: FILTER_ORDER.map((filter, index) => (_jsx(Box, { marginRight: 2, children: _jsxs(Text, { color: index === filterIndex ? "cyan" : "gray", children: ["[", filter, "]"] }) }, filter))) }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsxs(Box, { width: 72, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Review Queue" }), filteredIndices.length === 0 ? (_jsx(Text, { color: "gray", children: "No review candidates match this filter." })) : (filteredIndices.map((index, visibleIndex) => {
|
|
118
|
-
const update = updates[index];
|
|
119
|
-
const isFocused = visibleIndex === boundedCursor;
|
|
120
|
-
const isSelected = selectedIndices.has(index);
|
|
121
|
-
const decision = decisionLabel(update);
|
|
122
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: isFocused ? "cyan" : "gray", children: [isFocused ? ">" : " ", " ", isSelected ? "[x]" : "[ ]", " "] }), _jsx(Box, { width: 22, children: _jsx(Text, { bold: isFocused, children: update.name }) }), _jsx(Box, { width: 10, children: _jsx(Text, { color: diffColor(update.diffType), children: update.diffType }) }), _jsx(Box, { width: 18, children: _jsx(Text, { color: riskColor(update.riskLevel), children: update.riskLevel ?? update.impactScore?.rank ?? "low" }) }), _jsx(Box, { width: 12, children: _jsx(Text, { color: decisionColor(decision), children: decision }) }), _jsx(Box, { width: 10, children: _jsx(Text, { color: decisionColor(decision), children: typeof update.riskScore === "number" ? update.riskScore : "--" }) }), _jsx(VersionDiff, { from: update.fromRange, to: update.toVersionResolved })] }, `${update.packagePath}:${update.name}`));
|
|
123
|
-
}))] }), _jsxs(Box, { marginLeft: 1, width: 46, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Decision Panel" }), focusedUpdate ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: focusedUpdate.name }), _jsxs(Text, { color: "gray", children: ["package: ", focusedUpdate.packagePath] }), _jsxs(Text, { children: ["state:", " ", _jsx(Text, { color: decisionColor(decisionLabel(focusedUpdate)), children: decisionLabel(focusedUpdate) })] }), _jsxs(Text, { children: ["diff: ", _jsx(Text, { color: diffColor(focusedUpdate.diffType), children: focusedUpdate.diffType })] }), _jsxs(Text, { children: ["risk: ", _jsx(Text, { color: riskColor(focusedUpdate.riskLevel), children: focusedUpdate.riskLevel ?? focusedUpdate.impactScore?.rank ?? "low" })] }), _jsxs(Text, { children: ["risk score: ", focusedUpdate.riskScore ?? 0] }), _jsxs(Text, { children: ["impact score: ", focusedUpdate.impactScore?.score ?? 0] }), _jsxs(Text, { children: ["advisories: ", focusedUpdate.advisoryCount ?? 0] }), _jsxs(Text, { children: ["peer: ", focusedUpdate.peerConflictSeverity ?? "none"] }), _jsxs(Text, { children: ["license: ", focusedUpdate.licenseStatus ?? "allowed"] }), _jsxs(Text, { children: ["health: ", focusedUpdate.healthStatus ?? "healthy"] }), _jsxs(Text, { children: ["action:", " ", _jsx(Text, { color: decisionColor(decisionLabel(focusedUpdate)), children: focusedUpdate.recommendedAction ?? "Safe to keep in the review queue." })] }), focusedUpdate.homepage ? (_jsxs(Text, { color: "blue", children: ["homepage: ", focusedUpdate.homepage] })) : (_jsx(Text, { color: "gray", children: "homepage: unavailable" })), focusedUpdate.riskReasons && focusedUpdate.riskReasons.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Reasons" }), focusedUpdate.riskReasons.slice(0, 4).map((reason) => (_jsxs(Text, { color: "gray", children: ["- ", reason] }, reason)))] })) : (_jsx(Text, { color: "gray", children: "No elevated risk reasons." }))] })) : (_jsx(Text, { color: "gray", children: "No review candidate selected." }))] })] }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsxs(Text, { color: "gray", children: [selectedIndices.size, " selected for apply of ", updates.length, ". Filter: ", activeFilter, ". Enter confirms the review decision set."] }) })] }));
|
|
124
|
-
}
|
|
125
|
-
export async function runTui(updates) {
|
|
356
|
+
export async function runTui(items) {
|
|
126
357
|
return new Promise((resolve) => {
|
|
127
|
-
const { unmount } = render(_jsx(TuiApp, {
|
|
358
|
+
const { unmount } = render(_jsx(TuiApp, { items: items, onComplete: (selected) => {
|
|
128
359
|
unmount();
|
|
129
360
|
resolve(selected);
|
|
130
361
|
} }));
|
package/package.json
CHANGED