react-doctor 0.5.8-dev.849580c → 0.5.8-dev.88a5c3c

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/index.d.ts CHANGED
@@ -9271,6 +9271,15 @@ interface ReactDoctorConfig {
9271
9271
  * suppresses regardless of sibling content.
9272
9272
  */
9273
9273
  rawTextWrapperComponents?: string[];
9274
+ /**
9275
+ * Capitalized identifiers that exist at runtime but aren't imported in
9276
+ * the file that uses them — react-live's `<LiveProvider scope={...}>`,
9277
+ * Storybook globals, MDX live blocks, or an ambient `declare global` in a
9278
+ * separate `.d.ts`. The single-file `jsx-no-undef` rule can't see those
9279
+ * bindings and would report `<DatePicker />` as an undefined JSX component.
9280
+ * List the injected names here and `jsx-no-undef` treats them as known.
9281
+ */
9282
+ runtimeGlobals?: string[];
9274
9283
  /**
9275
9284
  * Project-level allowlist of function names that the
9276
9285
  * `server-auth-actions` rule treats as an auth check at the top of
@@ -9377,6 +9386,10 @@ interface ReactDoctorConfig {
9377
9386
  * { "categories": { "Maintainability": "off", "Performance": "warn" } }
9378
9387
  * ```
9379
9388
  *
9389
+ * A category severity re-stamps rules that are already enabled; it
9390
+ * never activates opt-out (`defaultEnabled: false`) rules — pin the
9391
+ * rule itself under `rules` to opt in.
9392
+ *
9380
9393
  * To silence a whole tag-defined rule family (e.g. `"design"`,
9381
9394
  * `"test-noise"`, `"migration-hint"`) that doesn't align with a
9382
9395
  * single category, use `ignore.tags` instead.
@@ -10082,6 +10095,14 @@ interface BuildJsonReportInput {
10082
10095
  fixedCount: number;
10083
10096
  baseTotalCount: number;
10084
10097
  };
10098
+ /**
10099
+ * True when a `changed` run was intended but its baseline delta couldn't be
10100
+ * computed (no merge base — usually a shallow CI checkout — or a failed
10101
+ * base/head lint), so `diagnostics` list every finding in the changed files
10102
+ * rather than only the introduced ones. Ignored when `baseline` is set: a
10103
+ * computed baseline (v2) wins, so callers pass at most one.
10104
+ */
10105
+ baselineDegraded?: boolean;
10085
10106
  }
10086
10107
  declare const buildJsonReport: (input: BuildJsonReportInput) => JsonReport; //#endregion
10087
10108
  //#region src/build-skipped-checks.d.ts
package/dist/index.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]="9807b1bf-1e62-5421-ad35-d1f4129c1538")}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]="7cfd5199-4216-5c6e-8cf5-c9623c317a1e")}catch(e){}}();
3
3
  import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import * as NFS from "node:fs";
@@ -7,7 +7,7 @@ import fs from "node:fs";
7
7
  import * as Path from "node:path";
8
8
  import path from "node:path";
9
9
  import * as NodeChildProcess from "node:child_process";
10
- import { spawn, spawnSync } from "node:child_process";
10
+ import { execFileSync, spawn, spawnSync } from "node:child_process";
11
11
  import * as ts from "typescript";
12
12
  import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
13
13
  import * as OS from "node:os";
@@ -19364,6 +19364,15 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
19364
19364
  ok: Boolean$1,
19365
19365
  directory: String$1,
19366
19366
  mode: JsonReportMode,
19367
+ /**
19368
+ * True when a `changed` (compare) run was intended but the base couldn't be
19369
+ * resolved — a shallow CI checkout with no merge base, or a failed base/head
19370
+ * lint. The report then lists every finding in the changed files (mode
19371
+ * downgrades to `diff`, the `baseline` block is dropped, the CI gate is
19372
+ * skipped) rather than only the ones the change introduced. Absent on a
19373
+ * successful `baseline` run (`schemaVersion: 2`) and on non-compare scopes.
19374
+ */
19375
+ baselineDegraded: optional(Boolean$1),
19367
19376
  diff: NullOr(JsonReportDiffInfo),
