react-doctor 0.5.8-dev.c16e8ea → 0.5.8-dev.c2af308

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/lsp.js CHANGED
@@ -6,7 +6,7 @@ import os from "node:os";
6
6
  import * as Path from "node:path";
7
7
  import path from "node:path";
8
8
  import * as NodeChildProcess from "node:child_process";
9
- import { spawn, spawnSync } from "node:child_process";
9
+ import { execFileSync, spawn, spawnSync } from "node:child_process";
10
10
  import * as ts from "typescript";
11
11
  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";
12
12
  import { parseJSON5 } from "confbox";
@@ -33731,6 +33731,9 @@ const SCORE_API_URL = "https://www.react.doctor/api/score";
33731
33731
  const FETCH_TIMEOUT_MS = 1e4;
33732
33732
  const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
33733
33733
  const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
33734
+ const SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX = 15e5;
33735
+ const SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN = 8e5;
33736
+ const NODE_VERSION_PROBE_TIMEOUT_MS = 5e3;
33734
33737
  const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
33735
33738
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
33736
33739
  const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
@@ -34578,7 +34581,7 @@ const tokenizeRuleList = (ruleList) => {
34578
34581
  const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
34579
34582
  const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
34580
34583
  const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
34581
- 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}\`.`;
34584
+ 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}\`).`;
34582
34585
  const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
34583
34586
  const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34584
