react-doctor 0.9.3 → 0.9.4-dev.8b97fdc

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.
@@ -1,1733 +0,0 @@
1
-
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="898bdb3e-afed-5fca-92a2-f14d1e8ca6fb")}catch(e){}}();
3
- import { A as toForwardSlashes, B as getCategoryImpact, C as buildRulePriorityMap, D as resolveBlockingLevel, E as formatLearnMoreLine, F as TUI_REPORT_DETAIL_WIDTH_FRACTION, G as runPromise, H as mapWithConcurrency, I as isCiEnvironment, J as succeed, K as sync, L as DIAGNOSTIC_CATEGORY_BUCKETS, M as isUnicodeSupported, N as METRIC, O as isReactDoctorWorkflowInstalled, P as TUI_REPORT_COMPACT_MAX_ROWS, R as Progress, S as hasLintHardFailure, T as formatFixRecipeLine, U as mergeReactDoctorConfigs, V as highlighter, W as resolveScanTarget, _ as canAnimateOnboarding, a as resolveCliInspectOptions, b as buildCodeFrame, c as detectLaunchableAgents, d as launchCliAgent, f as isShareOptedOut, g as doctorFace, h as easeOutCubic, i as selectProjects, j as findNearestPackageDirectory, k as recordCount, l as CLI_AGENT_BINARIES, m as printFooter, n as shouldFailScanGate, o as countUniqueScannedFiles, p as createInvocationInspect, q as void_, r as discoverWorkspacePackages, s as setUpGitHubActions, t as exitGracefully, u as copyToClipboard, v as computeProjectedScore, w as buildSortedRuleGroups, x as buildNoScoreMessage, y as formatElapsedTime, z as Reporter } from "./cli.js";
4
- import path from "node:path";
5
- import { performance } from "node:perf_hooks";
6
- import { getSkillAgentConfig } from "agent-install";
7
- import { Box, Text, render, useApp, useInput, useStdout } from "ink";
8
- import figures from "figures";
9
- import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
10
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
- import Spinner from "ink-spinner";
12
- //#region src/cli/ink/hooks/use-exit-on-ctrl-c.ts
13
- const SHOW_CURSOR = "\x1B[?25h";
14
- /**
15
- * Force-quits the whole CLI on Ctrl-C from any phase. Ink's built-in
16
- * `exitOnCtrlC` only unmounts the render — during a scan the in-flight
17
- * `inspect()` promise keeps the process alive, so Ctrl-C appears to do nothing.
18
- * Mounting this at the app root makes Ctrl-C always terminate: it restores the
19
- * terminal and then uses the CLI's shared SIGINT exit path so the in-flight
20
- * scan can't outlive the keystroke.
21
- */
22
- const useExitOnCtrlC = () => {
23
- const { exit } = useApp();
24
- useInput((input, key) => {
25
- if (key.ctrl && input === "c") {
26
- exit();
27
- process.stdin.setRawMode?.(false);
28
- process.stdout.write(SHOW_CURSOR);
29
- exitGracefully();
30
- }
31
- });
32
- };
33
- //#endregion
34
- //#region src/cli/ink/hooks/use-stdout-dimensions.ts
35
- const readDimensions = (stdout) => ({
36
- columns: stdout?.columns ?? 80,
37
- rows: stdout?.rows ?? 24
38
- });
39
- const useStdoutDimensions = () => {
40
- const { stdout } = useStdout();
41
- const [dimensions, setDimensions] = useState(() => readDimensions(stdout));
42
- useEffect(() => {
43
- if (!stdout) return;
44
- const onResize = () => setDimensions(readDimensions(stdout));
45
- stdout.on("resize", onResize);
46
- return () => {
47
- stdout.off("resize", onResize);
48
- };
49
- }, [stdout]);
50
- return dimensions;
51
- };
52
- //#endregion
53
- //#region src/cli/ink/lib/fuzzy-match.ts
54
- const isWordBoundaryBefore = (target, index) => {
55
- if (index === 0) return true;
56
- const previous = target[index - 1];
57
- return previous === "-" || previous === "_" || previous === "/" || previous === " ";
58
- };
59
- const fuzzyMatch = (query, target) => {
60
- if (query.length === 0) return {
61
- score: 0,
62
- matchedIndices: []
63
- };
64
- const lowerQuery = query.toLowerCase();
65
- const lowerTarget = target.toLowerCase();
66
- const matchedIndices = [];
67
- let score = 0;
68
- let queryIndex = 0;
69
- let previousMatchIndex = Number.NEGATIVE_INFINITY;
70
- for (let targetIndex = 0; targetIndex < lowerTarget.length && queryIndex < lowerQuery.length; targetIndex++) {
71
- if (lowerTarget[targetIndex] !== lowerQuery[queryIndex]) continue;
72
- matchedIndices.push(targetIndex);
73
- if (targetIndex === previousMatchIndex + 1) score += 5;
74
- if (isWordBoundaryBefore(target, targetIndex)) score += 10;
75
- previousMatchIndex = targetIndex;
76
- queryIndex++;
77
- }
78
- if (queryIndex < lowerQuery.length) return null;
79
- score -= matchedIndices[0] * 1;
80
- return {
81
- score,
82
- matchedIndices
83
- };
84
- };
85
- //#endregion
86
- //#region src/cli/ink/components/project-select.tsx
87
- const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
88
- const isPrintable = (input) => input.length > 0 && [...input].every((character) => character.charCodeAt(0) >= 32);
89
- const MatchedName = ({ name, matchedIndices, isSelected }) => {
90
- if (matchedIndices.length === 0) return /* @__PURE__ */ jsx(Text, {
91
- bold: isSelected,
92
- wrap: "truncate-end",
93
- children: name
94
- });
95
- const matched = new Set(matchedIndices);
96
- return /* @__PURE__ */ jsx(Text, {
97
- bold: isSelected,
98
- wrap: "truncate-end",
99
- children: [...name].map((char, index) => matched.has(index) ? /* @__PURE__ */ jsx(Text, {
100
- color: "yellow",
101
- children: char
102
- }, index) : char)
103
- });
104
- };
105
- const ProjectSelect = ({ packages, rootDirectory, onSubmit }) => {
106
- const { rows: terminalRows } = useStdoutDimensions();
107
- useExitOnCtrlC();
108
- const [mode, setMode] = useState("list");
109
- const [query, setQuery] = useState("");
110
- const [selectedIndex, setSelectedIndex] = useState(0);
111
- const [offset, setOffset] = useState(0);
112
- const [checked, setChecked] = useState(() => /* @__PURE__ */ new Set());
113
- const matches = useMemo(() => {
114
- const scored = packages.flatMap((workspacePackage) => {
115
- const result = fuzzyMatch(query, workspacePackage.name);
116
- return result ? [{
117
- workspacePackage,
118
- result
119
- }] : [];
120
- });
121
- if (query.length > 0) scored.sort((a, b) => b.result.score - a.result.score);
122
- return scored.map(({ workspacePackage, result }) => ({
123
- workspacePackage,
124
- matchedIndices: result.matchedIndices
125
- }));
126
- }, [packages, query]);
127
- const isSearching = mode === "search";
128
- const hasFilterLine = isSearching || query.length > 0;
129
- const listHeight = Math.max(1, Math.min(Math.max(matches.length, 1), terminalRows - 3 - (hasFilterLine ? 1 : 0)));
130
- const boundedSelected = matches.length === 0 ? 0 : clamp(selectedIndex, 0, matches.length - 1);
131
- const current = matches[boundedSelected]?.workspacePackage;
132
- const setFilter = (next) => {
133
- setQuery(next);
134
- setSelectedIndex(0);
135
- setOffset(0);
136
- };
137
- const move = (delta) => {
138
- if (matches.length === 0) return;
139
- const next = clamp(boundedSelected + delta, 0, matches.length - 1);
140
- setSelectedIndex(next);
141
- setOffset((current) => {
142
- if (next < current) return next;
143
- if (next >= current + listHeight) return next - listHeight + 1;
144
- return current;
145
- });
146
- };
147
- const toggleChecked = (directory) => {
148
- setChecked((current) => {
149
- const next = new Set(current);
150
- if (next.has(directory)) next.delete(directory);
151
- else next.add(directory);
152
- return next;
153
- });
154
- };
155
- const submit = (directories) => {
156
- onSubmit([...directories]);
157
- };
158
- const scanSelection = () => {
159
- if (checked.size > 0) {
160
- const selectedDirectories = [];
161
- for (const workspacePackage of packages) if (checked.has(workspacePackage.directory)) selectedDirectories.push(workspacePackage.directory);
162
- return submit(selectedDirectories);
163
- }
164
- if (current) submit([current.directory]);
165
- };
166
- useInput((input, key) => {
167
- if (isSearching) {
168
- if (key.return) return setMode("list");
169
- if (key.escape) {
170
- setFilter("");
171
- return setMode("list");
172
- }
173
- if (key.downArrow || key.ctrl && input === "n") return move(1);
174
- if (key.upArrow || key.ctrl && input === "p") return move(-1);
175
- if (key.backspace || key.delete) return setFilter(query.slice(0, -1));
176
- if (isPrintable(input) && !key.ctrl && !key.meta) setFilter(query + input);
177
- return;
178
- }
179
- if (input === "/") return setMode("search");
180
- if (input === " ") {
181
- if (current) toggleChecked(current.directory);
182
- return;
183
- }
184
- if (input === "a") {
185
- if (matches.length === 0) return;
186
- setChecked((current) => {
187
- const next = new Set(current);
188
- const shouldClearMatches = matches.every((match) => current.has(match.workspacePackage.directory));
189
- for (const match of matches) if (shouldClearMatches) next.delete(match.workspacePackage.directory);
190
- else next.add(match.workspacePackage.directory);
191
- return next;
192
- });
193
- return;
194
- }
195
- if (input === "q") return submit([]);
196
- if (key.escape) {
197
- if (query.length > 0) return setFilter("");
198
- if (checked.size > 0) return setChecked(/* @__PURE__ */ new Set());
199
- return submit([]);
200
- }
201
- if (key.return) return scanSelection();
202
- if (key.downArrow || input === "j") return move(1);
203
- if (key.upArrow || input === "k") return move(-1);
204
- if (key.pageDown) return move(listHeight);
205
- if (key.pageUp) return move(-listHeight);
206
- });
207
- const maxOffset = Math.max(0, matches.length - listHeight);
208
- const visibleStart = Math.min(offset, maxOffset);
209
- const visibleMatches = matches.slice(visibleStart, visibleStart + listHeight);
210
- const longestNameLength = Math.max(0, ...packages.map((workspacePackage) => workspacePackage.name.length));
211
- return /* @__PURE__ */ jsxs(Box, {
212
- flexDirection: "column",
213
- children: [
214
- /* @__PURE__ */ jsxs(Text, {
215
- wrap: "truncate-end",
216
- children: [/* @__PURE__ */ jsx(Text, {
217
- bold: true,
218
- children: "Select projects to scan"
219
- }), /* @__PURE__ */ jsxs(Text, {
220
- dimColor: true,
221
- children: [
222
- " ",
223
- checked.size,
224
- "/",
225
- packages.length
226
- ]
227
- })]
228
- }),
229
- isSearching ? /* @__PURE__ */ jsxs(Text, {
230
- wrap: "truncate-end",
231
- children: [
232
- /* @__PURE__ */ jsx(Text, {
233
- color: "cyan",
234
- children: "/ "
235
- }),
236
- query.length > 0 ? /* @__PURE__ */ jsx(Text, { children: query }) : null,
237
- /* @__PURE__ */ jsx(Text, {
238
- inverse: true,
239
- children: " "
240
- })
241
- ]
242
- }) : query.length > 0 ? /* @__PURE__ */ jsx(Text, {
243
- dimColor: true,
244
- wrap: "truncate-end",
245
- children: `filter: ${query}`
246
- }) : null,
247
- /* @__PURE__ */ jsx(Box, {
248
- flexDirection: "column",
249
- height: listHeight,
250
- children: matches.length === 0 ? /* @__PURE__ */ jsx(Text, {
251
- dimColor: true,
252
- children: "No matching projects"
253
- }) : visibleMatches.map((match, index) => {
254
- const isSelected = visibleStart + index === boundedSelected;
255
- const isChecked = checked.has(match.workspacePackage.directory);
256
- return /* @__PURE__ */ jsxs(Text, {
257
- wrap: "truncate-end",
258
- children: [
259
- /* @__PURE__ */ jsx(Text, {
260
- color: isSelected ? "cyan" : void 0,
261
- children: isSelected ? `${figures.pointer} ` : " "
262
- }),
263
- /* @__PURE__ */ jsx(Text, {
264
- color: isChecked ? "green" : void 0,
265
- children: isChecked ? `${figures.radioOn} ` : `${figures.radioOff} `
266
- }),
267
- /* @__PURE__ */ jsx(MatchedName, {
268
- name: match.workspacePackage.name,
269
- matchedIndices: match.matchedIndices,
270
- isSelected
271
- }),
272
- /* @__PURE__ */ jsxs(Text, {
273
- dimColor: true,
274
- children: [" ".repeat(longestNameLength - match.workspacePackage.name.length + 2), path.relative(rootDirectory, match.workspacePackage.directory) || "."]
275
- })
276
- ]
277
- }, match.workspacePackage.directory);
278
- })
279
- }),
280
- /* @__PURE__ */ jsx(Box, {
281
- marginTop: 1,
282
- children: /* @__PURE__ */ jsxs(Text, {
283
- dimColor: true,
284
- wrap: "truncate-end",
285
- children: [
286
- isSearching ? "type to filter · enter confirm · esc clear" : "space select · a all · / search · ",
287
- isSearching ? null : /* @__PURE__ */ jsx(Text, {
288
- color: "cyan",
289
- children: "enter"
290
- }),
291
- isSearching ? null : " to submit · q cancel"
292
- ]
293
- })
294
- })
295
- ]
296
- });
297
- };
298
- //#endregion
299
- //#region src/cli/utils/format-diagnostic-site.ts
300
- const formatDiagnosticSite = (site) => site.line > 0 ? `${site.filePath}:${site.line}` : site.filePath;
301
- //#endregion
302
- //#region src/cli/ink/lib/diagnostic-rows.ts
303
- const pickRepresentative = (diagnostics) => diagnostics.find((diagnostic) => diagnostic.line > 0) ?? diagnostics[0];
304
- const buildDiagnosticRows = (diagnostics, scores) => {
305
- return buildSortedRuleGroups(diagnostics, buildRulePriorityMap(scores)).map(([ruleKey, ruleDiagnostics]) => {
306
- const representative = pickRepresentative(ruleDiagnostics);
307
- return {
308
- ruleKey,
309
- diagnostics: ruleDiagnostics,
310
- severity: representative.severity === "error" ? "error" : "warning",
311
- category: representative.category,
312
- title: representative.title ?? ruleKey,
313
- location: formatDiagnosticSite(representative),
314
- siteCount: ruleDiagnostics.length,
315
- representative,
316
- learnMore: formatLearnMoreLine(representative)
317
- };
318
- });
319
- };
320
- //#endregion
321
- //#region src/cli/ink/hooks/use-scroll-viewport.ts
322
- const useScrollViewport = (options) => {
323
- const { itemCount, height, isActive = true, isSelectable } = options;
324
- const canSelect = (index) => index >= 0 && index < itemCount && (isSelectable ? isSelectable(index) : true);
325
- const seekSelectable = (start, step) => {
326
- for (let index = start; index >= 0 && index < itemCount; index += step) if (canSelect(index)) return index;
327
- return -1;
328
- };
329
- const nearestSelectable = (target, step) => {
330
- const ahead = seekSelectable(target, step);
331
- if (ahead !== -1) return ahead;
332
- const behind = seekSelectable(target, -step);
333
- return behind === -1 ? target : behind;
334
- };
335
- const [selectedIndex, setSelectedIndex] = useState(() => {
336
- const first = seekSelectable(0, 1);
337
- return first === -1 ? 0 : first;
338
- });
339
- const [offset, setOffset] = useState(0);
340
- const awaitingSecondG = useRef(false);
341
- const clampIndex = (index) => Math.max(0, Math.min(itemCount - 1, index));
342
- const moveTo = (rawIndex, step) => {
343
- const next = nearestSelectable(clampIndex(rawIndex), step);
344
- setSelectedIndex(next);
345
- setOffset((current) => {
346
- if (next < current) return next;
347
- if (next >= current + height) return next - height + 1;
348
- return current;
349
- });
350
- };
351
- useInput((input, key) => {
352
- if (itemCount === 0) return;
353
- const isSecondG = awaitingSecondG.current && input === "g";
354
- if (input !== "g") awaitingSecondG.current = false;
355
- if (key.downArrow || input === "j") return moveTo(selectedIndex + 1, 1);
356
- if (key.upArrow || input === "k") return moveTo(selectedIndex - 1, -1);
357
- if (key.pageDown) return moveTo(selectedIndex + height, 1);
358
- if (key.pageUp) return moveTo(selectedIndex - height, -1);
359
- if (key.ctrl && input === "d") return moveTo(selectedIndex + Math.floor(height / 2), 1);
360
- if (key.ctrl && input === "u") return moveTo(selectedIndex - Math.floor(height / 2), -1);
361
- if (input === "G") return moveTo(itemCount - 1, -1);
362
- if (isSecondG) {
363
- awaitingSecondG.current = false;
364
- return moveTo(0, 1);
365
- }
366
- if (input === "g") awaitingSecondG.current = true;
367
- }, { isActive });
368
- const maxOffset = Math.max(0, itemCount - height);
369
- const visibleStart = Math.min(offset, maxOffset);
370
- return {
371
- selectedIndex: canSelect(selectedIndex) ? selectedIndex : nearestSelectable(clampIndex(selectedIndex), 1),
372
- visibleStart,
373
- visibleEnd: Math.min(itemCount, visibleStart + height)
374
- };
375
- };
376
- //#endregion
377
- //#region src/cli/ink/lib/diagnostic-list-entries.ts
378
- const CATEGORY_RANK = new Map(DIAGNOSTIC_CATEGORY_BUCKETS.map((category, index) => [category, index]));
379
- const rankOf = (category) => CATEGORY_RANK.get(category) ?? Number.MAX_SAFE_INTEGER;
380
- const buildDiagnosticListEntries = (rows) => {
381
- const rowsByCategory = /* @__PURE__ */ new Map();
382
- for (const row of rows) {
383
- const categoryRows = rowsByCategory.get(row.category) ?? [];
384
- categoryRows.push(row);
385
- rowsByCategory.set(row.category, categoryRows);
386
- }
387
- const orderedCategories = [...rowsByCategory.keys()].sort((categoryA, categoryB) => {
388
- const rankDelta = rankOf(categoryA) - rankOf(categoryB);
389
- return rankDelta !== 0 ? rankDelta : categoryA.localeCompare(categoryB);
390
- });
391
- const entries = [];
392
- for (const category of orderedCategories) {
393
- const categoryRows = rowsByCategory.get(category) ?? [];
394
- entries.push({
395
- kind: "header",
396
- category
397
- });
398
- for (const row of categoryRows) entries.push({
399
- kind: "item",
400
- row
401
- });
402
- }
403
- return entries;
404
- };
405
- //#endregion
406
- //#region src/cli/ink/lib/build-issue-prompt.ts
407
- const buildIssuePrompt = ({ row, projectName }) => {
408
- const { representative } = row;
409
- const severityLabel = row.severity === "error" ? "ERROR" : "WARN";
410
- const uniqueSites = [...new Set(row.diagnostics.map(formatDiagnosticSite))];
411
- const inlineSites = uniqueSites.slice(0, 8);
412
- const remainingSiteCount = uniqueSites.length - inlineSites.length;
413
- const lines = [
414
- `Fix exactly one React Doctor rule in ${projectName}:`,
415
- "",
416
- `${severityLabel} ${row.category}: ${row.title} (${row.ruleKey}, ×${row.siteCount})`,
417
- representative.message
418
- ];
419
- if (representative.help) lines.push("", `Suggested fix: ${representative.help}`);
420
- const fixRecipeLine = formatFixRecipeLine(representative);
421
- if (fixRecipeLine) lines.push("", fixRecipeLine);
422
- lines.push("", "Scope:", `- Fix only ${row.ruleKey}.`, "- Fix the root cause; do not suppress, disable, or silence the rule.", "- Keep unrelated refactors out of this pass.", "", "Affected sites:", ...inlineSites.map((site) => `- ${site}`));
423
- if (remainingSiteCount > 0) lines.push(`- +${remainingSiteCount} more sites`);
424
- const learnMoreLine = formatLearnMoreLine(representative);
425
- if (learnMoreLine) lines.push("", learnMoreLine);
426
- lines.push("", `Verify with \`npx react-doctor@latest --verbose\` and confirm ${row.ruleKey} is gone before moving on.`);
427
- return lines.join("\n");
428
- };
429
- //#endregion
430
- //#region src/cli/ink/components/diagnostic-action-menu.tsx
431
- const DiagnosticActionMenu = ({ title, itemLabels, focusedIndex, maxWidth }) => {
432
- return /* @__PURE__ */ jsxs(Box, {
433
- width: Math.max(24, Math.min(44, maxWidth)),
434
- borderStyle: "round",
435
- borderColor: "cyan",
436
- paddingX: 2,
437
- paddingY: 1,
438
- flexDirection: "column",
439
- children: [
440
- /* @__PURE__ */ jsxs(Text, {
441
- wrap: "truncate-end",
442
- children: [/* @__PURE__ */ jsx(Text, {
443
- dimColor: true,
444
- children: "Fix "
445
- }), /* @__PURE__ */ jsx(Text, {
446
- bold: true,
447
- children: title
448
- })]
449
- }),
450
- /* @__PURE__ */ jsx(Box, {
451
- marginTop: 1,
452
- flexDirection: "column",
453
- children: itemLabels.map((label, index) => {
454
- const isFocused = index === focusedIndex;
455
- return /* @__PURE__ */ jsxs(Text, {
456
- wrap: "truncate-end",
457
- bold: isFocused,
458
- color: isFocused ? "cyan" : void 0,
459
- dimColor: !isFocused,
460
- children: [isFocused ? "› " : " ", label]
461
- }, label);
462
- })
463
- }),
464
- /* @__PURE__ */ jsx(Box, {
465
- marginTop: 1,
466
- children: /* @__PURE__ */ jsx(Text, {
467
- dimColor: true,
468
- wrap: "truncate-end",
469
- children: "↑/↓ select · enter run · esc close"
470
- })
471
- })
472
- ]
473
- });
474
- };
475
- //#endregion
476
- //#region src/cli/ink/lib/severity-variants.ts
477
- const ICONS = isUnicodeSupported() ? {
478
- error: "✖",
479
- warning: "⚠"
480
- } : {
481
- error: "x",
482
- warning: "!"
483
- };
484
- const severityVariant = (severity) => severity === "error" ? {
485
- color: "red",
486
- icon: ICONS.error,
487
- label: "error"
488
- } : {
489
- color: "yellow",
490
- icon: ICONS.warning,
491
- label: "warning"
492
- };
493
- //#endregion
494
- //#region src/cli/ink/components/diagnostic-detail.tsx
495
- const DiagnosticDetail = ({ row, rootDirectory }) => {
496
- const codeFrame = useMemo(() => {
497
- if (!row) return null;
498
- const { representative } = row;
499
- return buildCodeFrame({
500
- filePath: representative.filePath,
501
- line: representative.line,
502
- column: representative.column,
503
- rootDirectory
504
- });
505
- }, [row, rootDirectory]);
506
- if (!row) return null;
507
- const variant = severityVariant(row.severity);
508
- const { representative } = row;
509
- const impact = getCategoryImpact(row.category);
510
- return /* @__PURE__ */ jsxs(Box, {
511
- flexDirection: "column",
512
- children: [/* @__PURE__ */ jsxs(Text, {
513
- wrap: "truncate-end",
514
- children: [
515
- /* @__PURE__ */ jsxs(Text, {
516
- color: variant.color,
517
- children: [variant.icon, " "]
518
- }),
519
- /* @__PURE__ */ jsx(Text, {
520
- color: variant.color,
521
- bold: true,
522
- children: row.title
523
- }),
524
- row.siteCount > 1 ? /* @__PURE__ */ jsxs(Text, {
525
- dimColor: true,
526
- children: [" ×", row.siteCount]
527
- }) : null
528
- ]
529
- }), /* @__PURE__ */ jsxs(Box, {
530
- flexDirection: "column",
531
- paddingLeft: 2,
532
- children: [
533
- /* @__PURE__ */ jsxs(Text, {
534
- dimColor: true,
535
- wrap: "truncate-end",
536
- children: [
537
- row.category,
538
- " · ",
539
- variant.label,
540
- " · ",
541
- row.location
542
- ]
543
- }),
544
- impact ? /* @__PURE__ */ jsx(Text, {
545
- dimColor: true,
546
- wrap: "wrap",
547
- children: impact
548
- }) : null,
549
- /* @__PURE__ */ jsx(Text, {
550
- wrap: "wrap",
551
- children: representative.message
552
- }),
553
- codeFrame ? /* @__PURE__ */ jsx(Box, {
554
- marginTop: 1,
555
- children: /* @__PURE__ */ jsx(Text, { children: codeFrame })
556
- }) : null,
557
- representative.help ? /* @__PURE__ */ jsx(Box, {
558
- marginTop: 1,
559
- children: /* @__PURE__ */ jsxs(Text, {
560
- wrap: "wrap",
561
- children: [/* @__PURE__ */ jsx(Text, {
562
- color: "cyan",
563
- children: "Fix "
564
- }), representative.help]
565
- })
566
- }) : null,
567
- row.learnMore ? /* @__PURE__ */ jsx(Box, {
568
- marginTop: 1,
569
- children: /* @__PURE__ */ jsx(Text, {
570
- color: "blue",
571
- wrap: "truncate-end",
572
- children: row.learnMore
573
- })
574
- }) : null
575
- ]
576
- })]
577
- });
578
- };
579
- //#endregion
580
- //#region src/cli/ink/components/diagnostic-item.tsx
581
- const DiagnosticItem = ({ row, isSelected, isRead }) => {
582
- const variant = severityVariant(row.severity);
583
- const shouldHighlightSeverity = isSelected || !isRead;
584
- let marker = "• ";
585
- if (isRead) marker = " ";
586
- if (isSelected) marker = "› ";
587
- let markerColor = shouldHighlightSeverity ? variant.color : void 0;
588
- if (isSelected) markerColor = "cyan";
589
- return /* @__PURE__ */ jsxs(Text, {
590
- wrap: "truncate-end",
591
- dimColor: isRead && !isSelected,
592
- children: [
593
- /* @__PURE__ */ jsx(Text, {
594
- color: markerColor,
595
- children: marker
596
- }),
597
- /* @__PURE__ */ jsx(Text, {
598
- color: shouldHighlightSeverity ? variant.color : void 0,
599
- children: `${variant.icon} `
600
- }),
601
- /* @__PURE__ */ jsx(Text, {
602
- color: shouldHighlightSeverity ? variant.color : void 0,
603
- bold: isSelected,
604
- children: row.title
605
- }),
606
- row.siteCount > 1 ? /* @__PURE__ */ jsxs(Text, {
607
- dimColor: true,
608
- children: [" ×", row.siteCount]
609
- }) : null
610
- ]
611
- });
612
- };
613
- //#endregion
614
- //#region src/cli/ink/components/status-bar.tsx
615
- const StatusBar = ({ total, errorCount, warningCount, position, groupCount, unreadCount, projectCount, keyHints = /* @__PURE__ */ jsx(Text, {
616
- dimColor: true,
617
- children: "↑/↓ to move"
618
- }), exitHint = "q to quit", compact = false }) => {
619
- const counts = /* @__PURE__ */ jsxs(Fragment, { children: [
620
- /* @__PURE__ */ jsxs(Text, {
621
- bold: true,
622
- children: [
623
- total,
624
- " ",
625
- total === 1 ? "issue" : "issues"
626
- ]
627
- }),
628
- /* @__PURE__ */ jsx(Text, {
629
- dimColor: true,
630
- children: " "
631
- }),
632
- /* @__PURE__ */ jsxs(Text, {
633
- color: "red",
634
- children: [errorCount, " errors"]
635
- }),
636
- /* @__PURE__ */ jsx(Text, {
637
- dimColor: true,
638
- children: " "
639
- }),
640
- /* @__PURE__ */ jsxs(Text, {
641
- color: "yellow",
642
- children: [warningCount, " warnings"]
643
- })
644
- ] });
645
- if (compact) return /* @__PURE__ */ jsxs(Text, {
646
- wrap: "truncate-end",
647
- children: [
648
- counts,
649
- /* @__PURE__ */ jsxs(Text, {
650
- dimColor: true,
651
- children: [
652
- " · ",
653
- position,
654
- "/",
655
- groupCount,
656
- " · "
657
- ]
658
- }),
659
- keyHints,
660
- /* @__PURE__ */ jsxs(Text, {
661
- dimColor: true,
662
- children: [" · ", exitHint]
663
- })
664
- ]
665
- });
666
- return /* @__PURE__ */ jsxs(Box, {
667
- flexDirection: "column",
668
- children: [/* @__PURE__ */ jsxs(Text, {
669
- wrap: "truncate-end",
670
- children: [
671
- counts,
672
- /* @__PURE__ */ jsxs(Text, {
673
- dimColor: true,
674
- children: [
675
- " · ",
676
- position,
677
- "/",
678
- groupCount
679
- ]
680
- }),
681
- projectCount !== void 0 ? /* @__PURE__ */ jsxs(Text, {
682
- dimColor: true,
683
- children: [
684
- " · ",
685
- projectCount,
686
- " ",
687
- projectCount === 1 ? "project" : "projects"
688
- ]
689
- }) : null
690
- ]
691
- }), /* @__PURE__ */ jsxs(Text, {
692
- wrap: "truncate-end",
693
- children: [
694
- unreadCount !== void 0 ? /* @__PURE__ */ jsxs(Text, {
695
- color: unreadCount > 0 ? "cyan" : void 0,
696
- dimColor: unreadCount === 0,
697
- children: [
698
- unreadCount,
699
- " unread",
700
- " · "
701
- ]
702
- }) : null,
703
- keyHints,
704
- /* @__PURE__ */ jsxs(Text, {
705
- dimColor: true,
706
- children: [" · ", exitHint]
707
- })
708
- ]
709
- })]
710
- });
711
- };
712
- //#endregion
713
- //#region src/cli/ink/components/diagnostic-list.tsx
714
- const ADD_TO_CI_KEY = "a";
715
- const sumSites = (rows) => rows.reduce((total, row) => total + row.siteCount, 0);
716
- const renderEntry = (entry, entryIndex, selectedIndex, readRuleKeys) => {
717
- if (entry.kind === "header") return /* @__PURE__ */ jsx(Text, {
718
- bold: true,
719
- wrap: "truncate-end",
720
- children: entry.category
721
- }, `header:${entry.category}`);
722
- return /* @__PURE__ */ jsx(DiagnosticItem, {
723
- row: entry.row,
724
- isSelected: entryIndex === selectedIndex,
725
- isRead: readRuleKeys.has(entry.row.ruleKey)
726
- }, entry.row.ruleKey);
727
- };
728
- const DiagnosticList = ({ header, rows, width, listColumnWidth, detailColumnWidth, listHeight, detailHeight, layout, rootDirectory, projectName, launchableAgents, onHandoff, canAddToCi, onAddToCi, projectCount, onExit, exitHint }) => {
729
- const entries = useMemo(() => buildDiagnosticListEntries(rows), [rows]);
730
- const [isMenuOpen, setIsMenuOpen] = useState(false);
731
- const [menuIndex, setMenuIndex] = useState(0);
732
- const isSplit = layout === "split";
733
- const isCompact = layout === "compact";
734
- const { rows: terminalRows } = useStdoutDimensions();
735
- const { selectedIndex, visibleStart, visibleEnd } = useScrollViewport({
736
- itemCount: entries.length,
737
- height: listHeight,
738
- isSelectable: (index) => entries[index]?.kind === "item",
739
- isActive: !isMenuOpen
740
- });
741
- const visibleEntries = entries.slice(visibleStart, visibleEnd);
742
- const selectedEntry = entries[selectedIndex];
743
- const selected = selectedEntry?.kind === "item" ? selectedEntry.row : null;
744
- const selectedRuleKey = selected?.ruleKey ?? null;
745
- const initialSelectedRuleKey = useRef(selectedRuleKey);
746
- const didRecordFindingNavigation = useRef(false);
747
- const [readRuleKeys, setReadRuleKeys] = useState(() => /* @__PURE__ */ new Set());
748
- const [copiedRuleKey, setCopiedRuleKey] = useState(null);
749
- const menuLabels = useMemo(() => ["Copy prompt", ...launchableAgents.map((agentId) => getSkillAgentConfig(agentId).displayName)], [launchableAgents]);
750
- useEffect(() => {
751
- if (!selectedRuleKey) return;
752
- setReadRuleKeys((previous) => previous.has(selectedRuleKey) ? previous : new Set(previous).add(selectedRuleKey));
753
- }, [selectedRuleKey]);
754
- useEffect(() => {
755
- if (!selectedRuleKey || selectedRuleKey === initialSelectedRuleKey.current || didRecordFindingNavigation.current) return;
756
- didRecordFindingNavigation.current = true;
757
- recordCount(METRIC.tuiFindingNavigated);
758
- }, [selectedRuleKey]);
759
- const copySelectedPrompt = () => {
760
- if (!selected) return;
761
- const prompt = buildIssuePrompt({
762
- row: selected,
763
- projectName
764
- });
765
- const ruleKey = selected.ruleKey;
766
- copyToClipboard(prompt).then((didCopy) => {
767
- if (didCopy) setCopiedRuleKey(ruleKey);
768
- });
769
- };
770
- const launchSelectedInAgent = (agentId) => {
771
- if (!selected || !onHandoff) return;
772
- onHandoff({
773
- agentId,
774
- prompt: buildIssuePrompt({
775
- row: selected,
776
- projectName
777
- })
778
- });
779
- onExit();
780
- };
781
- const runMenuItem = () => {
782
- if (menuIndex === 0) {
783
- copySelectedPrompt();
784
- setIsMenuOpen(false);
785
- return;
786
- }
787
- const agentId = launchableAgents[menuIndex - 1];
788
- if (agentId) launchSelectedInAgent(agentId);
789
- };
790
- const showCiCallout = Boolean(canAddToCi && onAddToCi);
791
- useInput((input, key) => {
792
- if (input === "q" || key.escape) return onExit();
793
- if (showCiCallout && input === ADD_TO_CI_KEY) {
794
- onAddToCi?.();
795
- return onExit();
796
- }
797
- if (key.return && selected) {
798
- recordCount(METRIC.tuiActionMenuOpened);
799
- setMenuIndex(0);
800
- setIsMenuOpen(true);
801
- }
802
- }, { isActive: !isMenuOpen });
803
- useInput((input, key) => {
804
- if (key.escape) return setIsMenuOpen(false);
805
- if (key.upArrow || input === "k") return setMenuIndex((index) => Math.max(0, index - 1));
806
- if (key.downArrow || input === "j") return setMenuIndex((index) => Math.min(menuLabels.length - 1, index + 1));
807
- if (key.return) return runMenuItem();
808
- }, { isActive: isMenuOpen });
809
- if (isMenuOpen && selected) return /* @__PURE__ */ jsx(Box, {
810
- width,
811
- height: Math.max(6, terminalRows - 2),
812
- justifyContent: "center",
813
- alignItems: "center",
814
- children: /* @__PURE__ */ jsx(DiagnosticActionMenu, {
815
- title: selected.title,
816
- itemLabels: menuLabels,
817
- focusedIndex: menuIndex,
818
- maxWidth: width
819
- })
820
- });
821
- const errorRows = rows.filter((row) => row.severity === "error");
822
- const warningRows = rows.filter((row) => row.severity === "warning");
823
- const itemPosition = entries.slice(0, selectedIndex + 1).filter((entry) => entry.kind === "item").length;
824
- const unreadCount = rows.length - rows.filter((row) => readRuleKeys.has(row.ruleKey)).length;
825
- const listColumn = /* @__PURE__ */ jsx(Box, {
826
- flexDirection: "column",
827
- height: listHeight,
828
- width: isSplit ? listColumnWidth : width,
829
- children: visibleEntries.map((entry, index) => renderEntry(entry, visibleStart + index, selectedIndex, readRuleKeys))
830
- });
831
- const detailContent = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DiagnosticDetail, {
832
- row: selected,
833
- rootDirectory
834
- }), copiedRuleKey === selectedRuleKey ? /* @__PURE__ */ jsx(Box, {
835
- marginTop: 1,
836
- children: /* @__PURE__ */ jsx(Text, {
837
- color: "green",
838
- children: "✓ Copied fix prompt"
839
- })
840
- }) : null] });
841
- const keyHints = /* @__PURE__ */ jsxs(Fragment, { children: [
842
- /* @__PURE__ */ jsx(Text, {
843
- dimColor: true,
844
- children: "↑/↓ move · "
845
- }),
846
- /* @__PURE__ */ jsx(Text, {
847
- color: "cyan",
848
- children: "enter"
849
- }),
850
- /* @__PURE__ */ jsx(Text, {
851
- dimColor: true,
852
- children: " fix this"
853
- }),
854
- showCiCallout ? /* @__PURE__ */ jsxs(Fragment, { children: [
855
- /* @__PURE__ */ jsx(Text, {
856
- dimColor: true,
857
- children: " · "
858
- }),
859
- /* @__PURE__ */ jsx(Text, {
860
- color: "green",
861
- children: ADD_TO_CI_KEY
862
- }),
863
- /* @__PURE__ */ jsx(Text, {
864
- dimColor: true,
865
- children: " add CI"
866
- })
867
- ] }) : null
868
- ] });
869
- const statusBar = /* @__PURE__ */ jsx(Box, {
870
- marginTop: isCompact ? 0 : 1,
871
- children: /* @__PURE__ */ jsx(StatusBar, {
872
- total: sumSites(rows),
873
- errorCount: sumSites(errorRows),
874
- warningCount: sumSites(warningRows),
875
- position: rows.length === 0 ? 0 : itemPosition,
876
- groupCount: rows.length,
877
- unreadCount,
878
- projectCount,
879
- keyHints: isCompact && copiedRuleKey === selectedRuleKey ? /* @__PURE__ */ jsx(Text, {
880
- color: "green",
881
- children: "✓ Copied fix prompt"
882
- }) : keyHints,
883
- exitHint,
884
- compact: isCompact
885
- })
886
- });
887
- if (isSplit) return /* @__PURE__ */ jsxs(Box, {
888
- flexDirection: "column",
889
- width,
890
- children: [/* @__PURE__ */ jsxs(Box, {
891
- flexDirection: "row",
892
- children: [/* @__PURE__ */ jsxs(Box, {
893
- flexDirection: "column",
894
- width: listColumnWidth,
895
- marginRight: 1,
896
- children: [header, /* @__PURE__ */ jsx(Box, {
897
- marginTop: 1,
898
- children: listColumn
899
- })]
900
- }), /* @__PURE__ */ jsx(Box, {
901
- flexDirection: "column",
902
- width: detailColumnWidth,
903
- borderStyle: "single",
904
- borderColor: "gray",
905
- borderTop: false,
906
- borderRight: false,
907
- borderBottom: false,
908
- paddingLeft: 1,
909
- height: detailHeight,
910
- overflowY: "hidden",
911
- children: detailContent
912
- })]
913
- }), statusBar]
914
- });
915
- if (isCompact) return /* @__PURE__ */ jsxs(Box, {
916
- flexDirection: "column",
917
- width,
918
- children: [
919
- header,
920
- listColumn,
921
- statusBar
922
- ]
923
- });
924
- return /* @__PURE__ */ jsxs(Box, {
925
- flexDirection: "column",
926
- width,
927
- children: [
928
- header,
929
- /* @__PURE__ */ jsx(Box, {
930
- marginTop: 1,
931
- children: listColumn
932
- }),
933
- /* @__PURE__ */ jsx(Text, {
934
- dimColor: true,
935
- children: "─".repeat(width)
936
- }),
937
- /* @__PURE__ */ jsx(Box, {
938
- flexDirection: "column",
939
- height: detailHeight,
940
- overflowY: "hidden",
941
- children: detailContent
942
- }),
943
- statusBar
944
- ]
945
- });
946
- };
947
- //#endregion
948
- //#region src/cli/ink/hooks/use-animated-score.ts
949
- const useAnimatedScore = ({ score, projectedScore, animate }) => {
950
- const projectionTarget = projectedScore ?? score;
951
- const hasProjection = projectionTarget > score;
952
- const [displayScore, setDisplayScore] = useState(animate ? 0 : score);
953
- const [displayProjectedScore, setDisplayProjectedScore] = useState(animate ? null : hasProjection ? projectionTarget : null);
954
- useEffect(() => {
955
- if (!animate) {
956
- setDisplayScore(score);
957
- setDisplayProjectedScore(hasProjection ? projectionTarget : null);
958
- return;
959
- }
960
- let timeoutId;
961
- const runProjection = () => {
962
- let frame = 1;
963
- const tick = () => {
964
- const progress = easeOutCubic(frame / 16);
965
- setDisplayProjectedScore(score + (projectionTarget - score) * progress);
966
- if (frame < 16) {
967
- frame += 1;
968
- timeoutId = setTimeout(tick, 35);
969
- } else setDisplayProjectedScore(projectionTarget);
970
- };
971
- tick();
972
- };
973
- let frame = 0;
974
- const tick = () => {
975
- const progress = easeOutCubic(frame / 40);
976
- setDisplayScore(Math.round(score * progress));
977
- if (frame < 40) {
978
- frame += 1;
979
- timeoutId = setTimeout(tick, 50);
980
- return;
981
- }
982
- setDisplayScore(score);
983
- if (hasProjection) runProjection();
984
- };
985
- tick();
986
- return () => clearTimeout(timeoutId);
987
- }, [
988
- animate,
989
- score,
990
- projectionTarget,
991
- hasProjection
992
- ]);
993
- return {
994
- displayScore,
995
- displayProjectedScore
996
- };
997
- };
998
- //#endregion
999
- //#region src/cli/ink/lib/score-color.ts
1000
- const scoreColorName = (score) => {
1001
- if (score >= 75) return "green";
1002
- if (score >= 50) return "yellow";
1003
- return "red";
1004
- };
1005
- //#endregion
1006
- //#region src/cli/ink/components/score-header.tsx
1007
- const BRANDING = "https://react.doctor";
1008
- const ScoreHeader = ({ score, projectedScore, projectName, issueCount, noScoreMessage, width, compact = false }) => {
1009
- const { columns } = useStdoutDimensions();
1010
- const availableWidth = width ?? columns;
1011
- const { stdout } = useStdout();
1012
- const animate = useMemo(() => canAnimateOnboarding(stdout ?? void 0), [stdout]);
1013
- const { displayScore, displayProjectedScore } = useAnimatedScore({
1014
- score: score?.score ?? 0,
1015
- projectedScore,
1016
- animate: animate && score !== null
1017
- });
1018
- if (compact) return score ? /* @__PURE__ */ jsxs(Text, {
1019
- wrap: "truncate-end",
1020
- children: [
1021
- /* @__PURE__ */ jsx(Text, {
1022
- color: scoreColorName(score.score),
1023
- bold: true,
1024
- children: displayScore
1025
- }),
1026
- /* @__PURE__ */ jsxs(Text, {
1027
- dimColor: true,
1028
- children: [
1029
- " / ",
1030
- 100,
1031
- " "
1032
- ]
1033
- }),
1034
- /* @__PURE__ */ jsx(Text, {
1035
- color: scoreColorName(score.score),
1036
- children: score.label
1037
- }),
1038
- /* @__PURE__ */ jsxs(Text, {
1039
- dimColor: true,
1040
- children: [" · ", projectName]
1041
- })
1042
- ]
1043
- }) : /* @__PURE__ */ jsxs(Text, {
1044
- wrap: "truncate-end",
1045
- children: [
1046
- "React Doctor",
1047
- " ",
1048
- /* @__PURE__ */ jsxs(Text, {
1049
- dimColor: true,
1050
- children: ["· ", noScoreMessage ?? `${issueCount} issues · ${projectName}`]
1051
- })
1052
- ]
1053
- });
1054
- if (!score) return /* @__PURE__ */ jsxs(Box, {
1055
- flexDirection: "column",
1056
- paddingLeft: 2,
1057
- children: [/* @__PURE__ */ jsxs(Text, { children: ["React Doctor ", /* @__PURE__ */ jsxs(Text, {
1058
- dimColor: true,
1059
- children: [
1060
- "(",
1061
- BRANDING,
1062
- ")"
1063
- ]
1064
- })] }), /* @__PURE__ */ jsx(Text, {
1065
- dimColor: true,
1066
- children: noScoreMessage ?? `${issueCount} issues · ${projectName}`
1067
- })]
1068
- });
1069
- const color = scoreColorName(score.score);
1070
- const barWidth = Math.max(10, Math.min(50, availableWidth - 11 - 2));
1071
- const filled = Math.round(displayScore / 100 * barWidth);
1072
- const projectedFill = displayProjectedScore != null ? Math.min(barWidth, Math.round(displayProjectedScore / 100 * barWidth)) : filled;
1073
- const gain = Math.max(0, projectedFill - filled);
1074
- const empty = Math.max(0, barWidth - filled - gain);
1075
- const [eyes, mouth] = doctorFace(score.score);
1076
- return /* @__PURE__ */ jsxs(Box, {
1077
- flexDirection: "column",
1078
- children: [/* @__PURE__ */ jsxs(Box, {
1079
- paddingLeft: 2,
1080
- children: [/* @__PURE__ */ jsxs(Box, {
1081
- flexDirection: "column",
1082
- marginRight: 2,
1083
- children: [
1084
- /* @__PURE__ */ jsx(Text, {
1085
- color,
1086
- children: "┌─────┐"
1087
- }),
1088
- /* @__PURE__ */ jsxs(Text, {
1089
- color,
1090
- children: [
1091
- "│ ",
1092
- eyes,
1093
- " │"
1094
- ]
1095
- }),
1096
- /* @__PURE__ */ jsxs(Text, {
1097
- color,
1098
- children: [
1099
- "│ ",
1100
- mouth,
1101
- " │"
1102
- ]
1103
- }),
1104
- /* @__PURE__ */ jsx(Text, {
1105
- color,
1106
- children: "└─────┘"
1107
- })
1108
- ]
1109
- }), /* @__PURE__ */ jsxs(Box, {
1110
- flexDirection: "column",
1111
- children: [
1112
- /* @__PURE__ */ jsxs(Text, {
1113
- wrap: "truncate-end",
1114
- children: [
1115
- /* @__PURE__ */ jsx(Text, {
1116
- color,
1117
- bold: true,
1118
- children: displayScore
1119
- }),
1120
- /* @__PURE__ */ jsxs(Text, {
1121
- dimColor: true,
1122
- children: [
1123
- " / ",
1124
- 100,
1125
- " "
1126
- ]
1127
- }),
1128
- /* @__PURE__ */ jsx(Text, {
1129
- color,
1130
- children: score.label
1131
- }),
1132
- /* @__PURE__ */ jsxs(Text, {
1133
- dimColor: true,
1134
- children: [" · ", projectName]
1135
- })
1136
- ]
1137
- }),
1138
- /* @__PURE__ */ jsxs(Text, {
1139
- wrap: "truncate-end",
1140
- children: [
1141
- /* @__PURE__ */ jsx(Text, {
1142
- color,
1143
- children: "█".repeat(filled)
1144
- }),
1145
- /* @__PURE__ */ jsx(Text, {
1146
- color,
1147
- dimColor: true,
1148
- children: "▓".repeat(gain)
1149
- }),
1150
- /* @__PURE__ */ jsx(Text, {
1151
- dimColor: true,
1152
- children: "░".repeat(empty)
1153
- })
1154
- ]
1155
- }),
1156
- /* @__PURE__ */ jsxs(Text, { children: ["React Doctor ", /* @__PURE__ */ jsxs(Text, {
1157
- dimColor: true,
1158
- children: [
1159
- "(",
1160
- BRANDING,
1161
- ")"
1162
- ]
1163
- })] }),
1164
- /* @__PURE__ */ jsx(Text, { children: " " })
1165
- ]
1166
- })]
1167
- }), projectedScore != null && projectedScore > score.score ? /* @__PURE__ */ jsxs(Text, { children: [
1168
- /* @__PURE__ */ jsx(Text, {
1169
- dimColor: true,
1170
- children: " You could improve "
1171
- }),
1172
- /* @__PURE__ */ jsxs(Text, {
1173
- color: scoreColorName(projectedScore),
1174
- children: [
1175
- "+",
1176
- projectedScore - score.score,
1177
- "%"
1178
- ]
1179
- }),
1180
- /* @__PURE__ */ jsx(Text, {
1181
- dimColor: true,
1182
- children: ` by fixing the top 3 issues`
1183
- })
1184
- ] }) : null]
1185
- });
1186
- };
1187
- //#endregion
1188
- //#region src/cli/ink/components/report.tsx
1189
- const STACKED_FIXED_ROWS = 11;
1190
- const SPLIT_CHROME_ROWS = 10;
1191
- const EMPTY_LAUNCHABLE_AGENTS = [];
1192
- const Report = ({ report, onExit, launchableAgents = EMPTY_LAUNCHABLE_AGENTS, onHandoff, canAddToCi, onAddToCi, projectCount, priorityScores, exitHint = "q to quit" }) => {
1193
- const { rows: terminalRows, columns } = useStdoutDimensions();
1194
- const diagnosticRows = useMemo(() => buildDiagnosticRows(report.diagnostics, priorityScores ?? [report.score]), [
1195
- report.diagnostics,
1196
- report.score,
1197
- priorityScores
1198
- ]);
1199
- useInput((input, key) => {
1200
- if (input === "q" || key.escape) onExit();
1201
- }, { isActive: diagnosticRows.length === 0 });
1202
- const width = Math.max(24, columns - 2);
1203
- const isWide = columns >= 120 && terminalRows >= 22;
1204
- const isCompact = !isWide && terminalRows <= TUI_REPORT_COMPACT_MAX_ROWS;
1205
- const didRecordCompactReport = useRef(false);
1206
- const didRecordStackedReportCap = useRef(false);
1207
- useEffect(() => {
1208
- if (!isCompact || didRecordCompactReport.current) return;
1209
- didRecordCompactReport.current = true;
1210
- recordCount(METRIC.tuiCompactReportShown);
1211
- }, [isCompact]);
1212
- const detailHeight = isWide ? Math.max(0, terminalRows - 3) : Math.max(0, Math.min(15, terminalRows - STACKED_FIXED_ROWS - 3));
1213
- const availableListHeight = Math.max(3, terminalRows - (isWide ? SPLIT_CHROME_ROWS : STACKED_FIXED_ROWS + detailHeight));
1214
- const isStackedReportCapped = !isCompact && !isWide && availableListHeight > 16;
1215
- useEffect(() => {
1216
- if (!isStackedReportCapped || didRecordStackedReportCap.current) return;
1217
- didRecordStackedReportCap.current = true;
1218
- recordCount(METRIC.tuiStackedReportCapped);
1219
- }, [isStackedReportCapped]);
1220
- let listHeight = availableListHeight;
1221
- if (isCompact) listHeight = Math.max(3, terminalRows - 1 - 1);
1222
- else if (!isWide) listHeight = Math.min(16, availableListHeight);
1223
- const detailColumnWidth = Math.max(20, Math.floor(width * TUI_REPORT_DETAIL_WIDTH_FRACTION));
1224
- const listColumnWidth = Math.max(20, width - detailColumnWidth - 3);
1225
- let layout = "stacked";
1226
- if (isCompact) layout = "compact";
1227
- else if (isWide) layout = "split";
1228
- const scoreHeader = /* @__PURE__ */ jsx(ScoreHeader, {
1229
- score: report.score,
1230
- projectedScore: report.projectedScore,
1231
- projectName: report.projectName,
1232
- issueCount: report.diagnostics.length,
1233
- noScoreMessage: report.noScoreMessage,
1234
- width: isWide ? listColumnWidth : width,
1235
- compact: isCompact
1236
- });
1237
- if (diagnosticRows.length === 0) {
1238
- const lintFailureReason = report.lintFailureReason;
1239
- return /* @__PURE__ */ jsxs(Box, {
1240
- flexDirection: "column",
1241
- children: [
1242
- scoreHeader,
1243
- /* @__PURE__ */ jsx(Box, {
1244
- marginTop: 1,
1245
- children: lintFailureReason ? /* @__PURE__ */ jsxs(Text, {
1246
- color: "yellow",
1247
- children: ["⚠ Lint did not run: ", lintFailureReason]
1248
- }) : /* @__PURE__ */ jsx(Text, {
1249
- color: "green",
1250
- children: "✔ No issues found. Nice work."
1251
- })
1252
- }),
1253
- /* @__PURE__ */ jsx(Text, {
1254
- dimColor: true,
1255
- children: exitHint
1256
- })
1257
- ]
1258
- });
1259
- }
1260
- return /* @__PURE__ */ jsx(DiagnosticList, {
1261
- header: scoreHeader,
1262
- rows: diagnosticRows,
1263
- width,
1264
- listColumnWidth,
1265
- detailColumnWidth,
1266
- listHeight,
1267
- detailHeight,
1268
- layout,
1269
- rootDirectory: report.rootDirectory,
1270
- projectName: report.projectName,
1271
- launchableAgents,
1272
- onHandoff,
1273
- canAddToCi,
1274
- onAddToCi,
1275
- projectCount,
1276
- onExit,
1277
- exitHint
1278
- });
1279
- };
1280
- //#endregion
1281
- //#region src/cli/ink/components/scanning.tsx
1282
- const Scanning = ({ progressText, liveCount, recent }) => {
1283
- return /* @__PURE__ */ jsxs(Box, {
1284
- flexDirection: "column",
1285
- children: [/* @__PURE__ */ jsxs(Text, {
1286
- wrap: "truncate-end",
1287
- children: [
1288
- /* @__PURE__ */ jsx(Text, {
1289
- color: "cyan",
1290
- children: /* @__PURE__ */ jsx(Spinner, { type: "dots" })
1291
- }),
1292
- /* @__PURE__ */ jsxs(Text, { children: [" ", progressText ?? "Scanning…"] }),
1293
- liveCount > 0 ? /* @__PURE__ */ jsxs(Text, {
1294
- dimColor: true,
1295
- children: [
1296
- " · ",
1297
- liveCount,
1298
- " found"
1299
- ]
1300
- }) : null
1301
- ]
1302
- }), recent.map((diagnostic, diagnosticIndex) => {
1303
- const variant = severityVariant(diagnostic.severity === "error" ? "error" : "warning");
1304
- const location = formatDiagnosticSite(diagnostic);
1305
- return /* @__PURE__ */ jsxs(Text, {
1306
- wrap: "truncate-end",
1307
- children: [
1308
- " ",
1309
- /* @__PURE__ */ jsx(Text, {
1310
- color: variant.color,
1311
- children: variant.icon
1312
- }),
1313
- /* @__PURE__ */ jsxs(Text, { children: [" ", diagnostic.title ?? `${diagnostic.plugin}/${diagnostic.rule}`] }),
1314
- /* @__PURE__ */ jsxs(Text, {
1315
- dimColor: true,
1316
- children: [" ", location]
1317
- })
1318
- ]
1319
- }, `${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.plugin}:${diagnostic.rule}:${diagnosticIndex}`);
1320
- })]
1321
- });
1322
- };
1323
- //#endregion
1324
- //#region src/cli/ink/components/summary.tsx
1325
- const Summary = ({ summary, onExit, launchableAgents, onHandoff, canAddToCi, onAddToCi }) => {
1326
- return /* @__PURE__ */ jsx(Report, {
1327
- report: {
1328
- diagnostics: summary.combinedDiagnostics,
1329
- score: summary.aggregateScore,
1330
- projectedScore: summary.projectedScore,
1331
- projectName: summary.projectName,
1332
- rootDirectory: summary.rootDirectory,
1333
- scannedFileCount: summary.scannedFileCount,
1334
- elapsedMilliseconds: summary.elapsedMilliseconds,
1335
- isOffline: summary.isOffline,
1336
- noScoreMessage: summary.noScoreMessage,
1337
- ...summary.lintFailureReason ? { lintFailureReason: summary.lintFailureReason } : {}
1338
- },
1339
- onExit,
1340
- launchableAgents,
1341
- onHandoff,
1342
- canAddToCi,
1343
- onAddToCi,
1344
- projectCount: summary.projects.length,
1345
- priorityScores: summary.projects.map((project) => project.score)
1346
- });
1347
- };
1348
- //#endregion
1349
- //#region src/cli/ink/hooks/use-scan-store.ts
1350
- const useScanStore = (store) => useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
1351
- //#endregion
1352
- //#region src/cli/ink/scan-app.tsx
1353
- const ScanApp = ({ store, launchableAgents, onHandoff, canAddToCi, onAddToCi }) => {
1354
- const snapshot = useScanStore(store);
1355
- const { exit } = useApp();
1356
- useExitOnCtrlC();
1357
- if (snapshot.phase === "summary" && snapshot.summary) return /* @__PURE__ */ jsx(Summary, {
1358
- summary: snapshot.summary,
1359
- launchableAgents,
1360
- onHandoff,
1361
- canAddToCi,
1362
- onAddToCi,
1363
- onExit: () => exit()
1364
- });
1365
- if (snapshot.phase === "report" && snapshot.report) return /* @__PURE__ */ jsx(Report, {
1366
- report: snapshot.report,
1367
- launchableAgents,
1368
- onHandoff,
1369
- canAddToCi,
1370
- onAddToCi,
1371
- onExit: () => exit()
1372
- });
1373
- return /* @__PURE__ */ jsx(Scanning, {
1374
- progressText: snapshot.progress,
1375
- liveCount: snapshot.liveCount,
1376
- recent: snapshot.liveDiagnostics.slice(-5)
1377
- });
1378
- };
1379
- //#endregion
1380
- //#region src/cli/ink/scan-bridge-layers.ts
1381
- const reporterLayerForStore = (store) => succeed(Reporter, Reporter.of({
1382
- emit: (diagnostic) => sync(() => store.emitDiagnostic(diagnostic)),
1383
- finalize: void_
1384
- }));
1385
- const progressHandleForStore = (store) => (text) => {
1386
- store.setProgress(text);
1387
- return {
1388
- update: (displayText) => sync(() => store.setProgress(displayText)),
1389
- succeed: (displayText) => sync(() => store.setProgress(displayText)),
1390
- fail: (displayText) => sync(() => store.setProgress(displayText)),
1391
- stop: () => sync(() => store.setProgress(null))
1392
- };
1393
- };
1394
- const progressLayerForStore = (store) => Progress.layerOra(progressHandleForStore(store));
1395
- //#endregion
1396
- //#region src/cli/ink/scan-store.ts
1397
- const INITIAL_SNAPSHOT = {
1398
- phase: "scanning",
1399
- liveDiagnostics: [],
1400
- liveCount: 0,
1401
- progress: null,
1402
- report: null,
1403
- summary: null
1404
- };
1405
- const createScanStore = () => {
1406
- let snapshot = INITIAL_SNAPSHOT;
1407
- const listeners = /* @__PURE__ */ new Set();
1408
- const commit = (next) => {
1409
- snapshot = next;
1410
- for (const listener of listeners) listener();
1411
- };
1412
- return {
1413
- subscribe: (listener) => {
1414
- listeners.add(listener);
1415
- return () => {
1416
- listeners.delete(listener);
1417
- };
1418
- },
1419
- getSnapshot: () => snapshot,
1420
- emitDiagnostic: (diagnostic) => commit({
1421
- ...snapshot,
1422
- liveDiagnostics: [...snapshot.liveDiagnostics, diagnostic].slice(-25),
1423
- liveCount: snapshot.liveCount + 1
1424
- }),
1425
- setProgress: (progress) => commit({
1426
- ...snapshot,
1427
- progress
1428
- }),
1429
- setReport: (report) => commit({
1430
- ...snapshot,
1431
- report,
1432
- phase: "report"
1433
- }),
1434
- setSummary: (summary) => commit({
1435
- ...snapshot,
1436
- summary,
1437
- phase: "summary"
1438
- })
1439
- };
1440
- };
1441
- //#endregion
1442
- //#region src/cli/ink/run-scan-app.tsx
1443
- const resolveProjectScan = async (rootScanTarget, projectDirectory) => {
1444
- const projectScanTarget = projectDirectory === rootScanTarget.resolvedDirectory ? rootScanTarget : await resolveScanTarget(projectDirectory, { allowAmbiguous: true });
1445
- const config = projectDirectory === rootScanTarget.resolvedDirectory ? rootScanTarget.userConfig : mergeReactDoctorConfigs(rootScanTarget.userConfig, projectScanTarget.userConfig ?? void 0);
1446
- const configSourceDirectory = projectScanTarget.userConfig?.plugins === void 0 ? rootScanTarget.configSourceDirectory : projectScanTarget.configSourceDirectory;
1447
- return {
1448
- directory: projectScanTarget.resolvedDirectory,
1449
- config,
1450
- configSourceDirectory
1451
- };
1452
- };
1453
- const qualifyDiagnosticPaths = (diagnostics, rootDirectory, projectDirectory) => {
1454
- const prefix = path.relative(rootDirectory, projectDirectory);
1455
- if (prefix === "" || prefix.startsWith("..")) return [...diagnostics];
1456
- return diagnostics.map((diagnostic) => path.isAbsolute(diagnostic.filePath) ? diagnostic : {
1457
- ...diagnostic,
1458
- filePath: toForwardSlashes(path.join(prefix, diagnostic.filePath))
1459
- });
1460
- };
1461
- const resolveScanPresentation = (input, projectScans) => {
1462
- const isScoreDisabled = input.options?.noScore === true || projectScans.some((projectScan) => projectScan.config?.noScore === true);
1463
- return {
1464
- isOffline: isCiEnvironment() || input.share === false || isShareOptedOut(projectScans, input.options?.noScore),
1465
- noScoreMessage: buildNoScoreMessage(isScoreDisabled)
1466
- };
1467
- };
1468
- const resolveTuiInspectOptions = (input, config) => {
1469
- const warnings = resolveCliInspectOptions({
1470
- blocking: input.blocking,
1471
- warnings: input.options?.warnings
1472
- }, config).warnings;
1473
- return warnings === void 0 ? { ...input.options } : {
1474
- ...input.options,
1475
- warnings
1476
- };
1477
- };
1478
- const resolveSelectedDirectories = async (rootDirectory, input) => {
1479
- const packages = discoverWorkspacePackages(rootDirectory);
1480
- if (!(packages.length > 1 && !input.projectFlag && !input.skipPrompts && (input.configProjects ?? []).length === 0 && process.stdin.isTTY === true)) return selectProjects(rootDirectory, input.projectFlag, input.skipPrompts ?? false, input.configProjects);
1481
- return promptProjectSelection(packages, rootDirectory);
1482
- };
1483
- const promptProjectSelection = (packages, rootDirectory) => new Promise((resolve) => {
1484
- const instance = render(/* @__PURE__ */ jsx(ProjectSelect, {
1485
- packages,
1486
- rootDirectory,
1487
- onSubmit: (directories) => {
1488
- instance.clear();
1489
- instance.unmount();
1490
- resolve(directories);
1491
- }
1492
- }), { exitOnCtrlC: false });
1493
- });
1494
- const resolveLintFailureReason = (results) => {
1495
- for (const result of results) {
1496
- if (!hasLintHardFailure(result)) continue;
1497
- return result.skippedCheckReasons?.lint ?? "Lint failed before diagnostics were produced.";
1498
- }
1499
- return null;
1500
- };
1501
- const toScanReport = ({ result, rootDirectory, projectedScore, isOffline, noScoreMessage }) => {
1502
- const lintFailureReason = resolveLintFailureReason([result]);
1503
- return {
1504
- diagnostics: result.diagnostics,
1505
- score: result.score,
1506
- projectedScore,
1507
- projectName: result.project.projectName,
1508
- rootDirectory,
1509
- scannedFileCount: result.scannedFileCount ?? 0,
1510
- elapsedMilliseconds: result.elapsedMilliseconds,
1511
- isOffline,
1512
- noScoreMessage,
1513
- ...lintFailureReason ? { lintFailureReason } : {}
1514
- };
1515
- };
1516
- const findLowestScored = (reports) => {
1517
- let worst = null;
1518
- for (const report of reports) {
1519
- if (report.score === null) continue;
1520
- if (worst === null || report.score.score < worst.score.score) worst = {
1521
- score: report.score,
1522
- diagnostics: report.diagnostics
1523
- };
1524
- }
1525
- return worst;
1526
- };
1527
- const printExitFooter = async (input) => {
1528
- const fileLabel = input.scannedFileCount === 1 ? "file" : "files";
1529
- process.stdout.write(`${highlighter.success("✔")} Scanned ${input.scannedFileCount} ${fileLabel} in ${formatElapsedTime(input.elapsedMilliseconds)}\n`);
1530
- if (input.lintFailureReason !== null) process.stdout.write(`${highlighter.warn("⚠")} Lint did not run: ${input.lintFailureReason}\n`);
1531
- await runPromise(printFooter({
1532
- diagnostics: [...input.diagnostics],
1533
- scoreResult: input.scoreResult,
1534
- projectName: input.projectName,
1535
- isOffline: input.isOffline
1536
- }));
1537
- };
1538
- const performTuiHandoff = async (request, rootDirectory) => {
1539
- try {
1540
- await launchCliAgent(request.agentId, request.prompt, rootDirectory);
1541
- } catch {
1542
- process.stdout.write(`${highlighter.warn("⚠")} Couldn't launch ${CLI_AGENT_BINARIES[request.agentId]}. Here's the prompt instead:\n`);
1543
- process.stdout.write(`${highlighter.dim("──── Agent prompt ────")}\n`);
1544
- process.stdout.write(`${request.prompt}\n`);
1545
- process.stdout.write(`${highlighter.dim("──────────────────────")}\n`);
1546
- }
1547
- };
1548
- const isCiUnconfigured = (directory) => !isReactDoctorWorkflowInstalled(findNearestPackageDirectory(directory) ?? directory);
1549
- const performCiSetup = async (rootDirectory) => {
1550
- const didCreateWorkflow = await setUpGitHubActions({ rootDirectory });
1551
- recordCount(METRIC.agentHandoff, 1, {
1552
- outcome: "ci-yes",
1553
- source: "tui",
1554
- created: didCreateWorkflow
1555
- });
1556
- };
1557
- const mountScanApp = async (rootDirectory) => {
1558
- const store = createScanStore();
1559
- const launchableAgents = await detectLaunchableAgents();
1560
- const pending = {
1561
- handoff: null,
1562
- ciSetup: false
1563
- };
1564
- const instance = render(/* @__PURE__ */ jsx(ScanApp, {
1565
- store,
1566
- launchableAgents,
1567
- onHandoff: (request) => {
1568
- pending.handoff = request;
1569
- },
1570
- canAddToCi: isCiUnconfigured(rootDirectory),
1571
- onAddToCi: () => {
1572
- pending.ciSetup = true;
1573
- }
1574
- }), { exitOnCtrlC: false });
1575
- const settle = async () => {
1576
- if (pending.ciSetup) await performCiSetup(rootDirectory);
1577
- if (pending.handoff) await performTuiHandoff(pending.handoff, rootDirectory);
1578
- };
1579
- return {
1580
- store,
1581
- instance,
1582
- settle
1583
- };
1584
- };
1585
- const runMountedScan = async (rootDirectory, presentation, blockingLevel, executeScan) => {
1586
- const { store, instance, settle } = await mountScanApp(rootDirectory);
1587
- const context = {
1588
- store,
1589
- ...presentation
1590
- };
1591
- try {
1592
- const completedScan = await executeScan(context);
1593
- await instance.waitUntilExit();
1594
- await printExitFooter({
1595
- diagnostics: completedScan.diagnostics,
1596
- scoreResult: completedScan.scoreResult,
1597
- projectName: completedScan.projectName,
1598
- scannedFileCount: completedScan.scannedFileCount,
1599
- elapsedMilliseconds: completedScan.elapsedMilliseconds,
1600
- isOffline: context.isOffline,
1601
- lintFailureReason: resolveLintFailureReason(completedScan.scans.map(({ result }) => result))
1602
- });
1603
- await settle();
1604
- return { shouldFail: shouldFailScanGate({
1605
- scans: completedScan.scans,
1606
- blockingLevel
1607
- }) };
1608
- } catch (error) {
1609
- instance.unmount();
1610
- throw error;
1611
- }
1612
- };
1613
- const runSingleProjectScan = async (rootScanTarget, projectDirectory, input, blockingLevel, inspectProject) => {
1614
- const projectScan = await resolveProjectScan(rootScanTarget, projectDirectory);
1615
- const presentation = resolveScanPresentation(input, [projectScan]);
1616
- return runMountedScan(projectScan.directory, presentation, blockingLevel, async (context) => {
1617
- const result = await inspectProject(projectScan.directory, {
1618
- ...resolveTuiInspectOptions(input, projectScan.config),
1619
- isCi: isCiEnvironment(),
1620
- configOverride: projectScan.config,
1621
- configSourceDirectory: projectScan.configSourceDirectory ?? void 0,
1622
- uiLayers: {
1623
- reporter: reporterLayerForStore(context.store),
1624
- progress: progressLayerForStore(context.store)
1625
- }
1626
- });
1627
- const projectedScore = result.score ? await computeProjectedScore([...result.diagnostics], [...result.diagnostics], result.score) : null;
1628
- context.store.setReport(toScanReport({
1629
- result,
1630
- rootDirectory: projectScan.directory,
1631
- projectedScore,
1632
- isOffline: context.isOffline,
1633
- noScoreMessage: context.noScoreMessage
1634
- }));
1635
- return {
1636
- scans: [{
1637
- result,
1638
- config: projectScan.config
1639
- }],
1640
- diagnostics: result.diagnostics,
1641
- scoreResult: result.score,
1642
- projectName: result.project.projectName,
1643
- scannedFileCount: result.scannedFileCount ?? 0,
1644
- elapsedMilliseconds: result.elapsedMilliseconds
1645
- };
1646
- });
1647
- };
1648
- const runMultiProjectScan = async (rootScanTarget, directories, input, blockingLevel, inspectProject) => {
1649
- const rootDirectory = rootScanTarget.resolvedDirectory;
1650
- const projectScans = await mapWithConcurrency([...directories], 4, (projectDirectory) => resolveProjectScan(rootScanTarget, projectDirectory));
1651
- return runMountedScan(rootDirectory, resolveScanPresentation(input, projectScans), blockingLevel, async (context) => {
1652
- const startTime = performance.now();
1653
- let finishedCount = 0;
1654
- context.store.setProgress(`Scanning ${directories.length} projects…`);
1655
- const results = await mapWithConcurrency(projectScans, 4, async (projectScan) => {
1656
- const result = await inspectProject(projectScan.directory, {
1657
- ...resolveTuiInspectOptions(input, projectScan.config),
1658
- isCi: isCiEnvironment(),
1659
- configOverride: projectScan.config,
1660
- configSourceDirectory: projectScan.configSourceDirectory ?? void 0,
1661
- uiLayers: { reporter: Reporter.layerNoop },
1662
- concurrentScan: true
1663
- });
1664
- finishedCount += 1;
1665
- context.store.setProgress(`Scanning ${directories.length} projects… (${finishedCount}/${directories.length})`);
1666
- return {
1667
- directory: projectScan.directory,
1668
- result,
1669
- config: projectScan.config
1670
- };
1671
- });
1672
- const projects = results.map(({ directory, result }) => toScanReport({
1673
- result,
1674
- rootDirectory: directory,
1675
- projectedScore: null,
1676
- isOffline: context.isOffline,
1677
- noScoreMessage: context.noScoreMessage
1678
- }));
1679
- const combinedDiagnostics = projects.flatMap((project) => qualifyDiagnosticPaths(project.diagnostics, rootDirectory, project.rootDirectory));
1680
- const worst = findLowestScored(projects);
1681
- const projectedScore = worst ? await computeProjectedScore(combinedDiagnostics, [...worst.diagnostics], worst.score) : null;
1682
- const scannedFileCount = countUniqueScannedFiles(results.map(({ result }) => result));
1683
- const elapsedMilliseconds = performance.now() - startTime;
1684
- const lintFailureReason = resolveLintFailureReason(results.map(({ result }) => result));
1685
- const summary = {
1686
- projects,
1687
- aggregateScore: worst?.score ?? null,
1688
- projectedScore,
1689
- combinedDiagnostics,
1690
- scannedFileCount,
1691
- elapsedMilliseconds,
1692
- projectName: path.basename(rootDirectory),
1693
- rootDirectory,
1694
- isOffline: context.isOffline,
1695
- noScoreMessage: context.noScoreMessage,
1696
- ...lintFailureReason ? { lintFailureReason } : {}
1697
- };
1698
- context.store.setSummary(summary);
1699
- return {
1700
- scans: results,
1701
- diagnostics: combinedDiagnostics,
1702
- scoreResult: summary.aggregateScore,
1703
- projectName: summary.projectName,
1704
- scannedFileCount,
1705
- elapsedMilliseconds
1706
- };
1707
- });
1708
- };
1709
- const runScanApp = async (input) => {
1710
- const scanTarget = await resolveScanTarget(input.directory, { allowAmbiguous: true });
1711
- const rootDirectory = scanTarget.resolvedDirectory;
1712
- const deadlineEpochMs = input.options?.deadlineEpochMs ?? (input.options?.maxDurationMs != null ? Date.now() + input.options.maxDurationMs : void 0);
1713
- const resolvedInput = {
1714
- ...input,
1715
- options: {
1716
- ...input.options,
1717
- deadlineEpochMs
1718
- },
1719
- configProjects: input.configProjects ?? scanTarget.userConfig?.projects,
1720
- share: input.share ?? scanTarget.userConfig?.share ?? true
1721
- };
1722
- const selectedDirectories = await resolveSelectedDirectories(rootDirectory, resolvedInput);
1723
- const inspectProject = createInvocationInspect(input.options?.concurrency);
1724
- const blockingLevel = resolveBlockingLevel({ blocking: resolvedInput.blocking }, scanTarget.userConfig);
1725
- if (selectedDirectories.length === 0) return { shouldFail: false };
1726
- if (selectedDirectories.length === 1) return runSingleProjectScan(scanTarget, selectedDirectories[0], resolvedInput, blockingLevel, inspectProject);
1727
- return runMultiProjectScan(scanTarget, selectedDirectories, resolvedInput, blockingLevel, inspectProject);
1728
- };
1729
- //#endregion
1730
- export { runScanApp };
1731
-
1732
- //# sourceMappingURL=run-scan-app-DHRvqfkX.js.map
1733
- //# debugId=898bdb3e-afed-5fca-92a2-f14d1e8ca6fb