19368
19377
  projects: ArraySchema(JsonReportProjectEntry),
19369
19378
  diagnostics: ArraySchema(Diagnostic),
@@ -33695,6 +33704,9 @@ const SCORE_API_URL = "https://www.react.doctor/api/score";
33695
33704
  const FETCH_TIMEOUT_MS = 1e4;
33696
33705
  const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
33697
33706
  const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
33707
+ const SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX = 15e5;
33708
+ const SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN = 8e5;
33709
+ const NODE_VERSION_PROBE_TIMEOUT_MS = 5e3;
33698
33710
  const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
33699
33711
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
33700
33712
  const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
@@ -34519,7 +34531,7 @@ const tokenizeRuleList = (ruleList) => {
34519
34531
  const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
34520
34532
  const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
34521
34533
  const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
34522
- const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — change it to \`${ruleId}\`.`;
34534
+ const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — add \`${ruleId}\` to the comment (e.g. \`${token}, ${ruleId}\`).`;
34523
34535
  const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
34524
34536
  const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34525
34537
  const candidates = [{
@@ -34999,6 +35011,7 @@ const collectStringSet = (values) => {
34999
35011
  if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
35000
35012
  return new Set(values.filter((value) => typeof value === "string"));
35001
35013
  };
35014
+ const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
35002
35015
  /**
35003
35016
  * Pre-compiles every stateful filter and returns a single
35004
35017
  * `apply(diagnostic)` closure that runs (in order):
@@ -35032,11 +35045,28 @@ const buildDiagnosticPipeline = (input) => {
35032
35045
  const compiledOverrides = compileIgnoreOverrides(userConfig);
35033
35046
  const textComponentNames = collectStringSet(userConfig?.textComponents);
35034
35047
  const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
35048
+ const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
35035
35049
  const hasTextComponents = textComponentNames.size > 0;
35036
35050
  const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
35051
+ const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
35037
35052
  const fileLinesCache = /* @__PURE__ */ new Map();
35038
35053
  const fileContextCache = /* @__PURE__ */ new Map();
35039
35054
  const libraryFileCache = /* @__PURE__ */ new Map();
35055
+ const suppressions = /* @__PURE__ */ new Map();
35056
+ const suppress = (diagnostic, source) => {
35057
+ const { ruleKey } = getDiagnosticRuleIdentity(diagnostic);
35058
+ const suppressionKey = `${ruleKey}\u0000${source}`;
35059
+ const existing = suppressions.get(suppressionKey);
35060
+ suppressions.set(suppressionKey, existing ? {
35061
+ ...existing,
35062
+ count: existing.count + 1
35063
+ } : {
35064
+ rule: ruleKey,
35065
+ source,
35066
+ count: 1
35067
+ });
35068
+ return null;
35069
+ };
35040
35070
  const isLibraryFile = (filePath) => {
35041
35071
  let cached = libraryFileCache.get(filePath);
35042
35072
  if (cached === void 0) {
@@ -35085,50 +35115,60 @@ const buildDiagnosticPipeline = (input) => {
35085
35115
  if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
35086
35116
  return false;
35087
35117
  };
35088
- return { apply: (diagnostic) => {
35089
- if (shouldAutoSuppress(diagnostic)) return null;
35090
- let current = diagnostic;
35091
- let explicitSeverityOverride;
35092
- let explicitRuleOverride;
35093
- if (severityControls) {
35094
- const { ruleKey, category } = getDiagnosticRuleIdentity(current);
35095
- explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
35096
- explicitSeverityOverride = resolveRuleSeverityOverride({
35097
- ruleKey,
35098
- category
35099
- }, severityControls);
35100
- if (explicitSeverityOverride === "off") return null;
35101
- if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
35102
- }
35103
- if (explicitRuleOverride === void 0) {
35104
- if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
35105
- }
35106
- if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
35107
- if (userConfig) {
35108
- if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return null;
35109
- if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
35110
- if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
35111
- if (isRnRawTextSuppressedByConfig(current)) return null;
35112
- }
35113
- if (respectInlineDisables && current.line > 0) {
35114
- const lines = getFileLines(current.filePath);
35115
- if (lines) {
35116
- const ruleIdentifier = `${current.plugin}/${current.rule}`;
35117
- const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
35118
- if (evaluation.isSuppressed) return null;
35119
- if (evaluation.nearMissHint) current = {
35120
- ...current,
35121
- suppressionHint: evaluation.nearMissHint
35122
- };
35118
+ const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
35119
+ if (!hasRuntimeGlobals) return false;
35120
+ if (diagnostic.rule !== "jsx-no-undef") return false;
35121
+ const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
35122
+ return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
35123
+ };
35124
+ return {
35125
+ apply: (diagnostic) => {
35126
+ if (shouldAutoSuppress(diagnostic)) return null;
35127
+ let current = diagnostic;
35128
+ let explicitSeverityOverride;
35129
+ let explicitRuleOverride;
35130
+ if (severityControls) {
35131
+ const { ruleKey, category } = getDiagnosticRuleIdentity(current);
35132
+ explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
35133
+ explicitSeverityOverride = resolveRuleSeverityOverride({
35134
+ ruleKey,
35135
+ category
35136
+ }, severityControls);
35137
+ if (explicitSeverityOverride === "off") return suppress(current, "config");
35138
+ if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
35123
35139
  }
35124
- }
35125
- const fileContext = getFileContext(current.filePath);
35126
- if (fileContext !== "production") current = {
35127
- ...current,
35128
- fileContext
35129
- };
35130
- return current;
35131
- } };
35140
+ if (explicitRuleOverride === void 0) {
35141
+ if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
35142
+ }
35143
+ if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
35144
+ if (userConfig) {
35145
+ if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
35146
+ if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
35147
+ if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
35148
+ if (isRnRawTextSuppressedByConfig(current)) return null;
35149
+ if (isJsxNoUndefSuppressedByConfig(current)) return null;
35150
+ }
35151
+ if (respectInlineDisables && current.line > 0) {
35152
+ const lines = getFileLines(current.filePath);
35153
+ if (lines) {
35154
+ const ruleIdentifier = `${current.plugin}/${current.rule}`;
35155
+ const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
35156
+ if (evaluation.isSuppressed) return suppress(current, "inline");
35157
+ if (evaluation.nearMissHint) current = {
35158
+ ...current,
35159
+ suppressionHint: evaluation.nearMissHint
35160
+ };
35161
+ }
35162
+ }
35163
+ const fileContext = getFileContext(current.filePath);
35164
+ if (fileContext !== "production") current = {
35165
+ ...current,
35166
+ fileContext
35167
+ };
35168
+ return current;
35169
+ },
35170
+ summarizeSuppressions: () => [...suppressions.values()]
35171
+ };
35132
35172
  };
35133
35173
  const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
35134
35174
  var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
@@ -35432,13 +35472,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
35432
35472
  * without recompiling react-doctor. Tests override via
35433
35473
  * `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
35434
35474
  */
35435
- var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
35436
- const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
35437
- if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
35438
- const parsed = Number(raw);
35439
- if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
35440
- return parsed;
35441
- } }) {};
35475
+ var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
35442
35476
  /**
35443
35477
  * Hard cap on combined stdout+stderr bytes per oxlint batch. The
35444
35478
  * subprocess gets SIGKILL'd if it produces more; the recovery path
@@ -35586,6 +35620,7 @@ const STRING_ARRAY_FIELD_NAMES = [
35586
35620
  "projects",
35587
35621
  "textComponents",
35588
35622
  "rawTextWrapperComponents",
35623
+ "runtimeGlobals",
35589
35624
  "serverAuthFunctionNames"
35590
35625
  ];
35591
35626
  const SURFACE_CONTROL_FIELD_NAMES = [
@@ -37290,6 +37325,11 @@ const toCanonicalPath = (filePath) => {
37290
37325
  };
37291
37326
  const DEAD_CODE_PLUGIN = "deslop";
37292
37327
  const DEAD_CODE_CATEGORY = "Maintainability";
37328
+ const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
37329
+ "react-doctor",
37330
+ "eslint-plugin-react-doctor",
37331
+ "oxlint-plugin-react-doctor"
37332
+ ]);
37293
37333
  const DEAD_CODE_WORKER_SCRIPT = `
37294
37334
  const inputChunks = [];
37295
37335
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -37595,6 +37635,7 @@ const checkDeadCode = async (options) => {
37595
37635
  });
37596
37636
  }
37597
37637
  for (const unusedDependency of result.unusedDependencies) {
37638
+ if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
37598
37639
  const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
37599
37640
  diagnostics.push({
37600
37641
  filePath: "package.json",
@@ -37773,7 +37814,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
37773
37814
  return yield* tryPromise({
37774
37815
  try: (signal) => checkDeadCode({
37775
37816
  rootDirectory: input.rootDirectory,
37776
- userConfig: input.userConfig,
37777
37817
  parseConcurrency: input.parseConcurrency,
37778
37818
  workerTimeoutMs: input.workerTimeoutMs,
37779
37819
  abortSignal: signal
@@ -37896,6 +37936,11 @@ const isSafeGitRevision = (candidate) => {
37896
37936
  if (candidate.includes("..") || candidate.includes("@{")) return false;
37897
37937
  return SAFE_GIT_REVISION_PATTERN.test(candidate);
37898
37938
  };
37939
+ const resolveSpawnArgsLengthCap = () => {
37940
+ if (process.platform === "win32") return SPAWN_ARGS_MAX_LENGTH_CHARS;
37941
+ if (process.platform === "darwin") return SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN;
37942
+ return SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX;
37943
+ };
37899
37944
  /**
37900
37945
  * Splits a git revision range into its endpoints: three-dot `A...B`
37901
37946
  * (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
@@ -37980,7 +38025,8 @@ var Git = class Git extends Service()("react-doctor/Git") {
37980
38025
  return scoped(gen(function* () {
37981
38026
  if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
37982
38027
  const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
37983
- if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
38028
+ const spawnArgsLengthCap = resolveSpawnArgsLengthCap();
38029
+ if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
37984
38030
  const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37985
38031
  cwd: input.directory,
37986
38032
  env: input.env,
@@ -38149,6 +38195,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38149
38195
  if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
38150
38196
  const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
38151
38197
  if (baseBranch === null) return null;
38198
+ if (!isSafeGitRevision(baseBranch)) return null;
38152
38199
  if (explicitBaseBranch !== void 0) {
38153
38200
  if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
38154
38201
  }
@@ -38499,7 +38546,7 @@ const normalizeConfigForHash = (config) => {
38499
38546
  if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
38500
38547
  return clone;
38501
38548
  };
38502
- const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update(" ").update(input.tsconfigContent ?? "").digest("hex");
38549
+ const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("\0").update(input.tsconfigContent ?? "").digest("hex");
38503
38550
  /**
38504
38551
  * Loads a plugin module via the local require resolver and extracts
38505
38552
  * `(name, ruleNames)` from either `module.exports.meta + rules` or
@@ -38670,12 +38717,12 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38670
38717
  if (customRulesOnly && registryEntry.originallyExternal) continue;
38671
38718
  if (rule.framework !== "global" && !rule.requires) continue;
38672
38719
  if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) continue;
38673
- const explicitSeverity = resolveRuleSeverityOverride({
38720
+ const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
38721
+ if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
38722
+ const severity = resolveRuleSeverityOverride({
38674
38723
  ruleKey: registryEntry.key,
38675
38724
  category: rule.category
38676
- }, severityControls);
38677
- if (rule.defaultEnabled === false && explicitSeverity === void 0) continue;
38678
- const severity = explicitSeverity ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
38725
+ }, severityControls) ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
38679
38726
  if (severity === "off") continue;
38680
38727
  enabledReactDoctorRules[registryEntry.key] = severity;
38681
38728
  }
@@ -38777,24 +38824,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
38777
38824
  updatedAtMs: Date.now(),
38778
38825
  files: Object.fromEntries(cappedEntries)
38779
38826
  };
38780
- const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
38781
- const prunedRulesets = {};
38782
- for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
38783
38827
  atomicWriteJson(cacheFilePath, {
38784
38828
  version: 1,
38785
- rulesets: prunedRulesets
38829
+ rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
38786
38830
  });
38787
38831
  }
38788
38832
  };
38789
38833
  };
38790
38834
  const bundledRequire = createRequire(import.meta.url);
38835
+ const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
38836
+ const resolveChildNodeVersion = (nodeBinaryPath) => {
38837
+ if (nodeBinaryPath === process.execPath) return process.version;
38838
+ const cachedVersion = childNodeVersionByBinaryPath.get(nodeBinaryPath);
38839
+ if (cachedVersion !== void 0) return cachedVersion;
38840
+ let version;
38841
+ try {
38842
+ version = execFileSync(nodeBinaryPath, ["--version"], {
38843
+ encoding: "utf8",
38844
+ timeout: NODE_VERSION_PROBE_TIMEOUT_MS
38845
+ }).trim();
38846
+ } catch {
38847
+ version = `unknown:${nodeBinaryPath}`;
38848
+ }
38849
+ childNodeVersionByBinaryPath.set(nodeBinaryPath, version);
38850
+ return version;
38851
+ };
38791
38852
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
38792
38853
  "oxlint/package.json",
38793
38854
  "oxlint-plugin-react-doctor/package.json",
38794
38855
  "eslint-plugin-react-hooks/package.json"
38795
38856
  ];
38796
- const resolveOxlintToolchainVersions = () => {
38797
- const versions = [`node=${process.version}`];
38857
+ const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
38858
+ const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
38798
38859
  for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
38799
38860
  const packageJson = bundledRequire(specifier);
38800
38861
  const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
@@ -39328,11 +39389,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
39328
39389
  const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
39329
39390
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
39330
39391
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
39392
+ const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
39331
39393
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
39332
- const buildReactCompilerMessage = (reasonSummary) => {
39394
+ const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
39333
39395
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
39334
- if (!normalizedSummary) return REACT_COMPILER_GENERIC_MESSAGE;
39335
- return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${REACT_COMPILER_ACTION}`;
39396
+ if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
39397
+ return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
39336
39398
  };
39337
39399
  const PLUGIN_CATEGORY_MAP = {
39338
39400
  react: "Bugs",
@@ -39381,7 +39443,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
39381
39443
  const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
39382
39444
  const reasonDetail = reasonDetailLines.join("\n").trim();
39383
39445
  return {
39384
- message: buildReactCompilerMessage(reasonSummary.trim()),
39446
+ message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
39385
39447
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
39386
39448
  };
39387
39449
  }
@@ -39649,21 +39711,27 @@ const spawnLintBatches = async (input) => {
39649
39711
  const allDiagnostics = [];
39650
39712
  const droppedFiles = [];
39651
39713
  let firstDropReason = null;
39652
- const splitDeadlineMs = Date.now() + splitTotalBudgetMs;
39653
- const spawnLintBatch = async (batch, depth) => {
39714
+ const spawnLintBatch = async (batch, depth, splitBudget) => {
39654
39715
  const batchArgs = [...baseArgs, ...batch];
39655
39716
  try {
39656
39717
  return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
39657
39718
  } catch (error) {
39658
39719
  if (!isSplittableReactDoctorError(error)) throw error;
39659
- const splitBudgetExhausted = Date.now() >= splitDeadlineMs || depth >= splitMaxDepth;
39660
- if (batch.length <= 1 || splitBudgetExhausted) {
39720
+ splitBudget.deadlineMs ??= Date.now() + splitTotalBudgetMs;
39721
+ const isBudgetElapsed = Date.now() >= splitBudget.deadlineMs;
39722
+ const isDepthCapReached = depth >= splitMaxDepth;
39723
+ if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
39661
39724
  droppedFiles.push(...batch);
39662
- if (firstDropReason === null) firstDropReason = splitBudgetExhausted && batch.length > 1 ? `${error.message} (split budget exhausted after ${splitMaxDepth} levels / ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s)` : error.message;
39725
+ if (firstDropReason === null) {
39726
+ let limitHint = "";
39727
+ if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
39728
+ else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
39729
+ firstDropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
39730
+ }
39663
39731
  return [];
39664
39732
  }
39665
39733
  const splitIndex = Math.ceil(batch.length / 2);
39666
- return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
39734
+ return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1, splitBudget), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1, splitBudget)];
39667
39735
  }
39668
39736
  };
39669
39737
  let startedFileCount = 0;
@@ -39680,7 +39748,7 @@ const spawnLintBatches = async (input) => {
39680
39748
  try {
39681
39749
  const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
39682
39750
  startedFileCount += batch.length;
39683
- const batchDiagnostics = await spawnLintBatch(batch, 0);
39751
+ const batchDiagnostics = await spawnLintBatch(batch, 0, { deadlineMs: null });
39684
39752
  scannedFileCount += batch.length;
39685
39753
  if (onFileProgress) {
39686
39754
  displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
@@ -39754,7 +39822,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
39754
39822
  if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
39755
39823
  const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
39756
39824
  if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
39757
- return Path.join(os.tmpdir(), "react-doctor-cache", projectCacheSubdir(projectDirectory));
39825
+ return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
39826
+ };
39827
+ const resolveUserCacheScope = () => {
39828
+ try {
39829
+ return String(process.getuid?.() ?? os.userInfo().username);
39830
+ } catch {
39831
+ return "shared";
39832
+ }
39758
39833
  };
39759
39834
  const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
39760
39835
  /**
@@ -39922,7 +39997,7 @@ const runOxlint = async (options) => {
39922
39997
  extendsPaths: [],
39923
39998
  ruleSelection: "cacheable"
39924
39999
  }),
39925
- toolchainVersions: resolveOxlintToolchainVersions(),
40000
+ toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
39926
40001
  ignorePatterns: combinedPatterns,
39927
40002
  tsconfigContent
39928
40003
  });
@@ -39936,7 +40011,7 @@ const runOxlint = async (options) => {
39936
40011
  missFiles.push(candidateFile);
39937
40012
  continue;
39938
40013
  }
39939
- const cacheKey = `${candidateFile.replaceAll("\\", "/")}${contentHash}`;
40014
+ const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
39940
40015
  cacheKeyByFile.set(candidateFile, cacheKey);
39941
40016
  const cachedDiagnostics = cache.lookup(cacheKey);
39942
40017
  if (cachedDiagnostics === null) missFiles.push(candidateFile);
@@ -40252,7 +40327,7 @@ const ScoreApiResponseSchema = Struct({
40252
40327
  })))
40253
40328
  });
40254
40329
  const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
40255
- const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, ...rest }) => rest);
40330
+ const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, fixGroupId: _fixGroupId, ...rest }) => rest);
40256
40331
  const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
40257
40332
  const describeFailure = (error) => {
40258
40333
  if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
@@ -40556,7 +40631,10 @@ const ALERT_SEVERITY_RANK = {
40556
40631
  medium: 2,
40557
40632
  low: 1
40558
40633
  };
40559
- const severityRank = (severity) => ALERT_SEVERITY_RANK[severity.toLowerCase()] ?? 0;
40634
+ const severityRank = (severity) => {
40635
+ const normalized = severity.toLowerCase();
40636
+ return Object.hasOwn(ALERT_SEVERITY_RANK, normalized) ? ALERT_SEVERITY_RANK[normalized] : 0;
40637
+ };
40560
40638
  const displaySeverity = (severity) => {
40561
40639
  const normalized = sanitizeTerminalText(severity.toLowerCase());
40562
40640
  return normalized === "middle" ? "medium" : normalized;
@@ -40573,7 +40651,7 @@ const ALERT_TYPE_LABELS = {
40573
40651
  typosquat: "possible typosquat"
40574
40652
  };
40575
40653
  const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
40576
- const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type] ?? sanitizeTerminalText(humanizeAlertType(type));
40654
+ const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
40577
40655
  const summarizeAlertNote = (note) => {
40578
40656
  const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
40579
40657
  const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
@@ -40668,7 +40746,10 @@ const checkSupplyChain = (input) => gen(function* () {
40668
40746
  * outage.
40669
40747
  */
40670
40748
  var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
40671
- static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(checkSupplyChain(input).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
40749
+ static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
40750
+ ...input,
40751
+ totalTimeoutMs
40752
+ })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
40672
40753
  static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
40673
40754
  };
40674
40755
  const getObjectProperty = (value, propertyName) => {
@@ -40816,10 +40897,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40816
40897
  ...checkExpoProject(scanDirectory, project),
40817
40898
  ...checkReactNativeProject(scanDirectory, project)
40818
40899
  ])));
40819
- const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
40900
+ const securityScanFailedRef = yield* make$13(false);
40901
+ const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
40820
40902
  project,
40821
40903
  ignoredTags: input.ignoredTags
40822
- })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
40904
+ })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
40823
40905
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
40824
40906
  const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
