react-doctor 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/chunk-q7NCDQ7-.js +28 -0
- package/dist/cli.js +7433 -92141
- package/dist/devtools-BQATi33M.js +17003 -0
- package/dist/exit-gracefully-B18ck0kt.js +84140 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +169 -36
- package/dist/lsp.js +211 -33
- package/dist/run-scan-app-CjGdpTbP.js +28311 -0
- package/package.json +11 -10
- package/dist/run-scan-app-QbtG43NL.js +0 -2326
|
@@ -1,2326 +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]="536bbbaf-ba97-5df5-b678-926826008e2e")}catch(e){}}();
|
|
3
|
-
import { $ as TUI_REPORT_DETAIL_WIDTH_FRACTION, A as shouldRecordOnboarding, B as buildSortedRuleGroups, C as createInvocationInspect, D as markOnboardingComplete, E as doctorFace, F as buildEmptyReportMessage, G as recordCount, H as formatLearnMoreLine, I as hasIncompleteScoreAnalysis, J as findNearestPackageDirectory, K as recordDistribution, L as buildNoScoreMessage, M as computeProjectedScore, N as formatElapsedTime, O as canAnimateOnboarding, P as buildCodeFrame, Q as TUI_REPORT_COMPACT_MAX_ROWS, R as hasLintHardFailure, S as deduplicateProjectScans, T as easeOutCubic, U as resolveBlockingLevel, V as formatFixRecipeLine, W as isReactDoctorWorkflowInstalled, X as CI_TRUST_COMPANIES, Y as isUnicodeSupported, Z as METRIC, _ as launchCliAgent, _t as succeed, a as selectProjects, at as buildRuleDocsUrl, b as formatSkippedProjectsMessage, c as resolveWorkspaceDeadCodeOwner, ct as highlighter, d as countUniqueScannedFiles, dt as remainingDeadlineBudgetMs, et as isCiEnvironment, f as setUpGitHubActions, ft as resolveScanTarget, g as copyToClipboard, gt as void_, h as CLI_AGENT_BINARIES, ht as sync, i as discoverWorkspacePackages, it as Reporter, j as filterDiagnosticsByCategories, k as isOnboardingForced, l as resolveCliInspectOptions, lt as isPathInsideDirectory, m as detectLaunchableAgents, mt as runPromise, n as registerActiveTuiRenderer, nt as GITHUB_ACTIONS_SETUP_URL, o as resolveProjectScan, ot as getCategoryImpact, p as openUrl, pt as yieldToEventLoop, q as toForwardSlashes, r as shouldFailScanGate, rt as Progress, s as retryMissingProjectScores, st as hasPublishedFixRecipe, t as exitGracefully, tt as DIAGNOSTIC_CATEGORY_BUCKETS, u as filterScansForSurface, ut as mapWithConcurrency, v as buildHandoffPayload, w as printFooter, x as collectProjectSourceFileCounts, y as isShareOptedOut, z as buildRulePriorityMap } 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 Link from "ink-link";
|
|
12
|
-
import Spinner from "ink-spinner";
|
|
13
|
-
//#region src/cli/utils/pluralize.ts
|
|
14
|
-
const pluralize = (count, singularNoun) => `${count} ${singularNoun}${count === 1 ? "" : "s"}`;
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/cli/utils/select-report-diagnostics.ts
|
|
17
|
-
const selectReportDiagnostics = (input) => {
|
|
18
|
-
const surfaceDiagnostics = filterScansForSurface([input.scan], input.surface ?? "cli");
|
|
19
|
-
return {
|
|
20
|
-
diagnostics: filterDiagnosticsByCategories(surfaceDiagnostics, new Set(input.categoryFilters)),
|
|
21
|
-
demotedDiagnosticCount: input.scan.result.diagnostics.length - surfaceDiagnostics.length
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/cli/utils/clamp-number.ts
|
|
26
|
-
const clampNumber = (value, minimum, maximum) => Math.max(minimum, Math.min(maximum, value));
|
|
27
|
-
//#endregion
|
|
28
|
-
//#region src/cli/utils/is-printable-input.ts
|
|
29
|
-
const isPrintableInput = (input) => input.length > 0 && [...input].every((character) => character.charCodeAt(0) >= 32);
|
|
30
|
-
//#endregion
|
|
31
|
-
//#region src/cli/utils/resolve-visible-start.ts
|
|
32
|
-
const resolveVisibleStart = ({ itemCount, offset, selectedIndex, viewportHeight }) => {
|
|
33
|
-
if (viewportHeight <= 0) return 0;
|
|
34
|
-
const boundedOffset = clampNumber(offset, 0, Math.max(0, itemCount - viewportHeight));
|
|
35
|
-
if (selectedIndex < boundedOffset) return selectedIndex;
|
|
36
|
-
if (selectedIndex >= boundedOffset + viewportHeight) return Math.max(0, selectedIndex - viewportHeight + 1);
|
|
37
|
-
return boundedOffset;
|
|
38
|
-
};
|
|
39
|
-
//#endregion
|
|
40
|
-
//#region src/cli/ink/hooks/use-exit-on-ctrl-c.ts
|
|
41
|
-
const SHOW_CURSOR = "\x1B[?25h";
|
|
42
|
-
const useExitOnCtrlC = () => {
|
|
43
|
-
useInput((input, key) => {
|
|
44
|
-
if (key.ctrl && input === "c") {
|
|
45
|
-
recordCount(METRIC.tuiCancelled, 1, { resourceFooter: true });
|
|
46
|
-
process.stdin.setRawMode?.(false);
|
|
47
|
-
process.stdout.write(SHOW_CURSOR);
|
|
48
|
-
exitGracefully();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
};
|
|
52
|
-
//#endregion
|
|
53
|
-
//#region src/cli/ink/hooks/use-stdout-dimensions.ts
|
|
54
|
-
const readDimensions = (stdout) => ({
|
|
55
|
-
columns: stdout?.columns ?? 80,
|
|
56
|
-
rows: stdout?.rows ?? 24
|
|
57
|
-
});
|
|
58
|
-
const useStdoutDimensions = () => {
|
|
59
|
-
const { stdout } = useStdout();
|
|
60
|
-
const [dimensions, setDimensions] = useState(() => readDimensions(stdout));
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
if (!stdout) return;
|
|
63
|
-
const onResize = () => setDimensions(readDimensions(stdout));
|
|
64
|
-
stdout.on("resize", onResize);
|
|
65
|
-
return () => {
|
|
66
|
-
stdout.off("resize", onResize);
|
|
67
|
-
};
|
|
68
|
-
}, [stdout]);
|
|
69
|
-
return dimensions;
|
|
70
|
-
};
|
|
71
|
-
//#endregion
|
|
72
|
-
//#region src/cli/ink/lib/fuzzy-match.ts
|
|
73
|
-
const fuzzyMatch = (query, target) => {
|
|
74
|
-
if (query.length === 0) return {
|
|
75
|
-
score: 0,
|
|
76
|
-
matchedIndices: []
|
|
77
|
-
};
|
|
78
|
-
const lowerQuery = query.toLowerCase();
|
|
79
|
-
const lowerTarget = target.toLowerCase();
|
|
80
|
-
const matchedIndices = [];
|
|
81
|
-
let score = 0;
|
|
82
|
-
let queryIndex = 0;
|
|
83
|
-
let previousMatchIndex = Number.NEGATIVE_INFINITY;
|
|
84
|
-
for (let targetIndex = 0; targetIndex < lowerTarget.length && queryIndex < lowerQuery.length; targetIndex++) {
|
|
85
|
-
if (lowerTarget[targetIndex] !== lowerQuery[queryIndex]) continue;
|
|
86
|
-
matchedIndices.push(targetIndex);
|
|
87
|
-
if (targetIndex === previousMatchIndex + 1) score += 5;
|
|
88
|
-
const previousCharacter = target[targetIndex - 1];
|
|
89
|
-
if (targetIndex === 0 || previousCharacter === "-" || previousCharacter === "_" || previousCharacter === "/" || previousCharacter === " ") score += 10;
|
|
90
|
-
previousMatchIndex = targetIndex;
|
|
91
|
-
queryIndex++;
|
|
92
|
-
}
|
|
93
|
-
if (queryIndex < lowerQuery.length) return null;
|
|
94
|
-
score -= matchedIndices[0] * 1;
|
|
95
|
-
return {
|
|
96
|
-
score,
|
|
97
|
-
matchedIndices
|
|
98
|
-
};
|
|
99
|
-
};
|
|
100
|
-
//#endregion
|
|
101
|
-
//#region src/cli/ink/components/project-select.tsx
|
|
102
|
-
const MatchedName = ({ name, matchedIndices, isSelected }) => {
|
|
103
|
-
if (matchedIndices.length === 0) return /* @__PURE__ */ jsx(Text, {
|
|
104
|
-
bold: isSelected,
|
|
105
|
-
wrap: "truncate-end",
|
|
106
|
-
children: name
|
|
107
|
-
});
|
|
108
|
-
const matchedIndexSet = new Set(matchedIndices);
|
|
109
|
-
return /* @__PURE__ */ jsx(Text, {
|
|
110
|
-
bold: isSelected,
|
|
111
|
-
wrap: "truncate-end",
|
|
112
|
-
children: [...name].map((character, index) => matchedIndexSet.has(index) ? /* @__PURE__ */ jsx(Text, {
|
|
113
|
-
color: "yellow",
|
|
114
|
-
children: character
|
|
115
|
-
}, index) : character)
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
const ProjectSelect = ({ packages, rootDirectory, onSubmit }) => {
|
|
119
|
-
const { rows: terminalRows } = useStdoutDimensions();
|
|
120
|
-
useExitOnCtrlC();
|
|
121
|
-
const [selectionMode, setSelectionMode] = useState("list");
|
|
122
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
123
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
124
|
-
const [scrollOffset, setScrollOffset] = useState(0);
|
|
125
|
-
const [checkedDirectories, setCheckedDirectories] = useState(() => /* @__PURE__ */ new Set());
|
|
126
|
-
const didRecordProjectPathContext = useRef(false);
|
|
127
|
-
const matchedPackages = useMemo(() => {
|
|
128
|
-
const scoredPackages = packages.flatMap((workspacePackage) => {
|
|
129
|
-
const result = fuzzyMatch(searchQuery, workspacePackage.name);
|
|
130
|
-
return result ? [{
|
|
131
|
-
workspacePackage,
|
|
132
|
-
result
|
|
133
|
-
}] : [];
|
|
134
|
-
});
|
|
135
|
-
if (searchQuery.length > 0) scoredPackages.sort((leftPackage, rightPackage) => rightPackage.result.score - leftPackage.result.score);
|
|
136
|
-
return scoredPackages.map(({ workspacePackage, result }) => ({
|
|
137
|
-
workspacePackage,
|
|
138
|
-
matchedIndices: result.matchedIndices,
|
|
139
|
-
relativeDirectory: path.relative(rootDirectory, workspacePackage.directory) || "."
|
|
140
|
-
}));
|
|
141
|
-
}, [
|
|
142
|
-
packages,
|
|
143
|
-
rootDirectory,
|
|
144
|
-
searchQuery
|
|
145
|
-
]);
|
|
146
|
-
const hasProjectPathContext = matchedPackages.some(({ relativeDirectory, workspacePackage }) => relativeDirectory !== workspacePackage.name);
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
if (!hasProjectPathContext || didRecordProjectPathContext.current) return;
|
|
149
|
-
didRecordProjectPathContext.current = true;
|
|
150
|
-
recordCount(METRIC.tuiProjectPathContextShown);
|
|
151
|
-
}, [hasProjectPathContext]);
|
|
152
|
-
const isSearching = selectionMode === "search";
|
|
153
|
-
const hasFilterLine = isSearching || searchQuery.length > 0;
|
|
154
|
-
const listHeight = Math.max(1, Math.min(Math.max(matchedPackages.length, 1), terminalRows - 3 - (hasFilterLine ? 1 : 0)));
|
|
155
|
-
const selectedPackageIndex = matchedPackages.length === 0 ? 0 : clampNumber(selectedIndex, 0, matchedPackages.length - 1);
|
|
156
|
-
const selectedWorkspacePackage = matchedPackages[selectedPackageIndex]?.workspacePackage;
|
|
157
|
-
const updateFilter = (nextSearchQuery) => {
|
|
158
|
-
setSearchQuery(nextSearchQuery);
|
|
159
|
-
setSelectedIndex(0);
|
|
160
|
-
setScrollOffset(0);
|
|
161
|
-
};
|
|
162
|
-
const moveSelection = (indexDelta) => {
|
|
163
|
-
if (matchedPackages.length === 0) return;
|
|
164
|
-
const nextSelectedIndex = clampNumber(selectedPackageIndex + indexDelta, 0, matchedPackages.length - 1);
|
|
165
|
-
setSelectedIndex(nextSelectedIndex);
|
|
166
|
-
setScrollOffset((currentOffset) => {
|
|
167
|
-
if (nextSelectedIndex < currentOffset) return nextSelectedIndex;
|
|
168
|
-
if (nextSelectedIndex >= currentOffset + listHeight) return nextSelectedIndex - listHeight + 1;
|
|
169
|
-
return currentOffset;
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
const toggleCheckedDirectory = (directory) => {
|
|
173
|
-
setCheckedDirectories((currentCheckedDirectories) => {
|
|
174
|
-
const updatedCheckedDirectories = new Set(currentCheckedDirectories);
|
|
175
|
-
if (updatedCheckedDirectories.has(directory)) updatedCheckedDirectories.delete(directory);
|
|
176
|
-
else updatedCheckedDirectories.add(directory);
|
|
177
|
-
return updatedCheckedDirectories;
|
|
178
|
-
});
|
|
179
|
-
};
|
|
180
|
-
const submitDirectories = (directories) => {
|
|
181
|
-
onSubmit([...directories]);
|
|
182
|
-
};
|
|
183
|
-
const submitSelection = () => {
|
|
184
|
-
if (checkedDirectories.size > 0) {
|
|
185
|
-
const selectedDirectories = [];
|
|
186
|
-
for (const workspacePackage of packages) if (checkedDirectories.has(workspacePackage.directory)) selectedDirectories.push(workspacePackage.directory);
|
|
187
|
-
return submitDirectories(selectedDirectories);
|
|
188
|
-
}
|
|
189
|
-
if (selectedWorkspacePackage) submitDirectories([selectedWorkspacePackage.directory]);
|
|
190
|
-
};
|
|
191
|
-
useInput((input, key) => {
|
|
192
|
-
if (isSearching) {
|
|
193
|
-
if (key.return) return setSelectionMode("list");
|
|
194
|
-
if (key.escape) {
|
|
195
|
-
updateFilter("");
|
|
196
|
-
return setSelectionMode("list");
|
|
197
|
-
}
|
|
198
|
-
if (key.downArrow || key.ctrl && input === "n") return moveSelection(1);
|
|
199
|
-
if (key.upArrow || key.ctrl && input === "p") return moveSelection(-1);
|
|
200
|
-
if (key.backspace || key.delete) return updateFilter(searchQuery.slice(0, -1));
|
|
201
|
-
if (isPrintableInput(input) && !key.ctrl && !key.meta) updateFilter(searchQuery + input);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (input === "/") return setSelectionMode("search");
|
|
205
|
-
if (input === " ") {
|
|
206
|
-
if (selectedWorkspacePackage) toggleCheckedDirectory(selectedWorkspacePackage.directory);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
if (input === "a") {
|
|
210
|
-
if (matchedPackages.length === 0) return;
|
|
211
|
-
setCheckedDirectories((currentCheckedDirectories) => {
|
|
212
|
-
const updatedCheckedDirectories = new Set(currentCheckedDirectories);
|
|
213
|
-
const shouldClearMatches = matchedPackages.every((matchedPackage) => currentCheckedDirectories.has(matchedPackage.workspacePackage.directory));
|
|
214
|
-
for (const matchedPackage of matchedPackages) if (shouldClearMatches) updatedCheckedDirectories.delete(matchedPackage.workspacePackage.directory);
|
|
215
|
-
else updatedCheckedDirectories.add(matchedPackage.workspacePackage.directory);
|
|
216
|
-
return updatedCheckedDirectories;
|
|
217
|
-
});
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (input === "q") return submitDirectories([]);
|
|
221
|
-
if (key.escape) {
|
|
222
|
-
if (searchQuery.length > 0) return updateFilter("");
|
|
223
|
-
if (checkedDirectories.size > 0) return setCheckedDirectories(/* @__PURE__ */ new Set());
|
|
224
|
-
return submitDirectories([]);
|
|
225
|
-
}
|
|
226
|
-
if (key.return) return submitSelection();
|
|
227
|
-
if (key.downArrow || input === "j") return moveSelection(1);
|
|
228
|
-
if (key.upArrow || input === "k") return moveSelection(-1);
|
|
229
|
-
if (key.pageDown) return moveSelection(listHeight);
|
|
230
|
-
if (key.pageUp) return moveSelection(-listHeight);
|
|
231
|
-
});
|
|
232
|
-
const visibleStart = resolveVisibleStart({
|
|
233
|
-
itemCount: matchedPackages.length,
|
|
234
|
-
offset: scrollOffset,
|
|
235
|
-
selectedIndex: selectedPackageIndex,
|
|
236
|
-
viewportHeight: listHeight
|
|
237
|
-
});
|
|
238
|
-
const visibleMatches = matchedPackages.slice(visibleStart, visibleStart + listHeight);
|
|
239
|
-
const longestNameLength = Math.max(0, ...packages.map((workspacePackage) => workspacePackage.name.length));
|
|
240
|
-
let filterLine = null;
|
|
241
|
-
if (isSearching) filterLine = /* @__PURE__ */ jsxs(Text, {
|
|
242
|
-
wrap: "truncate-end",
|
|
243
|
-
children: [
|
|
244
|
-
/* @__PURE__ */ jsx(Text, {
|
|
245
|
-
color: "cyan",
|
|
246
|
-
children: "/ "
|
|
247
|
-
}),
|
|
248
|
-
searchQuery.length > 0 ? /* @__PURE__ */ jsx(Text, { children: searchQuery }) : null,
|
|
249
|
-
/* @__PURE__ */ jsx(Text, {
|
|
250
|
-
inverse: true,
|
|
251
|
-
children: " "
|
|
252
|
-
})
|
|
253
|
-
]
|
|
254
|
-
});
|
|
255
|
-
else if (searchQuery.length > 0) filterLine = /* @__PURE__ */ jsx(Text, {
|
|
256
|
-
dimColor: true,
|
|
257
|
-
wrap: "truncate-end",
|
|
258
|
-
children: `filter: ${searchQuery}`
|
|
259
|
-
});
|
|
260
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
261
|
-
flexDirection: "column",
|
|
262
|
-
children: [
|
|
263
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
264
|
-
wrap: "truncate-end",
|
|
265
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
266
|
-
bold: true,
|
|
267
|
-
children: "Select projects to scan"
|
|
268
|
-
}), /* @__PURE__ */ jsxs(Text, {
|
|
269
|
-
dimColor: true,
|
|
270
|
-
children: [
|
|
271
|
-
" ",
|
|
272
|
-
checkedDirectories.size,
|
|
273
|
-
"/",
|
|
274
|
-
packages.length
|
|
275
|
-
]
|
|
276
|
-
})]
|
|
277
|
-
}),
|
|
278
|
-
filterLine,
|
|
279
|
-
/* @__PURE__ */ jsx(Box, {
|
|
280
|
-
flexDirection: "column",
|
|
281
|
-
height: listHeight,
|
|
282
|
-
children: matchedPackages.length === 0 ? /* @__PURE__ */ jsx(Text, {
|
|
283
|
-
dimColor: true,
|
|
284
|
-
children: "No matching projects"
|
|
285
|
-
}) : visibleMatches.map((matchedPackage, visiblePackageIndex) => {
|
|
286
|
-
const isSelected = visibleStart + visiblePackageIndex === selectedPackageIndex;
|
|
287
|
-
const isChecked = checkedDirectories.has(matchedPackage.workspacePackage.directory);
|
|
288
|
-
const shouldShowRelativeDirectory = matchedPackage.relativeDirectory !== matchedPackage.workspacePackage.name;
|
|
289
|
-
return /* @__PURE__ */ jsxs(Text, {
|
|
290
|
-
wrap: "truncate-end",
|
|
291
|
-
children: [
|
|
292
|
-
/* @__PURE__ */ jsx(Text, {
|
|
293
|
-
color: isSelected ? "cyan" : void 0,
|
|
294
|
-
children: isSelected ? `${figures.pointer} ` : " "
|
|
295
|
-
}),
|
|
296
|
-
/* @__PURE__ */ jsx(Text, {
|
|
297
|
-
color: isChecked ? "green" : void 0,
|
|
298
|
-
children: isChecked ? `${figures.radioOn} ` : `${figures.radioOff} `
|
|
299
|
-
}),
|
|
300
|
-
/* @__PURE__ */ jsx(MatchedName, {
|
|
301
|
-
name: matchedPackage.workspacePackage.name,
|
|
302
|
-
matchedIndices: matchedPackage.matchedIndices,
|
|
303
|
-
isSelected
|
|
304
|
-
}),
|
|
305
|
-
shouldShowRelativeDirectory ? /* @__PURE__ */ jsxs(Text, {
|
|
306
|
-
dimColor: true,
|
|
307
|
-
children: [" ".repeat(longestNameLength - matchedPackage.workspacePackage.name.length + 2), matchedPackage.relativeDirectory]
|
|
308
|
-
}) : null
|
|
309
|
-
]
|
|
310
|
-
}, matchedPackage.workspacePackage.directory);
|
|
311
|
-
})
|
|
312
|
-
}),
|
|
313
|
-
/* @__PURE__ */ jsx(Box, {
|
|
314
|
-
marginTop: 1,
|
|
315
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
316
|
-
dimColor: true,
|
|
317
|
-
wrap: "truncate-end",
|
|
318
|
-
children: isSearching ? "type to filter · enter confirm · esc clear" : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
319
|
-
"space select · a all · / search · ",
|
|
320
|
-
/* @__PURE__ */ jsx(Text, {
|
|
321
|
-
color: "cyan",
|
|
322
|
-
children: "enter"
|
|
323
|
-
}),
|
|
324
|
-
" to submit · q cancel"
|
|
325
|
-
] })
|
|
326
|
-
})
|
|
327
|
-
})
|
|
328
|
-
]
|
|
329
|
-
});
|
|
330
|
-
};
|
|
331
|
-
//#endregion
|
|
332
|
-
//#region src/cli/ink/hooks/use-report-reveal.ts
|
|
333
|
-
const useReportReveal = ({ issueCount, onRevealComplete }) => {
|
|
334
|
-
const { stdout } = useStdout();
|
|
335
|
-
const shouldAnimate = issueCount > 0 && canAnimateOnboarding(stdout ?? void 0);
|
|
336
|
-
const streamStepCount = Math.min(12, Math.max(issueCount, 8));
|
|
337
|
-
const [streamStep, setStreamStep] = useState(0);
|
|
338
|
-
const [didRevealActions, setDidRevealActions] = useState(false);
|
|
339
|
-
const didNotifyRevealComplete = useRef(false);
|
|
340
|
-
const isStreaming = shouldAnimate && streamStep < streamStepCount;
|
|
341
|
-
const didReachActions = shouldAnimate ? didRevealActions : stdout?.isTTY === true;
|
|
342
|
-
useEffect(() => {
|
|
343
|
-
if (!shouldAnimate) return;
|
|
344
|
-
let nextStreamStep = 0;
|
|
345
|
-
const intervalId = setInterval(() => {
|
|
346
|
-
nextStreamStep = Math.min(streamStepCount, nextStreamStep + 1);
|
|
347
|
-
setStreamStep(nextStreamStep);
|
|
348
|
-
if (nextStreamStep === streamStepCount) clearInterval(intervalId);
|
|
349
|
-
}, 125);
|
|
350
|
-
return () => clearInterval(intervalId);
|
|
351
|
-
}, [shouldAnimate, streamStepCount]);
|
|
352
|
-
useEffect(() => {
|
|
353
|
-
if (!shouldAnimate || isStreaming) return;
|
|
354
|
-
const timeoutId = setTimeout(() => setDidRevealActions(true), 850);
|
|
355
|
-
return () => clearTimeout(timeoutId);
|
|
356
|
-
}, [isStreaming, shouldAnimate]);
|
|
357
|
-
useEffect(() => {
|
|
358
|
-
if (!didReachActions || didNotifyRevealComplete.current) return;
|
|
359
|
-
didNotifyRevealComplete.current = true;
|
|
360
|
-
onRevealComplete?.();
|
|
361
|
-
}, [didReachActions, onRevealComplete]);
|
|
362
|
-
if (!shouldAnimate) return {
|
|
363
|
-
phase: "actions",
|
|
364
|
-
streamSelectedIndex: 0
|
|
365
|
-
};
|
|
366
|
-
if (isStreaming) return {
|
|
367
|
-
phase: "streaming",
|
|
368
|
-
streamSelectedIndex: streamStep % issueCount
|
|
369
|
-
};
|
|
370
|
-
return {
|
|
371
|
-
phase: didRevealActions ? "actions" : "score",
|
|
372
|
-
streamSelectedIndex: 0
|
|
373
|
-
};
|
|
374
|
-
};
|
|
375
|
-
//#endregion
|
|
376
|
-
//#region src/cli/ink/lib/diagnostic-list-entries.ts
|
|
377
|
-
const CATEGORY_RANK = new Map(DIAGNOSTIC_CATEGORY_BUCKETS.map((category, index) => [category, index]));
|
|
378
|
-
const buildDiagnosticListEntries = (rows) => {
|
|
379
|
-
const rowsByCategory = /* @__PURE__ */ new Map();
|
|
380
|
-
for (const [rowIndex, row] of rows.entries()) {
|
|
381
|
-
const categoryRows = rowsByCategory.get(row.category) ?? [];
|
|
382
|
-
categoryRows.push({
|
|
383
|
-
row,
|
|
384
|
-
rowIndex
|
|
385
|
-
});
|
|
386
|
-
rowsByCategory.set(row.category, categoryRows);
|
|
387
|
-
}
|
|
388
|
-
const orderedCategories = [...rowsByCategory.keys()].sort((categoryA, categoryB) => {
|
|
389
|
-
const rankDelta = (CATEGORY_RANK.get(categoryA) ?? Number.MAX_SAFE_INTEGER) - (CATEGORY_RANK.get(categoryB) ?? Number.MAX_SAFE_INTEGER);
|
|
390
|
-
return rankDelta !== 0 ? rankDelta : categoryA.localeCompare(categoryB);
|
|
391
|
-
});
|
|
392
|
-
const entries = [];
|
|
393
|
-
for (const category of orderedCategories) {
|
|
394
|
-
const categoryRows = rowsByCategory.get(category) ?? [];
|
|
395
|
-
entries.push({
|
|
396
|
-
kind: "header",
|
|
397
|
-
category
|
|
398
|
-
});
|
|
399
|
-
for (const indexedRow of categoryRows) entries.push({
|
|
400
|
-
kind: "item",
|
|
401
|
-
...indexedRow
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
return entries;
|
|
405
|
-
};
|
|
406
|
-
//#endregion
|
|
407
|
-
//#region src/cli/utils/format-diagnostic-site.ts
|
|
408
|
-
const formatDiagnosticSite = (site) => site.line > 0 ? `${site.filePath}:${site.line}` : site.filePath;
|
|
409
|
-
//#endregion
|
|
410
|
-
//#region src/cli/ink/lib/diagnostic-rows.ts
|
|
411
|
-
const buildDiagnosticRows = (diagnostics, scores) => {
|
|
412
|
-
return buildSortedRuleGroups(diagnostics, buildRulePriorityMap(scores)).map(([ruleKey, ruleDiagnostics]) => {
|
|
413
|
-
const representative = ruleDiagnostics.find((diagnostic) => diagnostic.line > 0) ?? ruleDiagnostics[0];
|
|
414
|
-
return {
|
|
415
|
-
ruleKey,
|
|
416
|
-
diagnostics: ruleDiagnostics,
|
|
417
|
-
severity: representative.severity === "error" ? "error" : "warning",
|
|
418
|
-
category: representative.category,
|
|
419
|
-
title: representative.title ?? ruleKey,
|
|
420
|
-
location: formatDiagnosticSite(representative),
|
|
421
|
-
siteCount: ruleDiagnostics.length,
|
|
422
|
-
representative,
|
|
423
|
-
ruleGuideUrl: hasPublishedFixRecipe(representative) ? buildRuleDocsUrl(representative.plugin, representative.rule) : null
|
|
424
|
-
};
|
|
425
|
-
});
|
|
426
|
-
};
|
|
427
|
-
//#endregion
|
|
428
|
-
//#region src/cli/ink/lib/resolve-report-layout.ts
|
|
429
|
-
const STACKED_FIXED_ROWS = 9;
|
|
430
|
-
const resolveReportLayout = ({ columns, diagnosticEntryCount, terminalRows }) => {
|
|
431
|
-
const width = Math.max(1, columns - 2);
|
|
432
|
-
const reportRows = Math.max(0, terminalRows - 1);
|
|
433
|
-
const isWide = columns >= 120 && terminalRows >= 22;
|
|
434
|
-
const isCompact = !isWide && terminalRows <= TUI_REPORT_COMPACT_MAX_ROWS;
|
|
435
|
-
const showsViewerScoreHeader = !isCompact || reportRows >= 8;
|
|
436
|
-
const detailHeight = Math.max(0, isWide ? reportRows - 3 : Math.min(15, reportRows - STACKED_FIXED_ROWS - 3));
|
|
437
|
-
const availableListHeight = isWide ? Math.max(3, detailHeight - 4 - 1) : Math.max(3, reportRows - STACKED_FIXED_ROWS - detailHeight);
|
|
438
|
-
const isStackedReportCapped = !isCompact && !isWide && availableListHeight > 16;
|
|
439
|
-
let listHeight = availableListHeight;
|
|
440
|
-
if (isCompact) {
|
|
441
|
-
const viewerScoreHeaderRows = showsViewerScoreHeader ? 4 : 0;
|
|
442
|
-
listHeight = Math.min(diagnosticEntryCount, Math.max(3, reportRows - 1 - viewerScoreHeaderRows));
|
|
443
|
-
} else if (!isWide) listHeight = Math.min(diagnosticEntryCount, 16, availableListHeight);
|
|
444
|
-
const detailColumnWidth = Math.max(20, Math.floor(width * TUI_REPORT_DETAIL_WIDTH_FRACTION));
|
|
445
|
-
const listColumnWidth = Math.max(20, width - detailColumnWidth - 3);
|
|
446
|
-
let layout = "stacked";
|
|
447
|
-
if (isCompact) layout = "compact";
|
|
448
|
-
else if (isWide) layout = "split";
|
|
449
|
-
return {
|
|
450
|
-
detailColumnWidth,
|
|
451
|
-
detailHeight,
|
|
452
|
-
isStackedReportCapped,
|
|
453
|
-
layout,
|
|
454
|
-
listColumnWidth,
|
|
455
|
-
listHeight,
|
|
456
|
-
showsViewerScoreHeader,
|
|
457
|
-
width
|
|
458
|
-
};
|
|
459
|
-
};
|
|
460
|
-
//#endregion
|
|
461
|
-
//#region src/cli/ink/components/action-menu.tsx
|
|
462
|
-
const ActionMenu = ({ actions, selectedIndex: controlledSelectedIndex, onSelectionChange, onEscape, onQuit }) => {
|
|
463
|
-
const [internalSelectedIndex, setInternalSelectedIndex] = useState(0);
|
|
464
|
-
const selectedIndex = controlledSelectedIndex ?? internalSelectedIndex;
|
|
465
|
-
const changeSelection = onSelectionChange ?? setInternalSelectedIndex;
|
|
466
|
-
useInput((input, key) => {
|
|
467
|
-
if (input === "q") return onQuit();
|
|
468
|
-
if (key.escape) return onEscape();
|
|
469
|
-
if (actions.length === 0) return;
|
|
470
|
-
if (key.upArrow || input === "k") return changeSelection(Math.max(0, selectedIndex - 1));
|
|
471
|
-
if (key.downArrow || input === "j") return changeSelection(Math.min(actions.length - 1, selectedIndex + 1));
|
|
472
|
-
if (key.return) actions[selectedIndex]?.onSelect();
|
|
473
|
-
});
|
|
474
|
-
return /* @__PURE__ */ jsx(Box, {
|
|
475
|
-
flexDirection: "column",
|
|
476
|
-
marginTop: 1,
|
|
477
|
-
children: actions.map((action, index) => {
|
|
478
|
-
const isSelected = index === selectedIndex;
|
|
479
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
480
|
-
flexDirection: "column",
|
|
481
|
-
marginBottom: index < actions.length - 1 ? 1 : void 0,
|
|
482
|
-
children: [/* @__PURE__ */ jsxs(Text, {
|
|
483
|
-
color: isSelected ? "cyan" : void 0,
|
|
484
|
-
bold: isSelected,
|
|
485
|
-
children: [
|
|
486
|
-
isSelected ? figures.pointer : figures.pointerSmall,
|
|
487
|
-
" ",
|
|
488
|
-
action.label
|
|
489
|
-
]
|
|
490
|
-
}), isSelected ? action.description : null]
|
|
491
|
-
}, action.id);
|
|
492
|
-
})
|
|
493
|
-
});
|
|
494
|
-
};
|
|
495
|
-
//#endregion
|
|
496
|
-
//#region src/cli/ink/components/agent-handoff.tsx
|
|
497
|
-
const AgentHandoff = ({ agents, onSelect, onBack, onQuit }) => {
|
|
498
|
-
const actions = agents.map((agentId) => ({
|
|
499
|
-
id: agentId,
|
|
500
|
-
label: getSkillAgentConfig(agentId).displayName,
|
|
501
|
-
onSelect: () => onSelect(agentId)
|
|
502
|
-
}));
|
|
503
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
504
|
-
flexDirection: "column",
|
|
505
|
-
children: [
|
|
506
|
-
/* @__PURE__ */ jsx(Box, {
|
|
507
|
-
paddingLeft: 2,
|
|
508
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
509
|
-
bold: true,
|
|
510
|
-
children: "Choose an agent"
|
|
511
|
-
})
|
|
512
|
-
}),
|
|
513
|
-
/* @__PURE__ */ jsx(ActionMenu, {
|
|
514
|
-
actions,
|
|
515
|
-
onEscape: onBack,
|
|
516
|
-
onQuit
|
|
517
|
-
}),
|
|
518
|
-
/* @__PURE__ */ jsx(Box, {
|
|
519
|
-
marginTop: 1,
|
|
520
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
521
|
-
dimColor: true,
|
|
522
|
-
children: "↑/↓ move · enter select · esc back · q quit"
|
|
523
|
-
})
|
|
524
|
-
})
|
|
525
|
-
]
|
|
526
|
-
});
|
|
527
|
-
};
|
|
528
|
-
//#endregion
|
|
529
|
-
//#region src/cli/ink/components/tui-link.tsx
|
|
530
|
-
const TuiLink = ({ children, url }) => /* @__PURE__ */ jsx(Link, {
|
|
531
|
-
url,
|
|
532
|
-
fallback: false,
|
|
533
|
-
children
|
|
534
|
-
});
|
|
535
|
-
//#endregion
|
|
536
|
-
//#region src/cli/ink/components/ci-justification.tsx
|
|
537
|
-
const CiJustification = () => /* @__PURE__ */ jsxs(Box, {
|
|
538
|
-
flexDirection: "column",
|
|
539
|
-
paddingLeft: 2,
|
|
540
|
-
children: [
|
|
541
|
-
/* @__PURE__ */ jsx(Text, {
|
|
542
|
-
dimColor: true,
|
|
543
|
-
children: "Scan every pull request to prevent new React issues while you fix the backlog."
|
|
544
|
-
}),
|
|
545
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
546
|
-
dimColor: true,
|
|
547
|
-
children: [
|
|
548
|
-
"Used by teams at ",
|
|
549
|
-
CI_TRUST_COMPANIES,
|
|
550
|
-
"."
|
|
551
|
-
]
|
|
552
|
-
}),
|
|
553
|
-
/* @__PURE__ */ jsx(TuiLink, {
|
|
554
|
-
url: GITHUB_ACTIONS_SETUP_URL,
|
|
555
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
556
|
-
color: "cyan",
|
|
557
|
-
children: GITHUB_ACTIONS_SETUP_URL
|
|
558
|
-
})
|
|
559
|
-
})
|
|
560
|
-
]
|
|
561
|
-
});
|
|
562
|
-
//#endregion
|
|
563
|
-
//#region src/cli/ink/components/ci-setup.tsx
|
|
564
|
-
const CiSetup = ({ feedback, onConfirm, onLearnMore, onBack, onQuit }) => {
|
|
565
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
566
|
-
flexDirection: "column",
|
|
567
|
-
children: [
|
|
568
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
569
|
-
bold: true,
|
|
570
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
571
|
-
color: "cyan",
|
|
572
|
-
children: "?"
|
|
573
|
-
}), " Add React Doctor to GitHub Actions?"]
|
|
574
|
-
}),
|
|
575
|
-
/* @__PURE__ */ jsx(CiJustification, {}),
|
|
576
|
-
/* @__PURE__ */ jsx(ActionMenu, {
|
|
577
|
-
actions: [{
|
|
578
|
-
id: "confirm",
|
|
579
|
-
label: "Yes, add the workflow",
|
|
580
|
-
onSelect: onConfirm
|
|
581
|
-
}, {
|
|
582
|
-
id: "learn-more",
|
|
583
|
-
label: "Open the GitHub Actions guide",
|
|
584
|
-
onSelect: onLearnMore
|
|
585
|
-
}],
|
|
586
|
-
onEscape: onBack,
|
|
587
|
-
onQuit
|
|
588
|
-
}),
|
|
589
|
-
feedback ? /* @__PURE__ */ jsx(Text, {
|
|
590
|
-
color: feedback.didSucceed ? "green" : "yellow",
|
|
591
|
-
children: feedback.didSucceed ? feedback.message : /* @__PURE__ */ jsx(TuiLink, {
|
|
592
|
-
url: GITHUB_ACTIONS_SETUP_URL,
|
|
593
|
-
children: feedback.message
|
|
594
|
-
})
|
|
595
|
-
}) : null,
|
|
596
|
-
/* @__PURE__ */ jsx(Box, {
|
|
597
|
-
marginTop: 1,
|
|
598
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
599
|
-
dimColor: true,
|
|
600
|
-
children: "↑/↓ move · enter select · esc back · q quit"
|
|
601
|
-
})
|
|
602
|
-
})
|
|
603
|
-
]
|
|
604
|
-
});
|
|
605
|
-
};
|
|
606
|
-
//#endregion
|
|
607
|
-
//#region src/cli/ink/hooks/use-scroll-viewport.ts
|
|
608
|
-
const useScrollViewport = (options) => {
|
|
609
|
-
const { itemCount, height, initialSelectedIndex = 0, isActive = true, isSelectable, onSelectedIndexChange } = options;
|
|
610
|
-
const canSelect = (index) => index >= 0 && index < itemCount && (isSelectable ? isSelectable(index) : true);
|
|
611
|
-
const seekSelectable = (start, step) => {
|
|
612
|
-
for (let index = start; index >= 0 && index < itemCount; index += step) if (canSelect(index)) return index;
|
|
613
|
-
return -1;
|
|
614
|
-
};
|
|
615
|
-
const nearestSelectable = (target, step) => {
|
|
616
|
-
const ahead = seekSelectable(target, step);
|
|
617
|
-
if (ahead !== -1) return ahead;
|
|
618
|
-
const behind = seekSelectable(target, -step);
|
|
619
|
-
return behind === -1 ? target : behind;
|
|
620
|
-
};
|
|
621
|
-
const [selectedIndex, setSelectedIndex] = useState(() => {
|
|
622
|
-
const first = seekSelectable(clampNumber(initialSelectedIndex, 0, itemCount - 1), 1);
|
|
623
|
-
return first === -1 ? 0 : first;
|
|
624
|
-
});
|
|
625
|
-
const [offset, setOffset] = useState(0);
|
|
626
|
-
const awaitingSecondG = useRef(false);
|
|
627
|
-
const moveTo = (rawIndex, step) => {
|
|
628
|
-
const nextSelectedIndex = nearestSelectable(clampNumber(rawIndex, 0, itemCount - 1), step);
|
|
629
|
-
setSelectedIndex(nextSelectedIndex);
|
|
630
|
-
onSelectedIndexChange?.(nextSelectedIndex);
|
|
631
|
-
setOffset((currentOffset) => {
|
|
632
|
-
if (nextSelectedIndex < currentOffset) return nextSelectedIndex;
|
|
633
|
-
if (nextSelectedIndex >= currentOffset + height) return nextSelectedIndex - height + 1;
|
|
634
|
-
return currentOffset;
|
|
635
|
-
});
|
|
636
|
-
};
|
|
637
|
-
useInput((input, key) => {
|
|
638
|
-
if (itemCount === 0) return;
|
|
639
|
-
const isSecondG = awaitingSecondG.current && input === "g";
|
|
640
|
-
if (input !== "g") awaitingSecondG.current = false;
|
|
641
|
-
if (key.downArrow || input === "j") return moveTo(selectedIndex + 1, 1);
|
|
642
|
-
if (key.upArrow || input === "k") return moveTo(selectedIndex - 1, -1);
|
|
643
|
-
if (key.pageDown) return moveTo(selectedIndex + height, 1);
|
|
644
|
-
if (key.pageUp) return moveTo(selectedIndex - height, -1);
|
|
645
|
-
if (key.ctrl && input === "d") return moveTo(selectedIndex + Math.floor(height / 2), 1);
|
|
646
|
-
if (key.ctrl && input === "u") return moveTo(selectedIndex - Math.floor(height / 2), -1);
|
|
647
|
-
if (input === "G") return moveTo(itemCount - 1, -1);
|
|
648
|
-
if (isSecondG) {
|
|
649
|
-
awaitingSecondG.current = false;
|
|
650
|
-
return moveTo(0, 1);
|
|
651
|
-
}
|
|
652
|
-
if (input === "g") awaitingSecondG.current = true;
|
|
653
|
-
}, { isActive });
|
|
654
|
-
const resolvedSelected = canSelect(selectedIndex) ? selectedIndex : nearestSelectable(clampNumber(selectedIndex, 0, itemCount - 1), 1);
|
|
655
|
-
const visibleStart = resolveVisibleStart({
|
|
656
|
-
itemCount,
|
|
657
|
-
offset,
|
|
658
|
-
selectedIndex: resolvedSelected,
|
|
659
|
-
viewportHeight: height
|
|
660
|
-
});
|
|
661
|
-
return {
|
|
662
|
-
selectedIndex: resolvedSelected,
|
|
663
|
-
visibleStart,
|
|
664
|
-
visibleEnd: Math.min(itemCount, visibleStart + height)
|
|
665
|
-
};
|
|
666
|
-
};
|
|
667
|
-
//#endregion
|
|
668
|
-
//#region src/cli/ink/lib/build-issue-prompt.ts
|
|
669
|
-
const buildIssuePrompt = ({ row, projectName }) => {
|
|
670
|
-
const { representative } = row;
|
|
671
|
-
const severityLabel = row.severity === "error" ? "ERROR" : "WARN";
|
|
672
|
-
const uniqueSites = [...new Set(row.diagnostics.map(formatDiagnosticSite))];
|
|
673
|
-
const inlineSites = uniqueSites.slice(0, 8);
|
|
674
|
-
const remainingSiteCount = uniqueSites.length - inlineSites.length;
|
|
675
|
-
const lines = [
|
|
676
|
-
`Fix exactly one React Doctor rule in ${projectName}:`,
|
|
677
|
-
"",
|
|
678
|
-
`${severityLabel} ${row.category}: ${row.title} (${row.ruleKey}, ×${row.siteCount})`,
|
|
679
|
-
representative.message
|
|
680
|
-
];
|
|
681
|
-
if (representative.help) lines.push("", `Suggested fix: ${representative.help}`);
|
|
682
|
-
const fixRecipeLine = formatFixRecipeLine(representative);
|
|
683
|
-
if (fixRecipeLine) lines.push("", fixRecipeLine);
|
|
684
|
-
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}`));
|
|
685
|
-
if (remainingSiteCount > 0) lines.push(`- +${remainingSiteCount} more sites`);
|
|
686
|
-
const learnMoreLine = formatLearnMoreLine(representative);
|
|
687
|
-
if (learnMoreLine) lines.push("", learnMoreLine);
|
|
688
|
-
lines.push("", `Verify with \`npx react-doctor@latest --verbose\` and confirm ${row.ruleKey} is gone before moving on.`);
|
|
689
|
-
return lines.join("\n");
|
|
690
|
-
};
|
|
691
|
-
//#endregion
|
|
692
|
-
//#region src/cli/ink/lib/severity-variants.ts
|
|
693
|
-
const ICONS = isUnicodeSupported() ? {
|
|
694
|
-
error: "✖",
|
|
695
|
-
warning: "⚠"
|
|
696
|
-
} : {
|
|
697
|
-
error: "x",
|
|
698
|
-
warning: "!"
|
|
699
|
-
};
|
|
700
|
-
const severityVariant = (severity) => severity === "error" ? {
|
|
701
|
-
color: "red",
|
|
702
|
-
icon: ICONS.error,
|
|
703
|
-
label: "error"
|
|
704
|
-
} : {
|
|
705
|
-
color: "yellow",
|
|
706
|
-
icon: ICONS.warning,
|
|
707
|
-
label: "warning"
|
|
708
|
-
};
|
|
709
|
-
//#endregion
|
|
710
|
-
//#region src/cli/ink/components/diagnostic-detail.tsx
|
|
711
|
-
const DiagnosticDetail = ({ row, rootDirectory }) => {
|
|
712
|
-
const codeFrame = useMemo(() => {
|
|
713
|
-
if (!row) return null;
|
|
714
|
-
const { representative } = row;
|
|
715
|
-
return buildCodeFrame({
|
|
716
|
-
filePath: representative.filePath,
|
|
717
|
-
line: representative.line,
|
|
718
|
-
column: representative.column,
|
|
719
|
-
rootDirectory
|
|
720
|
-
});
|
|
721
|
-
}, [row, rootDirectory]);
|
|
722
|
-
if (!row) return null;
|
|
723
|
-
const variant = severityVariant(row.severity);
|
|
724
|
-
const { representative } = row;
|
|
725
|
-
const impact = getCategoryImpact(row.category);
|
|
726
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
727
|
-
flexDirection: "column",
|
|
728
|
-
children: [/* @__PURE__ */ jsxs(Text, {
|
|
729
|
-
wrap: "truncate-end",
|
|
730
|
-
children: [
|
|
731
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
732
|
-
color: variant.color,
|
|
733
|
-
children: [variant.icon, " "]
|
|
734
|
-
}),
|
|
735
|
-
/* @__PURE__ */ jsx(Text, {
|
|
736
|
-
color: variant.color,
|
|
737
|
-
bold: true,
|
|
738
|
-
children: row.title
|
|
739
|
-
}),
|
|
740
|
-
row.siteCount > 1 ? /* @__PURE__ */ jsxs(Text, {
|
|
741
|
-
dimColor: true,
|
|
742
|
-
children: [" ×", row.siteCount]
|
|
743
|
-
}) : null
|
|
744
|
-
]
|
|
745
|
-
}), /* @__PURE__ */ jsxs(Box, {
|
|
746
|
-
flexDirection: "column",
|
|
747
|
-
paddingLeft: 2,
|
|
748
|
-
children: [
|
|
749
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
750
|
-
dimColor: true,
|
|
751
|
-
wrap: "truncate-end",
|
|
752
|
-
children: [
|
|
753
|
-
row.category,
|
|
754
|
-
" · ",
|
|
755
|
-
variant.label,
|
|
756
|
-
" · ",
|
|
757
|
-
row.location,
|
|
758
|
-
representative.fileContext ? ` · ${representative.fileContext} file` : ""
|
|
759
|
-
]
|
|
760
|
-
}),
|
|
761
|
-
impact ? /* @__PURE__ */ jsx(Box, {
|
|
762
|
-
marginTop: 1,
|
|
763
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
764
|
-
wrap: "wrap",
|
|
765
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
766
|
-
color: "cyan",
|
|
767
|
-
children: "Impact "
|
|
768
|
-
}), impact]
|
|
769
|
-
})
|
|
770
|
-
}) : null,
|
|
771
|
-
/* @__PURE__ */ jsx(Box, {
|
|
772
|
-
marginTop: 1,
|
|
773
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
774
|
-
wrap: "wrap",
|
|
775
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
776
|
-
color: "cyan",
|
|
777
|
-
children: "Why "
|
|
778
|
-
}), representative.message]
|
|
779
|
-
})
|
|
780
|
-
}),
|
|
781
|
-
codeFrame ? /* @__PURE__ */ jsx(Box, {
|
|
782
|
-
marginTop: 1,
|
|
783
|
-
children: /* @__PURE__ */ jsx(Text, { children: codeFrame })
|
|
784
|
-
}) : null,
|
|
785
|
-
representative.help ? /* @__PURE__ */ jsx(Box, {
|
|
786
|
-
marginTop: 1,
|
|
787
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
788
|
-
wrap: "wrap",
|
|
789
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
790
|
-
color: "cyan",
|
|
791
|
-
children: "Fix "
|
|
792
|
-
}), representative.help]
|
|
793
|
-
})
|
|
794
|
-
}) : null,
|
|
795
|
-
row.ruleGuideUrl ? /* @__PURE__ */ jsx(Box, {
|
|
796
|
-
marginTop: 1,
|
|
797
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
798
|
-
color: "blue",
|
|
799
|
-
wrap: "truncate-end",
|
|
800
|
-
children: /* @__PURE__ */ jsxs(TuiLink, {
|
|
801
|
-
url: row.ruleGuideUrl,
|
|
802
|
-
children: ["Rule guide: ", row.ruleGuideUrl]
|
|
803
|
-
})
|
|
804
|
-
})
|
|
805
|
-
}) : null
|
|
806
|
-
]
|
|
807
|
-
})]
|
|
808
|
-
});
|
|
809
|
-
};
|
|
810
|
-
//#endregion
|
|
811
|
-
//#region src/cli/ink/components/diagnostic-item.tsx
|
|
812
|
-
const DiagnosticItem = ({ row, isSelected, isRead }) => {
|
|
813
|
-
const variant = severityVariant(row.severity);
|
|
814
|
-
const shouldHighlightSeverity = isSelected || !isRead;
|
|
815
|
-
let marker = "• ";
|
|
816
|
-
if (isRead) marker = " ";
|
|
817
|
-
if (isSelected) marker = "› ";
|
|
818
|
-
let markerColor = shouldHighlightSeverity ? variant.color : void 0;
|
|
819
|
-
if (isSelected) markerColor = "cyan";
|
|
820
|
-
return /* @__PURE__ */ jsxs(Text, {
|
|
821
|
-
wrap: "truncate-end",
|
|
822
|
-
dimColor: isRead && !isSelected,
|
|
823
|
-
children: [
|
|
824
|
-
/* @__PURE__ */ jsx(Text, {
|
|
825
|
-
color: markerColor,
|
|
826
|
-
children: marker
|
|
827
|
-
}),
|
|
828
|
-
/* @__PURE__ */ jsx(Text, {
|
|
829
|
-
color: shouldHighlightSeverity ? variant.color : void 0,
|
|
830
|
-
children: `${variant.icon} `
|
|
831
|
-
}),
|
|
832
|
-
/* @__PURE__ */ jsx(Text, {
|
|
833
|
-
color: shouldHighlightSeverity ? variant.color : void 0,
|
|
834
|
-
bold: isSelected,
|
|
835
|
-
children: row.title
|
|
836
|
-
}),
|
|
837
|
-
row.siteCount > 1 ? /* @__PURE__ */ jsxs(Text, {
|
|
838
|
-
dimColor: true,
|
|
839
|
-
children: [" ×", row.siteCount]
|
|
840
|
-
}) : null
|
|
841
|
-
]
|
|
842
|
-
});
|
|
843
|
-
};
|
|
844
|
-
//#endregion
|
|
845
|
-
//#region src/cli/ink/components/status-bar.tsx
|
|
846
|
-
const StatusBar = ({ total, errorCount, warningCount, position, issueCount, unreadCount, projectCount, keyHints = /* @__PURE__ */ jsx(Text, {
|
|
847
|
-
dimColor: true,
|
|
848
|
-
children: "↑/↓ to move"
|
|
849
|
-
}), exitHint = "q to quit", compact = false }) => {
|
|
850
|
-
const counts = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
851
|
-
/* @__PURE__ */ jsx(Text, {
|
|
852
|
-
bold: true,
|
|
853
|
-
children: pluralize(total, "finding")
|
|
854
|
-
}),
|
|
855
|
-
/* @__PURE__ */ jsx(Text, {
|
|
856
|
-
dimColor: true,
|
|
857
|
-
children: " "
|
|
858
|
-
}),
|
|
859
|
-
/* @__PURE__ */ jsx(Text, {
|
|
860
|
-
color: "red",
|
|
861
|
-
children: pluralize(errorCount, "error")
|
|
862
|
-
}),
|
|
863
|
-
/* @__PURE__ */ jsx(Text, {
|
|
864
|
-
dimColor: true,
|
|
865
|
-
children: " "
|
|
866
|
-
}),
|
|
867
|
-
/* @__PURE__ */ jsx(Text, {
|
|
868
|
-
color: "yellow",
|
|
869
|
-
children: pluralize(warningCount, "warning")
|
|
870
|
-
})
|
|
871
|
-
] });
|
|
872
|
-
if (compact) return /* @__PURE__ */ jsxs(Text, {
|
|
873
|
-
wrap: "truncate-end",
|
|
874
|
-
children: [
|
|
875
|
-
counts,
|
|
876
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
877
|
-
dimColor: true,
|
|
878
|
-
children: [
|
|
879
|
-
" · ",
|
|
880
|
-
"issue ",
|
|
881
|
-
position,
|
|
882
|
-
"/",
|
|
883
|
-
issueCount,
|
|
884
|
-
" · "
|
|
885
|
-
]
|
|
886
|
-
}),
|
|
887
|
-
keyHints,
|
|
888
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
889
|
-
dimColor: true,
|
|
890
|
-
children: [" · ", exitHint]
|
|
891
|
-
})
|
|
892
|
-
]
|
|
893
|
-
});
|
|
894
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
895
|
-
flexDirection: "column",
|
|
896
|
-
children: [/* @__PURE__ */ jsxs(Text, {
|
|
897
|
-
wrap: "truncate-end",
|
|
898
|
-
children: [
|
|
899
|
-
counts,
|
|
900
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
901
|
-
dimColor: true,
|
|
902
|
-
children: [
|
|
903
|
-
" · ",
|
|
904
|
-
"issue ",
|
|
905
|
-
position,
|
|
906
|
-
"/",
|
|
907
|
-
issueCount
|
|
908
|
-
]
|
|
909
|
-
}),
|
|
910
|
-
projectCount !== void 0 ? /* @__PURE__ */ jsxs(Text, {
|
|
911
|
-
dimColor: true,
|
|
912
|
-
children: [" · ", pluralize(projectCount, "project")]
|
|
913
|
-
}) : null
|
|
914
|
-
]
|
|
915
|
-
}), /* @__PURE__ */ jsxs(Text, {
|
|
916
|
-
wrap: "truncate-end",
|
|
917
|
-
children: [
|
|
918
|
-
unreadCount !== void 0 ? /* @__PURE__ */ jsxs(Text, {
|
|
919
|
-
color: unreadCount > 0 ? "cyan" : void 0,
|
|
920
|
-
dimColor: unreadCount === 0,
|
|
921
|
-
children: [
|
|
922
|
-
pluralize(unreadCount, "issue"),
|
|
923
|
-
" unread",
|
|
924
|
-
" · "
|
|
925
|
-
]
|
|
926
|
-
}) : null,
|
|
927
|
-
keyHints,
|
|
928
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
929
|
-
dimColor: true,
|
|
930
|
-
children: [" · ", exitHint]
|
|
931
|
-
})
|
|
932
|
-
]
|
|
933
|
-
})]
|
|
934
|
-
});
|
|
935
|
-
};
|
|
936
|
-
//#endregion
|
|
937
|
-
//#region src/cli/ink/components/diagnostic-list.tsx
|
|
938
|
-
const DIAGNOSTIC_KEY_HINTS = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
939
|
-
/* @__PURE__ */ jsx(Text, {
|
|
940
|
-
dimColor: true,
|
|
941
|
-
children: "↑/↓ move · "
|
|
942
|
-
}),
|
|
943
|
-
/* @__PURE__ */ jsx(Text, {
|
|
944
|
-
color: "cyan",
|
|
945
|
-
children: "enter"
|
|
946
|
-
}),
|
|
947
|
-
/* @__PURE__ */ jsx(Text, {
|
|
948
|
-
dimColor: true,
|
|
949
|
-
children: " copy context"
|
|
950
|
-
})
|
|
951
|
-
] });
|
|
952
|
-
const DiagnosticList = ({ header, rows, entries, width, listColumnWidth, detailColumnWidth, listHeight, detailHeight, layout, rootDirectory, projectName, projectCount, initialSelectedRowIndex, readRuleKeys, onSelectedRowIndexChange, onQuit, onBack = onQuit, exitHint }) => {
|
|
953
|
-
const isSplit = layout === "split";
|
|
954
|
-
const isCompact = layout === "compact";
|
|
955
|
-
const initialSelectedEntryIndex = initialSelectedRowIndex === null ? 0 : Math.max(0, entries.findIndex((entry) => entry.kind === "item" && entry.rowIndex === initialSelectedRowIndex));
|
|
956
|
-
const { selectedIndex: selectedEntryIndex, visibleStart: visibleEntryStart, visibleEnd: visibleEntryEnd } = useScrollViewport({
|
|
957
|
-
itemCount: entries.length,
|
|
958
|
-
height: listHeight,
|
|
959
|
-
initialSelectedIndex: initialSelectedEntryIndex,
|
|
960
|
-
isSelectable: (entryIndex) => entries[entryIndex]?.kind === "item",
|
|
961
|
-
onSelectedIndexChange: (entryIndex) => {
|
|
962
|
-
const entry = entries[entryIndex];
|
|
963
|
-
if (entry?.kind === "item") onSelectedRowIndexChange(entry.rowIndex);
|
|
964
|
-
}
|
|
965
|
-
});
|
|
966
|
-
const visibleEntries = entries.slice(visibleEntryStart, visibleEntryEnd);
|
|
967
|
-
const selectedEntry = entries[selectedEntryIndex];
|
|
968
|
-
const selected = selectedEntry?.kind === "item" ? selectedEntry.row : null;
|
|
969
|
-
const selectedRuleKey = selected?.ruleKey ?? null;
|
|
970
|
-
const initialSelectedRuleKey = useRef(selectedRuleKey);
|
|
971
|
-
const didRecordFindingNavigation = useRef(false);
|
|
972
|
-
const [copiedRuleKey, setCopiedRuleKey] = useState(null);
|
|
973
|
-
const [copyFailedRuleKey, setCopyFailedRuleKey] = useState(null);
|
|
974
|
-
const effectiveReadRuleKeys = useMemo(() => {
|
|
975
|
-
if (!selectedRuleKey || readRuleKeys.has(selectedRuleKey)) return readRuleKeys;
|
|
976
|
-
return new Set(readRuleKeys).add(selectedRuleKey);
|
|
977
|
-
}, [readRuleKeys, selectedRuleKey]);
|
|
978
|
-
useEffect(() => {
|
|
979
|
-
if (!selectedRuleKey || selectedRuleKey === initialSelectedRuleKey.current || didRecordFindingNavigation.current) return;
|
|
980
|
-
didRecordFindingNavigation.current = true;
|
|
981
|
-
recordCount(METRIC.tuiFindingNavigated);
|
|
982
|
-
}, [selectedRuleKey]);
|
|
983
|
-
const copySelectedIssueContext = () => {
|
|
984
|
-
if (!selected) return;
|
|
985
|
-
const issuePrompt = buildIssuePrompt({
|
|
986
|
-
row: selected,
|
|
987
|
-
projectName
|
|
988
|
-
});
|
|
989
|
-
const ruleKey = selected.ruleKey;
|
|
990
|
-
copyToClipboard(issuePrompt).then((didCopyIssueContext) => {
|
|
991
|
-
if (didCopyIssueContext) {
|
|
992
|
-
setCopiedRuleKey(ruleKey);
|
|
993
|
-
setCopyFailedRuleKey(null);
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
|
-
recordCount(METRIC.tuiReportActionSelected, 1, { action: "copy-issue-context-failed" });
|
|
997
|
-
setCopiedRuleKey(null);
|
|
998
|
-
setCopyFailedRuleKey(ruleKey);
|
|
999
|
-
});
|
|
1000
|
-
};
|
|
1001
|
-
useInput((input, key) => {
|
|
1002
|
-
if (input === "q") return onQuit();
|
|
1003
|
-
if (key.escape) return onBack();
|
|
1004
|
-
if (key.return && selected) {
|
|
1005
|
-
recordCount(METRIC.tuiReportActionSelected, 1, { action: "copy-issue-context" });
|
|
1006
|
-
copySelectedIssueContext();
|
|
1007
|
-
}
|
|
1008
|
-
});
|
|
1009
|
-
let totalFindingCount = 0;
|
|
1010
|
-
let errorFindingCount = 0;
|
|
1011
|
-
let warningFindingCount = 0;
|
|
1012
|
-
for (const row of rows) {
|
|
1013
|
-
totalFindingCount += row.siteCount;
|
|
1014
|
-
if (row.severity === "error") errorFindingCount += row.siteCount;
|
|
1015
|
-
else warningFindingCount += row.siteCount;
|
|
1016
|
-
}
|
|
1017
|
-
const selectedIssuePosition = entries.slice(0, selectedEntryIndex + 1).filter((entry) => entry.kind === "item").length;
|
|
1018
|
-
const unreadIssueCount = rows.length - rows.filter((row) => effectiveReadRuleKeys.has(row.ruleKey)).length;
|
|
1019
|
-
const listColumn = /* @__PURE__ */ jsx(Box, {
|
|
1020
|
-
flexDirection: "column",
|
|
1021
|
-
height: listHeight,
|
|
1022
|
-
width: isSplit ? listColumnWidth : width,
|
|
1023
|
-
children: visibleEntries.map((entry, index) => {
|
|
1024
|
-
const entryIndex = visibleEntryStart + index;
|
|
1025
|
-
if (entry.kind === "header") return /* @__PURE__ */ jsx(Text, {
|
|
1026
|
-
bold: true,
|
|
1027
|
-
children: entry.category
|
|
1028
|
-
}, `header-${entry.category}`);
|
|
1029
|
-
return /* @__PURE__ */ jsx(DiagnosticItem, {
|
|
1030
|
-
row: entry.row,
|
|
1031
|
-
isSelected: entryIndex === selectedEntryIndex,
|
|
1032
|
-
isRead: effectiveReadRuleKeys.has(entry.row.ruleKey)
|
|
1033
|
-
}, entry.row.ruleKey);
|
|
1034
|
-
})
|
|
1035
|
-
});
|
|
1036
|
-
let copyFeedback = null;
|
|
1037
|
-
if (copiedRuleKey === selectedRuleKey) copyFeedback = /* @__PURE__ */ jsx(Box, {
|
|
1038
|
-
marginTop: 1,
|
|
1039
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
1040
|
-
color: "green",
|
|
1041
|
-
children: "✓ Copied issue context"
|
|
1042
|
-
})
|
|
1043
|
-
});
|
|
1044
|
-
else if (copyFailedRuleKey === selectedRuleKey) copyFeedback = /* @__PURE__ */ jsx(Box, {
|
|
1045
|
-
marginTop: 1,
|
|
1046
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
1047
|
-
color: "yellow",
|
|
1048
|
-
children: "Couldn't copy issue context. Press Enter to try again."
|
|
1049
|
-
})
|
|
1050
|
-
});
|
|
1051
|
-
const detailContent = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DiagnosticDetail, {
|
|
1052
|
-
row: selected,
|
|
1053
|
-
rootDirectory
|
|
1054
|
-
}), copyFeedback] });
|
|
1055
|
-
let visibleKeyHints = DIAGNOSTIC_KEY_HINTS;
|
|
1056
|
-
if (isCompact && copiedRuleKey === selectedRuleKey) visibleKeyHints = /* @__PURE__ */ jsx(Text, {
|
|
1057
|
-
color: "green",
|
|
1058
|
-
children: "✓ Copied issue context"
|
|
1059
|
-
});
|
|
1060
|
-
else if (isCompact && copyFailedRuleKey === selectedRuleKey) visibleKeyHints = /* @__PURE__ */ jsx(Text, {
|
|
1061
|
-
color: "yellow",
|
|
1062
|
-
children: "Copy failed · enter retry"
|
|
1063
|
-
});
|
|
1064
|
-
const statusBar = /* @__PURE__ */ jsx(Box, {
|
|
1065
|
-
marginTop: isCompact ? 0 : 1,
|
|
1066
|
-
children: /* @__PURE__ */ jsx(StatusBar, {
|
|
1067
|
-
total: totalFindingCount,
|
|
1068
|
-
errorCount: errorFindingCount,
|
|
1069
|
-
warningCount: warningFindingCount,
|
|
1070
|
-
position: selectedIssuePosition,
|
|
1071
|
-
issueCount: rows.length,
|
|
1072
|
-
unreadCount: unreadIssueCount,
|
|
1073
|
-
projectCount,
|
|
1074
|
-
keyHints: visibleKeyHints,
|
|
1075
|
-
exitHint,
|
|
1076
|
-
compact: isCompact
|
|
1077
|
-
})
|
|
1078
|
-
});
|
|
1079
|
-
if (isCompact) return /* @__PURE__ */ jsxs(Box, {
|
|
1080
|
-
flexDirection: "column",
|
|
1081
|
-
width,
|
|
1082
|
-
children: [
|
|
1083
|
-
header,
|
|
1084
|
-
listColumn,
|
|
1085
|
-
statusBar
|
|
1086
|
-
]
|
|
1087
|
-
});
|
|
1088
|
-
if (isSplit) return /* @__PURE__ */ jsxs(Box, {
|
|
1089
|
-
flexDirection: "column",
|
|
1090
|
-
width,
|
|
1091
|
-
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1092
|
-
flexDirection: "row",
|
|
1093
|
-
maxHeight: detailHeight,
|
|
1094
|
-
overflowY: "hidden",
|
|
1095
|
-
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1096
|
-
flexDirection: "column",
|
|
1097
|
-
width: listColumnWidth,
|
|
1098
|
-
marginRight: 1,
|
|
1099
|
-
children: [header, /* @__PURE__ */ jsx(Box, {
|
|
1100
|
-
marginTop: 1,
|
|
1101
|
-
children: listColumn
|
|
1102
|
-
})]
|
|
1103
|
-
}), /* @__PURE__ */ jsx(Box, {
|
|
1104
|
-
flexDirection: "column",
|
|
1105
|
-
width: detailColumnWidth,
|
|
1106
|
-
borderStyle: "single",
|
|
1107
|
-
borderColor: "gray",
|
|
1108
|
-
borderTop: false,
|
|
1109
|
-
borderRight: false,
|
|
1110
|
-
borderBottom: false,
|
|
1111
|
-
paddingLeft: 1,
|
|
1112
|
-
overflowY: "hidden",
|
|
1113
|
-
children: detailContent
|
|
1114
|
-
})]
|
|
1115
|
-
}), statusBar]
|
|
1116
|
-
});
|
|
1117
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1118
|
-
flexDirection: "column",
|
|
1119
|
-
width,
|
|
1120
|
-
children: [
|
|
1121
|
-
header,
|
|
1122
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1123
|
-
marginTop: 1,
|
|
1124
|
-
children: listColumn
|
|
1125
|
-
}),
|
|
1126
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1127
|
-
dimColor: true,
|
|
1128
|
-
children: "─".repeat(width)
|
|
1129
|
-
}),
|
|
1130
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1131
|
-
flexDirection: "column",
|
|
1132
|
-
maxHeight: detailHeight,
|
|
1133
|
-
overflowY: "hidden",
|
|
1134
|
-
children: detailContent
|
|
1135
|
-
}),
|
|
1136
|
-
statusBar
|
|
1137
|
-
]
|
|
1138
|
-
});
|
|
1139
|
-
};
|
|
1140
|
-
//#endregion
|
|
1141
|
-
//#region src/cli/ink/components/handoff-ci-recommendation.tsx
|
|
1142
|
-
const HandoffCiRecommendation = ({ onAddToCi, onContinue, onQuit }) => {
|
|
1143
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1144
|
-
flexDirection: "column",
|
|
1145
|
-
children: [
|
|
1146
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1147
|
-
paddingLeft: 2,
|
|
1148
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
1149
|
-
bold: true,
|
|
1150
|
-
children: "Add React Doctor to GitHub Actions first"
|
|
1151
|
-
})
|
|
1152
|
-
}),
|
|
1153
|
-
/* @__PURE__ */ jsx(CiJustification, {}),
|
|
1154
|
-
/* @__PURE__ */ jsx(ActionMenu, {
|
|
1155
|
-
actions: [{
|
|
1156
|
-
id: "add-to-ci",
|
|
1157
|
-
label: "Add to GitHub Actions first (Recommended)",
|
|
1158
|
-
onSelect: onAddToCi
|
|
1159
|
-
}, {
|
|
1160
|
-
id: "continue",
|
|
1161
|
-
label: "Continue without GitHub Actions",
|
|
1162
|
-
onSelect: onContinue
|
|
1163
|
-
}],
|
|
1164
|
-
onEscape: onContinue,
|
|
1165
|
-
onQuit
|
|
1166
|
-
}),
|
|
1167
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1168
|
-
marginTop: 1,
|
|
1169
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
1170
|
-
dimColor: true,
|
|
1171
|
-
children: "↑/↓ move · enter select · esc skip · q quit"
|
|
1172
|
-
})
|
|
1173
|
-
})
|
|
1174
|
-
]
|
|
1175
|
-
});
|
|
1176
|
-
};
|
|
1177
|
-
//#endregion
|
|
1178
|
-
//#region src/cli/ink/components/report-landing.tsx
|
|
1179
|
-
const ReportLanding = ({ header, phase, issueCount, emptyStateMessage, lintFailureReason, skippedChecks, incompleteMessage, actions, selectedIndex, onSelectionChange, onQuit }) => {
|
|
1180
|
-
const showScore = phase === "actions" || phase === "score";
|
|
1181
|
-
const skippedCheckLabel = skippedChecks?.filter((skippedCheck) => skippedCheck !== "lint" || !lintFailureReason).join(" and ");
|
|
1182
|
-
const hasIncompleteResult = Boolean(incompleteMessage || lintFailureReason || skippedCheckLabel);
|
|
1183
|
-
useInput((input) => {
|
|
1184
|
-
if (input === "q") onQuit();
|
|
1185
|
-
}, { isActive: phase !== "actions" });
|
|
1186
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1187
|
-
flexDirection: "column",
|
|
1188
|
-
children: [
|
|
1189
|
-
showScore ? header : null,
|
|
1190
|
-
hasIncompleteResult || issueCount === 0 ? /* @__PURE__ */ jsxs(Box, {
|
|
1191
|
-
flexDirection: "column",
|
|
1192
|
-
marginTop: 1,
|
|
1193
|
-
children: [
|
|
1194
|
-
incompleteMessage ? /* @__PURE__ */ jsxs(Text, {
|
|
1195
|
-
color: "yellow",
|
|
1196
|
-
children: ["⚠ ", incompleteMessage]
|
|
1197
|
-
}) : null,
|
|
1198
|
-
lintFailureReason ? /* @__PURE__ */ jsxs(Text, {
|
|
1199
|
-
color: "yellow",
|
|
1200
|
-
children: ["⚠ Lint did not run: ", lintFailureReason]
|
|
1201
|
-
}) : null,
|
|
1202
|
-
skippedCheckLabel ? /* @__PURE__ */ jsxs(Text, {
|
|
1203
|
-
color: "yellow",
|
|
1204
|
-
children: [
|
|
1205
|
-
"⚠ ",
|
|
1206
|
-
issueCount === 0 ? "No issues detected, but " : "",
|
|
1207
|
-
skippedCheckLabel,
|
|
1208
|
-
" checks failed — results are incomplete."
|
|
1209
|
-
]
|
|
1210
|
-
}) : null,
|
|
1211
|
-
issueCount === 0 && !hasIncompleteResult ? /* @__PURE__ */ jsxs(Text, {
|
|
1212
|
-
color: "green",
|
|
1213
|
-
children: ["✔ ", emptyStateMessage ?? "No issues found. Nice work."]
|
|
1214
|
-
}) : null
|
|
1215
|
-
]
|
|
1216
|
-
}) : null,
|
|
1217
|
-
phase === "actions" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ActionMenu, {
|
|
1218
|
-
actions,
|
|
1219
|
-
selectedIndex,
|
|
1220
|
-
onSelectionChange,
|
|
1221
|
-
onEscape: onQuit,
|
|
1222
|
-
onQuit
|
|
1223
|
-
}), /* @__PURE__ */ jsx(Box, {
|
|
1224
|
-
marginTop: 1,
|
|
1225
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
1226
|
-
dimColor: true,
|
|
1227
|
-
children: actions.length === 0 ? "q quit" : "↑/↓ move · enter select · q quit"
|
|
1228
|
-
})
|
|
1229
|
-
})] }) : null
|
|
1230
|
-
]
|
|
1231
|
-
});
|
|
1232
|
-
};
|
|
1233
|
-
//#endregion
|
|
1234
|
-
//#region src/cli/ink/components/report-issue-stream.tsx
|
|
1235
|
-
const ReportIssueStream = ({ rows, selectedIndex, width }) => {
|
|
1236
|
-
const visibleRowCount = Math.min(rows.length, 3);
|
|
1237
|
-
const visibleRows = Array.from({ length: visibleRowCount }, (_, visibleRowIndex) => {
|
|
1238
|
-
const rowIndex = (selectedIndex - visibleRowCount + visibleRowIndex + 1 + rows.length) % rows.length;
|
|
1239
|
-
return {
|
|
1240
|
-
row: rows[rowIndex],
|
|
1241
|
-
rowIndex
|
|
1242
|
-
};
|
|
1243
|
-
});
|
|
1244
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1245
|
-
flexDirection: "column",
|
|
1246
|
-
width: Math.min(width, 60),
|
|
1247
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
1248
|
-
bold: true,
|
|
1249
|
-
children: "Reviewing issues"
|
|
1250
|
-
}), visibleRows.map(({ row, rowIndex }) => {
|
|
1251
|
-
if (!row) return null;
|
|
1252
|
-
const variant = severityVariant(row.severity);
|
|
1253
|
-
const isSelected = rowIndex === selectedIndex;
|
|
1254
|
-
return /* @__PURE__ */ jsxs(Text, {
|
|
1255
|
-
dimColor: !isSelected,
|
|
1256
|
-
wrap: "truncate-end",
|
|
1257
|
-
children: [
|
|
1258
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
1259
|
-
color: isSelected ? variant.color : void 0,
|
|
1260
|
-
children: [isSelected ? "›" : " ", " "]
|
|
1261
|
-
}),
|
|
1262
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
1263
|
-
color: isSelected ? variant.color : void 0,
|
|
1264
|
-
bold: isSelected,
|
|
1265
|
-
children: [
|
|
1266
|
-
row.category,
|
|
1267
|
-
": ",
|
|
1268
|
-
row.title
|
|
1269
|
-
]
|
|
1270
|
-
}),
|
|
1271
|
-
row.siteCount > 1 ? /* @__PURE__ */ jsxs(Text, {
|
|
1272
|
-
dimColor: true,
|
|
1273
|
-
children: [" ×", row.siteCount]
|
|
1274
|
-
}) : null
|
|
1275
|
-
]
|
|
1276
|
-
}, row.ruleKey);
|
|
1277
|
-
})]
|
|
1278
|
-
});
|
|
1279
|
-
};
|
|
1280
|
-
//#endregion
|
|
1281
|
-
//#region src/cli/ink/hooks/use-animated-score.ts
|
|
1282
|
-
const useAnimatedScore = ({ score, projectedScore, shouldAnimate }) => {
|
|
1283
|
-
const projectionTarget = projectedScore ?? score;
|
|
1284
|
-
const hasProjection = projectionTarget > score;
|
|
1285
|
-
const initialProjectedScore = !shouldAnimate && hasProjection ? projectionTarget : null;
|
|
1286
|
-
const [displayScore, setDisplayScore] = useState(shouldAnimate ? 0 : score);
|
|
1287
|
-
const [displayProjectedScore, setDisplayProjectedScore] = useState(initialProjectedScore);
|
|
1288
|
-
useEffect(() => {
|
|
1289
|
-
if (!shouldAnimate) {
|
|
1290
|
-
setDisplayScore(score);
|
|
1291
|
-
setDisplayProjectedScore(hasProjection ? projectionTarget : null);
|
|
1292
|
-
return;
|
|
1293
|
-
}
|
|
1294
|
-
let timeoutId;
|
|
1295
|
-
const runProjection = () => {
|
|
1296
|
-
let projectionFrame = 1;
|
|
1297
|
-
const renderProjectionFrame = () => {
|
|
1298
|
-
const progress = easeOutCubic(projectionFrame / 16);
|
|
1299
|
-
setDisplayProjectedScore(score + (projectionTarget - score) * progress);
|
|
1300
|
-
if (projectionFrame < 16) {
|
|
1301
|
-
projectionFrame += 1;
|
|
1302
|
-
timeoutId = setTimeout(renderProjectionFrame, 35);
|
|
1303
|
-
} else setDisplayProjectedScore(projectionTarget);
|
|
1304
|
-
};
|
|
1305
|
-
renderProjectionFrame();
|
|
1306
|
-
};
|
|
1307
|
-
let scoreFrame = 0;
|
|
1308
|
-
const renderScoreFrame = () => {
|
|
1309
|
-
const progress = easeOutCubic(scoreFrame / 40);
|
|
1310
|
-
setDisplayScore(Math.round(score * progress));
|
|
1311
|
-
if (scoreFrame < 40) {
|
|
1312
|
-
scoreFrame += 1;
|
|
1313
|
-
timeoutId = setTimeout(renderScoreFrame, 50);
|
|
1314
|
-
return;
|
|
1315
|
-
}
|
|
1316
|
-
setDisplayScore(score);
|
|
1317
|
-
if (hasProjection) runProjection();
|
|
1318
|
-
};
|
|
1319
|
-
renderScoreFrame();
|
|
1320
|
-
return () => {
|
|
1321
|
-
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
1322
|
-
};
|
|
1323
|
-
}, [
|
|
1324
|
-
shouldAnimate,
|
|
1325
|
-
score,
|
|
1326
|
-
projectionTarget,
|
|
1327
|
-
hasProjection
|
|
1328
|
-
]);
|
|
1329
|
-
return {
|
|
1330
|
-
displayScore,
|
|
1331
|
-
displayProjectedScore
|
|
1332
|
-
};
|
|
1333
|
-
};
|
|
1334
|
-
//#endregion
|
|
1335
|
-
//#region src/cli/ink/lib/score-color.ts
|
|
1336
|
-
const scoreColorName = (score) => {
|
|
1337
|
-
if (score >= 75) return "green";
|
|
1338
|
-
if (score >= 50) return "yellow";
|
|
1339
|
-
return "red";
|
|
1340
|
-
};
|
|
1341
|
-
//#endregion
|
|
1342
|
-
//#region src/cli/ink/components/score-header.tsx
|
|
1343
|
-
const REACT_DOCTOR_URL = "https://react.doctor";
|
|
1344
|
-
const ReactDoctorLink = () => /* @__PURE__ */ jsxs(TuiLink, {
|
|
1345
|
-
url: REACT_DOCTOR_URL,
|
|
1346
|
-
children: ["React Doctor ", /* @__PURE__ */ jsxs(Text, {
|
|
1347
|
-
dimColor: true,
|
|
1348
|
-
children: [
|
|
1349
|
-
"(",
|
|
1350
|
-
REACT_DOCTOR_URL,
|
|
1351
|
-
")"
|
|
1352
|
-
]
|
|
1353
|
-
})]
|
|
1354
|
-
});
|
|
1355
|
-
const ScoreHeader = ({ variant, score, projectedScore, projectName, issueCount, noScoreMessage, width }) => {
|
|
1356
|
-
const { columns } = useStdoutDimensions();
|
|
1357
|
-
const availableWidth = width ?? columns;
|
|
1358
|
-
const { stdout } = useStdout();
|
|
1359
|
-
const shouldAnimateScore = canAnimateOnboarding(stdout ?? void 0);
|
|
1360
|
-
const visibleProjectedScore = variant === "landing" ? projectedScore : null;
|
|
1361
|
-
const { displayScore, displayProjectedScore } = useAnimatedScore({
|
|
1362
|
-
score: score?.score ?? 0,
|
|
1363
|
-
projectedScore: visibleProjectedScore,
|
|
1364
|
-
shouldAnimate: variant === "landing" && shouldAnimateScore && score !== null
|
|
1365
|
-
});
|
|
1366
|
-
if (!score) return /* @__PURE__ */ jsxs(Box, {
|
|
1367
|
-
flexDirection: "column",
|
|
1368
|
-
paddingLeft: 2,
|
|
1369
|
-
width: availableWidth,
|
|
1370
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
1371
|
-
wrap: "truncate-end",
|
|
1372
|
-
children: /* @__PURE__ */ jsx(ReactDoctorLink, {})
|
|
1373
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
1374
|
-
dimColor: true,
|
|
1375
|
-
wrap: "wrap",
|
|
1376
|
-
children: noScoreMessage ?? `${pluralize(issueCount, "finding")} · ${projectName}`
|
|
1377
|
-
})]
|
|
1378
|
-
});
|
|
1379
|
-
const scoreColor = scoreColorName(score.score);
|
|
1380
|
-
const scoreSummaryLine = /* @__PURE__ */ jsxs(Text, {
|
|
1381
|
-
wrap: "truncate-end",
|
|
1382
|
-
children: [
|
|
1383
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1384
|
-
color: scoreColor,
|
|
1385
|
-
bold: true,
|
|
1386
|
-
children: displayScore
|
|
1387
|
-
}),
|
|
1388
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
1389
|
-
dimColor: true,
|
|
1390
|
-
children: [
|
|
1391
|
-
" / ",
|
|
1392
|
-
100,
|
|
1393
|
-
" "
|
|
1394
|
-
]
|
|
1395
|
-
}),
|
|
1396
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1397
|
-
color: scoreColor,
|
|
1398
|
-
children: score.label
|
|
1399
|
-
}),
|
|
1400
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
1401
|
-
dimColor: true,
|
|
1402
|
-
children: [" · ", projectName]
|
|
1403
|
-
})
|
|
1404
|
-
]
|
|
1405
|
-
});
|
|
1406
|
-
if (availableWidth < 23) return /* @__PURE__ */ jsxs(Box, {
|
|
1407
|
-
flexDirection: "column",
|
|
1408
|
-
width: availableWidth,
|
|
1409
|
-
children: [scoreSummaryLine, /* @__PURE__ */ jsx(Text, {
|
|
1410
|
-
wrap: "truncate-end",
|
|
1411
|
-
children: /* @__PURE__ */ jsx(ReactDoctorLink, {})
|
|
1412
|
-
})]
|
|
1413
|
-
});
|
|
1414
|
-
const barWidth = Math.max(10, Math.min(50, availableWidth - 11 - 2));
|
|
1415
|
-
const filledBarWidth = Math.round(displayScore / 100 * barWidth);
|
|
1416
|
-
const projectedBarWidth = displayProjectedScore != null ? Math.min(barWidth, Math.round(displayProjectedScore / 100 * barWidth)) : filledBarWidth;
|
|
1417
|
-
const projectedGainWidth = Math.max(0, projectedBarWidth - filledBarWidth);
|
|
1418
|
-
const emptyBarWidth = Math.max(0, barWidth - filledBarWidth - projectedGainWidth);
|
|
1419
|
-
const [eyes, mouth] = doctorFace(score.score);
|
|
1420
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1421
|
-
flexDirection: "column",
|
|
1422
|
-
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1423
|
-
paddingLeft: 2,
|
|
1424
|
-
children: [/* @__PURE__ */ jsx(Box, {
|
|
1425
|
-
flexDirection: "column",
|
|
1426
|
-
marginRight: 2,
|
|
1427
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
1428
|
-
color: scoreColor,
|
|
1429
|
-
children: `┌─────┐\n│ ${eyes} │\n│ ${mouth} │\n└─────┘`
|
|
1430
|
-
})
|
|
1431
|
-
}), /* @__PURE__ */ jsxs(Box, {
|
|
1432
|
-
flexDirection: "column",
|
|
1433
|
-
children: [
|
|
1434
|
-
scoreSummaryLine,
|
|
1435
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
1436
|
-
wrap: "truncate-end",
|
|
1437
|
-
children: [
|
|
1438
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1439
|
-
color: scoreColor,
|
|
1440
|
-
children: "█".repeat(filledBarWidth)
|
|
1441
|
-
}),
|
|
1442
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1443
|
-
color: scoreColor,
|
|
1444
|
-
dimColor: true,
|
|
1445
|
-
children: "▓".repeat(projectedGainWidth)
|
|
1446
|
-
}),
|
|
1447
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1448
|
-
dimColor: true,
|
|
1449
|
-
children: "░".repeat(emptyBarWidth)
|
|
1450
|
-
})
|
|
1451
|
-
]
|
|
1452
|
-
}),
|
|
1453
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1454
|
-
wrap: "truncate-end",
|
|
1455
|
-
children: /* @__PURE__ */ jsx(ReactDoctorLink, {})
|
|
1456
|
-
}),
|
|
1457
|
-
/* @__PURE__ */ jsx(Text, { children: " " })
|
|
1458
|
-
]
|
|
1459
|
-
})]
|
|
1460
|
-
}), variant === "landing" && projectedScore != null && projectedScore > score.score ? /* @__PURE__ */ jsxs(Text, {
|
|
1461
|
-
wrap: "truncate-end",
|
|
1462
|
-
children: [
|
|
1463
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1464
|
-
dimColor: true,
|
|
1465
|
-
children: " Potential score "
|
|
1466
|
-
}),
|
|
1467
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1468
|
-
color: scoreColorName(projectedScore),
|
|
1469
|
-
children: projectedScore
|
|
1470
|
-
}),
|
|
1471
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1472
|
-
dimColor: true,
|
|
1473
|
-
children: " after priority fixes "
|
|
1474
|
-
}),
|
|
1475
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
1476
|
-
color: scoreColorName(projectedScore),
|
|
1477
|
-
children: ["+", projectedScore - score.score]
|
|
1478
|
-
})
|
|
1479
|
-
]
|
|
1480
|
-
}) : null]
|
|
1481
|
-
});
|
|
1482
|
-
};
|
|
1483
|
-
//#endregion
|
|
1484
|
-
//#region src/cli/ink/components/report.tsx
|
|
1485
|
-
const EMPTY_LAUNCHABLE_AGENTS = [];
|
|
1486
|
-
const recordReportAction = (action) => {
|
|
1487
|
-
recordCount(METRIC.tuiReportActionSelected, 1, { action });
|
|
1488
|
-
};
|
|
1489
|
-
const completeReportOnboarding = () => {
|
|
1490
|
-
if (shouldRecordOnboarding({
|
|
1491
|
-
paceOnboardingSections: true,
|
|
1492
|
-
forceOnboarding: isOnboardingForced(),
|
|
1493
|
-
verbose: false,
|
|
1494
|
-
isNonInteractiveEnvironment: false
|
|
1495
|
-
})) markOnboardingComplete();
|
|
1496
|
-
};
|
|
1497
|
-
const Report = ({ report, onExit, onQuit = onExit, launchableAgents = EMPTY_LAUNCHABLE_AGENTS, onHandoff, canAddToCi, onAddToCi, projectCount, priorityScores, exitHint = "q to quit" }) => {
|
|
1498
|
-
const { rows: terminalRows, columns } = useStdoutDimensions();
|
|
1499
|
-
const diagnosticRows = useMemo(() => buildDiagnosticRows(report.diagnostics, priorityScores ?? [report.score]), [
|
|
1500
|
-
report.diagnostics,
|
|
1501
|
-
report.score,
|
|
1502
|
-
priorityScores
|
|
1503
|
-
]);
|
|
1504
|
-
const diagnosticListEntries = useMemo(() => buildDiagnosticListEntries(diagnosticRows), [diagnosticRows]);
|
|
1505
|
-
const reportLayout = resolveReportLayout({
|
|
1506
|
-
columns,
|
|
1507
|
-
diagnosticEntryCount: diagnosticListEntries.length,
|
|
1508
|
-
terminalRows
|
|
1509
|
-
});
|
|
1510
|
-
const reportReveal = useReportReveal({
|
|
1511
|
-
issueCount: diagnosticRows.length,
|
|
1512
|
-
onRevealComplete: completeReportOnboarding
|
|
1513
|
-
});
|
|
1514
|
-
const [activeReportScreen, setActiveReportScreen] = useState("landing");
|
|
1515
|
-
const [ciSetupFeedback, setCiSetupFeedback] = useState();
|
|
1516
|
-
const [landingSelectedIndex, setLandingSelectedIndex] = useState(0);
|
|
1517
|
-
const [viewerSelectedRowIndex, setViewerSelectedRowIndex] = useState(null);
|
|
1518
|
-
const [viewerReadRuleKeys, setViewerReadRuleKeys] = useState(() => /* @__PURE__ */ new Set());
|
|
1519
|
-
const [isCiSetupQueued, setIsCiSetupQueued] = useState(false);
|
|
1520
|
-
const [shouldShowIssueStream, setShouldShowIssueStream] = useState(true);
|
|
1521
|
-
const didRecordCompactReport = useRef(false);
|
|
1522
|
-
const didRecordIssueStream = useRef(false);
|
|
1523
|
-
const didRecordStackedReportCap = useRef(false);
|
|
1524
|
-
const isCompact = reportLayout.layout === "compact";
|
|
1525
|
-
const isWide = reportLayout.layout === "split";
|
|
1526
|
-
useEffect(() => {
|
|
1527
|
-
if (!isCompact || didRecordCompactReport.current) return;
|
|
1528
|
-
didRecordCompactReport.current = true;
|
|
1529
|
-
recordCount(METRIC.tuiCompactReportShown);
|
|
1530
|
-
}, [isCompact]);
|
|
1531
|
-
useEffect(() => {
|
|
1532
|
-
if (!reportLayout.isStackedReportCapped || didRecordStackedReportCap.current) return;
|
|
1533
|
-
didRecordStackedReportCap.current = true;
|
|
1534
|
-
recordCount(METRIC.tuiStackedReportCapped);
|
|
1535
|
-
}, [reportLayout.isStackedReportCapped]);
|
|
1536
|
-
useEffect(() => {
|
|
1537
|
-
if (reportReveal.phase !== "streaming" || didRecordIssueStream.current) return;
|
|
1538
|
-
didRecordIssueStream.current = true;
|
|
1539
|
-
recordCount(METRIC.tuiIssueStreamShown);
|
|
1540
|
-
}, [reportReveal.phase]);
|
|
1541
|
-
const scoreHeaderProps = {
|
|
1542
|
-
score: report.score,
|
|
1543
|
-
projectedScore: report.projectedScore,
|
|
1544
|
-
projectName: report.projectName,
|
|
1545
|
-
issueCount: report.diagnostics.length,
|
|
1546
|
-
noScoreMessage: report.noScoreMessage,
|
|
1547
|
-
width: isWide ? reportLayout.listColumnWidth : reportLayout.width
|
|
1548
|
-
};
|
|
1549
|
-
const isCiSetupAvailable = Boolean(canAddToCi && onAddToCi && !isCiSetupQueued);
|
|
1550
|
-
const isHandoffAvailable = diagnosticRows.length > 0 && launchableAgents.length > 0 && Boolean(onHandoff);
|
|
1551
|
-
const firstDiagnosticEntry = diagnosticListEntries.find((entry) => entry.kind === "item");
|
|
1552
|
-
const resolvedViewerSelectedRowIndex = viewerSelectedRowIndex ?? (firstDiagnosticEntry?.kind === "item" ? firstDiagnosticEntry.rowIndex : null);
|
|
1553
|
-
const markViewerRuleRead = (index) => {
|
|
1554
|
-
const ruleKey = diagnosticRows[index]?.ruleKey;
|
|
1555
|
-
if (!ruleKey) return;
|
|
1556
|
-
setViewerReadRuleKeys((previous) => previous.has(ruleKey) ? previous : new Set(previous).add(ruleKey));
|
|
1557
|
-
};
|
|
1558
|
-
const handleViewerSelectionChange = (index) => {
|
|
1559
|
-
setViewerSelectedRowIndex(index);
|
|
1560
|
-
markViewerRuleRead(index);
|
|
1561
|
-
};
|
|
1562
|
-
const openReportScreen = (nextScreen) => {
|
|
1563
|
-
setShouldShowIssueStream(false);
|
|
1564
|
-
setCiSetupFeedback(void 0);
|
|
1565
|
-
setActiveReportScreen(nextScreen);
|
|
1566
|
-
};
|
|
1567
|
-
const landingActions = [];
|
|
1568
|
-
if (report.diagnostics.length > 0) landingActions.push({
|
|
1569
|
-
id: "view-issues",
|
|
1570
|
-
label: `Review ${pluralize(diagnosticRows.length, "issue")}`,
|
|
1571
|
-
onSelect: () => {
|
|
1572
|
-
recordReportAction("view-issues");
|
|
1573
|
-
if (resolvedViewerSelectedRowIndex !== null) {
|
|
1574
|
-
setViewerSelectedRowIndex(resolvedViewerSelectedRowIndex);
|
|
1575
|
-
markViewerRuleRead(resolvedViewerSelectedRowIndex);
|
|
1576
|
-
}
|
|
1577
|
-
openReportScreen("issues");
|
|
1578
|
-
}
|
|
1579
|
-
});
|
|
1580
|
-
if (isCiSetupAvailable) landingActions.push({
|
|
1581
|
-
id: "add-to-ci",
|
|
1582
|
-
label: "Add to GitHub Actions (Recommended)",
|
|
1583
|
-
description: /* @__PURE__ */ jsx(CiJustification, {}),
|
|
1584
|
-
onSelect: () => {
|
|
1585
|
-
recordReportAction("add-to-ci");
|
|
1586
|
-
openReportScreen("ci");
|
|
1587
|
-
}
|
|
1588
|
-
});
|
|
1589
|
-
if (isHandoffAvailable) landingActions.push({
|
|
1590
|
-
id: "handoff",
|
|
1591
|
-
label: "Hand off to an agent",
|
|
1592
|
-
onSelect: () => {
|
|
1593
|
-
recordReportAction("handoff");
|
|
1594
|
-
openReportScreen(isCiSetupAvailable ? "handoff-ci" : "handoff");
|
|
1595
|
-
}
|
|
1596
|
-
});
|
|
1597
|
-
const postReviewLandingActionIndex = landingActions.findIndex((action) => action.id !== "view-issues");
|
|
1598
|
-
const resolvedLandingSelectedIndex = Math.min(landingSelectedIndex, Math.max(0, landingActions.length - 1));
|
|
1599
|
-
const issueStream = reportReveal.phase === "streaming" ? /* @__PURE__ */ jsx(ReportIssueStream, {
|
|
1600
|
-
rows: diagnosticRows,
|
|
1601
|
-
selectedIndex: reportReveal.streamSelectedIndex,
|
|
1602
|
-
width: reportLayout.width
|
|
1603
|
-
}) : null;
|
|
1604
|
-
let activeScreenContent;
|
|
1605
|
-
if (activeReportScreen === "ci") activeScreenContent = /* @__PURE__ */ jsx(CiSetup, {
|
|
1606
|
-
feedback: ciSetupFeedback,
|
|
1607
|
-
onConfirm: () => {
|
|
1608
|
-
onAddToCi?.();
|
|
1609
|
-
onExit();
|
|
1610
|
-
},
|
|
1611
|
-
onLearnMore: () => {
|
|
1612
|
-
recordReportAction("ci-learn-more");
|
|
1613
|
-
openUrl(GITHUB_ACTIONS_SETUP_URL).then((didOpen) => {
|
|
1614
|
-
setCiSetupFeedback({
|
|
1615
|
-
didSucceed: didOpen,
|
|
1616
|
-
message: didOpen ? "✓ Opened the GitHub Actions guide in your browser" : `Couldn't open a browser. Visit ${GITHUB_ACTIONS_SETUP_URL}`
|
|
1617
|
-
});
|
|
1618
|
-
});
|
|
1619
|
-
},
|
|
1620
|
-
onBack: () => {
|
|
1621
|
-
setCiSetupFeedback(void 0);
|
|
1622
|
-
setActiveReportScreen("landing");
|
|
1623
|
-
},
|
|
1624
|
-
onQuit
|
|
1625
|
-
});
|
|
1626
|
-
else if (activeReportScreen === "handoff") activeScreenContent = /* @__PURE__ */ jsx(AgentHandoff, {
|
|
1627
|
-
agents: launchableAgents,
|
|
1628
|
-
onSelect: (agentId) => {
|
|
1629
|
-
if (!onHandoff) return;
|
|
1630
|
-
onHandoff({
|
|
1631
|
-
agentId,
|
|
1632
|
-
prompt: buildHandoffPayload({
|
|
1633
|
-
diagnostics: report.diagnostics,
|
|
1634
|
-
projectName: report.projectName
|
|
1635
|
-
})
|
|
1636
|
-
});
|
|
1637
|
-
onExit();
|
|
1638
|
-
},
|
|
1639
|
-
onBack: () => setActiveReportScreen("landing"),
|
|
1640
|
-
onQuit
|
|
1641
|
-
});
|
|
1642
|
-
else if (activeReportScreen === "handoff-ci") activeScreenContent = /* @__PURE__ */ jsx(HandoffCiRecommendation, {
|
|
1643
|
-
onAddToCi: () => {
|
|
1644
|
-
onAddToCi?.();
|
|
1645
|
-
setIsCiSetupQueued(true);
|
|
1646
|
-
setActiveReportScreen("handoff");
|
|
1647
|
-
},
|
|
1648
|
-
onContinue: () => setActiveReportScreen("handoff"),
|
|
1649
|
-
onQuit
|
|
1650
|
-
});
|
|
1651
|
-
else if (activeReportScreen === "landing") activeScreenContent = /* @__PURE__ */ jsx(ReportLanding, {
|
|
1652
|
-
header: /* @__PURE__ */ jsx(ScoreHeader, {
|
|
1653
|
-
variant: "landing",
|
|
1654
|
-
...scoreHeaderProps
|
|
1655
|
-
}),
|
|
1656
|
-
phase: reportReveal.phase,
|
|
1657
|
-
issueCount: report.diagnostics.length,
|
|
1658
|
-
emptyStateMessage: report.emptyStateMessage,
|
|
1659
|
-
lintFailureReason: report.lintFailureReason,
|
|
1660
|
-
skippedChecks: report.skippedChecks,
|
|
1661
|
-
incompleteMessage: report.incompleteMessage,
|
|
1662
|
-
actions: landingActions,
|
|
1663
|
-
selectedIndex: resolvedLandingSelectedIndex,
|
|
1664
|
-
onSelectionChange: setLandingSelectedIndex,
|
|
1665
|
-
onQuit
|
|
1666
|
-
});
|
|
1667
|
-
else activeScreenContent = /* @__PURE__ */ jsx(DiagnosticList, {
|
|
1668
|
-
header: reportLayout.showsViewerScoreHeader ? /* @__PURE__ */ jsx(ScoreHeader, {
|
|
1669
|
-
variant: "viewer",
|
|
1670
|
-
...scoreHeaderProps
|
|
1671
|
-
}) : null,
|
|
1672
|
-
rows: diagnosticRows,
|
|
1673
|
-
entries: diagnosticListEntries,
|
|
1674
|
-
width: reportLayout.width,
|
|
1675
|
-
listColumnWidth: reportLayout.listColumnWidth,
|
|
1676
|
-
detailColumnWidth: reportLayout.detailColumnWidth,
|
|
1677
|
-
listHeight: reportLayout.listHeight,
|
|
1678
|
-
detailHeight: reportLayout.detailHeight,
|
|
1679
|
-
layout: reportLayout.layout,
|
|
1680
|
-
rootDirectory: report.rootDirectory,
|
|
1681
|
-
projectName: report.projectName,
|
|
1682
|
-
projectCount,
|
|
1683
|
-
initialSelectedRowIndex: resolvedViewerSelectedRowIndex,
|
|
1684
|
-
readRuleKeys: viewerReadRuleKeys,
|
|
1685
|
-
onSelectedRowIndexChange: handleViewerSelectionChange,
|
|
1686
|
-
onQuit,
|
|
1687
|
-
onBack: () => {
|
|
1688
|
-
setLandingSelectedIndex(Math.max(0, postReviewLandingActionIndex));
|
|
1689
|
-
setActiveReportScreen("landing");
|
|
1690
|
-
},
|
|
1691
|
-
exitHint: `esc back · ${exitHint}`
|
|
1692
|
-
});
|
|
1693
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [activeReportScreen === "landing" && shouldShowIssueStream ? issueStream : null, activeScreenContent] });
|
|
1694
|
-
};
|
|
1695
|
-
//#endregion
|
|
1696
|
-
//#region src/cli/ink/components/scanning.tsx
|
|
1697
|
-
const Scanning = ({ progressText, recent }) => {
|
|
1698
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1699
|
-
flexDirection: "column",
|
|
1700
|
-
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1701
|
-
wrap: "truncate-end",
|
|
1702
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
1703
|
-
color: "cyan",
|
|
1704
|
-
children: /* @__PURE__ */ jsx(Spinner, { type: "dots" })
|
|
1705
|
-
}), /* @__PURE__ */ jsxs(Text, { children: [" ", progressText ?? "Scanning…"] })]
|
|
1706
|
-
}), recent.map((diagnostic, diagnosticIndex) => {
|
|
1707
|
-
const variant = severityVariant(diagnostic.severity === "error" ? "error" : "warning");
|
|
1708
|
-
const location = formatDiagnosticSite(diagnostic);
|
|
1709
|
-
return /* @__PURE__ */ jsxs(Text, {
|
|
1710
|
-
wrap: "truncate-end",
|
|
1711
|
-
children: [
|
|
1712
|
-
" ",
|
|
1713
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1714
|
-
color: variant.color,
|
|
1715
|
-
children: variant.icon
|
|
1716
|
-
}),
|
|
1717
|
-
/* @__PURE__ */ jsxs(Text, { children: [" ", diagnostic.title ?? `${diagnostic.plugin}/${diagnostic.rule}`] }),
|
|
1718
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
1719
|
-
dimColor: true,
|
|
1720
|
-
children: [" ", location]
|
|
1721
|
-
})
|
|
1722
|
-
]
|
|
1723
|
-
}, `${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.plugin}:${diagnostic.rule}:${diagnosticIndex}`);
|
|
1724
|
-
})]
|
|
1725
|
-
});
|
|
1726
|
-
};
|
|
1727
|
-
//#endregion
|
|
1728
|
-
//#region src/cli/ink/components/summary.tsx
|
|
1729
|
-
const Summary = ({ summary, onExit, onQuit, launchableAgents, onHandoff, canAddToCi, onAddToCi }) => {
|
|
1730
|
-
const skippedProjects = summary.skippedProjects ?? [];
|
|
1731
|
-
return /* @__PURE__ */ jsx(Report, {
|
|
1732
|
-
report: {
|
|
1733
|
-
diagnostics: summary.combinedDiagnostics,
|
|
1734
|
-
score: summary.aggregateScore,
|
|
1735
|
-
projectedScore: summary.projectedScore,
|
|
1736
|
-
projectName: summary.projectName,
|
|
1737
|
-
rootDirectory: summary.rootDirectory,
|
|
1738
|
-
scannedFileCount: summary.scannedFileCount,
|
|
1739
|
-
elapsedMilliseconds: summary.elapsedMilliseconds,
|
|
1740
|
-
isOffline: summary.isOffline,
|
|
1741
|
-
noScoreMessage: summary.noScoreMessage,
|
|
1742
|
-
skippedChecks: [...new Set(summary.projects.flatMap((project) => project.skippedChecks ?? []))],
|
|
1743
|
-
...skippedProjects.length > 0 ? { incompleteMessage: formatSkippedProjectsMessage(skippedProjects.length) } : {},
|
|
1744
|
-
...summary.emptyStateMessage ? { emptyStateMessage: summary.emptyStateMessage } : {},
|
|
1745
|
-
...summary.lintFailureReason ? { lintFailureReason: summary.lintFailureReason } : {}
|
|
1746
|
-
},
|
|
1747
|
-
onExit,
|
|
1748
|
-
onQuit,
|
|
1749
|
-
launchableAgents,
|
|
1750
|
-
onHandoff,
|
|
1751
|
-
canAddToCi,
|
|
1752
|
-
onAddToCi,
|
|
1753
|
-
projectCount: summary.projects.length + skippedProjects.length,
|
|
1754
|
-
priorityScores: summary.projects.map((project) => project.score)
|
|
1755
|
-
});
|
|
1756
|
-
};
|
|
1757
|
-
//#endregion
|
|
1758
|
-
//#region src/cli/ink/hooks/use-scan-store.ts
|
|
1759
|
-
const useScanStore = (store) => useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
1760
|
-
//#endregion
|
|
1761
|
-
//#region src/cli/ink/scan-app.tsx
|
|
1762
|
-
const ScanApp = ({ store, displayMode = "report", launchableAgents, onHandoff, canAddToCi, onAddToCi, onQuit }) => {
|
|
1763
|
-
const snapshot = useScanStore(store);
|
|
1764
|
-
const { exit } = useApp();
|
|
1765
|
-
useExitOnCtrlC();
|
|
1766
|
-
const handleQuit = () => {
|
|
1767
|
-
onQuit?.();
|
|
1768
|
-
exit();
|
|
1769
|
-
};
|
|
1770
|
-
if (displayMode === "report" && snapshot.phase === "summary" && snapshot.summary) return /* @__PURE__ */ jsx(Summary, {
|
|
1771
|
-
summary: snapshot.summary,
|
|
1772
|
-
launchableAgents,
|
|
1773
|
-
onHandoff,
|
|
1774
|
-
canAddToCi,
|
|
1775
|
-
onAddToCi,
|
|
1776
|
-
onExit: exit,
|
|
1777
|
-
onQuit: handleQuit
|
|
1778
|
-
});
|
|
1779
|
-
if (displayMode === "report" && snapshot.phase === "report" && snapshot.report) return /* @__PURE__ */ jsx(Report, {
|
|
1780
|
-
report: snapshot.report,
|
|
1781
|
-
launchableAgents,
|
|
1782
|
-
onHandoff,
|
|
1783
|
-
canAddToCi,
|
|
1784
|
-
onAddToCi,
|
|
1785
|
-
onExit: exit,
|
|
1786
|
-
onQuit: handleQuit
|
|
1787
|
-
});
|
|
1788
|
-
return /* @__PURE__ */ jsx(Scanning, {
|
|
1789
|
-
progressText: snapshot.progress,
|
|
1790
|
-
recent: snapshot.liveDiagnostics.slice(-5)
|
|
1791
|
-
});
|
|
1792
|
-
};
|
|
1793
|
-
//#endregion
|
|
1794
|
-
//#region src/cli/ink/scan-bridge-layers.ts
|
|
1795
|
-
const reporterLayerForStore = (store) => succeed(Reporter, Reporter.of({
|
|
1796
|
-
emit: (diagnostic) => sync(() => store.emitDiagnostic(diagnostic)),
|
|
1797
|
-
finalize: void_
|
|
1798
|
-
}));
|
|
1799
|
-
const progressHandleForStore = (store, options = {}) => (text) => {
|
|
1800
|
-
const transformText = options.transformText ?? ((displayText) => displayText);
|
|
1801
|
-
store.setProgress(transformText(text));
|
|
1802
|
-
return {
|
|
1803
|
-
update: (displayText) => sync(() => store.scheduleProgress(transformText(displayText))),
|
|
1804
|
-
succeed: (displayText) => sync(() => store.setProgress(transformText(displayText))),
|
|
1805
|
-
fail: (displayText) => sync(() => store.setProgress(transformText(displayText))),
|
|
1806
|
-
stop: () => sync(() => store.setProgress(options.shouldClearOnStop === false ? transformText(text) : null))
|
|
1807
|
-
};
|
|
1808
|
-
};
|
|
1809
|
-
const progressLayerForStore = (store, options = {}) => Progress.layerOra(progressHandleForStore(store, options));
|
|
1810
|
-
//#endregion
|
|
1811
|
-
//#region src/cli/ink/scan-store.ts
|
|
1812
|
-
const INITIAL_SNAPSHOT = {
|
|
1813
|
-
phase: "scanning",
|
|
1814
|
-
liveDiagnostics: [],
|
|
1815
|
-
progress: null,
|
|
1816
|
-
report: null,
|
|
1817
|
-
summary: null
|
|
1818
|
-
};
|
|
1819
|
-
const createScanStore = () => {
|
|
1820
|
-
let snapshot = INITIAL_SNAPSHOT;
|
|
1821
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1822
|
-
let pendingProgress = null;
|
|
1823
|
-
let progressTimer = null;
|
|
1824
|
-
const commit = (next) => {
|
|
1825
|
-
snapshot = next;
|
|
1826
|
-
for (const listener of listeners) listener();
|
|
1827
|
-
};
|
|
1828
|
-
const cancelPendingProgress = () => {
|
|
1829
|
-
pendingProgress = null;
|
|
1830
|
-
if (progressTimer === null) return;
|
|
1831
|
-
clearTimeout(progressTimer);
|
|
1832
|
-
progressTimer = null;
|
|
1833
|
-
};
|
|
1834
|
-
const setProgress = (progress) => {
|
|
1835
|
-
cancelPendingProgress();
|
|
1836
|
-
commit({
|
|
1837
|
-
...snapshot,
|
|
1838
|
-
progress
|
|
1839
|
-
});
|
|
1840
|
-
};
|
|
1841
|
-
return {
|
|
1842
|
-
subscribe: (listener) => {
|
|
1843
|
-
listeners.add(listener);
|
|
1844
|
-
return () => {
|
|
1845
|
-
listeners.delete(listener);
|
|
1846
|
-
};
|
|
1847
|
-
},
|
|
1848
|
-
getSnapshot: () => snapshot,
|
|
1849
|
-
emitDiagnostic: (diagnostic) => commit({
|
|
1850
|
-
...snapshot,
|
|
1851
|
-
liveDiagnostics: [...snapshot.liveDiagnostics, diagnostic].slice(-25)
|
|
1852
|
-
}),
|
|
1853
|
-
scheduleProgress: (progress) => {
|
|
1854
|
-
pendingProgress = progress;
|
|
1855
|
-
if (progressTimer !== null) return;
|
|
1856
|
-
progressTimer = setTimeout(() => {
|
|
1857
|
-
progressTimer = null;
|
|
1858
|
-
const progressToCommit = pendingProgress;
|
|
1859
|
-
pendingProgress = null;
|
|
1860
|
-
if (progressToCommit !== null) commit({
|
|
1861
|
-
...snapshot,
|
|
1862
|
-
progress: progressToCommit
|
|
1863
|
-
});
|
|
1864
|
-
}, 250);
|
|
1865
|
-
progressTimer.unref?.();
|
|
1866
|
-
},
|
|
1867
|
-
setProgress,
|
|
1868
|
-
setReport: (report) => {
|
|
1869
|
-
cancelPendingProgress();
|
|
1870
|
-
commit({
|
|
1871
|
-
...snapshot,
|
|
1872
|
-
report,
|
|
1873
|
-
phase: "report"
|
|
1874
|
-
});
|
|
1875
|
-
},
|
|
1876
|
-
setSummary: (summary) => {
|
|
1877
|
-
cancelPendingProgress();
|
|
1878
|
-
commit({
|
|
1879
|
-
...snapshot,
|
|
1880
|
-
summary,
|
|
1881
|
-
phase: "summary"
|
|
1882
|
-
});
|
|
1883
|
-
}
|
|
1884
|
-
};
|
|
1885
|
-
};
|
|
1886
|
-
//#endregion
|
|
1887
|
-
//#region src/cli/ink/run-scan-app.tsx
|
|
1888
|
-
const qualifyDiagnosticPaths = (diagnostics, rootDirectory, projectDirectory) => {
|
|
1889
|
-
const prefix = path.relative(rootDirectory, projectDirectory);
|
|
1890
|
-
if (prefix === "" || prefix.startsWith("..")) return [...diagnostics];
|
|
1891
|
-
return diagnostics.map((diagnostic) => path.isAbsolute(diagnostic.filePath) ? diagnostic : {
|
|
1892
|
-
...diagnostic,
|
|
1893
|
-
filePath: toForwardSlashes(path.join(prefix, diagnostic.filePath))
|
|
1894
|
-
});
|
|
1895
|
-
};
|
|
1896
|
-
const resolveScanPresentation = (input, projectScans) => {
|
|
1897
|
-
const isScoreDisabled = input.options?.noScore === true || projectScans.some((projectScan) => projectScan.config?.noScore === true);
|
|
1898
|
-
return {
|
|
1899
|
-
isOffline: isCiEnvironment() || input.share === false || isShareOptedOut(projectScans, input.options?.noScore),
|
|
1900
|
-
initialProgress: projectScans.length > 1 ? "Indexing workspace files…" : "Scanning project…",
|
|
1901
|
-
noScoreMessage: buildNoScoreMessage({ isScoreDisabled }),
|
|
1902
|
-
shouldRecommendCi: projectScans.length > 1 || projectScans.some((projectScan) => isCiUnconfigured(projectScan.directory))
|
|
1903
|
-
};
|
|
1904
|
-
};
|
|
1905
|
-
const resolveTuiInspectOptions = (input, config) => {
|
|
1906
|
-
const warnings = resolveCliInspectOptions({
|
|
1907
|
-
blocking: input.blocking,
|
|
1908
|
-
warnings: input.options?.warnings
|
|
1909
|
-
}, config).warnings;
|
|
1910
|
-
return warnings === void 0 ? { ...input.options } : {
|
|
1911
|
-
...input.options,
|
|
1912
|
-
warnings
|
|
1913
|
-
};
|
|
1914
|
-
};
|
|
1915
|
-
const resolveSelectedDirectories = async (rootDirectory, input) => {
|
|
1916
|
-
const packages = discoverWorkspacePackages(rootDirectory);
|
|
1917
|
-
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);
|
|
1918
|
-
return promptProjectSelection(packages, rootDirectory);
|
|
1919
|
-
};
|
|
1920
|
-
const promptProjectSelection = (packages, rootDirectory) => new Promise((resolve) => {
|
|
1921
|
-
let disposeRenderer = () => {};
|
|
1922
|
-
recordCount(METRIC.tuiProjectSelectInlineShown);
|
|
1923
|
-
const instance = render(/* @__PURE__ */ jsx(ProjectSelect, {
|
|
1924
|
-
packages,
|
|
1925
|
-
rootDirectory,
|
|
1926
|
-
onSubmit: (directories) => {
|
|
1927
|
-
disposeRenderer();
|
|
1928
|
-
resolve(directories);
|
|
1929
|
-
}
|
|
1930
|
-
}), {
|
|
1931
|
-
alternateScreen: false,
|
|
1932
|
-
exitOnCtrlC: false
|
|
1933
|
-
});
|
|
1934
|
-
let didClearRenderer = false;
|
|
1935
|
-
const clearRenderer = () => {
|
|
1936
|
-
if (didClearRenderer) return;
|
|
1937
|
-
didClearRenderer = true;
|
|
1938
|
-
instance.clear();
|
|
1939
|
-
instance.unmount();
|
|
1940
|
-
};
|
|
1941
|
-
const unregisterActiveTuiRenderer = registerActiveTuiRenderer({ clear: clearRenderer });
|
|
1942
|
-
disposeRenderer = () => {
|
|
1943
|
-
unregisterActiveTuiRenderer();
|
|
1944
|
-
clearRenderer();
|
|
1945
|
-
};
|
|
1946
|
-
});
|
|
1947
|
-
const resolveLintFailureReason = (results) => {
|
|
1948
|
-
for (const result of results) {
|
|
1949
|
-
if (!hasLintHardFailure(result)) continue;
|
|
1950
|
-
return result.skippedCheckReasons?.lint ?? "Lint failed before diagnostics were produced.";
|
|
1951
|
-
}
|
|
1952
|
-
return null;
|
|
1953
|
-
};
|
|
1954
|
-
const toScanReport = ({ result, diagnostics, rootDirectory, projectedScore, isOffline, noScoreMessage, emptyStateMessage }) => {
|
|
1955
|
-
const lintFailureReason = resolveLintFailureReason([result]);
|
|
1956
|
-
return {
|
|
1957
|
-
diagnostics: diagnostics ?? result.diagnostics,
|
|
1958
|
-
score: result.score,
|
|
1959
|
-
projectedScore,
|
|
1960
|
-
projectName: result.project.projectName,
|
|
1961
|
-
rootDirectory,
|
|
1962
|
-
scannedFileCount: result.scannedFileCount ?? 0,
|
|
1963
|
-
elapsedMilliseconds: result.elapsedMilliseconds,
|
|
1964
|
-
isOffline,
|
|
1965
|
-
noScoreMessage,
|
|
1966
|
-
emptyStateMessage,
|
|
1967
|
-
skippedChecks: result.skippedChecks,
|
|
1968
|
-
...lintFailureReason ? { lintFailureReason } : {}
|
|
1969
|
-
};
|
|
1970
|
-
};
|
|
1971
|
-
const findLowestScoredReport = (reports) => {
|
|
1972
|
-
let lowestScoredReport = null;
|
|
1973
|
-
for (const report of reports) {
|
|
1974
|
-
if (report.score === null) continue;
|
|
1975
|
-
if (lowestScoredReport === null || report.score.score < lowestScoredReport.score.score) lowestScoredReport = {
|
|
1976
|
-
score: report.score,
|
|
1977
|
-
scoreDiagnostics: report.scoreDiagnostics
|
|
1978
|
-
};
|
|
1979
|
-
}
|
|
1980
|
-
return lowestScoredReport;
|
|
1981
|
-
};
|
|
1982
|
-
const resolveEmptyStateMessage = (input, demotedDiagnosticCount) => buildEmptyReportMessage({
|
|
1983
|
-
categoryFilters: input.options?.categoryFilters ?? [],
|
|
1984
|
-
demotedDiagnosticCount,
|
|
1985
|
-
outputSurface: input.options?.outputSurface ?? "cli"
|
|
1986
|
-
});
|
|
1987
|
-
const printExitFooter = async (input) => {
|
|
1988
|
-
process.stdout.write(`${highlighter.success("✔")} Scanned ${pluralize(input.scannedFileCount, "file")} in ${formatElapsedTime(input.elapsedMilliseconds)}\n`);
|
|
1989
|
-
if (input.lintFailureReason !== null) process.stdout.write(`${highlighter.warn("⚠")} Lint did not run: ${input.lintFailureReason}\n`);
|
|
1990
|
-
await runPromise(printFooter({
|
|
1991
|
-
diagnostics: [...input.diagnostics],
|
|
1992
|
-
scoreResult: input.scoreResult,
|
|
1993
|
-
projectName: input.projectName,
|
|
1994
|
-
isOffline: input.isOffline
|
|
1995
|
-
}));
|
|
1996
|
-
};
|
|
1997
|
-
const performTuiHandoff = async (request, rootDirectory) => {
|
|
1998
|
-
try {
|
|
1999
|
-
await launchCliAgent(request.agentId, request.prompt, rootDirectory);
|
|
2000
|
-
} catch {
|
|
2001
|
-
process.stdout.write(`${highlighter.warn("⚠")} Couldn't launch ${CLI_AGENT_BINARIES[request.agentId]}. Here's the prompt instead:\n`);
|
|
2002
|
-
process.stdout.write(`${highlighter.dim("──── Agent prompt ────")}\n`);
|
|
2003
|
-
process.stdout.write(`${request.prompt}\n`);
|
|
2004
|
-
process.stdout.write(`${highlighter.dim("──────────────────────")}\n`);
|
|
2005
|
-
}
|
|
2006
|
-
};
|
|
2007
|
-
const isCiUnconfigured = (directory) => !isReactDoctorWorkflowInstalled(findNearestPackageDirectory(directory) ?? directory);
|
|
2008
|
-
const performCiSetup = async (rootDirectory) => {
|
|
2009
|
-
const didCreateWorkflow = await setUpGitHubActions({ rootDirectory });
|
|
2010
|
-
recordCount(METRIC.agentHandoff, 1, {
|
|
2011
|
-
outcome: "ci-yes",
|
|
2012
|
-
source: "tui",
|
|
2013
|
-
created: didCreateWorkflow
|
|
2014
|
-
});
|
|
2015
|
-
};
|
|
2016
|
-
const mountScanApp = async (rootDirectory, shouldRecommendCi, initialProgress) => {
|
|
2017
|
-
const store = createScanStore();
|
|
2018
|
-
store.setProgress(initialProgress);
|
|
2019
|
-
const launchableAgents = await detectLaunchableAgents();
|
|
2020
|
-
const pendingActions = {
|
|
2021
|
-
handoffRequest: null,
|
|
2022
|
-
shouldSetUpCi: false,
|
|
2023
|
-
didQuit: false
|
|
2024
|
-
};
|
|
2025
|
-
const mountRenderer = (displayMode) => {
|
|
2026
|
-
const instance = render(/* @__PURE__ */ jsx(ScanApp, {
|
|
2027
|
-
store,
|
|
2028
|
-
displayMode,
|
|
2029
|
-
launchableAgents,
|
|
2030
|
-
onHandoff: (request) => {
|
|
2031
|
-
pendingActions.handoffRequest = request;
|
|
2032
|
-
},
|
|
2033
|
-
canAddToCi: shouldRecommendCi,
|
|
2034
|
-
onAddToCi: () => {
|
|
2035
|
-
pendingActions.shouldSetUpCi = true;
|
|
2036
|
-
},
|
|
2037
|
-
onQuit: () => {
|
|
2038
|
-
pendingActions.didQuit = true;
|
|
2039
|
-
}
|
|
2040
|
-
}), {
|
|
2041
|
-
alternateScreen: false,
|
|
2042
|
-
exitOnCtrlC: false
|
|
2043
|
-
});
|
|
2044
|
-
let didClearRenderer = false;
|
|
2045
|
-
const clearRenderer = () => {
|
|
2046
|
-
if (didClearRenderer) return;
|
|
2047
|
-
didClearRenderer = true;
|
|
2048
|
-
instance.clear();
|
|
2049
|
-
instance.unmount();
|
|
2050
|
-
};
|
|
2051
|
-
const unregisterActiveTuiRenderer = registerActiveTuiRenderer({ clear: clearRenderer });
|
|
2052
|
-
return {
|
|
2053
|
-
instance,
|
|
2054
|
-
dispose: () => {
|
|
2055
|
-
unregisterActiveTuiRenderer();
|
|
2056
|
-
clearRenderer();
|
|
2057
|
-
}
|
|
2058
|
-
};
|
|
2059
|
-
};
|
|
2060
|
-
const executePendingActions = async () => {
|
|
2061
|
-
if (pendingActions.shouldSetUpCi) await performCiSetup(rootDirectory);
|
|
2062
|
-
if (pendingActions.didQuit) return;
|
|
2063
|
-
if (pendingActions.handoffRequest) await performTuiHandoff(pendingActions.handoffRequest, rootDirectory);
|
|
2064
|
-
};
|
|
2065
|
-
return {
|
|
2066
|
-
store,
|
|
2067
|
-
pendingActions,
|
|
2068
|
-
mountRenderer,
|
|
2069
|
-
executePendingActions
|
|
2070
|
-
};
|
|
2071
|
-
};
|
|
2072
|
-
const runMountedScan = async (rootDirectory, presentation, blockingLevel, executeScan) => {
|
|
2073
|
-
const { store, pendingActions, mountRenderer, executePendingActions } = await mountScanApp(rootDirectory, presentation.shouldRecommendCi, presentation.initialProgress);
|
|
2074
|
-
let mountedRenderer = mountRenderer("scan");
|
|
2075
|
-
recordCount(METRIC.tuiScanInlineShown);
|
|
2076
|
-
const context = {
|
|
2077
|
-
store,
|
|
2078
|
-
...presentation
|
|
2079
|
-
};
|
|
2080
|
-
try {
|
|
2081
|
-
const completedScan = await executeScan(context);
|
|
2082
|
-
mountedRenderer.dispose();
|
|
2083
|
-
mountedRenderer = mountRenderer("report");
|
|
2084
|
-
await mountedRenderer.instance.waitUntilExit();
|
|
2085
|
-
mountedRenderer.dispose();
|
|
2086
|
-
if (!pendingActions.didQuit) await printExitFooter({
|
|
2087
|
-
diagnostics: completedScan.diagnostics,
|
|
2088
|
-
scoreResult: completedScan.scoreResult,
|
|
2089
|
-
projectName: completedScan.projectName,
|
|
2090
|
-
scannedFileCount: completedScan.scannedFileCount,
|
|
2091
|
-
elapsedMilliseconds: completedScan.elapsedMilliseconds,
|
|
2092
|
-
isOffline: context.isOffline,
|
|
2093
|
-
lintFailureReason: resolveLintFailureReason(completedScan.scans.map(({ result }) => result))
|
|
2094
|
-
});
|
|
2095
|
-
await executePendingActions();
|
|
2096
|
-
return { shouldFail: shouldFailScanGate({
|
|
2097
|
-
scans: completedScan.scans,
|
|
2098
|
-
blockingLevel
|
|
2099
|
-
}) };
|
|
2100
|
-
} catch (error) {
|
|
2101
|
-
mountedRenderer.dispose();
|
|
2102
|
-
throw error;
|
|
2103
|
-
}
|
|
2104
|
-
};
|
|
2105
|
-
const runSingleProjectScan = async (rootScanTarget, projectDirectory, input, blockingLevel, inspectProject) => {
|
|
2106
|
-
const projectScan = await resolveProjectScan(rootScanTarget, projectDirectory);
|
|
2107
|
-
const presentation = resolveScanPresentation(input, [projectScan]);
|
|
2108
|
-
return runMountedScan(projectScan.directory, presentation, blockingLevel, async (context) => {
|
|
2109
|
-
const result = await inspectProject(projectScan.directory, {
|
|
2110
|
-
...resolveTuiInspectOptions(input, projectScan.config),
|
|
2111
|
-
isCi: isCiEnvironment(),
|
|
2112
|
-
configOverride: projectScan.config,
|
|
2113
|
-
configSourceDirectory: projectScan.configSourceDirectory ?? void 0,
|
|
2114
|
-
uiLayers: {
|
|
2115
|
-
reporter: reporterLayerForStore(context.store),
|
|
2116
|
-
progress: progressLayerForStore(context.store)
|
|
2117
|
-
}
|
|
2118
|
-
});
|
|
2119
|
-
const reportSelection = selectReportDiagnostics({
|
|
2120
|
-
scan: {
|
|
2121
|
-
result,
|
|
2122
|
-
config: projectScan.config
|
|
2123
|
-
},
|
|
2124
|
-
categoryFilters: input.options?.categoryFilters,
|
|
2125
|
-
surface: input.options?.outputSurface
|
|
2126
|
-
});
|
|
2127
|
-
const scoreDiagnostics = filterScansForSurface([{
|
|
2128
|
-
result,
|
|
2129
|
-
config: projectScan.config
|
|
2130
|
-
}], "score");
|
|
2131
|
-
const projectedScore = result.score ? await computeProjectedScore([...reportSelection.diagnostics], scoreDiagnostics, result.score) : null;
|
|
2132
|
-
context.store.setReport(toScanReport({
|
|
2133
|
-
result,
|
|
2134
|
-
diagnostics: reportSelection.diagnostics,
|
|
2135
|
-
rootDirectory: projectScan.directory,
|
|
2136
|
-
projectedScore,
|
|
2137
|
-
isOffline: context.isOffline,
|
|
2138
|
-
noScoreMessage: buildNoScoreMessage({
|
|
2139
|
-
isScoreDisabled: input.options?.noScore === true || projectScan.config?.noScore === true,
|
|
2140
|
-
isAnalysisIncomplete: hasIncompleteScoreAnalysis(result.skippedChecks),
|
|
2141
|
-
disabledMessage: input.options?.scoreDisabledMessage
|
|
2142
|
-
}),
|
|
2143
|
-
emptyStateMessage: resolveEmptyStateMessage(input, reportSelection.demotedDiagnosticCount)
|
|
2144
|
-
}));
|
|
2145
|
-
return {
|
|
2146
|
-
scans: [{
|
|
2147
|
-
result,
|
|
2148
|
-
config: projectScan.config
|
|
2149
|
-
}],
|
|
2150
|
-
diagnostics: reportSelection.diagnostics,
|
|
2151
|
-
scoreResult: result.score,
|
|
2152
|
-
projectName: result.project.projectName,
|
|
2153
|
-
scannedFileCount: result.scannedFileCount ?? 0,
|
|
2154
|
-
elapsedMilliseconds: result.elapsedMilliseconds
|
|
2155
|
-
};
|
|
2156
|
-
});
|
|
2157
|
-
};
|
|
2158
|
-
const runMultiProjectScan = async (rootScanTarget, directories, input, blockingLevel, inspectProject) => {
|
|
2159
|
-
const feedbackStartTime = performance.now();
|
|
2160
|
-
const rootDirectory = rootScanTarget.resolvedDirectory;
|
|
2161
|
-
const projectScans = deduplicateProjectScans(await mapWithConcurrency([...directories], 4, (projectDirectory) => resolveProjectScan(rootScanTarget, projectDirectory)));
|
|
2162
|
-
const projectCount = projectScans.length;
|
|
2163
|
-
const rootProjectScan = projectScans.find((projectScan) => path.resolve(projectScan.directory) === path.resolve(rootDirectory));
|
|
2164
|
-
const workspaceDeadCodeOwner = resolveWorkspaceDeadCodeOwner({
|
|
2165
|
-
rootDirectory,
|
|
2166
|
-
projectDirectories: projectScans.map((projectScan) => projectScan.directory),
|
|
2167
|
-
isRootDeadCodeEnabled: input.options?.deadCode ?? rootProjectScan?.config?.deadCode ?? true
|
|
2168
|
-
});
|
|
2169
|
-
if (workspaceDeadCodeOwner !== null) recordCount(METRIC.scanWorkspaceDeadCodeShared, 1, { projectCount });
|
|
2170
|
-
return runMountedScan(rootDirectory, resolveScanPresentation(input, projectScans), blockingLevel, async (context) => {
|
|
2171
|
-
const startTime = performance.now();
|
|
2172
|
-
let finishedCount = 0;
|
|
2173
|
-
recordDistribution(METRIC.scanFeedbackDelay, performance.now() - feedbackStartTime, {
|
|
2174
|
-
unit: "millisecond",
|
|
2175
|
-
attributes: {
|
|
2176
|
-
surface: "tui",
|
|
2177
|
-
projectCount
|
|
2178
|
-
}
|
|
2179
|
-
});
|
|
2180
|
-
context.store.setProgress(`Scanning ${projectCount} projects…`);
|
|
2181
|
-
await yieldToEventLoop();
|
|
2182
|
-
const precomputedSourceFileCounts = await collectProjectSourceFileCounts(rootDirectory, projectScans.map((projectScan) => projectScan.directory));
|
|
2183
|
-
const scanOutcomes = await mapWithConcurrency(projectScans, 4, async (projectScan) => {
|
|
2184
|
-
if (input.options?.deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(input.options.deadlineEpochMs) === 0) {
|
|
2185
|
-
finishedCount += 1;
|
|
2186
|
-
context.store.setProgress(`Scanning ${projectCount} projects… (${finishedCount}/${projectCount})`);
|
|
2187
|
-
return {
|
|
2188
|
-
status: "skipped",
|
|
2189
|
-
directory: projectScan.directory,
|
|
2190
|
-
reason: "max-duration"
|
|
2191
|
-
};
|
|
2192
|
-
}
|
|
2193
|
-
const projectLabel = path.relative(rootDirectory, projectScan.directory) || path.basename(rootDirectory);
|
|
2194
|
-
const formatProjectProgress = (displayText) => `Scanning ${projectCount} projects… (${finishedCount}/${projectCount}) · ${projectLabel}: ${displayText}`;
|
|
2195
|
-
const inspectOptions = resolveTuiInspectOptions(input, projectScan.config);
|
|
2196
|
-
const ownsWorkspaceDeadCode = projectScan.directory === workspaceDeadCodeOwner;
|
|
2197
|
-
const result = await inspectProject(projectScan.directory, {
|
|
2198
|
-
...inspectOptions,
|
|
2199
|
-
deadCode: workspaceDeadCodeOwner === null ? inspectOptions.deadCode : ownsWorkspaceDeadCode,
|
|
2200
|
-
isCi: isCiEnvironment(),
|
|
2201
|
-
configOverride: projectScan.config,
|
|
2202
|
-
configSourceDirectory: projectScan.configSourceDirectory ?? void 0,
|
|
2203
|
-
precomputedSourceFileCount: precomputedSourceFileCounts.get(projectScan.directory),
|
|
2204
|
-
uiLayers: {
|
|
2205
|
-
reporter: Reporter.layerNoop,
|
|
2206
|
-
progress: progressLayerForStore(context.store, {
|
|
2207
|
-
transformText: formatProjectProgress,
|
|
2208
|
-
shouldClearOnStop: false
|
|
2209
|
-
})
|
|
2210
|
-
},
|
|
2211
|
-
concurrentScan: true,
|
|
2212
|
-
excludedProjectDirectories: projectScans.filter((candidateProjectScan) => isPathInsideDirectory(candidateProjectScan.directory, projectScan.directory)).map((candidateProjectScan) => candidateProjectScan.directory),
|
|
2213
|
-
retainExcludedProjectDeadCodeDiagnostics: ownsWorkspaceDeadCode
|
|
2214
|
-
});
|
|
2215
|
-
finishedCount += 1;
|
|
2216
|
-
context.store.setProgress(`Scanning ${projectCount} projects… (${finishedCount}/${projectCount})`);
|
|
2217
|
-
await yieldToEventLoop();
|
|
2218
|
-
return {
|
|
2219
|
-
status: "completed",
|
|
2220
|
-
directory: projectScan.directory,
|
|
2221
|
-
result,
|
|
2222
|
-
config: projectScan.config
|
|
2223
|
-
};
|
|
2224
|
-
});
|
|
2225
|
-
const results = await retryMissingProjectScores(scanOutcomes.filter((scanOutcome) => scanOutcome.status === "completed").map((completedScan) => ({
|
|
2226
|
-
...completedScan,
|
|
2227
|
-
isScoreDisabled: input.options?.noScore ?? completedScan.config?.noScore ?? false
|
|
2228
|
-
})));
|
|
2229
|
-
const skippedProjects = scanOutcomes.filter((scanOutcome) => scanOutcome.status === "skipped").map(({ directory, reason }) => ({
|
|
2230
|
-
directory,
|
|
2231
|
-
reason
|
|
2232
|
-
})).sort((left, right) => left.directory.localeCompare(right.directory));
|
|
2233
|
-
if (skippedProjects.length > 0) recordCount(METRIC.scanProjectSkipped, skippedProjects.length, { reason: "max-duration" });
|
|
2234
|
-
const projectEntries = results.map(({ directory, result, config }) => {
|
|
2235
|
-
const reportSelection = selectReportDiagnostics({
|
|
2236
|
-
scan: {
|
|
2237
|
-
result,
|
|
2238
|
-
config
|
|
2239
|
-
},
|
|
2240
|
-
categoryFilters: input.options?.categoryFilters,
|
|
2241
|
-
surface: input.options?.outputSurface
|
|
2242
|
-
});
|
|
2243
|
-
return {
|
|
2244
|
-
report: toScanReport({
|
|
2245
|
-
result,
|
|
2246
|
-
diagnostics: reportSelection.diagnostics,
|
|
2247
|
-
rootDirectory: directory,
|
|
2248
|
-
projectedScore: null,
|
|
2249
|
-
isOffline: context.isOffline,
|
|
2250
|
-
noScoreMessage: buildNoScoreMessage({
|
|
2251
|
-
isScoreDisabled: input.options?.noScore === true || config?.noScore === true,
|
|
2252
|
-
isAnalysisIncomplete: hasIncompleteScoreAnalysis(result.skippedChecks),
|
|
2253
|
-
disabledMessage: input.options?.scoreDisabledMessage
|
|
2254
|
-
}),
|
|
2255
|
-
emptyStateMessage: resolveEmptyStateMessage(input, reportSelection.demotedDiagnosticCount)
|
|
2256
|
-
}),
|
|
2257
|
-
score: result.score,
|
|
2258
|
-
scoreDiagnostics: filterScansForSurface([{
|
|
2259
|
-
result,
|
|
2260
|
-
config
|
|
2261
|
-
}], "score"),
|
|
2262
|
-
demotedDiagnosticCount: reportSelection.demotedDiagnosticCount
|
|
2263
|
-
};
|
|
2264
|
-
});
|
|
2265
|
-
const projects = projectEntries.map(({ report }) => report);
|
|
2266
|
-
const combinedDiagnostics = projects.flatMap((project) => qualifyDiagnosticPaths(project.diagnostics, rootDirectory, project.rootDirectory));
|
|
2267
|
-
const lowestScoredReport = findLowestScoredReport(projectEntries);
|
|
2268
|
-
const projectedScore = lowestScoredReport ? await computeProjectedScore(combinedDiagnostics, [...lowestScoredReport.scoreDiagnostics], lowestScoredReport.score) : null;
|
|
2269
|
-
const scannedFileCount = countUniqueScannedFiles(results.map(({ result }) => result));
|
|
2270
|
-
const elapsedMilliseconds = performance.now() - startTime;
|
|
2271
|
-
const lintFailureReason = resolveLintFailureReason(results.map(({ result }) => result));
|
|
2272
|
-
const summary = {
|
|
2273
|
-
projects,
|
|
2274
|
-
skippedProjects,
|
|
2275
|
-
aggregateScore: lowestScoredReport?.score ?? null,
|
|
2276
|
-
projectedScore,
|
|
2277
|
-
combinedDiagnostics,
|
|
2278
|
-
scannedFileCount,
|
|
2279
|
-
elapsedMilliseconds,
|
|
2280
|
-
projectName: path.basename(rootDirectory),
|
|
2281
|
-
rootDirectory,
|
|
2282
|
-
isOffline: context.isOffline,
|
|
2283
|
-
noScoreMessage: buildNoScoreMessage({
|
|
2284
|
-
isScoreDisabled: input.options?.noScore === true || results.some(({ config }) => config?.noScore === true),
|
|
2285
|
-
isAnalysisIncomplete: results.some(({ result }) => hasIncompleteScoreAnalysis(result.skippedChecks)),
|
|
2286
|
-
disabledMessage: input.options?.scoreDisabledMessage
|
|
2287
|
-
}),
|
|
2288
|
-
emptyStateMessage: resolveEmptyStateMessage(input, projectEntries.reduce((total, projectEntry) => total + projectEntry.demotedDiagnosticCount, 0)),
|
|
2289
|
-
...lintFailureReason ? { lintFailureReason } : {}
|
|
2290
|
-
};
|
|
2291
|
-
context.store.setSummary(summary);
|
|
2292
|
-
return {
|
|
2293
|
-
scans: results,
|
|
2294
|
-
diagnostics: combinedDiagnostics,
|
|
2295
|
-
scoreResult: summary.aggregateScore,
|
|
2296
|
-
projectName: summary.projectName,
|
|
2297
|
-
scannedFileCount,
|
|
2298
|
-
elapsedMilliseconds
|
|
2299
|
-
};
|
|
2300
|
-
});
|
|
2301
|
-
};
|
|
2302
|
-
const runScanApp = async (input) => {
|
|
2303
|
-
const scanTarget = input.scanTarget ?? await resolveScanTarget(input.directory, { allowAmbiguous: true });
|
|
2304
|
-
const rootDirectory = scanTarget.resolvedDirectory;
|
|
2305
|
-
const deadlineEpochMs = input.options?.deadlineEpochMs ?? (input.options?.maxDurationMs != null ? Date.now() + input.options.maxDurationMs : void 0);
|
|
2306
|
-
const resolvedInput = {
|
|
2307
|
-
...input,
|
|
2308
|
-
options: {
|
|
2309
|
-
...input.options,
|
|
2310
|
-
deadlineEpochMs
|
|
2311
|
-
},
|
|
2312
|
-
configProjects: input.configProjects ?? scanTarget.userConfig?.projects,
|
|
2313
|
-
share: input.share ?? scanTarget.userConfig?.share ?? true
|
|
2314
|
-
};
|
|
2315
|
-
const selectedDirectories = await resolveSelectedDirectories(rootDirectory, resolvedInput);
|
|
2316
|
-
const inspectProject = createInvocationInspect(input.options?.concurrency);
|
|
2317
|
-
const blockingLevel = resolveBlockingLevel({ blocking: resolvedInput.blocking }, scanTarget.userConfig);
|
|
2318
|
-
if (selectedDirectories.length === 0) return { shouldFail: false };
|
|
2319
|
-
if (selectedDirectories.length === 1) return runSingleProjectScan(scanTarget, selectedDirectories[0], resolvedInput, blockingLevel, inspectProject);
|
|
2320
|
-
return runMultiProjectScan(scanTarget, selectedDirectories, resolvedInput, blockingLevel, inspectProject);
|
|
2321
|
-
};
|
|
2322
|
-
//#endregion
|
|
2323
|
-
export { runScanApp };
|
|
2324
|
-
|
|
2325
|
-
//# sourceMappingURL=run-scan-app-QbtG43NL.js.map
|
|
2326
|
-
//# debugId=536bbbaf-ba97-5df5-b678-926826008e2e
|