@visulima/vis 1.0.0-alpha.11 → 1.0.0-alpha.13
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 +101 -0
- package/LICENSE.md +559 -186
- package/README.md +18 -0
- package/dist/bin.js +1 -9
- package/dist/config/index.d.ts +477 -556
- package/dist/config/index.js +1 -2
- package/dist/generate/index.js +1 -3
- package/dist/packem_chunks/applyDefaults.js +2 -336
- package/dist/packem_chunks/bin.js +234 -9552
- package/dist/packem_chunks/doctor-probe.js +2 -112
- package/dist/packem_chunks/fix.js +11 -234
- package/dist/packem_chunks/handler.js +1 -99
- package/dist/packem_chunks/handler10.js +2 -53
- package/dist/packem_chunks/handler11.js +1 -32
- package/dist/packem_chunks/handler12.js +5 -100
- package/dist/packem_chunks/handler13.js +1 -25
- package/dist/packem_chunks/handler14.js +18 -916
- package/dist/packem_chunks/handler15.js +15 -201
- package/dist/packem_chunks/handler16.js +1 -124
- package/dist/packem_chunks/handler17.js +1 -13
- package/dist/packem_chunks/handler18.js +1 -106
- package/dist/packem_chunks/handler19.js +1 -19
- package/dist/packem_chunks/handler2.js +2 -75
- package/dist/packem_chunks/handler20.js +5 -29
- package/dist/packem_chunks/handler21.js +1 -222
- package/dist/packem_chunks/handler22.js +1 -237
- package/dist/packem_chunks/handler23.js +5 -101
- package/dist/packem_chunks/handler24.js +1 -110
- package/dist/packem_chunks/handler25.js +3 -402
- package/dist/packem_chunks/handler26.js +1 -13
- package/dist/packem_chunks/handler27.js +1 -63
- package/dist/packem_chunks/handler28.js +7 -34
- package/dist/packem_chunks/handler29.js +21 -456
- package/dist/packem_chunks/handler3.js +4 -95
- package/dist/packem_chunks/handler30.js +3 -170
- package/dist/packem_chunks/handler31.js +1 -530
- package/dist/packem_chunks/handler32.js +2 -214
- package/dist/packem_chunks/handler33.js +25 -119
- package/dist/packem_chunks/handler34.js +2 -630
- package/dist/packem_chunks/handler35.js +3 -283
- package/dist/packem_chunks/handler36.js +22 -542
- package/dist/packem_chunks/handler37.js +410 -744
- package/dist/packem_chunks/handler38.js +22 -989
- package/dist/packem_chunks/handler39.js +22 -574
- package/dist/packem_chunks/handler4.js +2 -90
- package/dist/packem_chunks/handler40.js +22 -1685
- package/dist/packem_chunks/handler41.js +6 -1088
- package/dist/packem_chunks/handler42.js +5 -797
- package/dist/packem_chunks/handler43.js +10 -2658
- package/dist/packem_chunks/handler44.js +51 -3784
- package/dist/packem_chunks/handler45.js +25 -2574
- package/dist/packem_chunks/handler46.js +3 -3769
- package/dist/packem_chunks/handler47.js +21 -1485
- package/dist/packem_chunks/handler48.js +42 -0
- package/dist/packem_chunks/handler5.js +8 -174
- package/dist/packem_chunks/handler6.js +1 -95
- package/dist/packem_chunks/handler7.js +1 -115
- package/dist/packem_chunks/handler8.js +1 -12
- package/dist/packem_chunks/handler9.js +1 -29
- package/dist/packem_chunks/heal-accept.js +10 -522
- package/dist/packem_chunks/heal.js +14 -673
- package/dist/packem_chunks/index.js +7 -873
- package/dist/packem_chunks/loader.js +1 -23
- package/dist/packem_chunks/tar.js +3 -0
- package/dist/packem_shared/ai-analysis-hm8d2W7z.js +67 -0
- package/dist/packem_shared/ai-cache-DoiF80AR.js +1 -0
- package/dist/packem_shared/ai-fix-nn4zOE95.js +43 -0
- package/dist/packem_shared/cache-directory-CwHlJhgx.js +1 -0
- package/dist/packem_shared/dependency-scan-COr5n63B.js +2 -0
- package/dist/packem_shared/docker-D6OGr5_S.js +2 -0
- package/dist/packem_shared/failure-log-iUVLf6ts.js +2 -0
- package/dist/packem_shared/flakiness-D9wf0t56.js +1 -0
- package/dist/packem_shared/giget-CcEy_Elm.js +2 -0
- package/dist/packem_shared/index-DH-5hsrC.js +1 -0
- package/dist/packem_shared/otel-DxDUPJJH.js +6 -0
- package/dist/packem_shared/otelPlugin-CQq6poq8.js +1 -0
- package/dist/packem_shared/registry-CkubDdiY.js +2 -0
- package/dist/packem_shared/run-summary-utils-BfBvjzhY.js +1 -0
- package/dist/packem_shared/runtime-check-BXZ43CBW.js +1 -0
- package/dist/packem_shared/selectors-BylODRiM.js +3 -0
- package/dist/packem_shared/symbols-CQmER5MT.js +1 -0
- package/dist/packem_shared/toolchain-BgBOUHII.js +5 -0
- package/dist/packem_shared/typosquats-CcZl99B1.js +1 -0
- package/dist/packem_shared/use-measured-height-DjYgUOKk.js +1 -0
- package/dist/packem_shared/utils-DrNg0XTR.js +1 -0
- package/dist/packem_shared/verify-Baj5mFJ7.js +1 -0
- package/dist/packem_shared/vis-update-app-D1jl0UZZ.js +1 -0
- package/dist/packem_shared/xxh3-DrAUNq4n.js +1 -0
- package/index.js +556 -727
- package/package.json +19 -29
- package/schemas/project.schema.json +739 -297
- package/schemas/vis-config.schema.json +3365 -384
- package/templates/buildkite-ci/template.yml +20 -20
- package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +0 -1316
- package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
- package/dist/packem_shared/ai-analysis-CHeB1joD.js +0 -367
- package/dist/packem_shared/ai-cache-Be_jexe4.js +0 -142
- package/dist/packem_shared/ai-fix-B9iQVcD2.js +0 -379
- package/dist/packem_shared/cache-directory-2qvs4goY.js +0 -98
- package/dist/packem_shared/catalog-BJTtyi-O.js +0 -1371
- package/dist/packem_shared/dependency-scan-A0KSklpG.js +0 -188
- package/dist/packem_shared/docker-2iZzc280.js +0 -181
- package/dist/packem_shared/failure-log-Cz3Z4SKL.js +0 -100
- package/dist/packem_shared/flakiness-goTxXuCX.js +0 -180
- package/dist/packem_shared/otel-DCvqCTz_.js +0 -158
- package/dist/packem_shared/otelPlugin-DFaLDvJf.js +0 -3
- package/dist/packem_shared/registry-CbqXI0rc.js +0 -272
- package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +0 -130
- package/dist/packem_shared/runtime-check-Cobi3p6l.js +0 -127
- package/dist/packem_shared/selectors-SM69TfqC.js +0 -194
- package/dist/packem_shared/symbols-Ta7g2nU-.js +0 -14
- package/dist/packem_shared/toolchain-BdZd9eBi.js +0 -975
- package/dist/packem_shared/typosquats-C-bCh3PX.js +0 -1210
- package/dist/packem_shared/use-measured-height-CNP0vT4M.js +0 -20
- package/dist/packem_shared/utils-CthVdBPS.js +0 -40
- package/dist/packem_shared/xxh3-Ck8mXNg1.js +0 -239
|
@@ -1,2658 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const __cjs_require = __cjs_createRequire(import.meta.url);
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
-
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
-
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
// Fallback to createRequire
|
|
17
|
-
return __cjs_require(module);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
import { dim, bold, cyan, green, red, yellow } from '@visulima/colorize';
|
|
21
|
-
import { findPackageManagerSync } from '@visulima/package';
|
|
22
|
-
import { join, resolve } from '@visulima/path';
|
|
23
|
-
import { Box, Text, ScrollView, Tabs, Tab, Spinner, ScrollBar, useApp, useWindowSize, useInput, Dialog, render } from '@visulima/tui';
|
|
24
|
-
import { j as scoreColor, _ as QuitDialog, D as DEFAULT_LOW_SCORE_THRESHOLD, p as pail, c as detectPm, i as isInCi, S as SYMBOLS, x as runInstall, a as buildSocketOptions, f as fetchSocketReports } from './bin.js';
|
|
25
|
-
import React, { useSyncExternalStore, useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
|
26
|
-
const {
|
|
27
|
-
rmSync,
|
|
28
|
-
writeFileSync,
|
|
29
|
-
statSync,
|
|
30
|
-
readFileSync
|
|
31
|
-
} = __cjs_getBuiltinModule("node:fs");
|
|
32
|
-
const {
|
|
33
|
-
homedir
|
|
34
|
-
} = __cjs_getBuiltinModule("node:os");
|
|
35
|
-
import { x as xxh3Hash } from '../packem_shared/xxh3-Ck8mXNg1.js';
|
|
36
|
-
import { isAccessibleSync, readJsonSync, ensureDirSync } from '@visulima/fs';
|
|
37
|
-
import { findVisConfigFile } from './applyDefaults.js';
|
|
38
|
-
import { d as discoverWorkspacePackages, c as collectDepsFromPkgJson, r as readLockfileText, b as buildE18eEntries, a as buildSocketEntries, e as applyOverrides, f as runCodemod, m as markCodemodAvailability } from './handler41.js';
|
|
39
|
-
import { c as checkRuntimeVersions } from '../packem_shared/runtime-check-Cobi3p6l.js';
|
|
40
|
-
const {
|
|
41
|
-
spawnSync
|
|
42
|
-
} = __cjs_getBuiltinModule("node:child_process");
|
|
43
|
-
import { l as lockedPackages, s as startScanProgress, f as findDuplicateDependencies } from '../packem_shared/dependency-scan-A0KSklpG.js';
|
|
44
|
-
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
45
|
-
import { u as useMeasuredHeight } from '../packem_shared/use-measured-height-CNP0vT4M.js';
|
|
46
|
-
import { r as readCatalogs, l as loadNpmrc, c as checkOutdated, a as fetchVulnerabilities } from '../packem_shared/catalog-BJTtyi-O.js';
|
|
47
|
-
|
|
48
|
-
const getCacheDirectory = () => join(homedir(), ".vis", "cache", "doctor");
|
|
49
|
-
const DEFAULT_TTL_MS = 30 * 60 * 1e3;
|
|
50
|
-
const safeMtime = (path) => {
|
|
51
|
-
if (!path) {
|
|
52
|
-
return "";
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
return String(statSync(path).mtimeMs);
|
|
56
|
-
} catch {
|
|
57
|
-
return "";
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
const CACHE_SCHEMA_VERSION = 2;
|
|
61
|
-
const buildDoctorCacheKey = (input) => {
|
|
62
|
-
const payload = JSON.stringify({
|
|
63
|
-
configMtime: safeMtime(input.configPath),
|
|
64
|
-
lockfileMtime: safeMtime(input.lockfilePath),
|
|
65
|
-
schema: CACHE_SCHEMA_VERSION,
|
|
66
|
-
sections: [...input.sections].toSorted(),
|
|
67
|
-
socketEnabled: input.socketEnabled,
|
|
68
|
-
workspaceRoot: input.workspaceRoot
|
|
69
|
-
});
|
|
70
|
-
return xxh3Hash(Buffer.from(payload));
|
|
71
|
-
};
|
|
72
|
-
const readDoctorCache = (cacheKey) => {
|
|
73
|
-
const filePath = join(getCacheDirectory(), `${cacheKey}.json`);
|
|
74
|
-
if (!isAccessibleSync(filePath)) {
|
|
75
|
-
return void 0;
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
const entry = readJsonSync(filePath);
|
|
79
|
-
if (Date.now() - entry.createdAt > entry.ttlMs) {
|
|
80
|
-
rmSync(filePath, { force: true });
|
|
81
|
-
return void 0;
|
|
82
|
-
}
|
|
83
|
-
return { ...entry.results, sections: new Set(entry.results.sections) };
|
|
84
|
-
} catch {
|
|
85
|
-
rmSync(filePath, { force: true });
|
|
86
|
-
return void 0;
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
const writeDoctorCache = (cacheKey, results, ttlMs = DEFAULT_TTL_MS) => {
|
|
90
|
-
ensureDirSync(getCacheDirectory());
|
|
91
|
-
const entry = {
|
|
92
|
-
createdAt: Date.now(),
|
|
93
|
-
results: { ...results, sections: [...results.sections] },
|
|
94
|
-
ttlMs
|
|
95
|
-
};
|
|
96
|
-
writeFileSync(join(getCacheDirectory(), `${cacheKey}.json`), JSON.stringify(entry, void 0, 2), "utf8");
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const ORPHANS_DIAGNOSTIC_ID = "orphans";
|
|
100
|
-
const checkInotifyCapacity = () => {
|
|
101
|
-
if (process.platform !== "linux") {
|
|
102
|
-
return {
|
|
103
|
-
id: "inotify",
|
|
104
|
-
message: "inotify capacity check skipped (not Linux).",
|
|
105
|
-
status: "skip"
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
let maxWatches;
|
|
109
|
-
try {
|
|
110
|
-
const raw = readFileSync("/proc/sys/fs/inotify/max_user_watches", "utf8").trim();
|
|
111
|
-
const parsed = Number.parseInt(raw, 10);
|
|
112
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
113
|
-
maxWatches = parsed;
|
|
114
|
-
}
|
|
115
|
-
} catch {
|
|
116
|
-
return {
|
|
117
|
-
id: "inotify",
|
|
118
|
-
message: "Could not read /proc/sys/fs/inotify/max_user_watches.",
|
|
119
|
-
status: "warn"
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
if (maxWatches === void 0) {
|
|
123
|
-
return {
|
|
124
|
-
id: "inotify",
|
|
125
|
-
message: "inotify max_user_watches reported a non-numeric value.",
|
|
126
|
-
status: "warn"
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
if (maxWatches < 65536) {
|
|
130
|
-
return {
|
|
131
|
-
detail: { maxWatches },
|
|
132
|
-
id: "inotify",
|
|
133
|
-
message: `inotify watcher limit is ${String(maxWatches)} — large monorepos can exhaust this. Bump now with \`sudo sysctl fs.inotify.max_user_watches=524288\` and persist via \`/etc/sysctl.d/99-vis.conf\` so it survives reboot.`,
|
|
134
|
-
status: "warn"
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
return {
|
|
138
|
-
detail: { maxWatches },
|
|
139
|
-
id: "inotify",
|
|
140
|
-
message: `inotify capacity OK (${String(maxWatches)} watches).`,
|
|
141
|
-
status: "ok"
|
|
142
|
-
};
|
|
143
|
-
};
|
|
144
|
-
const checkTtyAvailability = () => {
|
|
145
|
-
const stdinIsTty = Boolean(process.stdin.isTTY);
|
|
146
|
-
const stdoutIsTty = Boolean(process.stdout.isTTY);
|
|
147
|
-
if (stdinIsTty && stdoutIsTty) {
|
|
148
|
-
return {
|
|
149
|
-
id: "tty",
|
|
150
|
-
message: "Interactive TTY available — watch keybinds enabled.",
|
|
151
|
-
status: "ok"
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
if (!stdinIsTty && !stdoutIsTty) {
|
|
155
|
-
return {
|
|
156
|
-
id: "tty",
|
|
157
|
-
message: "No TTY on stdin/stdout — running in CI / piped mode (keybinds disabled).",
|
|
158
|
-
status: "skip"
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
return {
|
|
162
|
-
detail: { stdin: String(stdinIsTty), stdout: String(stdoutIsTty) },
|
|
163
|
-
id: "tty",
|
|
164
|
-
message: stdinIsTty ? "stdin is a TTY but stdout is not — output is being captured; keybinds still work." : "stdout is a TTY but stdin is not — keybinds disabled (input is piped).",
|
|
165
|
-
status: "skip"
|
|
166
|
-
};
|
|
167
|
-
};
|
|
168
|
-
const listOrphanPids = () => {
|
|
169
|
-
const selfPid = process.pid;
|
|
170
|
-
try {
|
|
171
|
-
return process.platform === "win32" ? listOrphansWindows(selfPid) : listOrphansUnix(selfPid);
|
|
172
|
-
} catch {
|
|
173
|
-
return [];
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
const checkOrphanedRunners = () => {
|
|
177
|
-
const selfPid = process.pid;
|
|
178
|
-
let pids;
|
|
179
|
-
try {
|
|
180
|
-
pids = process.platform === "win32" ? listOrphansWindows(selfPid) : listOrphansUnix(selfPid);
|
|
181
|
-
} catch {
|
|
182
|
-
return {
|
|
183
|
-
id: ORPHANS_DIAGNOSTIC_ID,
|
|
184
|
-
message: "Could not enumerate processes (ps/tasklist failed).",
|
|
185
|
-
status: "warn"
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
if (pids.length === 0) {
|
|
189
|
-
return {
|
|
190
|
-
id: ORPHANS_DIAGNOSTIC_ID,
|
|
191
|
-
message: "No orphaned vis/task-runner processes detected.",
|
|
192
|
-
status: "ok"
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
if (pids.length <= 2) {
|
|
196
|
-
return {
|
|
197
|
-
detail: { count: pids.length, pids: pids.join(",") },
|
|
198
|
-
id: ORPHANS_DIAGNOSTIC_ID,
|
|
199
|
-
message: `${String(pids.length)} possibly orphaned process(es) detected (PIDs: ${pids.join(", ")}). Likely benign.`,
|
|
200
|
-
status: "skip"
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
const killSnippet = process.platform === "win32" ? pids.map((p) => `taskkill /F /PID ${String(p)}`).join(" & ") : `kill ${pids.join(" ")}`;
|
|
204
|
-
return {
|
|
205
|
-
detail: { count: pids.length, pids: pids.join(",") },
|
|
206
|
-
id: "orphans",
|
|
207
|
-
message: `${String(pids.length)} possibly orphaned vis/task-runner processes — run \`vis doctor --fix\` to clean them up, or kill them manually: ${killSnippet}`,
|
|
208
|
-
status: "warn"
|
|
209
|
-
};
|
|
210
|
-
};
|
|
211
|
-
const killOrphanedRunners = (options = {}) => {
|
|
212
|
-
const enumerate = options.enumerate ?? listOrphanPids;
|
|
213
|
-
const force = options.force === true;
|
|
214
|
-
const signal = force ? "SIGKILL" : "SIGTERM";
|
|
215
|
-
const killFn = options.kill ?? defaultKill;
|
|
216
|
-
const pids = enumerate();
|
|
217
|
-
const killed = [];
|
|
218
|
-
const failed = [];
|
|
219
|
-
for (const pid of pids) {
|
|
220
|
-
try {
|
|
221
|
-
killFn(pid, signal);
|
|
222
|
-
killed.push(pid);
|
|
223
|
-
} catch (error) {
|
|
224
|
-
const reason = error.code ?? error.message;
|
|
225
|
-
if (reason === "ESRCH") {
|
|
226
|
-
killed.push(pid);
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
failed.push({ pid, reason });
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return { failed, killed };
|
|
233
|
-
};
|
|
234
|
-
const defaultTaskkillRunner = (args) => spawnSync("taskkill", args, { encoding: "utf8" });
|
|
235
|
-
const defaultProcessKill = (pid, signal) => {
|
|
236
|
-
process.kill(pid, signal);
|
|
237
|
-
};
|
|
238
|
-
const killViaTaskkill = (pid, signal, runner = defaultTaskkillRunner) => {
|
|
239
|
-
const args = signal === "SIGKILL" ? ["/F", "/PID", String(pid)] : ["/PID", String(pid)];
|
|
240
|
-
const result = runner(args);
|
|
241
|
-
if (result.error) {
|
|
242
|
-
throw result.error;
|
|
243
|
-
}
|
|
244
|
-
if (typeof result.status === "number" && result.status !== 0) {
|
|
245
|
-
const code = result.status === 128 ? "ESRCH" : `taskkill exited with code ${String(result.status)}`;
|
|
246
|
-
const error = new Error(code);
|
|
247
|
-
error.code = code;
|
|
248
|
-
throw error;
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
const killViaSignal = (pid, signal, kill = defaultProcessKill) => {
|
|
252
|
-
kill(pid, signal);
|
|
253
|
-
};
|
|
254
|
-
const defaultKill = (pid, signal) => {
|
|
255
|
-
if (process.platform === "win32") {
|
|
256
|
-
killViaTaskkill(pid, signal);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
killViaSignal(pid, signal);
|
|
260
|
-
};
|
|
261
|
-
const runProcessListing = (command, args) => {
|
|
262
|
-
const result = spawnSync(command, args, { encoding: "utf8" });
|
|
263
|
-
if (result.error) {
|
|
264
|
-
throw result.error;
|
|
265
|
-
}
|
|
266
|
-
if (typeof result.status === "number" && result.status !== 0) {
|
|
267
|
-
throw new Error(`${command} exited with code ${String(result.status)}`);
|
|
268
|
-
}
|
|
269
|
-
return typeof result.stdout === "string" ? result.stdout : "";
|
|
270
|
-
};
|
|
271
|
-
const listOrphansUnix = (selfPid) => {
|
|
272
|
-
const stdout = runProcessListing("ps", ["-Ao", "pid=,command="]);
|
|
273
|
-
const pids = [];
|
|
274
|
-
for (const line of stdout.split("\n")) {
|
|
275
|
-
if (line.length === 0) {
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
279
|
-
if (!match) {
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
283
|
-
const command = (match[2] ?? "").toLowerCase();
|
|
284
|
-
if (!Number.isFinite(pid) || pid === selfPid) {
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
if (/(?:^|[ /])vis-native(?:\s|$|[-.])/.test(command) || /(?:^|[ /])vis\s+run\b/.test(command) || /(?:^|[ /])task-runner(?:\s|$|[-.])/.test(command)) {
|
|
288
|
-
pids.push(pid);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return pids;
|
|
292
|
-
};
|
|
293
|
-
const listOrphansWindows = (selfPid) => {
|
|
294
|
-
const stdout = runProcessListing("tasklist", ["/FO", "CSV", "/NH"]);
|
|
295
|
-
const pids = [];
|
|
296
|
-
for (const line of stdout.split(/\r?\n/)) {
|
|
297
|
-
if (line.length === 0) {
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
const cells = line.split(/","/).map((cell) => cell.replaceAll(/^"|"$/g, ""));
|
|
301
|
-
const image = (cells[0] ?? "").toLowerCase();
|
|
302
|
-
const pid = Number.parseInt(cells[1] ?? "", 10);
|
|
303
|
-
if (!Number.isFinite(pid) || pid === selfPid) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
if (image === "vis.exe" || image.startsWith("vis-native") || image.includes("task-runner")) {
|
|
307
|
-
pids.push(pid);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
return pids;
|
|
311
|
-
};
|
|
312
|
-
const runRuntimeDiagnostics = () => [checkInotifyCapacity(), checkTtyAvailability(), checkOrphanedRunners()];
|
|
313
|
-
|
|
314
|
-
const FILTER_TABS = [
|
|
315
|
-
{ id: "dependencies", label: "Deps" },
|
|
316
|
-
{ id: "security", label: "Security" },
|
|
317
|
-
{ id: "optimization", label: "Optimize" },
|
|
318
|
-
{ id: "runtime", label: "Runtime" }
|
|
319
|
-
];
|
|
320
|
-
const SECTION_ORDER = ["dependencies", "security", "optimization", "runtime"];
|
|
321
|
-
const groupBySection = (findings) => {
|
|
322
|
-
const map = /* @__PURE__ */ new Map();
|
|
323
|
-
for (const section of SECTION_ORDER) {
|
|
324
|
-
map.set(section, []);
|
|
325
|
-
}
|
|
326
|
-
for (const finding of findings) {
|
|
327
|
-
map.get(finding.section).push(finding);
|
|
328
|
-
}
|
|
329
|
-
for (const [section, items] of map) {
|
|
330
|
-
if (items.length === 0) {
|
|
331
|
-
map.delete(section);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return map;
|
|
335
|
-
};
|
|
336
|
-
const filterFindings = (findings, filterType, filterText, severityFilter) => {
|
|
337
|
-
let filtered = findings.filter((f) => f.section === filterType);
|
|
338
|
-
if (severityFilter) {
|
|
339
|
-
filtered = filtered.filter((f) => f.severity === severityFilter);
|
|
340
|
-
}
|
|
341
|
-
if (filterText) {
|
|
342
|
-
const lower = filterText.toLowerCase();
|
|
343
|
-
filtered = filtered.filter((f) => f.title.toLowerCase().includes(lower));
|
|
344
|
-
}
|
|
345
|
-
return [...filtered];
|
|
346
|
-
};
|
|
347
|
-
const initialStatus = (activeSections) => {
|
|
348
|
-
const status = {
|
|
349
|
-
dependencies: "idle",
|
|
350
|
-
optimization: "idle",
|
|
351
|
-
runtime: "idle",
|
|
352
|
-
security: "idle"
|
|
353
|
-
};
|
|
354
|
-
for (const id of SECTION_ORDER) {
|
|
355
|
-
if (activeSections.has(id)) {
|
|
356
|
-
status[id] = "idle";
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return status;
|
|
360
|
-
};
|
|
361
|
-
class DoctorStore {
|
|
362
|
-
#state;
|
|
363
|
-
#listeners = /* @__PURE__ */ new Set();
|
|
364
|
-
constructor(input = []) {
|
|
365
|
-
const options = Array.isArray(input) ? { findings: input } : input;
|
|
366
|
-
const findings = options.findings ?? [];
|
|
367
|
-
const activeSections = options.activeSections ?? new Set(SECTION_ORDER);
|
|
368
|
-
const initialFilter = SECTION_ORDER.find((id) => activeSections.has(id)) ?? "dependencies";
|
|
369
|
-
const seed = filterFindings(findings, initialFilter, "", void 0);
|
|
370
|
-
const status = initialStatus(activeSections);
|
|
371
|
-
if (findings.length > 0) {
|
|
372
|
-
for (const finding of findings) {
|
|
373
|
-
status[finding.section] = "done";
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
this.#state = {
|
|
377
|
-
all: findings,
|
|
378
|
-
entries: seed,
|
|
379
|
-
filterActive: false,
|
|
380
|
-
filterText: "",
|
|
381
|
-
filterType: initialFilter,
|
|
382
|
-
focusedPanel: "list",
|
|
383
|
-
grouped: groupBySection(seed),
|
|
384
|
-
pendingAction: void 0,
|
|
385
|
-
sectionError: {},
|
|
386
|
-
sectionMessage: {},
|
|
387
|
-
sectionStatus: status,
|
|
388
|
-
selectedIndex: 0,
|
|
389
|
-
severityFilter: void 0
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
getSnapshot = () => this.#state;
|
|
393
|
-
subscribe = (listener) => {
|
|
394
|
-
this.#listeners.add(listener);
|
|
395
|
-
return () => {
|
|
396
|
-
this.#listeners.delete(listener);
|
|
397
|
-
};
|
|
398
|
-
};
|
|
399
|
-
setSelectedIndex(index) {
|
|
400
|
-
const clamped = Math.max(0, Math.min(index, this.#state.entries.length - 1));
|
|
401
|
-
if (clamped !== this.#state.selectedIndex) {
|
|
402
|
-
this.#emit({ ...this.#state, selectedIndex: clamped });
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
setFocusedPanel(panel) {
|
|
406
|
-
if (panel !== this.#state.focusedPanel) {
|
|
407
|
-
this.#emit({ ...this.#state, focusedPanel: panel });
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
setFilterType(type) {
|
|
411
|
-
if (type === this.#state.filterType) {
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const newEntries = filterFindings(this.#state.all, type, this.#state.filterText, this.#state.severityFilter);
|
|
415
|
-
this.#emit({
|
|
416
|
-
...this.#state,
|
|
417
|
-
entries: newEntries,
|
|
418
|
-
filterType: type,
|
|
419
|
-
grouped: groupBySection(newEntries),
|
|
420
|
-
selectedIndex: 0
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
setFilter(text) {
|
|
424
|
-
const newEntries = filterFindings(this.#state.all, this.#state.filterType, text, this.#state.severityFilter);
|
|
425
|
-
this.#emit({
|
|
426
|
-
...this.#state,
|
|
427
|
-
entries: newEntries,
|
|
428
|
-
filterText: text,
|
|
429
|
-
grouped: groupBySection(newEntries),
|
|
430
|
-
selectedIndex: 0
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
setFilterActive(active) {
|
|
434
|
-
if (active === this.#state.filterActive) {
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
if (active) {
|
|
438
|
-
this.#emit({ ...this.#state, filterActive: true });
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
const newEntries = filterFindings(this.#state.all, this.#state.filterType, "", this.#state.severityFilter);
|
|
442
|
-
this.#emit({
|
|
443
|
-
...this.#state,
|
|
444
|
-
entries: newEntries,
|
|
445
|
-
filterActive: false,
|
|
446
|
-
filterText: "",
|
|
447
|
-
grouped: groupBySection(newEntries),
|
|
448
|
-
selectedIndex: 0
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
setPendingAction(action) {
|
|
452
|
-
this.#emit({ ...this.#state, pendingAction: action });
|
|
453
|
-
}
|
|
454
|
-
setSeverityFilter(severity) {
|
|
455
|
-
if (severity === this.#state.severityFilter) {
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
const newEntries = filterFindings(this.#state.all, this.#state.filterType, this.#state.filterText, severity);
|
|
459
|
-
this.#emit({
|
|
460
|
-
...this.#state,
|
|
461
|
-
entries: newEntries,
|
|
462
|
-
grouped: groupBySection(newEntries),
|
|
463
|
-
selectedIndex: 0,
|
|
464
|
-
severityFilter: severity
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
startSection(section, message) {
|
|
468
|
-
this.#emit({
|
|
469
|
-
...this.#state,
|
|
470
|
-
sectionMessage: { ...this.#state.sectionMessage, [section]: message },
|
|
471
|
-
sectionStatus: { ...this.#state.sectionStatus, [section]: "running" }
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
completeSection(section, findings) {
|
|
475
|
-
const newAll = [...this.#state.all, ...findings];
|
|
476
|
-
const newEntries = filterFindings(newAll, this.#state.filterType, this.#state.filterText, this.#state.severityFilter);
|
|
477
|
-
const nextMessage = { ...this.#state.sectionMessage };
|
|
478
|
-
delete nextMessage[section];
|
|
479
|
-
this.#emit({
|
|
480
|
-
...this.#state,
|
|
481
|
-
all: newAll,
|
|
482
|
-
entries: newEntries,
|
|
483
|
-
grouped: groupBySection(newEntries),
|
|
484
|
-
sectionMessage: nextMessage,
|
|
485
|
-
sectionStatus: { ...this.#state.sectionStatus, [section]: "done" }
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
failSection(section, error) {
|
|
489
|
-
this.#emit({
|
|
490
|
-
...this.#state,
|
|
491
|
-
sectionError: { ...this.#state.sectionError, [section]: error },
|
|
492
|
-
sectionStatus: { ...this.#state.sectionStatus, [section]: "error" }
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
#emit(newState) {
|
|
496
|
-
this.#state = newState;
|
|
497
|
-
for (const listener of this.#listeners) {
|
|
498
|
-
try {
|
|
499
|
-
listener();
|
|
500
|
-
} catch {
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
const SEVERITY_RANK = { error: 0, warn: 1 };
|
|
507
|
-
const isAcknowledged = (entry) => Boolean(entry.acceptedRisk);
|
|
508
|
-
const flattenFindings = (results) => {
|
|
509
|
-
const findings = [];
|
|
510
|
-
if (results.sections.has("dependencies")) {
|
|
511
|
-
for (const entry of results.outdated) {
|
|
512
|
-
findings.push({
|
|
513
|
-
entry,
|
|
514
|
-
id: `outdated:${entry.packageName}`,
|
|
515
|
-
kind: "outdated",
|
|
516
|
-
section: "dependencies",
|
|
517
|
-
severity: "warn",
|
|
518
|
-
subtitle: `${entry.currentRange} → ${entry.newRange} (${entry.updateType})`,
|
|
519
|
-
title: entry.packageName
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
for (const pkg of results.duplicates) {
|
|
523
|
-
findings.push({
|
|
524
|
-
id: `duplicate:${pkg.name}`,
|
|
525
|
-
kind: "duplicate",
|
|
526
|
-
pkg,
|
|
527
|
-
section: "dependencies",
|
|
528
|
-
severity: "warn",
|
|
529
|
-
subtitle: `${String(pkg.versions.length)} versions installed`,
|
|
530
|
-
title: pkg.name
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
if (results.sections.has("security")) {
|
|
535
|
-
for (const entry of results.outdated) {
|
|
536
|
-
if (entry.vulnerabilities && entry.vulnerabilities.length > 0) {
|
|
537
|
-
const top = entry.vulnerabilities[0];
|
|
538
|
-
const sev = isAcknowledged(entry) ? "warn" : "error";
|
|
539
|
-
const count = entry.vulnerabilities.length;
|
|
540
|
-
findings.push({
|
|
541
|
-
entry,
|
|
542
|
-
id: `vuln:${entry.packageName}`,
|
|
543
|
-
kind: "vulnerability",
|
|
544
|
-
packageName: entry.packageName,
|
|
545
|
-
section: "security",
|
|
546
|
-
severity: sev,
|
|
547
|
-
subtitle: count === 1 ? `${top.severity} · ${top.id}` : `${String(count)} advisories · top: ${top.severity} ${top.id}`,
|
|
548
|
-
title: entry.packageName
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
if (entry.socketReport && entry.socketReport.alerts.length > 0) {
|
|
552
|
-
const score = Math.round(entry.socketReport.score.overall * 100);
|
|
553
|
-
findings.push({
|
|
554
|
-
entry,
|
|
555
|
-
id: `socket:${entry.packageName}`,
|
|
556
|
-
kind: "socket",
|
|
557
|
-
packageName: entry.packageName,
|
|
558
|
-
section: "security",
|
|
559
|
-
severity: "warn",
|
|
560
|
-
subtitle: `${String(entry.socketReport.alerts.length)} alert${entry.socketReport.alerts.length === 1 ? "" : "s"} · score ${String(score)}%`,
|
|
561
|
-
title: entry.packageName
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (results.sections.has("optimization")) {
|
|
567
|
-
for (const entry of results.optimizations) {
|
|
568
|
-
findings.push({
|
|
569
|
-
entry,
|
|
570
|
-
id: `opt:${entry.packageName}`,
|
|
571
|
-
kind: "optimization",
|
|
572
|
-
section: "optimization",
|
|
573
|
-
severity: "warn",
|
|
574
|
-
subtitle: `${entry.category} → ${entry.replacement}`,
|
|
575
|
-
title: entry.packageName
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
if (results.sections.has("runtime")) {
|
|
580
|
-
for (const diagnostic of results.runtime) {
|
|
581
|
-
if (diagnostic.status !== "warn") {
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
findings.push({
|
|
585
|
-
diagnostic,
|
|
586
|
-
id: `runtime:${diagnostic.id}`,
|
|
587
|
-
kind: "runtime",
|
|
588
|
-
section: "runtime",
|
|
589
|
-
severity: "warn",
|
|
590
|
-
title: diagnostic.message
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
findings.sort((a, b) => {
|
|
595
|
-
if (a.section !== b.section) {
|
|
596
|
-
const order = ["dependencies", "security", "optimization", "runtime"];
|
|
597
|
-
return order.indexOf(a.section) - order.indexOf(b.section);
|
|
598
|
-
}
|
|
599
|
-
return SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
|
|
600
|
-
});
|
|
601
|
-
return findings;
|
|
602
|
-
};
|
|
603
|
-
const SECTION_LABELS = {
|
|
604
|
-
dependencies: "Dependencies",
|
|
605
|
-
optimization: "Optimization",
|
|
606
|
-
runtime: "Runtime",
|
|
607
|
-
security: "Security"
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
const SEVERITY_COLORS$2 = {
|
|
611
|
-
error: "red",
|
|
612
|
-
warn: "yellow"
|
|
613
|
-
};
|
|
614
|
-
const SEVERITY_GLYPHS = {
|
|
615
|
-
error: "✖",
|
|
616
|
-
warn: "⚠"
|
|
617
|
-
};
|
|
618
|
-
const SEVERITY_LABELS = {
|
|
619
|
-
error: " ERROR ",
|
|
620
|
-
warn: " WARN "
|
|
621
|
-
};
|
|
622
|
-
const ConfigBanner = ({ children, hint, message, severity, title }) => {
|
|
623
|
-
const color = SEVERITY_COLORS$2[severity];
|
|
624
|
-
return jsxs(Box, { borderColor: color, borderStyle: "single", flexDirection: "column", flexShrink: 0, paddingX: 1, children: [
|
|
625
|
-
jsxs(Box, { gap: 1, children: [
|
|
626
|
-
jsx(Text, { backgroundColor: color, bold: true, color: "black", children: SEVERITY_LABELS[severity] }),
|
|
627
|
-
jsx(Text, { bold: true, color, children: SEVERITY_GLYPHS[severity] }),
|
|
628
|
-
jsx(Text, { bold: true, wrap: "truncate-end", children: title })
|
|
629
|
-
] }),
|
|
630
|
-
jsx(Text, { wrap: "truncate-end", children: message }),
|
|
631
|
-
hint ? jsx(Text, { dimColor: true, wrap: "truncate-end", children: hint }) : null,
|
|
632
|
-
children
|
|
633
|
-
] });
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
const SEVERITY_COLORS$1 = {
|
|
637
|
-
CRITICAL: "red",
|
|
638
|
-
HIGH: "red",
|
|
639
|
-
LOW: "gray",
|
|
640
|
-
MODERATE: "yellow",
|
|
641
|
-
UNKNOWN: "gray"
|
|
642
|
-
};
|
|
643
|
-
const SOCKET_SEVERITY_COLORS = {
|
|
644
|
-
critical: "red",
|
|
645
|
-
high: "red",
|
|
646
|
-
low: "gray",
|
|
647
|
-
medium: "yellow"
|
|
648
|
-
};
|
|
649
|
-
const UPDATE_TYPE_COLORS = {
|
|
650
|
-
major: "red",
|
|
651
|
-
minor: "yellow",
|
|
652
|
-
patch: "green"
|
|
653
|
-
};
|
|
654
|
-
const FieldRow = ({ children, label, width = 14 }) => jsxs(Box, { children: [
|
|
655
|
-
jsx(Box, { width, children: jsxs(Text, { dimColor: true, children: [
|
|
656
|
-
label,
|
|
657
|
-
":"
|
|
658
|
-
] }) }),
|
|
659
|
-
typeof children === "string" ? jsx(Text, { children }) : children
|
|
660
|
-
] });
|
|
661
|
-
const SectionTitle = ({ children }) => jsx(Box, { marginTop: 1, children: jsx(Text, { bold: true, color: "white", children }) });
|
|
662
|
-
const OutdatedDetail = ({ finding }) => {
|
|
663
|
-
const { entry } = finding;
|
|
664
|
-
const typeColor = UPDATE_TYPE_COLORS[entry.updateType] ?? "white";
|
|
665
|
-
return jsxs(Box, { flexDirection: "column", children: [
|
|
666
|
-
jsx(FieldRow, { label: "Current", children: entry.currentRange }),
|
|
667
|
-
jsxs(FieldRow, { label: "Target", children: [
|
|
668
|
-
jsx(Text, { children: entry.newRange }),
|
|
669
|
-
jsxs(Text, { bold: true, color: typeColor, children: [
|
|
670
|
-
" (",
|
|
671
|
-
entry.updateType,
|
|
672
|
-
")"
|
|
673
|
-
] })
|
|
674
|
-
] }),
|
|
675
|
-
jsx(FieldRow, { label: "Catalog", children: entry.catalogName }),
|
|
676
|
-
entry.acceptedRisk ? jsx(FieldRow, { label: "Risk ack", children: jsx(Text, { dimColor: true, children: entry.acceptedRisk.reason ?? "(no reason recorded)" }) }) : null,
|
|
677
|
-
jsx(SectionTitle, { children: "Action" }),
|
|
678
|
-
jsxs(Text, { dimColor: true, children: [
|
|
679
|
-
"Run",
|
|
680
|
-
" ",
|
|
681
|
-
jsx(Text, { bold: true, color: "white", children: "vis update" }),
|
|
682
|
-
" ",
|
|
683
|
-
"to apply this change."
|
|
684
|
-
] })
|
|
685
|
-
] });
|
|
686
|
-
};
|
|
687
|
-
const DuplicateDetail = ({ finding }) => jsxs(Box, { flexDirection: "column", children: [
|
|
688
|
-
jsx(FieldRow, { label: "Versions", children: jsx(Text, { children: String(finding.pkg.versions.length) }) }),
|
|
689
|
-
jsx(SectionTitle, { children: "Installed versions" }),
|
|
690
|
-
finding.pkg.versions.map((v) => jsxs(Text, { children: [
|
|
691
|
-
" · ",
|
|
692
|
-
v
|
|
693
|
-
] }, v)),
|
|
694
|
-
jsx(SectionTitle, { children: "Action" }),
|
|
695
|
-
jsxs(Text, { dimColor: true, children: [
|
|
696
|
-
"Run",
|
|
697
|
-
" ",
|
|
698
|
-
jsx(Text, { bold: true, color: "white", children: "vis dedupe" }),
|
|
699
|
-
" ",
|
|
700
|
-
"to consolidate to a single resolution."
|
|
701
|
-
] })
|
|
702
|
-
] });
|
|
703
|
-
const VulnerabilityDetail = ({ finding }) => {
|
|
704
|
-
const vulns = finding.entry.vulnerabilities ?? [];
|
|
705
|
-
return jsxs(Box, { flexDirection: "column", children: [
|
|
706
|
-
jsx(FieldRow, { label: "Package", children: finding.packageName }),
|
|
707
|
-
jsx(FieldRow, { label: "Current", children: finding.entry.currentRange }),
|
|
708
|
-
jsx(FieldRow, { label: "Advisories", children: String(vulns.length) }),
|
|
709
|
-
finding.entry.acceptedRisk ? jsx(FieldRow, { label: "Risk ack", children: jsx(Text, { dimColor: true, children: finding.entry.acceptedRisk.reason ?? "(no reason recorded)" }) }) : null,
|
|
710
|
-
vulns.map((v) => {
|
|
711
|
-
const sevColor = SEVERITY_COLORS$1[v.severity] ?? "gray";
|
|
712
|
-
return jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
713
|
-
jsxs(Box, { children: [
|
|
714
|
-
jsx(Text, { bold: true, color: sevColor, children: v.severity }),
|
|
715
|
-
jsx(Text, { children: " " }),
|
|
716
|
-
jsx(Text, { children: v.id }),
|
|
717
|
-
typeof v.cvssScore === "number" ? jsxs(Text, { dimColor: true, children: [
|
|
718
|
-
" · CVSS ",
|
|
719
|
-
v.cvssScore.toFixed(1)
|
|
720
|
-
] }) : null
|
|
721
|
-
] }),
|
|
722
|
-
jsx(Text, { wrap: "wrap", children: v.summary }),
|
|
723
|
-
v.fixedVersions.length > 0 ? jsxs(Text, { dimColor: true, children: [
|
|
724
|
-
"Fixed in: ",
|
|
725
|
-
v.fixedVersions.join(", ")
|
|
726
|
-
] }) : null,
|
|
727
|
-
v.aliases && v.aliases.length > 0 ? jsxs(Text, { dimColor: true, children: [
|
|
728
|
-
"Aliases: ",
|
|
729
|
-
v.aliases.join(", ")
|
|
730
|
-
] }) : null
|
|
731
|
-
] }, v.id);
|
|
732
|
-
})
|
|
733
|
-
] });
|
|
734
|
-
};
|
|
735
|
-
const SocketDetail = ({ finding }) => {
|
|
736
|
-
const report = finding.entry.socketReport;
|
|
737
|
-
if (!report) {
|
|
738
|
-
return jsx(Text, { dimColor: true, children: "No Socket report attached." });
|
|
739
|
-
}
|
|
740
|
-
const overall = Math.round(report.score.overall * 100);
|
|
741
|
-
const overallColor = scoreColor(report.score.overall);
|
|
742
|
-
return jsxs(Box, { flexDirection: "column", children: [
|
|
743
|
-
jsx(FieldRow, { label: "Package", children: finding.packageName }),
|
|
744
|
-
jsx(FieldRow, { label: "Overall", children: jsxs(Text, { color: overallColor, children: [
|
|
745
|
-
String(overall),
|
|
746
|
-
"%"
|
|
747
|
-
] }) }),
|
|
748
|
-
jsx(FieldRow, { label: "Alerts", children: String(report.alerts.length) }),
|
|
749
|
-
finding.entry.acceptedRisk ? jsx(FieldRow, { label: "Risk ack", children: jsx(Text, { dimColor: true, children: finding.entry.acceptedRisk.reason ?? "(no reason recorded)" }) }) : null,
|
|
750
|
-
jsx(SectionTitle, { children: "Score breakdown" }),
|
|
751
|
-
Object.entries(report.score).map(([key, value]) => {
|
|
752
|
-
if (key === "overall") {
|
|
753
|
-
return null;
|
|
754
|
-
}
|
|
755
|
-
const numericValue = typeof value === "number" ? value : 0;
|
|
756
|
-
const pct = Math.round(numericValue * 100);
|
|
757
|
-
const color = scoreColor(numericValue);
|
|
758
|
-
return jsxs(Box, { children: [
|
|
759
|
-
jsx(Box, { width: 14, children: jsxs(Text, { dimColor: true, children: [
|
|
760
|
-
key,
|
|
761
|
-
":"
|
|
762
|
-
] }) }),
|
|
763
|
-
jsxs(Text, { color, children: [
|
|
764
|
-
String(pct),
|
|
765
|
-
"%"
|
|
766
|
-
] })
|
|
767
|
-
] }, key);
|
|
768
|
-
}),
|
|
769
|
-
jsx(SectionTitle, { children: "Alerts" }),
|
|
770
|
-
report.alerts.map((alert, index) => {
|
|
771
|
-
const sevColor = SOCKET_SEVERITY_COLORS[alert.severity] ?? "gray";
|
|
772
|
-
return jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
773
|
-
jsxs(Box, { children: [
|
|
774
|
-
jsx(Text, { bold: true, color: sevColor, children: alert.severity }),
|
|
775
|
-
jsx(Text, { children: " " }),
|
|
776
|
-
jsx(Text, { children: alert.type })
|
|
777
|
-
] }),
|
|
778
|
-
alert.props ? jsx(Text, { dimColor: true, wrap: "wrap", children: JSON.stringify(alert.props) }) : null
|
|
779
|
-
] }, `${alert.type}-${String(index)}`);
|
|
780
|
-
})
|
|
781
|
-
] });
|
|
782
|
-
};
|
|
783
|
-
const OptimizationDetail = ({ finding }) => {
|
|
784
|
-
const { entry } = finding;
|
|
785
|
-
return jsxs(Box, { flexDirection: "column", children: [
|
|
786
|
-
jsx(FieldRow, { label: "Package", children: entry.packageName }),
|
|
787
|
-
jsx(FieldRow, { label: "Category", children: entry.category }),
|
|
788
|
-
jsx(FieldRow, { label: "Replacement", children: entry.replacement }),
|
|
789
|
-
entry.overrideSpec ? jsx(FieldRow, { label: "Override", children: entry.overrideSpec }) : null,
|
|
790
|
-
jsx(FieldRow, { label: "Codemod", children: jsx(Text, { color: entry.hasCodemod ? "green" : "gray", children: entry.hasCodemod ? "available" : "not available" }) }),
|
|
791
|
-
entry.docUrl ? jsx(FieldRow, { label: "Guide", children: jsx(Text, { color: "cyan", underline: true, children: entry.docUrl }) }) : null,
|
|
792
|
-
jsx(SectionTitle, { children: "Action" }),
|
|
793
|
-
entry.hasCodemod ? jsxs(Text, { dimColor: true, children: [
|
|
794
|
-
"Run",
|
|
795
|
-
" ",
|
|
796
|
-
jsx(Text, { bold: true, color: "white", children: "vis optimize" }),
|
|
797
|
-
" ",
|
|
798
|
-
"to apply the codemod interactively."
|
|
799
|
-
] }) : entry.overrideSpec ? jsxs(Text, { dimColor: true, children: [
|
|
800
|
-
"Run",
|
|
801
|
-
" ",
|
|
802
|
-
jsx(Text, { bold: true, color: "white", children: "vis optimize" }),
|
|
803
|
-
" ",
|
|
804
|
-
"to install the package override."
|
|
805
|
-
] }) : entry.docUrl ? jsx(Text, { dimColor: true, children: "No automated codemod. Open the migration guide above for the recommended alternative and steps." }) : jsx(Text, { dimColor: true, children: "No automated codemod. Consult the package's docs or the e18e module-replacements guide for an alternative." })
|
|
806
|
-
] });
|
|
807
|
-
};
|
|
808
|
-
const RuntimeDetail = ({ finding }) => {
|
|
809
|
-
const { diagnostic } = finding;
|
|
810
|
-
const statusColor = diagnostic.status === "warn" ? "yellow" : diagnostic.status === "ok" ? "green" : "gray";
|
|
811
|
-
return jsxs(Box, { flexDirection: "column", children: [
|
|
812
|
-
jsx(FieldRow, { label: "Check", children: diagnostic.id }),
|
|
813
|
-
jsx(FieldRow, { label: "Status", children: jsx(Text, { color: statusColor, children: diagnostic.status }) }),
|
|
814
|
-
jsx(SectionTitle, { children: "Message" }),
|
|
815
|
-
jsx(Text, { wrap: "wrap", children: diagnostic.message }),
|
|
816
|
-
diagnostic.detail && Object.keys(diagnostic.detail).length > 0 ? jsxs(Fragment, { children: [
|
|
817
|
-
jsx(SectionTitle, { children: "Details" }),
|
|
818
|
-
Object.entries(diagnostic.detail).map(([key, value]) => jsxs(Box, { children: [
|
|
819
|
-
jsx(Box, { width: 20, children: jsxs(Text, { dimColor: true, children: [
|
|
820
|
-
key,
|
|
821
|
-
":"
|
|
822
|
-
] }) }),
|
|
823
|
-
jsx(Text, { children: String(value) })
|
|
824
|
-
] }, key))
|
|
825
|
-
] }) : null
|
|
826
|
-
] });
|
|
827
|
-
};
|
|
828
|
-
const DoctorDetailPanel = ({ finding, focused, scrollRef }) => {
|
|
829
|
-
const borderColor = focused ? "white" : "gray";
|
|
830
|
-
if (!finding) {
|
|
831
|
-
return jsx(Box, { alignItems: "center", borderColor: "gray", borderStyle: "single", flexDirection: "column", flexGrow: 1, justifyContent: "center", children: jsx(Text, { dimColor: true, children: "No finding selected" }) });
|
|
832
|
-
}
|
|
833
|
-
let body;
|
|
834
|
-
switch (finding.kind) {
|
|
835
|
-
case "duplicate": {
|
|
836
|
-
body = jsx(DuplicateDetail, { finding });
|
|
837
|
-
break;
|
|
838
|
-
}
|
|
839
|
-
case "optimization": {
|
|
840
|
-
body = jsx(OptimizationDetail, { finding });
|
|
841
|
-
break;
|
|
842
|
-
}
|
|
843
|
-
case "outdated": {
|
|
844
|
-
body = jsx(OutdatedDetail, { finding });
|
|
845
|
-
break;
|
|
846
|
-
}
|
|
847
|
-
case "runtime": {
|
|
848
|
-
body = jsx(RuntimeDetail, { finding });
|
|
849
|
-
break;
|
|
850
|
-
}
|
|
851
|
-
case "socket": {
|
|
852
|
-
body = jsx(SocketDetail, { finding });
|
|
853
|
-
break;
|
|
854
|
-
}
|
|
855
|
-
case "vulnerability": {
|
|
856
|
-
body = jsx(VulnerabilityDetail, { finding });
|
|
857
|
-
break;
|
|
858
|
-
}
|
|
859
|
-
default: {
|
|
860
|
-
body = jsx(Text, { dimColor: true, children: "Unknown finding kind." });
|
|
861
|
-
break;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
return jsxs(Box, { borderColor, borderStyle: "single", flexDirection: "column", flexGrow: 1, children: [
|
|
865
|
-
jsxs(Box, { flexShrink: 0, paddingTop: 1, paddingX: 2, children: [
|
|
866
|
-
jsx(Text, { bold: true, color: "white", children: finding.title }),
|
|
867
|
-
jsxs(Text, { dimColor: true, children: [
|
|
868
|
-
" ",
|
|
869
|
-
SECTION_LABELS[finding.section]
|
|
870
|
-
] })
|
|
871
|
-
] }),
|
|
872
|
-
jsxs(ScrollView, { flexGrow: 1, flexShrink: 1, paddingX: 2, ref: scrollRef, scrollbar: true, scrollbarColor: "gray", scrollbarStyle: "block", children: [
|
|
873
|
-
jsx(Text, {}),
|
|
874
|
-
body
|
|
875
|
-
] })
|
|
876
|
-
] });
|
|
877
|
-
};
|
|
878
|
-
|
|
879
|
-
const SEVERITY_COLORS = {
|
|
880
|
-
error: "red",
|
|
881
|
-
warn: "yellow"
|
|
882
|
-
};
|
|
883
|
-
const SEVERITY_GLYPH = {
|
|
884
|
-
error: "✖",
|
|
885
|
-
warn: "⚠"
|
|
886
|
-
};
|
|
887
|
-
const hasAcceptedRisk = (finding) => {
|
|
888
|
-
if (finding.kind === "outdated" || finding.kind === "vulnerability" || finding.kind === "socket") {
|
|
889
|
-
return Boolean(finding.entry.acceptedRisk);
|
|
890
|
-
}
|
|
891
|
-
return false;
|
|
892
|
-
};
|
|
893
|
-
const FindingRow = ({ finding, isSelected }) => {
|
|
894
|
-
const sevColor = SEVERITY_COLORS[finding.severity];
|
|
895
|
-
const acked = hasAcceptedRisk(finding);
|
|
896
|
-
return jsxs(Box, { flexShrink: 0, height: 1, children: [
|
|
897
|
-
jsx(Text, { children: isSelected ? ">" : " " }),
|
|
898
|
-
jsxs(Text, { color: sevColor, children: [
|
|
899
|
-
" ",
|
|
900
|
-
SEVERITY_GLYPH[finding.severity],
|
|
901
|
-
" "
|
|
902
|
-
] }),
|
|
903
|
-
jsx(Box, { flexGrow: 1, children: jsx(Text, { bold: isSelected, inverse: isSelected, wrap: "truncate", children: finding.title }) }),
|
|
904
|
-
acked ? jsx(Text, { color: "cyan", children: " ack" }) : null,
|
|
905
|
-
finding.subtitle ? jsxs(Text, { dimColor: true, wrap: "truncate", children: [
|
|
906
|
-
" ",
|
|
907
|
-
finding.subtitle
|
|
908
|
-
] }) : null
|
|
909
|
-
] });
|
|
910
|
-
};
|
|
911
|
-
const SectionHeader = ({ count, section }) => jsxs(Box, { flexShrink: 0, height: 1, marginTop: 1, children: [
|
|
912
|
-
jsx(Text, { dimColor: true, children: "▼ " }),
|
|
913
|
-
jsx(Text, { bold: true, color: "white", children: SECTION_LABELS[section].toUpperCase() }),
|
|
914
|
-
jsxs(Text, { dimColor: true, children: [
|
|
915
|
-
" (",
|
|
916
|
-
count,
|
|
917
|
-
")"
|
|
918
|
-
] })
|
|
919
|
-
] });
|
|
920
|
-
const TabLabel = ({ count, label, status }) => {
|
|
921
|
-
const showSpinner = status === "running";
|
|
922
|
-
return jsxs(Text, { children: [
|
|
923
|
-
label,
|
|
924
|
-
showSpinner ? jsxs(Text, { children: [
|
|
925
|
-
" ",
|
|
926
|
-
jsx(Spinner, { type: "dots" })
|
|
927
|
-
] }) : null,
|
|
928
|
-
status === "error" ? jsx(Text, { bold: true, color: "red", children: " ✖" }) : jsxs(Text, { dimColor: true, children: [
|
|
929
|
-
" (",
|
|
930
|
-
String(count),
|
|
931
|
-
")"
|
|
932
|
-
] })
|
|
933
|
-
] });
|
|
934
|
-
};
|
|
935
|
-
const DoctorListPanel = ({
|
|
936
|
-
elapsedMs,
|
|
937
|
-
entries,
|
|
938
|
-
filterActive,
|
|
939
|
-
filterText,
|
|
940
|
-
filterType,
|
|
941
|
-
focused,
|
|
942
|
-
fromCache = false,
|
|
943
|
-
grouped,
|
|
944
|
-
onViewportHeightChange,
|
|
945
|
-
scrollOffset,
|
|
946
|
-
sectionCounts,
|
|
947
|
-
sectionMessage,
|
|
948
|
-
sectionStatus,
|
|
949
|
-
selectedIndex,
|
|
950
|
-
severityFilter,
|
|
951
|
-
totalAll,
|
|
952
|
-
viewportHeight
|
|
953
|
-
}) => {
|
|
954
|
-
const borderColor = focused ? "white" : "gray";
|
|
955
|
-
const { measuredHeight: measuredViewportHeight, ref: contentRowRef } = useMeasuredHeight(viewportHeight, onViewportHeightChange);
|
|
956
|
-
let errors = 0;
|
|
957
|
-
let warns = 0;
|
|
958
|
-
for (const finding of entries) {
|
|
959
|
-
if (finding.severity === "error") {
|
|
960
|
-
errors += 1;
|
|
961
|
-
} else if (finding.severity === "warn") {
|
|
962
|
-
warns += 1;
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
const summaryParts = [];
|
|
966
|
-
if (errors > 0) {
|
|
967
|
-
summaryParts.push(`${String(errors)} error${errors === 1 ? "" : "s"}`);
|
|
968
|
-
}
|
|
969
|
-
if (warns > 0) {
|
|
970
|
-
summaryParts.push(`${String(warns)} warn${warns === 1 ? "" : "s"}`);
|
|
971
|
-
}
|
|
972
|
-
const summary = summaryParts.length > 0 ? ` (${summaryParts.join(", ")})` : "";
|
|
973
|
-
const elapsedSeconds = (elapsedMs / 1e3).toFixed(1);
|
|
974
|
-
const rows = [];
|
|
975
|
-
for (const [section, items] of grouped) {
|
|
976
|
-
rows.push(jsx(SectionHeader, { count: items.length, section }, `hdr-${section}`));
|
|
977
|
-
for (const item of items) {
|
|
978
|
-
const flatIndex = entries.indexOf(item);
|
|
979
|
-
rows.push(jsx(FindingRow, { finding: item, isSelected: flatIndex === selectedIndex }, item.id));
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
let contentHeight = 0;
|
|
983
|
-
for (const [, items] of grouped) {
|
|
984
|
-
contentHeight += 2 + items.length;
|
|
985
|
-
}
|
|
986
|
-
const showScrollbar = contentHeight > measuredViewportHeight && measuredViewportHeight > 0;
|
|
987
|
-
return jsxs(Box, { borderColor, borderStyle: "single", flexDirection: "column", flexGrow: 1, children: [
|
|
988
|
-
jsxs(Box, { flexShrink: 0, gap: 1, paddingX: 1, children: [
|
|
989
|
-
jsx(Text, { bold: true, inverse: true, children: " DOCTOR " }),
|
|
990
|
-
jsxs(Text, { wrap: "truncate", children: [
|
|
991
|
-
entries.length,
|
|
992
|
-
entries.length === totalAll ? "" : `/${String(totalAll)}`,
|
|
993
|
-
" finding",
|
|
994
|
-
entries.length === 1 ? "" : "s",
|
|
995
|
-
summary
|
|
996
|
-
] }),
|
|
997
|
-
severityFilter ? jsx(Text, { bold: true, color: SEVERITY_COLORS[severityFilter], inverse: true, children: ` ${severityFilter.toUpperCase()} ONLY ` }) : null,
|
|
998
|
-
fromCache ? jsx(Text, { bold: true, color: "cyan", inverse: true, children: " CACHED " }) : null,
|
|
999
|
-
jsxs(Text, { dimColor: true, children: [
|
|
1000
|
-
" · ",
|
|
1001
|
-
elapsedSeconds,
|
|
1002
|
-
"s"
|
|
1003
|
-
] })
|
|
1004
|
-
] }),
|
|
1005
|
-
jsx(Box, { flexShrink: 0, paddingX: 1, paddingY: 1, children: jsx(
|
|
1006
|
-
Tabs,
|
|
1007
|
-
{
|
|
1008
|
-
isFocused: focused,
|
|
1009
|
-
keyMap: { next: [], previous: [], useNumbers: false, useTab: false },
|
|
1010
|
-
onChange: () => {
|
|
1011
|
-
},
|
|
1012
|
-
showIndex: false,
|
|
1013
|
-
value: filterType,
|
|
1014
|
-
children: FILTER_TABS.map(({ id, label }) => jsx(Tab, { name: id, children: jsx(TabLabel, { count: sectionCounts[id], label, status: sectionStatus[id] }) }, id))
|
|
1015
|
-
}
|
|
1016
|
-
) }),
|
|
1017
|
-
(() => {
|
|
1018
|
-
const running = Object.keys(sectionStatus).filter((id) => sectionStatus[id] === "running" && sectionMessage[id]).map((id) => sectionMessage[id]);
|
|
1019
|
-
if (running.length === 0) {
|
|
1020
|
-
return null;
|
|
1021
|
-
}
|
|
1022
|
-
return jsx(Box, { flexShrink: 0, paddingX: 1, children: jsxs(Text, { dimColor: true, wrap: "truncate", children: [
|
|
1023
|
-
jsx(Spinner, { type: "dots" }),
|
|
1024
|
-
" ",
|
|
1025
|
-
running.join(" · ")
|
|
1026
|
-
] }) });
|
|
1027
|
-
})(),
|
|
1028
|
-
filterActive && jsxs(Box, { flexShrink: 0, paddingX: 1, children: [
|
|
1029
|
-
jsx(Text, { bold: true, color: "white", children: "/ " }),
|
|
1030
|
-
jsx(Text, { children: filterText }),
|
|
1031
|
-
jsx(Text, { inverse: true, children: " " })
|
|
1032
|
-
] }),
|
|
1033
|
-
jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", ref: contentRowRef, children: [
|
|
1034
|
-
jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingLeft: 1, children: jsx(Box, { flexDirection: "column", marginTop: -scrollOffset, children: rows.length > 0 ? rows : jsx(Box, { marginTop: 1, children: jsx(Text, { dimColor: true, children: "No findings match the current filter." }) }) }) }),
|
|
1035
|
-
showScrollbar && jsx(Box, { flexShrink: 0, marginLeft: 1, marginRight: 1, children: jsx(
|
|
1036
|
-
ScrollBar,
|
|
1037
|
-
{
|
|
1038
|
-
contentHeight,
|
|
1039
|
-
placement: "inset",
|
|
1040
|
-
scrollOffset,
|
|
1041
|
-
style: "block",
|
|
1042
|
-
viewportHeight: measuredViewportHeight
|
|
1043
|
-
}
|
|
1044
|
-
) })
|
|
1045
|
-
] }, `list-${filterType}-${filterText}`)
|
|
1046
|
-
] });
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
|
-
const buildUpdateAction = (finding) => {
|
|
1050
|
-
if (finding.kind === "outdated") {
|
|
1051
|
-
return {
|
|
1052
|
-
command: `vis update ${finding.entry.packageName}`,
|
|
1053
|
-
description: `Update ${finding.entry.packageName} to ${finding.entry.newRange}`
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
if (finding.kind === "duplicate") {
|
|
1057
|
-
return {
|
|
1058
|
-
command: `vis dedupe ${finding.pkg.name}`,
|
|
1059
|
-
description: `Dedupe ${finding.pkg.name} (${String(finding.pkg.versions.length)} versions)`
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
return void 0;
|
|
1063
|
-
};
|
|
1064
|
-
const buildOptimizeAction = (finding) => {
|
|
1065
|
-
if (finding.kind !== "optimization") {
|
|
1066
|
-
return void 0;
|
|
1067
|
-
}
|
|
1068
|
-
return {
|
|
1069
|
-
command: `vis optimize ${finding.entry.packageName}`,
|
|
1070
|
-
description: `Replace ${finding.entry.packageName} with ${finding.entry.replacement}`
|
|
1071
|
-
};
|
|
1072
|
-
};
|
|
1073
|
-
const buildAckAction = (finding) => {
|
|
1074
|
-
if (finding.kind !== "outdated" && finding.kind !== "vulnerability" && finding.kind !== "socket") {
|
|
1075
|
-
return void 0;
|
|
1076
|
-
}
|
|
1077
|
-
const pkg = finding.kind === "outdated" ? finding.entry.packageName : finding.packageName;
|
|
1078
|
-
const snippet = [
|
|
1079
|
-
"// Add to vis.config.ts:",
|
|
1080
|
-
"security: {",
|
|
1081
|
-
" acceptedRisks: {",
|
|
1082
|
-
` "${pkg}": {`,
|
|
1083
|
-
' reason: "explain why this risk is acceptable",',
|
|
1084
|
-
' expiresAt: "YYYY-MM-DD",',
|
|
1085
|
-
" },",
|
|
1086
|
-
" },",
|
|
1087
|
-
"},"
|
|
1088
|
-
].join("\n");
|
|
1089
|
-
return {
|
|
1090
|
-
command: snippet,
|
|
1091
|
-
configSnippet: snippet,
|
|
1092
|
-
description: `Acknowledge risk for ${pkg}`
|
|
1093
|
-
};
|
|
1094
|
-
};
|
|
1095
|
-
const MIN_HORIZONTAL_WIDTH = 100;
|
|
1096
|
-
const MIN_VIEWPORT_WIDTH = 40;
|
|
1097
|
-
const MIN_VIEWPORT_HEIGHT = 10;
|
|
1098
|
-
const VisDoctorApp = ({ autoExitSeconds = 0, banner, fromCache = false, startedAt, store }) => {
|
|
1099
|
-
const { exit } = useApp();
|
|
1100
|
-
const { columns, rows } = useWindowSize();
|
|
1101
|
-
const state = useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
1102
|
-
const [helpVisible, setHelpVisible] = useState(false);
|
|
1103
|
-
const [quitDialogVisible, setQuitDialogVisible] = useState(false);
|
|
1104
|
-
const [listScrollOffset, setListScrollOffset] = useState(0);
|
|
1105
|
-
const [now, setNow] = useState(() => Date.now());
|
|
1106
|
-
useEffect(() => {
|
|
1107
|
-
const id = setInterval(() => {
|
|
1108
|
-
setNow(Date.now());
|
|
1109
|
-
}, 1e3);
|
|
1110
|
-
return () => {
|
|
1111
|
-
clearInterval(id);
|
|
1112
|
-
};
|
|
1113
|
-
}, []);
|
|
1114
|
-
const elapsedMs = now - startedAt;
|
|
1115
|
-
const helpScrollRef = useRef(null);
|
|
1116
|
-
const detailScrollRef = useRef(null);
|
|
1117
|
-
const selectedFinding = state.entries[state.selectedIndex] ?? null;
|
|
1118
|
-
const sectionCounts = useMemo(() => {
|
|
1119
|
-
const counts = {
|
|
1120
|
-
dependencies: 0,
|
|
1121
|
-
optimization: 0,
|
|
1122
|
-
runtime: 0,
|
|
1123
|
-
security: 0
|
|
1124
|
-
};
|
|
1125
|
-
for (const finding of state.all) {
|
|
1126
|
-
counts[finding.section] += 1;
|
|
1127
|
-
}
|
|
1128
|
-
return counts;
|
|
1129
|
-
}, [state.all]);
|
|
1130
|
-
const bannerHeight = banner ? banner.hint ? 5 : 4 : 0;
|
|
1131
|
-
const activityLineHeight = useMemo(() => {
|
|
1132
|
-
for (const id of Object.keys(state.sectionStatus)) {
|
|
1133
|
-
if (state.sectionStatus[id] === "running" && state.sectionMessage[id]) {
|
|
1134
|
-
return 1;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
return 0;
|
|
1138
|
-
}, [state.sectionStatus, state.sectionMessage]);
|
|
1139
|
-
const isHorizontal = columns >= MIN_HORIZONTAL_WIDTH;
|
|
1140
|
-
const listPanelHeight = isHorizontal ? Math.max(1, rows - bannerHeight - 2) : Math.floor(rows * 0.55);
|
|
1141
|
-
const estimatedViewportHeight = useMemo(
|
|
1142
|
-
() => Math.max(1, listPanelHeight - 6 - activityLineHeight - (state.filterActive ? 1 : 0)),
|
|
1143
|
-
[listPanelHeight, activityLineHeight, state.filterActive]
|
|
1144
|
-
);
|
|
1145
|
-
const [measuredViewportHeight, setMeasuredViewportHeight] = useState(estimatedViewportHeight);
|
|
1146
|
-
const listViewportHeight = measuredViewportHeight > 0 ? measuredViewportHeight : estimatedViewportHeight;
|
|
1147
|
-
const contentHeight = useMemo(() => {
|
|
1148
|
-
let height = 0;
|
|
1149
|
-
for (const [, items] of state.grouped) {
|
|
1150
|
-
height += 2 + items.length;
|
|
1151
|
-
}
|
|
1152
|
-
return height;
|
|
1153
|
-
}, [state.grouped]);
|
|
1154
|
-
const maxScrollOffset = Math.max(0, contentHeight - listViewportHeight);
|
|
1155
|
-
useEffect(() => {
|
|
1156
|
-
setListScrollOffset((current) => Math.min(current, maxScrollOffset));
|
|
1157
|
-
}, [maxScrollOffset]);
|
|
1158
|
-
const getRowForIndex = useCallback(
|
|
1159
|
-
(index) => {
|
|
1160
|
-
let row = 0;
|
|
1161
|
-
let count = 0;
|
|
1162
|
-
for (const [, items] of state.grouped) {
|
|
1163
|
-
row += 2;
|
|
1164
|
-
for (const _ of items) {
|
|
1165
|
-
if (count === index) {
|
|
1166
|
-
return row;
|
|
1167
|
-
}
|
|
1168
|
-
row += 1;
|
|
1169
|
-
count += 1;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
return row;
|
|
1173
|
-
},
|
|
1174
|
-
[state.grouped]
|
|
1175
|
-
);
|
|
1176
|
-
const scrollToIndex = useCallback(
|
|
1177
|
-
(index) => {
|
|
1178
|
-
const targetRow = getRowForIndex(index);
|
|
1179
|
-
setListScrollOffset((current) => {
|
|
1180
|
-
if (targetRow > current + listViewportHeight - 2) {
|
|
1181
|
-
return Math.min(maxScrollOffset, Math.max(0, targetRow - listViewportHeight + 2));
|
|
1182
|
-
}
|
|
1183
|
-
if (targetRow < current + 1) {
|
|
1184
|
-
return Math.max(0, targetRow - 1);
|
|
1185
|
-
}
|
|
1186
|
-
return current;
|
|
1187
|
-
});
|
|
1188
|
-
},
|
|
1189
|
-
[getRowForIndex, listViewportHeight, maxScrollOffset]
|
|
1190
|
-
);
|
|
1191
|
-
useEffect(() => {
|
|
1192
|
-
detailScrollRef.current?.scrollToTop();
|
|
1193
|
-
}, [selectedFinding?.id]);
|
|
1194
|
-
useInput(
|
|
1195
|
-
(input, key) => {
|
|
1196
|
-
if (input === "c" && key.ctrl) {
|
|
1197
|
-
exit();
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
if (quitDialogVisible) {
|
|
1201
|
-
return;
|
|
1202
|
-
}
|
|
1203
|
-
if (helpVisible) {
|
|
1204
|
-
if (key.escape || input === "?") {
|
|
1205
|
-
setHelpVisible(false);
|
|
1206
|
-
} else if (input === "q") {
|
|
1207
|
-
setHelpVisible(false);
|
|
1208
|
-
setQuitDialogVisible(true);
|
|
1209
|
-
} else if (key.downArrow || input === "j") {
|
|
1210
|
-
helpScrollRef.current?.scrollBy(1);
|
|
1211
|
-
} else if (key.upArrow || input === "k") {
|
|
1212
|
-
helpScrollRef.current?.scrollBy(-1);
|
|
1213
|
-
}
|
|
1214
|
-
return;
|
|
1215
|
-
}
|
|
1216
|
-
if (input === "?") {
|
|
1217
|
-
setHelpVisible(true);
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
if (input === "q") {
|
|
1221
|
-
setQuitDialogVisible(true);
|
|
1222
|
-
return;
|
|
1223
|
-
}
|
|
1224
|
-
if (key.tab) {
|
|
1225
|
-
store.setFocusedPanel(state.focusedPanel === "list" ? "detail" : "list");
|
|
1226
|
-
return;
|
|
1227
|
-
}
|
|
1228
|
-
if (state.filterActive) {
|
|
1229
|
-
if (key.escape || key.return) {
|
|
1230
|
-
store.setFilterActive(false);
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
if (key.backspace) {
|
|
1234
|
-
setListScrollOffset(0);
|
|
1235
|
-
store.setFilter(state.filterText.slice(0, -1));
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
if (input && !key.ctrl && !key.meta) {
|
|
1239
|
-
setListScrollOffset(0);
|
|
1240
|
-
store.setFilter(state.filterText + input);
|
|
1241
|
-
}
|
|
1242
|
-
return;
|
|
1243
|
-
}
|
|
1244
|
-
if (state.focusedPanel === "list" && (key.leftArrow || key.rightArrow)) {
|
|
1245
|
-
const currentIndex = FILTER_TABS.findIndex((tab) => tab.id === state.filterType);
|
|
1246
|
-
const nextIndex = key.rightArrow ? (currentIndex + 1) % FILTER_TABS.length : (currentIndex - 1 + FILTER_TABS.length) % FILTER_TABS.length;
|
|
1247
|
-
setListScrollOffset(0);
|
|
1248
|
-
detailScrollRef.current?.scrollToTop();
|
|
1249
|
-
store.setFilterType(FILTER_TABS[nextIndex].id);
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
if (state.focusedPanel === "list") {
|
|
1253
|
-
if (key.downArrow || input === "j") {
|
|
1254
|
-
const next = Math.min(state.selectedIndex + 1, state.entries.length - 1);
|
|
1255
|
-
store.setSelectedIndex(next);
|
|
1256
|
-
scrollToIndex(next);
|
|
1257
|
-
return;
|
|
1258
|
-
}
|
|
1259
|
-
if (key.upArrow || input === "k") {
|
|
1260
|
-
const next = Math.max(state.selectedIndex - 1, 0);
|
|
1261
|
-
store.setSelectedIndex(next);
|
|
1262
|
-
scrollToIndex(next);
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
if (key.pageDown) {
|
|
1266
|
-
const next = Math.min(state.selectedIndex + 10, state.entries.length - 1);
|
|
1267
|
-
store.setSelectedIndex(next);
|
|
1268
|
-
scrollToIndex(next);
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
if (key.pageUp) {
|
|
1272
|
-
const next = Math.max(state.selectedIndex - 10, 0);
|
|
1273
|
-
store.setSelectedIndex(next);
|
|
1274
|
-
scrollToIndex(next);
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
if (key.home) {
|
|
1278
|
-
store.setSelectedIndex(0);
|
|
1279
|
-
setListScrollOffset(0);
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
if (key.end) {
|
|
1283
|
-
const last = state.entries.length - 1;
|
|
1284
|
-
store.setSelectedIndex(last);
|
|
1285
|
-
scrollToIndex(last);
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1288
|
-
if (input === "/") {
|
|
1289
|
-
store.setFilterActive(true);
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
if (input === "e") {
|
|
1293
|
-
store.setSeverityFilter(state.severityFilter === "error" ? void 0 : "error");
|
|
1294
|
-
setListScrollOffset(0);
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
if (input === "w") {
|
|
1298
|
-
store.setSeverityFilter(state.severityFilter === "warn" ? void 0 : "warn");
|
|
1299
|
-
setListScrollOffset(0);
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
if (input === "u" && selectedFinding) {
|
|
1303
|
-
const action = buildUpdateAction(selectedFinding);
|
|
1304
|
-
if (action) {
|
|
1305
|
-
store.setPendingAction(action);
|
|
1306
|
-
exit();
|
|
1307
|
-
}
|
|
1308
|
-
return;
|
|
1309
|
-
}
|
|
1310
|
-
if (input === "o" && selectedFinding) {
|
|
1311
|
-
const action = buildOptimizeAction(selectedFinding);
|
|
1312
|
-
if (action) {
|
|
1313
|
-
store.setPendingAction(action);
|
|
1314
|
-
exit();
|
|
1315
|
-
}
|
|
1316
|
-
return;
|
|
1317
|
-
}
|
|
1318
|
-
if (input === "a" && selectedFinding) {
|
|
1319
|
-
const action = buildAckAction(selectedFinding);
|
|
1320
|
-
if (action) {
|
|
1321
|
-
store.setPendingAction(action);
|
|
1322
|
-
exit();
|
|
1323
|
-
}
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
if (input === "d") {
|
|
1327
|
-
store.setFocusedPanel("detail");
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
if (key.escape || key.leftArrow) {
|
|
1333
|
-
store.setFocusedPanel("list");
|
|
1334
|
-
return;
|
|
1335
|
-
}
|
|
1336
|
-
if (key.downArrow || input === "j") {
|
|
1337
|
-
detailScrollRef.current?.scrollBy(1);
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
if (key.upArrow || input === "k") {
|
|
1341
|
-
detailScrollRef.current?.scrollBy(-1);
|
|
1342
|
-
return;
|
|
1343
|
-
}
|
|
1344
|
-
if (key.pageDown) {
|
|
1345
|
-
detailScrollRef.current?.scrollBy(10);
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
if (key.pageUp) {
|
|
1349
|
-
detailScrollRef.current?.scrollBy(-10);
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
if (key.home) {
|
|
1353
|
-
detailScrollRef.current?.scrollToTop();
|
|
1354
|
-
return;
|
|
1355
|
-
}
|
|
1356
|
-
if (key.end) {
|
|
1357
|
-
detailScrollRef.current?.scrollToBottom();
|
|
1358
|
-
}
|
|
1359
|
-
},
|
|
1360
|
-
{ isActive: true }
|
|
1361
|
-
);
|
|
1362
|
-
if (columns < MIN_VIEWPORT_WIDTH || rows < MIN_VIEWPORT_HEIGHT) {
|
|
1363
|
-
return jsx(Box, { alignItems: "center", height: rows, justifyContent: "center", width: columns, children: jsxs(Text, { color: "yellow", children: [
|
|
1364
|
-
"Terminal too small (",
|
|
1365
|
-
columns,
|
|
1366
|
-
"x",
|
|
1367
|
-
rows,
|
|
1368
|
-
")"
|
|
1369
|
-
] }) });
|
|
1370
|
-
}
|
|
1371
|
-
const detailFocused = state.focusedPanel === "detail";
|
|
1372
|
-
const footerItems = [
|
|
1373
|
-
jsxs(Box, { gap: 1, children: [
|
|
1374
|
-
jsx(Text, { bold: true, color: "white", children: "q" }),
|
|
1375
|
-
jsx(Text, { dimColor: true, children: "QUIT" })
|
|
1376
|
-
] }, "q"),
|
|
1377
|
-
jsxs(Box, { gap: 1, children: [
|
|
1378
|
-
jsx(Text, { bold: true, color: "white", children: "?" }),
|
|
1379
|
-
jsx(Text, { dimColor: true, children: "HELP" })
|
|
1380
|
-
] }, "?"),
|
|
1381
|
-
jsxs(Box, { gap: 1, children: [
|
|
1382
|
-
jsx(Text, { bold: true, color: "white", children: "↑↓" }),
|
|
1383
|
-
jsx(Text, { dimColor: true, children: detailFocused ? "SCROLL" : "NAV" })
|
|
1384
|
-
] }, "nav"),
|
|
1385
|
-
detailFocused ? jsxs(Box, { gap: 1, children: [
|
|
1386
|
-
jsx(Text, { bold: true, color: "white", children: "←/Esc" }),
|
|
1387
|
-
jsx(Text, { dimColor: true, children: "LIST" })
|
|
1388
|
-
] }, "lr") : jsxs(Box, { gap: 1, children: [
|
|
1389
|
-
jsx(Text, { bold: true, color: "white", children: "←→" }),
|
|
1390
|
-
jsx(Text, { dimColor: true, children: "SECTION" })
|
|
1391
|
-
] }, "lr"),
|
|
1392
|
-
jsxs(Box, { gap: 1, children: [
|
|
1393
|
-
jsx(Text, { bold: true, color: "white", children: "/" }),
|
|
1394
|
-
jsx(Text, { dimColor: true, children: "SEARCH" })
|
|
1395
|
-
] }, "search"),
|
|
1396
|
-
jsxs(Box, { gap: 1, children: [
|
|
1397
|
-
jsx(Text, { bold: true, color: "white", children: "e/w" }),
|
|
1398
|
-
jsx(Text, { dimColor: true, children: "SEVERITY" })
|
|
1399
|
-
] }, "sev"),
|
|
1400
|
-
jsxs(Box, { gap: 1, children: [
|
|
1401
|
-
jsx(Text, { bold: true, color: "white", children: "u/o/a" }),
|
|
1402
|
-
jsx(Text, { dimColor: true, children: "ACTION" })
|
|
1403
|
-
] }, "actions"),
|
|
1404
|
-
jsxs(Box, { gap: 1, children: [
|
|
1405
|
-
jsx(Text, { bold: true, color: "white", children: "Tab" }),
|
|
1406
|
-
jsx(Text, { dimColor: true, children: "PANEL" })
|
|
1407
|
-
] }, "tab")
|
|
1408
|
-
];
|
|
1409
|
-
const footer = jsx(Box, { borderBottom: false, borderColor: "gray", borderLeft: false, borderRight: false, borderStyle: "single", flexShrink: 0, children: jsx(Box, { gap: 2, overflow: "hidden", paddingX: 1, children: footerItems }) });
|
|
1410
|
-
const helpPopup = jsxs(
|
|
1411
|
-
Dialog,
|
|
1412
|
-
{
|
|
1413
|
-
footer: jsxs(Text, { dimColor: true, children: [
|
|
1414
|
-
jsx(Text, { bold: true, color: "white", children: "↑↓" }),
|
|
1415
|
-
" scroll ",
|
|
1416
|
-
jsx(Text, { bold: true, color: "white", children: "?" }),
|
|
1417
|
-
"/",
|
|
1418
|
-
jsx(Text, { bold: true, color: "white", children: "Esc" }),
|
|
1419
|
-
" close"
|
|
1420
|
-
] }),
|
|
1421
|
-
scrollRef: helpScrollRef,
|
|
1422
|
-
title: "DOCTOR — KEYBOARD SHORTCUTS",
|
|
1423
|
-
visible: helpVisible,
|
|
1424
|
-
width: 56,
|
|
1425
|
-
children: [
|
|
1426
|
-
jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1427
|
-
jsxs(Box, { marginBottom: 1, children: [
|
|
1428
|
-
jsx(Text, { dimColor: true, children: "── " }),
|
|
1429
|
-
jsx(Text, { bold: true, color: "white", children: "NAVIGATION" })
|
|
1430
|
-
] }),
|
|
1431
|
-
jsxs(Box, { children: [
|
|
1432
|
-
jsx(Box, { width: 26, children: jsxs(Text, { children: [
|
|
1433
|
-
jsx(Text, { bold: true, color: "white", children: " ↑/k " }),
|
|
1434
|
-
jsx(Text, { dimColor: true, children: "Move up" })
|
|
1435
|
-
] }) }),
|
|
1436
|
-
jsxs(Text, { children: [
|
|
1437
|
-
jsx(Text, { bold: true, color: "white", children: " ↓/j " }),
|
|
1438
|
-
jsx(Text, { dimColor: true, children: "Move down" })
|
|
1439
|
-
] })
|
|
1440
|
-
] }),
|
|
1441
|
-
jsxs(Box, { children: [
|
|
1442
|
-
jsx(Box, { width: 26, children: jsxs(Text, { children: [
|
|
1443
|
-
jsx(Text, { bold: true, color: "white", children: " PgUp" }),
|
|
1444
|
-
jsx(Text, { dimColor: true, children: " Jump up 10" })
|
|
1445
|
-
] }) }),
|
|
1446
|
-
jsxs(Text, { children: [
|
|
1447
|
-
jsx(Text, { bold: true, color: "white", children: " PgDn" }),
|
|
1448
|
-
jsx(Text, { dimColor: true, children: " Jump down 10" })
|
|
1449
|
-
] })
|
|
1450
|
-
] }),
|
|
1451
|
-
jsxs(Box, { children: [
|
|
1452
|
-
jsx(Box, { width: 26, children: jsxs(Text, { children: [
|
|
1453
|
-
jsx(Text, { bold: true, color: "white", children: " Home" }),
|
|
1454
|
-
jsx(Text, { dimColor: true, children: " Jump to top" })
|
|
1455
|
-
] }) }),
|
|
1456
|
-
jsxs(Text, { children: [
|
|
1457
|
-
jsx(Text, { bold: true, color: "white", children: " End" }),
|
|
1458
|
-
jsx(Text, { dimColor: true, children: " Jump to bottom" })
|
|
1459
|
-
] })
|
|
1460
|
-
] }),
|
|
1461
|
-
jsxs(Text, { children: [
|
|
1462
|
-
jsx(Text, { bold: true, color: "white", children: " Tab" }),
|
|
1463
|
-
jsx(Text, { dimColor: true, children: " Switch panel" })
|
|
1464
|
-
] }),
|
|
1465
|
-
jsxs(Text, { children: [
|
|
1466
|
-
jsx(Text, { bold: true, color: "white", children: " →/←" }),
|
|
1467
|
-
jsx(Text, { dimColor: true, children: " Section tabs (list) / Focus list (detail)" })
|
|
1468
|
-
] })
|
|
1469
|
-
] }),
|
|
1470
|
-
jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1471
|
-
jsxs(Box, { marginBottom: 1, children: [
|
|
1472
|
-
jsx(Text, { dimColor: true, children: "── " }),
|
|
1473
|
-
jsx(Text, { bold: true, color: "white", children: "FILTER" })
|
|
1474
|
-
] }),
|
|
1475
|
-
jsxs(Text, { children: [
|
|
1476
|
-
jsx(Text, { bold: true, color: "white", children: " /" }),
|
|
1477
|
-
jsx(Text, { dimColor: true, children: " Open text filter (Esc/Enter to close)" })
|
|
1478
|
-
] }),
|
|
1479
|
-
jsxs(Text, { children: [
|
|
1480
|
-
jsx(Text, { bold: true, color: "white", children: " e" }),
|
|
1481
|
-
jsx(Text, { dimColor: true, children: " Toggle errors-only filter" })
|
|
1482
|
-
] }),
|
|
1483
|
-
jsxs(Text, { children: [
|
|
1484
|
-
jsx(Text, { bold: true, color: "white", children: " w" }),
|
|
1485
|
-
jsx(Text, { dimColor: true, children: " Toggle warns-only filter" })
|
|
1486
|
-
] })
|
|
1487
|
-
] }),
|
|
1488
|
-
jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1489
|
-
jsxs(Box, { marginBottom: 1, children: [
|
|
1490
|
-
jsx(Text, { dimColor: true, children: "── " }),
|
|
1491
|
-
jsx(Text, { bold: true, color: "white", children: "ACTIONS" })
|
|
1492
|
-
] }),
|
|
1493
|
-
jsxs(Text, { children: [
|
|
1494
|
-
jsx(Text, { bold: true, color: "white", children: " u" }),
|
|
1495
|
-
jsx(Text, { dimColor: true, children: " Exit + suggest update / dedupe command" })
|
|
1496
|
-
] }),
|
|
1497
|
-
jsxs(Text, { children: [
|
|
1498
|
-
jsx(Text, { bold: true, color: "white", children: " o" }),
|
|
1499
|
-
jsx(Text, { dimColor: true, children: " Exit + suggest optimize command" })
|
|
1500
|
-
] }),
|
|
1501
|
-
jsxs(Text, { children: [
|
|
1502
|
-
jsx(Text, { bold: true, color: "white", children: " a" }),
|
|
1503
|
-
jsx(Text, { dimColor: true, children: " Exit + print risk-ack snippet" })
|
|
1504
|
-
] }),
|
|
1505
|
-
jsxs(Text, { children: [
|
|
1506
|
-
jsx(Text, { bold: true, color: "white", children: " d" }),
|
|
1507
|
-
jsx(Text, { dimColor: true, children: " Focus detail panel" })
|
|
1508
|
-
] })
|
|
1509
|
-
] }),
|
|
1510
|
-
jsxs(Box, { flexDirection: "column", children: [
|
|
1511
|
-
jsxs(Box, { marginBottom: 1, children: [
|
|
1512
|
-
jsx(Text, { dimColor: true, children: "── " }),
|
|
1513
|
-
jsx(Text, { bold: true, color: "white", children: "EXIT" })
|
|
1514
|
-
] }),
|
|
1515
|
-
jsxs(Text, { children: [
|
|
1516
|
-
jsx(Text, { bold: true, color: "white", children: " q" }),
|
|
1517
|
-
jsx(Text, { dimColor: true, children: " Quit (with countdown)" })
|
|
1518
|
-
] }),
|
|
1519
|
-
jsxs(Text, { children: [
|
|
1520
|
-
jsx(Text, { bold: true, color: "white", children: " Ctrl+C" }),
|
|
1521
|
-
jsx(Text, { dimColor: true, children: " Quit immediately" })
|
|
1522
|
-
] })
|
|
1523
|
-
] })
|
|
1524
|
-
]
|
|
1525
|
-
}
|
|
1526
|
-
);
|
|
1527
|
-
const listPanel = jsx(
|
|
1528
|
-
DoctorListPanel,
|
|
1529
|
-
{
|
|
1530
|
-
elapsedMs,
|
|
1531
|
-
entries: state.entries,
|
|
1532
|
-
filterActive: state.filterActive,
|
|
1533
|
-
filterText: state.filterText,
|
|
1534
|
-
filterType: state.filterType,
|
|
1535
|
-
focused: state.focusedPanel === "list",
|
|
1536
|
-
fromCache,
|
|
1537
|
-
grouped: state.grouped,
|
|
1538
|
-
onViewportHeightChange: setMeasuredViewportHeight,
|
|
1539
|
-
scrollOffset: listScrollOffset,
|
|
1540
|
-
sectionCounts,
|
|
1541
|
-
sectionMessage: state.sectionMessage,
|
|
1542
|
-
sectionStatus: state.sectionStatus,
|
|
1543
|
-
selectedIndex: state.selectedIndex,
|
|
1544
|
-
severityFilter: state.severityFilter,
|
|
1545
|
-
totalAll: state.all.length,
|
|
1546
|
-
viewportHeight: listViewportHeight
|
|
1547
|
-
}
|
|
1548
|
-
);
|
|
1549
|
-
const bannerNode = banner ? jsx(ConfigBanner, { hint: banner.hint, message: banner.message, severity: banner.severity, title: banner.title }) : null;
|
|
1550
|
-
const detailPanel = jsx(DoctorDetailPanel, { finding: selectedFinding, focused: state.focusedPanel === "detail", scrollRef: detailScrollRef });
|
|
1551
|
-
if (isHorizontal) {
|
|
1552
|
-
const detailWidth = Math.floor(columns * 0.4);
|
|
1553
|
-
return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
|
|
1554
|
-
bannerNode,
|
|
1555
|
-
jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [
|
|
1556
|
-
jsx(Box, { flexGrow: 1, children: listPanel }),
|
|
1557
|
-
jsx(Box, { width: detailWidth, children: detailPanel })
|
|
1558
|
-
] }),
|
|
1559
|
-
footer,
|
|
1560
|
-
jsx(
|
|
1561
|
-
QuitDialog,
|
|
1562
|
-
{
|
|
1563
|
-
autoExitSeconds: autoExitSeconds || 3,
|
|
1564
|
-
onCancel: () => {
|
|
1565
|
-
setQuitDialogVisible(false);
|
|
1566
|
-
},
|
|
1567
|
-
visible: quitDialogVisible
|
|
1568
|
-
}
|
|
1569
|
-
),
|
|
1570
|
-
helpPopup
|
|
1571
|
-
] });
|
|
1572
|
-
}
|
|
1573
|
-
return jsxs(Box, { flexDirection: "column", height: rows, width: columns, children: [
|
|
1574
|
-
bannerNode,
|
|
1575
|
-
jsx(Box, { height: listPanelHeight, children: listPanel }),
|
|
1576
|
-
jsx(Box, { flexGrow: 1, children: detailPanel }),
|
|
1577
|
-
footer,
|
|
1578
|
-
jsx(
|
|
1579
|
-
QuitDialog,
|
|
1580
|
-
{
|
|
1581
|
-
autoExitSeconds: autoExitSeconds || 3,
|
|
1582
|
-
onCancel: () => {
|
|
1583
|
-
setQuitDialogVisible(false);
|
|
1584
|
-
},
|
|
1585
|
-
visible: quitDialogVisible
|
|
1586
|
-
}
|
|
1587
|
-
),
|
|
1588
|
-
helpPopup
|
|
1589
|
-
] });
|
|
1590
|
-
};
|
|
1591
|
-
|
|
1592
|
-
const escapeRegex = (literal) => literal.replaceAll(/[$()+.?[\\\]^{|}]/g, String.raw`\$&`);
|
|
1593
|
-
const compilePattern = (pattern) => {
|
|
1594
|
-
const segments = pattern.split("*").map(escapeRegex);
|
|
1595
|
-
return new RegExp(`^${segments.join(".*")}$`, "i");
|
|
1596
|
-
};
|
|
1597
|
-
const parseFilterPatterns = (raw) => {
|
|
1598
|
-
if (!raw) {
|
|
1599
|
-
return [];
|
|
1600
|
-
}
|
|
1601
|
-
return raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0).map(compilePattern);
|
|
1602
|
-
};
|
|
1603
|
-
const matchesAny = (name, patterns) => {
|
|
1604
|
-
for (const pattern of patterns) {
|
|
1605
|
-
if (pattern.test(name)) {
|
|
1606
|
-
return true;
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
return false;
|
|
1610
|
-
};
|
|
1611
|
-
const applyFilter = (results, patterns) => {
|
|
1612
|
-
if (patterns.length === 0) {
|
|
1613
|
-
return results;
|
|
1614
|
-
}
|
|
1615
|
-
const outdated = results.outdated.filter((entry) => matchesAny(entry.packageName, patterns));
|
|
1616
|
-
const duplicates = results.duplicates.filter((dup) => matchesAny(dup.name, patterns));
|
|
1617
|
-
const optimizations = results.optimizations.filter((entry) => matchesAny(entry.packageName, patterns));
|
|
1618
|
-
let vulnCount = 0;
|
|
1619
|
-
let socketAlerts = 0;
|
|
1620
|
-
let socketLowScore = 0;
|
|
1621
|
-
for (const entry of outdated) {
|
|
1622
|
-
if (entry.vulnerabilities) {
|
|
1623
|
-
vulnCount += entry.vulnerabilities.length;
|
|
1624
|
-
}
|
|
1625
|
-
if (entry.socketReport) {
|
|
1626
|
-
socketAlerts += entry.socketReport.alerts.length;
|
|
1627
|
-
if (entry.socketReport.score.overall < DEFAULT_LOW_SCORE_THRESHOLD) {
|
|
1628
|
-
socketLowScore += 1;
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
return {
|
|
1633
|
-
...results,
|
|
1634
|
-
duplicates,
|
|
1635
|
-
optimizations,
|
|
1636
|
-
outdated,
|
|
1637
|
-
socketIssues: { alerts: socketAlerts, lowScore: socketLowScore },
|
|
1638
|
-
vulnCount
|
|
1639
|
-
};
|
|
1640
|
-
};
|
|
1641
|
-
const filterFindingsByPattern = (findings, patterns) => {
|
|
1642
|
-
if (patterns.length === 0) {
|
|
1643
|
-
return [...findings];
|
|
1644
|
-
}
|
|
1645
|
-
return findings.filter((finding) => {
|
|
1646
|
-
if (finding.kind === "runtime") {
|
|
1647
|
-
return true;
|
|
1648
|
-
}
|
|
1649
|
-
const name = finding.kind === "duplicate" ? finding.pkg.name : finding.kind === "outdated" || finding.kind === "optimization" ? finding.entry.packageName : finding.packageName;
|
|
1650
|
-
return matchesAny(name, patterns);
|
|
1651
|
-
});
|
|
1652
|
-
};
|
|
1653
|
-
|
|
1654
|
-
const SECTION_IDS = ["dependencies", "security", "optimization", "runtime"];
|
|
1655
|
-
const parseSectionList = (raw) => {
|
|
1656
|
-
const parsed = /* @__PURE__ */ new Set();
|
|
1657
|
-
if (!raw) {
|
|
1658
|
-
return parsed;
|
|
1659
|
-
}
|
|
1660
|
-
for (const token of raw.split(",")) {
|
|
1661
|
-
const trimmed = token.trim().toLowerCase();
|
|
1662
|
-
if (SECTION_IDS.includes(trimmed)) {
|
|
1663
|
-
parsed.add(trimmed);
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
return parsed;
|
|
1667
|
-
};
|
|
1668
|
-
const resolveSections = (only, skip) => {
|
|
1669
|
-
if (only !== void 0 && only !== "") {
|
|
1670
|
-
return parseSectionList(only);
|
|
1671
|
-
}
|
|
1672
|
-
const skipped = parseSectionList(skip);
|
|
1673
|
-
return new Set(SECTION_IDS.filter((s) => !skipped.has(s)));
|
|
1674
|
-
};
|
|
1675
|
-
const summarizeOptimizations = (entries) => {
|
|
1676
|
-
const counts = { micro: 0, native: 0, preferred: 0, socket: 0, total: entries.length };
|
|
1677
|
-
for (const entry of entries) {
|
|
1678
|
-
switch (entry.category) {
|
|
1679
|
-
case "micro-utility": {
|
|
1680
|
-
counts.micro += 1;
|
|
1681
|
-
break;
|
|
1682
|
-
}
|
|
1683
|
-
case "native": {
|
|
1684
|
-
counts.native += 1;
|
|
1685
|
-
break;
|
|
1686
|
-
}
|
|
1687
|
-
case "preferred": {
|
|
1688
|
-
counts.preferred += 1;
|
|
1689
|
-
break;
|
|
1690
|
-
}
|
|
1691
|
-
case "socket": {
|
|
1692
|
-
counts.socket += 1;
|
|
1693
|
-
break;
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
return counts;
|
|
1698
|
-
};
|
|
1699
|
-
const sectionStatus = (results, section) => {
|
|
1700
|
-
if (!results.sections.has(section)) {
|
|
1701
|
-
return "skip";
|
|
1702
|
-
}
|
|
1703
|
-
switch (section) {
|
|
1704
|
-
case "dependencies": {
|
|
1705
|
-
if (results.outdated.length > 0 || results.duplicates.length > 0) {
|
|
1706
|
-
return "warn";
|
|
1707
|
-
}
|
|
1708
|
-
return "ok";
|
|
1709
|
-
}
|
|
1710
|
-
case "optimization": {
|
|
1711
|
-
return results.optimizations.length > 0 ? "warn" : "ok";
|
|
1712
|
-
}
|
|
1713
|
-
case "runtime": {
|
|
1714
|
-
return results.runtime.some((d) => d.status === "warn") ? "warn" : "ok";
|
|
1715
|
-
}
|
|
1716
|
-
case "security": {
|
|
1717
|
-
if (results.vulnCount > 0 || results.socketIssues.alerts > 0) {
|
|
1718
|
-
return "error";
|
|
1719
|
-
}
|
|
1720
|
-
if (results.socketIssues.lowScore > 0) {
|
|
1721
|
-
return "warn";
|
|
1722
|
-
}
|
|
1723
|
-
return "ok";
|
|
1724
|
-
}
|
|
1725
|
-
default: {
|
|
1726
|
-
return "ok";
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
const buildJsonPayload = (results, packageManagerName) => {
|
|
1731
|
-
const counts = summarizeOptimizations(results.optimizations);
|
|
1732
|
-
const sectionsObj = {
|
|
1733
|
-
dependencies: sectionStatus(results, "dependencies"),
|
|
1734
|
-
optimization: sectionStatus(results, "optimization"),
|
|
1735
|
-
runtime: sectionStatus(results, "runtime"),
|
|
1736
|
-
security: sectionStatus(results, "security")
|
|
1737
|
-
};
|
|
1738
|
-
const statuses = /* @__PURE__ */ new Set([...Object.values(sectionsObj), results.supplyChain.status]);
|
|
1739
|
-
const overall = statuses.has("error") ? "error" : statuses.has("warn") ? "warn" : "ok";
|
|
1740
|
-
return {
|
|
1741
|
-
dependencies: {
|
|
1742
|
-
duplicates: results.duplicates.length,
|
|
1743
|
-
installed: results.installedCount,
|
|
1744
|
-
outdated: results.outdated.length,
|
|
1745
|
-
status: sectionsObj.dependencies
|
|
1746
|
-
},
|
|
1747
|
-
elapsedMs: results.elapsedMs,
|
|
1748
|
-
optimizations: {
|
|
1749
|
-
microUtilities: counts.micro,
|
|
1750
|
-
native: counts.native,
|
|
1751
|
-
preferred: counts.preferred,
|
|
1752
|
-
socket: counts.socket,
|
|
1753
|
-
status: sectionsObj.optimization,
|
|
1754
|
-
total: counts.total
|
|
1755
|
-
},
|
|
1756
|
-
packageManager: packageManagerName,
|
|
1757
|
-
runtime: results.runtime.map((d) => {
|
|
1758
|
-
return {
|
|
1759
|
-
detail: d.detail,
|
|
1760
|
-
id: d.id,
|
|
1761
|
-
message: d.message,
|
|
1762
|
-
status: d.status
|
|
1763
|
-
};
|
|
1764
|
-
}),
|
|
1765
|
-
runtimeStatus: sectionsObj.runtime,
|
|
1766
|
-
security: {
|
|
1767
|
-
alerts: results.socketIssues.alerts,
|
|
1768
|
-
lowScorePackages: results.socketIssues.lowScore,
|
|
1769
|
-
status: sectionsObj.security,
|
|
1770
|
-
vulnerabilities: results.vulnCount
|
|
1771
|
-
},
|
|
1772
|
-
status: overall,
|
|
1773
|
-
supplyChain: {
|
|
1774
|
-
findings: results.supplyChain.findings.map((f) => {
|
|
1775
|
-
return { detail: f.detail, label: f.label, severity: f.severity };
|
|
1776
|
-
}),
|
|
1777
|
-
status: results.supplyChain.status
|
|
1778
|
-
},
|
|
1779
|
-
workspaces: results.workspaceCount
|
|
1780
|
-
};
|
|
1781
|
-
};
|
|
1782
|
-
const shouldFail = (results, strict) => {
|
|
1783
|
-
const hasRuntimeWarn = results.runtime.some((d) => d.status === "warn");
|
|
1784
|
-
const baseFail = results.vulnCount > 0 || results.socketIssues.alerts > 0;
|
|
1785
|
-
if (!strict) {
|
|
1786
|
-
return baseFail;
|
|
1787
|
-
}
|
|
1788
|
-
return baseFail || results.outdated.length > 0 || results.duplicates.length > 0 || hasRuntimeWarn;
|
|
1789
|
-
};
|
|
1790
|
-
|
|
1791
|
-
const rollUpStatus = (findings) => {
|
|
1792
|
-
if (findings.some((f) => f.severity === "error")) {
|
|
1793
|
-
return "error";
|
|
1794
|
-
}
|
|
1795
|
-
if (findings.some((f) => f.severity === "warn")) {
|
|
1796
|
-
return "warn";
|
|
1797
|
-
}
|
|
1798
|
-
return "ok";
|
|
1799
|
-
};
|
|
1800
|
-
const buildSupplyChainPosture = (config) => {
|
|
1801
|
-
const findings = [];
|
|
1802
|
-
const security = config?.security;
|
|
1803
|
-
if (!security) {
|
|
1804
|
-
findings.push({
|
|
1805
|
-
detail: "Use defineConfig() from '@visulima/vis/config' to apply secure defaults.",
|
|
1806
|
-
label: "No security config — running with the PM's native defaults",
|
|
1807
|
-
severity: "warn"
|
|
1808
|
-
});
|
|
1809
|
-
return { findings, status: rollUpStatus(findings) };
|
|
1810
|
-
}
|
|
1811
|
-
if (security.minimumReleaseAge === void 0) {
|
|
1812
|
-
findings.push({
|
|
1813
|
-
detail: "Set security.minimumReleaseAge to block packages published in the last N minutes (mitigates supply-chain attacks).",
|
|
1814
|
-
label: "minimumReleaseAge is not set",
|
|
1815
|
-
severity: "warn"
|
|
1816
|
-
});
|
|
1817
|
-
} else if (security.minimumReleaseAge === 0) {
|
|
1818
|
-
findings.push({
|
|
1819
|
-
detail: "New packages can be installed immediately after publishing. Consider setting a non-zero cooldown.",
|
|
1820
|
-
label: "minimumReleaseAge is explicitly 0",
|
|
1821
|
-
severity: "warn"
|
|
1822
|
-
});
|
|
1823
|
-
} else {
|
|
1824
|
-
findings.push({
|
|
1825
|
-
label: `minimumReleaseAge: ${String(security.minimumReleaseAge)} minutes`,
|
|
1826
|
-
severity: "ok"
|
|
1827
|
-
});
|
|
1828
|
-
}
|
|
1829
|
-
if (security.trustPolicy === void 0 || security.trustPolicy === "off") {
|
|
1830
|
-
findings.push({
|
|
1831
|
-
detail: "Packages whose trust level has decreased will not be blocked. Consider 'no-downgrade'.",
|
|
1832
|
-
label: `trustPolicy: ${security.trustPolicy ?? "not set"}`,
|
|
1833
|
-
severity: "warn"
|
|
1834
|
-
});
|
|
1835
|
-
} else {
|
|
1836
|
-
findings.push({
|
|
1837
|
-
label: `trustPolicy: ${security.trustPolicy}`,
|
|
1838
|
-
severity: "ok"
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
if (security.blockExoticSubdeps === void 0 || !security.blockExoticSubdeps) {
|
|
1842
|
-
findings.push({
|
|
1843
|
-
detail: "Transitive dependencies can pull code from git repos or tarball URLs. Set to true to block.",
|
|
1844
|
-
label: `blockExoticSubdeps: ${String(security.blockExoticSubdeps ?? false)}`,
|
|
1845
|
-
severity: "warn"
|
|
1846
|
-
});
|
|
1847
|
-
} else {
|
|
1848
|
-
findings.push({
|
|
1849
|
-
label: "blockExoticSubdeps: true",
|
|
1850
|
-
severity: "ok"
|
|
1851
|
-
});
|
|
1852
|
-
}
|
|
1853
|
-
const allowBuildsCount = security.allowBuilds ? Object.keys(security.allowBuilds).length : 0;
|
|
1854
|
-
if (allowBuildsCount === 0) {
|
|
1855
|
-
findings.push({
|
|
1856
|
-
detail: "Lifecycle scripts are blocked by default. List trusted packages here to opt them back in (e.g. esbuild, @prisma/client).",
|
|
1857
|
-
label: "allowBuilds: not configured",
|
|
1858
|
-
severity: "warn"
|
|
1859
|
-
});
|
|
1860
|
-
} else {
|
|
1861
|
-
findings.push({
|
|
1862
|
-
label: `allowBuilds: ${String(allowBuildsCount)} ${allowBuildsCount === 1 ? "entry" : "entries"}`,
|
|
1863
|
-
severity: "ok"
|
|
1864
|
-
});
|
|
1865
|
-
}
|
|
1866
|
-
if (security.strictDepBuilds && allowBuildsCount === 0) {
|
|
1867
|
-
findings.push({
|
|
1868
|
-
detail: "All dependencies with build scripts will be blocked. Run 'vis approve-builds' to populate allowBuilds.",
|
|
1869
|
-
label: "strictDepBuilds is on but allowBuilds is empty",
|
|
1870
|
-
severity: "error"
|
|
1871
|
-
});
|
|
1872
|
-
}
|
|
1873
|
-
return { findings, status: rollUpStatus(findings) };
|
|
1874
|
-
};
|
|
1875
|
-
|
|
1876
|
-
const fmtDuration = (ms) => {
|
|
1877
|
-
if (ms >= 1e3) {
|
|
1878
|
-
return `${(ms / 1e3).toFixed(1)}s`;
|
|
1879
|
-
}
|
|
1880
|
-
return `${String(Math.round(ms))}ms`;
|
|
1881
|
-
};
|
|
1882
|
-
const tracked = async (progress, id, factory, summarize) => {
|
|
1883
|
-
if (!progress) {
|
|
1884
|
-
return factory();
|
|
1885
|
-
}
|
|
1886
|
-
progress.start(id);
|
|
1887
|
-
const startedAt = Date.now();
|
|
1888
|
-
try {
|
|
1889
|
-
const value = await factory();
|
|
1890
|
-
const elapsed = Date.now() - startedAt;
|
|
1891
|
-
const { status, summary } = summarize(value, elapsed);
|
|
1892
|
-
progress.finish(id, status, summary);
|
|
1893
|
-
return value;
|
|
1894
|
-
} catch (error) {
|
|
1895
|
-
const elapsed = Date.now() - startedAt;
|
|
1896
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1897
|
-
progress.finish(id, "error", `${message} (${fmtDuration(elapsed)})`);
|
|
1898
|
-
throw error;
|
|
1899
|
-
}
|
|
1900
|
-
};
|
|
1901
|
-
const buildSectionFindings = (section, payload) => {
|
|
1902
|
-
const partial = {
|
|
1903
|
-
duplicates: payload.duplicates,
|
|
1904
|
-
optimizations: payload.optimizations,
|
|
1905
|
-
outdated: payload.outdated,
|
|
1906
|
-
runtime: payload.runtime,
|
|
1907
|
-
sections: /* @__PURE__ */ new Set([section])};
|
|
1908
|
-
return flattenFindings(partial);
|
|
1909
|
-
};
|
|
1910
|
-
const streamScans = async (context) => {
|
|
1911
|
-
const { filterPatterns, installed, progress, resolveCodemods, sections, store, visConfig, workspaceRoot } = context;
|
|
1912
|
-
const wantsDeps = sections.has("dependencies");
|
|
1913
|
-
const wantsSec = sections.has("security");
|
|
1914
|
-
const wantsOpt = sections.has("optimization");
|
|
1915
|
-
const wantsRuntime = sections.has("runtime");
|
|
1916
|
-
const sectionFindings = (section, payload) => filterFindingsByPattern(buildSectionFindings(section, payload), filterPatterns);
|
|
1917
|
-
const pm = detectPm(workspaceRoot);
|
|
1918
|
-
const { packageManager } = findPackageManagerSync(workspaceRoot);
|
|
1919
|
-
const rootDeps = collectDepsFromPkgJson(join(workspaceRoot, "package.json"), false);
|
|
1920
|
-
const workspaceDirectories = discoverWorkspacePackages(workspaceRoot);
|
|
1921
|
-
const allDeps = new Set(rootDeps);
|
|
1922
|
-
for (const wsDir of workspaceDirectories) {
|
|
1923
|
-
const wsDeps = collectDepsFromPkgJson(join(resolve(workspaceRoot, wsDir), "package.json"), false);
|
|
1924
|
-
for (const dep of wsDeps) {
|
|
1925
|
-
allDeps.add(dep);
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
const npmrcConfig = loadNpmrc(workspaceRoot);
|
|
1929
|
-
const catalogs = readCatalogs(workspaceRoot, packageManager);
|
|
1930
|
-
const socketOptions = buildSocketOptions(visConfig?.security?.socket);
|
|
1931
|
-
const acceptedRisks = visConfig?.security?.socket?.acceptedRisks;
|
|
1932
|
-
const lockText = readLockfileText(workspaceRoot, pm.name);
|
|
1933
|
-
const checkOptions = {
|
|
1934
|
-
exclude: [],
|
|
1935
|
-
ignore: [],
|
|
1936
|
-
include: [],
|
|
1937
|
-
includeLocked: false,
|
|
1938
|
-
includePrerelease: false,
|
|
1939
|
-
security: true,
|
|
1940
|
-
target: "latest"
|
|
1941
|
-
};
|
|
1942
|
-
const duplicates = wantsDeps ? findDuplicateDependencies(workspaceRoot, pm.name) : [];
|
|
1943
|
-
const e18eEntries = wantsOpt ? buildE18eEntries(allDeps) : [];
|
|
1944
|
-
const socketOverrideEntries = wantsOpt ? buildSocketEntries(allDeps, lockText, pm, false) : [];
|
|
1945
|
-
const e18eNames = new Set(e18eEntries.map((e) => e.packageName));
|
|
1946
|
-
const dedupedSocket = socketOverrideEntries.filter((e) => !e18eNames.has(e.packageName));
|
|
1947
|
-
const allOptimizations = [...e18eEntries, ...dedupedSocket];
|
|
1948
|
-
const runtime = wantsRuntime ? runRuntimeDiagnostics() : [];
|
|
1949
|
-
if (store) {
|
|
1950
|
-
if (wantsDeps) {
|
|
1951
|
-
store.startSection("dependencies", catalogs.size > 0 ? "checking outdated catalog dependencies" : "scanning duplicates");
|
|
1952
|
-
}
|
|
1953
|
-
if (wantsSec) {
|
|
1954
|
-
store.startSection(
|
|
1955
|
-
"security",
|
|
1956
|
-
installed.length > 0 ? `scanning ${String(installed.length)} packages for advisories` : "no installed packages to scan"
|
|
1957
|
-
);
|
|
1958
|
-
}
|
|
1959
|
-
if (wantsOpt) {
|
|
1960
|
-
store.startSection("optimization", "matching e18e + socket overrides");
|
|
1961
|
-
}
|
|
1962
|
-
if (wantsRuntime) {
|
|
1963
|
-
store.startSection("runtime", "running runtime diagnostics");
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
if (store && wantsRuntime) {
|
|
1967
|
-
store.completeSection(
|
|
1968
|
-
"runtime",
|
|
1969
|
-
sectionFindings("runtime", {
|
|
1970
|
-
duplicates: [],
|
|
1971
|
-
optimizations: [],
|
|
1972
|
-
outdated: [],
|
|
1973
|
-
runtime
|
|
1974
|
-
})
|
|
1975
|
-
);
|
|
1976
|
-
}
|
|
1977
|
-
const needsOutdated = (wantsDeps || wantsSec) && catalogs.size > 0;
|
|
1978
|
-
const outdatedPromise = needsOutdated ? tracked(
|
|
1979
|
-
progress,
|
|
1980
|
-
"outdated",
|
|
1981
|
-
() => checkOutdated(catalogs, checkOptions, npmrcConfig, void 0, workspaceRoot, socketOptions, acceptedRisks),
|
|
1982
|
-
(result, ms) => {
|
|
1983
|
-
const count = result.outdated.length;
|
|
1984
|
-
return {
|
|
1985
|
-
status: count > 0 ? "warn" : "ok",
|
|
1986
|
-
summary: count > 0 ? `${String(count)} outdated · ${fmtDuration(ms)}` : `up to date · ${fmtDuration(ms)}`
|
|
1987
|
-
};
|
|
1988
|
-
}
|
|
1989
|
-
) : Promise.resolve({ failed: [], ignored: [], outdated: [] });
|
|
1990
|
-
const vulnPromise = wantsSec && installed.length > 0 ? tracked(
|
|
1991
|
-
progress,
|
|
1992
|
-
"vulnerabilities",
|
|
1993
|
-
() => fetchVulnerabilities(
|
|
1994
|
-
installed.map((p) => {
|
|
1995
|
-
return { name: p.name, version: p.version };
|
|
1996
|
-
})
|
|
1997
|
-
),
|
|
1998
|
-
(vulnMap2, ms) => {
|
|
1999
|
-
let count = 0;
|
|
2000
|
-
for (const list of vulnMap2.values()) {
|
|
2001
|
-
count += list.length;
|
|
2002
|
-
}
|
|
2003
|
-
return {
|
|
2004
|
-
status: count > 0 ? "error" : "ok",
|
|
2005
|
-
summary: count > 0 ? `${String(count)} found · ${fmtDuration(ms)}` : `none found · ${fmtDuration(ms)}`
|
|
2006
|
-
};
|
|
2007
|
-
}
|
|
2008
|
-
) : Promise.resolve(/* @__PURE__ */ new Map());
|
|
2009
|
-
const socketReportsPromise = wantsSec && socketOptions && installed.length > 0 ? tracked(
|
|
2010
|
-
progress,
|
|
2011
|
-
"socket",
|
|
2012
|
-
() => fetchSocketReports(
|
|
2013
|
-
installed.map((p) => {
|
|
2014
|
-
return { name: p.name, version: p.version };
|
|
2015
|
-
}),
|
|
2016
|
-
socketOptions
|
|
2017
|
-
),
|
|
2018
|
-
(reports, ms) => {
|
|
2019
|
-
let alerts = 0;
|
|
2020
|
-
let low = 0;
|
|
2021
|
-
for (const report of reports.values()) {
|
|
2022
|
-
alerts += report.alerts.length;
|
|
2023
|
-
if (report.score.overall < DEFAULT_LOW_SCORE_THRESHOLD) {
|
|
2024
|
-
low += 1;
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
const issues = alerts + low;
|
|
2028
|
-
return {
|
|
2029
|
-
status: issues > 0 ? "warn" : "ok",
|
|
2030
|
-
summary: issues > 0 ? `${String(alerts)} alert${alerts === 1 ? "" : "s"}, ${String(low)} low-score · ${fmtDuration(ms)}` : `clean · ${fmtDuration(ms)}`
|
|
2031
|
-
};
|
|
2032
|
-
}
|
|
2033
|
-
) : Promise.resolve(/* @__PURE__ */ new Map());
|
|
2034
|
-
let outdatedError;
|
|
2035
|
-
let vulnError;
|
|
2036
|
-
let socketError;
|
|
2037
|
-
let optError;
|
|
2038
|
-
const outdatedSafe = outdatedPromise.catch((error) => {
|
|
2039
|
-
outdatedError = error instanceof Error ? error.message : String(error);
|
|
2040
|
-
if (!store) {
|
|
2041
|
-
pail.warn(`Outdated scan failed: ${outdatedError}`);
|
|
2042
|
-
}
|
|
2043
|
-
return { failed: [], ignored: [], outdated: [] };
|
|
2044
|
-
});
|
|
2045
|
-
const vulnSafe = vulnPromise.catch((error) => {
|
|
2046
|
-
vulnError = error instanceof Error ? error.message : String(error);
|
|
2047
|
-
if (!store) {
|
|
2048
|
-
pail.warn(`Vulnerability scan failed: ${vulnError}`);
|
|
2049
|
-
}
|
|
2050
|
-
return /* @__PURE__ */ new Map();
|
|
2051
|
-
});
|
|
2052
|
-
const socketSafe = socketReportsPromise.catch((error) => {
|
|
2053
|
-
socketError = error instanceof Error ? error.message : String(error);
|
|
2054
|
-
if (!store) {
|
|
2055
|
-
pail.warn(`Socket scan failed: ${socketError}`);
|
|
2056
|
-
}
|
|
2057
|
-
return /* @__PURE__ */ new Map();
|
|
2058
|
-
});
|
|
2059
|
-
const depsStream = store && wantsDeps ? outdatedSafe.then((res) => {
|
|
2060
|
-
if (outdatedError) {
|
|
2061
|
-
store.failSection("dependencies", outdatedError);
|
|
2062
|
-
return;
|
|
2063
|
-
}
|
|
2064
|
-
store.completeSection(
|
|
2065
|
-
"dependencies",
|
|
2066
|
-
sectionFindings("dependencies", {
|
|
2067
|
-
duplicates,
|
|
2068
|
-
optimizations: [],
|
|
2069
|
-
outdated: res.outdated,
|
|
2070
|
-
runtime: []
|
|
2071
|
-
})
|
|
2072
|
-
);
|
|
2073
|
-
}) : void 0;
|
|
2074
|
-
const secStream = store && wantsSec ? Promise.all([outdatedSafe, vulnSafe, socketSafe]).then(([res]) => {
|
|
2075
|
-
const firstError = outdatedError ?? vulnError ?? socketError;
|
|
2076
|
-
if (firstError) {
|
|
2077
|
-
store.failSection("security", firstError);
|
|
2078
|
-
return;
|
|
2079
|
-
}
|
|
2080
|
-
store.completeSection(
|
|
2081
|
-
"security",
|
|
2082
|
-
sectionFindings("security", {
|
|
2083
|
-
duplicates: [],
|
|
2084
|
-
optimizations: [],
|
|
2085
|
-
outdated: res.outdated,
|
|
2086
|
-
runtime: []
|
|
2087
|
-
})
|
|
2088
|
-
);
|
|
2089
|
-
}) : void 0;
|
|
2090
|
-
const optStream = (async () => {
|
|
2091
|
-
if (resolveCodemods && wantsOpt && allOptimizations.length > 0) {
|
|
2092
|
-
await tracked(
|
|
2093
|
-
progress,
|
|
2094
|
-
"codemods",
|
|
2095
|
-
async () => {
|
|
2096
|
-
await markCodemodAvailability(allOptimizations);
|
|
2097
|
-
return allOptimizations;
|
|
2098
|
-
},
|
|
2099
|
-
(entries, ms) => {
|
|
2100
|
-
const fixable = entries.filter((entry) => entry.hasCodemod || entry.category === "socket").length;
|
|
2101
|
-
return {
|
|
2102
|
-
status: "ok",
|
|
2103
|
-
summary: `${String(fixable)} auto-fixable · ${fmtDuration(ms)}`
|
|
2104
|
-
};
|
|
2105
|
-
}
|
|
2106
|
-
).catch((error) => {
|
|
2107
|
-
optError = error instanceof Error ? error.message : String(error);
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
if (store && wantsOpt) {
|
|
2111
|
-
if (optError) {
|
|
2112
|
-
store.failSection("optimization", optError);
|
|
2113
|
-
return;
|
|
2114
|
-
}
|
|
2115
|
-
store.completeSection(
|
|
2116
|
-
"optimization",
|
|
2117
|
-
sectionFindings("optimization", {
|
|
2118
|
-
duplicates: [],
|
|
2119
|
-
optimizations: allOptimizations,
|
|
2120
|
-
outdated: [],
|
|
2121
|
-
runtime: []
|
|
2122
|
-
})
|
|
2123
|
-
);
|
|
2124
|
-
}
|
|
2125
|
-
})();
|
|
2126
|
-
const [outdatedResult, vulnMap, socketReports] = await Promise.all([outdatedSafe, vulnSafe, socketSafe]);
|
|
2127
|
-
await Promise.all([depsStream, secStream, optStream]);
|
|
2128
|
-
let socketAlerts = 0;
|
|
2129
|
-
let socketLowScore = 0;
|
|
2130
|
-
if (wantsSec && socketOptions) {
|
|
2131
|
-
for (const report of socketReports.values()) {
|
|
2132
|
-
socketAlerts += report.alerts.length;
|
|
2133
|
-
if (report.score.overall < DEFAULT_LOW_SCORE_THRESHOLD) {
|
|
2134
|
-
socketLowScore += 1;
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
let vulnCount = 0;
|
|
2139
|
-
if (wantsSec) {
|
|
2140
|
-
for (const entry of outdatedResult.outdated) {
|
|
2141
|
-
if (entry.vulnerabilities && entry.vulnerabilities.length > 0) {
|
|
2142
|
-
vulnCount += entry.vulnerabilities.length;
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
for (const list of vulnMap.values()) {
|
|
2146
|
-
vulnCount += list.length;
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
return {
|
|
2150
|
-
duplicates,
|
|
2151
|
-
installedCount: installed.length,
|
|
2152
|
-
optimizations: wantsOpt ? allOptimizations : [],
|
|
2153
|
-
outdated: wantsDeps ? outdatedResult.outdated : [],
|
|
2154
|
-
runtime,
|
|
2155
|
-
sections,
|
|
2156
|
-
socketIssues: { alerts: socketAlerts, lowScore: socketLowScore },
|
|
2157
|
-
// Supply-chain posture is config-derived and cheap; always
|
|
2158
|
-
// compute so the doctor section can render even when --only is
|
|
2159
|
-
// limited to other sections (the section filters its own
|
|
2160
|
-
// visibility in displayResults).
|
|
2161
|
-
supplyChain: buildSupplyChainPosture(visConfig),
|
|
2162
|
-
vulnCount,
|
|
2163
|
-
workspaceCount: workspaceDirectories.length
|
|
2164
|
-
};
|
|
2165
|
-
};
|
|
2166
|
-
const sectionIcon = (status) => {
|
|
2167
|
-
switch (status) {
|
|
2168
|
-
case "error": {
|
|
2169
|
-
return red(SYMBOLS.failure);
|
|
2170
|
-
}
|
|
2171
|
-
case "skip": {
|
|
2172
|
-
return dim(SYMBOLS.dash);
|
|
2173
|
-
}
|
|
2174
|
-
case "warn": {
|
|
2175
|
-
return yellow(SYMBOLS.warning);
|
|
2176
|
-
}
|
|
2177
|
-
default: {
|
|
2178
|
-
return green(SYMBOLS.success);
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
};
|
|
2182
|
-
const heading = (title, status) => {
|
|
2183
|
-
const cols = process.stderr.columns ?? 80;
|
|
2184
|
-
const cap = Math.max(20, Math.min(cols - 2, 60));
|
|
2185
|
-
const decoration = SYMBOLS.dash.repeat(2);
|
|
2186
|
-
const label = `${sectionIcon(status)} ${bold(title)}`;
|
|
2187
|
-
const labelWidth = label.replaceAll(/\[[0-9;]*m/g, "").length;
|
|
2188
|
-
const remaining = Math.max(0, cap - labelWidth - decoration.length - 2);
|
|
2189
|
-
return `${decoration} ${label} ${dim(SYMBOLS.dash.repeat(remaining))}`;
|
|
2190
|
-
};
|
|
2191
|
-
const itemOk = (text) => ` ${green(SYMBOLS.success)} ${text}`;
|
|
2192
|
-
const itemWarn = (text) => ` ${yellow(SYMBOLS.warning)} ${text}`;
|
|
2193
|
-
const itemError = (text) => ` ${red(SYMBOLS.failure)} ${text}`;
|
|
2194
|
-
const itemSkip = (text) => ` ${dim(SYMBOLS.dash)} ${dim(text)}`;
|
|
2195
|
-
const countLine = (count, label, parenthetical) => {
|
|
2196
|
-
const head = `${bold(String(count))} ${dim(label)}`;
|
|
2197
|
-
return parenthetical ? `${head} ${dim(`(${parenthetical})`)}` : head;
|
|
2198
|
-
};
|
|
2199
|
-
const displayDependencies = (results) => {
|
|
2200
|
-
if (!results.sections.has("dependencies")) {
|
|
2201
|
-
return;
|
|
2202
|
-
}
|
|
2203
|
-
pail.log("");
|
|
2204
|
-
pail.log(heading("Dependencies", sectionStatus(results, "dependencies")));
|
|
2205
|
-
pail.log(itemOk(countLine(results.installedCount, "packages installed")));
|
|
2206
|
-
if (results.outdated.length > 0) {
|
|
2207
|
-
const majors = results.outdated.filter((e) => e.updateType === "major").length;
|
|
2208
|
-
const minors = results.outdated.filter((e) => e.updateType === "minor").length;
|
|
2209
|
-
const patches = results.outdated.filter((e) => e.updateType === "patch").length;
|
|
2210
|
-
const parts = [];
|
|
2211
|
-
if (majors > 0) {
|
|
2212
|
-
parts.push(`${String(majors)} major`);
|
|
2213
|
-
}
|
|
2214
|
-
if (minors > 0) {
|
|
2215
|
-
parts.push(`${String(minors)} minor`);
|
|
2216
|
-
}
|
|
2217
|
-
if (patches > 0) {
|
|
2218
|
-
parts.push(`${String(patches)} patch`);
|
|
2219
|
-
}
|
|
2220
|
-
pail.log(itemWarn(countLine(results.outdated.length, "outdated", parts.join(", "))));
|
|
2221
|
-
} else {
|
|
2222
|
-
pail.log(itemOk("All dependencies up to date"));
|
|
2223
|
-
}
|
|
2224
|
-
if (results.duplicates.length > 0) {
|
|
2225
|
-
pail.log(itemWarn(countLine(results.duplicates.length, "packages with duplicate versions")));
|
|
2226
|
-
} else {
|
|
2227
|
-
pail.log(itemOk("No duplicate dependencies"));
|
|
2228
|
-
}
|
|
2229
|
-
};
|
|
2230
|
-
const displaySecurity = (results) => {
|
|
2231
|
-
if (!results.sections.has("security")) {
|
|
2232
|
-
return;
|
|
2233
|
-
}
|
|
2234
|
-
pail.log("");
|
|
2235
|
-
pail.log(heading("Security", sectionStatus(results, "security")));
|
|
2236
|
-
if (results.vulnCount > 0) {
|
|
2237
|
-
pail.log(itemError(countLine(results.vulnCount, `vulnerabilit${results.vulnCount === 1 ? "y" : "ies"} found`)));
|
|
2238
|
-
} else {
|
|
2239
|
-
pail.log(itemOk("No known vulnerabilities"));
|
|
2240
|
-
}
|
|
2241
|
-
if (results.socketIssues.alerts > 0) {
|
|
2242
|
-
pail.log(itemWarn(countLine(results.socketIssues.alerts, `Socket.dev security alert${results.socketIssues.alerts === 1 ? "" : "s"}`)));
|
|
2243
|
-
}
|
|
2244
|
-
if (results.socketIssues.lowScore > 0) {
|
|
2245
|
-
pail.log(itemWarn(countLine(results.socketIssues.lowScore, `package${results.socketIssues.lowScore === 1 ? "" : "s"} with low security score`)));
|
|
2246
|
-
}
|
|
2247
|
-
if (results.socketIssues.alerts === 0 && results.socketIssues.lowScore === 0 && results.vulnCount === 0) {
|
|
2248
|
-
pail.log(itemOk("No security issues detected"));
|
|
2249
|
-
}
|
|
2250
|
-
};
|
|
2251
|
-
const displayOptimization = (results) => {
|
|
2252
|
-
if (!results.sections.has("optimization")) {
|
|
2253
|
-
return;
|
|
2254
|
-
}
|
|
2255
|
-
pail.log("");
|
|
2256
|
-
pail.log(heading("Optimization", sectionStatus(results, "optimization")));
|
|
2257
|
-
const counts = summarizeOptimizations(results.optimizations);
|
|
2258
|
-
if (counts.total === 0) {
|
|
2259
|
-
pail.log(itemOk("No optimizations available"));
|
|
2260
|
-
return;
|
|
2261
|
-
}
|
|
2262
|
-
if (counts.native > 0) {
|
|
2263
|
-
pail.log(itemWarn(countLine(counts.native, "replaceable with native APIs")));
|
|
2264
|
-
}
|
|
2265
|
-
if (counts.preferred > 0) {
|
|
2266
|
-
pail.log(itemWarn(countLine(counts.preferred, "with lighter alternatives")));
|
|
2267
|
-
}
|
|
2268
|
-
if (counts.micro > 0) {
|
|
2269
|
-
pail.log(itemWarn(countLine(counts.micro, "trivial micro-utilities")));
|
|
2270
|
-
}
|
|
2271
|
-
if (counts.socket > 0) {
|
|
2272
|
-
pail.log(itemWarn(countLine(counts.socket, "@socketregistry overrides available")));
|
|
2273
|
-
}
|
|
2274
|
-
};
|
|
2275
|
-
const displaySupplyChain = (results) => {
|
|
2276
|
-
pail.log("");
|
|
2277
|
-
pail.log(heading("Supply Chain", results.supplyChain.status));
|
|
2278
|
-
for (const finding of results.supplyChain.findings) {
|
|
2279
|
-
const line = finding.severity === "ok" ? itemOk(finding.label) : finding.severity === "error" ? itemError(finding.label) : itemWarn(finding.label);
|
|
2280
|
-
pail.log(line);
|
|
2281
|
-
if (finding.detail) {
|
|
2282
|
-
pail.log(` ${dim(SYMBOLS.arrow)} ${dim(finding.detail)}`);
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
if (results.supplyChain.status !== "ok") {
|
|
2286
|
-
pail.log(` ${dim(SYMBOLS.arrow)} ${dim("Configure with security.* in vis.config.ts. See `vis check --security-config` for details.")}`);
|
|
2287
|
-
}
|
|
2288
|
-
};
|
|
2289
|
-
const displayRuntime = (results) => {
|
|
2290
|
-
if (!results.sections.has("runtime")) {
|
|
2291
|
-
return;
|
|
2292
|
-
}
|
|
2293
|
-
pail.log("");
|
|
2294
|
-
pail.log(heading("Runtime", sectionStatus(results, "runtime")));
|
|
2295
|
-
for (const diagnostic of results.runtime) {
|
|
2296
|
-
if (diagnostic.status === "ok") {
|
|
2297
|
-
pail.log(itemOk(diagnostic.message));
|
|
2298
|
-
} else if (diagnostic.status === "skip") {
|
|
2299
|
-
pail.log(itemSkip(diagnostic.message));
|
|
2300
|
-
} else {
|
|
2301
|
-
pail.log(itemWarn(diagnostic.message));
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
};
|
|
2305
|
-
const displaySummary = (results, quiet) => {
|
|
2306
|
-
const criticalCount = results.vulnCount;
|
|
2307
|
-
const runtimeWarnings = results.runtime.filter((d) => d.status === "warn").length;
|
|
2308
|
-
const improvementCount = results.outdated.length + results.duplicates.length + results.optimizations.length + runtimeWarnings;
|
|
2309
|
-
if (quiet) {
|
|
2310
|
-
if (criticalCount === 0 && improvementCount === 0) {
|
|
2311
|
-
pail.success(`Everything looks good! ${dim(`(${fmtDuration(results.elapsedMs)})`)}`);
|
|
2312
|
-
} else {
|
|
2313
|
-
const parts = [];
|
|
2314
|
-
if (criticalCount > 0) {
|
|
2315
|
-
parts.push(red(`${String(criticalCount)} security`));
|
|
2316
|
-
}
|
|
2317
|
-
if (improvementCount > 0) {
|
|
2318
|
-
parts.push(yellow(`${String(improvementCount)} improvement${improvementCount === 1 ? "" : "s"}`));
|
|
2319
|
-
}
|
|
2320
|
-
pail.log(`${red(SYMBOLS.failure)} ${parts.join(", ")} ${dim(`(${fmtDuration(results.elapsedMs)})`)}`);
|
|
2321
|
-
}
|
|
2322
|
-
return;
|
|
2323
|
-
}
|
|
2324
|
-
pail.log("");
|
|
2325
|
-
pail.log(heading("Summary", "ok"));
|
|
2326
|
-
if (criticalCount === 0 && improvementCount === 0) {
|
|
2327
|
-
pail.success(`Everything looks good! ${dim(`(${fmtDuration(results.elapsedMs)})`)}`);
|
|
2328
|
-
} else {
|
|
2329
|
-
if (criticalCount > 0) {
|
|
2330
|
-
pail.error(`${String(criticalCount)} security issue${criticalCount === 1 ? "" : "s"}`);
|
|
2331
|
-
}
|
|
2332
|
-
if (improvementCount > 0) {
|
|
2333
|
-
pail.log(
|
|
2334
|
-
` ${cyan(SYMBOLS.arrow)} ${bold(String(improvementCount))} ${dim(`improvement${improvementCount === 1 ? "" : "s"} available`)} ${dim(`(${fmtDuration(results.elapsedMs)})`)}`
|
|
2335
|
-
);
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
};
|
|
2339
|
-
const displayActions = (results) => {
|
|
2340
|
-
const actions = [];
|
|
2341
|
-
if (results.outdated.length > 0) {
|
|
2342
|
-
actions.push("vis update — update outdated dependencies");
|
|
2343
|
-
}
|
|
2344
|
-
if (results.vulnCount > 0 || results.socketIssues.alerts > 0) {
|
|
2345
|
-
actions.push("vis audit — detailed security analysis");
|
|
2346
|
-
}
|
|
2347
|
-
if (results.optimizations.length > 0) {
|
|
2348
|
-
actions.push("vis optimize — apply optimizations interactively");
|
|
2349
|
-
}
|
|
2350
|
-
if (results.duplicates.length > 0) {
|
|
2351
|
-
actions.push("vis dedupe — reduce duplicate versions");
|
|
2352
|
-
}
|
|
2353
|
-
if (actions.length > 0) {
|
|
2354
|
-
pail.log("");
|
|
2355
|
-
pail.log(bold("Next steps:"));
|
|
2356
|
-
for (const action of actions) {
|
|
2357
|
-
pail.log(` ${dim(SYMBOLS.arrow)} ${action}`);
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
pail.log("");
|
|
2361
|
-
};
|
|
2362
|
-
const displayResults = (results, quiet) => {
|
|
2363
|
-
if (!quiet) {
|
|
2364
|
-
displayDependencies(results);
|
|
2365
|
-
displaySecurity(results);
|
|
2366
|
-
displayOptimization(results);
|
|
2367
|
-
displayRuntime(results);
|
|
2368
|
-
displaySupplyChain(results);
|
|
2369
|
-
}
|
|
2370
|
-
displaySummary(results, quiet);
|
|
2371
|
-
};
|
|
2372
|
-
const planScanTasks = (sections, catalogsCount, socketEnabled, installedCount, fix) => {
|
|
2373
|
-
const tasks = [];
|
|
2374
|
-
const wantsDeps = sections.has("dependencies");
|
|
2375
|
-
const wantsSec = sections.has("security");
|
|
2376
|
-
const wantsOpt = sections.has("optimization");
|
|
2377
|
-
if ((wantsDeps || wantsSec) && catalogsCount > 0) {
|
|
2378
|
-
tasks.push({ id: "outdated", label: "Outdated catalog dependencies" });
|
|
2379
|
-
}
|
|
2380
|
-
if (wantsSec && installedCount > 0) {
|
|
2381
|
-
tasks.push({ id: "vulnerabilities", label: "Known vulnerabilities (OSV)" });
|
|
2382
|
-
}
|
|
2383
|
-
if (wantsSec && socketEnabled && installedCount > 0) {
|
|
2384
|
-
tasks.push({ id: "socket", label: "Socket.dev supply-chain reports" });
|
|
2385
|
-
}
|
|
2386
|
-
if (wantsOpt && fix) {
|
|
2387
|
-
tasks.push({ id: "codemods", label: "Codemod availability" });
|
|
2388
|
-
}
|
|
2389
|
-
return tasks;
|
|
2390
|
-
};
|
|
2391
|
-
const printBanner = (input) => {
|
|
2392
|
-
pail.log("");
|
|
2393
|
-
pail.log(`${bold(cyan("vis doctor"))} ${dim("— project health check")}`);
|
|
2394
|
-
pail.log(itemOk(`Detected ${input.packageManagerName} v${input.packageManagerVersion}`));
|
|
2395
|
-
if (input.runtimeFindings.length === 0) {
|
|
2396
|
-
pail.log(itemOk(`Node.js ${input.nodeVersion}`));
|
|
2397
|
-
} else {
|
|
2398
|
-
for (const finding of input.runtimeFindings) {
|
|
2399
|
-
const colour = finding.severity === "error" ? red : yellow;
|
|
2400
|
-
pail.log(itemError(`Runtime: ${colour(finding.message)}`));
|
|
2401
|
-
}
|
|
2402
|
-
pail.log(
|
|
2403
|
-
` ${dim(SYMBOLS.arrow)} Run ${green("vis toolchain install")} to install pinned versions, or ${green("vis toolchain status")} for the per-tool breakdown.`
|
|
2404
|
-
);
|
|
2405
|
-
}
|
|
2406
|
-
pail.log("");
|
|
2407
|
-
};
|
|
2408
|
-
const execute = async ({ logger, options, visConfig, visConfigError, workspaceRoot: wsRoot }) => {
|
|
2409
|
-
if (!wsRoot) {
|
|
2410
|
-
throw new Error("Could not determine workspace root.");
|
|
2411
|
-
}
|
|
2412
|
-
const isJson = options.format === "json" || options.json === true;
|
|
2413
|
-
const sections = resolveSections(options.only, options.skip);
|
|
2414
|
-
const quiet = Boolean(options.quiet);
|
|
2415
|
-
const noProgress = Boolean(options.noProgress);
|
|
2416
|
-
const filterPatterns = parseFilterPatterns(options.filter);
|
|
2417
|
-
if (sections.size === 0) {
|
|
2418
|
-
pail.error("No sections selected. Check your --only / --skip values.");
|
|
2419
|
-
process.exitCode = 2;
|
|
2420
|
-
return;
|
|
2421
|
-
}
|
|
2422
|
-
const startedAt = Date.now();
|
|
2423
|
-
const pm = detectPm(wsRoot);
|
|
2424
|
-
const runtimeFindings = checkRuntimeVersions(wsRoot);
|
|
2425
|
-
const stdoutTty = Boolean(process.stdout.isTTY);
|
|
2426
|
-
const wantsInteractive = !isJson && stdoutTty && !isInCi && !quiet && !noProgress;
|
|
2427
|
-
if (!isJson && !wantsInteractive) {
|
|
2428
|
-
printBanner({
|
|
2429
|
-
nodeVersion: process.versions.node,
|
|
2430
|
-
packageManagerName: pm.name,
|
|
2431
|
-
packageManagerVersion: pm.version,
|
|
2432
|
-
runtimeFindings});
|
|
2433
|
-
}
|
|
2434
|
-
const catalogs = readCatalogs(wsRoot, findPackageManagerSync(wsRoot).packageManager);
|
|
2435
|
-
const installed = lockedPackages(wsRoot, pm.name);
|
|
2436
|
-
const installedCount = installed.length;
|
|
2437
|
-
const socketEnabled = Boolean(buildSocketOptions(visConfig?.security?.socket));
|
|
2438
|
-
const workspaceDirectories = discoverWorkspacePackages(wsRoot);
|
|
2439
|
-
if (!isJson && !quiet && !wantsInteractive) {
|
|
2440
|
-
const wsLine = workspaceDirectories.length > 0 ? dim(` · ${String(workspaceDirectories.length)} workspace package${workspaceDirectories.length === 1 ? "" : "s"}`) : "";
|
|
2441
|
-
pail.log(`${dim("·")} ${dim("Found")} ${bold(String(installedCount))} ${dim(`installed package${installedCount === 1 ? "" : "s"}`)}${wsLine}`);
|
|
2442
|
-
}
|
|
2443
|
-
const banner = visConfigError ? {
|
|
2444
|
-
hint: visConfigError.file ? `Continuing with default settings — fix or regenerate ${visConfigError.file} (vis init --force).` : "Continuing with default settings.",
|
|
2445
|
-
message: visConfigError.message,
|
|
2446
|
-
severity: "error",
|
|
2447
|
-
title: visConfigError.file ? `Failed to load ${visConfigError.file}` : "Failed to load vis.config"
|
|
2448
|
-
} : void 0;
|
|
2449
|
-
const lockfileNameByPm = {
|
|
2450
|
-
bun: "bun.lock",
|
|
2451
|
-
npm: "package-lock.json",
|
|
2452
|
-
pnpm: "pnpm-lock.yaml",
|
|
2453
|
-
yarn: "yarn.lock"
|
|
2454
|
-
};
|
|
2455
|
-
const lockfileFile = lockfileNameByPm[pm.name];
|
|
2456
|
-
const lockfilePath = lockfileFile ? join(wsRoot, lockfileFile) : void 0;
|
|
2457
|
-
const configFilePath = findVisConfigFile(wsRoot);
|
|
2458
|
-
const cacheEnabled = !options.noCache && !options.fix;
|
|
2459
|
-
const cacheKey = cacheEnabled ? buildDoctorCacheKey({
|
|
2460
|
-
configPath: configFilePath,
|
|
2461
|
-
lockfilePath,
|
|
2462
|
-
sections,
|
|
2463
|
-
socketEnabled,
|
|
2464
|
-
workspaceRoot: wsRoot
|
|
2465
|
-
}) : void 0;
|
|
2466
|
-
const cachedResults = cacheKey ? readDoctorCache(cacheKey) : void 0;
|
|
2467
|
-
const cacheHit = cachedResults !== void 0;
|
|
2468
|
-
let scanResults;
|
|
2469
|
-
let pendingAction;
|
|
2470
|
-
if (wantsInteractive) {
|
|
2471
|
-
const store = cachedResults ? new DoctorStore({ activeSections: sections, findings: filterFindingsByPattern(flattenFindings(cachedResults), filterPatterns) }) : new DoctorStore({ activeSections: sections });
|
|
2472
|
-
const instance = render(React.createElement(VisDoctorApp, { banner, fromCache: cacheHit, startedAt, store }), {
|
|
2473
|
-
alternateScreen: true,
|
|
2474
|
-
exitOnCtrlC: false,
|
|
2475
|
-
interactive: true,
|
|
2476
|
-
patchConsole: true
|
|
2477
|
-
});
|
|
2478
|
-
try {
|
|
2479
|
-
scanResults = cachedResults ?? await streamScans({
|
|
2480
|
-
filterPatterns,
|
|
2481
|
-
installed,
|
|
2482
|
-
resolveCodemods: Boolean(options.fix),
|
|
2483
|
-
sections,
|
|
2484
|
-
store,
|
|
2485
|
-
visConfig,
|
|
2486
|
-
workspaceRoot: wsRoot
|
|
2487
|
-
});
|
|
2488
|
-
} catch (error) {
|
|
2489
|
-
instance.unmount();
|
|
2490
|
-
throw error;
|
|
2491
|
-
}
|
|
2492
|
-
await instance.waitUntilExit();
|
|
2493
|
-
pendingAction = store.getSnapshot().pendingAction;
|
|
2494
|
-
} else if (cachedResults) {
|
|
2495
|
-
scanResults = cachedResults;
|
|
2496
|
-
} else {
|
|
2497
|
-
const tasks = planScanTasks(sections, catalogs.size, socketEnabled, installedCount, Boolean(options.fix));
|
|
2498
|
-
const live = !isJson && !quiet && !noProgress;
|
|
2499
|
-
const progress = startScanProgress(tasks, { live });
|
|
2500
|
-
try {
|
|
2501
|
-
scanResults = await streamScans({
|
|
2502
|
-
filterPatterns,
|
|
2503
|
-
installed,
|
|
2504
|
-
progress,
|
|
2505
|
-
resolveCodemods: Boolean(options.fix),
|
|
2506
|
-
sections,
|
|
2507
|
-
visConfig,
|
|
2508
|
-
workspaceRoot: wsRoot
|
|
2509
|
-
});
|
|
2510
|
-
} finally {
|
|
2511
|
-
progress.stop();
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
const fullResults = { ...scanResults, elapsedMs: Date.now() - startedAt };
|
|
2515
|
-
if (cacheKey && !cacheHit) {
|
|
2516
|
-
try {
|
|
2517
|
-
writeDoctorCache(cacheKey, fullResults);
|
|
2518
|
-
} catch {
|
|
2519
|
-
}
|
|
2520
|
-
}
|
|
2521
|
-
const results = applyFilter(fullResults, filterPatterns);
|
|
2522
|
-
if (isJson) {
|
|
2523
|
-
process.stdout.write(`${JSON.stringify(buildJsonPayload(results, pm.name), void 0, 2)}
|
|
2524
|
-
`);
|
|
2525
|
-
if (options.exitCode && shouldFail(results, Boolean(options.strict))) {
|
|
2526
|
-
process.exitCode = 1;
|
|
2527
|
-
}
|
|
2528
|
-
return;
|
|
2529
|
-
}
|
|
2530
|
-
if (cacheHit && !quiet) {
|
|
2531
|
-
pail.log(`${dim("·")} Cached results (use --no-cache to refresh)`);
|
|
2532
|
-
}
|
|
2533
|
-
if (filterPatterns.length > 0 && !quiet) {
|
|
2534
|
-
pail.log(`${dim("·")} Filter active: ${cyan(options.filter ?? "")}`);
|
|
2535
|
-
}
|
|
2536
|
-
displayResults(results, quiet);
|
|
2537
|
-
const hasOrphanWarning = results.runtime.some((d) => d.id === ORPHANS_DIAGNOSTIC_ID && d.status === "warn");
|
|
2538
|
-
const hasOptimizationFixes = results.sections.has("optimization") && results.optimizations.length > 0;
|
|
2539
|
-
if (options.fix && (hasOptimizationFixes || hasOrphanWarning)) {
|
|
2540
|
-
await runFixes({
|
|
2541
|
-
force: Boolean(options.fixForce),
|
|
2542
|
-
logger,
|
|
2543
|
-
pm,
|
|
2544
|
-
recoverOrphans: hasOrphanWarning,
|
|
2545
|
-
results,
|
|
2546
|
-
workspaceRoot: wsRoot
|
|
2547
|
-
});
|
|
2548
|
-
} else if (!quiet) {
|
|
2549
|
-
displayActions(results);
|
|
2550
|
-
}
|
|
2551
|
-
if (pendingAction) {
|
|
2552
|
-
process.stdout.write("\n");
|
|
2553
|
-
process.stdout.write(`${bold("→ ")}${pendingAction.description}
|
|
2554
|
-
`);
|
|
2555
|
-
if (pendingAction.configSnippet) {
|
|
2556
|
-
process.stdout.write("\n");
|
|
2557
|
-
process.stdout.write(`${dim(pendingAction.configSnippet)}
|
|
2558
|
-
`);
|
|
2559
|
-
} else {
|
|
2560
|
-
process.stdout.write(` ${cyan(pendingAction.command)}
|
|
2561
|
-
`);
|
|
2562
|
-
}
|
|
2563
|
-
process.stdout.write("\n");
|
|
2564
|
-
}
|
|
2565
|
-
if (options.exitCode && shouldFail(results, Boolean(options.strict))) {
|
|
2566
|
-
process.exitCode = 1;
|
|
2567
|
-
}
|
|
2568
|
-
};
|
|
2569
|
-
const runFixes = async (opts) => {
|
|
2570
|
-
const { force, logger, pm, recoverOrphans, results, workspaceRoot } = opts;
|
|
2571
|
-
pail.log("");
|
|
2572
|
-
pail.log(heading("Applying fixes", "ok"));
|
|
2573
|
-
const socketEntries = results.optimizations.filter((o) => o.category === "socket" && o.overrideSpec).map((o) => {
|
|
2574
|
-
return { original: o.packageName, spec: o.overrideSpec };
|
|
2575
|
-
});
|
|
2576
|
-
const codemodEntries = results.optimizations.filter((o) => o.category !== "socket" && o.hasCodemod);
|
|
2577
|
-
const manualEntries = results.optimizations.filter((o) => o.category !== "socket" && !o.hasCodemod);
|
|
2578
|
-
let didSomething = false;
|
|
2579
|
-
let codemodsApplied = 0;
|
|
2580
|
-
const codemodFailures = [];
|
|
2581
|
-
if (recoverOrphans) {
|
|
2582
|
-
const recovery = killOrphanedRunners({ force });
|
|
2583
|
-
if (recovery.killed.length > 0) {
|
|
2584
|
-
pail.success(
|
|
2585
|
-
`Cleaned up ${String(recovery.killed.length)} orphaned process${recovery.killed.length === 1 ? "" : "es"} (PIDs: ${recovery.killed.join(", ")}).`
|
|
2586
|
-
);
|
|
2587
|
-
didSomething = true;
|
|
2588
|
-
}
|
|
2589
|
-
if (recovery.failed.length > 0) {
|
|
2590
|
-
const escalation = force ? "" : " Re-run with `--fix --fix-force` to escalate to SIGKILL.";
|
|
2591
|
-
pail.warn(
|
|
2592
|
-
`Could not signal ${String(recovery.failed.length)} orphan${recovery.failed.length === 1 ? "" : "s"}: ${recovery.failed.map((f) => `${String(f.pid)} (${f.reason})`).join(", ")}.${escalation}`
|
|
2593
|
-
);
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
if (socketEntries.length > 0) {
|
|
2597
|
-
const overrideResult = applyOverrides(workspaceRoot, join(workspaceRoot, "package.json"), socketEntries, pm);
|
|
2598
|
-
if (overrideResult.added.length > 0) {
|
|
2599
|
-
pail.success(`Added ${String(overrideResult.added.length)} security override${overrideResult.added.length === 1 ? "" : "s"}.`);
|
|
2600
|
-
didSomething = true;
|
|
2601
|
-
}
|
|
2602
|
-
if (overrideResult.updated.length > 0) {
|
|
2603
|
-
pail.success(`Updated ${String(overrideResult.updated.length)} override${overrideResult.updated.length === 1 ? "" : "s"}.`);
|
|
2604
|
-
didSomething = true;
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
for (const entry of codemodEntries) {
|
|
2608
|
-
try {
|
|
2609
|
-
const codemodResult = await runCodemod(workspaceRoot, entry.packageName);
|
|
2610
|
-
if (codemodResult.filesChanged > 0) {
|
|
2611
|
-
pail.success(`${entry.packageName}: ${String(codemodResult.filesChanged)} file${codemodResult.filesChanged === 1 ? "" : "s"} updated`);
|
|
2612
|
-
codemodsApplied += 1;
|
|
2613
|
-
didSomething = true;
|
|
2614
|
-
}
|
|
2615
|
-
} catch (error) {
|
|
2616
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2617
|
-
codemodFailures.push({ error: message, package: entry.packageName });
|
|
2618
|
-
pail.warn(`${entry.packageName}: codemod failed — ${message}`);
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
if (socketEntries.length > 0) {
|
|
2622
|
-
pail.log(`${cyan(SYMBOLS.arrow)} Running ${pm.name} install to update lockfile…`);
|
|
2623
|
-
const installOptions = {
|
|
2624
|
-
dev: false,
|
|
2625
|
-
filter: [],
|
|
2626
|
-
force: false,
|
|
2627
|
-
frozenLockfile: false,
|
|
2628
|
-
ignoreScripts: false,
|
|
2629
|
-
lockfileOnly: false,
|
|
2630
|
-
noOptional: false,
|
|
2631
|
-
offline: false,
|
|
2632
|
-
prod: false,
|
|
2633
|
-
recursive: false,
|
|
2634
|
-
silent: false,
|
|
2635
|
-
workspaceRoot: false
|
|
2636
|
-
};
|
|
2637
|
-
runInstall(pm, installOptions, workspaceRoot, logger);
|
|
2638
|
-
didSomething = true;
|
|
2639
|
-
}
|
|
2640
|
-
pail.log("");
|
|
2641
|
-
if (didSomething) {
|
|
2642
|
-
pail.success(`Fixes applied. ${codemodsApplied > 0 ? `${String(codemodsApplied)} codemod${codemodsApplied === 1 ? "" : "s"} applied.` : ""}`.trim());
|
|
2643
|
-
} else {
|
|
2644
|
-
pail.log(itemSkip("No auto-fixable items in the current run."));
|
|
2645
|
-
}
|
|
2646
|
-
if (codemodFailures.length > 0) {
|
|
2647
|
-
pail.warn(
|
|
2648
|
-
`${String(codemodFailures.length)} codemod${codemodFailures.length === 1 ? "" : "s"} failed (run ${green("vis optimize")} for the interactive picker).`
|
|
2649
|
-
);
|
|
2650
|
-
}
|
|
2651
|
-
if (manualEntries.length > 0) {
|
|
2652
|
-
pail.notice(
|
|
2653
|
-
`${String(manualEntries.length)} optimization${manualEntries.length === 1 ? "" : "s"} need manual review (no codemod). Run ${green("vis optimize")} to inspect them.`
|
|
2654
|
-
);
|
|
2655
|
-
}
|
|
2656
|
-
};
|
|
2657
|
-
|
|
2658
|
-
export { execute as default };
|
|
1
|
+
var Dt=Object.defineProperty;var D=(e,t)=>Dt(e,"name",{value:t,configurable:!0});import{createRequire as Ot}from"node:module";import{green as we,yellow as ze,dim as b,red as Re,bold as ae,cyan as Te}from"@visulima/colorize";import{findPackageManagerSync as at}from"@visulima/package";import{join as te,isAbsolute as Ft,resolve as ct}from"@visulima/path";import{Box as h,Text as n,ScrollView as Mt,Spinner as dt,Tabs as zt,Tab as jt,ScrollBar as Et,useApp as Bt,useWindowSize as Lt,useInput as _t,Dialog as Ut,render as Vt}from"@visulima/tui";import{B as qe,an as Xe,F as Le,A as ut,u as Ht,g as ht,V as pt,S as Gt,b as Yt,U as Kt,p as u,ah as j,s as Jt,h as qt}from"./bin.js";import Xt,{useSyncExternalStore as Wt,useState as xe,useEffect as Be,useRef as We,useMemo as Pe,useCallback as Qe}from"react";import{X as ii}from"../packem_shared/xxh3-DrAUNq4n.js";import{isAccessibleSync as Fe,readJsonSync as gt,ensureDirSync as ri}from"@visulima/fs";import{findVisConfigFile as ni}from"./applyDefaults.js";import{x as et,A as ft,L as oi,F as si,T as li,I as ai,a as ci,U as di}from"./handler40.js";import{c as ui}from"../packem_shared/runtime-check-BXZ43CBW.js";import{f as hi,l as pi,s as gi}from"../packem_shared/dependency-scan-COr5n63B.js";import{jsxs as a,jsx as r,Fragment as fi}from"react/jsx-runtime";import{u as mi}from"../packem_shared/use-measured-height-DjYgUOKk.js";import{readYamlSync as yi}from"@visulima/fs/yaml";import{s as ki}from"../packem_shared/verify-Baj5mFJ7.js";const Nt=Ot(import.meta.url),Ce=typeof globalThis<"u"&&typeof globalThis.process<"u"?globalThis.process:process,Ue=D(e=>{if(typeof Ce<"u"&&Ce.versions&&Ce.versions.node){const[t,i]=Ce.versions.node.split(".").map(Number);if(t>22||t===22&&i>=3||t===20&&i>=16)return Ce.getBuiltinModule(e)}return Nt(e)},"__cjs_getBuiltinModule"),{statSync:Qt,rmSync:Ze,writeFileSync:Zt,readFileSync:ei}=Ue("node:fs"),{homedir:ti}=Ue("node:os"),{spawnSync:mt}=Ue("node:child_process");var wi=Object.defineProperty,Ae=D((e,t)=>wi(e,"name",{value:t,configurable:!0}),"r$2");const _e=Ae(()=>te(ti(),".vis","cache","doctor"),"getCacheDirectory"),vi=1800*1e3,tt=Ae(e=>{if(!e)return"";try{return String(Qt(e).mtimeMs)}catch{return""}},"safeMtime"),bi=2,Si=Ae(e=>{const t=JSON.stringify({configMtime:tt(e.configPath),lockfileMtime:tt(e.lockfilePath),schema:bi,sections:[...e.sections].toSorted(),socketEnabled:e.socketEnabled,workspaceRoot:e.workspaceRoot});return ii(Buffer.from(t))},"buildDoctorCacheKey"),$i=Ae(e=>{const t=te(_e(),`${e}.json`);if(Fe(t))try{const i=gt(t);if(Date.now()-i.createdAt>i.ttlMs){Ze(t,{force:!0});return}return{...i.results,sections:new Set(i.results.sections)}}catch{Ze(t,{force:!0});return}},"readDoctorCache"),Ci=Ae((e,t,i=vi)=>{ri(_e());const s={createdAt:Date.now(),results:{...t,sections:[...t.sections]},ttlMs:i};Zt(te(_e(),`${e}.json`),JSON.stringify(s,void 0,2),"utf8")},"writeDoctorCache");var xi=Object.defineProperty,U=D((e,t)=>xi(e,"name",{value:t,configurable:!0}),"i$3");const Oe="orphans",Ri=U(()=>{if(process.platform!=="linux")return{id:"inotify",message:"inotify capacity check skipped (not Linux).",status:"skip"};let e;try{const t=ei("/proc/sys/fs/inotify/max_user_watches","utf8").trim(),i=Number.parseInt(t,10);Number.isFinite(i)&&i>0&&(e=i)}catch{return{id:"inotify",message:"Could not read /proc/sys/fs/inotify/max_user_watches.",status:"warn"}}return e===void 0?{id:"inotify",message:"inotify max_user_watches reported a non-numeric value.",status:"warn"}:e<65536?{detail:{maxWatches:e},id:"inotify",message:`inotify watcher limit is ${String(e)} — large monorepos can exhaust this. Bump now with \`sudo sysctl fs.inotify.max_user_watches=524288\` and persist via \`/etc/sysctl.d/99-vis.conf\` so it survives reboot.`,status:"warn"}:{detail:{maxWatches:e},id:"inotify",message:`inotify capacity OK (${String(e)} watches).`,status:"ok"}},"checkInotifyCapacity"),Ti=U(()=>{const e=!!process.stdin.isTTY,t=!!process.stdout.isTTY;return e&&t?{id:"tty",message:"Interactive TTY available — watch keybinds enabled.",status:"ok"}:!e&&!t?{id:"tty",message:"No TTY on stdin/stdout — running in CI / piped mode (keybinds disabled).",status:"skip"}:{detail:{stdin:String(e),stdout:String(t)},id:"tty",message:e?"stdin is a TTY but stdout is not — output is being captured; keybinds still work.":"stdout is a TTY but stdin is not — keybinds disabled (input is piped).",status:"skip"}},"checkTtyAvailability"),Ai=U(()=>{const e=process.pid;try{return process.platform==="win32"?wt(e):kt(e)}catch{return[]}},"listOrphanPids"),Ii=U(()=>{const e=process.pid;let t;try{t=process.platform==="win32"?wt(e):kt(e)}catch{return{id:Oe,message:"Could not enumerate processes (ps/tasklist failed).",status:"warn"}}if(t.length===0)return{id:Oe,message:"No orphaned vis/task-runner processes detected.",status:"ok"};if(t.length<=2)return{detail:{count:t.length,pids:t.join(",")},id:Oe,message:`${String(t.length)} possibly orphaned process(es) detected (PIDs: ${t.join(", ")}). Likely benign.`,status:"skip"};const i=process.platform==="win32"?t.map(s=>`taskkill /F /PID ${String(s)}`).join(" & "):`kill ${t.join(" ")}`;return{detail:{count:t.length,pids:t.join(",")},id:"orphans",message:`${String(t.length)} possibly orphaned vis/task-runner processes — run \`vis doctor --fix\` to clean them up, or kill them manually: ${i}`,status:"warn"}},"checkOrphanedRunners"),Pi=U((e={})=>{const t=e.enumerate??Ai,i=e.force===!0?"SIGKILL":"SIGTERM",s=e.kill??Mi,o=t(),l=[],c=[];for(const p of o)try{s(p,i),l.push(p)}catch(d){const $=d.code??d.message;if($==="ESRCH"){l.push(p);continue}c.push({pid:p,reason:$})}return{failed:c,killed:l}},"killOrphanedRunners"),Di=U(e=>mt("taskkill",e,{encoding:"utf8"}),"defaultTaskkillRunner"),Oi=U((e,t)=>{process.kill(e,t)},"defaultProcessKill"),Ni=U((e,t,i=Di)=>{const s=t==="SIGKILL"?["/F","/PID",String(e)]:["/PID",String(e)],o=i(s);if(o.error)throw o.error;if(typeof o.status=="number"&&o.status!==0){const l=o.status===128?"ESRCH":`taskkill exited with code ${String(o.status)}`,c=new Error(l);throw c.code=l,c}},"killViaTaskkill"),Fi=U((e,t,i=Oi)=>{i(e,t)},"killViaSignal"),Mi=U((e,t)=>{if(process.platform==="win32"){Ni(e,t);return}Fi(e,t)},"defaultKill"),yt=U((e,t)=>{const i=mt(e,t,{encoding:"utf8"});if(i.error)throw i.error;if(typeof i.status=="number"&&i.status!==0)throw new Error(`${e} exited with code ${String(i.status)}`);return typeof i.stdout=="string"?i.stdout:""},"runProcessListing"),kt=U(e=>{const t=yt("ps",["-Ao","pid=,command="]),i=[];for(const s of t.split(`
|
|
2
|
+
`)){if(s.length===0)continue;const o=/^\s*(\d+)\s+(.+)$/.exec(s);if(!o)continue;const l=Number.parseInt(o[1]??"",10),c=(o[2]??"").toLowerCase();!Number.isFinite(l)||l===e||(/(?:^|[ /])vis-native(?:\s|$|[-.])/.test(c)||/(?:^|[ /])vis\s+run\b/.test(c)||/(?:^|[ /])task-runner(?:\s|$|[-.])/.test(c))&&i.push(l)}return i},"listOrphansUnix"),wt=U(e=>{const t=yt("tasklist",["/FO","CSV","/NH"]),i=[];for(const s of t.split(/\r?\n/)){if(s.length===0)continue;const o=s.split(/","/).map(p=>p.replaceAll(/^"|"$/g,"")),l=(o[0]??"").toLowerCase(),c=Number.parseInt(o[1]??"",10);!Number.isFinite(c)||c===e||(l==="vis.exe"||l.startsWith("vis-native")||l.includes("task-runner"))&&i.push(c)}return i},"listOrphansWindows"),zi=U(()=>[Ri(),Ti(),Ii()],"runRuntimeDiagnostics");var ji=Object.defineProperty,ke=D((e,t)=>ji(e,"name",{value:t,configurable:!0}),"s$2");const ye=[{id:"dependencies",label:"Deps"},{id:"security",label:"Security"},{id:"optimization",label:"Optimize"},{id:"runtime",label:"Runtime"}],Me=["dependencies","security","optimization","runtime"],fe=ke(e=>{const t=new Map;for(const i of Me)t.set(i,[]);for(const i of e)t.get(i.section).push(i);for(const[i,s]of t)s.length===0&&t.delete(i);return t},"groupBySection"),me=ke((e,t,i,s)=>{let o=e.filter(l=>l.section===t);if(s&&(o=o.filter(l=>l.severity===s)),i){const l=i.toLowerCase();o=o.filter(c=>c.title.toLowerCase().includes(l))}return[...o]},"filterFindings"),Ei=ke(e=>{const t={dependencies:"idle",optimization:"idle",runtime:"idle",security:"idle"};for(const i of Me)e.has(i)&&(t[i]="idle");return t},"initialStatus");class it{static{D(this,"DoctorStore")}static{ke(this,"DoctorStore")}#e;#i=new Set;constructor(t=[]){const i=Array.isArray(t)?{findings:t}:t,s=i.findings??[],o=i.activeSections??new Set(Me),l=Me.find(d=>o.has(d))??"dependencies",c=me(s,l,"",void 0),p=Ei(o);if(s.length>0)for(const d of s)p[d.section]="done";this.#e={all:s,entries:c,filterActive:!1,filterText:"",filterType:l,focusedPanel:"list",grouped:fe(c),pendingAction:void 0,sectionError:{},sectionMessage:{},sectionStatus:p,selectedIndex:0,severityFilter:void 0}}getSnapshot=ke(()=>this.#e,"getSnapshot");subscribe=ke(t=>(this.#i.add(t),()=>{this.#i.delete(t)}),"subscribe");setSelectedIndex(t){const i=Math.max(0,Math.min(t,this.#e.entries.length-1));i!==this.#e.selectedIndex&&this.#t({...this.#e,selectedIndex:i})}setFocusedPanel(t){t!==this.#e.focusedPanel&&this.#t({...this.#e,focusedPanel:t})}setFilterType(t){if(t===this.#e.filterType)return;const i=me(this.#e.all,t,this.#e.filterText,this.#e.severityFilter);this.#t({...this.#e,entries:i,filterType:t,grouped:fe(i),selectedIndex:0})}setFilter(t){const i=me(this.#e.all,this.#e.filterType,t,this.#e.severityFilter);this.#t({...this.#e,entries:i,filterText:t,grouped:fe(i),selectedIndex:0})}setFilterActive(t){if(t===this.#e.filterActive)return;if(t){this.#t({...this.#e,filterActive:!0});return}const i=me(this.#e.all,this.#e.filterType,"",this.#e.severityFilter);this.#t({...this.#e,entries:i,filterActive:!1,filterText:"",grouped:fe(i),selectedIndex:0})}setPendingAction(t){this.#t({...this.#e,pendingAction:t})}setSeverityFilter(t){if(t===this.#e.severityFilter)return;const i=me(this.#e.all,this.#e.filterType,this.#e.filterText,t);this.#t({...this.#e,entries:i,grouped:fe(i),selectedIndex:0,severityFilter:t})}startSection(t,i){this.#t({...this.#e,sectionMessage:{...this.#e.sectionMessage,[t]:i},sectionStatus:{...this.#e.sectionStatus,[t]:"running"}})}completeSection(t,i){const s=[...this.#e.all,...i],o=me(s,this.#e.filterType,this.#e.filterText,this.#e.severityFilter),l={...this.#e.sectionMessage};delete l[t],this.#t({...this.#e,all:s,entries:o,grouped:fe(o),sectionMessage:l,sectionStatus:{...this.#e.sectionStatus,[t]:"done"}})}failSection(t,i){this.#t({...this.#e,sectionError:{...this.#e.sectionError,[t]:i},sectionStatus:{...this.#e.sectionStatus,[t]:"error"}})}#t(t){this.#e=t;for(const i of this.#i)try{i()}catch{}}}var Bi=Object.defineProperty,vt=D((e,t)=>Bi(e,"name",{value:t,configurable:!0}),"r$1");const rt={error:0,warn:1},Li=vt(e=>!!e.acceptedRisk,"isAcknowledged"),bt=vt(e=>{const t=[];if(e.sections.has("dependencies")){for(const i of e.outdated)t.push({entry:i,id:`outdated:${i.packageName}`,kind:"outdated",section:"dependencies",severity:"warn",subtitle:`${i.currentRange} → ${i.newRange} (${i.updateType})`,title:i.packageName});for(const i of e.duplicates)t.push({id:`duplicate:${i.name}`,kind:"duplicate",pkg:i,section:"dependencies",severity:"warn",subtitle:`${String(i.versions.length)} versions installed`,title:i.name})}if(e.sections.has("security"))for(const i of e.outdated){if(i.vulnerabilities&&i.vulnerabilities.length>0){const s=i.vulnerabilities[0],o=Li(i)?"warn":"error",l=i.vulnerabilities.length;t.push({entry:i,id:`vuln:${i.packageName}`,kind:"vulnerability",packageName:i.packageName,section:"security",severity:o,subtitle:l===1?`${s.severity} · ${s.id}`:`${String(l)} advisories · top: ${s.severity} ${s.id}`,title:i.packageName})}if(i.socketReport&&i.socketReport.alerts.length>0){const s=Math.round(i.socketReport.score.overall*100);t.push({entry:i,id:`socket:${i.packageName}`,kind:"socket",packageName:i.packageName,section:"security",severity:"warn",subtitle:`${String(i.socketReport.alerts.length)} alert${i.socketReport.alerts.length===1?"":"s"} · score ${String(s)}%`,title:i.packageName})}}if(e.sections.has("optimization"))for(const i of e.optimizations)t.push({entry:i,id:`opt:${i.packageName}`,kind:"optimization",section:"optimization",severity:"warn",subtitle:`${i.category} → ${i.replacement}`,title:i.packageName});if(e.sections.has("runtime"))for(const i of e.runtime)i.status==="warn"&&t.push({diagnostic:i,id:`runtime:${i.id}`,kind:"runtime",section:"runtime",severity:"warn",title:i.message});return t.sort((i,s)=>{if(i.section!==s.section){const o=["dependencies","security","optimization","runtime"];return o.indexOf(i.section)-o.indexOf(s.section)}return rt[i.severity]-rt[s.severity]}),t},"flattenFindings"),St={dependencies:"Dependencies",optimization:"Optimization",runtime:"Runtime",security:"Security"};var _i=Object.defineProperty,Ui=D((e,t)=>_i(e,"name",{value:t,configurable:!0}),"i$2");const Vi={error:"red",warn:"yellow"},Hi={error:"✖",warn:"⚠"},Gi={error:" ERROR ",warn:" WARN "},Yi=Ui(({children:e,hint:t,message:i,severity:s,title:o})=>{const l=Vi[s];return a(h,{borderColor:l,borderStyle:"single",flexDirection:"column",flexShrink:0,paddingX:1,children:[a(h,{gap:1,children:[r(n,{backgroundColor:l,bold:!0,color:"black",children:Gi[s]}),r(n,{bold:!0,color:l,children:Hi[s]}),r(n,{bold:!0,wrap:"truncate-end",children:o})]}),r(n,{wrap:"truncate-end",children:i}),t?r(n,{dimColor:!0,wrap:"truncate-end",children:t}):null,e]})},"ConfigBanner");var Ki=Object.defineProperty,ie=D((e,t)=>Ki(e,"name",{value:t,configurable:!0}),"d");const Ji={CRITICAL:"red",HIGH:"red",LOW:"gray",MODERATE:"yellow",UNKNOWN:"gray"},qi={critical:"red",high:"red",low:"gray",medium:"yellow"},Xi={major:"red",minor:"yellow",patch:"green"},x=ie(({children:e,label:t,width:i=14})=>a(h,{children:[r(h,{width:i,children:a(n,{dimColor:!0,children:[t,":"]})}),typeof e=="string"?r(n,{children:e}):e]}),"FieldRow"),se=ie(({children:e})=>r(h,{marginTop:1,children:r(n,{bold:!0,color:"white",children:e})}),"SectionTitle"),Wi=ie(({finding:e})=>{const{entry:t}=e,i=Xi[t.updateType]??"white";return a(h,{flexDirection:"column",children:[r(x,{label:"Current",children:t.currentRange}),a(x,{label:"Target",children:[r(n,{children:t.newRange}),a(n,{bold:!0,color:i,children:[" (",t.updateType,")"]})]}),r(x,{label:"Catalog",children:t.catalogName}),t.acceptedRisk?r(x,{label:"Risk ack",children:r(n,{dimColor:!0,children:t.acceptedRisk.reason??"(no reason recorded)"})}):null,r(se,{children:"Action"}),a(n,{dimColor:!0,children:["Run"," ",r(n,{bold:!0,color:"white",children:"vis update"})," ","to apply this change."]})]})},"OutdatedDetail"),Qi=ie(({finding:e})=>a(h,{flexDirection:"column",children:[r(x,{label:"Versions",children:r(n,{children:String(e.pkg.versions.length)})}),r(se,{children:"Installed versions"}),e.pkg.versions.map(t=>a(n,{children:[" · ",t]},t)),r(se,{children:"Action"}),a(n,{dimColor:!0,children:["Run"," ",r(n,{bold:!0,color:"white",children:"vis dedupe"})," ","to consolidate to a single resolution."]})]}),"DuplicateDetail"),Zi=ie(({finding:e})=>{const t=e.entry.vulnerabilities??[];return a(h,{flexDirection:"column",children:[r(x,{label:"Package",children:e.packageName}),r(x,{label:"Current",children:e.entry.currentRange}),r(x,{label:"Advisories",children:String(t.length)}),e.entry.acceptedRisk?r(x,{label:"Risk ack",children:r(n,{dimColor:!0,children:e.entry.acceptedRisk.reason??"(no reason recorded)"})}):null,t.map(i=>{const s=Ji[i.severity]??"gray";return a(h,{flexDirection:"column",marginTop:1,children:[a(h,{children:[r(n,{bold:!0,color:s,children:i.severity}),r(n,{children:" "}),r(n,{children:i.id}),typeof i.cvssScore=="number"?a(n,{dimColor:!0,children:[" · CVSS ",i.cvssScore.toFixed(1)]}):null]}),r(n,{wrap:"wrap",children:i.summary}),i.fixedVersions.length>0?a(n,{dimColor:!0,children:["Fixed in: ",i.fixedVersions.join(", ")]}):null,i.aliases&&i.aliases.length>0?a(n,{dimColor:!0,children:["Aliases: ",i.aliases.join(", ")]}):null]},i.id)})]})},"VulnerabilityDetail"),er=ie(({finding:e})=>{const t=e.entry.socketReport;if(!t)return r(n,{dimColor:!0,children:"No Socket report attached."});const i=Math.round(t.score.overall*100),s=qe(t.score.overall);return a(h,{flexDirection:"column",children:[r(x,{label:"Package",children:e.packageName}),r(x,{label:"Overall",children:a(n,{color:s,children:[String(i),"%"]})}),r(x,{label:"Alerts",children:String(t.alerts.length)}),e.entry.acceptedRisk?r(x,{label:"Risk ack",children:r(n,{dimColor:!0,children:e.entry.acceptedRisk.reason??"(no reason recorded)"})}):null,r(se,{children:"Score breakdown"}),Object.entries(t.score).map(([o,l])=>{if(o==="overall")return null;const c=typeof l=="number"?l:0,p=Math.round(c*100),d=qe(c);return a(h,{children:[r(h,{width:14,children:a(n,{dimColor:!0,children:[o,":"]})}),a(n,{color:d,children:[String(p),"%"]})]},o)}),r(se,{children:"Alerts"}),t.alerts.map((o,l)=>{const c=qi[o.severity]??"gray";return a(h,{flexDirection:"column",marginBottom:1,children:[a(h,{children:[r(n,{bold:!0,color:c,children:o.severity}),r(n,{children:" "}),r(n,{children:o.type})]}),o.props?r(n,{dimColor:!0,wrap:"wrap",children:JSON.stringify(o.props)}):null]},`${o.type}-${String(l)}`)})]})},"SocketDetail"),tr=ie(({finding:e})=>{const{entry:t}=e;return a(h,{flexDirection:"column",children:[r(x,{label:"Package",children:t.packageName}),r(x,{label:"Category",children:t.category}),r(x,{label:"Replacement",children:t.replacement}),t.overrideSpec?r(x,{label:"Override",children:t.overrideSpec}):null,r(x,{label:"Codemod",children:r(n,{color:t.hasCodemod?"green":"gray",children:t.hasCodemod?"available":"not available"})}),t.docUrl?r(x,{label:"Guide",children:r(n,{color:"cyan",underline:!0,children:t.docUrl})}):null,r(se,{children:"Action"}),t.hasCodemod?a(n,{dimColor:!0,children:["Run"," ",r(n,{bold:!0,color:"white",children:"vis optimize"})," ","to apply the codemod interactively."]}):t.overrideSpec?a(n,{dimColor:!0,children:["Run"," ",r(n,{bold:!0,color:"white",children:"vis optimize"})," ","to install the package override."]}):t.docUrl?r(n,{dimColor:!0,children:"No automated codemod. Open the migration guide above for the recommended alternative and steps."}):r(n,{dimColor:!0,children:"No automated codemod. Consult the package's docs or the e18e module-replacements guide for an alternative."})]})},"OptimizationDetail"),ir=ie(({finding:e})=>{const{diagnostic:t}=e,i=t.status==="warn"?"yellow":t.status==="ok"?"green":"gray";return a(h,{flexDirection:"column",children:[r(x,{label:"Check",children:t.id}),r(x,{label:"Status",children:r(n,{color:i,children:t.status})}),r(se,{children:"Message"}),r(n,{wrap:"wrap",children:t.message}),t.detail&&Object.keys(t.detail).length>0?a(fi,{children:[r(se,{children:"Details"}),Object.entries(t.detail).map(([s,o])=>a(h,{children:[r(h,{width:20,children:a(n,{dimColor:!0,children:[s,":"]})}),r(n,{children:String(o)})]},s))]}):null]})},"RuntimeDetail"),rr=ie(({finding:e,focused:t,scrollRef:i})=>{const s=t?"white":"gray";if(!e)return r(h,{alignItems:"center",borderColor:"gray",borderStyle:"single",flexDirection:"column",flexGrow:1,justifyContent:"center",children:r(n,{dimColor:!0,children:"No finding selected"})});let o;switch(e.kind){case"duplicate":{o=r(Qi,{finding:e});break}case"optimization":{o=r(tr,{finding:e});break}case"outdated":{o=r(Wi,{finding:e});break}case"runtime":{o=r(ir,{finding:e});break}case"socket":{o=r(er,{finding:e});break}case"vulnerability":{o=r(Zi,{finding:e});break}default:{o=r(n,{dimColor:!0,children:"Unknown finding kind."});break}}return a(h,{borderColor:s,borderStyle:"single",flexDirection:"column",flexGrow:1,children:[a(h,{flexShrink:0,paddingTop:1,paddingX:2,children:[r(n,{bold:!0,color:"white",children:e.title}),a(n,{dimColor:!0,children:[" ",St[e.section]]})]}),a(Mt,{flexGrow:1,flexShrink:1,paddingX:2,ref:i,scrollbar:!0,scrollbarColor:"gray",scrollbarStyle:"block",children:[r(n,{}),o]})]})},"DoctorDetailPanel");var nr=Object.defineProperty,Ie=D((e,t)=>nr(e,"name",{value:t,configurable:!0}),"c$2");const $t={error:"red",warn:"yellow"},or={error:"✖",warn:"⚠"},sr=Ie(e=>e.kind==="outdated"||e.kind==="vulnerability"||e.kind==="socket"?!!e.entry.acceptedRisk:!1,"hasAcceptedRisk"),lr=Ie(({finding:e,isSelected:t})=>{const i=$t[e.severity],s=sr(e);return a(h,{flexShrink:0,height:1,children:[r(n,{children:t?">":" "}),a(n,{color:i,children:[" ",or[e.severity]," "]}),r(h,{flexGrow:1,children:r(n,{bold:t,inverse:t,wrap:"truncate",children:e.title})}),s?r(n,{color:"cyan",children:" ack"}):null,e.subtitle?a(n,{dimColor:!0,wrap:"truncate",children:[" ",e.subtitle]}):null]})},"FindingRow"),ar=Ie(({count:e,section:t})=>a(h,{flexShrink:0,height:1,marginTop:1,children:[r(n,{dimColor:!0,children:"▼ "}),r(n,{bold:!0,color:"white",children:St[t].toUpperCase()}),a(n,{dimColor:!0,children:[" (",e,")"]})]}),"SectionHeader"),cr=Ie(({count:e,label:t,status:i})=>a(n,{children:[t,i==="running"?a(n,{children:[" ",r(dt,{type:"dots"})]}):null,i==="error"?r(n,{bold:!0,color:"red",children:" ✖"}):a(n,{dimColor:!0,children:[" (",String(e),")"]})]}),"TabLabel"),dr=Ie(({elapsedMs:e,entries:t,filterActive:i,filterText:s,filterType:o,focused:l,fromCache:c=!1,grouped:p,onViewportHeightChange:d,scrollOffset:$,sectionCounts:R,sectionMessage:w,sectionStatus:A,selectedIndex:E,severityFilter:g,totalAll:I,viewportHeight:B})=>{const X=l?"white":"gray",{measuredHeight:L,ref:F}=mi(B,d);let P=0,_=0;for(const v of t)v.severity==="error"?P+=1:v.severity==="warn"&&(_+=1);const W=[];P>0&&W.push(`${String(P)} error${P===1?"":"s"}`),_>0&&W.push(`${String(_)} warn${_===1?"":"s"}`);const re=W.length>0?` (${W.join(", ")})`:"",Y=(e/1e3).toFixed(1),O=[];for(const[v,T]of p){O.push(r(ar,{count:T.length,section:v},`hdr-${v}`));for(const C of T){const H=t.indexOf(C);O.push(r(lr,{finding:C,isSelected:H===E},C.id))}}let V=0;for(const[,v]of p)V+=2+v.length;const K=V>L&&L>0;return a(h,{borderColor:X,borderStyle:"single",flexDirection:"column",flexGrow:1,children:[a(h,{flexShrink:0,gap:1,paddingX:1,children:[r(n,{bold:!0,inverse:!0,children:" DOCTOR "}),a(n,{wrap:"truncate",children:[t.length,t.length===I?"":`/${String(I)}`," finding",t.length===1?"":"s",re]}),g?r(n,{bold:!0,color:$t[g],inverse:!0,children:` ${g.toUpperCase()} ONLY `}):null,c?r(n,{bold:!0,color:"cyan",inverse:!0,children:" CACHED "}):null,a(n,{dimColor:!0,children:[" · ",Y,"s"]})]}),r(h,{flexShrink:0,paddingX:1,paddingY:1,children:r(zt,{isFocused:l,keyMap:{next:[],previous:[],useNumbers:!1,useTab:!1},onChange:D(()=>{},"onChange"),showIndex:!1,value:o,children:ye.map(({id:v,label:T})=>r(jt,{name:v,children:r(cr,{count:R[v],label:T,status:A[v]})},v))})}),(()=>{const v=Object.keys(A).filter(T=>A[T]==="running"&&w[T]).map(T=>w[T]);return v.length===0?null:r(h,{flexShrink:0,paddingX:1,children:a(n,{dimColor:!0,wrap:"truncate",children:[r(dt,{type:"dots"})," ",v.join(" · ")]})})})(),i&&a(h,{flexShrink:0,paddingX:1,children:[r(n,{bold:!0,color:"white",children:"/ "}),r(n,{children:s}),r(n,{inverse:!0,children:" "})]}),a(h,{flexDirection:"row",flexGrow:1,overflow:"hidden",ref:F,children:[r(h,{flexDirection:"column",flexGrow:1,overflow:"hidden",paddingLeft:1,children:r(h,{flexDirection:"column",marginTop:-$,children:O.length>0?O:r(h,{marginTop:1,children:r(n,{dimColor:!0,children:"No findings match the current filter."})})})}),K&&r(h,{flexShrink:0,marginLeft:1,marginRight:1,children:r(Et,{contentHeight:V,placement:"inset",scrollOffset:$,style:"block",viewportHeight:L})})]},`list-${o}-${s}`)]})},"DoctorListPanel");var ur=Object.defineProperty,je=D((e,t)=>ur(e,"name",{value:t,configurable:!0}),"g$1");const hr=je(e=>{if(e.kind==="outdated")return{command:`vis update ${e.entry.packageName}`,description:`Update ${e.entry.packageName} to ${e.entry.newRange}`};if(e.kind==="duplicate")return{command:`vis dedupe ${e.pkg.name}`,description:`Dedupe ${e.pkg.name} (${String(e.pkg.versions.length)} versions)`}},"buildUpdateAction"),pr=je(e=>{if(e.kind==="optimization")return{command:`vis optimize ${e.entry.packageName}`,description:`Replace ${e.entry.packageName} with ${e.entry.replacement}`}},"buildOptimizeAction"),gr=je(e=>{if(e.kind!=="outdated"&&e.kind!=="vulnerability"&&e.kind!=="socket")return;const t=e.kind==="outdated"?e.entry.packageName:e.packageName,i=["// Add to vis.config.ts:","security: {"," acceptedRisks: {",` "${t}": {`,' reason: "explain why this risk is acceptable",',' expiresAt: "YYYY-MM-DD",'," },"," },","},"].join(`
|
|
3
|
+
`);return{command:i,configSnippet:i,description:`Acknowledge risk for ${t}`}},"buildAckAction"),fr=100,mr=40,yr=10,kr=je(({autoExitSeconds:e=0,banner:t,fromCache:i=!1,startedAt:s,store:o})=>{const{exit:l}=Bt(),{columns:c,rows:p}=Lt(),d=Wt(o.subscribe,o.getSnapshot),[$,R]=xe(!1),[w,A]=xe(!1),[E,g]=xe(0),[I,B]=xe(()=>Date.now());Be(()=>{const y=setInterval(()=>{B(Date.now())},1e3);return()=>{clearInterval(y)}},[]);const X=I-s,L=We(null),F=We(null),P=d.entries[d.selectedIndex]??null,_=Pe(()=>{const y={dependencies:0,optimization:0,runtime:0,security:0};for(const m of d.all)y[m.section]+=1;return y},[d.all]),W=t?t.hint?5:4:0,re=Pe(()=>{for(const y of Object.keys(d.sectionStatus))if(d.sectionStatus[y]==="running"&&d.sectionMessage[y])return 1;return 0},[d.sectionStatus,d.sectionMessage]),Y=c>=fr,O=Y?Math.max(1,p-W-2):Math.floor(p*.55),V=Pe(()=>Math.max(1,O-6-re-(d.filterActive?1:0)),[O,re,d.filterActive]),[K,v]=xe(V),T=K>0?K:V,C=Pe(()=>{let y=0;for(const[,m]of d.grouped)y+=2+m.length;return y},[d.grouped]),H=Math.max(0,C-T);Be(()=>{g(y=>Math.min(y,H))},[H]);const de=Qe(y=>{let m=0,k=0;for(const[,$e]of d.grouped){m+=2;for(let le=0;le<$e.length;le++){if(k===y)return m;m+=1,k+=1}}return m},[d.grouped]),N=Qe(y=>{const m=de(y);g(k=>m>k+T-2?Math.min(H,Math.max(0,m-T+2)):m<k+1?Math.max(0,m-1):k)},[de,T,H]);if(Be(()=>{F.current?.scrollToTop()},[P?.id]),_t((y,m)=>{if(y==="c"&&m.ctrl){l();return}if(!w){if($){m.escape||y==="?"?R(!1):y==="q"?(R(!1),A(!0)):m.downArrow||y==="j"?L.current?.scrollBy(1):(m.upArrow||y==="k")&&L.current?.scrollBy(-1);return}if(y==="?"){R(!0);return}if(y==="q"){A(!0);return}if(m.tab){o.setFocusedPanel(d.focusedPanel==="list"?"detail":"list");return}if(d.filterActive){if(m.escape||m.return){o.setFilterActive(!1);return}if(m.backspace){g(0),o.setFilter(d.filterText.slice(0,-1));return}y&&!m.ctrl&&!m.meta&&(g(0),o.setFilter(d.filterText+y));return}if(d.focusedPanel==="list"&&(m.leftArrow||m.rightArrow)){const k=ye.findIndex(le=>le.id===d.filterType),$e=m.rightArrow?(k+1)%ye.length:(k-1+ye.length)%ye.length;g(0),F.current?.scrollToTop(),o.setFilterType(ye[$e].id);return}if(d.focusedPanel==="list"){if(m.downArrow||y==="j"){const k=Math.min(d.selectedIndex+1,d.entries.length-1);o.setSelectedIndex(k),N(k);return}if(m.upArrow||y==="k"){const k=Math.max(d.selectedIndex-1,0);o.setSelectedIndex(k),N(k);return}if(m.pageDown){const k=Math.min(d.selectedIndex+10,d.entries.length-1);o.setSelectedIndex(k),N(k);return}if(m.pageUp){const k=Math.max(d.selectedIndex-10,0);o.setSelectedIndex(k),N(k);return}if(m.home){o.setSelectedIndex(0),g(0);return}if(m.end){const k=d.entries.length-1;o.setSelectedIndex(k),N(k);return}if(y==="/"){o.setFilterActive(!0);return}if(y==="e"){o.setSeverityFilter(d.severityFilter==="error"?void 0:"error"),g(0);return}if(y==="w"){o.setSeverityFilter(d.severityFilter==="warn"?void 0:"warn"),g(0);return}if(y==="u"&&P){const k=hr(P);k&&(o.setPendingAction(k),l());return}if(y==="o"&&P){const k=pr(P);k&&(o.setPendingAction(k),l());return}if(y==="a"&&P){const k=gr(P);k&&(o.setPendingAction(k),l());return}if(y==="d"){o.setFocusedPanel("detail");return}return}if(m.escape||m.leftArrow){o.setFocusedPanel("list");return}if(m.downArrow||y==="j"){F.current?.scrollBy(1);return}if(m.upArrow||y==="k"){F.current?.scrollBy(-1);return}if(m.pageDown){F.current?.scrollBy(10);return}if(m.pageUp){F.current?.scrollBy(-10);return}if(m.home){F.current?.scrollToTop();return}m.end&&F.current?.scrollToBottom()}},{isActive:!0}),c<mr||p<yr)return r(h,{alignItems:"center",height:p,justifyContent:"center",width:c,children:a(n,{color:"yellow",children:["Terminal too small (",c,"x",p,")"]})});const Q=d.focusedPanel==="detail",ee=[a(h,{gap:1,children:[r(n,{bold:!0,color:"white",children:"q"}),r(n,{dimColor:!0,children:"QUIT"})]},"q"),a(h,{gap:1,children:[r(n,{bold:!0,color:"white",children:"?"}),r(n,{dimColor:!0,children:"HELP"})]},"?"),a(h,{gap:1,children:[r(n,{bold:!0,color:"white",children:"↑↓"}),r(n,{dimColor:!0,children:Q?"SCROLL":"NAV"})]},"nav"),Q?a(h,{gap:1,children:[r(n,{bold:!0,color:"white",children:"←/Esc"}),r(n,{dimColor:!0,children:"LIST"})]},"lr"):a(h,{gap:1,children:[r(n,{bold:!0,color:"white",children:"←→"}),r(n,{dimColor:!0,children:"SECTION"})]},"lr"),a(h,{gap:1,children:[r(n,{bold:!0,color:"white",children:"/"}),r(n,{dimColor:!0,children:"SEARCH"})]},"search"),a(h,{gap:1,children:[r(n,{bold:!0,color:"white",children:"e/w"}),r(n,{dimColor:!0,children:"SEVERITY"})]},"sev"),a(h,{gap:1,children:[r(n,{bold:!0,color:"white",children:"u/o/a"}),r(n,{dimColor:!0,children:"ACTION"})]},"actions"),a(h,{gap:1,children:[r(n,{bold:!0,color:"white",children:"Tab"}),r(n,{dimColor:!0,children:"PANEL"})]},"tab")],ue=r(h,{borderBottom:!1,borderColor:"gray",borderLeft:!1,borderRight:!1,borderStyle:"single",flexShrink:0,children:r(h,{gap:2,overflow:"hidden",paddingX:1,children:ee})}),he=a(Ut,{footer:a(n,{dimColor:!0,children:[r(n,{bold:!0,color:"white",children:"↑↓"})," scroll ",r(n,{bold:!0,color:"white",children:"?"}),"/",r(n,{bold:!0,color:"white",children:"Esc"})," close"]}),scrollRef:L,title:"DOCTOR — KEYBOARD SHORTCUTS",visible:$,width:56,children:[a(h,{flexDirection:"column",marginBottom:1,children:[a(h,{marginBottom:1,children:[r(n,{dimColor:!0,children:"── "}),r(n,{bold:!0,color:"white",children:"NAVIGATION"})]}),a(h,{children:[r(h,{width:26,children:a(n,{children:[r(n,{bold:!0,color:"white",children:" ↑/k "}),r(n,{dimColor:!0,children:"Move up"})]})}),a(n,{children:[r(n,{bold:!0,color:"white",children:" ↓/j "}),r(n,{dimColor:!0,children:"Move down"})]})]}),a(h,{children:[r(h,{width:26,children:a(n,{children:[r(n,{bold:!0,color:"white",children:" PgUp"}),r(n,{dimColor:!0,children:" Jump up 10"})]})}),a(n,{children:[r(n,{bold:!0,color:"white",children:" PgDn"}),r(n,{dimColor:!0,children:" Jump down 10"})]})]}),a(h,{children:[r(h,{width:26,children:a(n,{children:[r(n,{bold:!0,color:"white",children:" Home"}),r(n,{dimColor:!0,children:" Jump to top"})]})}),a(n,{children:[r(n,{bold:!0,color:"white",children:" End"}),r(n,{dimColor:!0,children:" Jump to bottom"})]})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" Tab"}),r(n,{dimColor:!0,children:" Switch panel"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" →/←"}),r(n,{dimColor:!0,children:" Section tabs (list) / Focus list (detail)"})]})]}),a(h,{flexDirection:"column",marginBottom:1,children:[a(h,{marginBottom:1,children:[r(n,{dimColor:!0,children:"── "}),r(n,{bold:!0,color:"white",children:"FILTER"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" /"}),r(n,{dimColor:!0,children:" Open text filter (Esc/Enter to close)"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" e"}),r(n,{dimColor:!0,children:" Toggle errors-only filter"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" w"}),r(n,{dimColor:!0,children:" Toggle warns-only filter"})]})]}),a(h,{flexDirection:"column",marginBottom:1,children:[a(h,{marginBottom:1,children:[r(n,{dimColor:!0,children:"── "}),r(n,{bold:!0,color:"white",children:"ACTIONS"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" u"}),r(n,{dimColor:!0,children:" Exit + suggest update / dedupe command"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" o"}),r(n,{dimColor:!0,children:" Exit + suggest optimize command"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" a"}),r(n,{dimColor:!0,children:" Exit + print risk-ack snippet"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" d"}),r(n,{dimColor:!0,children:" Focus detail panel"})]})]}),a(h,{flexDirection:"column",children:[a(h,{marginBottom:1,children:[r(n,{dimColor:!0,children:"── "}),r(n,{bold:!0,color:"white",children:"EXIT"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" q"}),r(n,{dimColor:!0,children:" Quit (with countdown)"})]}),a(n,{children:[r(n,{bold:!0,color:"white",children:" Ctrl+C"}),r(n,{dimColor:!0,children:" Quit immediately"})]})]})]}),pe=r(dr,{elapsedMs:X,entries:d.entries,filterActive:d.filterActive,filterText:d.filterText,filterType:d.filterType,focused:d.focusedPanel==="list",fromCache:i,grouped:d.grouped,onViewportHeightChange:v,scrollOffset:E,sectionCounts:_,sectionMessage:d.sectionMessage,sectionStatus:d.sectionStatus,selectedIndex:d.selectedIndex,severityFilter:d.severityFilter,totalAll:d.all.length,viewportHeight:T}),ge=t?r(Yi,{hint:t.hint,message:t.message,severity:t.severity,title:t.title}):null,Se=r(rr,{finding:P,focused:d.focusedPanel==="detail",scrollRef:F});if(Y){const y=Math.floor(c*.4);return a(h,{flexDirection:"column",height:p,width:c,children:[ge,a(h,{flexDirection:"row",flexGrow:1,children:[r(h,{flexGrow:1,children:pe}),r(h,{width:y,children:Se})]}),ue,r(Xe,{autoExitSeconds:e||3,onCancel:D(()=>{A(!1)},"onCancel"),visible:w}),he]})}return a(h,{flexDirection:"column",height:p,width:c,children:[ge,r(h,{height:O,children:pe}),r(h,{flexGrow:1,children:Se}),ue,r(Xe,{autoExitSeconds:e||3,onCancel:D(()=>{A(!1)},"onCancel"),visible:w}),he]})},"VisDoctorApp");var wr=Object.defineProperty,ve=D((e,t)=>wr(e,"name",{value:t,configurable:!0}),"n");const vr=ve(e=>e.replaceAll(/[$()+.?[\\\]^{|}]/g,String.raw`\$&`),"escapeRegex"),br=ve(e=>{const t=e.split("*").map(i=>vr(i));return new RegExp(`^${t.join(".*")}$`,"i")},"compilePattern"),Sr=ve(e=>e?e.split(",").map(t=>t.trim()).filter(t=>t.length>0).map(t=>br(t)):[],"parseFilterPatterns"),Ne=ve((e,t)=>{for(const i of t)if(i.test(e))return!0;return!1},"matchesAny"),$r=ve((e,t)=>{if(t.length===0)return e;const i=e.outdated.filter(d=>Ne(d.packageName,t)),s=e.duplicates.filter(d=>Ne(d.name,t)),o=e.optimizations.filter(d=>Ne(d.packageName,t));let l=0,c=0,p=0;for(const d of i)d.vulnerabilities&&(l+=d.vulnerabilities.length),d.socketReport&&(c+=d.socketReport.alerts.length,d.socketReport.score.overall<Le&&(p+=1));return{...e,duplicates:s,optimizations:o,outdated:i,socketIssues:{alerts:c,lowScore:p},vulnCount:l}},"applyFilter"),Ct=ve((e,t)=>t.length===0?[...e]:e.filter(i=>{if(i.kind==="runtime")return!0;const s=i.kind==="duplicate"?i.pkg.name:i.kind==="outdated"||i.kind==="optimization"?i.entry.packageName:i.packageName;return Ne(s,t)}),"filterFindingsByPattern");var Cr=Object.defineProperty,be=D((e,t)=>Cr(e,"name",{value:t,configurable:!0}),"r");const xt=["dependencies","security","optimization","runtime"],nt=be(e=>{const t=new Set;if(!e)return t;for(const i of e.split(",")){const s=i.trim().toLowerCase();xt.includes(s)&&t.add(s)}return t},"parseSectionList"),xr=be((e,t)=>{if(e!==void 0&&e!=="")return nt(e);const i=nt(t);return new Set(xt.filter(s=>!i.has(s)))},"resolveSections"),Rt=be(e=>{const t={micro:0,native:0,preferred:0,socket:0,total:e.length};for(const i of e)switch(i.category){case"micro-utility":{t.micro+=1;break}case"native":{t.native+=1;break}case"preferred":{t.preferred+=1;break}case"socket":{t.socket+=1;break}}return t},"summarizeOptimizations"),oe=be((e,t)=>{if(!e.sections.has(t))return"skip";switch(t){case"dependencies":return e.outdated.length>0||e.duplicates.length>0?"warn":"ok";case"optimization":return e.optimizations.length>0?"warn":"ok";case"runtime":return e.runtime.some(i=>i.status==="warn")?"warn":"ok";case"security":return e.vulnCount>0||e.socketIssues.alerts>0?"error":e.socketIssues.lowScore>0?"warn":"ok";default:return"ok"}},"sectionStatus"),Rr=be((e,t)=>{const i=Rt(e.optimizations),s={dependencies:oe(e,"dependencies"),optimization:oe(e,"optimization"),runtime:oe(e,"runtime"),security:oe(e,"security")},o=new Set([...Object.values(s),e.supplyChain.status]),l=o.has("error")?"error":o.has("warn")?"warn":"ok";return{dependencies:{duplicates:e.duplicates.length,installed:e.installedCount,outdated:e.outdated.length,status:s.dependencies},elapsedMs:e.elapsedMs,optimizations:{microUtilities:i.micro,native:i.native,preferred:i.preferred,socket:i.socket,status:s.optimization,total:i.total},packageManager:t,runtime:e.runtime.map(c=>({detail:c.detail,id:c.id,message:c.message,status:c.status})),runtimeStatus:s.runtime,security:{alerts:e.socketIssues.alerts,lowScorePackages:e.socketIssues.lowScore,status:s.security,vulnerabilities:e.vulnCount},status:l,supplyChain:{findings:e.supplyChain.findings.map(c=>({detail:c.detail,label:c.label,severity:c.severity})),status:e.supplyChain.status},workspaces:e.workspaceCount}},"buildJsonPayload"),ot=be((e,t)=>{const i=e.runtime.some(o=>o.status==="warn"),s=e.vulnCount>0||e.socketIssues.alerts>0;return t?s||e.outdated.length>0||e.duplicates.length>0||i:s},"shouldFail");var Tr=Object.defineProperty,Ve=D((e,t)=>Tr(e,"name",{value:t,configurable:!0}),"i");const Ar=/^(@[\w./-]+\/[\w./-]+|[\w.-]+)@(.+)$/,Ir=Ve(e=>{const t=Ar.exec(e);if(t)return{name:t[1],version:t[2]}},"parsePatchKey"),Pr=Ve((e,t)=>{let i;try{if(t==="pnpm"){const o=te(e,"pnpm-workspace.yaml");Fe(o)&&(i=yi(o)?.patchedDependencies)}else if(t==="bun"){const o=te(e,"package.json");Fe(o)&&(i=gt(o)?.patchedDependencies)}}catch{return[]}if(!i||typeof i!="object")return[];const s=[];for(const[o,l]of Object.entries(i)){if(typeof l!="string"||l.length===0)continue;const c=Ir(o);c&&s.push({name:c.name,patchFile:l,resolvedPatchFile:Ft(l)?l:ct(e,l),version:c.version})}return s},"readPatchedDependencies"),Dr=Ve(e=>{const t=[];for(const i of e)Fe(i.resolvedPatchFile)||t.push({entry:i,kind:"missing-file"});return t},"findPatchIssues");var Or=Object.defineProperty,Tt=D((e,t)=>Or(e,"name",{value:t,configurable:!0}),"a");const st=Tt(e=>e.some(t=>t.severity==="error")?"error":e.some(t=>t.severity==="warn")?"warn":"ok","rollUpStatus"),Nr=Tt((e,t={})=>{const i=[],s=e?.security;if(!s)return i.push({detail:"Use defineConfig() from '@visulima/vis/config' to apply secure defaults.",label:"No security config — running with the PM's native defaults",severity:"warn"}),{findings:i,status:st(i)};s.minimumReleaseAge===void 0?i.push({detail:"Set security.minimumReleaseAge to block packages published in the last N minutes (mitigates supply-chain attacks).",label:"minimumReleaseAge is not set",severity:"warn"}):s.minimumReleaseAge===0?i.push({detail:"New packages can be installed immediately after publishing. Consider setting a non-zero cooldown.",label:"minimumReleaseAge is explicitly 0",severity:"warn"}):i.push({label:`minimumReleaseAge: ${String(s.minimumReleaseAge)} minutes`,severity:"ok"}),s.trustPolicy===void 0||s.trustPolicy==="off"?i.push({detail:"Packages whose trust level has decreased will not be blocked. Consider 'no-downgrade'.",label:`trustPolicy: ${s.trustPolicy??"not set"}`,severity:"warn"}):i.push({label:`trustPolicy: ${s.trustPolicy}`,severity:"ok"}),s.blockExoticSubdeps===void 0||!s.blockExoticSubdeps?i.push({detail:"Transitive dependencies can pull code from git repos or tarball URLs. Set to true to block.",label:`blockExoticSubdeps: ${String(s.blockExoticSubdeps??!1)}`,severity:"warn"}):i.push({label:"blockExoticSubdeps: true",severity:"ok"});const o=s.allowBuilds?Object.keys(s.allowBuilds).length:0;if(o===0?i.push({detail:"Lifecycle scripts are blocked by default. List trusted packages here to opt them back in (e.g. esbuild, @prisma/client).",label:"allowBuilds: not configured",severity:"warn"}):i.push({label:`allowBuilds: ${String(o)} ${o===1?"entry":"entries"}`,severity:"ok"}),s.strictDepBuilds&&o===0&&i.push({detail:"All dependencies with build scripts will be blocked. Run 'vis approve-builds' to populate allowBuilds.",label:"strictDepBuilds is on but allowBuilds is empty",severity:"error"}),t.workspaceRoot){const l=ki(t.workspaceRoot);if(l.length>0){const c=[...new Set(l.map(p=>p.tool))].sort((p,d)=>p.localeCompare(d)).join(", ");i.push({detail:"Run `vis migrate verify` for the full list, then re-run `vis migrate <tool>` to clean up.",label:`${String(l.length)} leftover ${l.length===1?"reference":"references"} to ${c}`,severity:"warn"})}}if(t.workspaceRoot&&t.packageManager){const l=Pr(t.workspaceRoot,t.packageManager);if(l.length>0){const c=Dr(l);if(c.length===0)i.push({label:`patchedDependencies: ${String(l.length)} ${l.length===1?"entry":"entries"} resolved`,severity:"ok"});else for(const p of c)i.push({detail:`Referenced from ${t.packageManager==="pnpm"?"pnpm-workspace.yaml":"package.json"} but the file is not present at ${p.entry.patchFile}.`,label:`patchedDependencies: missing patch file for ${p.entry.name}@${p.entry.version}`,severity:"error"})}}return{findings:i,status:st(i)}},"buildSupplyChainPosture");var Fr=Object.defineProperty,S=D((e,t)=>Fr(e,"name",{value:t,configurable:!0}),"u");const G=S(e=>e>=1e3?`${(e/1e3).toFixed(1)}s`:`${String(Math.round(e))}ms`,"fmtDuration"),De=S(async(e,t,i,s)=>{if(!e)return i();e.start(t);const o=Date.now();try{const l=await i(),c=Date.now()-o,{status:p,summary:d}=s(l,c);return e.finish(t,p,d),l}catch(l){const c=Date.now()-o,p=l instanceof Error?l.message:String(l);throw e.finish(t,"error",`${p} (${G(c)})`),l}},"tracked"),Mr=S((e,t)=>{const i={duplicates:t.duplicates,elapsedMs:0,installedCount:0,optimizations:t.optimizations,outdated:t.outdated,runtime:t.runtime,sections:new Set([e]),socketIssues:{alerts:0,lowScore:0},supplyChain:{findings:[],status:"ok"},vulnCount:0,workspaceCount:0};return bt(i)},"buildSectionFindings"),lt=S(async e=>{const{filterPatterns:t,installed:i,progress:s,resolveCodemods:o,sections:l,store:c,visConfig:p,workspaceRoot:d}=e,$=l.has("dependencies"),R=l.has("security"),w=l.has("optimization"),A=l.has("runtime"),E=S((f,M)=>Ct(Mr(f,M),t),"sectionFindings"),g=ut(d),{packageManager:I}=at(d),B=et(te(d,"package.json"),!1),X=ft(d),L=new Set(B);for(const f of X){const M=et(te(ct(d,f),"package.json"),!1);for(const z of M)L.add(z)}const F=Ht(d),P=ht(d,I),_=pt(p?.security?.socket),W=p?.security?.socket?.acceptedRisks,re=oi(d,g.name),Y={exclude:[],ignore:[],include:[],includeLocked:!1,includePrerelease:!1,security:!0,target:"latest"},O=$?hi(d,g.name):[],V=w?si(L):[],K=w?li(L,re,g,!1):[],v=new Set(V.map(f=>f.packageName)),T=K.filter(f=>!v.has(f.packageName)),C=[...V,...T],H=A?zi():[];c&&($&&c.startSection("dependencies",P.size>0?"checking outdated catalog dependencies":"scanning duplicates"),R&&c.startSection("security",i.length>0?`scanning ${String(i.length)} packages for advisories`:"no installed packages to scan"),w&&c.startSection("optimization","matching e18e + socket overrides"),A&&c.startSection("runtime","running runtime diagnostics")),c&&A&&c.completeSection("runtime",E("runtime",{duplicates:[],optimizations:[],outdated:[],runtime:H}));const de=($||R)&&P.size>0?De(s,"outdated",()=>Gt(P,Y,F,void 0,d,_,W),(f,M)=>{const z=f.outdated.length;return{status:z>0?"warn":"ok",summary:z>0?`${String(z)} outdated · ${G(M)}`:`up to date · ${G(M)}`}}):Promise.resolve({failed:[],ignored:[],outdated:[]}),N=R&&i.length>0?De(s,"vulnerabilities",()=>Yt(i.map(f=>({name:f.name,version:f.version}))),(f,M)=>{let z=0;for(const ne of f.values())z+=ne.length;return{status:z>0?"error":"ok",summary:z>0?`${String(z)} found · ${G(M)}`:`none found · ${G(M)}`}}):Promise.resolve(new Map),Q=R&&_&&i.length>0?De(s,"socket",()=>Kt(i.map(f=>({name:f.name,version:f.version})),_),(f,M)=>{let z=0,ne=0;for(const Je of f.values())z+=Je.alerts.length,Je.score.overall<Le&&(ne+=1);const Ke=z+ne;return{status:Ke>0?"warn":"ok",summary:Ke>0?`${String(z)} alert${z===1?"":"s"}, ${String(ne)} low-score · ${G(M)}`:`clean · ${G(M)}`}}):Promise.resolve(new Map);let ee,ue,he,pe;const ge=de.catch(f=>(ee=f instanceof Error?f.message:String(f),c||u.warn(`Outdated scan failed: ${ee}`),{failed:[],ignored:[],outdated:[]})),Se=N.catch(f=>(ue=f instanceof Error?f.message:String(f),c||u.warn(`Vulnerability scan failed: ${ue}`),new Map)),y=Q.catch(f=>(he=f instanceof Error?f.message:String(f),c||u.warn(`Socket scan failed: ${he}`),new Map)),m=c&&$?ge.then(f=>{if(ee){c.failSection("dependencies",ee);return}c.completeSection("dependencies",E("dependencies",{duplicates:O,optimizations:[],outdated:f.outdated,runtime:[]}))}):void 0,k=c&&R?Promise.all([ge,Se,y]).then(([f])=>{const M=ee??ue??he;if(M){c.failSection("security",M);return}c.completeSection("security",E("security",{duplicates:[],optimizations:[],outdated:f.outdated,runtime:[]}))}):void 0,$e=(async()=>{if(o&&w&&C.length>0&&await De(s,"codemods",async()=>(await ai(C),C),(f,M)=>{const z=f.filter(ne=>ne.hasCodemod||ne.category==="socket").length;return{status:"ok",summary:`${String(z)} auto-fixable · ${G(M)}`}}).catch(f=>{pe=f instanceof Error?f.message:String(f)}),c&&w){if(pe){c.failSection("optimization",pe);return}c.completeSection("optimization",E("optimization",{duplicates:[],optimizations:C,outdated:[],runtime:[]}))}})(),[le,It,Pt]=await Promise.all([ge,Se,y]);await Promise.all([m,k,$e]);let Ge=0,Ye=0;if(R&&_)for(const f of Pt.values())Ge+=f.alerts.length,f.score.overall<Le&&(Ye+=1);let Ee=0;if(R){for(const f of le.outdated)f.vulnerabilities&&f.vulnerabilities.length>0&&(Ee+=f.vulnerabilities.length);for(const f of It.values())Ee+=f.length}return{duplicates:O,installedCount:i.length,optimizations:w?C:[],outdated:$?le.outdated:[],runtime:H,sections:l,socketIssues:{alerts:Ge,lowScore:Ye},supplyChain:Nr(p,{packageManager:I,workspaceRoot:d}),vulnCount:Ee,workspaceCount:X.length}},"streamScans"),zr=S(e=>{switch(e){case"error":return Re(j.failure);case"skip":return b(j.dash);case"warn":return ze(j.warning);default:return we(j.success)}},"sectionIcon"),ce=S((e,t)=>{const i=process.stderr.columns??80,s=Math.max(20,Math.min(i-2,60)),o=j.dash.repeat(2),l=`${zr(t)} ${ae(e)}`,c=l.replaceAll(/\[[0-9;]*m/g,"").length,p=Math.max(0,s-c-o.length-2);return`${o} ${l} ${b(j.dash.repeat(p))}`},"heading"),q=S(e=>` ${we(j.success)} ${e}`,"itemOk"),Z=S(e=>` ${ze(j.warning)} ${e}`,"itemWarn"),He=S(e=>` ${Re(j.failure)} ${e}`,"itemError"),At=S(e=>` ${b(j.dash)} ${b(e)}`,"itemSkip"),J=S((e,t,i)=>{const s=`${ae(String(e))} ${b(t)}`;return i?`${s} ${b(`(${i})`)}`:s},"countLine"),jr=S(e=>{if(e.sections.has("dependencies")){if(u.log(""),u.log(ce("Dependencies",oe(e,"dependencies"))),u.log(q(J(e.installedCount,"packages installed"))),e.outdated.length>0){const t=e.outdated.filter(l=>l.updateType==="major").length,i=e.outdated.filter(l=>l.updateType==="minor").length,s=e.outdated.filter(l=>l.updateType==="patch").length,o=[];t>0&&o.push(`${String(t)} major`),i>0&&o.push(`${String(i)} minor`),s>0&&o.push(`${String(s)} patch`),u.log(Z(J(e.outdated.length,"outdated",o.join(", "))))}else u.log(q("All dependencies up to date"));e.duplicates.length>0?u.log(Z(J(e.duplicates.length,"packages with duplicate versions"))):u.log(q("No duplicate dependencies"))}},"displayDependencies"),Er=S(e=>{e.sections.has("security")&&(u.log(""),u.log(ce("Security",oe(e,"security"))),e.vulnCount>0?u.log(He(J(e.vulnCount,`vulnerabilit${e.vulnCount===1?"y":"ies"} found`))):u.log(q("No known vulnerabilities")),e.socketIssues.alerts>0&&u.log(Z(J(e.socketIssues.alerts,`Socket.dev security alert${e.socketIssues.alerts===1?"":"s"}`))),e.socketIssues.lowScore>0&&u.log(Z(J(e.socketIssues.lowScore,`package${e.socketIssues.lowScore===1?"":"s"} with low security score`))),e.socketIssues.alerts===0&&e.socketIssues.lowScore===0&&e.vulnCount===0&&u.log(q("No security issues detected")))},"displaySecurity"),Br=S(e=>{if(!e.sections.has("optimization"))return;u.log(""),u.log(ce("Optimization",oe(e,"optimization")));const t=Rt(e.optimizations);if(t.total===0){u.log(q("No optimizations available"));return}t.native>0&&u.log(Z(J(t.native,"replaceable with native APIs"))),t.preferred>0&&u.log(Z(J(t.preferred,"with lighter alternatives"))),t.micro>0&&u.log(Z(J(t.micro,"trivial micro-utilities"))),t.socket>0&&u.log(Z(J(t.socket,"@socketregistry overrides available")))},"displayOptimization"),Lr=S(e=>{u.log(""),u.log(ce("Supply Chain",e.supplyChain.status));for(const t of e.supplyChain.findings){const i=t.severity==="ok"?q(t.label):t.severity==="error"?He(t.label):Z(t.label);u.log(i),t.detail&&u.log(` ${b(j.arrow)} ${b(t.detail)}`)}e.supplyChain.status!=="ok"&&u.log(` ${b(j.arrow)} ${b("Configure with security.* in vis.config.ts. See `vis check --security-config` for details.")}`)},"displaySupplyChain"),_r=S(e=>{if(e.sections.has("runtime")){u.log(""),u.log(ce("Runtime",oe(e,"runtime")));for(const t of e.runtime)t.status==="ok"?u.log(q(t.message)):t.status==="skip"?u.log(At(t.message)):u.log(Z(t.message))}},"displayRuntime"),Ur=S((e,t)=>{const i=e.vulnCount,s=e.runtime.filter(l=>l.status==="warn").length,o=e.outdated.length+e.duplicates.length+e.optimizations.length+s;if(t){if(i===0&&o===0)u.success(`Everything looks good! ${b(`(${G(e.elapsedMs)})`)}`);else{const l=[];i>0&&l.push(Re(`${String(i)} security`)),o>0&&l.push(ze(`${String(o)} improvement${o===1?"":"s"}`)),u.log(`${Re(j.failure)} ${l.join(", ")} ${b(`(${G(e.elapsedMs)})`)}`)}return}u.log(""),u.log(ce("Summary","ok")),i===0&&o===0?u.success(`Everything looks good! ${b(`(${G(e.elapsedMs)})`)}`):(i>0&&u.error(`${String(i)} security issue${i===1?"":"s"}`),o>0&&u.log(` ${Te(j.arrow)} ${ae(String(o))} ${b(`improvement${o===1?"":"s"} available`)} ${b(`(${G(e.elapsedMs)})`)}`))},"displaySummary"),Vr=S(e=>{const t=[];if(e.outdated.length>0&&t.push("vis update — update outdated dependencies"),(e.vulnCount>0||e.socketIssues.alerts>0)&&t.push("vis audit — detailed security analysis"),e.optimizations.length>0&&t.push("vis optimize — apply optimizations interactively"),e.duplicates.length>0&&t.push("vis dedupe — reduce duplicate versions"),t.length>0){u.log(""),u.log(ae("Next steps:"));for(const i of t)u.log(` ${b(j.arrow)} ${i}`)}u.log("")},"displayActions"),Hr=S((e,t)=>{t||(jr(e),Er(e),Br(e),_r(e),Lr(e)),Ur(e,t)},"displayResults"),Gr=S((e,t,i,s,o)=>{const l=[],c=e.has("dependencies"),p=e.has("security"),d=e.has("optimization");return(c||p)&&t>0&&l.push({id:"outdated",label:"Outdated catalog dependencies"}),p&&s>0&&l.push({id:"vulnerabilities",label:"Known vulnerabilities (OSV)"}),p&&i&&s>0&&l.push({id:"socket",label:"Socket.dev supply-chain reports"}),d&&o&&l.push({id:"codemods",label:"Codemod availability"}),l},"planScanTasks"),Yr=S(e=>{if(u.log(""),u.log(`${ae(Te("vis doctor"))} ${b("— project health check")}`),u.log(q(`Detected ${e.packageManagerName} v${e.packageManagerVersion}`)),e.workspaceCount!==void 0&&e.workspaceCount>0&&u.log(q(J(e.workspaceCount,`workspace package${e.workspaceCount===1?"":"s"}`))),e.runtimeFindings.length===0)u.log(q(`Node.js ${e.nodeVersion}`));else{for(const t of e.runtimeFindings){const i=t.severity==="error"?Re:ze;u.log(He(`Runtime: ${i(t.message)}`))}u.log(` ${b(j.arrow)} Run ${we("vis toolchain install")} to install pinned versions, or ${we("vis toolchain status")} for the per-tool breakdown.`)}u.log("")},"printBanner"),pn=S(async({logger:e,options:t,visConfig:i,visConfigError:s,workspaceRoot:o})=>{if(!o)throw new Error("Could not determine workspace root.");const l=t.format==="json"||t.json===!0,c=xr(t.only,t.skip),p=!!t.quiet,d=!!t.noProgress,$=Sr(t.filter);if(c.size===0){u.error("No sections selected. Check your --only / --skip values."),process.exitCode=2;return}const R=Date.now(),w=ut(o),A=ui(o),E=!!process.stdout.isTTY,g=!l&&E&&!Jt&&!p&&!d;!l&&!g&&Yr({nodeVersion:process.versions.node,packageManagerName:w.name,packageManagerVersion:w.version,runtimeFindings:A,workspaceCount:void 0});const I=ht(o,at(o).packageManager),B=pi(o,w.name),X=B.length,L=!!pt(i?.security?.socket),F=ft(o);if(!l&&!p&&!g){const N=F.length>0?b(` · ${String(F.length)} workspace package${F.length===1?"":"s"}`):"";u.log(`${b("·")} ${b("Found")} ${ae(String(X))} ${b(`installed package${X===1?"":"s"}`)}${N}`)}const P=s?{hint:s.file?`Continuing with default settings — fix or regenerate ${s.file} (vis init --force).`:"Continuing with default settings.",message:s.message,severity:"error",title:s.file?`Failed to load ${s.file}`:"Failed to load vis.config"}:void 0,_={bun:"bun.lock",npm:"package-lock.json",pnpm:"pnpm-lock.yaml",yarn:"yarn.lock"}[w.name],W=_?te(o,_):void 0,re=ni(o),Y=!t.noCache&&!t.fix?Si({configPath:re,lockfilePath:W,sections:c,socketEnabled:L,workspaceRoot:o}):void 0,O=Y?$i(Y):void 0,V=O!==void 0;let K,v;if(g){const N=O?new it({activeSections:c,findings:Ct(bt(O),$)}):new it({activeSections:c}),Q=Vt(Xt.createElement(kr,{banner:P,fromCache:V,startedAt:R,store:N}),{alternateScreen:!0,exitOnCtrlC:!1,interactive:!0,patchConsole:!0});try{K=O??await lt({filterPatterns:$,installed:B,resolveCodemods:!!t.fix,sections:c,store:N,visConfig:i,workspaceRoot:o})}catch(ee){throw Q.unmount(),ee}await Q.waitUntilExit(),v=N.getSnapshot().pendingAction}else if(O)K=O;else{const N=Gr(c,I.size,L,X,!!t.fix),Q=gi(N,{live:!l&&!p&&!d});try{K=await lt({filterPatterns:$,installed:B,progress:Q,resolveCodemods:!!t.fix,sections:c,visConfig:i,workspaceRoot:o})}finally{Q.stop()}}const T={...K,elapsedMs:Date.now()-R};if(Y&&!V)try{Ci(Y,T)}catch{}const C=$r(T,$);if(l){process.stdout.write(`${JSON.stringify(Rr(C,w.name),void 0,2)}
|
|
4
|
+
`),t.exitCode&&ot(C,!!t.strict)&&(process.exitCode=1);return}V&&!p&&u.log(`${b("·")} Cached results (use --no-cache to refresh)`),$.length>0&&!p&&u.log(`${b("·")} Filter active: ${Te(t.filter??"")}`),Hr(C,p);const H=C.runtime.some(N=>N.id===Oe&&N.status==="warn"),de=C.sections.has("optimization")&&C.optimizations.length>0;t.fix&&(de||H)?await Kr({force:!!t.fixForce,logger:e,pm:w,recoverOrphans:H,results:C,useEditorconfig:i?.editorconfig??!0,workspaceRoot:o}):p||Vr(C),v&&(process.stdout.write(`
|
|
5
|
+
`),process.stdout.write(`${ae("→ ")}${v.description}
|
|
6
|
+
`),v.configSnippet?(process.stdout.write(`
|
|
7
|
+
`),process.stdout.write(`${b(v.configSnippet)}
|
|
8
|
+
`)):process.stdout.write(` ${Te(v.command)}
|
|
9
|
+
`),process.stdout.write(`
|
|
10
|
+
`)),t.exitCode&&ot(C,!!t.strict)&&(process.exitCode=1)},"execute"),Kr=S(async e=>{const{force:t,logger:i,pm:s,recoverOrphans:o,results:l,useEditorconfig:c,workspaceRoot:p}=e;u.log(""),u.log(ce("Applying fixes","ok"));const d=l.optimizations.filter(g=>g.category==="socket"&&g.overrideSpec).map(g=>({original:g.packageName,spec:g.overrideSpec})),$=l.optimizations.filter(g=>g.category!=="socket"&&g.hasCodemod),R=l.optimizations.filter(g=>g.category!=="socket"&&!g.hasCodemod);let w=!1,A=0;const E=[];if(o){const g=Pi({force:t});if(g.killed.length>0&&(u.success(`Cleaned up ${String(g.killed.length)} orphaned process${g.killed.length===1?"":"es"} (PIDs: ${g.killed.join(", ")}).`),w=!0),g.failed.length>0){const I=t?"":" Re-run with `--fix --fix-force` to escalate to SIGKILL.";u.warn(`Could not signal ${String(g.failed.length)} orphan${g.failed.length===1?"":"s"}: ${g.failed.map(B=>`${String(B.pid)} (${B.reason})`).join(", ")}.${I}`)}}if(d.length>0){const g=ci(p,te(p,"package.json"),d,s,c);g.added.length>0&&(u.success(`Added ${String(g.added.length)} security override${g.added.length===1?"":"s"}.`),w=!0),g.updated.length>0&&(u.success(`Updated ${String(g.updated.length)} override${g.updated.length===1?"":"s"}.`),w=!0)}for(const g of $)try{const I=await di(p,g.packageName);I.filesChanged>0&&(u.success(`${g.packageName}: ${String(I.filesChanged)} file${I.filesChanged===1?"":"s"} updated`),A+=1,w=!0)}catch(I){const B=I instanceof Error?I.message:String(I);E.push({error:B,package:g.packageName}),u.warn(`${g.packageName}: codemod failed — ${B}`)}d.length>0&&(u.log(`${Te(j.arrow)} Running ${s.name} install to update lockfile…`),qt(s,{dev:!1,filter:[],force:!1,frozenLockfile:!1,ignoreScripts:!1,lockfileOnly:!1,noOptional:!1,offline:!1,prod:!1,recursive:!1,silent:!1,workspaceRoot:!1},p,i),w=!0),u.log(""),w?u.success(`Fixes applied. ${A>0?`${String(A)} codemod${A===1?"":"s"} applied.`:""}`.trim()):u.log(At("No auto-fixable items in the current run.")),E.length>0&&u.warn(`${String(E.length)} codemod${E.length===1?"":"s"} failed (run ${we("vis optimize")} for the interactive picker).`),R.length>0&&u.notice(`${String(R.length)} optimization${R.length===1?"":"s"} need manual review (no codemod). Run ${we("vis optimize")} to inspect them.`)},"runFixes");export{pn as default};
|