react-doctor 0.7.6-dev.3f22fe3 → 0.7.6-dev.4fb3694

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]="4c1e681e-4dee-5ffd-a4ba-69b876f47147")}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]="bc56f5c4-5b95-5727-8e86-d43a534abbbc")}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)) {
@@ -39594,49 +39592,6 @@ const assignFixGroups = (diagnostics) => {
39594
39592
  };
39595
39593
  });
39596
39594
  };
39597
- const DERIVED_STATE_RULE_PRIORITY = new Map([
39598
- ["no-initialize-state", 0],
39599
- ["no-adjust-state-on-prop-change", 1],
39600
- ["no-derived-state", 2],
39601
- ["no-derived-state-effect", 3]
39602
- ]);
39603
- const buildDiagnosticSiteKey = (diagnostic) => `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}`;
39604
- const isReactDoctorRulesOfHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-doctor" && diagnostic.rule === "rules-of-hooks";
39605
- const isReactCompilerHooksDiagnostic = (diagnostic) => diagnostic.plugin === "react-hooks-js" && diagnostic.rule === "hooks";
39606
- const doDiagnosticSpansOverlap = (first, second) => {
39607
- if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
39608
- if (first.offset !== void 0 && second.offset !== void 0) {
39609
- const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
39610
- const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
39611
- return first.offset < secondEnd && second.offset < firstEnd;
39612
- }
39613
- const firstEndLine = first.endLine ?? first.line;
39614
- const secondEndLine = second.endLine ?? second.line;
39615
- return first.line <= secondEndLine && second.line <= firstEndLine;
39616
- };
39617
- const keepHighestSeverity = (survivingDiagnostic, collapsedDiagnostic) => collapsedDiagnostic.severity === "error" && survivingDiagnostic.severity !== "error" ? {
39618
- ...survivingDiagnostic,
39619
- severity: "error"
39620
- } : survivingDiagnostic;
39621
- const dedupeRelatedDiagnostics = (diagnostics) => {
39622
- const reactDoctorHookSites = new Set(diagnostics.filter(isReactDoctorRulesOfHooksDiagnostic).map(buildDiagnosticSiteKey));
39623
- const uniqueDiagnostics = [];
39624
- for (const diagnostic of diagnostics) {
39625
- if (isReactCompilerHooksDiagnostic(diagnostic) && reactDoctorHookSites.has(buildDiagnosticSiteKey(diagnostic))) continue;
39626
- const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
39627
- if (priority !== void 0) {
39628
- const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
39629
- if (overlappingDiagnosticIndex >= 0) {
39630
- const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
39631
- const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
39632
- if (existingDiagnostic && existingPriority !== void 0) uniqueDiagnostics[overlappingDiagnosticIndex] = priority < existingPriority ? keepHighestSeverity(diagnostic, existingDiagnostic) : keepHighestSeverity(existingDiagnostic, diagnostic);
39633
- continue;
39634
- }
39635
- }
39636
- uniqueDiagnostics.push(diagnostic);
39637
- }
39638
- return uniqueDiagnostics;
39639
- };
39640
39595
  const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
39641
39596
  const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
39642
39597
  const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
@@ -40062,6 +40017,7 @@ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
40062
40017
  const PNPM_LOCKFILE = "pnpm-lock.yaml";
40063
40018
  const PACKAGE_JSON_FILE = "package.json";
40064
40019
  const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
