react-doctor 0.7.6-dev.0d11bd5 → 0.7.6-dev.17418

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]="58e54b02-7ba2-5f13-a535-98c236467a86")}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]="63b8a227-543e-5501-a998-58c03a1fb10e")}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";
@@ -36341,12 +36341,10 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
36341
36341
  const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
36342
36342
  const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
36343
36343
  const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
36344
- const UTF8_BOM_CHARACTER = "";
36345
- const stripUtf8Bom = (content) => content.startsWith(UTF8_BOM_CHARACTER) ? content.slice(1) : content;
36346
36344
  const cachedPackageJsons = /* @__PURE__ */ new Map();
36347
36345
  const readPackageJsonUncached = (packageJsonPath) => {
36348
36346
  try {
36349
- return JSON.parse(stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8")));
36347
+ return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
36350
36348
  } catch (error) {
36351
36349
  if (error instanceof SyntaxError) return {};
36352
36350
  if (isErrnoException(error)) {
@@ -40019,6 +40017,7 @@ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
40019
40017
  const PNPM_LOCKFILE = "pnpm-lock.yaml";
40020
40018
  const PACKAGE_JSON_FILE = "package.json";
40021
40019
  const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
40020
+ const UTF8_BOM_CHAR = "";
40022
40021
  const HARDENING_SETTING_KEYS = new Set([
40023
40022
  "minimumReleaseAge",
40024
40023
  "blockExoticSubdeps",
@@ -40043,11 +40042,12 @@ const stripInlineComment = (rawValue) => {
40043
40042
  return rawValue;
40044
40043
  };
40045
40044
  const unquote = (rawValue) => rawValue.replace(/^["']|["']$/g, "");
40045
+ const stripBom = (rawContent) => rawContent.startsWith(UTF8_BOM_CHAR) ? rawContent.slice(1) : rawContent;
40046
40046
  const parseHardeningSettings = (content) => {
40047
40047
  let minimumReleaseAge = null;
40048
40048
  let blockExoticSubdeps = null;
40049
40049
  let trustPolicy = null;
40050
- const lines = stripUtf8Bom(content).split(/\r?\n/);
40050
+ const lines = stripBom(content).split(/\r?\n/);
40051
40051
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
40052
40052
  const lineText = lines[lineIndex];
40053
40053
  if (lineText === void 0) continue;
@@ -40081,7 +40081,7 @@ const isPnpmManagedProject = (rootDirectory) => {
40081
40081
  const packageJsonPath = Path.join(rootDirectory, PACKAGE_JSON_FILE);
40082
40082
  if (!isFile(packageJsonPath)) return false;
40083
40083
  try {
40084
- const packageJsonRaw = stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8"));
40084
+ const packageJsonRaw = NFS.readFileSync(packageJsonPath, "utf-8");
40085
40085
  const packageJson = JSON.parse(packageJsonRaw);
40086
40086
  if (packageJson !== null && typeof packageJson === "object" && "packageManager" in packageJson && typeof packageJson.packageManager === "string" && packageJson.packageManager.startsWith("pnpm@")) return true;
40087
40087
  } catch {
@@ -40762,7 +40762,9 @@ const setColorEnabled = (enabled) => {
40762
40762
  highlighter.gray = colors.gray;
40763
40763
  highlighter.bold = colors.bold;
40764
40764
  };
40765
- const computeExplicitLintIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter(isLintableSourceFile) : void 0;
40765
+ const NEXT_ENTRY_FILE_PATTERN = /^(?:\.\/)?(?:src\/)?(?:middleware|proxy)\.(?:tsx?|jsx?|mts|mjs)$/;
40766
+ const shouldIncludeExplicitLintPath = (filePath, project) => JSX_FILE_PATTERN.test(filePath) || project?.framework === "nextjs" && NEXT_ENTRY_FILE_PATTERN.test(filePath);
40767
+ const computeExplicitLintIncludePaths = (includePaths, project) => includePaths.length > 0 ? includePaths.filter((filePath) => shouldIncludeExplicitLintPath(filePath, project)) : void 0;
40766
40768
  const LINGUIST_ATTRIBUTE_PATTERN = /^linguist-(?:vendored|generated)(?:=([a-zA-Z0-9]+))?$/i;
40767
40769
  const FALSY_VALUES = new Set([
40768
40770
  "false",
@@ -41601,11 +41603,11 @@ const resolveDeadCodeTimeout = (input) => {
41601
41603
  phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
41602
41604
  };
41603
41605
  };
41604
- const resolveLintIncludePaths = (rootDirectory, userConfig) => {
41606
+ const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
41605
41607
  if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
41606
41608
  const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
41607
41609
  return listSourceFiles(rootDirectory).filter((filePath) => {
41608
- if (!isLintableSourceFile(filePath)) return false;
41610
+ if (!shouldIncludeExplicitLintPath(filePath, project)) return false;
41609
41611
  return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
41610
41612
  });
41611
41613
  };
@@ -41823,7 +41825,6 @@ const parseGithubViewerPermission = (stdout) => {
41823
41825
  return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
41824
41826
  };
41825
41827
  const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
41826
- const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
41827
41828
  /**
41828
41829
  * `Git` wraps every `git`-via-subprocess call react-doctor makes
41829
41830
  * behind a `Context.Service`. The production layer (`layerNode`)
@@ -41894,14 +41895,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
41894
41895
  args,
41895
41896
  directory
41896
41897
  });
41897
- const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
41898
- "ls-files",
41899
- "-z",
41900
- "--others",
41901
- "--exclude-standard",
41902
- ...includePaths.length > 0 ? ["--", ...includePaths] : []
41903
- ]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
41904
- const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
41905
41898
  const currentBranch = (directory) => runGit(directory, [
41906
41899
  "rev-parse",
41907
41900
  "--abbrev-ref",
@@ -42024,7 +42017,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42024
42017
  githubViewerPermission,
42025
42018
  branchExists,
42026
42019
  mergeBase,
42027
- diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
42020
+ diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
42028
42021
  if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
42029
42022
  if (explicitBaseBranch !== void 0) {
42030
42023
  const range = parseGitDiffRange(explicitBaseBranch);
@@ -42053,7 +42046,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42053
42046
  "HEAD"
42054
42047
  ]);
42055
42048
  if (uncommitted.status !== 0) return null;
42056
- const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
42049
+ const files = splitNullSeparated(uncommitted.stdout);
42057
42050
  if (files.length === 0) return null;
42058
42051
  return {
42059
42052
  currentBranch: resolvedCurrentBranch,
@@ -42083,7 +42076,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42083
42076
  currentBranch: resolvedCurrentBranch,
42084
42077
  baseBranch,
42085
42078
  diffBaseRef: mergeBaseRef,
42086
- changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
42079
+ changedFiles: splitNullSeparated(diff.stdout),
42087
42080
  isCurrentChanges: false
42088
42081
  };
42089
42082
  }).pipe(withSpan("Git.diffSelection")),
@@ -42129,7 +42122,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42129
42122
  stdout: result.stdout
42130
42123
  };
42131
42124
  }).pipe(withSpan("Git.grep")),
42132
- changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
42125
+ changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
42133
42126
  if (files.length === 0) return [];
42134
42127
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
42135
42128
  const result = yield* runGit(directory, [
@@ -42143,14 +42136,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42143
42136
  ...files
42144
42137
  ]);
42145
42138
  if (result.status !== 0) return null;
42146
- const changedLineRanges = parseChangedLineRanges(result.stdout);
42147
- if (cached || !includeUntracked) return changedLineRanges;
42148
- const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
42149
- if (untrackedFilePaths === null) return changedLineRanges;
42150
- return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
42151
- file,
42152
- ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
42153
- }))];
42139
+ return parseChangedLineRanges(result.stdout);
42154
42140
  }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
42155
42141
  });
42156
42142
  })).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
@@ -42902,12 +42888,6 @@ const mapWithConcurrency = async (items, concurrency, task) => {
42902
42888
  if (errors.length > 0) throw errors[0];
42903
42889
  return results;
42904
42890
  };
42905
- const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
42906
- let lineNumber = 1;
42907
- const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
42908
- for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === 10) lineNumber++;
42909
- return lineNumber;
42910
- };
42911
42891
  const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
42912
42892
  const ACCESSORS_MIN_VERSION = "3.15.0";
42913
42893
  const supportsAccessors = (reanimatedVersion) => {
@@ -43479,6 +43459,13 @@ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
43479
43459
  visit(sourceFile);
43480
43460
  return foundRange;
43481
43461
  };
43462
+ const NEWLINE_BYTE = 10;
43463
+ const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
43464
+ let lineNumber = 1;
43465
+ const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
43466
+ for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
43467
+ return lineNumber;
43468
+ };
43482
43469
  /**
43483
43470
  * Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
43484
43471
  * whose enclosing function also carries a `react-hooks-js` (React
@@ -43659,11 +43646,11 @@ const buildRelatedLocations = (labels, filePath) => {
43659
43646
  }
43660
43647
  return related;
43661
43648
  };
43662
- const isOxlintSpan = (value) => isRecord$1(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
43663
- const isOxlintLabel = (value) => isRecord$1(value) && isOxlintSpan(value.span);
43664
- const isMappableOxlintDiagnostic = (value) => isRecord$1(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
43665
- const isEngineFailureDiagnostic = (value) => !isRecord$1(value) || typeof value.filename !== "string" || value.filename.length === 0;
43666
- const isOxlintOutput = (value) => isRecord$1(value) && Array.isArray(value.diagnostics);
43649
+ const isOxlintOutput = (value) => {
43650
+ if (typeof value !== "object" || value === null) return false;
43651
+ const candidate = value;
43652
+ return Array.isArray(candidate.diagnostics);
43653
+ };
43667
43654
  /**
43668
43655
  * Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
43669
43656
  * Tolerates the leading-notice-line shape oxlint sometimes prints
@@ -43682,59 +43669,23 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43682
43669
  throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
43683
43670
  }
43684
43671
  if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
43685
- const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
43686
- if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
43687
43672
  const minifiedFileCache = /* @__PURE__ */ new Map();
43688
- const sourceBufferCache = /* @__PURE__ */ new Map();
43689
- const sourceLinesCache = /* @__PURE__ */ new Map();
43690
- const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
43691
- const readSourceBuffer = (filename) => {
43692
- const absolutePath = resolveAbsolutePath(filename);
43693
- const cached = sourceBufferCache.get(absolutePath);
43694
- if (cached !== void 0) return cached;
43695
- let sourceBuffer;
43696
- try {
43697
- sourceBuffer = NFS.readFileSync(absolutePath);
43698
- } catch {
43699
- sourceBuffer = null;
43700
- }
43701
- sourceBufferCache.set(absolutePath, sourceBuffer);
43702
- return sourceBuffer;
43703
- };
43704
- const readSourceLines = (filename) => {
43705
- const absolutePath = resolveAbsolutePath(filename);
43706
- const cached = sourceLinesCache.get(absolutePath);
43707
- if (cached !== void 0) return cached;
43708
- const sourceBuffer = readSourceBuffer(filename);
43709
- const sourceLines = sourceBuffer ? sourceBuffer.toString("utf8").split("\n") : null;
43710
- sourceLinesCache.set(absolutePath, sourceLines);
43711
- return sourceLines;
43712
- };
43713
43673
  const isMinifiedDiagnosticFile = (filename) => {
43714
- const absolutePath = resolveAbsolutePath(filename);
43674
+ const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
43715
43675
  const cached = minifiedFileCache.get(absolutePath);
43716
43676
  if (cached !== void 0) return cached;
43717
43677
  const minified = isMinifiedSource(absolutePath);
43718
43678
  minifiedFileCache.set(absolutePath, minified);
43719
43679
  return minified;
43720
43680
  };
43721
- return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
43681
+ return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
43722
43682
  const { plugin, rule } = parseRuleCode(diagnostic.code);
43723
43683
  const primaryLabel = diagnostic.labels[0];
43724
43684
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
43725
43685
  const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
43726
43686
  const primarySpan = primaryLabel?.span;
43727
- const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
43728
43687
  const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
43729
43688
  const category = resolveDiagnosticCategory(plugin, rule);
43730
- const matchByOccurrence = resolveMatchByOccurrence(rule, category);
43731
- const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
43732
- const primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
43733
- const primaryLine = sourceLines?.[primaryLineIndex];
43734
- const primaryColumnIndex = primarySpan ? primarySpan.column - 1 : -1;
43735
- const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
43736
- const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
43737
- const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
43738
43689
  return {
43739
43690
  filePath: normalizedFilePath,
43740
43691
  plugin,
@@ -43748,11 +43699,10 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43748
43699
  column: primarySpan?.column ?? 0,
43749
43700
  ...primarySpan ? {
43750
43701
  offset: primarySpan.offset,
43751
- length: primarySpan.length,
43752
- ...primarySpanEndLine !== void 0 ? { endLine: primarySpanEndLine } : {}
43702
+ length: primarySpan.length
43753
43703
  } : {},
43754
43704
  category,
43755
- ...matchByOccurrence ? { matchByOccurrence: true } : {},
43705
+ ...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
43756
43706
  ...relatedLocations.length > 0 ? { relatedLocations } : {}
43757
43707
  };
43758
43708
  }), rootDirectory);
@@ -45539,7 +45489,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
45539
45489
  directory: scanDirectory,
45540
45490
  repo
45541
45491
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
45542
- const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
45492
+ const lintIncludePaths = (input.skipJsxIncludeFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths], project)) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, project);
45543
45493
  const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
45544
45494
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
45545
45495
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
@@ -46284,11 +46234,10 @@ const computeDiagnosticDelta = (input) => {
46284
46234
  * render diff-base mistakes (`GitBaseBranchInvalid` /
46285
46235
  * `GitBaseBranchMissing`) as clean user errors instead of crashes.
46286
46236
  */
46287
- const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runPromise(gen(function* () {
46237
+ const getDiffInfo = (directory, explicitBaseBranch) => runPromise(gen(function* () {
46288
46238
  const selection = yield* (yield* Git).diffSelection({
46289
46239
  directory,
46290
- explicitBaseBranch,
46291
- includeUntracked
46240
+ explicitBaseBranch
46292
46241
  });
46293
46242
  if (selection === null) return null;
46294
46243
  return {
@@ -46624,43 +46573,6 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
46624
46573
  //#region src/cli/utils/constants.ts
46625
46574
  const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
46626
46575
  const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
46627
- const STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES = [
46628
- ".babelrc",
46629
- ".babelrc.json",
46630
- ".eslintignore",
46631
- ".eslintrc.json",
46632
- ".oxlintignore",
46633
- "app.config.cjs",
46634
- "app.config.js",
46635
- "app.config.json",
46636
- "app.config.mjs",
46637
- "app.config.ts",
46638
- "app.json",
46639
- "babel.config.cjs",
46640
- "babel.config.js",
46641
- "babel.config.json",
46642
- "babel.config.mjs",
46643
- "eslint.config.cjs",
46644
- "eslint.config.cts",
46645
- "eslint.config.js",
46646
- "eslint.config.mjs",
46647
- "eslint.config.mts",
46648
- "eslint.config.ts",
46649
- "next.config.cjs",
46650
- "next.config.js",
46651
- "next.config.mjs",
46652
- "next.config.ts",
46653
- "oxlint.config.json",
46654
- "react-doctor.config.json",
46655
- "vite.config.cjs",
46656
- "vite.config.cts",
46657
- "vite.config.js",
46658
- "vite.config.mjs",
46659
- "vite.config.mts",
46660
- "vite.config.ts",
46661
- "vitest.config.js",
46662
- "vitest.config.ts"
46663
- ];
46664
46576
  const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
46665
46577
  const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
46666
46578
  const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
@@ -46675,7 +46587,6 @@ const METRIC = {
46675
46587
  cliInvoked: "cli.invoked",
46676
46588
  cliError: "cli.error",
46677
46589
  cliEnvironmentError: "cli.env_error",
46678
- stagedSnapshotDivergence: "staged.snapshot_divergence",
46679
46590
  projectDetected: "project.detected",
46680
46591
  projectPathSelected: "project.path_selected",
46681
46592
  projectConfigSelected: "project.config_selected",
@@ -46756,7 +46667,7 @@ const makeNoopConsole = () => ({
46756
46667
  });
46757
46668
  //#endregion
46758
46669
  //#region src/cli/utils/version.ts
46759
- const VERSION = "0.7.6-dev.0d11bd5";
46670
+ const VERSION = "0.7.6-dev.0017418";
46760
46671
  //#endregion
46761
46672
  //#region src/cli/utils/json-mode.ts
46762
46673
  let context = null;
@@ -47125,13 +47036,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
47125
47036
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
47126
47037
  * standard `SENTRY_RELEASE` override.
47127
47038
  */
47128
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.0d11bd5`;
47039
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.0017418`;
47129
47040
  /**
47130
47041
  * Deployment environment shown in Sentry's environment filter. Defaults to
47131
47042
  * `production` for tagged releases and `development` for dev/unbuilt versions,
47132
47043
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
47133
47044
  */
47134
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.0d11bd5") ? "development" : "production");
47045
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.0017418") ? "development" : "production");
47135
47046
  /**
47136
47047
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
47137
47048
  * (set to `0` to disable tracing) and falls back to
@@ -47323,7 +47234,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
47323
47234
  "fi"
47324
47235
  ].join(" ");
47325
47236
  const PACKAGE_JSON_FILE_NAME = "package.json";
47326
- const runGitRaw = (projectRoot, args) => {
47237
+ const runGit = (projectRoot, args) => {
47327
47238
  try {
47328
47239
  return execFileSync("git", [...args], {
47329
47240
  cwd: projectRoot,
@@ -47334,12 +47245,11 @@ const runGitRaw = (projectRoot, args) => {
47334
47245
  "ignore"
47335
47246
  ],
47336
47247
  maxBuffer: RUN_GIT_MAX_BUFFER_BYTES
47337
- });
47248
+ }).trim();
47338
47249
  } catch {
47339
47250
  return null;
47340
47251
  }
47341
47252
  };
47342
- const runGit = (projectRoot, args) => runGitRaw(projectRoot, args)?.trim() ?? null;
47343
47253
  const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
47344
47254
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
47345
47255
  const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
@@ -50897,7 +50807,7 @@ const BLOCKING_SUMMARY = {
50897
50807
  const SCOPE_SUMMARY = {
50898
50808
  full: "Scan the whole project on every run",
50899
50809
  files: "Report every issue in changed files",
50900
- lines: "Report issues whose source spans touch changed lines",
50810
+ lines: "Report issues on changed lines",
50901
50811
  changed: "Report only the issues a change introduces"
50902
50812
  };
50903
50813
  const gatesEqual = (left, right) => left.blocking === right.blocking && left.scope === right.scope && left.comment === right.comment && left.reviewComments === right.reviewComments && left.commitStatus === right.commitStatus;
@@ -59210,17 +59120,6 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
59210
59120
  })).toSorted((left, right) => right.fileCount - left.fileCount);
59211
59121
  const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
59212
59122
  //#endregion
59213
- //#region src/cli/utils/has-lint-hard-failure.ts
59214
- const hasLintHardFailure = (result) => result.skippedChecks.includes("lint");
59215
- //#endregion
59216
- //#region src/cli/utils/is-inspect-result-complete.ts
59217
- const isInspectResultComplete = (result) => isScanComplete({
59218
- analyzedFileCount: result.analyzedFiles?.length,
59219
- scannedFileCount: result.scannedFileCount,
59220
- skippedCheckCount: result.skippedChecks.length,
59221
- skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
59222
- });
59223
- //#endregion
59224
59123
  //#region src/cli/utils/scan-result-cache.ts
59225
59124
  const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
59226
59125
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
@@ -59303,7 +59202,7 @@ const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
59303
59202
  return null;
59304
59203
  }
59305
59204
  if (!stats.isFile()) return null;
59306
- if (stats.size > 10485760) return null;
59205
+ if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
59307
59206
  return hashFileContents(absolutePath);
59308
59207
  };
59309
59208
  const buildWorktreeFingerprint = (projectDirectory) => {
@@ -59626,12 +59525,15 @@ const buildOutcomeAttributes = (input) => {
59626
59525
  const summary = summarizeDiagnostics(result.diagnostics);
59627
59526
  const blockingLevel = resolveTelemetryBlocking(input.userConfig);
59628
59527
  const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
59629
- const complete = isInspectResultComplete(result);
59630
- const didLintHardFail = hasLintHardFailure(result);
59631
- const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
59632
59528
  const wouldBlock = !input.scoreOnly && !input.gateExempt && shouldBlockCi(gateDiagnostics, blockingLevel);
59529
+ const complete = isScanComplete({
59530
+ analyzedFileCount: result.analyzedFiles?.length,
59531
+ scannedFileCount: result.scannedFileCount,
59532
+ skippedCheckCount: result.skippedChecks.length,
59533
+ skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
59534
+ });
59633
59535
  const isClean = result.diagnostics.length === 0 && complete;
59634
- const outcome = didLintHardFail ? "error" : wouldBlock ? "blocked" : isClean ? "clean" : "ok";
59536
+ const outcome = wouldBlock ? "blocked" : isClean ? "clean" : "ok";
59635
59537
  const firings = summarizeRuleFirings(result.diagnostics);
59636
59538
  const countByRule = /* @__PURE__ */ new Map();
59637
59539
  const countByCategory = /* @__PURE__ */ new Map();
@@ -59676,7 +59578,7 @@ const buildOutcomeAttributes = (input) => {
59676
59578
  const attributes = {
59677
59579
  ...withNamespace("outcome", {
59678
59580
  status: outcome,
59679
- exitCode: failsOnHardFailure || wouldBlock ? 1 : 0,
59581
+ exitCode: wouldBlock ? 1 : 0,
59680
59582
  wouldBlock,
59681
59583
  blocking: blockingLevel,
59682
59584
  clean: isClean,
@@ -59774,9 +59676,7 @@ const buildScanAttributes = (input) => {
59774
59676
  hasCustomConfig: input.hasCustomConfig,
59775
59677
  rulesConfigured: ruleKeys.length,
59776
59678
  rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
59777
- fileCount: input.result?.scannedFileCount ?? null,
59778
- nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length ?? null,
59779
- multilineDiagnosticCount: input.result?.diagnostics.filter((diagnostic) => (diagnostic.endLine ?? diagnostic.line) > diagnostic.line).length ?? null
59679
+ fileCount: input.result?.scannedFileCount ?? null
59780
59680
  });
59781
59681
  };
59782
59682
  /**
@@ -59845,12 +59745,6 @@ const countDeadlineSkippedFiles = (lintPartialFailures) => sumFileCountsMatching
59845
59745
  const DROPPED_FILES_MESSAGE_PATTERN = /^(\d+) file\(s\) failed to lint and were skipped/;
59846
59746
  const countDroppedLintFiles = (lintPartialFailures) => sumFileCountsMatching(lintPartialFailures, DROPPED_FILES_MESSAGE_PATTERN);
59847
59747
  //#endregion
59848
- //#region src/cli/utils/diagnostic-intersects-line-ranges.ts
59849
- const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
59850
- const diagnosticEndLine = Math.max(diagnostic.line, diagnostic.endLine ?? diagnostic.line);
59851
- return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
59852
- };
59853
- //#endregion
59854
59748
  //#region src/cli/utils/materialize-baseline-files.ts
59855
59749
  /**
59856
59750
  * Materializes the `ref` version of `files` into `tempDirectory` (via
@@ -61036,7 +60930,7 @@ const buildChangedLineMatcher = (directory, changedLineRanges) => {
61036
60930
  const relativePath = toForwardSlashes(path.isAbsolute(diagnostic.filePath) ? path.relative(directory, diagnostic.filePath) : diagnostic.filePath);
61037
60931
  const ranges = rangesByFile.get(relativePath);
61038
60932
  if (ranges === void 0) return false;
61039
- return diagnosticIntersectsLineRanges(diagnostic, ranges);
60933
+ return ranges.some(([start, end]) => diagnostic.line >= start && diagnostic.line <= end);
61040
60934
  };
61041
60935
  };
61042
60936
  const buildIgnoredTags = (userConfig) => {
@@ -63858,15 +63752,10 @@ const warnIfAiTrainingEnvironment = () => {
63858
63752
  //#region src/cli/utils/validate-mode-flags.ts
63859
63753
  const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
63860
63754
  const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
63861
- const validateIncludeUntrackedScope = (includeUntracked, scope) => {
63862
- if (!includeUntracked || scope !== void 0 && scope !== "full") return;
63863
- throw new CliInputError("--include-untracked requires the files, changed, or lines scope (via --scope, --diff, or config).");
63864
- };
63865
63755
  const validateModeFlags = (flags) => {
63866
63756
  if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
63867
63757
  if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
63868
63758
  if (flags.staged && (flags.scope === "full" || flags.scope === "changed")) throw new CliInputError(`Cannot combine --staged with --scope ${flags.scope}; use --scope files or --scope lines, or drop --scope.`);
63869
- if (flags.includeUntracked && flags.staged) throw new CliInputError("Cannot combine --include-untracked with --staged; the git index never holds untracked files.");
63870
63759
  if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
63871
63760
  if (flags.score && flags.telemetry === false) throw new CliInputError("Cannot combine --score with --no-telemetry; --score prints the score that --no-telemetry disables.");
63872
63761
  if (flags.debug && (flags.score === false || flags.telemetry === false)) {
@@ -63875,39 +63764,6 @@ const validateModeFlags = (flags) => {
63875
63764
  }
63876
63765
  };
63877
63766
  //#endregion
63878
- //#region src/cli/utils/parse-staged-snapshot-divergences.ts
63879
- const SNAPSHOT_CONFIG_FILENAMES = new Set([...STAGED_FILES_PROJECT_CONFIG_FILENAMES, ...STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES]);
63880
- const parseStagedSnapshotDivergences = (statusOutput) => {
63881
- const divergentConfigFiles = /* @__PURE__ */ new Set();
63882
- const statusEntries = statusOutput.split("\0").filter((entry) => entry.length > 0);
63883
- for (let entryIndex = 0; entryIndex < statusEntries.length; entryIndex += 1) {
63884
- const entry = statusEntries[entryIndex];
63885
- const [indexStatus, worktreeStatus] = entry;
63886
- const filePath = entry.slice(3);
63887
- const hasRenameOrCopySource = indexStatus === "R" || indexStatus === "C" || worktreeStatus === "R" || worktreeStatus === "C";
63888
- const sourcePath = hasRenameOrCopySource ? statusEntries[entryIndex + 1] : void 0;
63889
- const worktreePaths = worktreeStatus === "R" || worktreeStatus === "C" ? [filePath, sourcePath] : [filePath];
63890
- if (worktreeStatus !== " ") {
63891
- for (const worktreePath of worktreePaths) if (worktreePath && SNAPSHOT_CONFIG_FILENAMES.has(Path.basename(worktreePath))) divergentConfigFiles.add(worktreePath);
63892
- }
63893
- if (hasRenameOrCopySource) entryIndex += 1;
63894
- }
63895
- return [...divergentConfigFiles].sort();
63896
- };
63897
- //#endregion
63898
- //#region src/cli/utils/find-staged-snapshot-divergences.ts
63899
- const findStagedSnapshotDivergences = (directory) => {
63900
- const statusOutput = runGitRaw(directory, [
63901
- "status",
63902
- "--porcelain=v1",
63903
- "-z",
63904
- "--untracked-files=all",
63905
- "--ignored=matching"
63906
- ]);
63907
- if (statusOutput === null) return null;
63908
- return parseStagedSnapshotDivergences(statusOutput);
63909
- };
63910
- //#endregion
63911
63767
  //#region src/cli/commands/inspect.ts
63912
63768
  const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
63913
63769
  if (categoryFilters.size === 0) return [...completedScans];
@@ -63922,14 +63778,9 @@ const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
63922
63778
  /**
63923
63779
  * Post-scan finalization shared by the staged-arm and project-loop
63924
63780
  * paths of `inspectAction`: emit the JSON report (when in JSON mode)
63925
- * and set `process.exitCode = 1` when any scan's lint pass hard-failed
63926
- * (an engine/plugin/binding failure destroys the findings, so success
63927
- * would be a false clean) or a diagnostic at or above the `--blocking`
63928
- * threshold (default `"error"`) reaches the `ciFailure` surface.
63929
- * `--blocking none` keeps the scan advisory (always exits 0), and
63930
- * fail-open degradations — `--no-lint`, `--max-duration` truncation,
63931
- * supply-chain/security skips — stay advisory too, surfaced through
63932
- * `complete: false` in the JSON report.
63781
+ * and set `process.exitCode = 1` when a diagnostic at or above the
63782
+ * `--blocking` threshold (default `"error"`) reaches the `ciFailure`
63783
+ * surface. `--blocking none` keeps the scan advisory (always exits 0).
63933
63784
  */
63934
63785
  const finalizeScans = (input) => {
63935
63786
  const baselineDeltas = input.completedScans.flatMap((scan) => scan.result.baselineDelta ? [scan.result.baselineDelta] : []);
@@ -63959,13 +63810,8 @@ const finalizeScans = (input) => {
63959
63810
  baselineDegraded
63960
63811
  }));
63961
63812
  }
63962
- const blockingLevel = resolveBlockingLevel(input.flags, input.userConfig);
63963
- if (input.completedScans.some(({ result }) => hasLintHardFailure(result)) && blockingLevel !== "none") {
63964
- process.exitCode = 1;
63965
- return;
63966
- }
63967
63813
  if (input.isScoreOnly || baselineDegraded) return;
63968
- if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), blockingLevel)) process.exitCode = 1;
63814
+ if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), resolveBlockingLevel(input.flags, input.userConfig))) process.exitCode = 1;
63969
63815
  };
63970
63816
  const buildChangedFilesDiffInfo = (changedFiles) => ({
63971
63817
  currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
@@ -64003,7 +63849,6 @@ const inspectAction = async (directory, flags) => {
64003
63849
  recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
64004
63850
  try {
64005
63851
  validateModeFlags(flags);
64006
- if (flags.staged) setJsonReportMode("staged");
64007
63852
  await maybeMigrateLegacyConfig(requestedDirectory, {
64008
63853
  isQuiet,
64009
63854
  isStaged: Boolean(flags.staged)
@@ -64019,14 +63864,6 @@ const inspectAction = async (directory, flags) => {
64019
63864
  cliLogger.dim(`Redirected to ${highlighter.info(toRelativePath(resolvedDirectory, requestedDirectory))} via react-doctor config "rootDir".`);
64020
63865
  cliLogger.break();
64021
63866
  }
64022
- if (flags.staged) {
64023
- const divergentConfigFiles = findStagedSnapshotDivergences(resolvedDirectory);
64024
- if (divergentConfigFiles === null) throw new CliInputError("Could not verify that staged configuration matches the worktree. Run the command from a Git worktree with Git available.");
64025
- if (divergentConfigFiles.length > 0) {
64026
- recordCount(METRIC.stagedSnapshotDivergence, 1, { divergentInputCount: divergentConfigFiles.length });
64027
- throw new CliInputError(`Cannot scan staged files while configuration differs between the index and worktree: ${divergentConfigFiles.join(", ")}. Stage or restore those files, then rerun react-doctor --staged.`);
64028
- }
64029
- }
64030
63867
  const explainArgument = flags.explain;
64031
63868
  if (explainArgument !== void 0) {
64032
63869
  await runExplain(explainArgument, {
@@ -64047,6 +63884,7 @@ const inspectAction = async (directory, flags) => {
64047
63884
  json: flags.json
64048
63885
  });
64049
63886
  if (flags.staged) {
63887
+ setJsonReportMode("staged");
64050
63888
  const stagedFiles = await getStagedSourceFiles(resolvedDirectory);
64051
63889
  if (stagedFiles.length === 0) {
64052
63890
  if (isJsonMode) writeJsonReport(buildJsonReport({
@@ -64127,15 +63965,13 @@ const inspectAction = async (directory, flags) => {
64127
63965
  const projectDirectories = await selectProjects(resolvedDirectory, flags.project, skipPrompts, userConfig?.projects);
64128
63966
  const changedFilesDiffInfo = flags.changedFilesFrom ? buildChangedFilesDiffInfo(readChangedFilesFrom(Path.resolve(flags.changedFilesFrom))) : null;
64129
63967
  const requestedScope = resolveScope(flags, userConfig);
64130
- const includeUntracked = flags.includeUntracked ?? false;
64131
63968
  const scopeRequest = requestedScope.scope === void 0 && changedFilesDiffInfo !== null ? {
64132
63969
  ...requestedScope,
64133
63970
  scope: "changed"
64134
63971
  } : requestedScope;
64135
- validateIncludeUntrackedScope(includeUntracked, scopeRequest.scope);
64136
63972
  const wantsDiffMode = scopeRequest.scope !== void 0 && scopeRequest.scope !== "full";
64137
63973
  const shouldDetectDiff = changedFilesDiffInfo === null && (wantsDiffMode || scopeRequest.scope === void 0 && !skipPrompts && !isQuiet);
64138
- const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base, includeUntracked) : null);
63974
+ const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base) : null);
64139
63975
  const scope = await finalizeScope({
64140
63976
  requested: scopeRequest,
64141
63977
  diffInfo,
@@ -64149,8 +63985,7 @@ const inspectAction = async (directory, flags) => {
64149
63985
  const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
64150
63986
  directory: resolvedDirectory,
64151
63987
  baseRef: linesBaseRef ?? void 0,
64152
- files: [...diffInfo.changedFiles],
64153
- includeUntracked
63988
+ files: [...diffInfo.changedFiles]
64154
63989
  }) : null;
64155
63990
  if (scope === "lines" && changedLineRanges === null && !isQuiet) {
64156
63991
  cliLogger.warn("Could not determine changed lines (no base ref or git diff failed); reporting all issues in changed files.");
@@ -65083,7 +64918,6 @@ const ROOT_FLAG_SPEC = {
65083
64918
  "--dead-code",
65084
64919
  "--debug",
65085
64920
  "--help",
65086
- "--include-untracked",
65087
64921
  "--json",
65088
64922
  "--json-compact",
65089
64923
  "--lint",
@@ -65347,7 +65181,7 @@ ${highlighter.dim("Learn more:")}
65347
65181
  ${highlighter.info(CI_URL)}
65348
65182
  `;
65349
65183
  const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
65350
- 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("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").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("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
65184
+ 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("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").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("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
65351
65185
  program.action(inspectAction);
65352
65186
  program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
65353
65187
  program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
@@ -65397,4 +65231,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
65397
65231
  export {};
65398
65232
 
65399
65233
  //# sourceMappingURL=cli.js.map
65400
- //# debugId=58e54b02-7ba2-5f13-a535-98c236467a86
65234
+ //# debugId=63b8a227-543e-5501-a998-58c03a1fb10e