@westbayberry/dg 1.0.52 → 1.0.56
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/README.md +5 -1
- package/dist/index.mjs +349 -168
- package/dist/packages/cli/src/alt-screen.js +36 -0
- package/dist/packages/cli/src/api.js +322 -0
- package/dist/packages/cli/src/auth.js +218 -0
- package/dist/packages/cli/src/bin.js +386 -0
- package/dist/packages/cli/src/config.js +228 -0
- package/dist/packages/cli/src/discover.js +126 -0
- package/dist/packages/cli/src/first-run.js +135 -0
- package/dist/packages/cli/src/hook.js +360 -0
- package/dist/packages/cli/src/lockfile.js +303 -0
- package/dist/packages/cli/src/npm-wrapper.js +218 -0
- package/dist/packages/cli/src/pip-wrapper.js +273 -0
- package/dist/packages/cli/src/sanitize.js +38 -0
- package/dist/packages/cli/src/scan-core.js +144 -0
- package/dist/packages/cli/src/setup-status.js +46 -0
- package/dist/packages/cli/src/static-output.js +625 -0
- package/dist/packages/cli/src/telemetry.js +141 -0
- package/dist/packages/cli/src/ui/App.js +137 -0
- package/dist/packages/cli/src/ui/InitApp.js +391 -0
- package/dist/packages/cli/src/ui/LoginApp.js +51 -0
- package/dist/packages/cli/src/ui/NpmWrapperApp.js +73 -0
- package/dist/packages/cli/src/ui/PipWrapperApp.js +72 -0
- package/dist/packages/cli/src/ui/components/ConfirmPrompt.js +24 -0
- package/dist/packages/cli/src/ui/components/DemoScanAnimation.js +26 -0
- package/dist/packages/cli/src/ui/components/DurationLine.js +7 -0
- package/dist/packages/cli/src/ui/components/ErrorView.js +30 -0
- package/dist/packages/cli/src/ui/components/FileSavePrompt.js +210 -0
- package/dist/packages/cli/src/ui/components/InteractiveResultsView.js +557 -0
- package/dist/packages/cli/src/ui/components/Mascot.js +33 -0
- package/dist/packages/cli/src/ui/components/ProgressBar.js +51 -0
- package/dist/packages/cli/src/ui/components/ProgressDots.js +35 -0
- package/dist/packages/cli/src/ui/components/ProjectSelector.js +60 -0
- package/dist/packages/cli/src/ui/components/ResultsView.js +105 -0
- package/dist/packages/cli/src/ui/components/ScanResultCard.js +54 -0
- package/dist/packages/cli/src/ui/components/ScoreHeader.js +142 -0
- package/dist/packages/cli/src/ui/components/SetupBanner.js +17 -0
- package/dist/packages/cli/src/ui/components/Spinner.js +11 -0
- package/dist/packages/cli/src/ui/hooks/useExpandAnimation.js +44 -0
- package/dist/packages/cli/src/ui/hooks/useInit.js +341 -0
- package/dist/packages/cli/src/ui/hooks/useLogin.js +121 -0
- package/dist/packages/cli/src/ui/hooks/useNpmWrapper.js +192 -0
- package/dist/packages/cli/src/ui/hooks/usePipWrapper.js +195 -0
- package/dist/packages/cli/src/ui/hooks/useScan.js +202 -0
- package/dist/packages/cli/src/ui/hooks/useTerminalSize.js +29 -0
- package/dist/packages/cli/src/update-check.js +152 -0
- package/dist/packages/cli/src/wizard-demo-data.js +63 -0
- package/dist/src/ecosystem.js +2 -0
- package/dist/src/lockfile/diff.js +38 -0
- package/dist/src/lockfile/parse_package_json.js +41 -0
- package/dist/src/lockfile/parse_package_lock.js +55 -0
- package/dist/src/lockfile/parse_pipfile_lock.js +69 -0
- package/dist/src/lockfile/parse_pnpm_lock.js +62 -0
- package/dist/src/lockfile/parse_poetry_lock.js +71 -0
- package/dist/src/lockfile/parse_requirements.js +83 -0
- package/dist/src/lockfile/parse_yarn_lock.js +66 -0
- package/dist/src/logger.js +21 -0
- package/dist/src/npm/h2pool.js +161 -0
- package/dist/src/npm/registry.js +299 -0
- package/dist/src/npm/tarball.js +274 -0
- package/dist/src/pypi/registry.js +299 -0
- package/dist/src/pypi/tarball.js +361 -0
- package/dist/src/types.js +2 -0
- package/package.json +6 -3
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.InteractiveResultsView = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
const ink_1 = require("ink");
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const ScoreHeader_1 = require("./ScoreHeader");
|
|
12
|
+
const useExpandAnimation_1 = require("../hooks/useExpandAnimation");
|
|
13
|
+
const useTerminalSize_1 = require("../hooks/useTerminalSize");
|
|
14
|
+
function groupPackages(packages) {
|
|
15
|
+
const map = new Map();
|
|
16
|
+
for (const pkg of packages) {
|
|
17
|
+
const fingerprint = pkg.findings.length === 0
|
|
18
|
+
? `__clean_${pkg.score}`
|
|
19
|
+
: pkg.findings
|
|
20
|
+
.map((f) => `${f.category ?? ""}:${f.severity}`)
|
|
21
|
+
.sort()
|
|
22
|
+
.join("|") + `|score:${pkg.score}`;
|
|
23
|
+
const group = map.get(fingerprint) ?? [];
|
|
24
|
+
group.push(pkg);
|
|
25
|
+
map.set(fingerprint, group);
|
|
26
|
+
}
|
|
27
|
+
return [...map.entries()]
|
|
28
|
+
.map(([fingerprint, pkgs]) => ({ packages: pkgs, key: fingerprint }))
|
|
29
|
+
.sort((a, b) => b.packages[0].score - a.packages[0].score);
|
|
30
|
+
}
|
|
31
|
+
const SEVERITY_LABELS = {
|
|
32
|
+
5: "CRIT",
|
|
33
|
+
4: "HIGH",
|
|
34
|
+
3: "MED",
|
|
35
|
+
2: "LOW",
|
|
36
|
+
1: "INFO",
|
|
37
|
+
};
|
|
38
|
+
const SEVERITY_COLORS = {
|
|
39
|
+
5: (s) => chalk_1.default.red.bold(s),
|
|
40
|
+
4: (s) => chalk_1.default.red(s),
|
|
41
|
+
3: (s) => chalk_1.default.yellow(s),
|
|
42
|
+
2: (s) => chalk_1.default.cyan(s),
|
|
43
|
+
1: (s) => chalk_1.default.gray(s),
|
|
44
|
+
};
|
|
45
|
+
function actionBadge(score) {
|
|
46
|
+
if (score >= 70)
|
|
47
|
+
return { label: "Block", color: chalk_1.default.red };
|
|
48
|
+
if (score >= 60)
|
|
49
|
+
return { label: "Warn", color: chalk_1.default.yellow };
|
|
50
|
+
return { label: "Pass", color: chalk_1.default.green };
|
|
51
|
+
}
|
|
52
|
+
function truncate(s, max) {
|
|
53
|
+
return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
|
|
54
|
+
}
|
|
55
|
+
function pad(s, len) {
|
|
56
|
+
return s + " ".repeat(Math.max(0, len - s.length));
|
|
57
|
+
}
|
|
58
|
+
const EVIDENCE_LIMIT = 2;
|
|
59
|
+
// Fixed lines outside the scrollable group area:
|
|
60
|
+
// 5 ScoreHeader box | 2 Flagged box top | 2 scroll indicators
|
|
61
|
+
// 1 Flagged box bottom | 4 Clean/Duration box | 1 help bar | 1 margin
|
|
62
|
+
const FIXED_CHROME = 21;
|
|
63
|
+
function findingsSummaryHeight(group) {
|
|
64
|
+
const rep = group.packages[0];
|
|
65
|
+
const visibleFindings = rep.findings.filter((f) => f.severity > 1);
|
|
66
|
+
const isFree = visibleFindings.length > 0 && !visibleFindings[0].title;
|
|
67
|
+
let h = 0;
|
|
68
|
+
if (rep.license)
|
|
69
|
+
h += 1; // license info line
|
|
70
|
+
if (isFree) {
|
|
71
|
+
// Free tier: just the upgrade prompt line
|
|
72
|
+
h += 1;
|
|
73
|
+
}
|
|
74
|
+
else if (visibleFindings.length > 0) {
|
|
75
|
+
// Paid tier: one line per finding
|
|
76
|
+
h += visibleFindings.length;
|
|
77
|
+
}
|
|
78
|
+
else if (rep.score > 0) {
|
|
79
|
+
h += 1; // score-only fallback
|
|
80
|
+
}
|
|
81
|
+
if (group.packages.length > 3)
|
|
82
|
+
h += 1;
|
|
83
|
+
return h;
|
|
84
|
+
}
|
|
85
|
+
function findingsDetailHeight(group, safeVersions) {
|
|
86
|
+
const rep = group.packages[0];
|
|
87
|
+
const visibleFindings = rep.findings
|
|
88
|
+
.filter((f) => f.severity > 1)
|
|
89
|
+
.sort((a, b) => b.severity - a.severity);
|
|
90
|
+
let h = 0;
|
|
91
|
+
if (rep.license)
|
|
92
|
+
h += 1; // license info line
|
|
93
|
+
if (group.packages.length > 3)
|
|
94
|
+
h += 1;
|
|
95
|
+
// Free tier: reasons + upgrade hint
|
|
96
|
+
if (visibleFindings.length === 0 && rep.score > 0) {
|
|
97
|
+
h += (rep.reasons ?? []).length;
|
|
98
|
+
h += 1; // upgrade hint
|
|
99
|
+
}
|
|
100
|
+
const hasEvidence = visibleFindings.some((f) => f.evidence && f.evidence.length > 0);
|
|
101
|
+
for (const finding of visibleFindings) {
|
|
102
|
+
h += 1; // badge + id
|
|
103
|
+
h += 1; // title
|
|
104
|
+
const evidence = finding.evidence ?? [];
|
|
105
|
+
h += Math.min(evidence.length, EVIDENCE_LIMIT);
|
|
106
|
+
if (evidence.length > EVIDENCE_LIMIT)
|
|
107
|
+
h += 1;
|
|
108
|
+
}
|
|
109
|
+
// Upgrade hint for pro tier (findings but no evidence)
|
|
110
|
+
if (visibleFindings.length > 0 && !hasEvidence)
|
|
111
|
+
h += 1;
|
|
112
|
+
if (rep.recommendation)
|
|
113
|
+
h += 1;
|
|
114
|
+
if (safeVersions[rep.name])
|
|
115
|
+
h += 1;
|
|
116
|
+
return h;
|
|
117
|
+
}
|
|
118
|
+
function groupRowHeight(group, level, safeVersions) {
|
|
119
|
+
if (level === null)
|
|
120
|
+
return 1;
|
|
121
|
+
if (level === "summary")
|
|
122
|
+
return 1 + findingsSummaryHeight(group);
|
|
123
|
+
return 1 + findingsDetailHeight(group, safeVersions);
|
|
124
|
+
}
|
|
125
|
+
function groupNames(group) {
|
|
126
|
+
if (group.packages.length === 1)
|
|
127
|
+
return group.packages[0].name;
|
|
128
|
+
if (group.packages.length <= 3)
|
|
129
|
+
return group.packages.map((p) => p.name).join(", ");
|
|
130
|
+
return `${group.packages[0].name} + ${group.packages.length - 1} similar`;
|
|
131
|
+
}
|
|
132
|
+
function affectsLine(group) {
|
|
133
|
+
const names = group.packages.map((p) => p.name);
|
|
134
|
+
if (names.length <= 5)
|
|
135
|
+
return names.join(", ");
|
|
136
|
+
return names.slice(0, 5).join(", ") + ` + ${names.length - 5} more`;
|
|
137
|
+
}
|
|
138
|
+
// Chrome lines in detail pane mode:
|
|
139
|
+
// 7 ScoreHeader | 3 detail pane borders+header | 2 scroll indicators
|
|
140
|
+
// 4 Clean/Duration box | 1 separator | 1 help bar
|
|
141
|
+
const DETAIL_PANE_CHROME = 20;
|
|
142
|
+
function buildDetailLines(group, safeVersion, maxWidth) {
|
|
143
|
+
const rep = group.packages[0];
|
|
144
|
+
const visibleFindings = rep.findings
|
|
145
|
+
.filter((f) => f.severity > 1)
|
|
146
|
+
.sort((a, b) => b.severity - a.severity);
|
|
147
|
+
const lines = [];
|
|
148
|
+
if (group.packages.length > 3) {
|
|
149
|
+
lines.push((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: ["Affects: ", affectsLine(group)] }, "affects"));
|
|
150
|
+
lines.push((0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }, "affects-gap"));
|
|
151
|
+
}
|
|
152
|
+
if (rep.score > 0) {
|
|
153
|
+
lines.push((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: ["Score: ", rep.score, "/100"] }, "score-info"));
|
|
154
|
+
lines.push((0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }, "score-gap"));
|
|
155
|
+
}
|
|
156
|
+
// Paid tier: show findings with category + severity
|
|
157
|
+
if (visibleFindings.length > 0) {
|
|
158
|
+
for (let i = 0; i < visibleFindings.length; i++) {
|
|
159
|
+
const f = visibleFindings[i];
|
|
160
|
+
const sevLabel = SEVERITY_LABELS[f.severity] ?? "INFO";
|
|
161
|
+
const sevColor = SEVERITY_COLORS[f.severity] ?? SEVERITY_COLORS[1];
|
|
162
|
+
const connector = i === visibleFindings.length - 1 ? T.last : T.branch;
|
|
163
|
+
lines.push((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [connector, " ", sevColor(pad(sevLabel, 8)), chalk_1.default.dim(f.category ?? "")] }, `finding-${i}`));
|
|
164
|
+
}
|
|
165
|
+
lines.push((0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }, "findings-gap"));
|
|
166
|
+
}
|
|
167
|
+
else if (rep.score > 0) {
|
|
168
|
+
// Free tier: no findings returned — show upgrade prompt
|
|
169
|
+
lines.push((0, jsx_runtime_1.jsxs)(ink_1.Text, { color: "yellow", children: [chalk_1.default.yellow("\u2192"), " Upgrade to Pro to see risk categories"] }, "upgrade"));
|
|
170
|
+
lines.push((0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }, "upgrade-gap"));
|
|
171
|
+
}
|
|
172
|
+
if (rep.recommendation) {
|
|
173
|
+
lines.push((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [chalk_1.default.dim("Recommendation:"), " ", chalk_1.default.cyan(truncate(rep.recommendation, maxWidth - 18))] }, "recommendation"));
|
|
174
|
+
}
|
|
175
|
+
if (safeVersion) {
|
|
176
|
+
lines.push((0, jsx_runtime_1.jsx)(ink_1.Text, { children: chalk_1.default.green(`Safe version: ${rep.name}@${safeVersion}`) }, "safe"));
|
|
177
|
+
}
|
|
178
|
+
return lines;
|
|
179
|
+
}
|
|
180
|
+
function viewReducer(_state, action) {
|
|
181
|
+
switch (action.type) {
|
|
182
|
+
case "MOVE":
|
|
183
|
+
return { ..._state, cursor: action.cursor, viewport: action.viewport };
|
|
184
|
+
case "EXPAND":
|
|
185
|
+
return { ..._state, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
|
|
186
|
+
case "MOVE_EXPAND":
|
|
187
|
+
return { cursor: action.cursor, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const InteractiveResultsView = ({ result, config: _config, durationMs, onExit, onBack, discoveredTotal, userStatus, scanUsage: scanUsageProp, }) => {
|
|
191
|
+
// If the scan returned trialScansRemaining (free tier), show it as the
|
|
192
|
+
// scan usage — overrides the generic "free tier" placeholder from bin.ts
|
|
193
|
+
const scanUsage = result.trialScansRemaining !== undefined
|
|
194
|
+
? `${result.trialScansRemaining} scans left`
|
|
195
|
+
: scanUsageProp;
|
|
196
|
+
const flagged = (0, react_1.useMemo)(() => result.packages.filter((p) => p.score > 0), [result.packages]);
|
|
197
|
+
const clean = (0, react_1.useMemo)(() => result.packages.filter((p) => p.score === 0), [result.packages]);
|
|
198
|
+
const total = result.packages.length;
|
|
199
|
+
const [searchQuery, setSearchQuery] = (0, react_1.useState)("");
|
|
200
|
+
const allGroups = (0, react_1.useMemo)(() => groupPackages(flagged), [flagged]);
|
|
201
|
+
const allGroupCount = allGroups.length;
|
|
202
|
+
const groups = (0, react_1.useMemo)(() => {
|
|
203
|
+
if (!searchQuery)
|
|
204
|
+
return allGroups;
|
|
205
|
+
const q = searchQuery.toLowerCase();
|
|
206
|
+
return allGroups.filter(g => g.packages.some(p => p.name.toLowerCase().includes(q)));
|
|
207
|
+
}, [allGroups, searchQuery]);
|
|
208
|
+
const severityCounts = (0, react_1.useMemo)(() => {
|
|
209
|
+
const counts = {};
|
|
210
|
+
for (const pkg of flagged) {
|
|
211
|
+
const maxSev = pkg.findings.reduce((m, f) => Math.max(m, f.severity), 0);
|
|
212
|
+
if (maxSev >= 2)
|
|
213
|
+
counts[maxSev] = (counts[maxSev] ?? 0) + 1;
|
|
214
|
+
}
|
|
215
|
+
return counts;
|
|
216
|
+
}, [flagged]);
|
|
217
|
+
const [view, dispatchView] = (0, react_1.useReducer)(viewReducer, {
|
|
218
|
+
cursor: 0,
|
|
219
|
+
expandLevel: null,
|
|
220
|
+
expandedIndex: null,
|
|
221
|
+
viewport: 0,
|
|
222
|
+
});
|
|
223
|
+
const viewRef = (0, react_1.useRef)(view);
|
|
224
|
+
viewRef.current = view;
|
|
225
|
+
const [detailPane, setDetailPane] = (0, react_1.useState)(null);
|
|
226
|
+
const detailPaneRef = (0, react_1.useRef)(detailPane);
|
|
227
|
+
detailPaneRef.current = detailPane;
|
|
228
|
+
const [showHelp, setShowHelp] = (0, react_1.useState)(false);
|
|
229
|
+
const showHelpRef = (0, react_1.useRef)(showHelp);
|
|
230
|
+
showHelpRef.current = showHelp;
|
|
231
|
+
const [searchMode, setSearchMode] = (0, react_1.useState)(false);
|
|
232
|
+
const searchModeRef = (0, react_1.useRef)(searchMode);
|
|
233
|
+
searchModeRef.current = searchMode;
|
|
234
|
+
const { rows: termRows, cols: termCols } = (0, useTerminalSize_1.useTerminalSize)();
|
|
235
|
+
const availableRows = Math.max(5, termRows - FIXED_CHROME);
|
|
236
|
+
const innerWidth = Math.max(40, termCols - 6);
|
|
237
|
+
const detailGroupIdx = detailPane?.groupIndex ?? -1;
|
|
238
|
+
const detailLines = (0, react_1.useMemo)(() => {
|
|
239
|
+
if (detailGroupIdx < 0)
|
|
240
|
+
return [];
|
|
241
|
+
const group = groups[detailGroupIdx];
|
|
242
|
+
if (!group)
|
|
243
|
+
return [];
|
|
244
|
+
return buildDetailLines(group, result.safeVersions[group.packages[0].name], innerWidth);
|
|
245
|
+
}, [detailGroupIdx, groups, result.safeVersions, innerWidth]);
|
|
246
|
+
const detailContentRows = Math.max(3, termRows - DETAIL_PANE_CHROME);
|
|
247
|
+
const getLevel = (idx) => {
|
|
248
|
+
return view.expandedIndex === idx ? view.expandLevel : null;
|
|
249
|
+
};
|
|
250
|
+
const expandTargetHeight = (0, react_1.useMemo)(() => {
|
|
251
|
+
if (view.expandedIndex === null || view.expandLevel === null)
|
|
252
|
+
return 0;
|
|
253
|
+
const group = groups[view.expandedIndex];
|
|
254
|
+
if (!group)
|
|
255
|
+
return 0;
|
|
256
|
+
if (view.expandLevel === "summary")
|
|
257
|
+
return findingsSummaryHeight(group);
|
|
258
|
+
return findingsDetailHeight(group, result.safeVersions);
|
|
259
|
+
}, [view.expandedIndex, view.expandLevel, groups, result.safeVersions]);
|
|
260
|
+
const { visibleLines: animVisibleLines } = (0, useExpandAnimation_1.useExpandAnimation)(expandTargetHeight, view.expandedIndex !== null);
|
|
261
|
+
const animatedGroupHeight = (group, level, idx) => {
|
|
262
|
+
if (level === null)
|
|
263
|
+
return 1;
|
|
264
|
+
if (idx === view.expandedIndex)
|
|
265
|
+
return 1 + animVisibleLines;
|
|
266
|
+
return groupRowHeight(group, level, result.safeVersions);
|
|
267
|
+
};
|
|
268
|
+
const visibleEnd = (0, react_1.useMemo)(() => {
|
|
269
|
+
let consumed = 0;
|
|
270
|
+
let end = view.viewport;
|
|
271
|
+
while (end < groups.length) {
|
|
272
|
+
const level = getLevel(end);
|
|
273
|
+
const h = animatedGroupHeight(groups[end], level, end);
|
|
274
|
+
if (consumed + h > availableRows)
|
|
275
|
+
break;
|
|
276
|
+
consumed += h;
|
|
277
|
+
end++;
|
|
278
|
+
}
|
|
279
|
+
if (end === view.viewport && groups.length > 0)
|
|
280
|
+
end = view.viewport + 1;
|
|
281
|
+
return end;
|
|
282
|
+
}, [view.viewport, groups, view.expandedIndex, view.expandLevel, animVisibleLines, availableRows, result.safeVersions]);
|
|
283
|
+
const adjustViewport = (cursor, expIdx, expLvl, currentStart) => {
|
|
284
|
+
if (cursor < currentStart)
|
|
285
|
+
return cursor;
|
|
286
|
+
const getLvl = (i) => (expIdx === i ? expLvl : null);
|
|
287
|
+
let consumed = 0;
|
|
288
|
+
for (let i = currentStart; i <= cursor && i < groups.length; i++) {
|
|
289
|
+
consumed += groupRowHeight(groups[i], getLvl(i), result.safeVersions);
|
|
290
|
+
}
|
|
291
|
+
if (consumed <= availableRows)
|
|
292
|
+
return currentStart;
|
|
293
|
+
let newStart = currentStart;
|
|
294
|
+
while (newStart < cursor) {
|
|
295
|
+
newStart++;
|
|
296
|
+
consumed = 0;
|
|
297
|
+
for (let i = newStart; i <= cursor; i++) {
|
|
298
|
+
consumed += groupRowHeight(groups[i], getLvl(i), result.safeVersions);
|
|
299
|
+
}
|
|
300
|
+
if (consumed <= availableRows)
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
return newStart;
|
|
304
|
+
};
|
|
305
|
+
// Re-clamp viewport when terminal is resized
|
|
306
|
+
(0, react_1.useEffect)(() => {
|
|
307
|
+
if (groups.length === 0)
|
|
308
|
+
return;
|
|
309
|
+
const { cursor, expandedIndex, expandLevel, viewport } = viewRef.current;
|
|
310
|
+
const clamped = Math.min(viewport, Math.max(0, groups.length - 1));
|
|
311
|
+
const newVp = adjustViewport(cursor, expandedIndex, expandLevel, clamped);
|
|
312
|
+
dispatchView({ type: "MOVE", cursor, viewport: newVp });
|
|
313
|
+
}, [availableRows]);
|
|
314
|
+
// Clamp detail pane scroll when terminal resizes
|
|
315
|
+
(0, react_1.useEffect)(() => {
|
|
316
|
+
const dp = detailPaneRef.current;
|
|
317
|
+
if (dp && detailLines.length > 0) {
|
|
318
|
+
const maxScroll = Math.max(0, detailLines.length - detailContentRows);
|
|
319
|
+
if (dp.scroll > maxScroll) {
|
|
320
|
+
setDetailPane({ groupIndex: dp.groupIndex, scroll: maxScroll });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}, [detailContentRows, detailLines.length]);
|
|
324
|
+
(0, ink_1.useInput)((input, key) => {
|
|
325
|
+
if (groups.length === 0) {
|
|
326
|
+
if (input === "q" || key.return)
|
|
327
|
+
onExit();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
// Help overlay
|
|
331
|
+
if (showHelpRef.current) {
|
|
332
|
+
if (input === "?" || key.escape)
|
|
333
|
+
setShowHelp(false);
|
|
334
|
+
else if (input === "q")
|
|
335
|
+
onExit();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (input === "?") {
|
|
339
|
+
setShowHelp(true);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
// Search mode — capture text input
|
|
343
|
+
if (searchModeRef.current) {
|
|
344
|
+
if (key.escape) {
|
|
345
|
+
setSearchMode(false);
|
|
346
|
+
setSearchQuery("");
|
|
347
|
+
dispatchView({ type: "MOVE", cursor: 0, viewport: 0 });
|
|
348
|
+
}
|
|
349
|
+
else if (key.return) {
|
|
350
|
+
setSearchMode(false);
|
|
351
|
+
}
|
|
352
|
+
else if (key.backspace || key.delete) {
|
|
353
|
+
setSearchQuery(prev => prev.slice(0, -1));
|
|
354
|
+
dispatchView({ type: "MOVE", cursor: 0, viewport: 0 });
|
|
355
|
+
}
|
|
356
|
+
else if (input && !key.upArrow && !key.downArrow && /^[\x20-\x7e]+$/.test(input)) {
|
|
357
|
+
setSearchQuery(prev => prev + input);
|
|
358
|
+
dispatchView({ type: "MOVE", cursor: 0, viewport: 0 });
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// Detail pane mode — scroll within the pane
|
|
363
|
+
const dp = detailPaneRef.current;
|
|
364
|
+
if (dp !== null) {
|
|
365
|
+
const maxScroll = Math.max(0, detailLines.length - detailContentRows);
|
|
366
|
+
if (key.upArrow || input === "k") {
|
|
367
|
+
setDetailPane({ groupIndex: dp.groupIndex, scroll: Math.max(0, dp.scroll - 1) });
|
|
368
|
+
}
|
|
369
|
+
else if (key.downArrow || input === "j") {
|
|
370
|
+
setDetailPane({ groupIndex: dp.groupIndex, scroll: Math.min(maxScroll, dp.scroll + 1) });
|
|
371
|
+
}
|
|
372
|
+
else if (input === "g") {
|
|
373
|
+
setDetailPane({ groupIndex: dp.groupIndex, scroll: 0 });
|
|
374
|
+
}
|
|
375
|
+
else if (input === "G") {
|
|
376
|
+
setDetailPane({ groupIndex: dp.groupIndex, scroll: maxScroll });
|
|
377
|
+
}
|
|
378
|
+
else if (input === "b" || key.escape) {
|
|
379
|
+
setDetailPane(null);
|
|
380
|
+
}
|
|
381
|
+
else if (input === "q") {
|
|
382
|
+
onExit();
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
// List mode
|
|
387
|
+
if (groups.length === 0) {
|
|
388
|
+
if (input === "q")
|
|
389
|
+
onExit();
|
|
390
|
+
else if (input === "/")
|
|
391
|
+
setSearchMode(true);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const { cursor, expandLevel: expLvl, expandedIndex: expIdx, viewport: vpStart } = viewRef.current;
|
|
395
|
+
if (key.upArrow || input === "k") {
|
|
396
|
+
const next = Math.max(0, cursor - 1);
|
|
397
|
+
const newVp = adjustViewport(next, expIdx, expLvl, vpStart < next ? vpStart : next);
|
|
398
|
+
dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
|
|
399
|
+
}
|
|
400
|
+
else if (key.downArrow || input === "j") {
|
|
401
|
+
const next = Math.min(groups.length - 1, cursor + 1);
|
|
402
|
+
const newVp = adjustViewport(next, expIdx, expLvl, vpStart);
|
|
403
|
+
dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
|
|
404
|
+
}
|
|
405
|
+
else if (input === "g") {
|
|
406
|
+
const newVp = adjustViewport(0, expIdx, expLvl, 0);
|
|
407
|
+
dispatchView({ type: "MOVE", cursor: 0, viewport: newVp });
|
|
408
|
+
}
|
|
409
|
+
else if (input === "G") {
|
|
410
|
+
const last = groups.length - 1;
|
|
411
|
+
const newVp = adjustViewport(last, expIdx, expLvl, vpStart);
|
|
412
|
+
dispatchView({ type: "MOVE", cursor: last, viewport: newVp });
|
|
413
|
+
}
|
|
414
|
+
else if (key.pageDown) {
|
|
415
|
+
const next = Math.min(groups.length - 1, cursor + availableRows);
|
|
416
|
+
const newVp = adjustViewport(next, expIdx, expLvl, vpStart);
|
|
417
|
+
dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
|
|
418
|
+
}
|
|
419
|
+
else if (key.pageUp) {
|
|
420
|
+
const next = Math.max(0, cursor - availableRows);
|
|
421
|
+
const newVp = adjustViewport(next, expIdx, expLvl, next);
|
|
422
|
+
dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
|
|
423
|
+
}
|
|
424
|
+
else if (key.return) {
|
|
425
|
+
// Enter: toggle inline expand (findings dropdown under the package row)
|
|
426
|
+
const newLevel = expIdx === cursor && expLvl === "summary" ? null : "summary";
|
|
427
|
+
const newExpIdx = newLevel === null ? null : cursor;
|
|
428
|
+
const newVp = adjustViewport(cursor, newExpIdx, newLevel, vpStart);
|
|
429
|
+
dispatchView({ type: "EXPAND", expandedIndex: newExpIdx, expandLevel: newLevel, viewport: newVp });
|
|
430
|
+
}
|
|
431
|
+
else if (input === "/") {
|
|
432
|
+
setSearchMode(true);
|
|
433
|
+
}
|
|
434
|
+
else if (input === "b" && onBack) {
|
|
435
|
+
onBack();
|
|
436
|
+
}
|
|
437
|
+
else if (input === "q") {
|
|
438
|
+
onExit();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
const visibleGroups = groups.slice(view.viewport, visibleEnd);
|
|
442
|
+
const aboveCount = view.viewport;
|
|
443
|
+
const belowCount = groups.length - visibleEnd;
|
|
444
|
+
const lcCol = 16; // fixed width for license column
|
|
445
|
+
const nameCol = Math.max(20, innerWidth - 22 - lcCol);
|
|
446
|
+
// Clamp cursor to valid range (groups may shrink via search filter)
|
|
447
|
+
const clampedCursor = groups.length > 0 ? Math.min(view.cursor, groups.length - 1) : 0;
|
|
448
|
+
// ── Help overlay ──
|
|
449
|
+
if (showHelp) {
|
|
450
|
+
const isDetail = detailPane !== null;
|
|
451
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ScoreHeader_1.ScoreHeader, { score: result.score, action: result.action, total: total, flagged: flagged.length, clean: clean.length, severityCounts: severityCounts, userStatus: userStatus, scanUsage: scanUsage }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, width: "100%", children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { bold: true, children: [chalk_1.default.cyan("\u25C6"), " Keyboard Shortcuts"] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }), (0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: " Navigation" }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("\u2191 k"), " ", chalk_1.default.dim("Move up")] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("\u2193 j"), " ", chalk_1.default.dim("Move down")] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("g"), " ", chalk_1.default.dim("Jump to top")] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("G"), " ", chalk_1.default.dim("Jump to bottom")] }), !isDetail && (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("PgUp"), " ", chalk_1.default.dim("Page up")] }), !isDetail && (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("PgDn"), " ", chalk_1.default.dim("Page down")] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }), (0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: " Actions" }), !isDetail && (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("\u23CE"), " ", chalk_1.default.dim("Expand findings")] }), isDetail && (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("Esc"), " ", chalk_1.default.dim("Back to list")] }), !isDetail && (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("/"), " ", chalk_1.default.dim("Search packages")] }), !isDetail && onBack && (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("b"), " ", chalk_1.default.dim("Back to project selector")] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.cyan("q"), " ", chalk_1.default.dim("Quit")] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" Press ", chalk_1.default.bold.cyan("?"), " or ", chalk_1.default.bold.cyan("Esc"), " to close"] })] })] }));
|
|
452
|
+
}
|
|
453
|
+
// ── Detail pane mode ──
|
|
454
|
+
if (detailPane !== null) {
|
|
455
|
+
const dpGroup = groups[detailPane.groupIndex];
|
|
456
|
+
if (!dpGroup) {
|
|
457
|
+
setDetailPane(null);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
const dpRep = dpGroup.packages[0];
|
|
461
|
+
const { color: dpColor } = actionBadge(dpRep.score);
|
|
462
|
+
const dpScroll = detailPane.scroll;
|
|
463
|
+
const dpAbove = dpScroll;
|
|
464
|
+
const dpBelow = Math.max(0, detailLines.length - dpScroll - detailContentRows);
|
|
465
|
+
const dpVisible = detailLines.slice(dpScroll, dpScroll + detailContentRows);
|
|
466
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ScoreHeader_1.ScoreHeader, { score: result.score, action: result.action, total: total, flagged: flagged.length, clean: clean.length, severityCounts: severityCounts, userStatus: userStatus, scanUsage: scanUsage }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingLeft: 1, paddingRight: 1, width: "100%", children: [(0, jsx_runtime_1.jsxs)(ink_1.Box, { justifyContent: "space-between", children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { bold: true, children: [groupNames(dpGroup), dpRep.license ? chalk_1.default.dim(" \u00B7 ") + (dpRep.license.riskCategory === "permissive" ? chalk_1.default.green(dpRep.license.spdx ?? dpRep.license.raw ?? "") : dpRep.license.riskCategory === "no-license" || dpRep.license.riskCategory === "network-copyleft" ? chalk_1.default.red(dpRep.license.spdx ?? dpRep.license.raw ?? "No license") : chalk_1.default.yellow(dpRep.license.spdx ?? dpRep.license.raw ?? "")) : ""] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: dpColor(`score ${dpRep.score}`) })] }), dpAbove > 0 && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [chalk_1.default.cyan(" \u2191"), " ", dpAbove, " more above"] })), (0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginLeft: 2, children: dpVisible }), dpBelow > 0 && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [chalk_1.default.cyan(" \u2193"), " ", dpBelow, " more below"] }))] }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingLeft: 1, paddingRight: 1, width: "100%", children: [clean.length > 0 && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [chalk_1.default.green("\u2713"), " ", chalk_1.default.green.bold(String(clean.length)), " ", chalk_1.default.dim(`package${clean.length !== 1 ? "s" : ""} passed with score 0`)] })), (0, jsx_runtime_1.jsxs)(ink_1.Box, { justifyContent: "space-between", children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [(durationMs / 1000).toFixed(1), "s"] }), result.trialScansRemaining !== undefined && ((0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "Free tier \u00B7 dg login for higher scan limits" }))] })] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: chalk_1.default.dim("\u2500".repeat(Math.max(20, termCols - 4))) }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.bold.cyan("\u2191\u2193"), " ", chalk_1.default.dim("scroll"), " ", chalk_1.default.bold.cyan("Esc"), " ", chalk_1.default.dim("back"), " ", chalk_1.default.bold.cyan("q"), " ", chalk_1.default.dim("quit")] })] }));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// ── List mode ──
|
|
470
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ScoreHeader_1.ScoreHeader, { score: result.score, action: result.action, total: total, flagged: flagged.length, clean: clean.length, severityCounts: severityCounts, userStatus: userStatus, scanUsage: scanUsage }), groups.length > 0 && ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingLeft: 1, paddingRight: 1, width: "100%", children: [(0, jsx_runtime_1.jsxs)(ink_1.Box, { justifyContent: "space-between", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Flagged Packages" }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: searchQuery ? `${groups.length} of ${allGroupCount}` : `${clampedCursor + 1}/${groups.length}` })] }), aboveCount > 0 && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [chalk_1.default.cyan(" \u2191"), " ", aboveCount, " more above"] })), visibleGroups.map((group, visIdx) => {
|
|
471
|
+
const globalIdx = view.viewport + visIdx;
|
|
472
|
+
const isCursor = globalIdx === clampedCursor;
|
|
473
|
+
const level = getLevel(globalIdx);
|
|
474
|
+
const rep = group.packages[0];
|
|
475
|
+
const { label, color } = actionBadge(rep.score);
|
|
476
|
+
const names = groupNames(group);
|
|
477
|
+
const scoreStr = String(rep.score);
|
|
478
|
+
const lcInfo = rep.license;
|
|
479
|
+
const lcStr = lcInfo ? truncate(lcInfo.spdx ?? lcInfo.raw ?? "", lcCol - 2) : "";
|
|
480
|
+
const lcColor = !lcInfo ? chalk_1.default.dim
|
|
481
|
+
: lcInfo.riskCategory === "permissive" ? chalk_1.default.green
|
|
482
|
+
: (lcInfo.riskCategory === "no-license" || lcInfo.riskCategory === "unlicensed" || lcInfo.riskCategory === "network-copyleft") ? chalk_1.default.red
|
|
483
|
+
: chalk_1.default.yellow;
|
|
484
|
+
const arrow = level === "summary" ? "\u25BE" : "\u25B8"; // ▾ expanded, ▸ collapsed
|
|
485
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [isCursor ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { backgroundColor: "#1a1a2e", children: [chalk_1.default.cyan("\u258C"), " ", chalk_1.default.cyan(arrow), " ", ` `, color(pad(label, 6)), chalk_1.default.bold(pad(truncate(names, nameCol - 2), nameCol)), lcColor(pad(lcStr, lcCol)), color(scoreStr.padStart(3)), " "] })) : ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [` ${chalk_1.default.dim(arrow)} `, color(pad(label, 6)), pad(truncate(names, nameCol - 2), nameCol), lcColor(pad(lcStr, lcCol)), color(scoreStr.padStart(3))] })), level === "summary" && ((0, jsx_runtime_1.jsx)(FindingsSummary, { group: group, maxWidth: innerWidth - 8, maxLines: globalIdx === view.expandedIndex ? animVisibleLines : undefined }))] }, group.key));
|
|
486
|
+
}), belowCount > 0 && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [chalk_1.default.cyan(" \u2193"), " ", belowCount, " more below"] }))] })), (0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingLeft: 1, paddingRight: 1, width: "100%", children: [clean.length > 0 && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [chalk_1.default.green("\u2713"), " ", chalk_1.default.green.bold(String(clean.length)), " ", chalk_1.default.dim(`package${clean.length !== 1 ? "s" : ""} passed with score 0`)] })), discoveredTotal !== undefined && discoveredTotal > total && ((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: ["Scanned ", total, " of ", discoveredTotal, " packages"] })), (0, jsx_runtime_1.jsxs)(ink_1.Box, { justifyContent: "space-between", children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [(durationMs / 1000).toFixed(1), "s"] }), result.trialScansRemaining !== undefined && ((0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: "Free tier \u00B7 dg login for higher scan limits" }))] })] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: chalk_1.default.dim("\u2500".repeat(Math.max(20, termCols - 4))) }), searchMode ? ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", chalk_1.default.bold.cyan("/"), " ", searchQuery, chalk_1.default.cyan("\u2588"), " ", chalk_1.default.dim("Esc clear")] })) : ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", groups.length > 0 ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [chalk_1.default.bold.cyan("\u2191\u2193"), " ", chalk_1.default.dim("navigate"), " ", chalk_1.default.bold.cyan("\u23CE"), " ", chalk_1.default.dim("expand"), " ", chalk_1.default.bold.cyan("/"), " ", chalk_1.default.dim("search"), " ", onBack && (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [chalk_1.default.bold.cyan("b"), " ", chalk_1.default.dim("back"), " "] }), chalk_1.default.bold.cyan("q"), " ", chalk_1.default.dim("quit")] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["Press ", chalk_1.default.bold.cyan("q"), " or ", chalk_1.default.bold.cyan("Enter"), " ", chalk_1.default.dim("to exit")] }))] }))] }));
|
|
487
|
+
};
|
|
488
|
+
exports.InteractiveResultsView = InteractiveResultsView;
|
|
489
|
+
const T = {
|
|
490
|
+
branch: chalk_1.default.dim("\u251C\u2500\u2500"),
|
|
491
|
+
last: chalk_1.default.dim("\u2514\u2500\u2500"),
|
|
492
|
+
pipe: chalk_1.default.dim("\u2502"),
|
|
493
|
+
blank: " ",
|
|
494
|
+
};
|
|
495
|
+
const LICENSE_DESCRIPTIONS = {
|
|
496
|
+
"permissive": "Permissive \u2014 free to use, modify, and distribute. Include the copyright notice.",
|
|
497
|
+
"weak-copyleft": "Weak copyleft \u2014 changes to this library must be shared, but your code stays private.",
|
|
498
|
+
"strong-copyleft": "Strong copyleft \u2014 your entire project must be open-sourced under the same license.",
|
|
499
|
+
"network-copyleft": "Network copyleft \u2014 even SaaS/server use requires releasing your source code.",
|
|
500
|
+
"no-license": "No license found \u2014 legally all rights reserved. Use may require permission from the author.",
|
|
501
|
+
"unlicensed": "Explicitly unlicensed \u2014 proprietary software. A commercial agreement is required.",
|
|
502
|
+
"unknown": "Unrecognized license \u2014 have your legal team review before using.",
|
|
503
|
+
"deferred": "License declared in a file \u2014 check the LICENSE file in the package.",
|
|
504
|
+
};
|
|
505
|
+
function licenseLine(rep) {
|
|
506
|
+
const lc = rep.license;
|
|
507
|
+
if (!lc)
|
|
508
|
+
return null;
|
|
509
|
+
const spdx = lc.spdx ?? lc.raw ?? "";
|
|
510
|
+
const desc = LICENSE_DESCRIPTIONS[lc.riskCategory] ?? "";
|
|
511
|
+
const lcColor = lc.riskCategory === "permissive" ? chalk_1.default.green
|
|
512
|
+
: (lc.riskCategory === "no-license" || lc.riskCategory === "unlicensed" || lc.riskCategory === "network-copyleft") ? chalk_1.default.red
|
|
513
|
+
: chalk_1.default.yellow;
|
|
514
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [T.branch, " ", lcColor(spdx), " ", chalk_1.default.dim("\u2014"), " ", chalk_1.default.dim(desc)] }, "license-info"));
|
|
515
|
+
}
|
|
516
|
+
const FindingsSummary = ({ group, maxWidth, maxLines }) => {
|
|
517
|
+
const rep = group.packages[0];
|
|
518
|
+
const visibleFindings = rep.findings
|
|
519
|
+
.filter((f) => f.severity > 1)
|
|
520
|
+
.sort((a, b) => b.severity - a.severity);
|
|
521
|
+
const hasAffects = group.packages.length > 3;
|
|
522
|
+
const allLines = [];
|
|
523
|
+
// License info
|
|
524
|
+
const lcLine = licenseLine(rep);
|
|
525
|
+
if (lcLine)
|
|
526
|
+
allLines.push(lcLine);
|
|
527
|
+
// Render findings — API returns tier-gated data:
|
|
528
|
+
// Free: { category, severity } — don't show raw IDs, just upgrade prompt
|
|
529
|
+
// Pro: { category, severity, title } — show category + title
|
|
530
|
+
// Team: { category, severity, title, evidence } — show everything
|
|
531
|
+
const isFree = visibleFindings.length > 0 && !visibleFindings[0].title;
|
|
532
|
+
if (isFree) {
|
|
533
|
+
// Free tier: don't show raw category IDs — just the upgrade prompt
|
|
534
|
+
allLines.push((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [hasAffects ? T.branch : T.last, " ", chalk_1.default.yellow("\u2192"), " ", chalk_1.default.yellow("Upgrade to Pro"), " for finding details"] }, "upgrade"));
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
// Paid tier: show findings with category + title
|
|
538
|
+
for (let idx = 0; idx < visibleFindings.length; idx++) {
|
|
539
|
+
const f = visibleFindings[idx];
|
|
540
|
+
const isLast = !hasAffects && idx === visibleFindings.length - 1;
|
|
541
|
+
const connector = isLast ? T.last : T.branch;
|
|
542
|
+
const sevLabel = SEVERITY_LABELS[f.severity] ?? "INFO";
|
|
543
|
+
const sevColor = SEVERITY_COLORS[f.severity] ?? SEVERITY_COLORS[1];
|
|
544
|
+
const title = f.title ? `: ${f.title}` : "";
|
|
545
|
+
allLines.push((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [connector, " ", sevColor(pad(sevLabel, 5)), " ", chalk_1.default.dim(f.category ?? ""), title] }, `finding-${idx}`));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (visibleFindings.length === 0 && rep.score > 0) {
|
|
549
|
+
// No findings at all (shouldn't happen after API change, but safety fallback)
|
|
550
|
+
allLines.push((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [hasAffects ? T.branch : T.last, " Score: ", rep.score, "/100"] }, "score-only"));
|
|
551
|
+
}
|
|
552
|
+
if (hasAffects) {
|
|
553
|
+
allLines.push((0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [T.last, " ", truncate(affectsLine(group), maxWidth - 8)] }, "affects"));
|
|
554
|
+
}
|
|
555
|
+
const linesToShow = maxLines !== undefined ? allLines.slice(0, maxLines) : allLines;
|
|
556
|
+
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", marginLeft: 5, children: linesToShow }));
|
|
557
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Mascot = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ink_1 = require("ink");
|
|
6
|
+
const FACES = {
|
|
7
|
+
idle: { left: "o", right: "o", mouth: "^" },
|
|
8
|
+
alert: { left: "O", right: "O", mouth: "^" },
|
|
9
|
+
scanning: { left: "-", right: "-", mouth: "^" },
|
|
10
|
+
alarmed: { left: "O", right: "O", mouth: "o" },
|
|
11
|
+
happy: { left: "^", right: "^", mouth: "v" },
|
|
12
|
+
curious: { left: "O", right: "o", mouth: "^" },
|
|
13
|
+
explaining: { left: "o", right: "o", mouth: "~" },
|
|
14
|
+
proud: { left: "^", right: "^", mouth: "^" },
|
|
15
|
+
wink: { left: "-", right: "o", mouth: "^" },
|
|
16
|
+
worried: { left: "o", right: "o", mouth: "_" },
|
|
17
|
+
};
|
|
18
|
+
const Mascot = ({ mood = "idle", color }) => {
|
|
19
|
+
const { left, right, mouth } = FACES[mood];
|
|
20
|
+
// Lines are kept as plain template strings so width is exact and verifiable
|
|
21
|
+
// by eye. Anything that drifts here breaks the alignment of the whole cat.
|
|
22
|
+
const lines = [
|
|
23
|
+
" /\\_____/\\",
|
|
24
|
+
` / ${left} ${right} \\`,
|
|
25
|
+
` ( == ${mouth} == )`,
|
|
26
|
+
" ) (",
|
|
27
|
+
" ( )",
|
|
28
|
+
" ( ( ) ( ) )",
|
|
29
|
+
"(__(__)___(__)__)",
|
|
30
|
+
];
|
|
31
|
+
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: lines.map((line, i) => ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: color, children: line }, i))) }));
|
|
32
|
+
};
|
|
33
|
+
exports.Mascot = Mascot;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ProgressBar = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
const ink_1 = require("ink");
|
|
10
|
+
const ink_spinner_1 = __importDefault(require("ink-spinner"));
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const useTerminalSize_1 = require("../hooks/useTerminalSize");
|
|
13
|
+
function estimateScanSeconds(packageCount) {
|
|
14
|
+
// Prefetch + analysis scales non-linearly with package count.
|
|
15
|
+
// Calibrated against local API: 45→6s, 100→25s, 200→60s
|
|
16
|
+
return Math.ceil(Math.max(5, packageCount * 0.35 - 10));
|
|
17
|
+
}
|
|
18
|
+
function formatTime(seconds) {
|
|
19
|
+
if (seconds < 60)
|
|
20
|
+
return `${seconds}s`;
|
|
21
|
+
const m = Math.floor(seconds / 60);
|
|
22
|
+
const s = seconds % 60;
|
|
23
|
+
return s > 0 ? `${m}m ${s}s` : `${m}m`;
|
|
24
|
+
}
|
|
25
|
+
const ProgressBar = ({ value, total, label, }) => {
|
|
26
|
+
const startRef = (0, react_1.useRef)(Date.now());
|
|
27
|
+
const [elapsed, setElapsed] = (0, react_1.useState)(0);
|
|
28
|
+
(0, react_1.useEffect)(() => {
|
|
29
|
+
const timer = setInterval(() => {
|
|
30
|
+
setElapsed(Math.floor((Date.now() - startRef.current) / 1000));
|
|
31
|
+
}, 1000);
|
|
32
|
+
return () => clearInterval(timer);
|
|
33
|
+
}, []);
|
|
34
|
+
// Subscribe to terminal resizes so the bar re-lays-out when the user
|
|
35
|
+
// resizes the window mid-scan. Reading process.stdout.columns directly
|
|
36
|
+
// only captures the width at mount time and goes stale on resize.
|
|
37
|
+
const { cols: termWidth } = (0, useTerminalSize_1.useTerminalSize)();
|
|
38
|
+
const percent = total > 0 ? Math.round((value / total) * 100) : 0;
|
|
39
|
+
const estimate = estimateScanSeconds(total);
|
|
40
|
+
const timeInfo = `${formatTime(elapsed)} / ~${formatTime(estimate)}`;
|
|
41
|
+
const counter = `${value}/${total} ${percent}%`;
|
|
42
|
+
// Reserve: 4 indent + counter length + 2 padding
|
|
43
|
+
const barWidth = Math.max(10, termWidth - counter.length - 8);
|
|
44
|
+
const fraction = total > 0 ? Math.min(1, value / total) : 0;
|
|
45
|
+
const filled = Math.round(fraction * barWidth);
|
|
46
|
+
const empty = barWidth - filled;
|
|
47
|
+
const filledBar = "\u2501".repeat(filled);
|
|
48
|
+
const emptyBar = "\u2501".repeat(empty);
|
|
49
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", paddingLeft: 2, children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [chalk_1.default.cyan("\u25C6"), " ", chalk_1.default.bold("Dependency Guardian")] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: "" }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: "cyan", children: (0, jsx_runtime_1.jsx)(ink_spinner_1.default, { type: "dots" }) }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" Scanning ", total, " packages... "] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: timeInfo })] }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { children: " " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: "green", children: filledBar }), (0, jsx_runtime_1.jsx)(ink_1.Text, { dimColor: true, children: emptyBar }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: [" ", counter] })] }), label && ((0, jsx_runtime_1.jsx)(ink_1.Box, { children: (0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" ", chalk_1.default.dim("\u203A"), " ", label] }) }))] }));
|
|
50
|
+
};
|
|
51
|
+
exports.ProgressBar = ProgressBar;
|