40825
40907
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
@@ -40857,7 +40939,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40857
40939
  const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
40858
40940
  const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
40859
40941
  rootDirectory: scanDirectory,
40860
- userConfig: resolvedConfig.config,
40861
40942
  parseConcurrency: deadCodeParseConcurrency,
40862
40943
  workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
40863
40944
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
@@ -40985,6 +41066,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40985
41066
  metadata: scoreMetadata
40986
41067
  });
40987
41068
  const lintPartialFailures = yield* get$2(partialFailuresRef);
41069
+ const securityScanFailed = yield* get$2(securityScanFailedRef);
40988
41070
  return {
40989
41071
  project,
40990
41072
  userConfig: resolvedConfig.config,
@@ -41005,8 +41087,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41005
41087
  scanElapsedMilliseconds,
41006
41088
  scanConcurrency,
41007
41089
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
41090
+ securityScanFailed,
41008
41091
  lintCacheHitFileCount,
41009
- lintCacheTotalFileCount
41092
+ lintCacheTotalFileCount,
41093
+ suppressedRuleCounts: transform.summarizeSuppressions()
41010
41094
  };
41011
41095
  }).pipe(withSpan("runInspect", { attributes: {
41012
41096
  "inspect.directory": input.directory,
@@ -41345,6 +41429,7 @@ const buildJsonReport = (input) => {
41345
41429
  return {
41346
41430
  schemaVersion: 1,
41347
41431
  mode: input.mode,
41432
+ ...input.baselineDegraded ? { baselineDegraded: true } : {},
41348
41433
  ...shared
41349
41434
  };
41350
41435
  };
@@ -41645,4 +41730,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41645
41730
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41646
41731
 
41647
41732
  //# sourceMappingURL=index.js.map
41648
- //# debugId=9807b1bf-1e62-5421-ad35-d1f4129c1538
41733
+ //# debugId=7cfd5199-4216-5c6e-8cf5-c9623c317a1e