34587
  const candidates = [{
@@ -35095,6 +35098,21 @@ const buildDiagnosticPipeline = (input) => {
35095
35098
  const fileLinesCache = /* @__PURE__ */ new Map();
35096
35099
  const fileContextCache = /* @__PURE__ */ new Map();
35097
35100
  const libraryFileCache = /* @__PURE__ */ new Map();
35101
+ const suppressions = /* @__PURE__ */ new Map();
35102
+ const suppress = (diagnostic, source) => {
35103
+ const { ruleKey } = getDiagnosticRuleIdentity(diagnostic);
35104
+ const suppressionKey = `${ruleKey}\u0000${source}`;
35105
+ const existing = suppressions.get(suppressionKey);
35106
+ suppressions.set(suppressionKey, existing ? {
35107
+ ...existing,
35108
+ count: existing.count + 1
35109
+ } : {
35110
+ rule: ruleKey,
35111
+ source,
35112
+ count: 1
35113
+ });
35114
+ return null;
35115
+ };
35098
35116
  const isLibraryFile = (filePath) => {
35099
35117
  let cached = libraryFileCache.get(filePath);
35100
35118
  if (cached === void 0) {
@@ -35149,51 +35167,54 @@ const buildDiagnosticPipeline = (input) => {
35149
35167
  const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
35150
35168
  return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
35151
35169
  };
35152
- return { apply: (diagnostic) => {
35153
- if (shouldAutoSuppress(diagnostic)) return null;
35154
- let current = diagnostic;
35155
- let explicitSeverityOverride;
35156
- let explicitRuleOverride;
35157
- if (severityControls) {
35158
- const { ruleKey, category } = getDiagnosticRuleIdentity(current);
35159
- explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
35160
- explicitSeverityOverride = resolveRuleSeverityOverride({
35161
- ruleKey,
35162
- category
35163
- }, severityControls);
35164
- if (explicitSeverityOverride === "off") return null;
35165
- if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
35166
- }
35167
- if (explicitRuleOverride === void 0) {
35168
- if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
35169
- }
35170
- if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
35171
- if (userConfig) {
35172
- if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return null;
35173
- if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
35174
- if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
35175
- if (isRnRawTextSuppressedByConfig(current)) return null;
35176
- if (isJsxNoUndefSuppressedByConfig(current)) return null;
35177
- }
35178
- if (respectInlineDisables && current.line > 0) {
35179
- const lines = getFileLines(current.filePath);
35180
- if (lines) {
35181
- const ruleIdentifier = `${current.plugin}/${current.rule}`;
35182
- const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
35183
- if (evaluation.isSuppressed) return null;
35184
- if (evaluation.nearMissHint) current = {
35185
- ...current,
35186
- suppressionHint: evaluation.nearMissHint
35187
- };
35170
+ return {
35171
+ apply: (diagnostic) => {
35172
+ if (shouldAutoSuppress(diagnostic)) return null;
35173
+ let current = diagnostic;
35174
+ let explicitSeverityOverride;
35175
+ let explicitRuleOverride;
35176
+ if (severityControls) {
35177
+ const { ruleKey, category } = getDiagnosticRuleIdentity(current);
35178
+ explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
35179
+ explicitSeverityOverride = resolveRuleSeverityOverride({
35180
+ ruleKey,
35181
+ category
35182
+ }, severityControls);
35183
+ if (explicitSeverityOverride === "off") return suppress(current, "config");
35184
+ if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
35188
35185
  }
35189
- }
35190
- const fileContext = getFileContext(current.filePath);
35191
- if (fileContext !== "production") current = {
35192
- ...current,
35193
- fileContext
35194
- };
35195
- return current;
35196
- } };
35186
+ if (explicitRuleOverride === void 0) {
35187
+ if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
35188
+ }
35189
+ if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
35190
+ if (userConfig) {
35191
+ if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
35192
+ if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
35193
+ if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
35194
+ if (isRnRawTextSuppressedByConfig(current)) return null;
35195
+ if (isJsxNoUndefSuppressedByConfig(current)) return null;
35196
+ }
35197
+ if (respectInlineDisables && current.line > 0) {
35198
+ const lines = getFileLines(current.filePath);
35199
+ if (lines) {
35200
+ const ruleIdentifier = `${current.plugin}/${current.rule}`;
35201
+ const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
35202
+ if (evaluation.isSuppressed) return suppress(current, "inline");
35203
+ if (evaluation.nearMissHint) current = {
35204
+ ...current,
35205
+ suppressionHint: evaluation.nearMissHint
35206
+ };
35207
+ }
35208
+ }
35209
+ const fileContext = getFileContext(current.filePath);
35210
+ if (fileContext !== "production") current = {
35211
+ ...current,
35212
+ fileContext
35213
+ };
35214
+ return current;
35215
+ },
35216
+ summarizeSuppressions: () => [...suppressions.values()]
35217
+ };
35197
35218
  };
35198
35219
  const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
35199
35220
  var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
@@ -35475,13 +35496,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
35475
35496
  * without recompiling react-doctor. Tests override via
35476
35497
  * `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
35477
35498
  */
35478
- var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
35479
- const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
35480
- if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
35481
- const parsed = Number(raw);
35482
- if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
35483
- return parsed;
35484
- } }) {};
35499
+ var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
35485
35500
  /**
35486
35501
  * Hard cap on combined stdout+stderr bytes per oxlint batch. The
35487
35502
  * subprocess gets SIGKILL'd if it produces more; the recovery path
@@ -37775,7 +37790,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
37775
37790
  return yield* tryPromise({
37776
37791
  try: (signal) => checkDeadCode({
37777
37792
  rootDirectory: input.rootDirectory,
37778
- userConfig: input.userConfig,
37779
37793
  parseConcurrency: input.parseConcurrency,
37780
37794
  workerTimeoutMs: input.workerTimeoutMs,
37781
37795
  abortSignal: signal
@@ -37898,6 +37912,11 @@ const isSafeGitRevision = (candidate) => {
37898
37912
  if (candidate.includes("..") || candidate.includes("@{")) return false;
37899
37913
  return SAFE_GIT_REVISION_PATTERN.test(candidate);
37900
37914
  };
37915
+ const resolveSpawnArgsLengthCap = () => {
37916
+ if (process.platform === "win32") return SPAWN_ARGS_MAX_LENGTH_CHARS;
37917
+ if (process.platform === "darwin") return SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN;
37918
+ return SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX;
37919
+ };
37901
37920
  /**
37902
37921
  * Splits a git revision range into its endpoints: three-dot `A...B`
37903
37922
  * (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
@@ -37982,7 +38001,8 @@ var Git = class Git extends Service()("react-doctor/Git") {
37982
38001
  return scoped(gen(function* () {
37983
38002
  if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
37984
38003
  const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
37985
- if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
38004
+ const spawnArgsLengthCap = resolveSpawnArgsLengthCap();
38005
+ if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
37986
38006
  const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37987
38007
  cwd: input.directory,
37988
38008
  env: input.env,
@@ -38151,6 +38171,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38151
38171
  if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
38152
38172
  const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
38153
38173
  if (baseBranch === null) return null;
38174
+ if (!isSafeGitRevision(baseBranch)) return null;
38154
38175
  if (explicitBaseBranch !== void 0) {
38155
38176
  if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
38156
38177
  }
@@ -38501,7 +38522,7 @@ const normalizeConfigForHash = (config) => {
38501
38522
  if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
38502
38523
  return clone;
38503
38524
  };
38504
- 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");
38525
+ 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");
38505
38526
  /**
38506
38527
  * Loads a plugin module via the local require resolver and extracts
38507
38528
  * `(name, ruleNames)` from either `module.exports.meta + rules` or
@@ -38779,24 +38800,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
38779
38800
  updatedAtMs: Date.now(),
38780
38801
  files: Object.fromEntries(cappedEntries)
38781
38802
  };
38782
- const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
38783
- const prunedRulesets = {};
38784
- for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
38785
38803
  atomicWriteJson(cacheFilePath, {
38786
38804
  version: 1,
38787
- rulesets: prunedRulesets
38805
+ rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
38788
38806
  });
38789
38807
  }
38790
38808
  };
38791
38809
  };
38792
38810
  const bundledRequire = createRequire(import.meta.url);
38811
+ const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
38812
+ const resolveChildNodeVersion = (nodeBinaryPath) => {
38813
+ if (nodeBinaryPath === process.execPath) return process.version;
38814
+ const cachedVersion = childNodeVersionByBinaryPath.get(nodeBinaryPath);
38815
+ if (cachedVersion !== void 0) return cachedVersion;
38816
+ let version;
38817
+ try {
38818
+ version = execFileSync(nodeBinaryPath, ["--version"], {
38819
+ encoding: "utf8",
38820
+ timeout: NODE_VERSION_PROBE_TIMEOUT_MS
38821
+ }).trim();
38822
+ } catch {
38823
+ version = `unknown:${nodeBinaryPath}`;
38824
+ }
38825
+ childNodeVersionByBinaryPath.set(nodeBinaryPath, version);
38826
+ return version;
38827
+ };
38793
38828
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
38794
38829
  "oxlint/package.json",
38795
38830
  "oxlint-plugin-react-doctor/package.json",
38796
38831
  "eslint-plugin-react-hooks/package.json"
38797
38832
  ];
38798
- const resolveOxlintToolchainVersions = () => {
38799
- const versions = [`node=${process.version}`];
38833
+ const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
38834
+ const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
38800
38835
  for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
38801
38836
  const packageJson = bundledRequire(specifier);
38802
38837
  const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
@@ -39652,21 +39687,27 @@ const spawnLintBatches = async (input) => {
39652
39687
  const allDiagnostics = [];
39653
39688
  const droppedFiles = [];
39654
39689
  let firstDropReason = null;
39655
- const splitDeadlineMs = Date.now() + splitTotalBudgetMs;
39656
- const spawnLintBatch = async (batch, depth) => {
39690
+ const spawnLintBatch = async (batch, depth, splitBudget) => {
39657
39691
  const batchArgs = [...baseArgs, ...batch];
39658
39692
  try {
39659
39693
  return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
39660
39694
  } catch (error) {
39661
39695
  if (!isSplittableReactDoctorError(error)) throw error;
39662
- const splitBudgetExhausted = Date.now() >= splitDeadlineMs || depth >= splitMaxDepth;
39663
- if (batch.length <= 1 || splitBudgetExhausted) {
39696
+ splitBudget.deadlineMs ??= Date.now() + splitTotalBudgetMs;
39697
+ const isBudgetElapsed = Date.now() >= splitBudget.deadlineMs;
39698
+ const isDepthCapReached = depth >= splitMaxDepth;
39699
+ if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
39664
39700
  droppedFiles.push(...batch);
39665
- if (firstDropReason === null) firstDropReason = splitBudgetExhausted && batch.length > 1 ? `${error.message} (split budget exhausted after ${splitMaxDepth} levels / ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s)` : error.message;
39701
+ if (firstDropReason === null) {
39702
+ let limitHint = "";
39703
+ if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
39704
+ else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
39705
+ firstDropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
39706
+ }
39666
39707
  return [];
39667
39708
  }
39668
39709
  const splitIndex = Math.ceil(batch.length / 2);
39669
- return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
39710
+ return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1, splitBudget), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1, splitBudget)];
39670
39711
  }
39671
39712
  };
39672
39713
  let startedFileCount = 0;
@@ -39683,7 +39724,7 @@ const spawnLintBatches = async (input) => {
39683
39724
  try {
39684
39725
  const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
39685
39726
  startedFileCount += batch.length;
39686
- const batchDiagnostics = await spawnLintBatch(batch, 0);
39727
+ const batchDiagnostics = await spawnLintBatch(batch, 0, { deadlineMs: null });
39687
39728
  scannedFileCount += batch.length;
39688
39729
  if (onFileProgress) {
39689
39730
  displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
@@ -39757,7 +39798,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
39757
39798
  if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
39758
39799
  const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
39759
39800
  if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
39760
- return Path.join(os.tmpdir(), "react-doctor-cache", projectCacheSubdir(projectDirectory));
39801
+ return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
39802
+ };
39803
+ const resolveUserCacheScope = () => {
39804
+ try {
39805
+ return String(process.getuid?.() ?? os.userInfo().username);
39806
+ } catch {
39807
+ return "shared";
39808
+ }
39761
39809
  };
39762
39810
  const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
39763
39811
  /**
@@ -39925,7 +39973,7 @@ const runOxlint = async (options) => {
39925
39973
  extendsPaths: [],
39926
39974
  ruleSelection: "cacheable"
39927
39975
  }),
39928
- toolchainVersions: resolveOxlintToolchainVersions(),
39976
+ toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
39929
39977
  ignorePatterns: combinedPatterns,
39930
39978
  tsconfigContent
39931
39979
  });
@@ -39939,7 +39987,7 @@ const runOxlint = async (options) => {
39939
39987
  missFiles.push(candidateFile);
39940
39988
  continue;
39941
39989
  }
39942
- const cacheKey = `${candidateFile.replaceAll("\\", "/")}${contentHash}`;
39990
+ const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
39943
39991
  cacheKeyByFile.set(candidateFile, cacheKey);
39944
39992
  const cachedDiagnostics = cache.lookup(cacheKey);
39945
39993
  if (cachedDiagnostics === null) missFiles.push(candidateFile);
@@ -40255,7 +40303,7 @@ const ScoreApiResponseSchema = Struct({
40255
40303
  })))
40256
40304
  });
40257
40305
  const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
40258
- const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, ...rest }) => rest);
40306
+ const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, fixGroupId: _fixGroupId, ...rest }) => rest);
40259
40307
  const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
40260
40308
  const describeFailure = (error) => {
40261
40309
  if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
@@ -40559,7 +40607,10 @@ const ALERT_SEVERITY_RANK = {
40559
40607
  medium: 2,
40560
40608
  low: 1
40561
40609
  };
40562
- const severityRank = (severity) => ALERT_SEVERITY_RANK[severity.toLowerCase()] ?? 0;
40610
+ const severityRank = (severity) => {
40611
+ const normalized = severity.toLowerCase();
40612
+ return Object.hasOwn(ALERT_SEVERITY_RANK, normalized) ? ALERT_SEVERITY_RANK[normalized] : 0;
40613
+ };
40563
40614
  const displaySeverity = (severity) => {
40564
40615
  const normalized = sanitizeTerminalText(severity.toLowerCase());
40565
40616
  return normalized === "middle" ? "medium" : normalized;
@@ -40576,7 +40627,7 @@ const ALERT_TYPE_LABELS = {
40576
40627
  typosquat: "possible typosquat"
40577
40628
  };
40578
40629
  const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
40579
- const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type] ?? sanitizeTerminalText(humanizeAlertType(type));
40630
+ const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
40580
40631
  const summarizeAlertNote = (note) => {
40581
40632
  const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
40582
40633
  const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
@@ -40671,7 +40722,10 @@ const checkSupplyChain = (input) => gen(function* () {
40671
40722
  * outage.
40672
40723
  */
40673
40724
  var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
40674
- static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(checkSupplyChain(input).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
40725
+ static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
40726
+ ...input,
40727
+ totalTimeoutMs
40728
+ })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
40675
40729
  static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
40676
40730
  };
40677
40731
  const getObjectProperty = (value, propertyName) => {
@@ -40819,10 +40873,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40819
40873
  ...checkExpoProject(scanDirectory, project),
40820
40874
  ...checkReactNativeProject(scanDirectory, project)
40821
40875
  ])));
40822
- const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
40876
+ const securityScanFailedRef = yield* make$13(false);
40877
+ const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
40823
40878
  project,
40824
40879
  ignoredTags: input.ignoredTags
40825
- })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
40880
+ })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
40826
40881
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
40827
40882
  const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
40828
40883
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
@@ -40860,7 +40915,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40860
40915
  const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
40861
40916
  const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
40862
40917
  rootDirectory: scanDirectory,
40863
- userConfig: resolvedConfig.config,
40864
40918
  parseConcurrency: deadCodeParseConcurrency,
40865
40919
  workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
40866
40920
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
@@ -40988,6 +41042,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40988
41042
  metadata: scoreMetadata
40989
41043
  });
40990
41044
  const lintPartialFailures = yield* get$2(partialFailuresRef);
41045
+ const securityScanFailed = yield* get$2(securityScanFailedRef);
40991
41046
  return {
40992
41047
  project,
40993
41048
  userConfig: resolvedConfig.config,
@@ -41008,8 +41063,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41008
41063
  scanElapsedMilliseconds,
41009
41064
  scanConcurrency,
41010
41065
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
41066
+ securityScanFailed,
41011
41067
  lintCacheHitFileCount,
41012
- lintCacheTotalFileCount
41068
+ lintCacheTotalFileCount,
41069
+ suppressedRuleCounts: transform.summarizeSuppressions()
41013
41070
  };
41014
41071
  }).pipe(withSpan("runInspect", { attributes: {
41015
41072
  "inspect.directory": input.directory,
@@ -43047,6 +43104,8 @@ const METRIC = {
43047
43104
  scanCheckSkipped: "scan.check_skipped",
43048
43105
  baselineDegraded: "baseline.degraded",
43049
43106
  ruleFired: "rule.fired",
43107
+ ruleDisabled: "rule.disabled",
43108
+ ruleSuppressed: "rule.suppressed",
43050
43109
  lintFailed: "lint.failed",
43051
43110
  deadCodeFailed: "deadcode.failed",
43052
43111
  scoreUnavailable: "score.unavailable",
@@ -43059,6 +43118,9 @@ const METRIC = {
43059
43118
  installWorkflow: "install.workflow",
43060
43119
  installAgentHooks: "install.agent_hooks",
43061
43120
  installDependency: "install.dependency",
43121
+ ciScaffolded: "ci.scaffolded",
43122
+ ciUpgraded: "ci.upgraded",
43123
+ ciConfigured: "ci.configured",
43062
43124
  rulesChanged: "rules.changed",
43063
43125
  rulesQueried: "rules.queried",
43064
43126
  lspSessionStarted: "lsp.session.started",
@@ -43383,5 +43445,5 @@ const startLanguageServer = () => {
43383
43445
  };
43384
43446
  //#endregion
43385
43447
  export { startLanguageServer };
43386
- !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]="195b3b8e-1867-5a60-b233-6b100ac6462b")}catch(e){}}();
43387
- //# debugId=195b3b8e-1867-5a60-b233-6b100ac6462b
43448
+ !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]="349b2e37-65e9-5f86-8168-70334a95aeec")}catch(e){}}();
43449
+ //# debugId=349b2e37-65e9-5f86-8168-70334a95aeec
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.8-dev.c16e8ea",
3
+ "version": "0.5.8-dev.c2af308",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -64,8 +64,9 @@
64
64
  "vscode-languageserver": "^9.0.1",
65
65
  "vscode-languageserver-textdocument": "^1.0.12",
66
66
  "vscode-uri": "^3.1.0",
67
- "deslop-js": "0.5.8",
68
- "oxlint-plugin-react-doctor": "0.5.8-dev.c16e8ea"
67
+ "yaml": "^2.9.0",
68
+ "oxlint-plugin-react-doctor": "0.5.8-dev.c2af308",
69
+ "deslop-js": "0.5.8"
69
70
  },
70
71
  "devDependencies": {
71
72
  "@types/babel__code-frame": "^7.27.0",
@@ -73,9 +74,9 @@
73
74
  "@xterm/headless": "^6.0.0",
74
75
  "commander": "^14.0.3",
75
76
  "ora": "^9.4.0",
76
- "@react-doctor/api": "0.5.8",
77
77
  "@react-doctor/core": "0.5.8",
78
- "@react-doctor/language-server": "0.5.8"
78
+ "@react-doctor/language-server": "0.5.8",
79
+ "@react-doctor/api": "0.5.8"
79
80
  },
80
81
  "engines": {
81
82
  "node": "^20.19.0 || >=22.13.0"