40020
+ const UTF8_BOM_CHAR = "";
40065
40021
  const HARDENING_SETTING_KEYS = new Set([
40066
40022
  "minimumReleaseAge",
40067
40023
  "blockExoticSubdeps",
@@ -40086,11 +40042,12 @@ const stripInlineComment = (rawValue) => {
40086
40042
  return rawValue;
40087
40043
  };
40088
40044
  const unquote = (rawValue) => rawValue.replace(/^["']|["']$/g, "");
40045
+ const stripBom = (rawContent) => rawContent.startsWith(UTF8_BOM_CHAR) ? rawContent.slice(1) : rawContent;
40089
40046
  const parseHardeningSettings = (content) => {
40090
40047
  let minimumReleaseAge = null;
40091
40048
  let blockExoticSubdeps = null;
40092
40049
  let trustPolicy = null;
40093
- const lines = stripUtf8Bom(content).split(/\r?\n/);
40050
+ const lines = stripBom(content).split(/\r?\n/);
40094
40051
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
40095
40052
  const lineText = lines[lineIndex];
40096
40053
  if (lineText === void 0) continue;
@@ -40124,7 +40081,7 @@ const isPnpmManagedProject = (rootDirectory) => {
40124
40081
  const packageJsonPath = Path.join(rootDirectory, PACKAGE_JSON_FILE);
40125
40082
  if (!isFile(packageJsonPath)) return false;
40126
40083
  try {
40127
- const packageJsonRaw = stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8"));
40084
+ const packageJsonRaw = NFS.readFileSync(packageJsonPath, "utf-8");
40128
40085
  const packageJson = JSON.parse(packageJsonRaw);
40129
40086
  if (packageJson !== null && typeof packageJson === "object" && "packageManager" in packageJson && typeof packageJson.packageManager === "string" && packageJson.packageManager.startsWith("pnpm@")) return true;
40130
40087
  } catch {
@@ -41866,7 +41823,6 @@ const parseGithubViewerPermission = (stdout) => {
41866
41823
  return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
41867
41824
  };
41868
41825
  const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
41869
- const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
41870
41826
  /**
41871
41827
  * `Git` wraps every `git`-via-subprocess call react-doctor makes
41872
41828
  * behind a `Context.Service`. The production layer (`layerNode`)
@@ -41937,14 +41893,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
41937
41893
  args,
41938
41894
  directory
41939
41895
  });
41940
- const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
41941
- "ls-files",
41942
- "-z",
41943
- "--others",
41944
- "--exclude-standard",
41945
- ...includePaths.length > 0 ? ["--", ...includePaths] : []
41946
- ]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
41947
- const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
41948
41896
  const currentBranch = (directory) => runGit(directory, [
41949
41897
  "rev-parse",
41950
41898
  "--abbrev-ref",
@@ -42067,7 +42015,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42067
42015
  githubViewerPermission,
42068
42016
  branchExists,
42069
42017
  mergeBase,
42070
- diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
42018
+ diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
42071
42019
  if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
42072
42020
  if (explicitBaseBranch !== void 0) {
42073
42021
  const range = parseGitDiffRange(explicitBaseBranch);
@@ -42096,7 +42044,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42096
42044
  "HEAD"
42097
42045
  ]);
42098
42046
  if (uncommitted.status !== 0) return null;
42099
- const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
42047
+ const files = splitNullSeparated(uncommitted.stdout);
42100
42048
  if (files.length === 0) return null;
42101
42049
  return {
42102
42050
  currentBranch: resolvedCurrentBranch,
@@ -42126,7 +42074,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42126
42074
  currentBranch: resolvedCurrentBranch,
42127
42075
  baseBranch,
42128
42076
  diffBaseRef: mergeBaseRef,
42129
- changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
42077
+ changedFiles: splitNullSeparated(diff.stdout),
42130
42078
  isCurrentChanges: false
42131
42079
  };
42132
42080
  }).pipe(withSpan("Git.diffSelection")),
@@ -42172,7 +42120,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42172
42120
  stdout: result.stdout
42173
42121
  };
42174
42122
  }).pipe(withSpan("Git.grep")),
42175
- changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
42123
+ changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
42176
42124
  if (files.length === 0) return [];
42177
42125
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
42178
42126
  const result = yield* runGit(directory, [
@@ -42186,14 +42134,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
42186
42134
  ...files
42187
42135
  ]);
42188
42136
  if (result.status !== 0) return null;
42189
- const changedLineRanges = parseChangedLineRanges(result.stdout);
42190
- if (cached || !includeUntracked) return changedLineRanges;
42191
- const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
42192
- if (untrackedFilePaths === null) return changedLineRanges;
42193
- return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
42194
- file,
42195
- ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
42196
- }))];
42137
+ return parseChangedLineRanges(result.stdout);
42197
42138
  }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
42198
42139
  });
42199
42140
  })).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
@@ -42778,13 +42719,13 @@ const encodeSidecarFileEntry = (entry) => ({
42778
42719
  diagnostics: entry.diagnostics
42779
42720
  });
42780
42721
  const emptyCache = () => ({
42781
- version: 2,
42722
+ version: 1,
42782
42723
  rulesets: {}
42783
42724
  });
42784
42725
  const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
42785
42726
  const entries = /* @__PURE__ */ new Map();
42786
42727
  const persisted = failOpenReadJson(cacheFilePath, emptyCache());
42787
- if (persisted.version !== 2 || !isRecord$1(persisted.rulesets)) return entries;
42728
+ if (persisted.version !== 1 || !isRecord$1(persisted.rulesets)) return entries;
42788
42729
  const bucket = persisted.rulesets[rulesetHash];
42789
42730
  if (!isRecord$1(bucket) || !isRecord$1(bucket.files)) return entries;
42790
42731
  for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
@@ -42804,7 +42745,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
42804
42745
  },
42805
42746
  persist: () => {
42806
42747
  const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
42807
- const rulesets = onDisk.version === 2 && isRecord$1(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
42748
+ const rulesets = onDisk.version === 1 && isRecord$1(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
42808
42749
  const existingBucket = rulesets[rulesetHash];
42809
42750
  const existingFiles = isRecord$1(existingBucket) && isRecord$1(existingBucket.files) ? existingBucket.files : {};
42810
42751
  const ourFiles = {};
@@ -42818,7 +42759,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
42818
42759
  files: Object.fromEntries(cappedEntries)
42819
42760
  };
42820
42761
  atomicWriteJson(cacheFilePath, {
42821
- version: 2,
42762
+ version: 1,
42822
42763
  rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord$1(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
42823
42764
  });
42824
42765
  }
@@ -42869,6 +42810,23 @@ const resolveTsConfigRelativePath = (rootDirectory) => {
42869
42810
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
42870
42811
  return null;
42871
42812
  };
42813
+ const DERIVED_STATE_RULE_PRIORITY = new Map([
42814
+ ["no-initialize-state", 0],
42815
+ ["no-adjust-state-on-prop-change", 1],
42816
+ ["no-derived-state", 2],
42817
+ ["no-derived-state-effect", 3]
42818
+ ]);
42819
+ const doDiagnosticSpansOverlap = (first, second) => {
42820
+ if (first.filePath !== second.filePath || first.plugin !== second.plugin) return false;
42821
+ if (first.offset !== void 0 && second.offset !== void 0) {
42822
+ const firstEnd = first.offset + Math.max(first.length ?? 1, 1);
42823
+ const secondEnd = second.offset + Math.max(second.length ?? 1, 1);
42824
+ return first.offset < secondEnd && second.offset < firstEnd;
42825
+ }
42826
+ const firstEndLine = first.endLine ?? first.line;
42827
+ const secondEndLine = second.endLine ?? second.line;
42828
+ return first.line <= secondEndLine && second.line <= firstEndLine;
42829
+ };
42872
42830
  const dedupeDiagnostics = (diagnostics) => {
42873
42831
  const seenKeys = /* @__PURE__ */ new Set();
42874
42832
  const uniqueDiagnostics = [];
@@ -42876,6 +42834,16 @@ const dedupeDiagnostics = (diagnostics) => {
42876
42834
  const key = `${diagnostic.filePath}\u0000${diagnostic.line}\u0000${diagnostic.column}\u0000${diagnostic.plugin}\u0000${diagnostic.rule}\u0000${diagnostic.severity}\u0000${diagnostic.message}`;
42877
42835
  if (seenKeys.has(key)) continue;
42878
42836
  seenKeys.add(key);
42837
+ const priority = DERIVED_STATE_RULE_PRIORITY.get(diagnostic.rule);
42838
+ if (priority !== void 0) {
42839
+ const overlappingDiagnosticIndex = uniqueDiagnostics.findIndex((candidate) => candidate.rule !== diagnostic.rule && candidate.severity === diagnostic.severity && DERIVED_STATE_RULE_PRIORITY.has(candidate.rule) && doDiagnosticSpansOverlap(candidate, diagnostic));
42840
+ if (overlappingDiagnosticIndex >= 0) {
42841
+ const existingDiagnostic = uniqueDiagnostics[overlappingDiagnosticIndex];
42842
+ const existingPriority = existingDiagnostic ? DERIVED_STATE_RULE_PRIORITY.get(existingDiagnostic.rule) : void 0;
42843
+ if (existingPriority !== void 0 && priority < existingPriority) uniqueDiagnostics[overlappingDiagnosticIndex] = diagnostic;
42844
+ continue;
42845
+ }
42846
+ }
42879
42847
  uniqueDiagnostics.push(diagnostic);
42880
42848
  }
42881
42849
  return uniqueDiagnostics;
@@ -42918,12 +42886,6 @@ const mapWithConcurrency = async (items, concurrency, task) => {
42918
42886
  if (errors.length > 0) throw errors[0];
42919
42887
  return results;
42920
42888
  };
42921
- const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
42922
- let lineNumber = 1;
42923
- const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
42924
- for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === 10) lineNumber++;
42925
- return lineNumber;
42926
- };
42927
42889
  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";
42928
42890
  const ACCESSORS_MIN_VERSION = "3.15.0";
42929
42891
  const supportsAccessors = (reanimatedVersion) => {
@@ -43495,6 +43457,13 @@ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
43495
43457
  visit(sourceFile);
43496
43458
  return foundRange;
43497
43459
  };
43460
+ const NEWLINE_BYTE = 10;
43461
+ const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
43462
+ let lineNumber = 1;
43463
+ const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
43464
+ for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
43465
+ return lineNumber;
43466
+ };
43498
43467
  /**
43499
43468
  * Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
43500
43469
  * whose enclosing function also carries a `react-hooks-js` (React
@@ -43675,11 +43644,11 @@ const buildRelatedLocations = (labels, filePath) => {
43675
43644
  }
43676
43645
  return related;
43677
43646
  };
43678
- const isOxlintSpan = (value) => isRecord$1(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
43679
- const isOxlintLabel = (value) => isRecord$1(value) && isOxlintSpan(value.span);
43680
- 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);
43681
- const isEngineFailureDiagnostic = (value) => !isRecord$1(value) || typeof value.filename !== "string" || value.filename.length === 0;
43682
- const isOxlintOutput = (value) => isRecord$1(value) && Array.isArray(value.diagnostics);
43647
+ const isOxlintOutput = (value) => {
43648
+ if (typeof value !== "object" || value === null) return false;
43649
+ const candidate = value;
43650
+ return Array.isArray(candidate.diagnostics);
43651
+ };
43683
43652
  /**
43684
43653
  * Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
43685
43654
  * Tolerates the leading-notice-line shape oxlint sometimes prints
@@ -43698,59 +43667,23 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43698
43667
  throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
43699
43668
  }
43700
43669
  if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
43701
- const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
43702
- if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
43703
43670
  const minifiedFileCache = /* @__PURE__ */ new Map();
43704
- const sourceBufferCache = /* @__PURE__ */ new Map();
43705
- const sourceLinesCache = /* @__PURE__ */ new Map();
43706
- const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
43707
- const readSourceBuffer = (filename) => {
43708
- const absolutePath = resolveAbsolutePath(filename);
43709
- const cached = sourceBufferCache.get(absolutePath);
43710
- if (cached !== void 0) return cached;
43711
- let sourceBuffer;
43712
- try {
43713
- sourceBuffer = NFS.readFileSync(absolutePath);
43714
- } catch {
43715
- sourceBuffer = null;
43716
- }
43717
- sourceBufferCache.set(absolutePath, sourceBuffer);
43718
- return sourceBuffer;
43719
- };
43720
- const readSourceLines = (filename) => {
43721
- const absolutePath = resolveAbsolutePath(filename);
43722
- const cached = sourceLinesCache.get(absolutePath);
43723
- if (cached !== void 0) return cached;
43724
- const sourceBuffer = readSourceBuffer(filename);
43725
- const sourceLines = sourceBuffer ? sourceBuffer.toString("utf8").split("\n") : null;
43726
- sourceLinesCache.set(absolutePath, sourceLines);
43727
- return sourceLines;
43728
- };
43729
43671
  const isMinifiedDiagnosticFile = (filename) => {
43730
- const absolutePath = resolveAbsolutePath(filename);
43672
+ const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
43731
43673
  const cached = minifiedFileCache.get(absolutePath);
43732
43674
  if (cached !== void 0) return cached;
43733
43675
  const minified = isMinifiedSource(absolutePath);
43734
43676
  minifiedFileCache.set(absolutePath, minified);
43735
43677
  return minified;
43736
43678
  };
43737
- return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
43679
+ return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
43738
43680
  const { plugin, rule } = parseRuleCode(diagnostic.code);
43739
43681
  const primaryLabel = diagnostic.labels[0];
43740
43682
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
43741
43683
  const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
43742
43684
  const primarySpan = primaryLabel?.span;
43743
- const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
43744
43685
  const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
43745
43686
  const category = resolveDiagnosticCategory(plugin, rule);
43746
- const matchByOccurrence = resolveMatchByOccurrence(rule, category);
43747
- const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
43748
- const primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
43749
- const primaryLine = sourceLines?.[primaryLineIndex];
43750
- const primaryColumnIndex = primarySpan ? primarySpan.column - 1 : -1;
43751
- const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
43752
- const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
43753
- const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
43754
43687
  return {
43755
43688
  filePath: normalizedFilePath,
43756
43689
  plugin,
@@ -43764,11 +43697,10 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
43764
43697
  column: primarySpan?.column ?? 0,
43765
43698
  ...primarySpan ? {
43766
43699
  offset: primarySpan.offset,
43767
- length: primarySpan.length,
43768
- ...primarySpanEndLine !== void 0 ? { endLine: primarySpanEndLine } : {}
43700
+ length: primarySpan.length
43769
43701
  } : {},
43770
43702
  category,
43771
- ...matchByOccurrence ? { matchByOccurrence: true } : {},
43703
+ ...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
43772
43704
  ...relatedLocations.length > 0 ? { relatedLocations } : {}
43773
43705
  };
43774
43706
  }), rootDirectory);
@@ -45569,8 +45501,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
45569
45501
  respectInlineDisables: input.respectInlineDisables,
45570
45502
  showWarnings
45571
45503
  });
45572
- const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
45573
- const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
45504
+ const applyPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)), tap((diagnostic) => reporterService.emit(diagnostic)));
45574
45505
  const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
