react-doctor 0.8.3-dev.01820bb → 0.8.3-dev.3d7ea66

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]="be7f877d-9463-5212-8e1f-dc5feea97da0")}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]="729213e0-f120-5159-8212-e0a881c2cf17")}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,14 +64964,6 @@ 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);
64975
64967
  const APP_ONLY_RULE_KEYS = new Set([
64976
64968
  "react-hooks-js/static-components",
64977
64969
  "react-doctor/no-render-prop-children",
@@ -65715,20 +65707,18 @@ const getReactCompilerFlagState = (expression, analysis) => {
65715
65707
  }
65716
65708
  return null;
65717
65709
  };
65718
- const getSelectedObjectProperty = (expression, propertyName, analysis, visitedExpressions = /* @__PURE__ */ new Set()) => {
65710
+ const getSelectedObjectProperty = (expression, propertyName, analysis) => {
65719
65711
  let resolvedExpression = expression;
65720
65712
  while (ts.isParenthesizedExpression(resolvedExpression) || ts.isAsExpression(resolvedExpression) || ts.isTypeAssertionExpression(resolvedExpression) || ts.isSatisfiesExpression(resolvedExpression) || ts.isNonNullExpression(resolvedExpression)) resolvedExpression = resolvedExpression.expression;
65721
- if (visitedExpressions.has(resolvedExpression)) return null;
65722
- visitedExpressions.add(resolvedExpression);
65723
65713
  if (ts.isIdentifier(resolvedExpression)) {
65724
65714
  if (analysis.localBindings.has(resolvedExpression.text)) {
65725
65715
  const localInitializer = analysis.localBindings.get(resolvedExpression.text);
65726
- return localInitializer ? getSelectedObjectProperty(localInitializer, propertyName, analysis, visitedExpressions) : null;
65716
+ return localInitializer ? getSelectedObjectProperty(localInitializer, propertyName, analysis) : null;
65727
65717
  }
65728
65718
  const scopedBinding = getScopedConfigBinding(resolvedExpression);
65729
- if (scopedBinding.wasFound) return scopedBinding.initializer ? getSelectedObjectProperty(scopedBinding.initializer, propertyName, analysis, visitedExpressions) : null;
65719
+ if (scopedBinding.wasFound) return scopedBinding.initializer ? getSelectedObjectProperty(scopedBinding.initializer, propertyName, analysis) : null;
65730
65720
  const topLevelBinding = getTopLevelBinding(analysis.sourceFile, resolvedExpression.text);
65731
- return topLevelBinding && ts.isExpression(topLevelBinding) ? getSelectedObjectProperty(topLevelBinding, propertyName, analysis, visitedExpressions) : null;
65721
+ return topLevelBinding && ts.isExpression(topLevelBinding) ? getSelectedObjectProperty(topLevelBinding, propertyName, analysis) : null;
65732
65722
  }
65733
65723
  if (!ts.isObjectLiteralExpression(resolvedExpression)) return null;
65734
65724
  for (const property of [...resolvedExpression.properties].reverse()) {
@@ -65736,7 +65726,7 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
65736
65726
  if (ts.isMethodDeclaration(property) && getStaticPropertyName$1(property.name) === propertyName) return property;
65737
65727
  if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) return property.name;
65738
65728
  if (ts.isSpreadAssignment(property)) {
65739
- const spreadProperty = getSelectedObjectProperty(property.expression, propertyName, analysis, visitedExpressions);
65729
+ const spreadProperty = getSelectedObjectProperty(property.expression, propertyName, analysis);
65740
65730
  if (spreadProperty) return spreadProperty;
65741
65731
  }
65742
65732
  }
@@ -77317,7 +77307,6 @@ const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
77317
77307
  const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
77318
77308
  const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
77319
77309
  const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
77320
- const TUI_REPORT_DETAIL_WIDTH_FRACTION = .6;
77321
77310
  const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
77322
77311
  const WELCOME_EXPLANATION_HOLD_MS = 1e3;
77323
77312
  const CATEGORY_COUNTUP_SETTLE_HOLD_MS = 1e3;
@@ -77409,7 +77398,7 @@ const makeNoopConsole = () => ({
77409
77398
  });
77410
77399
  //#endregion
77411
77400
  //#region src/cli/utils/version.ts
77412
- const VERSION = "0.8.3-dev.01820bb";
77401
+ const VERSION = "0.8.3-dev.3d7ea66";
77413
77402
  //#endregion
77414
77403
  //#region src/cli/utils/json-mode.ts
77415
77404
  let context = null;
@@ -77767,13 +77756,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
77767
77756
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
77768
77757
  * standard `SENTRY_RELEASE` override.
77769
77758
  */
77770
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.3-dev.01820bb`;
77759
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.3-dev.3d7ea66`;
77771
77760
  /**
77772
77761
  * Deployment environment shown in Sentry's environment filter. Defaults to
77773
77762
  * `production` for tagged releases and `development` for dev/unbuilt versions,
77774
77763
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
77775
77764
  */
77776
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.3-dev.01820bb") ? "development" : "production");
77765
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.3-dev.3d7ea66") ? "development" : "production");
77777
77766
  /**
77778
77767
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
77779
77768
  * (set to `0` to disable tracing) and falls back to
@@ -89620,13 +89609,12 @@ const buildRuntimeLayers = (input) => {
89620
89609
  const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
89621
89610
  const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
89622
89611
  const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
89623
- const progressLayer = input.progressLayer ?? (input.shouldShowProgressSpinners ? Progress.layerOra(buildSpinnerProgressHandle) : Progress.layerNoop);
89624
- const reporterLayer = input.reporterLayer ?? Reporter.layerNoop;
89612
+ const progressLayer = input.shouldShowProgressSpinners ? Progress.layerOra(buildSpinnerProgressHandle) : Progress.layerNoop;
89625
89613
  const baseLayers = mergeAll$1(projectLayer, input.hasConfigOverride ? Config.layerOf({
89626
89614
  config: input.userConfig,
89627
89615
  resolvedDirectory: input.directory,
89628
89616
  configSourceDirectory: input.configSourceDirectory
89629
- }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, deadCodeLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
89617
+ }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, deadCodeLayer, progressLayer, Reporter.layerNoop, scoreLayer, supplyChainLayer);
89630
89618
  return input.oxlintConcurrency === void 0 ? baseLayers : mergeAll$1(baseLayers, succeed$3(OxlintConcurrency, input.oxlintConcurrency));
89631
89619
  };
89632
89620
  //#endregion
@@ -91461,9 +91449,6 @@ const supportsHyperlinks = (stream = process.stdout, env = process.env) => {
91461
91449
  * would choke on the escape sequences).
91462
91450
  */
91463
91451
  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;
91467
91452
  const FORCE_ONBOARDING_ENV_VAR = "REACT_DOCTOR_FORCE_ONBOARDING";
91468
91453
  const FALSY_FLAG_VALUES = new Set([
91469
91454
  "",
@@ -91682,13 +91667,6 @@ const colorizeByScore = (text, score) => {
91682
91667
  return highlighter.error(text);
91683
91668
  };
91684
91669
  //#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
91692
91670
  //#region src/cli/utils/ease-out-cubic.ts
91693
91671
  const easeOutCubic = (progress) => 1 - (1 - progress) ** 3;
91694
91672
  //#endregion
@@ -91754,10 +91732,15 @@ const buildProjectedScoreBar = (currentScore, potentialScore) => {
91754
91732
  const emptyCount = Math.max(0, barWidth - currentFill - gainCount);
91755
91733
  return colorizeByScore("█".repeat(currentFill), currentScore) + highlighter.dim(colorizeByScore("▓".repeat(gainCount), currentScore)) + highlighter.dim("░".repeat(emptyCount));
91756
91734
  };
91735
+ const getDoctorFace = (score) => {
91736
+ if (score >= 75) return ["◠ ◠", " ▽ "];
91737
+ if (score >= 50) return ["• •", " ─ "];
91738
+ return ["x x", " ▽ "];
91739
+ };
91757
91740
  const BRANDING_LINE = `React Doctor ${highlighter.dim("(https://react.doctor)")}`;
91758
91741
  const RAW_BRANDING_LINE = "React Doctor (https://react.doctor)";
91759
91742
  const buildRawFaceLines = (score) => {
91760
- const [eyes, mouth] = doctorFace(score);
91743
+ const [eyes, mouth] = getDoctorFace(score);
91761
91744
  return [
91762
91745
  FACE_BOX_TOP_BORDER,
91763
91746
  `│ ${eyes} │`,
@@ -92091,8 +92074,7 @@ const mergeInspectOptions = (inputOptions, userConfig) => {
92091
92074
  includeTagDefaults: inputOptions.includeTagDefaults ?? false,
92092
92075
  scoreDisabledMessage: inputOptions.scoreDisabledMessage,
92093
92076
  outputSurface: inputOptions.outputSurface ?? "cli",
92094
- suppressRendering: (inputOptions.suppressRendering ?? false) || inputOptions.uiLayers != null,
92095
- uiLayers: inputOptions.uiLayers ?? null,
92077
+ suppressRendering: inputOptions.suppressRendering ?? false,
92096
92078
  concurrentScan: inputOptions.concurrentScan ?? false,
92097
92079
  concurrency: inputOptions.concurrency,
92098
92080
  maxDurationMs: inputOptions.maxDurationMs ?? null,
@@ -92316,9 +92298,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
92316
92298
  shouldRunSupplyChain: options.supplyChain,
92317
92299
  shouldComputeScore: !options.noScore,
92318
92300
  shouldShowProgressSpinners,
92319
- oxlintConcurrency: options.concurrency,
92320
- reporterLayer: options.uiLayers?.reporter,
92321
- progressLayer: options.uiLayers?.progress
92301
+ oxlintConcurrency: options.concurrency
92322
92302
  });
92323
92303
  const program = runInspect({
92324
92304
  directory,
@@ -92356,7 +92336,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
92356
92336
  const output = await runPromise(restoreLegacyThrow(applyObservability(options.silent ? program.pipe(provide(layers), provideService$2(Console, silentConsole)) : program.pipe(provide(layers)), rootSentrySpan)));
92357
92337
  const didLintFail = lintBindingMissing || output.didLintFail;
92358
92338
  const lintFailureReason = lintBindingMissing ? `oxlint native binding not found for Node ${process.version}; expected one matching ${OXLINT_NODE_REQUIREMENT}` : output.lintFailureReason;
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`)));
92339
+ 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`)));
92360
92340
  else runConsole(error$1(highlighter.error(lintFailureReason)));
92361
92341
  let inspectDiagnostics = output.diagnostics;
92362
92342
  let baselineDelta;
@@ -92591,7 +92571,7 @@ const finalizeAndRender = (input) => gen(function* () {
92591
92571
  const scoreDiagnostics = filterDiagnosticsForSurface([...diagnostics], "score", userConfig);
92592
92572
  const displayedScoreDiagnostics = filterDiagnosticsForSurface([...printedDiagnostics], "score", userConfig);
92593
92573
  const potentialScore = score ? yield* promise(() => computeProjectedScore(displayedScoreDiagnostics, scoreDiagnostics, score)) : null;
92594
- const showShareLink = shouldShowShareLink(options);
92574
+ const shouldShowShareLink = !options.noScore && options.share && !options.isCi;
92595
92575
  yield* pause;
92596
92576
  yield* printSummary({
92597
92577
  diagnostics: [...printedDiagnostics],
@@ -92614,7 +92594,7 @@ const finalizeAndRender = (input) => gen(function* () {
92614
92594
  diagnostics: [...printedDiagnostics],
92615
92595
  scoreResult: score,
92616
92596
  projectName: project.projectName,
92617
- isOffline: !showShareLink
92597
+ isOffline: !shouldShowShareLink
92618
92598
  });
92619
92599
  return buildResult();
92620
92600
  });
@@ -92724,127 +92704,6 @@ const computeDefaultSelectedAgents = (detectedAgents, rememberedAgents) => {
92724
92704
  return detectedAgents.length === 1 ? [...detectedAgents] : [];
92725
92705
  };
92726
92706
  //#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
92848
92707
  //#region src/cli/utils/action-upgrade-prompt.ts
92849
92708
  const ACTION_UPGRADE_GATE = {
92850
92709
  id: ACTION_UPGRADE_EVENT,
@@ -94087,6 +93946,116 @@ const installReactDoctorSkillForAgent = async (agent, projectRoot) => {
94087
93946
  return result.skills.length > 0 && result.failed.length === 0;
94088
93947
  };
94089
93948
  //#endregion
93949
+ //#region src/cli/utils/launch-agent.ts
93950
+ const isWindows = process.platform === "win32";
93951
+ const CLI_AGENT_BINARIES = {
93952
+ "claude-code": "claude",
93953
+ codex: "codex",
93954
+ cursor: "cursor-agent"
93955
+ };
93956
+ const CLI_AGENT_AUTO_FLAGS = {
93957
+ "claude-code": ["--dangerously-skip-permissions"],
93958
+ codex: ["--yolo"],
93959
+ cursor: ["--force"]
93960
+ };
93961
+ const resolveCursorBundledNode = () => {
93962
+ if (!isWindows) return null;
93963
+ const localAppData = process.env.LOCALAPPDATA;
93964
+ if (!localAppData) return null;
93965
+ const cursorAgentRoot = Path.join(localAppData, "cursor-agent", "versions");
93966
+ try {
93967
+ const versions = NFS.readdirSync(cursorAgentRoot);
93968
+ if (versions.length === 0) return null;
93969
+ const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
93970
+ for (const version of sortedVersions) try {
93971
+ const bundledNodePath = Path.join(cursorAgentRoot, version, "node.exe");
93972
+ const entryScriptPath = Path.join(cursorAgentRoot, version, "index.js");
93973
+ if (NFS.statSync(bundledNodePath).isFile() && NFS.statSync(entryScriptPath).isFile()) return {
93974
+ command: bundledNodePath,
93975
+ args: [entryScriptPath]
93976
+ };
93977
+ } catch {}
93978
+ } catch {}
93979
+ return null;
93980
+ };
93981
+ const resolveWindowsCmdEntryScript = (command) => {
93982
+ if (!isWindows) return null;
93983
+ const pathDirectories = (process.env.PATH ?? "").split(Path.delimiter).filter(Boolean);
93984
+ for (const directory of pathDirectories) {
93985
+ const cmdFilePath = Path.join(directory, `${command}.cmd`);
93986
+ try {
93987
+ if (!NFS.statSync(cmdFilePath).isFile()) continue;
93988
+ } catch {
93989
+ continue;
93990
+ }
93991
+ try {
93992
+ const entryScriptMatch = NFS.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
93993
+ if (!entryScriptMatch) continue;
93994
+ const resolvedScriptPath = Path.resolve(directory, entryScriptMatch[1]);
93995
+ if (NFS.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
93996
+ } catch {}
93997
+ }
93998
+ return null;
93999
+ };
94000
+ const spawnAgent = (command, args, cwd) => new Promise((resolve, reject) => {
94001
+ const child = spawn(command, [...args], {
94002
+ cwd,
94003
+ stdio: "inherit"
94004
+ });
94005
+ child.on("error", reject);
94006
+ child.on("close", (code) => resolve(code ?? 0));
94007
+ });
94008
+ const launchCliAgent = async (agentId, prompt, cwd) => {
94009
+ const binary = CLI_AGENT_BINARIES[agentId];
94010
+ const agentArgs = [...CLI_AGENT_AUTO_FLAGS[agentId], prompt];
94011
+ if (isWindows) {
94012
+ if (agentId === "cursor") {
94013
+ const cursorResolution = resolveCursorBundledNode();
94014
+ if (cursorResolution) return spawnAgent(cursorResolution.command, [...cursorResolution.args, ...agentArgs], cwd);
94015
+ }
94016
+ const entryScript = resolveWindowsCmdEntryScript(binary);
94017
+ if (entryScript) return spawnAgent(process.execPath, [entryScript, ...agentArgs], cwd);
94018
+ }
94019
+ try {
94020
+ return await spawnAgent(binary, agentArgs, cwd);
94021
+ } catch {
94022
+ throw new Error(`Failed to launch ${binary}`);
94023
+ }
94024
+ };
94025
+ const CLIPBOARD_COMMANDS = [
94026
+ {
94027
+ binary: "pbcopy",
94028
+ args: []
94029
+ },
94030
+ {
94031
+ binary: "wl-copy",
94032
+ args: []
94033
+ },
94034
+ {
94035
+ binary: "xclip",
94036
+ args: ["-selection", "clipboard"]
94037
+ },
94038
+ {
94039
+ binary: "xsel",
94040
+ args: ["--clipboard", "--input"]
94041
+ },
94042
+ {
94043
+ binary: "clip",
94044
+ args: []
94045
+ }
94046
+ ];
94047
+ const copyToClipboard = (text) => {
94048
+ const command = CLIPBOARD_COMMANDS.find((candidate) => isCommandAvailable(candidate.binary));
94049
+ if (!command) return Promise.resolve(false);
94050
+ return new Promise((resolve) => {
94051
+ const child = spawn(command.binary, command.args);
94052
+ child.on("error", () => resolve(false));
94053
+ child.on("close", (code) => resolve(code === 0));
94054
+ child.stdin.on("error", () => resolve(false));
94055
+ child.stdin.end(text);
94056
+ });
94057
+ };
94058
+ //#endregion
94090
94059
  //#region src/cli/utils/handoff-to-agent.ts
94091
94060
  const CLIPBOARD_CHOICE = "clipboard";
94092
94061
  const SKIP_CHOICE = "skip";
@@ -94150,6 +94119,10 @@ const maybeOfferActionUpgrade = async (projectRoot) => {
94150
94119
  }
94151
94120
  if (await upgradeGitHubActionsWorkflow(workflow)) recordActionUpgradeDecision(projectRoot, "accepted");
94152
94121
  };
94122
+ const detectLaunchableAgents = async () => {
94123
+ const detected = new Set(await detectAvailableAgents());
94124
+ return Object.keys(CLI_AGENT_BINARIES).filter((agentId) => detected.has(agentId) && isCommandAvailable(CLI_AGENT_BINARIES[agentId]));
94125
+ };
94153
94126
  const handoffToAgent = async (input) => {
94154
94127
  if (!input.interactive || input.diagnostics.length === 0) return;
94155
94128
  cliLogger.break();
@@ -94464,29 +94437,15 @@ const readChangedFilesFrom = (filePath) => {
94464
94437
  return [...uniqueFiles];
94465
94438
  };
94466
94439
  //#endregion
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
- };
94440
+ //#region src/cli/utils/filter-scans-for-surface.ts
94441
+ const filterScansForSurface = (completedScans, surface) => completedScans.flatMap((scan) => filterDiagnosticsForSurface([...scan.result.diagnostics], surface, scan.config));
94478
94442
  //#endregion
94479
- //#region src/cli/utils/score-band-label.ts
94480
- const scoreBandLabel = (score) => {
94443
+ //#region src/cli/utils/render-multi-project-summary.ts
94444
+ const getScoreLabel = (score) => {
94481
94445
  if (score >= 75) return "Great";
94482
94446
  if (score >= 50) return "OK";
94483
94447
  return "Needs work";
94484
94448
  };
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
94490
94449
  const buildSummaryLine = (entry, longestProjectNameLength) => {
94491
94450
  const paddedName = entry.projectName.padEnd(longestProjectNameLength);
94492
94451
  const nameRendering = entry.score !== null ? colorizeByScore(paddedName, entry.score) : highlighter.dim(paddedName);
@@ -94495,7 +94454,7 @@ const buildSummaryLine = (entry, longestProjectNameLength) => {
94495
94454
  return ` ${nameRendering} ${highlighter.dim("no score")} ${highlighter.dim(issueLabel)}`;
94496
94455
  }
94497
94456
  const scoreRendering = colorizeByScore(String(entry.score).padStart(3), entry.score);
94498
- const label = colorizeByScore(scoreBandLabel(entry.score), entry.score);
94457
+ const label = colorizeByScore(getScoreLabel(entry.score), entry.score);
94499
94458
  const issuesParts = [];
94500
94459
  if (entry.errorCount > 0) issuesParts.push(highlighter.error(`${entry.errorCount} ${entry.errorCount === 1 ? "error" : "errors"}`));
94501
94460
  const warningCount = entry.issueCount - entry.errorCount;
@@ -94517,7 +94476,14 @@ const printMultiProjectSummary = (input) => gen(function* () {
94517
94476
  const projectRootByDiagnostic = /* @__PURE__ */ new WeakMap();
94518
94477
  for (const scan of completedScans) for (const diagnostic of scan.result.diagnostics) projectRootByDiagnostic.set(diagnostic, scan.result.project.rootDirectory);
94519
94478
  const resolveDiagnosticSourceRoot = (diagnostic) => projectRootByDiagnostic.get(diagnostic) ?? "";
94520
- const totalScannedFileCount = countUniqueScannedFiles(completedScans.map((scan) => scan.result));
94479
+ const uniqueScannedFilePaths = /* @__PURE__ */ new Set();
94480
+ let fileCountFromScansWithoutPaths = 0;
94481
+ for (const scan of completedScans) {
94482
+ const scannedFilePaths = scan.result.scannedFilePaths;
94483
+ if (scannedFilePaths && scannedFilePaths.length > 0) for (const filePath of scannedFilePaths) uniqueScannedFilePaths.add(filePath);
94484
+ else fileCountFromScansWithoutPaths += scan.result.scannedFileCount ?? scan.result.project.sourceFileCount;
94485
+ }
94486
+ const totalScannedFileCount = uniqueScannedFilePaths.size + fileCountFromScansWithoutPaths;
94521
94487
  yield* log(`${highlighter.success("✔")} Scanned ${totalScannedFileCount} ${totalScannedFileCount === 1 ? "file" : "files"} in ${formatElapsedTime(totalElapsedMilliseconds)}`);
94522
94488
  if (displayDiagnostics.length > 0) {
94523
94489
  yield* log("");
@@ -94789,14 +94755,10 @@ const parseFileLineArgument = (rawArgument) => {
94789
94755
  };
94790
94756
  //#endregion
94791
94757
  //#region src/cli/utils/select-projects.ts
94792
- const discoverWorkspacePackages = (rootDirectory) => {
94793
- const hasRootPackageJson = isFile(Path.join(rootDirectory, "package.json"));
94794
- const packages = listWorkspacePackages(rootDirectory);
94795
- if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return discoverReactSubprojects(rootDirectory);
94796
- return packages;
94797
- };
94798
94758
  const selectProjects = async (rootDirectory, projectFlag, skipPrompts, configProjects) => {
94799
- const packages = discoverWorkspacePackages(rootDirectory);
94759
+ const hasRootPackageJson = isFile(Path.join(rootDirectory, "package.json"));
94760
+ let packages = listWorkspacePackages(rootDirectory);
94761
+ if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) packages = discoverReactSubprojects(rootDirectory);
94800
94762
  if (projectFlag) return resolveProjectFlag(projectFlag, packages, rootDirectory);
94801
94763
  const configRequestedNames = (configProjects ?? []).map((requestedName) => requestedName.trim()).filter((requestedName) => requestedName.length > 0);
94802
94764
  if (configRequestedNames.length > 0) {
@@ -94925,14 +94887,6 @@ const projectManifestChanged = (rootDirectory, projectDirectory, diffInfo) => {
94925
94887
  return diffInfo.changedFiles.some((filePath) => toForwardSlashes(filePath) === manifestPath);
94926
94888
  };
94927
94889
  //#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
94936
94890
  //#region src/cli/utils/warn-deprecated-fail-on.ts
94937
94891
  const warnDeprecatedFailOn = (flags, userConfig) => {
94938
94892
  const usedFlag = flags.failOn !== void 0;
@@ -95059,11 +95013,12 @@ const finalizeScans = (input) => {
95059
95013
  }));
95060
95014
  }
95061
95015
  const blockingLevel = resolveBlockingLevel(input.flags, input.userConfig);
95062
- if (shouldFailScanGate({
95063
- scans: input.completedScans,
95064
- blockingLevel,
95065
- diagnosticsAreGateExempt: input.isScoreOnly || baselineDegraded
95066
- })) process.exitCode = 1;
95016
+ if (input.completedScans.some(({ result }) => hasLintHardFailure(result)) && blockingLevel !== "none") {
95017
+ process.exitCode = 1;
95018
+ return;
95019
+ }
95020
+ if (input.isScoreOnly || baselineDegraded) return;
95021
+ if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), blockingLevel)) process.exitCode = 1;
95067
95022
  };
95068
95023
  const buildChangedFilesDiffInfo = (changedFiles) => ({
95069
95024
  currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
@@ -95339,13 +95294,13 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
95339
95294
  completedScans.push(scanOutcome);
95340
95295
  }
95341
95296
  if (!isQuiet && isMultiProject && completedScans.length > 0) {
95342
- const showShareLink = !isShareOptedOut(completedScans, scanOptions.noScore) && !scanOptions.isCi;
95297
+ const shouldShowShareLink = !isShareOptedOut(completedScans, scanOptions.noScore) && !scanOptions.isCi;
95343
95298
  await runPromise(printMultiProjectSummary({
95344
95299
  completedScans,
95345
95300
  categoryFilters,
95346
95301
  verbose: Boolean(flags.verbose),
95347
95302
  outputDirectory: flags.outputDir,
95348
- isOffline: !showShareLink,
95303
+ isOffline: !shouldShowShareLink,
95349
95304
  projectName: Path.basename(resolvedDirectory),
95350
95305
  totalElapsedMilliseconds: performance$1.now() - scanLoopStartTime
95351
95306
  }));
@@ -96320,20 +96275,6 @@ const COMMAND_FLAG_SPECS = new Map([
96320
96275
  longOptionsWithOptionalValues: /* @__PURE__ */ new Set(),
96321
96276
  shortOptionsWithoutValues: new Set(["-h"]),
96322
96277
  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"])
96337
96278
  }]
96338
96279
  ]);
96339
96280
  const isFlagLike = (argument) => argument.startsWith("-") && argument !== "-";
@@ -96513,35 +96454,6 @@ rules.command("category <category> <severity>").description("Set the severity fo
96513
96454
  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()));
96514
96455
  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()));
96515
96456
  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
- });
96545
96457
  process.stdout.on("error", (error) => {
96546
96458
  if (error.code === "EPIPE") process.exit(0);
96547
96459
  });
@@ -96568,7 +96480,7 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
96568
96480
  handleError(error, { sentryEventId });
96569
96481
  });
96570
96482
  //#endregion
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 };
96483
+ export {};
96572
96484
 
96573
96485
  //# sourceMappingURL=cli.js.map
96574
- //# debugId=be7f877d-9463-5212-8e1f-dc5feea97da0
96486
+ //# debugId=729213e0-f120-5159-8212-e0a881c2cf17