react-doctor 0.2.11-dev.402c7ea → 0.2.11-dev.b5cf767

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,6 +43,32 @@ Works with Claude Code, Cursor, Codex, OpenCode, and many more.
43
43
 
44
44
  Add the reusable GitHub Action from Marketplace to scan every pull request, show inline annotations, and leave findings where reviewers already look.
45
45
 
46
+ ```yaml
47
+ name: React Doctor
48
+
49
+ on:
50
+ pull_request:
51
+ types: [opened, synchronize, reopened, ready_for_review]
52
+
53
+ permissions:
54
+ contents: read
55
+ pull-requests: write
56
+ issues: write
57
+
58
+ concurrency:
59
+ group: react-doctor-${{ github.event.pull_request.number || github.ref }}
60
+ cancel-in-progress: true
61
+
62
+ jobs:
63
+ react-doctor:
64
+ runs-on: ubuntu-latest
65
+ steps:
66
+ - uses: actions/checkout@v5
67
+ - uses: millionco/react-doctor@v1
68
+ ```
69
+
70
+ React Doctor scans the files changed in the pull request, emits inline annotations, blocks on error-level findings, and updates one sticky PR comment with the score and issue summary. The built-in GitHub token is used automatically; no secret or PAT is required. On forked PRs where GitHub withholds write permissions, the scan and annotations still run, but the sticky comment may be skipped.
71
+
46
72
  [Add GitHub Action →](https://github.com/marketplace/actions/react-doctor)
47
73
 
48
74
  ## Contributing
@@ -3050,6 +3050,7 @@ const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
3050
3050
  const MILLISECONDS_PER_SECOND = 1e3;
3051
3051
  const SCORE_API_URL = "https://www.react.doctor/api/score";
3052
3052
  const SHARE_BASE_URL = "https://www.react.doctor/share";
3053
+ const PROMPTS_RULES_BASE_URL = "https://www.react.doctor/prompts/rules";
3053
3054
  const FETCH_TIMEOUT_MS = 1e4;
3054
3055
  const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
3055
3056
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
@@ -7360,6 +7361,13 @@ const highlighter = {
7360
7361
  gray: import_picocolors.default.gray,
7361
7362
  bold: import_picocolors.default.bold
7362
7363
  };
7364
+ /**
7365
+ * Canonical URL for a rule's reviewer-tested fix recipe, served at
7366
+ * `https://www.react.doctor/prompts/rules/<plugin>/<rule>.md`. The
7367
+ * `/doctor` playbook fetches it on demand so each fix follows the
7368
+ * canonical recipe instead of being improvised per diagnostic.
7369
+ */
7370
+ const buildRulePromptUrl = (plugin, rule) => `${PROMPTS_RULES_BASE_URL}/${plugin}/${rule}.md`;
7363
7371
  const groupBy = (items, keyFn) => {
7364
7372
  const groups = /* @__PURE__ */ new Map();
7365
7373
  for (const item of items) {
@@ -7407,6 +7415,6 @@ const cliLogger = {
7407
7415
  }
7408
7416
  };
7409
7417
  //#endregion
7410
- export { isReactDoctorError as A, filterSourceFiles as C, groupBy as D, getDiffInfo as E, runInspect as F, toRelativePath as I, listWorkspacePackages as M, resolveScanTarget as N, highlighter as O, restoreLegacyThrow as P, filterDiagnosticsForSurface as S, formatReactDoctorError as T, Score as _, DeadCode as a, buildJsonReportError as b, LintPartialFailures as c, OXLINT_NODE_REQUIREMENT as d, Progress as f, SKILL_NAME as g, SHARE_BASE_URL as h, Config as i, layerOtlp as j, isMonorepoRoot as k, Linter as l, Reporter as m, cli_logger_exports as n, Files as o, Project as p, CANONICAL_GITHUB_URL as r, Git as s, cliLogger as t, NodeResolver as u, StagedFiles as v, formatErrorChain as w, discoverReactSubprojects as x, buildJsonReport as y };
7418
+ export { isMonorepoRoot as A, filterDiagnosticsForSurface as C, getDiffInfo as D, formatReactDoctorError as E, restoreLegacyThrow as F, runInspect as I, toRelativePath as L, layerOtlp as M, listWorkspacePackages as N, groupBy as O, resolveScanTarget as P, discoverReactSubprojects as S, formatErrorChain as T, Score as _, DeadCode as a, buildJsonReportError as b, LintPartialFailures as c, OXLINT_NODE_REQUIREMENT as d, Progress as f, SKILL_NAME as g, SHARE_BASE_URL as h, Config as i, isReactDoctorError as j, highlighter as k, Linter as l, Reporter as m, cli_logger_exports as n, Files as o, Project as p, CANONICAL_GITHUB_URL as r, Git as s, cliLogger as t, NodeResolver as u, StagedFiles as v, filterSourceFiles as w, buildRulePromptUrl as x, buildJsonReport as y };
7411
7419
 
7412
- //# sourceMappingURL=cli-logger-Df45H6Lw.js.map
7420
+ //# sourceMappingURL=cli-logger-CmMJBgYF.js.map
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./rolldown-runtime-uZX_iqCz.js";
2
- import { A as isReactDoctorError, C as filterSourceFiles, D as groupBy, E as getDiffInfo, F as runInspect, I as toRelativePath, M as listWorkspacePackages, N as resolveScanTarget, O as highlighter, P as restoreLegacyThrow, S as filterDiagnosticsForSurface, T as formatReactDoctorError, _ as Score, a as DeadCode, b as buildJsonReportError, c as LintPartialFailures, d as OXLINT_NODE_REQUIREMENT, f as Progress, g as SKILL_NAME, h as SHARE_BASE_URL, i as Config, j as layerOtlp, k as isMonorepoRoot, l as Linter, m as Reporter, o as Files, p as Project, r as CANONICAL_GITHUB_URL, s as Git, t as cliLogger, u as NodeResolver, v as StagedFiles, w as formatErrorChain, x as discoverReactSubprojects, y as buildJsonReport } from "./cli-logger-Df45H6Lw.js";
2
+ import { A as isMonorepoRoot, C as filterDiagnosticsForSurface, D as getDiffInfo, E as formatReactDoctorError, F as restoreLegacyThrow, I as runInspect, L as toRelativePath, M as layerOtlp, N as listWorkspacePackages, O as groupBy, P as resolveScanTarget, S as discoverReactSubprojects, T as formatErrorChain, _ as Score, a as DeadCode, b as buildJsonReportError, c as LintPartialFailures, d as OXLINT_NODE_REQUIREMENT, f as Progress, g as SKILL_NAME, h as SHARE_BASE_URL, i as Config, j as isReactDoctorError, k as highlighter, l as Linter, m as Reporter, o as Files, p as Project, r as CANONICAL_GITHUB_URL, s as Git, t as cliLogger, u as NodeResolver, v as StagedFiles, w as filterSourceFiles, x as buildRulePromptUrl, y as buildJsonReport } from "./cli-logger-CmMJBgYF.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { execFileSync, execSync } from "node:child_process";
5
5
  import path, { join } from "node:path";
6
- import { accessSync, chmodSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
6
+ import fs, { accessSync, chmodSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, rmdirSync, statSync, writeFileSync } from "node:fs";
7
7
  import process$1 from "node:process";
8
8
  import * as Effect from "effect/Effect";
9
9
  import * as Layer from "effect/Layer";
@@ -6220,6 +6220,8 @@ const padRuleNameToColumn = (ruleName, columnWidth) => {
6220
6220
  return ruleName + " ".repeat(columnWidth - ruleName.length);
6221
6221
  };
6222
6222
  const grayLine = (text) => highlighter.gray(text);
6223
+ const FETCH_FIX_RECIPE_LABEL = "Fetch & follow the canonical fix recipe before fixing";
6224
+ const formatFixRecipeLine = (diagnostic) => `${FETCH_FIX_RECIPE_LABEL}: ${buildRulePromptUrl(diagnostic.plugin, diagnostic.rule)}`;
6223
6225
  const buildCompactRuleGroupLine = (ruleKey, ruleDiagnostics, ruleNameColumnWidth) => {
6224
6226
  const firstDiagnostic = ruleDiagnostics[0];
6225
6227
  const icon = colorizeBySeverity(firstDiagnostic.severity === "error" ? "✗" : "⚠", firstDiagnostic.severity);
@@ -6255,6 +6257,7 @@ const buildVerboseRuleGroupLines = (ruleKey, ruleDiagnostics, ruleNameColumnWidt
6255
6257
  const firstDiagnostic = ruleDiagnostics[0];
6256
6258
  lines.push(grayLine(indentMultilineText(firstDiagnostic.message, " ")));
6257
6259
  if (firstDiagnostic.help) lines.push(grayLine(indentMultilineText(`→ ${firstDiagnostic.help}`, " ")));
6260
+ lines.push(grayLine(` ${formatFixRecipeLine(firstDiagnostic)}`));
6258
6261
  const fileSites = buildVerboseSiteMap(ruleDiagnostics);
6259
6262
  for (const [filePath, sites] of fileSites) if (sites.length > 0) for (const site of sites) {
6260
6263
  lines.push(grayLine(` ${filePath}:${site.line}`));
@@ -6299,6 +6302,7 @@ const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
6299
6302
  ];
6300
6303
  if (firstDiagnostic.help) sections.push("", `Suggestion: ${firstDiagnostic.help}`);
6301
6304
  if (firstDiagnostic.url) sections.push("", `Docs: ${firstDiagnostic.url}`);
6305
+ sections.push("", formatFixRecipeLine(firstDiagnostic));
6302
6306
  sections.push("", "Files:");
6303
6307
  const fileSites = buildVerboseSiteMap(ruleDiagnostics);
6304
6308
  for (const [filePath, sites] of fileSites) if (sites.length > 0) for (const site of sites) {
@@ -6539,7 +6543,11 @@ const printCountsSummaryLine = (diagnostics, isVerbose) => Effect.gen(function*
6539
6543
  const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
6540
6544
  const issueText = (errorCount > 0 ? highlighter.error : warningCount > 0 ? highlighter.warn : highlighter.dim)(`${totalIssueCount} ${totalIssueCount === 1 ? "issue" : "issues"}`);
6541
6545
  yield* Console.log(` ${issueText}`);
6542
- if (!isVerbose && totalIssueCount > 0) yield* Console.log(highlighter.dim(` Run ${highlighter.info("npx react-doctor@latest --verbose")} to see details`));
6546
+ if (!isVerbose && totalIssueCount > 0) {
6547
+ const exampleDiagnostic = diagnostics.find((diagnostic) => diagnostic.severity === "error") ?? diagnostics[0];
6548
+ yield* Console.log(highlighter.dim(` Run ${highlighter.info("npx react-doctor@latest --verbose")} to list every issue with its fix-recipe URL`));
6549
+ yield* Console.log(highlighter.dim(` Each rule links a canonical fix recipe to fetch & follow before fixing, e.g. ${highlighter.info(buildRulePromptUrl(exampleDiagnostic.plugin, exampleDiagnostic.rule))}`));
6550
+ }
6543
6551
  });
6544
6552
  const printSummary = (input) => Effect.gen(function* () {
6545
6553
  if (input.scoreResult) yield* printScoreHeader(input.scoreResult, input.projectName);
@@ -6666,7 +6674,7 @@ const resolveOxlintNodeEffect = (isLintEnabled, isQuiet) => Effect.gen(function*
6666
6674
  const resolveOxlintNode = (isLintEnabled, isQuiet) => Effect.runPromise(resolveOxlintNodeEffect(isLintEnabled, isQuiet).pipe(Effect.provide(NodeResolver.layerNode)));
6667
6675
  //#endregion
6668
6676
  //#region src/cli/utils/version.ts
6669
- const VERSION = "0.2.11-dev.402c7ea";
6677
+ const VERSION = "0.2.11-dev.b5cf767";
6670
6678
  //#endregion
6671
6679
  //#region src/inspect.ts
6672
6680
  const silentConsole = makeNoopConsole();
@@ -7066,15 +7074,16 @@ const buildIssuesSummary = (input) => {
7066
7074
  if (input.score) lines.push(`Score: ${input.score.score}/100`);
7067
7075
  lines.push(`${input.diagnostics.length} issues found`);
7068
7076
  lines.push("");
7069
- const sortedRules = [...groupBy([...input.diagnostics], (diagnostic) => diagnostic.rule).entries()].sort(([, diagnosticsA], [, diagnosticsB]) => diagnosticsB.length - diagnosticsA.length);
7077
+ const sortedRules = [...groupBy([...input.diagnostics], (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()].sort(([, diagnosticsA], [, diagnosticsB]) => diagnosticsB.length - diagnosticsA.length);
7070
7078
  const visibleRules = sortedRules.slice(0, MAX_RULES_SHOWN);
7071
- for (const [rule, ruleDiagnostics] of visibleRules) {
7079
+ for (const [ruleKey, ruleDiagnostics] of visibleRules) {
7072
7080
  const severity = ruleDiagnostics[0].severity;
7073
7081
  const uniqueFiles = [...new Set(ruleDiagnostics.map((diagnostic) => diagnostic.filePath))];
7074
7082
  const shownFiles = uniqueFiles.slice(0, MAX_FILES_PER_RULE);
7075
7083
  const remainingFileCount = uniqueFiles.length - shownFiles.length;
7076
- lines.push(`${severity === "error" ? "ERROR" : "WARN"} ${rule} (×${ruleDiagnostics.length})`);
7084
+ lines.push(`${severity === "error" ? "ERROR" : "WARN"} ${ruleKey} (×${ruleDiagnostics.length})`);
7077
7085
  lines.push(` ${ruleDiagnostics[0].message}`);
7086
+ lines.push(` ${formatFixRecipeLine(ruleDiagnostics[0])}`);
7078
7087
  for (const filePath of shownFiles) {
7079
7088
  const firstSite = ruleDiagnostics.find((diagnostic) => diagnostic.filePath === filePath && diagnostic.line > 0);
7080
7089
  lines.push(` - ${filePath}${firstSite ? `:${firstSite.line}` : ""}`);
@@ -7094,11 +7103,12 @@ const buildIssuesSummary = (input) => {
7094
7103
  lines.push("");
7095
7104
  lines.push("## How to fix");
7096
7105
  lines.push("1. Run `npx react-doctor@latest --verbose` to see full details");
7097
- lines.push("2. Fix errors first, then warnings. Start with high-count rules.");
7098
- lines.push("3. Read the code before acting. Treat findings as hypotheses, not commands.");
7099
- lines.push("4. Fix root causes, not symptoms. Don't suppress rules without evidence.");
7100
- lines.push("5. Run `npx react-doctor@latest --verbose --diff` after changes to verify.");
7101
- lines.push("6. Split unrelated fixes into separate PRs.");
7106
+ lines.push("2. For each rule above, fetch & follow its canonical fix recipe URL before fixing.");
7107
+ lines.push("3. Fix errors first, then warnings. Start with high-count rules.");
7108
+ lines.push("4. Read the code before acting. Treat findings as hypotheses, not commands.");
7109
+ lines.push("5. Fix root causes, not symptoms. Don't suppress rules without evidence.");
7110
+ lines.push("6. Run `npx react-doctor@latest --verbose --diff` after changes to verify.");
7111
+ lines.push("7. Split unrelated fixes into separate PRs.");
7102
7112
  return lines.join("\n");
7103
7113
  };
7104
7114
  const copyToClipboard = (text) => {
@@ -7153,6 +7163,29 @@ const promptCopyIssues = async (input) => {
7153
7163
  else cliLogger.log(issuesSummary);
7154
7164
  };
7155
7165
  //#endregion
7166
+ //#region src/cli/utils/path-format.ts
7167
+ const toForwardSlashes = (filePath) => filePath.replaceAll("\\", "/");
7168
+ //#endregion
7169
+ //#region src/cli/utils/read-changed-files-from.ts
7170
+ const isSafeRelativePath = (filePath) => {
7171
+ if (filePath.length === 0) return false;
7172
+ if (filePath.includes("\0")) return false;
7173
+ if (path.isAbsolute(filePath)) return false;
7174
+ const normalized = path.posix.normalize(filePath);
7175
+ if (normalized === "." || normalized.startsWith("../") || normalized === "..") return false;
7176
+ return normalized === filePath;
7177
+ };
7178
+ const readChangedFilesFrom = (filePath) => {
7179
+ const raw = fs.readFileSync(filePath, "utf8");
7180
+ const uniqueFiles = /* @__PURE__ */ new Set();
7181
+ for (const line of raw.split(/\r?\n/)) {
7182
+ const candidate = toForwardSlashes(line.trim());
7183
+ if (!isSafeRelativePath(candidate)) continue;
7184
+ uniqueFiles.add(candidate);
7185
+ }
7186
+ return [...uniqueFiles];
7187
+ };
7188
+ //#endregion
7156
7189
  //#region src/cli/utils/render-multi-project-summary.ts
7157
7190
  const SUMMARY_BAR_WIDTH_CHARS = 20;
7158
7191
  const buildMiniBar = (score) => {
@@ -7215,6 +7248,7 @@ const printMultiProjectSummary = (input) => Effect.gen(function* () {
7215
7248
  };
7216
7249
  });
7217
7250
  const longestProjectNameLength = Math.max(...entries.map((entry) => entry.projectName.length));
7251
+ yield* Console.log("");
7218
7252
  for (const entry of entries) yield* Console.log(buildSummaryLine(entry, longestProjectNameLength));
7219
7253
  yield* Console.log("");
7220
7254
  });
@@ -7461,7 +7495,7 @@ const warnSetupPromptFailure = async (options, error) => {
7461
7495
  return;
7462
7496
  }
7463
7497
  try {
7464
- const { cliLogger } = await import("./cli-logger-Df45H6Lw.js").then((n) => n.n);
7498
+ const { cliLogger } = await import("./cli-logger-CmMJBgYF.js").then((n) => n.n);
7465
7499
  cliLogger.warn(message);
7466
7500
  } catch {}
7467
7501
  };
@@ -7611,7 +7645,6 @@ const resolveFailOnLevel = (flags, userConfig) => {
7611
7645
  };
7612
7646
  //#endregion
7613
7647
  //#region src/cli/utils/resolve-project-diff-include-paths.ts
7614
- const toForwardSlashes = (filePath) => filePath.replaceAll("\\", "/");
7615
7648
  const resolveProjectDiffIncludePaths = (rootDirectory, projectDirectory, diffInfo) => {
7616
7649
  const changedSourceFiles = filterSourceFiles(diffInfo.changedFiles);
7617
7650
  const relativeProjectDirectory = toForwardSlashes(path.relative(rootDirectory, projectDirectory));
@@ -7810,7 +7843,7 @@ const validateModeFlags = (flags) => {
7810
7843
  if (flags.yes && flags.full) throw new Error("Cannot combine --yes and --full; pick one.");
7811
7844
  if (flags.score && flags.json) throw new Error("Cannot combine --score and --json; pick one output mode.");
7812
7845
  if (flags.prComment && (flags.json || flags.score)) throw new Error("--pr-comment cannot be combined with --json or --score.");
7813
- if (flags.annotations && (flags.json || flags.score)) throw new Error("--annotations cannot be combined with --json or --score.");
7846
+ if (flags.annotations && flags.score) throw new Error("--annotations cannot be combined with --score.");
7814
7847
  if (flags.explain !== void 0 && flags.why !== void 0) throw new Error("Use --explain or --why, not both — they're aliases of the same flag.");
7815
7848
  if ((flags.explain ?? flags.why) !== void 0 && (flags.json || flags.score || flags.annotations || flags.staged)) throw new Error("--explain cannot be combined with --json, --score, --annotations, or --staged.");
7816
7849
  };
@@ -7836,6 +7869,12 @@ const finalizeScans = (input) => {
7836
7869
  const ciFailureDiagnostics = filterDiagnosticsForSurface(input.diagnostics, "ciFailure", input.userConfig);
7837
7870
  if (!input.isScoreOnly && shouldFailForDiagnostics(ciFailureDiagnostics, resolveFailOnLevel(input.flags, input.userConfig))) process.exitCode = 1;
7838
7871
  };
7872
+ const buildChangedFilesDiffInfo = (changedFiles) => ({
7873
+ currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
7874
+ baseBranch: process.env.GITHUB_BASE_REF?.trim() || "pull request target",
7875
+ changedFiles,
7876
+ isCurrentChanges: false
7877
+ });
7839
7878
  const inspectAction = async (directory, flags) => {
7840
7879
  const isScoreOnly = Boolean(flags.score);
7841
7880
  const isJsonMode = Boolean(flags.json);
@@ -7938,9 +7977,10 @@ const inspectAction = async (directory, flags) => {
7938
7977
  return;
7939
7978
  }
7940
7979
  const projectDirectories = await selectProjects(resolvedDirectory, flags.project, skipPrompts);
7980
+ const changedFilesDiffInfo = flags.changedFilesFrom && !flags.full ? buildChangedFilesDiffInfo(readChangedFilesFrom(path.resolve(flags.changedFilesFrom))) : null;
7941
7981
  const effectiveDiff = resolveEffectiveDiff(flags, userConfig);
7942
- const diffInfo = effectiveDiff !== void 0 && effectiveDiff !== false || !skipPrompts && !isQuiet ? await getDiffInfo(resolvedDirectory, typeof effectiveDiff === "string" ? effectiveDiff : void 0) : null;
7943
- const isDiffMode = await resolveDiffMode(diffInfo, effectiveDiff, skipPrompts, isQuiet);
7982
+ const diffInfo = changedFilesDiffInfo ?? (changedFilesDiffInfo === null && (effectiveDiff !== void 0 && effectiveDiff !== false || !skipPrompts && !isQuiet) ? await getDiffInfo(resolvedDirectory, typeof effectiveDiff === "string" ? effectiveDiff : void 0) : null);
7983
+ const isDiffMode = changedFilesDiffInfo !== null || await resolveDiffMode(diffInfo, effectiveDiff, skipPrompts, isQuiet);
7944
7984
  setJsonReportMode(isDiffMode ? "diff" : "full");
7945
7985
  if (isDiffMode && diffInfo && !isQuiet) {
7946
7986
  if (diffInfo.isCurrentChanges) cliLogger.log("Scanning uncommitted changes");
@@ -8838,21 +8878,23 @@ const buildWorkflowContent = () => [
8838
8878
  "",
8839
8879
  "on:",
8840
8880
  " pull_request:",
8841
- " branches: [main]",
8881
+ " types: [opened, synchronize, reopened, ready_for_review]",
8842
8882
  "",
8843
8883
  "permissions:",
8844
8884
  " contents: read",
8845
8885
  " pull-requests: write",
8886
+ " issues: write",
8887
+ "",
8888
+ "concurrency:",
8889
+ " group: react-doctor-${{ github.event.pull_request.number || github.ref }}",
8890
+ " cancel-in-progress: true",
8846
8891
  "",
8847
8892
  "jobs:",
8848
8893
  " react-doctor:",
8849
8894
  " runs-on: ubuntu-latest",
8850
8895
  " steps:",
8851
- " - uses: actions/checkout@v4",
8852
- " - uses: millionco/react-doctor@main",
8853
- " with:",
8854
- " github-token: ${{ secrets.GITHUB_TOKEN }}",
8855
- " diff: main",
8896
+ " - uses: actions/checkout@v5",
8897
+ " - uses: millionco/react-doctor@v1",
8856
8898
  ""
8857
8899
  ].join("\n");
8858
8900
  const runInstallReactDoctor = async (options = {}) => {
@@ -9050,6 +9092,7 @@ const ROOT_FLAG_SPEC = {
9050
9092
  "--yes"
9051
9093
  ]),
9052
9094
  longOptionsWithRequiredValues: new Set([
9095
+ "--changed-files-from",
9053
9096
  "--explain",
9054
9097
  "--fail-on",
9055
9098
  "--project",
@@ -9143,10 +9186,16 @@ const stripUnknownCliFlags = (argv) => {
9143
9186
  ];
9144
9187
  };
9145
9188
  //#endregion
9189
+ //#region src/cli/utils/unref-stdin.ts
9190
+ const unrefStdin = () => {
9191
+ process.stdin.unref?.();
9192
+ };
9193
+ //#endregion
9146
9194
  //#region src/cli/index.ts
9147
9195
  process.on("SIGINT", exitGracefully);
9148
9196
  process.on("SIGTERM", exitGracefully);
9149
- const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("-y, --yes", "skip prompts, scan all workspace projects").option("--full", "force a full scan (overrides any `diff` value in config or `--diff`)").option("--project <name>", "select workspace project (comma-separated for multiple)").option("--diff [base]", "scan only files changed vs base branch (pass `false` to disable; overridden by --full)").option("--no-score", "skip the score API and the share URL").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--fail-on <level>", "exit with error code on diagnostics: error, warning, none (default: none)").option("--annotations", "output diagnostics as GitHub Actions annotations").option("--pr-comment", "tune CLI output for sticky PR comments (drops weak-signal rule families like `design` from the printed list and the fail-on gate; configure via config.surfaces)").option("--explain <file:line>", "diagnose why a rule fired or why a suppression didn't apply at a specific location").option("--why <file:line>", "alias for --explain").option("--respect-inline-disables", "respect inline `// eslint-disable*` / `// oxlint-disable*` comments (default)").option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").addHelpText("after", `
9197
+ unrefStdin();
9198
+ const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("-y, --yes", "skip prompts, scan all workspace projects").option("--full", "force a full scan (overrides any `diff` value in config or `--diff`)").option("--project <name>", "select workspace project (comma-separated for multiple)").option("--diff [base]", "scan only files changed vs base branch (pass `false` to disable; overridden by --full)").option("--changed-files-from <file>", "internal: scan source files listed in a newline-delimited changed-files file").option("--no-score", "skip the score API and the share URL").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--fail-on <level>", "exit with error code on diagnostics: error, warning, none (default: none)").option("--annotations", "output diagnostics as GitHub Actions annotations").option("--pr-comment", "tune CLI output for sticky PR comments (drops weak-signal rule families like `design` from the printed list and the fail-on gate; configure via config.surfaces)").option("--explain <file:line>", "diagnose why a rule fired or why a suppression didn't apply at a specific location").option("--why <file:line>", "alias for --explain").option("--respect-inline-disables", "respect inline `// eslint-disable*` / `// oxlint-disable*` comments (default)").option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").addHelpText("after", `
9150
9199
  ${highlighter.dim("Configuration:")}
9151
9200
  Place a ${highlighter.info("react-doctor.config.json")} (or ${highlighter.info("\"reactDoctor\"")} key in your package.json) in the project root.
9152
9201
  CLI flags always override config values. See the README for the full schema.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.2.11-dev.402c7ea",
3
+ "version": "0.2.11-dev.b5cf767",
4
4
  "description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -58,14 +58,14 @@
58
58
  "oxlint": "^1.66.0",
59
59
  "prompts": "^2.4.2",
60
60
  "typescript": ">=5.0.4 <7",
61
- "oxlint-plugin-react-doctor": "0.2.11-dev.402c7ea"
61
+ "oxlint-plugin-react-doctor": "0.2.11-dev.b5cf767"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/prompts": "^2.4.9",
65
65
  "commander": "^14.0.3",
66
66
  "ora": "^9.4.0",
67
- "@react-doctor/core": "0.2.11",
68
- "@react-doctor/api": "0.2.11"
67
+ "@react-doctor/api": "0.2.11",
68
+ "@react-doctor/core": "0.2.11"
69
69
  },
70
70
  "engines": {
71
71
  "node": "^20.19.0 || >=22.12.0"