45575
45506
  ...checkReducedMotion(scanDirectory),
45576
45507
  ...checkPnpmHardening(scanDirectory),
@@ -45699,7 +45630,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
45699
45630
  });
45700
45631
  return empty$4;
45701
45632
  }))));
45702
- const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
45633
+ const lintCollected = yield* runCollect(applyPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
45703
45634
  onNone: () => set(lintFailure, {
45704
45635
  didFail: true,
45705
45636
  reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
@@ -45707,8 +45638,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
45707
45638
  reasonKind: null
45708
45639
  }).pipe(as([])),
45709
45640
  onSome: succeed$2
45710
- }))));
45711
- yield* forEach$1(lintCollected, reporterService.emit, { discard: true });
45641
+ })));
45712
45642
  const lintFailureState = yield* get$2(lintFailure);
45713
45643
  yield* afterLint(lintFailureState.didFail);
45714
45644
  if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
@@ -46302,11 +46232,10 @@ const computeDiagnosticDelta = (input) => {
46302
46232
  * render diff-base mistakes (`GitBaseBranchInvalid` /
46303
46233
  * `GitBaseBranchMissing`) as clean user errors instead of crashes.
46304
46234
  */
46305
- const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runPromise(gen(function* () {
46235
+ const getDiffInfo = (directory, explicitBaseBranch) => runPromise(gen(function* () {
46306
46236
  const selection = yield* (yield* Git).diffSelection({
46307
46237
  directory,
46308
- explicitBaseBranch,
46309
- includeUntracked
46238
+ explicitBaseBranch
46310
46239
  });
46311
46240
  if (selection === null) return null;
46312
46241
  return {
@@ -46642,43 +46571,6 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
46642
46571
  //#region src/cli/utils/constants.ts
46643
46572
  const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
46644
46573
  const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
46645
- const STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES = [
46646
- ".babelrc",
46647
- ".babelrc.json",
46648
- ".eslintignore",
46649
- ".eslintrc.json",
46650
- ".oxlintignore",
46651
- "app.config.cjs",
46652
- "app.config.js",
46653
- "app.config.json",
46654
- "app.config.mjs",
46655
- "app.config.ts",
46656
- "app.json",
46657
- "babel.config.cjs",
46658
- "babel.config.js",
46659
- "babel.config.json",
46660
- "babel.config.mjs",
46661
- "eslint.config.cjs",
46662
- "eslint.config.cts",
46663
- "eslint.config.js",
46664
- "eslint.config.mjs",
46665
- "eslint.config.mts",
46666
- "eslint.config.ts",
46667
- "next.config.cjs",
46668
- "next.config.js",
46669
- "next.config.mjs",
46670
- "next.config.ts",
46671
- "oxlint.config.json",
46672
- "react-doctor.config.json",
46673
- "vite.config.cjs",
46674
- "vite.config.cts",
46675
- "vite.config.js",
46676
- "vite.config.mjs",
46677
- "vite.config.mts",
46678
- "vite.config.ts",
46679
- "vitest.config.js",
46680
- "vitest.config.ts"
46681
- ];
46682
46574
  const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
46683
46575
  const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
46684
46576
  const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
@@ -46693,7 +46585,6 @@ const METRIC = {
46693
46585
  cliInvoked: "cli.invoked",
46694
46586
  cliError: "cli.error",
46695
46587
  cliEnvironmentError: "cli.env_error",
46696
- stagedSnapshotDivergence: "staged.snapshot_divergence",
46697
46588
  projectDetected: "project.detected",
46698
46589
  projectPathSelected: "project.path_selected",
46699
46590
  projectConfigSelected: "project.config_selected",
@@ -46774,7 +46665,7 @@ const makeNoopConsole = () => ({
46774
46665
  });
46775
46666
  //#endregion
46776
46667
  //#region src/cli/utils/version.ts
46777
- const VERSION = "0.7.6-dev.3f22fe3";
46668
+ const VERSION = "0.7.6-dev.4fb3694";
46778
46669
  //#endregion
46779
46670
  //#region src/cli/utils/json-mode.ts
46780
46671
  let context = null;
@@ -47143,13 +47034,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
47143
47034
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
47144
47035
  * standard `SENTRY_RELEASE` override.
47145
47036
  */
47146
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.3f22fe3`;
47037
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.4fb3694`;
47147
47038
  /**
47148
47039
  * Deployment environment shown in Sentry's environment filter. Defaults to
47149
47040
  * `production` for tagged releases and `development` for dev/unbuilt versions,
47150
47041
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
47151
47042
  */
47152
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.3f22fe3") ? "development" : "production");
47043
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.4fb3694") ? "development" : "production");
47153
47044
  /**
47154
47045
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
47155
47046
  * (set to `0` to disable tracing) and falls back to
@@ -47341,7 +47232,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
47341
47232
  "fi"
47342
47233
  ].join(" ");
47343
47234
  const PACKAGE_JSON_FILE_NAME = "package.json";
47344
- const runGitRaw = (projectRoot, args) => {
47235
+ const runGit = (projectRoot, args) => {
47345
47236
  try {
47346
47237
  return execFileSync("git", [...args], {
47347
47238
  cwd: projectRoot,
@@ -47352,12 +47243,11 @@ const runGitRaw = (projectRoot, args) => {
47352
47243
  "ignore"
47353
47244
  ],
47354
47245
  maxBuffer: RUN_GIT_MAX_BUFFER_BYTES
47355
- });
47246
+ }).trim();
47356
47247
  } catch {
47357
47248
  return null;
47358
47249
  }
47359
47250
  };
47360
- const runGit = (projectRoot, args) => runGitRaw(projectRoot, args)?.trim() ?? null;
47361
47251
  const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
47362
47252
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
47363
47253
  const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
@@ -50915,7 +50805,7 @@ const BLOCKING_SUMMARY = {
50915
50805
  const SCOPE_SUMMARY = {
50916
50806
  full: "Scan the whole project on every run",
50917
50807
  files: "Report every issue in changed files",
50918
- lines: "Report issues whose source spans touch changed lines",
50808
+ lines: "Report issues on changed lines",
50919
50809
  changed: "Report only the issues a change introduces"
50920
50810
  };
50921
50811
  const gatesEqual = (left, right) => left.blocking === right.blocking && left.scope === right.scope && left.comment === right.comment && left.reviewComments === right.reviewComments && left.commitStatus === right.commitStatus;
@@ -59228,17 +59118,6 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
59228
59118
  })).toSorted((left, right) => right.fileCount - left.fileCount);
59229
59119
  const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
59230
59120
  //#endregion
59231
- //#region src/cli/utils/has-lint-hard-failure.ts
59232
- const hasLintHardFailure = (result) => result.skippedChecks.includes("lint");
59233
- //#endregion
59234
- //#region src/cli/utils/is-inspect-result-complete.ts
59235
- const isInspectResultComplete = (result) => isScanComplete({
59236
- analyzedFileCount: result.analyzedFiles?.length,
59237
- scannedFileCount: result.scannedFileCount,
59238
- skippedCheckCount: result.skippedChecks.length,
59239
- skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
59240
- });
59241
- //#endregion
59242
59121
  //#region src/cli/utils/scan-result-cache.ts
59243
59122
  const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
59244
59123
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
@@ -59644,12 +59523,15 @@ const buildOutcomeAttributes = (input) => {
59644
59523
  const summary = summarizeDiagnostics(result.diagnostics);
59645
59524
  const blockingLevel = resolveTelemetryBlocking(input.userConfig);
59646
59525
  const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
59647
- const complete = isInspectResultComplete(result);
59648
- const didLintHardFail = hasLintHardFailure(result);
59649
- const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
59650
59526
  const wouldBlock = !input.scoreOnly && !input.gateExempt && shouldBlockCi(gateDiagnostics, blockingLevel);
59527
+ const complete = isScanComplete({
59528
+ analyzedFileCount: result.analyzedFiles?.length,
59529
+ scannedFileCount: result.scannedFileCount,
59530
+ skippedCheckCount: result.skippedChecks.length,
59531
+ skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
59532
+ });
59651
59533
  const isClean = result.diagnostics.length === 0 && complete;
59652
- const outcome = didLintHardFail ? "error" : wouldBlock ? "blocked" : isClean ? "clean" : "ok";
59534
+ const outcome = wouldBlock ? "blocked" : isClean ? "clean" : "ok";
59653
59535
  const firings = summarizeRuleFirings(result.diagnostics);
59654
59536
  const countByRule = /* @__PURE__ */ new Map();
59655
59537
  const countByCategory = /* @__PURE__ */ new Map();
@@ -59694,7 +59576,7 @@ const buildOutcomeAttributes = (input) => {
59694
59576
  const attributes = {
59695
59577
  ...withNamespace("outcome", {
59696
59578
  status: outcome,
59697
- exitCode: failsOnHardFailure || wouldBlock ? 1 : 0,
59579
+ exitCode: wouldBlock ? 1 : 0,
59698
59580
  wouldBlock,
59699
59581
  blocking: blockingLevel,
59700
59582
  clean: isClean,
@@ -59793,8 +59675,7 @@ const buildScanAttributes = (input) => {
59793
59675
  rulesConfigured: ruleKeys.length,
59794
59676
  rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
59795
59677
  fileCount: input.result?.scannedFileCount ?? null,
59796
- nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length ?? null,
59797
- multilineDiagnosticCount: input.result?.diagnostics.filter((diagnostic) => (diagnostic.endLine ?? diagnostic.line) > diagnostic.line).length ?? null
59678
+ nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length ?? null
59798
59679
  });
59799
59680
  };
59800
59681
  /**
@@ -59863,12 +59744,6 @@ const countDeadlineSkippedFiles = (lintPartialFailures) => sumFileCountsMatching
59863
59744
  const DROPPED_FILES_MESSAGE_PATTERN = /^(\d+) file\(s\) failed to lint and were skipped/;
59864
59745
  const countDroppedLintFiles = (lintPartialFailures) => sumFileCountsMatching(lintPartialFailures, DROPPED_FILES_MESSAGE_PATTERN);
59865
59746
  //#endregion
59866
- //#region src/cli/utils/diagnostic-intersects-line-ranges.ts
59867
- const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
59868
- const diagnosticEndLine = Math.max(diagnostic.line, diagnostic.endLine ?? diagnostic.line);
59869
- return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
59870
- };
59871
- //#endregion
59872
59747
  //#region src/cli/utils/materialize-baseline-files.ts
59873
59748
  /**
59874
59749
  * Materializes the `ref` version of `files` into `tempDirectory` (via
@@ -61054,7 +60929,7 @@ const buildChangedLineMatcher = (directory, changedLineRanges) => {
61054
60929
  const relativePath = toForwardSlashes(path.isAbsolute(diagnostic.filePath) ? path.relative(directory, diagnostic.filePath) : diagnostic.filePath);
61055
60930
  const ranges = rangesByFile.get(relativePath);
61056
60931
  if (ranges === void 0) return false;
61057
- return diagnosticIntersectsLineRanges(diagnostic, ranges);
60932
+ return ranges.some(([start, end]) => diagnostic.line >= start && diagnostic.line <= end);
61058
60933
  };
61059
60934
  };
61060
60935
  const buildIgnoredTags = (userConfig) => {
@@ -63876,15 +63751,10 @@ const warnIfAiTrainingEnvironment = () => {
63876
63751
  //#region src/cli/utils/validate-mode-flags.ts
63877
63752
  const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
63878
63753
  const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
63879
- const validateIncludeUntrackedScope = (includeUntracked, scope) => {
63880
- if (!includeUntracked || scope !== void 0 && scope !== "full") return;
63881
- throw new CliInputError("--include-untracked requires the files, changed, or lines scope (via --scope, --diff, or config).");
63882
- };
63883
63754
  const validateModeFlags = (flags) => {
63884
63755
  if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
63885
63756
  if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
63886
63757
  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.`);
63887
- if (flags.includeUntracked && flags.staged) throw new CliInputError("Cannot combine --include-untracked with --staged; the git index never holds untracked files.");
63888
63758
  if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
63889
63759
  if (flags.score && flags.telemetry === false) throw new CliInputError("Cannot combine --score with --no-telemetry; --score prints the score that --no-telemetry disables.");
63890
63760
  if (flags.debug && (flags.score === false || flags.telemetry === false)) {
@@ -63893,39 +63763,6 @@ const validateModeFlags = (flags) => {
63893
63763
  }
63894
63764
  };
63895
63765
  //#endregion
63896
- //#region src/cli/utils/parse-staged-snapshot-divergences.ts
63897
- const SNAPSHOT_CONFIG_FILENAMES = new Set([...STAGED_FILES_PROJECT_CONFIG_FILENAMES, ...STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES]);
63898
- const parseStagedSnapshotDivergences = (statusOutput) => {
63899
- const divergentConfigFiles = /* @__PURE__ */ new Set();
63900
- const statusEntries = statusOutput.split("\0").filter((entry) => entry.length > 0);
63901
- for (let entryIndex = 0; entryIndex < statusEntries.length; entryIndex += 1) {
63902
- const entry = statusEntries[entryIndex];
63903
- const [indexStatus, worktreeStatus] = entry;
63904
- const filePath = entry.slice(3);
63905
- const hasRenameOrCopySource = indexStatus === "R" || indexStatus === "C" || worktreeStatus === "R" || worktreeStatus === "C";
63906
- const sourcePath = hasRenameOrCopySource ? statusEntries[entryIndex + 1] : void 0;
63907
- const worktreePaths = worktreeStatus === "R" || worktreeStatus === "C" ? [filePath, sourcePath] : [filePath];
63908
- if (worktreeStatus !== " ") {
63909
- for (const worktreePath of worktreePaths) if (worktreePath && SNAPSHOT_CONFIG_FILENAMES.has(Path.basename(worktreePath))) divergentConfigFiles.add(worktreePath);
63910
- }
63911
- if (hasRenameOrCopySource) entryIndex += 1;
63912
- }
63913
- return [...divergentConfigFiles].sort();
63914
- };
63915
- //#endregion
63916
- //#region src/cli/utils/find-staged-snapshot-divergences.ts
63917
- const findStagedSnapshotDivergences = (directory) => {
63918
- const statusOutput = runGitRaw(directory, [
63919
- "status",
63920
- "--porcelain=v1",
63921
- "-z",
63922
- "--untracked-files=all",
63923
- "--ignored=matching"
63924
- ]);
63925
- if (statusOutput === null) return null;
63926
- return parseStagedSnapshotDivergences(statusOutput);
63927
- };
63928
- //#endregion
63929
63766
  //#region src/cli/commands/inspect.ts
63930
63767
  const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
63931
63768
  if (categoryFilters.size === 0) return [...completedScans];
@@ -63940,14 +63777,9 @@ const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
63940
63777
  /**
63941
63778
  * Post-scan finalization shared by the staged-arm and project-loop
63942
63779
  * paths of `inspectAction`: emit the JSON report (when in JSON mode)
63943
- * and set `process.exitCode = 1` when any scan's lint pass hard-failed
63944
- * (an engine/plugin/binding failure destroys the findings, so success
63945
- * would be a false clean) or a diagnostic at or above the `--blocking`
63946
- * threshold (default `"error"`) reaches the `ciFailure` surface.
63947
- * `--blocking none` keeps the scan advisory (always exits 0), and
63948
- * fail-open degradations — `--no-lint`, `--max-duration` truncation,
63949
- * supply-chain/security skips — stay advisory too, surfaced through
63950
- * `complete: false` in the JSON report.
63780
+ * and set `process.exitCode = 1` when a diagnostic at or above the
63781
+ * `--blocking` threshold (default `"error"`) reaches the `ciFailure`
63782
+ * surface. `--blocking none` keeps the scan advisory (always exits 0).
63951
63783
  */
63952
63784
  const finalizeScans = (input) => {
63953
63785
  const baselineDeltas = input.completedScans.flatMap((scan) => scan.result.baselineDelta ? [scan.result.baselineDelta] : []);
@@ -63977,13 +63809,8 @@ const finalizeScans = (input) => {
63977
63809
  baselineDegraded
63978
63810
  }));
63979
63811
  }
63980
- const blockingLevel = resolveBlockingLevel(input.flags, input.userConfig);
63981
- if (input.completedScans.some(({ result }) => hasLintHardFailure(result)) && blockingLevel !== "none") {
63982
- process.exitCode = 1;
63983
- return;
63984
- }
63985
63812
  if (input.isScoreOnly || baselineDegraded) return;
63986
- if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), blockingLevel)) process.exitCode = 1;
63813
+ if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), resolveBlockingLevel(input.flags, input.userConfig))) process.exitCode = 1;
63987
63814
  };
63988
63815
  const buildChangedFilesDiffInfo = (changedFiles) => ({
63989
63816
  currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
@@ -64021,7 +63848,6 @@ const inspectAction = async (directory, flags) => {
64021
63848
  recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
64022
63849
  try {
64023
63850
  validateModeFlags(flags);
64024
- if (flags.staged) setJsonReportMode("staged");
64025
63851
  await maybeMigrateLegacyConfig(requestedDirectory, {
64026
63852
  isQuiet,
64027
63853
  isStaged: Boolean(flags.staged)
@@ -64037,14 +63863,6 @@ const inspectAction = async (directory, flags) => {
64037
63863
  cliLogger.dim(`Redirected to ${highlighter.info(toRelativePath(resolvedDirectory, requestedDirectory))} via react-doctor config "rootDir".`);
64038
63864
  cliLogger.break();
64039
63865
  }
64040
- if (flags.staged) {
64041
- const divergentConfigFiles = findStagedSnapshotDivergences(resolvedDirectory);
64042
- 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.");
64043
- if (divergentConfigFiles.length > 0) {
64044
- recordCount(METRIC.stagedSnapshotDivergence, 1, { divergentInputCount: divergentConfigFiles.length });
64045
- 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.`);
64046
- }
64047
- }
64048
63866
  const explainArgument = flags.explain;
64049
63867
  if (explainArgument !== void 0) {
64050
63868
  await runExplain(explainArgument, {
@@ -64065,6 +63883,7 @@ const inspectAction = async (directory, flags) => {
64065
63883
  json: flags.json
64066
63884
  });
64067
63885
  if (flags.staged) {
63886
+ setJsonReportMode("staged");
64068
63887
  const stagedFiles = await getStagedSourceFiles(resolvedDirectory);
64069
63888
  if (stagedFiles.length === 0) {
64070
63889
  if (isJsonMode) writeJsonReport(buildJsonReport({
@@ -64145,15 +63964,13 @@ const inspectAction = async (directory, flags) => {
64145
63964
  const projectDirectories = await selectProjects(resolvedDirectory, flags.project, skipPrompts, userConfig?.projects);
64146
63965
  const changedFilesDiffInfo = flags.changedFilesFrom ? buildChangedFilesDiffInfo(readChangedFilesFrom(Path.resolve(flags.changedFilesFrom))) : null;
64147
63966
  const requestedScope = resolveScope(flags, userConfig);
64148
- const includeUntracked = flags.includeUntracked ?? false;
64149
63967
  const scopeRequest = requestedScope.scope === void 0 && changedFilesDiffInfo !== null ? {
64150
63968
  ...requestedScope,
64151
63969
  scope: "changed"
64152
63970
  } : requestedScope;
64153
- validateIncludeUntrackedScope(includeUntracked, scopeRequest.scope);
64154
63971
  const wantsDiffMode = scopeRequest.scope !== void 0 && scopeRequest.scope !== "full";
64155
63972
  const shouldDetectDiff = changedFilesDiffInfo === null && (wantsDiffMode || scopeRequest.scope === void 0 && !skipPrompts && !isQuiet);
64156
- const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base, includeUntracked) : null);
63973
+ const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base) : null);
64157
63974
  const scope = await finalizeScope({
64158
63975
  requested: scopeRequest,
64159
63976
  diffInfo,
@@ -64167,8 +63984,7 @@ const inspectAction = async (directory, flags) => {
64167
63984
  const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
64168
63985
  directory: resolvedDirectory,
64169
63986
  baseRef: linesBaseRef ?? void 0,
64170
- files: [...diffInfo.changedFiles],
64171
- includeUntracked
63987
+ files: [...diffInfo.changedFiles]
64172
63988
  }) : null;
64173
63989
  if (scope === "lines" && changedLineRanges === null && !isQuiet) {
64174
63990
  cliLogger.warn("Could not determine changed lines (no base ref or git diff failed); reporting all issues in changed files.");
@@ -65101,7 +64917,6 @@ const ROOT_FLAG_SPEC = {
65101
64917
  "--dead-code",
65102
64918
  "--debug",
65103
64919
  "--help",
65104
- "--include-untracked",
65105
64920
  "--json",
65106
64921
  "--json-compact",
65107
64922
  "--lint",
@@ -65365,7 +65180,7 @@ ${highlighter.dim("Learn more:")}
65365
65180
  ${highlighter.info(CI_URL)}
65366
65181
  `;
65367
65182
  const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
65368
- 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);
65183
+ 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 (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);
65369
65184
  program.action(inspectAction);
65370
65185
  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));
65371
65186
  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);
@@ -65415,4 +65230,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
65415
65230
  export {};
65416
65231
 
65417
65232
  //# sourceMappingURL=cli.js.map
65418
- //# debugId=4c1e681e-4dee-5ffd-a4ba-69b876f47147
65233
+ //# debugId=bc56f5c4-5b95-5727-8e86-d43a534abbbc