react-doctor 0.8.3-dev.76263ec → 0.8.3-dev.9155bc0

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 CHANGED
@@ -1,5 +1,5 @@
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]="81e6305d-aa88-52f6-82ff-ac652d90107a")}catch(e){}}();
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]="be7f877d-9463-5212-8e1f-dc5feea97da0")}catch(e){}}();
3
3
  import { createRequire } from "node:module";
4
4
  import * as NodeChildProcess from "node:child_process";
5
5
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
@@ -64964,6 +64964,14 @@ const DIAGNOSTIC_CATEGORY_BUCKETS = [
64964
64964
  "Maintainability"
64965
64965
  ];
64966
64966
  const OCCURRENCE_MATCHED_CATEGORIES = new Set(["Accessibility"]);
64967
+ const CATEGORY_IMPACT_BY_NAME = new Map(Object.entries({
64968
+ Security: "An attacker can read user data, act as your users, or run code you never shipped.",
64969
+ Bugs: "Real users hit crashes, wrong output, or state that silently corrupts.",
64970
+ Performance: "Users feel extra latency and wasted renders on every interaction.",
64971
+ Accessibility: "Users on screen readers, keyboards, or assistive tech get locked out.",
64972
+ Maintainability: "Every future change gets slower and riskier to make."
64973
+ }));
64974
+ const getCategoryImpact = (category) => CATEGORY_IMPACT_BY_NAME.get(category);
64967
64975
  const APP_ONLY_RULE_KEYS = new Set([
64968
64976
  "react-hooks-js/static-components",
64969
64977
  "react-doctor/no-render-prop-children",
@@ -77309,6 +77317,7 @@ const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
77309
77317
  const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
77310
77318
  const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
77311
77319
  const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
77320
+ const TUI_REPORT_DETAIL_WIDTH_FRACTION = .6;
77312
77321
  const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
77313
77322
  const WELCOME_EXPLANATION_HOLD_MS = 1e3;
77314
77323
  const CATEGORY_COUNTUP_SETTLE_HOLD_MS = 1e3;
@@ -77400,7 +77409,7 @@ const makeNoopConsole = () => ({
77400
77409
  });
77401
77410
  //#endregion
77402
77411
  //#region src/cli/utils/version.ts
77403
- const VERSION = "0.8.3-dev.76263ec";
77412
+ const VERSION = "0.8.3-dev.9155bc0";
77404
77413
  //#endregion
77405
77414
  //#region src/cli/utils/json-mode.ts
77406
77415
  let context = null;
@@ -77758,13 +77767,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
77758
77767
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
77759
77768
  * standard `SENTRY_RELEASE` override.
77760
77769
  */
77761
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.3-dev.76263ec`;
77770
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.3-dev.9155bc0`;
77762
77771
  /**
77763
77772
  * Deployment environment shown in Sentry's environment filter. Defaults to
77764
77773
  * `production` for tagged releases and `development` for dev/unbuilt versions,
77765
77774
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
77766
77775
  */
77767
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.3-dev.76263ec") ? "development" : "production");
77776
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.3-dev.9155bc0") ? "development" : "production");
77768
77777
  /**
77769
77778
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
77770
77779
  * (set to `0` to disable tracing) and falls back to
@@ -89611,12 +89620,13 @@ const buildRuntimeLayers = (input) => {
89611
89620
  const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
89612
89621
  const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
89613
89622
  const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
89614
- const progressLayer = input.shouldShowProgressSpinners ? Progress.layerOra(buildSpinnerProgressHandle) : Progress.layerNoop;
89623
+ const progressLayer = input.progressLayer ?? (input.shouldShowProgressSpinners ? Progress.layerOra(buildSpinnerProgressHandle) : Progress.layerNoop);
89624
+ const reporterLayer = input.reporterLayer ?? Reporter.layerNoop;
89615
89625
  const baseLayers = mergeAll$1(projectLayer, input.hasConfigOverride ? Config.layerOf({
89616
89626
  config: input.userConfig,
89617
89627
  resolvedDirectory: input.directory,
89618
89628
  configSourceDirectory: input.configSourceDirectory
89619
- }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, deadCodeLayer, progressLayer, Reporter.layerNoop, scoreLayer, supplyChainLayer);
89629
+ }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, deadCodeLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
89620
89630
  return input.oxlintConcurrency === void 0 ? baseLayers : mergeAll$1(baseLayers, succeed$3(OxlintConcurrency, input.oxlintConcurrency));
89621
89631
  };
89622
89632
  //#endregion
@@ -91451,6 +91461,9 @@ const supportsHyperlinks = (stream = process.stdout, env = process.env) => {
91451
91461
  * would choke on the escape sequences).
91452
91462
  */
91453
91463
  const shouldRenderHyperlinks = (stream = process.stdout) => supportsHyperlinks(stream) && !isCodingAgentEnvironment();
91464
+ //#endregion
91465
+ //#region src/cli/utils/should-show-share-link.ts
91466
+ const shouldShowShareLink = ({ noScore, share, isCi }) => !noScore && share && !isCi;
91454
91467
  const FORCE_ONBOARDING_ENV_VAR = "REACT_DOCTOR_FORCE_ONBOARDING";
91455
91468
  const FALSY_FLAG_VALUES = new Set([
91456
91469
  "",
@@ -91669,6 +91682,13 @@ const colorizeByScore = (text, score) => {
91669
91682
  return highlighter.error(text);
91670
91683
  };
91671
91684
  //#endregion
91685
+ //#region src/cli/utils/doctor-face.ts
91686
+ const doctorFace = (score) => {
91687
+ if (score >= 75) return ["◠ ◠", " ▽ "];
91688
+ if (score >= 50) return ["• •", " ─ "];
91689
+ return ["x x", " ▽ "];
91690
+ };
91691
+ //#endregion
91672
91692
  //#region src/cli/utils/ease-out-cubic.ts
91673
91693
  const easeOutCubic = (progress) => 1 - (1 - progress) ** 3;
91674
91694
  //#endregion
@@ -91734,15 +91754,10 @@ const buildProjectedScoreBar = (currentScore, potentialScore) => {
91734
91754
  const emptyCount = Math.max(0, barWidth - currentFill - gainCount);
91735
91755
  return colorizeByScore("█".repeat(currentFill), currentScore) + highlighter.dim(colorizeByScore("▓".repeat(gainCount), currentScore)) + highlighter.dim("░".repeat(emptyCount));
91736
91756
  };
91737
- const getDoctorFace = (score) => {
91738
- if (score >= 75) return ["◠ ◠", " ▽ "];
91739
- if (score >= 50) return ["• •", " ─ "];
91740
- return ["x x", " ▽ "];
91741
- };
91742
91757
  const BRANDING_LINE = `React Doctor ${highlighter.dim("(https://react.doctor)")}`;
91743
91758
  const RAW_BRANDING_LINE = "React Doctor (https://react.doctor)";
91744
91759
  const buildRawFaceLines = (score) => {
91745
- const [eyes, mouth] = getDoctorFace(score);
91760
+ const [eyes, mouth] = doctorFace(score);
91746
91761
  return [
91747
91762
  FACE_BOX_TOP_BORDER,
91748
91763
  `│ ${eyes} │`,
@@ -92076,7 +92091,8 @@ const mergeInspectOptions = (inputOptions, userConfig) => {
92076
92091
  includeTagDefaults: inputOptions.includeTagDefaults ?? false,
92077
92092
  scoreDisabledMessage: inputOptions.scoreDisabledMessage,
92078
92093
  outputSurface: inputOptions.outputSurface ?? "cli",
92079
- suppressRendering: inputOptions.suppressRendering ?? false,
92094
+ suppressRendering: (inputOptions.suppressRendering ?? false) || inputOptions.uiLayers != null,
92095
+ uiLayers: inputOptions.uiLayers ?? null,
92080
92096
  concurrentScan: inputOptions.concurrentScan ?? false,
92081
92097
  concurrency: inputOptions.concurrency,
92082
92098
  maxDurationMs: inputOptions.maxDurationMs ?? null,
@@ -92300,7 +92316,9 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
92300
92316
  shouldRunSupplyChain: options.supplyChain,
92301
92317
  shouldComputeScore: !options.noScore,
92302
92318
  shouldShowProgressSpinners,
92303
- oxlintConcurrency: options.concurrency
92319
+ oxlintConcurrency: options.concurrency,
92320
+ reporterLayer: options.uiLayers?.reporter,
92321
+ progressLayer: options.uiLayers?.progress
92304
92322
  });
92305
92323
  const program = runInspect({
92306
92324
  directory,
@@ -92338,7 +92356,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
92338
92356
  const output = await runPromise(restoreLegacyThrow(applyObservability(options.silent ? program.pipe(provide(layers), provideService$2(Console, silentConsole)) : program.pipe(provide(layers)), rootSentrySpan)));
92339
92357
  const didLintFail = lintBindingMissing || output.didLintFail;
92340
92358
  const lintFailureReason = lintBindingMissing ? `oxlint native binding not found for Node ${process.version}; expected one matching ${OXLINT_NODE_REQUIREMENT}` : output.lintFailureReason;
92341
- if (!options.scoreOnly && !lintBindingMissing && output.didLintFail && lintFailureReason !== null) if (output.lintFailureReasonKind === "native-binding-missing") runConsole(log(highlighter.gray(` Upgrade to Node ${OXLINT_NODE_REQUIREMENT} or run: npx -p oxlint@latest react-doctor@latest`)));
92359
+ if (!options.scoreOnly && !options.uiLayers && !lintBindingMissing && output.didLintFail && lintFailureReason !== null) if (output.lintFailureReasonKind === "native-binding-missing") runConsole(log(highlighter.gray(` Upgrade to Node ${OXLINT_NODE_REQUIREMENT} or run: npx -p oxlint@latest react-doctor@latest`)));
92342
92360
  else runConsole(error$1(highlighter.error(lintFailureReason)));
92343
92361
  let inspectDiagnostics = output.diagnostics;
92344
92362
  let baselineDelta;
@@ -92573,7 +92591,7 @@ const finalizeAndRender = (input) => gen(function* () {
92573
92591
  const scoreDiagnostics = filterDiagnosticsForSurface([...diagnostics], "score", userConfig);
92574
92592
  const displayedScoreDiagnostics = filterDiagnosticsForSurface([...printedDiagnostics], "score", userConfig);
92575
92593
  const potentialScore = score ? yield* promise(() => computeProjectedScore(displayedScoreDiagnostics, scoreDiagnostics, score)) : null;
92576
- const shouldShowShareLink = !options.noScore && options.share && !options.isCi;
92594
+ const showShareLink = shouldShowShareLink(options);
92577
92595
  yield* pause;
92578
92596
  yield* printSummary({
92579
92597
  diagnostics: [...printedDiagnostics],
@@ -92596,7 +92614,7 @@ const finalizeAndRender = (input) => gen(function* () {
92596
92614
  diagnostics: [...printedDiagnostics],
92597
92615
  scoreResult: score,
92598
92616
  projectName: project.projectName,
92599
- isOffline: !shouldShowShareLink
92617
+ isOffline: !showShareLink
92600
92618
  });
92601
92619
  return buildResult();
92602
92620
  });
@@ -92706,6 +92724,127 @@ const computeDefaultSelectedAgents = (detectedAgents, rememberedAgents) => {
92706
92724
  return detectedAgents.length === 1 ? [...detectedAgents] : [];
92707
92725
  };
92708
92726
  //#endregion
92727
+ //#region src/cli/utils/launch-agent.ts
92728
+ const isWindows = process.platform === "win32";
92729
+ const CLI_AGENT_IDS = [
92730
+ "claude-code",
92731
+ "codex",
92732
+ "cursor"
92733
+ ];
92734
+ const CLI_AGENT_BINARIES = {
92735
+ "claude-code": "claude",
92736
+ codex: "codex",
92737
+ cursor: "cursor-agent"
92738
+ };
92739
+ const CLI_AGENT_AUTO_FLAGS = {
92740
+ "claude-code": ["--dangerously-skip-permissions"],
92741
+ codex: ["--yolo"],
92742
+ cursor: ["--force"]
92743
+ };
92744
+ const resolveCursorBundledNode = () => {
92745
+ if (!isWindows) return null;
92746
+ const localAppData = process.env.LOCALAPPDATA;
92747
+ if (!localAppData) return null;
92748
+ const cursorAgentRoot = Path.join(localAppData, "cursor-agent", "versions");
92749
+ try {
92750
+ const versions = NFS.readdirSync(cursorAgentRoot);
92751
+ if (versions.length === 0) return null;
92752
+ const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
92753
+ for (const version of sortedVersions) try {
92754
+ const bundledNodePath = Path.join(cursorAgentRoot, version, "node.exe");
92755
+ const entryScriptPath = Path.join(cursorAgentRoot, version, "index.js");
92756
+ if (NFS.statSync(bundledNodePath).isFile() && NFS.statSync(entryScriptPath).isFile()) return {
92757
+ command: bundledNodePath,
92758
+ args: [entryScriptPath]
92759
+ };
92760
+ } catch {}
92761
+ } catch {}
92762
+ return null;
92763
+ };
92764
+ const resolveWindowsCmdEntryScript = (command) => {
92765
+ if (!isWindows) return null;
92766
+ const pathDirectories = (process.env.PATH ?? "").split(Path.delimiter).filter(Boolean);
92767
+ for (const directory of pathDirectories) {
92768
+ const cmdFilePath = Path.join(directory, `${command}.cmd`);
92769
+ try {
92770
+ if (!NFS.statSync(cmdFilePath).isFile()) continue;
92771
+ } catch {
92772
+ continue;
92773
+ }
92774
+ try {
92775
+ const entryScriptMatch = NFS.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
92776
+ if (!entryScriptMatch) continue;
92777
+ const resolvedScriptPath = Path.resolve(directory, entryScriptMatch[1]);
92778
+ if (NFS.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
92779
+ } catch {}
92780
+ }
92781
+ return null;
92782
+ };
92783
+ const spawnAgent = (command, args, cwd) => new Promise((resolve, reject) => {
92784
+ const child = spawn(command, [...args], {
92785
+ cwd,
92786
+ stdio: "inherit"
92787
+ });
92788
+ child.on("error", reject);
92789
+ child.on("close", (code) => resolve(code ?? 0));
92790
+ });
92791
+ const launchCliAgent = async (agentId, prompt, cwd) => {
92792
+ const binary = CLI_AGENT_BINARIES[agentId];
92793
+ const agentArgs = [...CLI_AGENT_AUTO_FLAGS[agentId], prompt];
92794
+ if (isWindows) {
92795
+ if (agentId === "cursor") {
92796
+ const cursorResolution = resolveCursorBundledNode();
92797
+ if (cursorResolution) return spawnAgent(cursorResolution.command, [...cursorResolution.args, ...agentArgs], cwd);
92798
+ }
92799
+ const entryScript = resolveWindowsCmdEntryScript(binary);
92800
+ if (entryScript) return spawnAgent(process.execPath, [entryScript, ...agentArgs], cwd);
92801
+ }
92802
+ try {
92803
+ return await spawnAgent(binary, agentArgs, cwd);
92804
+ } catch {
92805
+ throw new Error(`Failed to launch ${binary}`);
92806
+ }
92807
+ };
92808
+ const CLIPBOARD_COMMANDS = [
92809
+ {
92810
+ binary: "pbcopy",
92811
+ args: []
92812
+ },
92813
+ {
92814
+ binary: "wl-copy",
92815
+ args: []
92816
+ },
92817
+ {
92818
+ binary: "xclip",
92819
+ args: ["-selection", "clipboard"]
92820
+ },
92821
+ {
92822
+ binary: "xsel",
92823
+ args: ["--clipboard", "--input"]
92824
+ },
92825
+ {
92826
+ binary: "clip",
92827
+ args: []
92828
+ }
92829
+ ];
92830
+ const copyToClipboard = (text) => {
92831
+ const command = CLIPBOARD_COMMANDS.find((candidate) => isCommandAvailable(candidate.binary));
92832
+ if (!command) return Promise.resolve(false);
92833
+ return new Promise((resolve) => {
92834
+ const child = spawn(command.binary, command.args);
92835
+ child.on("error", () => resolve(false));
92836
+ child.on("close", (code) => resolve(code === 0));
92837
+ child.stdin.on("error", () => resolve(false));
92838
+ child.stdin.end(text);
92839
+ });
92840
+ };
92841
+ //#endregion
92842
+ //#region src/cli/utils/detect-launchable-agents.ts
92843
+ const detectLaunchableAgents = async () => {
92844
+ const detected = new Set(await detectAvailableAgents());
92845
+ return CLI_AGENT_IDS.filter((agentId) => detected.has(agentId) && isCommandAvailable(CLI_AGENT_BINARIES[agentId]));
92846
+ };
92847
+ //#endregion
92709
92848
  //#region src/cli/utils/action-upgrade-prompt.ts
92710
92849
  const ACTION_UPGRADE_GATE = {
92711
92850
  id: ACTION_UPGRADE_EVENT,
@@ -93948,116 +94087,6 @@ const installReactDoctorSkillForAgent = async (agent, projectRoot) => {
93948
94087
  return result.skills.length > 0 && result.failed.length === 0;
93949
94088
  };
93950
94089
  //#endregion
93951
- //#region src/cli/utils/launch-agent.ts
93952
- const isWindows = process.platform === "win32";
93953
- const CLI_AGENT_BINARIES = {
93954
- "claude-code": "claude",
93955
- codex: "codex",
93956
- cursor: "cursor-agent"
93957
- };
93958
- const CLI_AGENT_AUTO_FLAGS = {
93959
- "claude-code": ["--dangerously-skip-permissions"],
93960
- codex: ["--yolo"],
93961
- cursor: ["--force"]
93962
- };
93963
- const resolveCursorBundledNode = () => {
93964
- if (!isWindows) return null;
93965
- const localAppData = process.env.LOCALAPPDATA;
93966
- if (!localAppData) return null;
93967
- const cursorAgentRoot = Path.join(localAppData, "cursor-agent", "versions");
93968
- try {
93969
- const versions = NFS.readdirSync(cursorAgentRoot);
93970
- if (versions.length === 0) return null;
93971
- const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
93972
- for (const version of sortedVersions) try {
93973
- const bundledNodePath = Path.join(cursorAgentRoot, version, "node.exe");
93974
- const entryScriptPath = Path.join(cursorAgentRoot, version, "index.js");
93975
- if (NFS.statSync(bundledNodePath).isFile() && NFS.statSync(entryScriptPath).isFile()) return {
93976
- command: bundledNodePath,
93977
- args: [entryScriptPath]
93978
- };
93979
- } catch {}
93980
- } catch {}
93981
- return null;
93982
- };
93983
- const resolveWindowsCmdEntryScript = (command) => {
93984
- if (!isWindows) return null;
93985
- const pathDirectories = (process.env.PATH ?? "").split(Path.delimiter).filter(Boolean);
93986
- for (const directory of pathDirectories) {
93987
- const cmdFilePath = Path.join(directory, `${command}.cmd`);
93988
- try {
93989
- if (!NFS.statSync(cmdFilePath).isFile()) continue;
93990
- } catch {
93991
- continue;
93992
- }
93993
- try {
93994
- const entryScriptMatch = NFS.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
93995
- if (!entryScriptMatch) continue;
93996
- const resolvedScriptPath = Path.resolve(directory, entryScriptMatch[1]);
93997
- if (NFS.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
93998
- } catch {}
93999
- }
94000
- return null;
94001
- };
94002
- const spawnAgent = (command, args, cwd) => new Promise((resolve, reject) => {
94003
- const child = spawn(command, [...args], {
94004
- cwd,
94005
- stdio: "inherit"
94006
- });
94007
- child.on("error", reject);
94008
- child.on("close", (code) => resolve(code ?? 0));
94009
- });
94010
- const launchCliAgent = async (agentId, prompt, cwd) => {
94011
- const binary = CLI_AGENT_BINARIES[agentId];
94012
- const agentArgs = [...CLI_AGENT_AUTO_FLAGS[agentId], prompt];
94013
- if (isWindows) {
94014
- if (agentId === "cursor") {
94015
- const cursorResolution = resolveCursorBundledNode();
94016
- if (cursorResolution) return spawnAgent(cursorResolution.command, [...cursorResolution.args, ...agentArgs], cwd);
94017
- }
94018
- const entryScript = resolveWindowsCmdEntryScript(binary);
94019
- if (entryScript) return spawnAgent(process.execPath, [entryScript, ...agentArgs], cwd);
94020
- }
94021
- try {
94022
- return await spawnAgent(binary, agentArgs, cwd);
94023
- } catch {
94024
- throw new Error(`Failed to launch ${binary}`);
94025
- }
94026
- };
94027
- const CLIPBOARD_COMMANDS = [
94028
- {
94029
- binary: "pbcopy",
94030
- args: []
94031
- },
94032
- {
94033
- binary: "wl-copy",
94034
- args: []
94035
- },
94036
- {
94037
- binary: "xclip",
94038
- args: ["-selection", "clipboard"]
94039
- },
94040
- {
94041
- binary: "xsel",
94042
- args: ["--clipboard", "--input"]
94043
- },
94044
- {
94045
- binary: "clip",
94046
- args: []
94047
- }
94048
- ];
94049
- const copyToClipboard = (text) => {
94050
- const command = CLIPBOARD_COMMANDS.find((candidate) => isCommandAvailable(candidate.binary));
94051
- if (!command) return Promise.resolve(false);
94052
- return new Promise((resolve) => {
94053
- const child = spawn(command.binary, command.args);
94054
- child.on("error", () => resolve(false));
94055
- child.on("close", (code) => resolve(code === 0));
94056
- child.stdin.on("error", () => resolve(false));
94057
- child.stdin.end(text);
94058
- });
94059
- };
94060
- //#endregion
94061
94090
  //#region src/cli/utils/handoff-to-agent.ts
94062
94091
  const CLIPBOARD_CHOICE = "clipboard";
94063
94092
  const SKIP_CHOICE = "skip";
@@ -94121,10 +94150,6 @@ const maybeOfferActionUpgrade = async (projectRoot) => {
94121
94150
  }
94122
94151
  if (await upgradeGitHubActionsWorkflow(workflow)) recordActionUpgradeDecision(projectRoot, "accepted");
94123
94152
  };
94124
- const detectLaunchableAgents = async () => {
94125
- const detected = new Set(await detectAvailableAgents());
94126
- return Object.keys(CLI_AGENT_BINARIES).filter((agentId) => detected.has(agentId) && isCommandAvailable(CLI_AGENT_BINARIES[agentId]));
94127
- };
94128
94153
  const handoffToAgent = async (input) => {
94129
94154
  if (!input.interactive || input.diagnostics.length === 0) return;
94130
94155
  cliLogger.break();
@@ -94439,15 +94464,29 @@ const readChangedFilesFrom = (filePath) => {
94439
94464
  return [...uniqueFiles];
94440
94465
  };
94441
94466
  //#endregion
94442
- //#region src/cli/utils/filter-scans-for-surface.ts
94443
- const filterScansForSurface = (completedScans, surface) => completedScans.flatMap((scan) => filterDiagnosticsForSurface([...scan.result.diagnostics], surface, scan.config));
94467
+ //#region src/cli/utils/count-unique-scanned-files.ts
94468
+ const countUniqueScannedFiles = (results) => {
94469
+ const uniqueScannedFilePaths = /* @__PURE__ */ new Set();
94470
+ let fileCountFromScansWithoutPaths = 0;
94471
+ for (const result of results) {
94472
+ const scannedFilePaths = result.scannedFilePaths;
94473
+ if (scannedFilePaths && scannedFilePaths.length > 0) for (const filePath of scannedFilePaths) uniqueScannedFilePaths.add(filePath);
94474
+ else fileCountFromScansWithoutPaths += result.scannedFileCount ?? result.project.sourceFileCount;
94475
+ }
94476
+ return uniqueScannedFilePaths.size + fileCountFromScansWithoutPaths;
94477
+ };
94444
94478
  //#endregion
94445
- //#region src/cli/utils/render-multi-project-summary.ts
94446
- const getScoreLabel = (score) => {
94479
+ //#region src/cli/utils/score-band-label.ts
94480
+ const scoreBandLabel = (score) => {
94447
94481
  if (score >= 75) return "Great";
94448
94482
  if (score >= 50) return "OK";
94449
94483
  return "Needs work";
94450
94484
  };
94485
+ //#endregion
94486
+ //#region src/cli/utils/filter-scans-for-surface.ts
94487
+ const filterScansForSurface = (completedScans, surface) => completedScans.flatMap((scan) => filterDiagnosticsForSurface([...scan.result.diagnostics], surface, scan.config));
94488
+ //#endregion
94489
+ //#region src/cli/utils/render-multi-project-summary.ts
94451
94490
  const buildSummaryLine = (entry, longestProjectNameLength) => {
94452
94491
  const paddedName = entry.projectName.padEnd(longestProjectNameLength);
94453
94492
  const nameRendering = entry.score !== null ? colorizeByScore(paddedName, entry.score) : highlighter.dim(paddedName);
@@ -94456,7 +94495,7 @@ const buildSummaryLine = (entry, longestProjectNameLength) => {
94456
94495
  return ` ${nameRendering} ${highlighter.dim("no score")} ${highlighter.dim(issueLabel)}`;
94457
94496
  }
94458
94497
  const scoreRendering = colorizeByScore(String(entry.score).padStart(3), entry.score);
94459
- const label = colorizeByScore(getScoreLabel(entry.score), entry.score);
94498
+ const label = colorizeByScore(scoreBandLabel(entry.score), entry.score);
94460
94499
  const issuesParts = [];
94461
94500
  if (entry.errorCount > 0) issuesParts.push(highlighter.error(`${entry.errorCount} ${entry.errorCount === 1 ? "error" : "errors"}`));
94462
94501
  const warningCount = entry.issueCount - entry.errorCount;
@@ -94478,14 +94517,7 @@ const printMultiProjectSummary = (input) => gen(function* () {
94478
94517
  const projectRootByDiagnostic = /* @__PURE__ */ new WeakMap();
94479
94518
  for (const scan of completedScans) for (const diagnostic of scan.result.diagnostics) projectRootByDiagnostic.set(diagnostic, scan.result.project.rootDirectory);
94480
94519
  const resolveDiagnosticSourceRoot = (diagnostic) => projectRootByDiagnostic.get(diagnostic) ?? "";
94481
- const uniqueScannedFilePaths = /* @__PURE__ */ new Set();
94482
- let fileCountFromScansWithoutPaths = 0;
94483
- for (const scan of completedScans) {
94484
- const scannedFilePaths = scan.result.scannedFilePaths;
94485
- if (scannedFilePaths && scannedFilePaths.length > 0) for (const filePath of scannedFilePaths) uniqueScannedFilePaths.add(filePath);
94486
- else fileCountFromScansWithoutPaths += scan.result.scannedFileCount ?? scan.result.project.sourceFileCount;
94487
- }
94488
- const totalScannedFileCount = uniqueScannedFilePaths.size + fileCountFromScansWithoutPaths;
94520
+ const totalScannedFileCount = countUniqueScannedFiles(completedScans.map((scan) => scan.result));
94489
94521
  yield* log(`${highlighter.success("✔")} Scanned ${totalScannedFileCount} ${totalScannedFileCount === 1 ? "file" : "files"} in ${formatElapsedTime(totalElapsedMilliseconds)}`);
94490
94522
  if (displayDiagnostics.length > 0) {
94491
94523
  yield* log("");
@@ -94757,10 +94789,14 @@ const parseFileLineArgument = (rawArgument) => {
94757
94789
  };
94758
94790
  //#endregion
94759
94791
  //#region src/cli/utils/select-projects.ts
94760
- const selectProjects = async (rootDirectory, projectFlag, skipPrompts, configProjects) => {
94792
+ const discoverWorkspacePackages = (rootDirectory) => {
94761
94793
  const hasRootPackageJson = isFile(Path.join(rootDirectory, "package.json"));
94762
- let packages = listWorkspacePackages(rootDirectory);
94763
- if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) packages = discoverReactSubprojects(rootDirectory);
94794
+ const packages = listWorkspacePackages(rootDirectory);
94795
+ if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return discoverReactSubprojects(rootDirectory);
94796
+ return packages;
94797
+ };
94798
+ const selectProjects = async (rootDirectory, projectFlag, skipPrompts, configProjects) => {
94799
+ const packages = discoverWorkspacePackages(rootDirectory);
94764
94800
  if (projectFlag) return resolveProjectFlag(projectFlag, packages, rootDirectory);
94765
94801
  const configRequestedNames = (configProjects ?? []).map((requestedName) => requestedName.trim()).filter((requestedName) => requestedName.length > 0);
94766
94802
  if (configRequestedNames.length > 0) {
@@ -94889,6 +94925,14 @@ const projectManifestChanged = (rootDirectory, projectDirectory, diffInfo) => {
94889
94925
  return diffInfo.changedFiles.some((filePath) => toForwardSlashes(filePath) === manifestPath);
94890
94926
  };
94891
94927
  //#endregion
94928
+ //#region src/cli/utils/should-fail-scan-gate.ts
94929
+ const shouldFailScanGate = (input) => {
94930
+ if (input.blockingLevel === "none") return false;
94931
+ if (input.scans.some(({ result }) => hasLintHardFailure(result))) return true;
94932
+ if (input.diagnosticsAreGateExempt === true) return false;
94933
+ return shouldBlockCi(filterScansForSurface(input.scans, "ciFailure"), input.blockingLevel);
94934
+ };
94935
+ //#endregion
94892
94936
  //#region src/cli/utils/warn-deprecated-fail-on.ts
94893
94937
  const warnDeprecatedFailOn = (flags, userConfig) => {
94894
94938
  const usedFlag = flags.failOn !== void 0;
@@ -95015,12 +95059,11 @@ const finalizeScans = (input) => {
95015
95059
  }));
95016
95060
  }
95017
95061
  const blockingLevel = resolveBlockingLevel(input.flags, input.userConfig);
95018
- if (input.completedScans.some(({ result }) => hasLintHardFailure(result)) && blockingLevel !== "none") {
95019
- process.exitCode = 1;
95020
- return;
95021
- }
95022
- if (input.isScoreOnly || baselineDegraded) return;
95023
- if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), blockingLevel)) process.exitCode = 1;
95062
+ if (shouldFailScanGate({
95063
+ scans: input.completedScans,
95064
+ blockingLevel,
95065
+ diagnosticsAreGateExempt: input.isScoreOnly || baselineDegraded
95066
+ })) process.exitCode = 1;
95024
95067
  };
95025
95068
  const buildChangedFilesDiffInfo = (changedFiles) => ({
95026
95069
  currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
@@ -95296,13 +95339,13 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
95296
95339
  completedScans.push(scanOutcome);
95297
95340
  }
95298
95341
  if (!isQuiet && isMultiProject && completedScans.length > 0) {
95299
- const shouldShowShareLink = !isShareOptedOut(completedScans, scanOptions.noScore) && !scanOptions.isCi;
95342
+ const showShareLink = !isShareOptedOut(completedScans, scanOptions.noScore) && !scanOptions.isCi;
95300
95343
  await runPromise(printMultiProjectSummary({
95301
95344
  completedScans,
95302
95345
  categoryFilters,
95303
95346
  verbose: Boolean(flags.verbose),
95304
95347
  outputDirectory: flags.outputDir,
95305
- isOffline: !shouldShowShareLink,
95348
+ isOffline: !showShareLink,
95306
95349
  projectName: Path.basename(resolvedDirectory),
95307
95350
  totalElapsedMilliseconds: performance$1.now() - scanLoopStartTime
95308
95351
  }));
@@ -96277,6 +96320,20 @@ const COMMAND_FLAG_SPECS = new Map([
96277
96320
  longOptionsWithOptionalValues: /* @__PURE__ */ new Set(),
96278
96321
  shortOptionsWithoutValues: new Set(["-h"]),
96279
96322
  shortOptionsWithRequiredValues: new Set(["-c"])
96323
+ }],
96324
+ ["experimental-tui", {
96325
+ longOptionsWithoutValues: new Set([
96326
+ "--color",
96327
+ "--help",
96328
+ "--no-color",
96329
+ "--no-dead-code",
96330
+ "--no-score",
96331
+ "--yes"
96332
+ ]),
96333
+ longOptionsWithRequiredValues: new Set(["--blocking", "--project"]),
96334
+ longOptionsWithOptionalValues: /* @__PURE__ */ new Set(),
96335
+ shortOptionsWithoutValues: new Set(["-h", "-y"]),
96336
+ shortOptionsWithRequiredValues: new Set(["-p"])
96280
96337
  }]
96281
96338
  ]);
96282
96339
  const isFlagLike = (argument) => argument.startsWith("-") && argument !== "-";
@@ -96456,6 +96513,35 @@ rules.command("category <category> <severity>").description("Set the severity fo
96456
96513
  rules.command("ignore-tag <tag>").description("Skip a whole rule family by tag before linting (e.g. design)").option("-c, --cwd <cwd>", "working directory", process.cwd()).action((tag, _options, command) => rulesIgnoreTagAction(tag, command.optsWithGlobals()));
96457
96514
  rules.command("unignore-tag <tag>").description("Stop ignoring a tag previously skipped via ignore-tag").option("-c, --cwd <cwd>", "working directory", process.cwd()).action((tag, _options, command) => rulesUnignoreTagAction(tag, command.optsWithGlobals()));
96458
96515
  program.command("experimental-lsp", { hidden: false }).description("[experimental] run the React Doctor language server over stdio (for editors)").allowUnknownOption().action(() => {});
96516
+ program.command("experimental-tui [directory]", { hidden: true }).description("[experimental] interactive, scrollable scan report").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").option("--no-dead-code", "skip dead-code analysis").option("--no-score", "skip the score API, the share URL, and crash reporting").option("-p, --project <names>", "scan specific workspace projects (comma-separated, or *)").option("-y, --yes", "skip the project prompt and scan every discovered project").action(async (directory = ".", _localOptions, command) => {
96517
+ const options = command.optsWithGlobals();
96518
+ const deadCode = options.deadCode === false ? false : void 0;
96519
+ const noScore = options.score === false ? true : void 0;
96520
+ const nodeMajorVersion = Number(process.versions.node.split(".")[0]);
96521
+ if (process.stdout.isTTY !== true || process.stdin.isTTY !== true || isNonInteractiveEnvironment() || nodeMajorVersion < 22) {
96522
+ await inspectAction(directory, {
96523
+ deadCode,
96524
+ score: options.score === false ? false : void 0,
96525
+ project: options.project,
96526
+ yes: options.yes,
96527
+ blocking: options.blocking
96528
+ });
96529
+ return;
96530
+ }
96531
+ recordCount(METRIC.cliInvoked, 1, { command: "experimental-tui" });
96532
+ const { runScanApp } = await import("./run-scan-app-DZ5jGWFf.js");
96533
+ const { shouldFail } = await runScanApp({
96534
+ directory,
96535
+ options: {
96536
+ deadCode,
96537
+ noScore
96538
+ },
96539
+ projectFlag: options.project,
96540
+ skipPrompts: options.yes ?? false,
96541
+ blocking: options.blocking
96542
+ });
96543
+ if (shouldFail) process.exitCode = 1;
96544
+ });
96459
96545
  process.stdout.on("error", (error) => {
96460
96546
  if (error.code === "EPIPE") process.exit(0);
96461
96547
  });
@@ -96482,7 +96568,7 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
96482
96568
  handleError(error, { sentryEventId });
96483
96569
  });
96484
96570
  //#endregion
96485
- export {};
96571
+ export { toForwardSlashes as A, highlighter as B, buildRulePriorityMap as C, resolveBlockingLevel as D, formatLearnMoreLine as E, isCiEnvironment as F, sync as G, mergeReactDoctorConfigs as H, DIAGNOSTIC_CATEGORY_BUCKETS as I, void_ as K, Progress as L, isUnicodeSupported as M, METRIC as N, isReactDoctorWorkflowInstalled as O, TUI_REPORT_DETAIL_WIDTH_FRACTION as P, Reporter as R, hasLintHardFailure as S, formatFixRecipeLine as T, resolveScanTarget as U, mapWithConcurrency as V, runPromise as W, canAnimateOnboarding as _, resolveCliInspectOptions as a, buildCodeFrame as b, detectLaunchableAgents as c, launchCliAgent as d, isShareOptedOut as f, doctorFace as g, easeOutCubic as h, selectProjects as i, findNearestPackageDirectory as j, recordCount as k, CLI_AGENT_BINARIES as l, printFooter as m, shouldFailScanGate as n, countUniqueScannedFiles as o, inspect as p, succeed$3 as q, discoverWorkspacePackages as r, setUpGitHubActions as s, exitGracefully as t, copyToClipboard as u, computeProjectedScore as v, buildSortedRuleGroups as w, buildNoScoreMessage as x, formatElapsedTime as y, getCategoryImpact as z };
96486
96572
 
96487
96573
  //# sourceMappingURL=cli.js.map
96488
- //# debugId=81e6305d-aa88-52f6-82ff-ac652d90107a
96574
+ //# debugId=be7f877d-9463-5212-8e1f-dc5feea97da0
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
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]="4b0e44db-b753-5cee-b79d-4976faf0c4cb")}catch(e){}}();
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]="ef9143d9-5b2e-5eca-adb3-89fde61aafe8")}catch(e){}}();
3
3
  import { i as __toESM$1, r as __require, t as __commonJSMin$1 } from "./chunk-ChKA3wi6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, FRAMEWORK_TOKENS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
@@ -61762,6 +61762,13 @@ const DIAGNOSTIC_CATEGORY_BUCKETS = [
61762
61762
  "Maintainability"
61763
61763
  ];
61764
61764
  const OCCURRENCE_MATCHED_CATEGORIES = new Set(["Accessibility"]);
61765
+ new Map(Object.entries({
61766
+ Security: "An attacker can read user data, act as your users, or run code you never shipped.",
61767
+ Bugs: "Real users hit crashes, wrong output, or state that silently corrupts.",
61768
+ Performance: "Users feel extra latency and wasted renders on every interaction.",
61769
+ Accessibility: "Users on screen readers, keyboards, or assistive tech get locked out.",
61770
+ Maintainability: "Every future change gets slower and riskier to make."
61771
+ }));
61765
61772
  const APP_ONLY_RULE_KEYS = new Set([
61766
61773
  "react-hooks-js/static-components",
61767
61774
  "react-doctor/no-render-prop-children",
@@ -73876,4 +73883,4 @@ const toJsonReport = (result, options) => buildJsonReport({
73876
73883
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
73877
73884
 
73878
73885
  //# sourceMappingURL=index.js.map
73879
- //# debugId=4b0e44db-b753-5cee-b79d-4976faf0c4cb
73886
+ //# debugId=ef9143d9-5b2e-5eca-adb3-89fde61aafe8
package/dist/lsp.js CHANGED
@@ -61815,6 +61815,13 @@ const DIAGNOSTIC_CATEGORY_BUCKETS = [
61815
61815
  "Maintainability"
61816
61816
  ];
61817
61817
  const OCCURRENCE_MATCHED_CATEGORIES = new Set(["Accessibility"]);
61818
+ new Map(Object.entries({
61819
+ Security: "An attacker can read user data, act as your users, or run code you never shipped.",
61820
+ Bugs: "Real users hit crashes, wrong output, or state that silently corrupts.",
61821
+ Performance: "Users feel extra latency and wasted renders on every interaction.",
61822
+ Accessibility: "Users on screen readers, keyboards, or assistive tech get locked out.",
61823
+ Maintainability: "Every future change gets slower and riskier to make."
61824
+ }));
61818
61825
  const APP_ONLY_RULE_KEYS = new Set([
61819
61826
  "react-hooks-js/static-components",
61820
61827
  "react-doctor/no-render-prop-children",
@@ -75497,5 +75504,5 @@ const startLanguageServer = () => {
75497
75504
  };
75498
75505
  //#endregion
75499
75506
  export { startLanguageServer };
75500
- !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]="efdb823c-9e83-5162-be2f-a67125fce48a")}catch(e){}}();
75501
- //# debugId=efdb823c-9e83-5162-be2f-a67125fce48a
75507
+ !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]="a35abfab-9805-5633-9c17-c7012a9dc0b3")}catch(e){}}();
75508
+ //# debugId=a35abfab-9805-5633-9c17-c7012a9dc0b3