react-doctor 0.9.3-dev.3a93a34 → 0.9.3-dev.3acb41b
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/dist/cli.js +794 -223
- package/dist/index.d.ts +9 -2
- package/dist/index.js +478 -146
- package/dist/lsp.js +487 -151
- package/dist/{run-scan-app-DtYeufgr.js → run-scan-app-QbtG43NL.js} +243 -112
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
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]="
|
|
3
|
-
import { $ as
|
|
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
4
|
import path from "node:path";
|
|
5
5
|
import { performance } from "node:perf_hooks";
|
|
6
6
|
import { getSkillAgentConfig } from "agent-install";
|
|
@@ -40,11 +40,9 @@ const resolveVisibleStart = ({ itemCount, offset, selectedIndex, viewportHeight
|
|
|
40
40
|
//#region src/cli/ink/hooks/use-exit-on-ctrl-c.ts
|
|
41
41
|
const SHOW_CURSOR = "\x1B[?25h";
|
|
42
42
|
const useExitOnCtrlC = () => {
|
|
43
|
-
const { exit } = useApp();
|
|
44
43
|
useInput((input, key) => {
|
|
45
44
|
if (key.ctrl && input === "c") {
|
|
46
45
|
recordCount(METRIC.tuiCancelled, 1, { resourceFooter: true });
|
|
47
|
-
exit();
|
|
48
46
|
process.stdin.setRawMode?.(false);
|
|
49
47
|
process.stdout.write(SHOW_CURSOR);
|
|
50
48
|
exitGracefully();
|
|
@@ -1178,9 +1176,10 @@ const HandoffCiRecommendation = ({ onAddToCi, onContinue, onQuit }) => {
|
|
|
1178
1176
|
};
|
|
1179
1177
|
//#endregion
|
|
1180
1178
|
//#region src/cli/ink/components/report-landing.tsx
|
|
1181
|
-
const ReportLanding = ({ header, phase, issueCount, emptyStateMessage, lintFailureReason, skippedChecks, actions, selectedIndex, onSelectionChange, onQuit }) => {
|
|
1179
|
+
const ReportLanding = ({ header, phase, issueCount, emptyStateMessage, lintFailureReason, skippedChecks, incompleteMessage, actions, selectedIndex, onSelectionChange, onQuit }) => {
|
|
1182
1180
|
const showScore = phase === "actions" || phase === "score";
|
|
1183
|
-
const skippedCheckLabel = skippedChecks?.join(" and ");
|
|
1181
|
+
const skippedCheckLabel = skippedChecks?.filter((skippedCheck) => skippedCheck !== "lint" || !lintFailureReason).join(" and ");
|
|
1182
|
+
const hasIncompleteResult = Boolean(incompleteMessage || lintFailureReason || skippedCheckLabel);
|
|
1184
1183
|
useInput((input) => {
|
|
1185
1184
|
if (input === "q") onQuit();
|
|
1186
1185
|
}, { isActive: phase !== "actions" });
|
|
@@ -1188,22 +1187,32 @@ const ReportLanding = ({ header, phase, issueCount, emptyStateMessage, lintFailu
|
|
|
1188
1187
|
flexDirection: "column",
|
|
1189
1188
|
children: [
|
|
1190
1189
|
showScore ? header : null,
|
|
1191
|
-
issueCount === 0 ? /* @__PURE__ */
|
|
1190
|
+
hasIncompleteResult || issueCount === 0 ? /* @__PURE__ */ jsxs(Box, {
|
|
1191
|
+
flexDirection: "column",
|
|
1192
1192
|
marginTop: 1,
|
|
1193
|
-
children:
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
+
]
|
|
1207
1216
|
}) : null,
|
|
1208
1217
|
phase === "actions" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ActionMenu, {
|
|
1209
1218
|
actions,
|
|
@@ -1357,12 +1366,13 @@ const ScoreHeader = ({ variant, score, projectedScore, projectName, issueCount,
|
|
|
1357
1366
|
if (!score) return /* @__PURE__ */ jsxs(Box, {
|
|
1358
1367
|
flexDirection: "column",
|
|
1359
1368
|
paddingLeft: 2,
|
|
1369
|
+
width: availableWidth,
|
|
1360
1370
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
1361
1371
|
wrap: "truncate-end",
|
|
1362
1372
|
children: /* @__PURE__ */ jsx(ReactDoctorLink, {})
|
|
1363
1373
|
}), /* @__PURE__ */ jsx(Text, {
|
|
1364
1374
|
dimColor: true,
|
|
1365
|
-
wrap: "
|
|
1375
|
+
wrap: "wrap",
|
|
1366
1376
|
children: noScoreMessage ?? `${pluralize(issueCount, "finding")} · ${projectName}`
|
|
1367
1377
|
})]
|
|
1368
1378
|
});
|
|
@@ -1648,6 +1658,7 @@ const Report = ({ report, onExit, onQuit = onExit, launchableAgents = EMPTY_LAUN
|
|
|
1648
1658
|
emptyStateMessage: report.emptyStateMessage,
|
|
1649
1659
|
lintFailureReason: report.lintFailureReason,
|
|
1650
1660
|
skippedChecks: report.skippedChecks,
|
|
1661
|
+
incompleteMessage: report.incompleteMessage,
|
|
1651
1662
|
actions: landingActions,
|
|
1652
1663
|
selectedIndex: resolvedLandingSelectedIndex,
|
|
1653
1664
|
onSelectionChange: setLandingSelectedIndex,
|
|
@@ -1683,26 +1694,15 @@ const Report = ({ report, onExit, onQuit = onExit, launchableAgents = EMPTY_LAUN
|
|
|
1683
1694
|
};
|
|
1684
1695
|
//#endregion
|
|
1685
1696
|
//#region src/cli/ink/components/scanning.tsx
|
|
1686
|
-
const Scanning = ({ progressText,
|
|
1697
|
+
const Scanning = ({ progressText, recent }) => {
|
|
1687
1698
|
return /* @__PURE__ */ jsxs(Box, {
|
|
1688
1699
|
flexDirection: "column",
|
|
1689
1700
|
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1690
1701
|
wrap: "truncate-end",
|
|
1691
|
-
children: [
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
}),
|
|
1696
|
-
/* @__PURE__ */ jsxs(Text, { children: [" ", progressText ?? "Scanning…"] }),
|
|
1697
|
-
liveCount > 0 ? /* @__PURE__ */ jsxs(Text, {
|
|
1698
|
-
dimColor: true,
|
|
1699
|
-
children: [
|
|
1700
|
-
" · ",
|
|
1701
|
-
liveCount,
|
|
1702
|
-
" found"
|
|
1703
|
-
]
|
|
1704
|
-
}) : null
|
|
1705
|
-
]
|
|
1702
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1703
|
+
color: "cyan",
|
|
1704
|
+
children: /* @__PURE__ */ jsx(Spinner, { type: "dots" })
|
|
1705
|
+
}), /* @__PURE__ */ jsxs(Text, { children: [" ", progressText ?? "Scanning…"] })]
|
|
1706
1706
|
}), recent.map((diagnostic, diagnosticIndex) => {
|
|
1707
1707
|
const variant = severityVariant(diagnostic.severity === "error" ? "error" : "warning");
|
|
1708
1708
|
const location = formatDiagnosticSite(diagnostic);
|
|
@@ -1727,6 +1727,7 @@ const Scanning = ({ progressText, liveCount, recent }) => {
|
|
|
1727
1727
|
//#endregion
|
|
1728
1728
|
//#region src/cli/ink/components/summary.tsx
|
|
1729
1729
|
const Summary = ({ summary, onExit, onQuit, launchableAgents, onHandoff, canAddToCi, onAddToCi }) => {
|
|
1730
|
+
const skippedProjects = summary.skippedProjects ?? [];
|
|
1730
1731
|
return /* @__PURE__ */ jsx(Report, {
|
|
1731
1732
|
report: {
|
|
1732
1733
|
diagnostics: summary.combinedDiagnostics,
|
|
@@ -1739,6 +1740,7 @@ const Summary = ({ summary, onExit, onQuit, launchableAgents, onHandoff, canAddT
|
|
|
1739
1740
|
isOffline: summary.isOffline,
|
|
1740
1741
|
noScoreMessage: summary.noScoreMessage,
|
|
1741
1742
|
skippedChecks: [...new Set(summary.projects.flatMap((project) => project.skippedChecks ?? []))],
|
|
1743
|
+
...skippedProjects.length > 0 ? { incompleteMessage: formatSkippedProjectsMessage(skippedProjects.length) } : {},
|
|
1742
1744
|
...summary.emptyStateMessage ? { emptyStateMessage: summary.emptyStateMessage } : {},
|
|
1743
1745
|
...summary.lintFailureReason ? { lintFailureReason: summary.lintFailureReason } : {}
|
|
1744
1746
|
},
|
|
@@ -1748,7 +1750,7 @@ const Summary = ({ summary, onExit, onQuit, launchableAgents, onHandoff, canAddT
|
|
|
1748
1750
|
onHandoff,
|
|
1749
1751
|
canAddToCi,
|
|
1750
1752
|
onAddToCi,
|
|
1751
|
-
projectCount: summary.projects.length,
|
|
1753
|
+
projectCount: summary.projects.length + skippedProjects.length,
|
|
1752
1754
|
priorityScores: summary.projects.map((project) => project.score)
|
|
1753
1755
|
});
|
|
1754
1756
|
};
|
|
@@ -1757,7 +1759,7 @@ const Summary = ({ summary, onExit, onQuit, launchableAgents, onHandoff, canAddT
|
|
|
1757
1759
|
const useScanStore = (store) => useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
1758
1760
|
//#endregion
|
|
1759
1761
|
//#region src/cli/ink/scan-app.tsx
|
|
1760
|
-
const ScanApp = ({ store, launchableAgents, onHandoff, canAddToCi, onAddToCi, onQuit }) => {
|
|
1762
|
+
const ScanApp = ({ store, displayMode = "report", launchableAgents, onHandoff, canAddToCi, onAddToCi, onQuit }) => {
|
|
1761
1763
|
const snapshot = useScanStore(store);
|
|
1762
1764
|
const { exit } = useApp();
|
|
1763
1765
|
useExitOnCtrlC();
|
|
@@ -1765,7 +1767,7 @@ const ScanApp = ({ store, launchableAgents, onHandoff, canAddToCi, onAddToCi, on
|
|
|
1765
1767
|
onQuit?.();
|
|
1766
1768
|
exit();
|
|
1767
1769
|
};
|
|
1768
|
-
if (snapshot.phase === "summary" && snapshot.summary) return /* @__PURE__ */ jsx(Summary, {
|
|
1770
|
+
if (displayMode === "report" && snapshot.phase === "summary" && snapshot.summary) return /* @__PURE__ */ jsx(Summary, {
|
|
1769
1771
|
summary: snapshot.summary,
|
|
1770
1772
|
launchableAgents,
|
|
1771
1773
|
onHandoff,
|
|
@@ -1774,7 +1776,7 @@ const ScanApp = ({ store, launchableAgents, onHandoff, canAddToCi, onAddToCi, on
|
|
|
1774
1776
|
onExit: exit,
|
|
1775
1777
|
onQuit: handleQuit
|
|
1776
1778
|
});
|
|
1777
|
-
if (snapshot.phase === "report" && snapshot.report) return /* @__PURE__ */ jsx(Report, {
|
|
1779
|
+
if (displayMode === "report" && snapshot.phase === "report" && snapshot.report) return /* @__PURE__ */ jsx(Report, {
|
|
1778
1780
|
report: snapshot.report,
|
|
1779
1781
|
launchableAgents,
|
|
1780
1782
|
onHandoff,
|
|
@@ -1785,7 +1787,6 @@ const ScanApp = ({ store, launchableAgents, onHandoff, canAddToCi, onAddToCi, on
|
|
|
1785
1787
|
});
|
|
1786
1788
|
return /* @__PURE__ */ jsx(Scanning, {
|
|
1787
1789
|
progressText: snapshot.progress,
|
|
1788
|
-
liveCount: snapshot.liveCount,
|
|
1789
1790
|
recent: snapshot.liveDiagnostics.slice(-5)
|
|
1790
1791
|
});
|
|
1791
1792
|
};
|
|
@@ -1795,22 +1796,22 @@ const reporterLayerForStore = (store) => succeed(Reporter, Reporter.of({
|
|
|
1795
1796
|
emit: (diagnostic) => sync(() => store.emitDiagnostic(diagnostic)),
|
|
1796
1797
|
finalize: void_
|
|
1797
1798
|
}));
|
|
1798
|
-
const progressHandleForStore = (store) => (text) => {
|
|
1799
|
-
|
|
1799
|
+
const progressHandleForStore = (store, options = {}) => (text) => {
|
|
1800
|
+
const transformText = options.transformText ?? ((displayText) => displayText);
|
|
1801
|
+
store.setProgress(transformText(text));
|
|
1800
1802
|
return {
|
|
1801
|
-
update: (displayText) => sync(() => store.
|
|
1802
|
-
succeed: (displayText) => sync(() => store.setProgress(displayText)),
|
|
1803
|
-
fail: (displayText) => sync(() => store.setProgress(displayText)),
|
|
1804
|
-
stop: () => sync(() => store.setProgress(null))
|
|
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))
|
|
1805
1807
|
};
|
|
1806
1808
|
};
|
|
1807
|
-
const progressLayerForStore = (store) => Progress.layerOra(progressHandleForStore(store));
|
|
1809
|
+
const progressLayerForStore = (store, options = {}) => Progress.layerOra(progressHandleForStore(store, options));
|
|
1808
1810
|
//#endregion
|
|
1809
1811
|
//#region src/cli/ink/scan-store.ts
|
|
1810
1812
|
const INITIAL_SNAPSHOT = {
|
|
1811
1813
|
phase: "scanning",
|
|
1812
1814
|
liveDiagnostics: [],
|
|
1813
|
-
liveCount: 0,
|
|
1814
1815
|
progress: null,
|
|
1815
1816
|
report: null,
|
|
1816
1817
|
summary: null
|
|
@@ -1818,10 +1819,25 @@ const INITIAL_SNAPSHOT = {
|
|
|
1818
1819
|
const createScanStore = () => {
|
|
1819
1820
|
let snapshot = INITIAL_SNAPSHOT;
|
|
1820
1821
|
const listeners = /* @__PURE__ */ new Set();
|
|
1822
|
+
let pendingProgress = null;
|
|
1823
|
+
let progressTimer = null;
|
|
1821
1824
|
const commit = (next) => {
|
|
1822
1825
|
snapshot = next;
|
|
1823
1826
|
for (const listener of listeners) listener();
|
|
1824
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
|
+
};
|
|
1825
1841
|
return {
|
|
1826
1842
|
subscribe: (listener) => {
|
|
1827
1843
|
listeners.add(listener);
|
|
@@ -1832,37 +1848,43 @@ const createScanStore = () => {
|
|
|
1832
1848
|
getSnapshot: () => snapshot,
|
|
1833
1849
|
emitDiagnostic: (diagnostic) => commit({
|
|
1834
1850
|
...snapshot,
|
|
1835
|
-
liveDiagnostics: [...snapshot.liveDiagnostics, diagnostic].slice(-25)
|
|
1836
|
-
liveCount: snapshot.liveCount + 1
|
|
1851
|
+
liveDiagnostics: [...snapshot.liveDiagnostics, diagnostic].slice(-25)
|
|
1837
1852
|
}),
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
}
|
|
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
|
+
}
|
|
1852
1884
|
};
|
|
1853
1885
|
};
|
|
1854
1886
|
//#endregion
|
|
1855
1887
|
//#region src/cli/ink/run-scan-app.tsx
|
|
1856
|
-
const resolveProjectScan = async (rootScanTarget, projectDirectory) => {
|
|
1857
|
-
const projectScanTarget = projectDirectory === rootScanTarget.resolvedDirectory ? rootScanTarget : await resolveScanTarget(projectDirectory, { allowAmbiguous: true });
|
|
1858
|
-
const config = projectDirectory === rootScanTarget.resolvedDirectory ? rootScanTarget.userConfig : mergeReactDoctorConfigs(rootScanTarget.userConfig, projectScanTarget.userConfig ?? void 0);
|
|
1859
|
-
const configSourceDirectory = projectScanTarget.userConfig?.plugins === void 0 ? rootScanTarget.configSourceDirectory : projectScanTarget.configSourceDirectory;
|
|
1860
|
-
return {
|
|
1861
|
-
directory: projectScanTarget.resolvedDirectory,
|
|
1862
|
-
config,
|
|
1863
|
-
configSourceDirectory
|
|
1864
|
-
};
|
|
1865
|
-
};
|
|
1866
1888
|
const qualifyDiagnosticPaths = (diagnostics, rootDirectory, projectDirectory) => {
|
|
1867
1889
|
const prefix = path.relative(rootDirectory, projectDirectory);
|
|
1868
1890
|
if (prefix === "" || prefix.startsWith("..")) return [...diagnostics];
|
|
@@ -1875,7 +1897,8 @@ const resolveScanPresentation = (input, projectScans) => {
|
|
|
1875
1897
|
const isScoreDisabled = input.options?.noScore === true || projectScans.some((projectScan) => projectScan.config?.noScore === true);
|
|
1876
1898
|
return {
|
|
1877
1899
|
isOffline: isCiEnvironment() || input.share === false || isShareOptedOut(projectScans, input.options?.noScore),
|
|
1878
|
-
|
|
1900
|
+
initialProgress: projectScans.length > 1 ? "Indexing workspace files…" : "Scanning project…",
|
|
1901
|
+
noScoreMessage: buildNoScoreMessage({ isScoreDisabled }),
|
|
1879
1902
|
shouldRecommendCi: projectScans.length > 1 || projectScans.some((projectScan) => isCiUnconfigured(projectScan.directory))
|
|
1880
1903
|
};
|
|
1881
1904
|
};
|
|
@@ -1895,15 +1918,31 @@ const resolveSelectedDirectories = async (rootDirectory, input) => {
|
|
|
1895
1918
|
return promptProjectSelection(packages, rootDirectory);
|
|
1896
1919
|
};
|
|
1897
1920
|
const promptProjectSelection = (packages, rootDirectory) => new Promise((resolve) => {
|
|
1921
|
+
let disposeRenderer = () => {};
|
|
1922
|
+
recordCount(METRIC.tuiProjectSelectInlineShown);
|
|
1898
1923
|
const instance = render(/* @__PURE__ */ jsx(ProjectSelect, {
|
|
1899
1924
|
packages,
|
|
1900
1925
|
rootDirectory,
|
|
1901
1926
|
onSubmit: (directories) => {
|
|
1902
|
-
|
|
1903
|
-
instance.unmount();
|
|
1927
|
+
disposeRenderer();
|
|
1904
1928
|
resolve(directories);
|
|
1905
1929
|
}
|
|
1906
|
-
}), {
|
|
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
|
+
};
|
|
1907
1946
|
});
|
|
1908
1947
|
const resolveLintFailureReason = (results) => {
|
|
1909
1948
|
for (const result of results) {
|
|
@@ -1974,28 +2013,50 @@ const performCiSetup = async (rootDirectory) => {
|
|
|
1974
2013
|
created: didCreateWorkflow
|
|
1975
2014
|
});
|
|
1976
2015
|
};
|
|
1977
|
-
const mountScanApp = async (rootDirectory, shouldRecommendCi) => {
|
|
2016
|
+
const mountScanApp = async (rootDirectory, shouldRecommendCi, initialProgress) => {
|
|
1978
2017
|
const store = createScanStore();
|
|
2018
|
+
store.setProgress(initialProgress);
|
|
1979
2019
|
const launchableAgents = await detectLaunchableAgents();
|
|
1980
2020
|
const pendingActions = {
|
|
1981
2021
|
handoffRequest: null,
|
|
1982
2022
|
shouldSetUpCi: false,
|
|
1983
2023
|
didQuit: false
|
|
1984
2024
|
};
|
|
1985
|
-
const
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
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
|
+
};
|
|
1999
2060
|
const executePendingActions = async () => {
|
|
2000
2061
|
if (pendingActions.shouldSetUpCi) await performCiSetup(rootDirectory);
|
|
2001
2062
|
if (pendingActions.didQuit) return;
|
|
@@ -2003,20 +2064,25 @@ const mountScanApp = async (rootDirectory, shouldRecommendCi) => {
|
|
|
2003
2064
|
};
|
|
2004
2065
|
return {
|
|
2005
2066
|
store,
|
|
2006
|
-
instance,
|
|
2007
2067
|
pendingActions,
|
|
2068
|
+
mountRenderer,
|
|
2008
2069
|
executePendingActions
|
|
2009
2070
|
};
|
|
2010
2071
|
};
|
|
2011
2072
|
const runMountedScan = async (rootDirectory, presentation, blockingLevel, executeScan) => {
|
|
2012
|
-
const { store,
|
|
2073
|
+
const { store, pendingActions, mountRenderer, executePendingActions } = await mountScanApp(rootDirectory, presentation.shouldRecommendCi, presentation.initialProgress);
|
|
2074
|
+
let mountedRenderer = mountRenderer("scan");
|
|
2075
|
+
recordCount(METRIC.tuiScanInlineShown);
|
|
2013
2076
|
const context = {
|
|
2014
2077
|
store,
|
|
2015
2078
|
...presentation
|
|
2016
2079
|
};
|
|
2017
2080
|
try {
|
|
2018
2081
|
const completedScan = await executeScan(context);
|
|
2019
|
-
|
|
2082
|
+
mountedRenderer.dispose();
|
|
2083
|
+
mountedRenderer = mountRenderer("report");
|
|
2084
|
+
await mountedRenderer.instance.waitUntilExit();
|
|
2085
|
+
mountedRenderer.dispose();
|
|
2020
2086
|
if (!pendingActions.didQuit) await printExitFooter({
|
|
2021
2087
|
diagnostics: completedScan.diagnostics,
|
|
2022
2088
|
scoreResult: completedScan.scoreResult,
|
|
@@ -2032,7 +2098,7 @@ const runMountedScan = async (rootDirectory, presentation, blockingLevel, execut
|
|
|
2032
2098
|
blockingLevel
|
|
2033
2099
|
}) };
|
|
2034
2100
|
} catch (error) {
|
|
2035
|
-
|
|
2101
|
+
mountedRenderer.dispose();
|
|
2036
2102
|
throw error;
|
|
2037
2103
|
}
|
|
2038
2104
|
};
|
|
@@ -2069,7 +2135,11 @@ const runSingleProjectScan = async (rootScanTarget, projectDirectory, input, blo
|
|
|
2069
2135
|
rootDirectory: projectScan.directory,
|
|
2070
2136
|
projectedScore,
|
|
2071
2137
|
isOffline: context.isOffline,
|
|
2072
|
-
noScoreMessage:
|
|
2138
|
+
noScoreMessage: buildNoScoreMessage({
|
|
2139
|
+
isScoreDisabled: input.options?.noScore === true || projectScan.config?.noScore === true,
|
|
2140
|
+
isAnalysisIncomplete: hasIncompleteScoreAnalysis(result.skippedChecks),
|
|
2141
|
+
disabledMessage: input.options?.scoreDisabledMessage
|
|
2142
|
+
}),
|
|
2073
2143
|
emptyStateMessage: resolveEmptyStateMessage(input, reportSelection.demotedDiagnosticCount)
|
|
2074
2144
|
}));
|
|
2075
2145
|
return {
|
|
@@ -2086,29 +2156,81 @@ const runSingleProjectScan = async (rootScanTarget, projectDirectory, input, blo
|
|
|
2086
2156
|
});
|
|
2087
2157
|
};
|
|
2088
2158
|
const runMultiProjectScan = async (rootScanTarget, directories, input, blockingLevel, inspectProject) => {
|
|
2159
|
+
const feedbackStartTime = performance.now();
|
|
2089
2160
|
const rootDirectory = rootScanTarget.resolvedDirectory;
|
|
2090
|
-
const projectScans = await mapWithConcurrency([...directories], 4, (projectDirectory) => resolveProjectScan(rootScanTarget, projectDirectory));
|
|
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 });
|
|
2091
2170
|
return runMountedScan(rootDirectory, resolveScanPresentation(input, projectScans), blockingLevel, async (context) => {
|
|
2092
2171
|
const startTime = performance.now();
|
|
2093
2172
|
let finishedCount = 0;
|
|
2094
|
-
|
|
2095
|
-
|
|
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;
|
|
2096
2197
|
const result = await inspectProject(projectScan.directory, {
|
|
2097
|
-
...
|
|
2198
|
+
...inspectOptions,
|
|
2199
|
+
deadCode: workspaceDeadCodeOwner === null ? inspectOptions.deadCode : ownsWorkspaceDeadCode,
|
|
2098
2200
|
isCi: isCiEnvironment(),
|
|
2099
2201
|
configOverride: projectScan.config,
|
|
2100
2202
|
configSourceDirectory: projectScan.configSourceDirectory ?? void 0,
|
|
2101
|
-
|
|
2102
|
-
|
|
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
|
|
2103
2214
|
});
|
|
2104
2215
|
finishedCount += 1;
|
|
2105
|
-
context.store.setProgress(`Scanning ${
|
|
2216
|
+
context.store.setProgress(`Scanning ${projectCount} projects… (${finishedCount}/${projectCount})`);
|
|
2217
|
+
await yieldToEventLoop();
|
|
2106
2218
|
return {
|
|
2219
|
+
status: "completed",
|
|
2107
2220
|
directory: projectScan.directory,
|
|
2108
2221
|
result,
|
|
2109
2222
|
config: projectScan.config
|
|
2110
2223
|
};
|
|
2111
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" });
|
|
2112
2234
|
const projectEntries = results.map(({ directory, result, config }) => {
|
|
2113
2235
|
const reportSelection = selectReportDiagnostics({
|
|
2114
2236
|
scan: {
|
|
@@ -2125,7 +2247,11 @@ const runMultiProjectScan = async (rootScanTarget, directories, input, blockingL
|
|
|
2125
2247
|
rootDirectory: directory,
|
|
2126
2248
|
projectedScore: null,
|
|
2127
2249
|
isOffline: context.isOffline,
|
|
2128
|
-
noScoreMessage:
|
|
2250
|
+
noScoreMessage: buildNoScoreMessage({
|
|
2251
|
+
isScoreDisabled: input.options?.noScore === true || config?.noScore === true,
|
|
2252
|
+
isAnalysisIncomplete: hasIncompleteScoreAnalysis(result.skippedChecks),
|
|
2253
|
+
disabledMessage: input.options?.scoreDisabledMessage
|
|
2254
|
+
}),
|
|
2129
2255
|
emptyStateMessage: resolveEmptyStateMessage(input, reportSelection.demotedDiagnosticCount)
|
|
2130
2256
|
}),
|
|
2131
2257
|
score: result.score,
|
|
@@ -2145,6 +2271,7 @@ const runMultiProjectScan = async (rootScanTarget, directories, input, blockingL
|
|
|
2145
2271
|
const lintFailureReason = resolveLintFailureReason(results.map(({ result }) => result));
|
|
2146
2272
|
const summary = {
|
|
2147
2273
|
projects,
|
|
2274
|
+
skippedProjects,
|
|
2148
2275
|
aggregateScore: lowestScoredReport?.score ?? null,
|
|
2149
2276
|
projectedScore,
|
|
2150
2277
|
combinedDiagnostics,
|
|
@@ -2153,7 +2280,11 @@ const runMultiProjectScan = async (rootScanTarget, directories, input, blockingL
|
|
|
2153
2280
|
projectName: path.basename(rootDirectory),
|
|
2154
2281
|
rootDirectory,
|
|
2155
2282
|
isOffline: context.isOffline,
|
|
2156
|
-
noScoreMessage:
|
|
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
|
+
}),
|
|
2157
2288
|
emptyStateMessage: resolveEmptyStateMessage(input, projectEntries.reduce((total, projectEntry) => total + projectEntry.demotedDiagnosticCount, 0)),
|
|
2158
2289
|
...lintFailureReason ? { lintFailureReason } : {}
|
|
2159
2290
|
};
|
|
@@ -2191,5 +2322,5 @@ const runScanApp = async (input) => {
|
|
|
2191
2322
|
//#endregion
|
|
2192
2323
|
export { runScanApp };
|
|
2193
2324
|
|
|
2194
|
-
//# sourceMappingURL=run-scan-app-
|
|
2195
|
-
//# debugId=
|
|
2325
|
+
//# sourceMappingURL=run-scan-app-QbtG43NL.js.map
|
|
2326
|
+
//# debugId=536bbbaf-ba97-5df5-b678-926826008e2e
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.9.3-dev.
|
|
3
|
+
"version": "0.9.3-dev.3acb41b",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"vscode-uri": "^3.1.0",
|
|
74
74
|
"yaml": "^2.9.0",
|
|
75
75
|
"deslop-js": "0.9.3",
|
|
76
|
-
"oxlint-plugin-react-doctor": "0.9.3-dev.
|
|
76
|
+
"oxlint-plugin-react-doctor": "0.9.3-dev.3acb41b"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@types/babel__code-frame": "^7.27.0",
|