react-doctor 0.7.6-dev.17418 → 0.7.6-dev.1d7b6e2

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]="63b8a227-543e-5501-a998-58c03a1fb10e")}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]="58e54b02-7ba2-5f13-a535-98c236467a86")}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,10 +36341,12 @@ 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;
36344
36346
  const cachedPackageJsons = /* @__PURE__ */ new Map();
36345
36347
  const readPackageJsonUncached = (packageJsonPath) => {
36346
36348
  try {
36347
- return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
36349
+ return JSON.parse(stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8")));
36348
36350
  } catch (error) {
36349
36351
  if (error instanceof SyntaxError) return {};
36350
36352
  if (isErrnoException(error)) {
@@ -40017,7 +40019,6 @@ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
40017
40019
  const PNPM_LOCKFILE = "pnpm-lock.yaml";
40018
40020
  const PACKAGE_JSON_FILE = "package.json";
40019
40021
  const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
40020
- const UTF8_BOM_CHAR = "";
40021
40022
  const HARDENING_SETTING_KEYS = new Set([
40022
40023
  "minimumReleaseAge",
40023
40024
  "blockExoticSubdeps",
@@ -40042,12 +40043,11 @@ const stripInlineComment = (rawValue) => {
40042
40043
  return rawValue;
40043
40044
  };
40044
40045
  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 = stripBom(content).split(/\r?\n/);
40050
+ const lines = stripUtf8Bom(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 = NFS.readFileSync(packageJsonPath, "utf-8");
40084
+ const packageJsonRaw = stripUtf8Bom(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,9 +40762,7 @@ const setColorEnabled = (enabled) => {
40762
40762
  highlighter.gray = colors.gray;
40763
40763
  highlighter.bold = colors.bold;
40764
40764
  };
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;
40765
+ const computeExplicitLintIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter(isLintableSourceFile) : void 0;
40768
40766
  const LINGUIST_ATTRIBUTE_PATTERN = /^linguist-(?:vendored|generated)(?:=([a-zA-Z0-9]+))?$/i;
40769
40767
  const FALSY_VALUES = new Set([
40770
40768
  "false",
@@ -41603,11 +41601,11 @@ const resolveDeadCodeTimeout = (input) => {
41603
41601
  phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
41604
41602
  };
41605
41603
  };
41606
- const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
41604
+ const resolveLintIncludePaths = (rootDirectory, userConfig) => {
41607
41605
  if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
41608
41606
  const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
41609
41607
  return listSourceFiles(rootDirectory).filter((filePath) => {
41610
- if (!shouldIncludeExplicitLintPath(filePath, project)) return false;
41608
+ if (!isLintableSourceFile(filePath)) return false;
41611
41609
  return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
41612
41610
  });
41613
41611
  };
@@ -41825,6 +41823,7 @@ const parseGithubViewerPermission = (stdout) => {
41825
41823
  return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
41826
41824
  };
41827
41825
  const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
41826
+ const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
41828
41827
  /**
41829
41828
  * `Git` wraps every `git`-via-subprocess call react-doctor makes
41830
41829
  * behind a `Context.Service`. The production layer (`layerNode`)
@@ -41895,6 +41894,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
41895
41894
  args,
41896
41895
  directory
41897
41896
  });
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);
41898
41905
  const currentBranch = (directory) => runGit(directory, [
41899
41906
  "rev-parse",
41900
41907
  "--abbrev-ref",
@@ -42017,7 +42024,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42017
42024
  githubViewerPermission,
42018
42025
  branchExists,
42019
42026
  mergeBase,
42020
- diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
42027
+ diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
42021
42028
  if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
42022
42029
  if (explicitBaseBranch !== void 0) {
42023
42030
  const range = parseGitDiffRange(explicitBaseBranch);
@@ -42046,7 +42053,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42046
42053
  "HEAD"
42047
42054
  ]);
42048
42055
  if (uncommitted.status !== 0) return null;
42049
- const files = splitNullSeparated(uncommitted.stdout);
42056
+ const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
42050
42057
  if (files.length === 0) return null;
42051
42058
  return {
42052
42059
  currentBranch: resolvedCurrentBranch,
@@ -42076,7 +42083,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42076
42083
  currentBranch: resolvedCurrentBranch,
42077
42084
  baseBranch,
42078
42085
  diffBaseRef: mergeBaseRef,
42079
- changedFiles: splitNullSeparated(diff.stdout),
42086
+ changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
42080
42087
  isCurrentChanges: false
42081
42088
  };
42082
42089
  }).pipe(withSpan("Git.diffSelection")),
@@ -42122,7 +42129,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42122
42129
  stdout: result.stdout
42123
42130
  };
42124
42131
  }).pipe(withSpan("Git.grep")),
42125
- changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
42132
+ changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
42126
42133
  if (files.length === 0) return [];
42127
42134
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
42128
42135
  const result = yield* runGit(directory, [
@@ -42136,7 +42143,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
42136
42143
  ...files
42137
42144
  ]);
42138
42145
  if (result.status !== 0) return null;
42139
- return parseChangedLineRanges(result.stdout);
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
+ }))];
42140
42154
  }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
42141
42155
  });
42142
42156
  })).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
@@ -42888,6 +42902,12 @@ const mapWithConcurrency = async (items, concurrency, task) => {
42888
42902
  if (errors.length > 0) throw errors[0];
42889
42903
  return results;
42890
42904
  };
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
+ };
42891
42911
  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";
42892
42912
  const ACCESSORS_MIN_VERSION = "3.15.0";
42893
42913
  const supportsAccessors = (reanimatedVersion) => {
@@ -43459,13 +43479,6 @@ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
43459
43479
  visit(sourceFile);
43460
43480
  return foundRange;
43461
43481
  };
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
- };
43469
43482
  /**
43470
43483
  * Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
43471
43484
  * whose enclosing function also carries a `react-hooks-js` (React
@@ -43646,11 +43659,11 @@ const buildRelatedLocations = (labels, filePath) => {
43646
43659
  }
43647
43660
  return related;
43648
43661
  };
43649
- const isOxlintOutput = (value) => {
43650
- if (typeof value !== "object" || value === null) return false;
43651
- const candidate = value;
43652
- return Array.isArray(candidate.diagnostics);
43653
- };
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);
43654
43667
  /**
43655
43668
  * Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
43656
43669
  * Tolerates the leading-notice-line shape oxlint sometimes prints
@@ -43669,23 +43682,59 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43669
43682
  throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
43670
43683
  }
43671
43684
  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) }) });
43672
43687
  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
+ };
43673
43713
  const isMinifiedDiagnosticFile = (filename) => {
43674
- const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
43714
+ const absolutePath = resolveAbsolutePath(filename);
43675
43715
  const cached = minifiedFileCache.get(absolutePath);
43676
43716
  if (cached !== void 0) return cached;
43677
43717
  const minified = isMinifiedSource(absolutePath);
43678
43718
  minifiedFileCache.set(absolutePath, minified);
43679
43719
  return minified;
43680
43720
  };
43681
- return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
43721
+ return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
43682
43722
  const { plugin, rule } = parseRuleCode(diagnostic.code);
43683
43723
  const primaryLabel = diagnostic.labels[0];
43684
43724
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
43685
43725
  const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
43686
43726
  const primarySpan = primaryLabel?.span;
43727
+ const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
43687
43728
  const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
43688
43729
  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;
43689
43738
  return {
43690
43739
  filePath: normalizedFilePath,
43691
43740
  plugin,
@@ -43699,10 +43748,11 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43699
43748
  column: primarySpan?.column ?? 0,
43700
43749
  ...primarySpan ? {
43701
43750
  offset: primarySpan.offset,
43702
- length: primarySpan.length
43751
+ length: primarySpan.length,
43752
+ ...primarySpanEndLine !== void 0 ? { endLine: primarySpanEndLine } : {}
43703
43753
  } : {},
43704
43754
  category,
43705
- ...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
43755
+ ...matchByOccurrence ? { matchByOccurrence: true } : {},
43706
43756
  ...relatedLocations.length > 0 ? { relatedLocations } : {}
43707
43757
  };
43708
43758
  }), rootDirectory);
@@ -45489,7 +45539,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
45489
45539
  directory: scanDirectory,
45490
45540
  repo
45491
45541
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
45492
- const lintIncludePaths = (input.skipJsxIncludeFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths], project)) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, project);
45542
+ const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
45493
45543
  const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
45494
45544
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
45495
45545
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
@@ -46234,10 +46284,11 @@ const computeDiagnosticDelta = (input) => {
46234
46284
  * render diff-base mistakes (`GitBaseBranchInvalid` /
46235
46285
  * `GitBaseBranchMissing`) as clean user errors instead of crashes.
46236
46286
  */
46237
- const getDiffInfo = (directory, explicitBaseBranch) => runPromise(gen(function* () {
46287
+ const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runPromise(gen(function* () {
46238
46288
  const selection = yield* (yield* Git).diffSelection({
46239
46289
  directory,
46240
- explicitBaseBranch
46290
+ explicitBaseBranch,
46291
+ includeUntracked
46241
46292
  });
46242
46293
  if (selection === null) return null;
46243
46294
  return {
@@ -46573,6 +46624,43 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
46573
46624
  //#region src/cli/utils/constants.ts
46574
46625
  const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
46575
46626
  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
+ ];
46576
46664
  const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
46577
46665
  const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
46578
46666
  const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
@@ -46587,6 +46675,7 @@ const METRIC = {
46587
46675
  cliInvoked: "cli.invoked",
46588
46676
  cliError: "cli.error",
46589
46677
  cliEnvironmentError: "cli.env_error",
46678
+ stagedSnapshotDivergence: "staged.snapshot_divergence",
46590
46679
  projectDetected: "project.detected",
46591
46680
  projectPathSelected: "project.path_selected",
46592
46681
  projectConfigSelected: "project.config_selected",
@@ -46667,7 +46756,7 @@ const makeNoopConsole = () => ({
46667
46756
  });
46668
46757
  //#endregion
46669
46758
  //#region src/cli/utils/version.ts
46670
- const VERSION = "0.7.6-dev.0017418";
46759
+ const VERSION = "0.7.6-dev.1d7b6e2";
46671
46760
  //#endregion
46672
46761
  //#region src/cli/utils/json-mode.ts
46673
46762
  let context = null;
@@ -47036,13 +47125,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
47036
47125
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
47037
47126
  * standard `SENTRY_RELEASE` override.
47038
47127
  */
47039
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.0017418`;
47128
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.1d7b6e2`;
47040
47129
  /**
47041
47130
  * Deployment environment shown in Sentry's environment filter. Defaults to
47042
47131
  * `production` for tagged releases and `development` for dev/unbuilt versions,
47043
47132
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
47044
47133
  */
47045
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.0017418") ? "development" : "production");
47134
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.1d7b6e2") ? "development" : "production");
47046
47135
  /**
47047
47136
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
47048
47137
  * (set to `0` to disable tracing) and falls back to
@@ -47234,7 +47323,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
47234
47323
  "fi"
47235
47324
  ].join(" ");
47236
47325
  const PACKAGE_JSON_FILE_NAME = "package.json";
47237
- const runGit = (projectRoot, args) => {
47326
+ const runGitRaw = (projectRoot, args) => {
47238
47327
  try {
47239
47328
  return execFileSync("git", [...args], {
47240
47329
  cwd: projectRoot,
@@ -47245,11 +47334,12 @@ const runGit = (projectRoot, args) => {
47245
47334
  "ignore"
47246
47335
  ],
47247
47336
  maxBuffer: RUN_GIT_MAX_BUFFER_BYTES
47248
- }).trim();
47337
+ });
47249
47338
  } catch {
47250
47339
  return null;
47251
47340
  }
47252
47341
  };
47342
+ const runGit = (projectRoot, args) => runGitRaw(projectRoot, args)?.trim() ?? null;
47253
47343
  const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
47254
47344
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
47255
47345
  const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
@@ -50807,7 +50897,7 @@ const BLOCKING_SUMMARY = {
50807
50897
  const SCOPE_SUMMARY = {
50808
50898
  full: "Scan the whole project on every run",
50809
50899
  files: "Report every issue in changed files",
50810
- lines: "Report issues on changed lines",
50900
+ lines: "Report issues whose source spans touch changed lines",
50811
50901
  changed: "Report only the issues a change introduces"
50812
50902
  };
50813
50903
  const gatesEqual = (left, right) => left.blocking === right.blocking && left.scope === right.scope && left.comment === right.comment && left.reviewComments === right.reviewComments && left.commitStatus === right.commitStatus;
@@ -59120,6 +59210,17 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
59120
59210
  })).toSorted((left, right) => right.fileCount - left.fileCount);
59121
59211
  const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
59122
59212
  //#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
59123
59224
  //#region src/cli/utils/scan-result-cache.ts
59124
59225
  const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
59125
59226
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
@@ -59202,7 +59303,7 @@ const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
59202
59303
  return null;
59203
59304
  }
59204
59305
  if (!stats.isFile()) return null;
59205
- if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
59306
+ if (stats.size > 10485760) return null;
59206
59307
  return hashFileContents(absolutePath);
59207
59308
  };
59208
59309
  const buildWorktreeFingerprint = (projectDirectory) => {
@@ -59525,15 +59626,12 @@ const buildOutcomeAttributes = (input) => {
59525
59626
  const summary = summarizeDiagnostics(result.diagnostics);
59526
59627
  const blockingLevel = resolveTelemetryBlocking(input.userConfig);
59527
59628
  const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
59629
+ const complete = isInspectResultComplete(result);
59630
+ const didLintHardFail = hasLintHardFailure(result);
59631
+ const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
59528
59632
  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
- });
59535
59633
  const isClean = result.diagnostics.length === 0 && complete;
59536
- const outcome = wouldBlock ? "blocked" : isClean ? "clean" : "ok";
59634
+ const outcome = didLintHardFail ? "error" : wouldBlock ? "blocked" : isClean ? "clean" : "ok";
59537
59635
  const firings = summarizeRuleFirings(result.diagnostics);
59538
59636
  const countByRule = /* @__PURE__ */ new Map();
59539
59637
  const countByCategory = /* @__PURE__ */ new Map();
@@ -59578,7 +59676,7 @@ const buildOutcomeAttributes = (input) => {
59578
59676
  const attributes = {
59579
59677
  ...withNamespace("outcome", {
59580
59678
  status: outcome,
59581
- exitCode: wouldBlock ? 1 : 0,
59679
+ exitCode: failsOnHardFailure || wouldBlock ? 1 : 0,
59582
59680
  wouldBlock,
59583
59681
  blocking: blockingLevel,
59584
59682
  clean: isClean,
@@ -59676,7 +59774,9 @@ const buildScanAttributes = (input) => {
59676
59774
  hasCustomConfig: input.hasCustomConfig,
59677
59775
  rulesConfigured: ruleKeys.length,
59678
59776
  rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
59679
- fileCount: input.result?.scannedFileCount ?? null
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
59680
59780
  });
59681
59781
  };
59682
59782
  /**
@@ -59745,6 +59845,12 @@ const countDeadlineSkippedFiles = (lintPartialFailures) => sumFileCountsMatching
59745
59845
  const DROPPED_FILES_MESSAGE_PATTERN = /^(\d+) file\(s\) failed to lint and were skipped/;
59746
59846
  const countDroppedLintFiles = (lintPartialFailures) => sumFileCountsMatching(lintPartialFailures, DROPPED_FILES_MESSAGE_PATTERN);
59747
59847
  //#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
59748
59854
  //#region src/cli/utils/materialize-baseline-files.ts
59749
59855
  /**
59750
59856
  * Materializes the `ref` version of `files` into `tempDirectory` (via
@@ -60930,7 +61036,7 @@ const buildChangedLineMatcher = (directory, changedLineRanges) => {
60930
61036
  const relativePath = toForwardSlashes(path.isAbsolute(diagnostic.filePath) ? path.relative(directory, diagnostic.filePath) : diagnostic.filePath);
60931
61037
  const ranges = rangesByFile.get(relativePath);
60932
61038
  if (ranges === void 0) return false;
60933
- return ranges.some(([start, end]) => diagnostic.line >= start && diagnostic.line <= end);
61039
+ return diagnosticIntersectsLineRanges(diagnostic, ranges);
60934
61040
  };
60935
61041
  };
60936
61042
  const buildIgnoredTags = (userConfig) => {
@@ -63752,10 +63858,15 @@ const warnIfAiTrainingEnvironment = () => {
63752
63858
  //#region src/cli/utils/validate-mode-flags.ts
63753
63859
  const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
63754
63860
  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
+ };
63755
63865
  const validateModeFlags = (flags) => {
63756
63866
  if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
63757
63867
  if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
63758
63868
  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.");
63759
63870
  if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
63760
63871
  if (flags.score && flags.telemetry === false) throw new CliInputError("Cannot combine --score with --no-telemetry; --score prints the score that --no-telemetry disables.");
63761
63872
  if (flags.debug && (flags.score === false || flags.telemetry === false)) {
@@ -63764,6 +63875,39 @@ const validateModeFlags = (flags) => {
63764
63875
  }
63765
63876
  };
63766
63877
  //#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
63767
63911
  //#region src/cli/commands/inspect.ts
63768
63912
  const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
63769
63913
  if (categoryFilters.size === 0) return [...completedScans];
@@ -63778,9 +63922,14 @@ const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
63778
63922
  /**
63779
63923
  * Post-scan finalization shared by the staged-arm and project-loop
63780
63924
  * paths of `inspectAction`: emit the JSON report (when in JSON mode)
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).
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.
63784
63933
  */
63785
63934
  const finalizeScans = (input) => {
63786
63935
  const baselineDeltas = input.completedScans.flatMap((scan) => scan.result.baselineDelta ? [scan.result.baselineDelta] : []);
@@ -63810,8 +63959,13 @@ const finalizeScans = (input) => {
63810
63959
  baselineDegraded
63811
63960
  }));
63812
63961
  }
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
+ }
63813
63967
  if (input.isScoreOnly || baselineDegraded) return;
63814
- if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), resolveBlockingLevel(input.flags, input.userConfig))) process.exitCode = 1;
63968
+ if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), blockingLevel)) process.exitCode = 1;
63815
63969
  };
63816
63970
  const buildChangedFilesDiffInfo = (changedFiles) => ({
63817
63971
  currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
@@ -63849,6 +64003,7 @@ const inspectAction = async (directory, flags) => {
63849
64003
  recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
63850
64004
  try {
63851
64005
  validateModeFlags(flags);
64006
+ if (flags.staged) setJsonReportMode("staged");
63852
64007
  await maybeMigrateLegacyConfig(requestedDirectory, {
63853
64008
  isQuiet,
63854
64009
  isStaged: Boolean(flags.staged)
@@ -63864,6 +64019,14 @@ const inspectAction = async (directory, flags) => {
63864
64019
  cliLogger.dim(`Redirected to ${highlighter.info(toRelativePath(resolvedDirectory, requestedDirectory))} via react-doctor config "rootDir".`);
63865
64020
  cliLogger.break();
63866
64021
  }
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
+ }
63867
64030
  const explainArgument = flags.explain;
63868
64031
  if (explainArgument !== void 0) {
63869
64032
  await runExplain(explainArgument, {
@@ -63884,7 +64047,6 @@ const inspectAction = async (directory, flags) => {
63884
64047
  json: flags.json
63885
64048
  });
63886
64049
  if (flags.staged) {
63887
- setJsonReportMode("staged");
63888
64050
  const stagedFiles = await getStagedSourceFiles(resolvedDirectory);
63889
64051
  if (stagedFiles.length === 0) {
63890
64052
  if (isJsonMode) writeJsonReport(buildJsonReport({
@@ -63965,13 +64127,15 @@ const inspectAction = async (directory, flags) => {
63965
64127
  const projectDirectories = await selectProjects(resolvedDirectory, flags.project, skipPrompts, userConfig?.projects);
63966
64128
  const changedFilesDiffInfo = flags.changedFilesFrom ? buildChangedFilesDiffInfo(readChangedFilesFrom(Path.resolve(flags.changedFilesFrom))) : null;
63967
64129
  const requestedScope = resolveScope(flags, userConfig);
64130
+ const includeUntracked = flags.includeUntracked ?? false;
63968
64131
  const scopeRequest = requestedScope.scope === void 0 && changedFilesDiffInfo !== null ? {
63969
64132
  ...requestedScope,
63970
64133
  scope: "changed"
63971
64134
  } : requestedScope;
64135
+ validateIncludeUntrackedScope(includeUntracked, scopeRequest.scope);
63972
64136
  const wantsDiffMode = scopeRequest.scope !== void 0 && scopeRequest.scope !== "full";
63973
64137
  const shouldDetectDiff = changedFilesDiffInfo === null && (wantsDiffMode || scopeRequest.scope === void 0 && !skipPrompts && !isQuiet);
63974
- const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base) : null);
64138
+ const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base, includeUntracked) : null);
63975
64139
  const scope = await finalizeScope({
63976
64140
  requested: scopeRequest,
63977
64141
  diffInfo,
@@ -63985,7 +64149,8 @@ const inspectAction = async (directory, flags) => {
63985
64149
  const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
63986
64150
  directory: resolvedDirectory,
63987
64151
  baseRef: linesBaseRef ?? void 0,
63988
- files: [...diffInfo.changedFiles]
64152
+ files: [...diffInfo.changedFiles],
64153
+ includeUntracked
63989
64154
  }) : null;
63990
64155
  if (scope === "lines" && changedLineRanges === null && !isQuiet) {
63991
64156
  cliLogger.warn("Could not determine changed lines (no base ref or git diff failed); reporting all issues in changed files.");
@@ -64918,6 +65083,7 @@ const ROOT_FLAG_SPEC = {
64918
65083
  "--dead-code",
64919
65084
  "--debug",
64920
65085
  "--help",
65086
+ "--include-untracked",
64921
65087
  "--json",
64922
65088
  "--json-compact",
64923
65089
  "--lint",
@@ -65181,7 +65347,7 @@ ${highlighter.dim("Learn more:")}
65181
65347
  ${highlighter.info(CI_URL)}
65182
65348
  `;
65183
65349
  const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
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);
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);
65185
65351
  program.action(inspectAction);
65186
65352
  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));
65187
65353
  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);
@@ -65231,4 +65397,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
65231
65397
  export {};
65232
65398
 
65233
65399
  //# sourceMappingURL=cli.js.map
65234
- //# debugId=63b8a227-543e-5501-a998-58c03a1fb10e
65400
+ //# debugId=58e54b02-7ba2-5f13-a535-98c236467a86