react-doctor 0.9.3-dev.19f2148 → 0.9.3-dev.3466fe1

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
@@ -9,7 +9,7 @@ import { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FIL
9
9
  import * as Crypto from "node:crypto";
10
10
  import crypto, { createHash } from "node:crypto";
11
11
  import * as NodeChildProcess from "node:child_process";
12
- import { execFileSync, spawn, spawnSync } from "node:child_process";
12
+ import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
13
13
  import { ResolverFactory } from "oxc-resolver";
14
14
  import ts from "typescript";
15
15
  import * as NodeUrl from "node:url";
@@ -3647,7 +3647,7 @@ const flatMap$5 = /* @__PURE__ */ dual(2, (self, f) => isNone(self) ? none() : f
3647
3647
  * @category filtering
3648
3648
  * @since 2.0.0
3649
3649
  */
3650
- const filter = /* @__PURE__ */ dual(2, (self, predicate) => isNone(self) ? none() : predicate(self.value) ? some(self.value) : none());
3650
+ const filter$1 = /* @__PURE__ */ dual(2, (self, predicate) => isNone(self) ? none() : predicate(self.value) ? some(self.value) : none());
3651
3651
  //#endregion
3652
3652
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Result.js
3653
3653
  /**
@@ -15586,7 +15586,7 @@ function transformOptional(f) {
15586
15586
  */
15587
15587
  function withDefault$1(defaultValue) {
15588
15588
  return new Getter((o) => {
15589
- const filtered = filter(o, isNotUndefined);
15589
+ const filtered = filter$1(o, isNotUndefined);
15590
15590
  return isSome(filtered) ? succeed$2(filtered) : mapEager(defaultValue, some);
15591
15591
  });
15592
15592
  }
@@ -19434,6 +19434,10 @@ var JsonReportProjectEntryV3 = class extends Class("JsonReportProjectEntryV3")({
19434
19434
  scannedFileCount: optional(Number$1),
19435
19435
  elapsedMilliseconds: Number$1
19436
19436
  }) {};
19437
+ var JsonReportSkippedProject = class extends Class("JsonReportSkippedProject")({
19438
+ directory: String$1,
19439
+ reason: Literal("max-duration")
19440
+ }) {};
19437
19441
  /** Original full, diff, and staged report contract. */
19438
19442
  var JsonReportV1 = class extends Class("JsonReportV1")({
19439
19443
  schemaVersion: Literal(1),
@@ -19462,6 +19466,7 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
19462
19466
  reactDetected: optional(Boolean$1),
19463
19467
  diff: NullOr(JsonReportDiffInfo),
19464
19468
  projects: ArraySchema(JsonReportProjectEntry),
19469
+ skippedProjects: optional(ArraySchema(JsonReportSkippedProject)),
19465
19470
  diagnostics: ArraySchema(Diagnostic),
19466
19471
  summary: JsonReportSummary,
19467
19472
  elapsedMilliseconds: Number$1,
@@ -19490,6 +19495,7 @@ var JsonReportV2 = class extends Class("JsonReportV2")({
19490
19495
  diff: NullOr(JsonReportDiffInfo),
19491
19496
  baseline: JsonReportBaseline,
19492
19497
  projects: ArraySchema(JsonReportProjectEntry),
19498
+ skippedProjects: optional(ArraySchema(JsonReportSkippedProject)),
19493
19499
  diagnostics: ArraySchema(Diagnostic),
19494
19500
  summary: JsonReportSummary,
19495
19501
  elapsedMilliseconds: Number$1,
@@ -19506,6 +19512,7 @@ var JsonReportV3 = class extends Class("JsonReportV3")({
19506
19512
  diff: NullOr(JsonReportDiffInfo),
19507
19513
  baseline: optional(JsonReportBaseline),
19508
19514
  projects: ArraySchema(JsonReportProjectEntryV3),
19515
+ skippedProjects: optional(ArraySchema(JsonReportSkippedProject)),
19509
19516
  diagnostics: ArraySchema(JsonReportDiagnosticV3),
19510
19517
  summary: JsonReportSummary,
19511
19518
  elapsedMilliseconds: Number$1,
@@ -53488,6 +53495,47 @@ const flattenArray = (self) => transformPull$1(self, (pull) => {
53488
53495
  }));
53489
53496
  });
53490
53497
  /**
53498
+ * Filters arrays of elements emitted by a channel, applying the filter
53499
+ * to each element within the arrays and only emitting non-empty filtered arrays.
53500
+ *
53501
+ * **Example** (Filtering array output)
53502
+ *
53503
+ * ```ts
53504
+ * import { Array, Channel } from "effect"
53505
+ *
53506
+ * const nonEmptyArrayPredicate = Array.isReadonlyArrayNonEmpty
53507
+ *
53508
+ * // Create a channel that outputs arrays of mixed data
53509
+ * const arrayChannel = Channel.fromIterable([
53510
+ * Array.make(1, 2, 3, 4, 5),
53511
+ * Array.make(6, 7, 8, 9, 10),
53512
+ * Array.make(11, 12, 13, 14, 15)
53513
+ * ]).pipe(Channel.filter(nonEmptyArrayPredicate))
53514
+ *
53515
+ * // Filter arrays to keep only even numbers
53516
+ * const evenArraysChannel = Channel.filterArray(arrayChannel, (n) => n % 2 === 0)
53517
+ * // Outputs: [2, 4], [6, 8, 10], [12, 14]
53518
+ * // Note: Only non-empty filtered arrays are emitted
53519
+ *
53520
+ * // Arrays that would become empty after filtering are discarded entirely
53521
+ * const oddChannel = Channel.fromIterable([
53522
+ * Array.make(1, 3, 5),
53523
+ * Array.make(2, 4),
53524
+ * Array.make(7, 9)
53525
+ * ]).pipe(Channel.filter(nonEmptyArrayPredicate))
53526
+ * const filteredOddChannel = Channel.filterArray(oddChannel, (n) => n % 2 === 0)
53527
+ * // Outputs: [2, 4] (the arrays [1,3,5] and [7,9] are discarded)
53528
+ * ```
53529
+ *
53530
+ * @category filtering
53531
+ * @since 4.0.0
53532
+ */
53533
+ const filterArray = /* @__PURE__ */ dual(2, (self, predicate) => transformPull$1(self, (pull) => succeed$2(flatMap$2(pull, function loop(arr) {
53534
+ const passes = [];
53535
+ for (let i = 0; i < arr.length; i++) if (predicate(arr[i])) passes.push(arr[i]);
53536
+ return isReadonlyArrayNonEmpty(passes) ? succeed$2(passes) : flatMap$2(pull, loop);
53537
+ }))));
53538
+ /**
53491
53539
  * Filters and maps each element inside emitted non-empty arrays using a
53492
53540
  * `Filter`.
53493
53541
  *
@@ -55125,6 +55173,30 @@ const concat = /* @__PURE__ */ dual(2, (self, that) => flatten(fromArray([self,
55125
55173
  */
55126
55174
  const merge$1 = /* @__PURE__ */ dual((args) => isStream(args[0]) && isStream(args[1]), (self, that, options) => fromChannel(merge$2(toChannel(self), toChannel(that), options)));
55127
55175
  /**
55176
+ * Filters a stream to the elements that satisfy a predicate.
55177
+ *
55178
+ * **Example** (Filtering stream values)
55179
+ *
55180
+ * ```ts
55181
+ * import { Console, Effect, Stream } from "effect"
55182
+ *
55183
+ * const program = Effect.gen(function*() {
55184
+ * const stream = Stream.make(1, 2, 3, 4).pipe(
55185
+ * Stream.filter((n) => n % 2 === 0)
55186
+ * )
55187
+ * const values = yield* Stream.runCollect(stream)
55188
+ * yield* Console.log(values)
55189
+ * })
55190
+ *
55191
+ * Effect.runPromise(program)
55192
+ * // Output: [ 2, 4 ]
55193
+ * ```
55194
+ *
55195
+ * @category filtering
55196
+ * @since 2.0.0
55197
+ */
55198
+ const filter = /* @__PURE__ */ dual(2, (self, predicate) => fromChannel(filterArray(toChannel(self), predicate)));
55199
+ /**
55128
55200
  * Filters and maps stream elements in one pass using a `Filter`.
55129
55201
  *
55130
55202
  * @category filtering
@@ -61908,6 +61980,7 @@ const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
61908
61980
  const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
61909
61981
  const MINIFIED_SNIFF_BYTES = 65536;
61910
61982
  const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
61983
+ const GIT_CHECK_ATTR_MAX_BUFFER_BYTES = GIT_LS_FILES_MAX_BUFFER_BYTES;
61911
61984
  const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
61912
61985
  const IGNORED_DIRECTORIES = new Set([
61913
61986
  ".angular",
@@ -62216,13 +62289,9 @@ const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
62216
62289
  const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
62217
62290
  const ABORT_EXIT_CODES = new Set([134, 3221226505]);
62218
62291
  const OXLINT_OOM_RESCUE_BUDGET_MS = 6e4;
62219
- const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
62220
- const LINT_PHASE_TIMEOUT_MS = 3e5;
62221
- const SCAN_TOTAL_DEADLINE_MS = 9e5;
62222
62292
  const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
62223
62293
  const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
62224
62294
  const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
62225
- const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
62226
62295
  const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
62227
62296
  const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
62228
62297
  const REACT_SERVER_DOM_PACKAGES = [
@@ -62317,6 +62386,66 @@ const isPlainObject = (value) => {
62317
62386
  const prototype = Object.getPrototypeOf(value);
62318
62387
  return prototype === null || prototype === Object.prototype;
62319
62388
  };
62389
+ const LINGUIST_ATTRIBUTES = ["linguist-generated", "linguist-vendored"];
62390
+ const DISABLED_ATTRIBUTE_VALUES = new Set([
62391
+ "unspecified",
62392
+ "unset",
62393
+ "false",
62394
+ "0",
62395
+ "off",
62396
+ "no"
62397
+ ]);
62398
+ const buildInput = (relativePaths) => `${relativePaths.join("\0")}\0`;
62399
+ const parseIgnoredPaths = (output) => {
62400
+ const ignoredPaths = /* @__PURE__ */ new Set();
62401
+ const fields = output.split("\0");
62402
+ for (let fieldIndex = 0; fieldIndex + 2 < fields.length; fieldIndex += 3) {
62403
+ const filePath = fields[fieldIndex];
62404
+ const value = fields[fieldIndex + 2]?.toLowerCase();
62405
+ if (filePath && value && !DISABLED_ATTRIBUTE_VALUES.has(value)) ignoredPaths.add(filePath);
62406
+ }
62407
+ return ignoredPaths;
62408
+ };
62409
+ const collectGitLinguistIgnoredPaths = (rootDirectory, relativePaths) => {
62410
+ if (relativePaths.length === 0) return /* @__PURE__ */ new Set();
62411
+ const result = spawnSync("git", [
62412
+ "check-attr",
62413
+ "-z",
62414
+ "--stdin",
62415
+ ...LINGUIST_ATTRIBUTES
62416
+ ], {
62417
+ cwd: rootDirectory,
62418
+ encoding: "utf-8",
62419
+ input: buildInput(relativePaths),
62420
+ maxBuffer: GIT_CHECK_ATTR_MAX_BUFFER_BYTES
62421
+ });
62422
+ if (result.error || result.status !== 0) return /* @__PURE__ */ new Set();
62423
+ return parseIgnoredPaths(result.stdout);
62424
+ };
62425
+ const collectGitLinguistIgnoredPathsCooperative = (rootDirectory, relativePaths, signal) => {
62426
+ if (relativePaths.length === 0) return Promise.resolve(/* @__PURE__ */ new Set());
62427
+ return new Promise((resolve, reject) => {
62428
+ signal?.throwIfAborted();
62429
+ execFile("git", [
62430
+ "check-attr",
62431
+ "-z",
62432
+ "--stdin",
62433
+ ...LINGUIST_ATTRIBUTES
62434
+ ], {
62435
+ cwd: rootDirectory,
62436
+ encoding: "utf-8",
62437
+ killSignal: "SIGKILL",
62438
+ maxBuffer: GIT_CHECK_ATTR_MAX_BUFFER_BYTES,
62439
+ signal
62440
+ }, (error, stdout) => {
62441
+ if (signal?.aborted) {
62442
+ reject(signal.reason);
62443
+ return;
62444
+ }
62445
+ resolve(error ? /* @__PURE__ */ new Set() : parseIgnoredPaths(stdout));
62446
+ }).stdin?.end(buildInput(relativePaths));
62447
+ });
62448
+ };
62320
62449
  const lastSourceMappingUrl = (fileContent) => {
62321
62450
  const lastReference = [...fileContent.matchAll(/\/\/# sourceMappingURL=(\S+)/g)].at(-1);
62322
62451
  return lastReference ? lastReference[1] : null;
@@ -62421,6 +62550,9 @@ function* walkSourceTreeFiles(rootDirectory) {
62421
62550
  }
62422
62551
  }
62423
62552
  }
62553
+ const yieldToEventLoop = () => new Promise((resolve) => {
62554
+ setImmediate(resolve);
62555
+ });
62424
62556
  const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
62425
62557
  const entries = [];
62426
62558
  for (const relativePath of relativePaths) {
@@ -62434,23 +62566,55 @@ const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
62434
62566
  }
62435
62567
  return entries;
62436
62568
  };
62437
- const gitLsFiles = (rootDirectory, flags) => {
62569
+ const collectSizedSourceFilesCooperative = async (rootDirectory, relativePaths, signal) => {
62570
+ const entries = [];
62571
+ let sliceStartedAt = performance.now();
62572
+ for (const relativePath of relativePaths) {
62573
+ signal?.throwIfAborted();
62574
+ const absolutePath = Path.resolve(rootDirectory, relativePath);
62575
+ const sizeBytes = statSourceFileSize(absolutePath);
62576
+ if (!isLargeMinifiedFile(absolutePath, sizeBytes)) entries.push({
62577
+ path: relativePath,
62578
+ sizeBytes: sizeBytes ?? 0
62579
+ });
62580
+ if (performance.now() - sliceStartedAt >= 12) {
62581
+ await yieldToEventLoop();
62582
+ sliceStartedAt = performance.now();
62583
+ }
62584
+ }
62585
+ return entries;
62586
+ };
62587
+ const parseGitSourceFilePaths = (output) => {
62588
+ const trackedPaths = /* @__PURE__ */ new Set();
62589
+ const untrackedPaths = [];
62590
+ for (const entry of output.split("\0")) {
62591
+ if (entry.length === 0) continue;
62592
+ const trackedPath = /^[0-7]{6} [0-9a-f]+ [0-3]\t([\s\S]+)$/.exec(entry)?.[1];
62593
+ if (trackedPath === void 0) untrackedPaths.push(entry);
62594
+ else trackedPaths.add(trackedPath);
62595
+ }
62596
+ return {
62597
+ trackedPaths: [...trackedPaths],
62598
+ untrackedPaths
62599
+ };
62600
+ };
62601
+ const listGitSourceFilePaths = (rootDirectory) => {
62438
62602
  const result = spawnSync("git", [
62439
62603
  "ls-files",
62440
62604
  "-z",
62441
- ...flags
62605
+ "--stage",
62606
+ "--others",
62607
+ "--exclude-standard"
62442
62608
  ], {
62443
62609
  cwd: rootDirectory,
62444
62610
  encoding: "utf-8",
62445
62611
  maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
62446
62612
  });
62447
62613
  if (result.error || result.status !== 0) return null;
62448
- return result.stdout.split("\0").filter((filePath) => filePath.length > 0);
62614
+ return parseGitSourceFilePaths(result.stdout);
62449
62615
  };
62450
- const listSourceFilesViaGit = (rootDirectory) => {
62451
- const trackedPaths = gitLsFiles(rootDirectory, ["--cached"]);
62452
- const untrackedPaths = gitLsFiles(rootDirectory, ["--others", "--exclude-standard"]);
62453
- if (trackedPaths === null || untrackedPaths === null) return null;
62616
+ const collectGitCandidateSourceFilePaths = (rootDirectory, paths) => {
62617
+ const { trackedPaths, untrackedPaths } = paths;
62454
62618
  const emitDuplicateJsPaths = collectTypeScriptEmitDuplicateJsPaths({
62455
62619
  trackedPaths: new Set(trackedPaths),
62456
62620
  untrackedPaths,
@@ -62458,12 +62622,65 @@ const listSourceFilesViaGit = (rootDirectory) => {
62458
62622
  });
62459
62623
  return [...trackedPaths, ...untrackedPaths].filter((filePath) => isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath) && !emitDuplicateJsPaths.has(filePath));
62460
62624
  };
62625
+ const filterGitSourceFilePaths = (rootDirectory, paths) => {
62626
+ const candidatePaths = collectGitCandidateSourceFilePaths(rootDirectory, paths);
62627
+ const linguistIgnoredPaths = collectGitLinguistIgnoredPaths(rootDirectory, candidatePaths);
62628
+ return candidatePaths.filter((filePath) => !linguistIgnoredPaths.has(filePath));
62629
+ };
62630
+ const listSourceFilesViaGit = (rootDirectory) => {
62631
+ const paths = listGitSourceFilePaths(rootDirectory);
62632
+ return paths === null ? null : filterGitSourceFilePaths(rootDirectory, paths);
62633
+ };
62634
+ const listSourceFilesViaGitCooperative = async (rootDirectory, signal) => {
62635
+ const paths = await new Promise((resolve, reject) => {
62636
+ signal?.throwIfAborted();
62637
+ execFile("git", [
62638
+ "ls-files",
62639
+ "-z",
62640
+ "--stage",
62641
+ "--others",
62642
+ "--exclude-standard"
62643
+ ], {
62644
+ cwd: rootDirectory,
62645
+ encoding: "utf-8",
62646
+ killSignal: "SIGKILL",
62647
+ maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES,
62648
+ signal
62649
+ }, (error, stdout) => {
62650
+ if (signal?.aborted) {
62651
+ reject(signal.reason);
62652
+ return;
62653
+ }
62654
+ resolve(error ? null : parseGitSourceFilePaths(stdout));
62655
+ });
62656
+ });
62657
+ if (paths === null) return null;
62658
+ const candidatePaths = collectGitCandidateSourceFilePaths(rootDirectory, paths);
62659
+ const linguistIgnoredPaths = await collectGitLinguistIgnoredPathsCooperative(rootDirectory, candidatePaths, signal);
62660
+ return candidatePaths.filter((filePath) => !linguistIgnoredPaths.has(filePath));
62661
+ };
62461
62662
  const listSourceFilesViaFilesystem = (rootDirectory) => {
62462
62663
  const filePaths = [];
62463
62664
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) if (isLintableSourceFile(name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
62464
62665
  return filePaths;
62465
62666
  };
62466
- const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
62667
+ const listSourceFilesViaFilesystemCooperative = async (rootDirectory, signal) => {
62668
+ const filePaths = [];
62669
+ let sliceStartedAt = performance.now();
62670
+ for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
62671
+ signal?.throwIfAborted();
62672
+ if (isLintableSourceFile(name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
62673
+ if (performance.now() - sliceStartedAt >= 12) {
62674
+ await yieldToEventLoop();
62675
+ sliceStartedAt = performance.now();
62676
+ }
62677
+ }
62678
+ return filePaths;
62679
+ };
62680
+ const listSourceFilePaths = (rootDirectory) => (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort();
62681
+ const listSourceFilePathsCooperative = async (rootDirectory, signal) => (await listSourceFilesViaGitCooperative(rootDirectory, signal) ?? await listSourceFilesViaFilesystemCooperative(rootDirectory, signal)).sort();
62682
+ const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, listSourceFilePaths(rootDirectory));
62683
+ const listSourceFilesCooperative = async (rootDirectory, signal) => (await collectSizedSourceFilesCooperative(rootDirectory, await listSourceFilePathsCooperative(rootDirectory, signal), signal)).map((entry) => entry.path);
62467
62684
  const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
62468
62685
  const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
62469
62686
  const UTF8_BOM_CHARACTER = "";
@@ -64792,7 +65009,16 @@ const getBrowserslistProtectionRequirement = (directory) => {
64792
65009
  if (!queries) return void 0;
64793
65010
  let requirement;
64794
65011
  let hasUnsupportedTarget = false;
64795
- for (const browserTarget of (0, import_browserslist.default)(queries)) {
65012
+ const previousIgnoreOldData = process.env.BROWSERSLIST_IGNORE_OLD_DATA;
65013
+ let browserTargets = [];
65014
+ try {
65015
+ process.env.BROWSERSLIST_IGNORE_OLD_DATA = "true";
65016
+ browserTargets = (0, import_browserslist.default)(queries);
65017
+ } finally {
65018
+ if (previousIgnoreOldData === void 0) delete process.env.BROWSERSLIST_IGNORE_OLD_DATA;
65019
+ else process.env.BROWSERSLIST_IGNORE_OLD_DATA = previousIgnoreOldData;
65020
+ }
65021
+ for (const browserTarget of browserTargets) {
64796
65022
  const browserRequirement = getBrowserProtectionRequirement(browserTarget);
64797
65023
  if (browserRequirement === "unsupported") hasUnsupportedTarget = true;
64798
65024
  if (browserRequirement === "noreferrer") requirement = "noreferrer";
@@ -64902,8 +65128,8 @@ const clearProjectCache = () => {
64902
65128
  * framework-agnostic rules. Throws only when the directory has nothing
64903
65129
  * to scan (no enclosing project and no source files of its own).
64904
65130
  */
64905
- const discoverProjectWithoutPackageJson = (directory) => {
64906
- const sourceFileCount = countSourceFiles(directory);
65131
+ const discoverProjectWithoutPackageJson = (directory, options) => {
65132
+ const sourceFileCount = options.sourceFileCount ?? countSourceFiles(directory);
64907
65133
  const hasOwnTsConfig = NFS.existsSync(Path.join(directory, "tsconfig.json"));
64908
65134
  const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
64909
65135
  const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
@@ -64970,12 +65196,15 @@ const discoverProjectWithoutPackageJson = (directory) => {
64970
65196
  sourceFileCount
64971
65197
  };
64972
65198
  };
64973
- const discoverProject = (directory) => {
65199
+ const discoverProject = (directory, options = {}) => {
64974
65200
  const cached = cachedProjectInfos.get(directory);
64975
- if (cached !== void 0) return cached;
65201
+ if (cached !== void 0) return options.sourceFileCount === void 0 ? cached : {
65202
+ ...cached,
65203
+ sourceFileCount: options.sourceFileCount
65204
+ };
64976
65205
  const packageJsonPath = Path.join(directory, "package.json");
64977
65206
  if (!isFile(packageJsonPath)) {
64978
- const synthesized = discoverProjectWithoutPackageJson(directory);
65207
+ const synthesized = discoverProjectWithoutPackageJson(directory, options);
64979
65208
  cachedProjectInfos.set(directory, synthesized);
64980
65209
  return synthesized;
64981
65210
  }
@@ -65038,7 +65267,7 @@ const discoverProject = (directory) => {
65038
65267
  const zodVersion = zod.version;
65039
65268
  const projectName = packageJson.name ?? Path.basename(directory);
65040
65269
  const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
65041
- const sourceFileCount = countSourceFiles(directory);
65270
+ const sourceFileCount = options.sourceFileCount ?? countSourceFiles(directory);
65042
65271
  const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
65043
65272
  const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
65044
65273
  rootDirectory: directory,
@@ -65845,6 +66074,10 @@ const classifyByDirectoryCohort = (packageDirectory) => {
65845
66074
  current = parent;
65846
66075
  }
65847
66076
  };
66077
+ const clearPackageRoleCache = () => {
66078
+ cachedRoleByPackageDirectory.clear();
66079
+ cachedPackageDirectoryByFilename.clear();
66080
+ };
65848
66081
  const classifyPackageRole = (filename) => {
65849
66082
  if (!filename) return "unknown";
65850
66083
  const packageDirectory = findNearestPackageDirectory(filename);
@@ -66394,7 +66627,8 @@ const readSystemFacts$1 = () => ({
66394
66627
  /**
66395
66628
  * Auto lint-worker count: the smaller of the (cgroup-CPU-aware) core count and
66396
66629
  * the number of `PER_WORKER_MEM_BUDGET_BYTES` workers that fit in available
66397
- * memory, then clamped to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
66630
+ * memory, and the measured automatic parallel-efficiency ceiling, then clamped
66631
+ * to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
66398
66632
  *
66399
66633
  * `os.availableParallelism()` already respects cgroup CPU quotas, so the core
66400
66634
  * term needs no help. Available memory is `os.totalmem()` floored by the cgroup
@@ -66410,7 +66644,7 @@ const readSystemFacts$1 = () => ({
66410
66644
  const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
66411
66645
  const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
66412
66646
  const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
66413
- return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
66647
+ return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers, 10));
66414
66648
  };
66415
66649
  const resolveLintBatchOrdering = () => {
66416
66650
  return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
@@ -66423,24 +66657,22 @@ const resolveLintBatchOrdering = () => {
66423
66657
  */
66424
66658
  var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS", OXLINT_SPAWN_TIMEOUT_MS) }) {};
66425
66659
  /**
66426
- * Effect-side cap on the lint phase. The env var lets CI / eval runners
66427
- * raise the phase budget for slow large repos without recompiling.
66660
+ * Optional Effect-side cap on the lint phase. Unbounded unless explicitly
66661
+ * configured through the env var or a test layer.
66428
66662
  * Tests override via `Layer.succeed(LintPhaseTimeoutMs, ...)`.
66429
66663
  */
66430
- var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", LINT_PHASE_TIMEOUT_MS) }) {};
66664
+ var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", null) }) {};
66431
66665
  /**
66432
- * Effect-side cap on the dead-code phase, sitting above the in-worker
66433
- * timeout as a runtime-independent backstop. The env var raises it for
66434
- * type-heavy projects; tests override via
66666
+ * Optional Effect-side cap on the dead-code phase. Unbounded unless explicitly
66667
+ * configured through the env var or a test layer. Tests override via
66435
66668
  * `Layer.succeed(DeadCodePhaseTimeoutMs, ...)`.
66436
66669
  */
66437
- var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", DEAD_CODE_PHASE_TIMEOUT_MS) }) {};
66670
+ var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", null) }) {};
66438
66671
  /**
66439
- * Overall scan deadline backstop, bounding everything the per-phase
66440
- * timeouts don't (wedged git / IO). The env var raises it for very
66441
- * large repos; tests override via `Layer.succeed(ScanDeadlineMs, ...)`.
66672
+ * Optional overall scan deadline. Unbounded unless explicitly configured
66673
+ * through the env var or a test layer.
66442
66674
  */
66443
- var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", SCAN_TOTAL_DEADLINE_MS) }) {};
66675
+ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", null) }) {};
66444
66676
  /**
66445
66677
  * Wall-clock budget for the supply-chain check when it runs on a background
66446
66678
  * fiber overlapping the lint pass. Reads from the env var on startup so the
@@ -66513,14 +66745,14 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
66513
66745
  /**
66514
66746
  * How the full-scan lint pass plans its file batches. `"cost"` (the default)
66515
66747
  * builds size-balanced LPT batches (`planLintBatches`): the same mandatory
66516
- * batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
66517
- * an even share of files AND bytes, so no 100-file chunk is a straggler while
66748
+ * batch count as greedy chunking, but every batch gets an even share of files
66749
+ * AND bytes, so no full chunk is a straggler while
66518
66750
  * the remainder-batch worker idles — and the heavy files are SPREAD across
66519
66751
  * batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
66520
66752
  * lacked (it packed the heaviest files into one wave-1 straggler batch,
66521
66753
  * measurably regressing size-skewed repos, which is why it never earned the
66522
66754
  * default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
66523
- * rollback hatch to plain greedy 100-file chunking in discovery order. Tests
66755
+ * rollback hatch to plain greedy fixed-size chunking in discovery order. Tests
66524
66756
  * override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
66525
66757
  * never reach this — they pass user-scoped `includePaths` that skip discovery
66526
66758
  * and stay in arrival order; only the full-scan branch reads it.
@@ -67010,6 +67242,10 @@ const dedupeRelatedDiagnostics = (diagnostics) => {
67010
67242
  }
67011
67243
  return uniqueDiagnostics;
67012
67244
  };
67245
+ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
67246
+ const relativePath = Path.relative(parentAbsolutePath, childAbsolutePath);
67247
+ return Boolean(relativePath) && !relativePath.startsWith("..") && !Path.isAbsolute(relativePath);
67248
+ };
67013
67249
  const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
67014
67250
  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));
67015
67251
  const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
@@ -68586,7 +68822,7 @@ const readScannedFile = (candidate) => {
68586
68822
  return null;
68587
68823
  }
68588
68824
  };
68589
- function* collectSecurityScanFiles(rootDirectory) {
68825
+ function* collectSecurityScanFiles(rootDirectory, excludedDirectories = /* @__PURE__ */ new Set()) {
68590
68826
  const priorityCandidates = [];
68591
68827
  const artifactCandidates = [];
68592
68828
  const otherCandidates = [];
@@ -68601,7 +68837,7 @@ function* collectSecurityScanFiles(rootDirectory) {
68601
68837
  for (const entry of readDirectoryEntries(current.absolutePath)) {
68602
68838
  const absolutePath = Path.join(current.absolutePath, entry.name);
68603
68839
  if (entry.isDirectory()) {
68604
- if (!SKIPPED_DIRECTORY_NAMES.has(entry.name)) stack.push({
68840
+ if (!SKIPPED_DIRECTORY_NAMES.has(entry.name) && !excludedDirectories.has(Path.resolve(absolutePath))) stack.push({
68605
68841
  absolutePath,
68606
68842
  depth: current.depth + 1
68607
68843
  });
@@ -68782,10 +69018,8 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhe
68782
69018
  }
68783
69019
  return true;
68784
69020
  };
69021
+ const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
68785
69022
  const shouldEnableRuleByDefaultStatus = (options) => options.defaultEnabled !== false || options.hasExplicitOverride === true || options.includeTagDefaults && options.hasIncludedTags;
68786
- const yieldToEventLoop = () => new Promise((resolve) => {
68787
- setImmediate(resolve);
68788
- });
68789
69023
  const createSecurityScanSession = (rootDirectory, options) => {
68790
69024
  const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
68791
69025
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
@@ -68838,19 +69072,35 @@ const createSecurityScanSession = (rootDirectory, options) => {
68838
69072
  };
68839
69073
  };
68840
69074
  const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
69075
+ const throwIfScanAborted = () => {
69076
+ options.signal?.throwIfAborted();
69077
+ };
69078
+ const didReachDeadline = () => {
69079
+ const hasDeadlineElapsed = options.deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(options.deadlineEpochMs) === 0;
69080
+ if (hasDeadlineElapsed) options.onDeadlineExceeded?.();
69081
+ return hasDeadlineElapsed;
69082
+ };
69083
+ throwIfScanAborted();
68841
69084
  const session = createSecurityScanSession(rootDirectory, options);
68842
69085
  if (session === null) return [];
69086
+ if (didReachDeadline()) return [];
68843
69087
  let sliceStartedAt = performance.now();
68844
- for (const file of collectSecurityScanFiles(rootDirectory)) {
69088
+ for (const file of collectSecurityScanFiles(rootDirectory, options.excludedDirectories)) {
69089
+ throwIfScanAborted();
69090
+ if (didReachDeadline()) return session.diagnostics;
68845
69091
  if (file === null) {
68846
69092
  if (performance.now() - sliceStartedAt >= 12) {
68847
69093
  await yieldToEventLoop();
69094
+ throwIfScanAborted();
69095
+ if (didReachDeadline()) return session.diagnostics;
68848
69096
  sliceStartedAt = performance.now();
68849
69097
  }
68850
69098
  continue;
68851
69099
  }
68852
69100
  for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
68853
69101
  await yieldToEventLoop();
69102
+ throwIfScanAborted();
69103
+ if (didReachDeadline()) return session.diagnostics;
68854
69104
  sliceStartedAt = performance.now();
68855
69105
  }
68856
69106
  }
@@ -69764,7 +70014,6 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
69764
70014
  return true;
69765
70015
  };
69766
70016
  const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
69767
- const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
69768
70017
  /**
69769
70018
  * Budget for the dead-code phase, scaled to the work. deslop's graph build is
69770
70019
  * CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
@@ -69772,25 +70021,28 @@ const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs
69772
70021
  * tipped over by any concurrent load — silently dropping every dead-code
69773
70022
  * finding. Scaling the budget with file count (and inversely with the core
69774
70023
  * share when overlapped) lets the pass complete, while the ceiling still
69775
- * reclaims a genuinely wedged worker. Returns the in-worker SIGKILL deadline
69776
- * and the Effect-side phase backstop that sits a margin above it.
70024
+ * reclaims a genuinely wedged worker.
69777
70025
  */
69778
70026
  const resolveDeadCodeTimeout = (input) => {
69779
70027
  const coreShareFactor = Math.max(1, input.fullConcurrency / Math.max(1, input.deadCodeConcurrency));
69780
- const workerTimeoutMs = Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor)));
69781
- return {
69782
- workerTimeoutMs,
69783
- phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
69784
- };
70028
+ return { workerTimeoutMs: Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor))) };
69785
70029
  };
69786
- const resolveLintIncludePaths = (rootDirectory, userConfig) => {
70030
+ const resolveLintIncludePaths = (rootDirectory, userConfig, sourceFiles) => {
69787
70031
  if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
69788
70032
  const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
69789
- return listSourceFiles(rootDirectory).filter((filePath) => {
70033
+ return (sourceFiles ?? listSourceFiles(rootDirectory)).filter((filePath) => {
69790
70034
  if (!isLintableSourceFile(filePath)) return false;
69791
70035
  return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
69792
70036
  });
69793
70037
  };
70038
+ const filterPathsOutsideDirectories = (input) => {
70039
+ const excludedPrefixes = input.excludedDirectories.map((excludedDirectory) => Path.resolve(excludedDirectory)).filter((excludedDirectory) => isPathInsideDirectory(excludedDirectory, input.rootDirectory)).map((excludedDirectory) => Path.relative(input.rootDirectory, excludedDirectory).replaceAll("\\", "/")).sort((left, right) => left.length - right.length).filter((candidatePrefix, index, prefixes) => !prefixes.slice(0, index).some((prefix) => candidatePrefix.startsWith(`${prefix}/`)));
70040
+ if (excludedPrefixes.length === 0) return [...input.relativePaths];
70041
+ return input.relativePaths.filter((relativePath) => {
70042
+ const normalizedRelativePath = relativePath.replaceAll("\\", "/").replace(/^\.\//, "");
70043
+ return !excludedPrefixes.some((excludedPrefix) => normalizedRelativePath === excludedPrefix || normalizedRelativePath.startsWith(`${excludedPrefix}/`));
70044
+ });
70045
+ };
69794
70046
  var Config = class Config extends Service()("react-doctor/Config") {
69795
70047
  static layerNode = effect(Config, gen(function* () {
69796
70048
  const cache = yield* make$3({
@@ -69852,6 +70104,7 @@ var Files = class Files extends Service()("react-doctor/Files") {
69852
70104
  static layerNode = succeed$3(Files, Files.of({
69853
70105
  readLines: (input) => sync(() => createNodeReadFileLinesSync(input.rootDirectory)(input.filePath)),
69854
70106
  listSourceFiles: (rootDirectory) => sync(() => listSourceFiles(rootDirectory)),
70107
+ listSourceFilesCooperative: (input) => promise(() => listSourceFilesCooperative(input.rootDirectory, input.signal)),
69855
70108
  isFile: (filePath) => sync(() => isFile(filePath)),
69856
70109
  isDirectory: (filePath) => sync(() => isDirectory(filePath))
69857
70110
  }));
@@ -69878,6 +70131,16 @@ var Files = class Files extends Service()("react-doctor/Files") {
69878
70131
  }
69879
70132
  return files;
69880
70133
  }),
70134
+ listSourceFilesCooperative: (input) => sync(() => {
70135
+ const rootDirectory = input.rootDirectory;
70136
+ const prefix = rootDirectory.endsWith("/") ? rootDirectory : `${rootDirectory}/`;
70137
+ const files = [];
70138
+ for (const absolute of tree.keys()) {
70139
+ if (!absolute.startsWith(prefix)) continue;
70140
+ files.push(absolute.slice(prefix.length));
70141
+ }
70142
+ return files;
70143
+ }),
69881
70144
  isFile: (filePath) => sync(() => tree.has(filePath)),
69882
70145
  isDirectory: (filePath) => sync(() => {
69883
70146
  const prefix = filePath.endsWith("/") ? filePath : `${filePath}/`;
@@ -70453,7 +70716,7 @@ const batchIncludePaths = (baseArgs, includePaths) => {
70453
70716
  for (const filePath of includePaths) {
70454
70717
  const entryLength = filePath.length + 1;
70455
70718
  const exceedsArgLength = currentBatch.length > 0 && currentBatchLength + entryLength > 24e3;
70456
- const exceedsFileCount = currentBatch.length >= 100;
70719
+ const exceedsFileCount = currentBatch.length >= 200;
70457
70720
  if (exceedsArgLength || exceedsFileCount) {
70458
70721
  batches.push(currentBatch);
70459
70722
  currentBatch = [];
@@ -70800,7 +71063,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
70800
71063
  }
70801
71064
  return enabled;
70802
71065
  };
70803
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, runtimeGlobals, unpluginAutoImportGlobalScopes, serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
71066
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, runtimeGlobals, unpluginAutoImportGlobalScopes, serverAuthFunctionNames, projectIndexModuleSources, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
70804
71067
  const hasIncludedTags = includedTags.size > 0;
70805
71068
  const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" || hasIncludedTags ? null : resolveReactHooksJsPlugin(project.hasReactCompiler || project.hasReactCompilerLintPlugin === true, customRulesOnly);
70806
71069
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
@@ -70864,7 +71127,8 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
70864
71127
  }))
70865
71128
  } : {},
70866
71129
  ...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
70867
- ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
71130
+ ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {},
71131
+ ...projectIndexModuleSources !== void 0 ? { projectIndexModuleSources: [...projectIndexModuleSources] } : {}
70868
71132
  } },
70869
71133
  rules: {
70870
71134
  ...reactCompilerRules,
@@ -72349,6 +72613,12 @@ const buildProfiledNodeArguments = (input) => [
72349
72613
  ...input.heapProfileDirectory ? ["--heap-prof", `--heap-prof-dir=${input.heapProfileDirectory}`] : [],
72350
72614
  ...input.argumentsList
72351
72615
  ];
72616
+ const lowerChildProcessPriority = (processId) => {
72617
+ if (processId === void 0) return;
72618
+ try {
72619
+ OS.setPriority(processId, OS.constants.priority.PRIORITY_BELOW_NORMAL);
72620
+ } catch {}
72621
+ };
72352
72622
  const SANITIZED_ENV = buildOxlintChildEnv(process.env);
72353
72623
  /**
72354
72624
  * Spawn one oxlint subprocess with hard ceilings on wall time and
@@ -72387,6 +72657,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
72387
72657
  "pipe"
72388
72658
  ]
72389
72659
  });
72660
+ lowerChildProcessPriority(child.pid);
72390
72661
  const onAbort = () => {
72391
72662
  child.kill("SIGKILL");
72392
72663
  reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
@@ -72509,7 +72780,7 @@ const isParallelismRelatedSpawnError = (error) => {
72509
72780
  * loop with a slimmer config in that case.
72510
72781
  */
72511
72782
  const spawnLintBatches = async (input) => {
72512
- const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, sourceMapByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
72783
+ const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, sourceMapByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 9, signal, deadlineEpochMs } = input;
72513
72784
  const resolveSourcePath = (filePath) => {
72514
72785
  const absoluteFilePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory, filePath);
72515
72786
  return sourcePathByLintPath?.get(Path.normalize(absoluteFilePath)) ?? filePath;
@@ -72517,6 +72788,12 @@ const spawnLintBatches = async (input) => {
72517
72788
  const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
72518
72789
  let rescueDeadlineEpochMs;
72519
72790
  const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0 || rescueDeadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(rescueDeadlineEpochMs) === 0;
72791
+ const resolveSpawnTimeoutMs = () => {
72792
+ const deadlineBudgets = [deadlineEpochMs, rescueDeadlineEpochMs].filter((deadline) => deadline !== void 0).map(remainingDeadlineBudgetMs);
72793
+ if (deadlineBudgets.length === 0) return spawnTimeoutMs;
72794
+ const remainingDeadlineMs = Math.min(...deadlineBudgets);
72795
+ return spawnTimeoutMs === void 0 ? remainingDeadlineMs : Math.min(spawnTimeoutMs, remainingDeadlineMs);
72796
+ };
72520
72797
  const runBatchPass = async (concurrency, passBatches, passOnFileProgress) => {
72521
72798
  const totalFileCount = passBatches.reduce((sum, batch) => sum + batch.length, 0);
72522
72799
  const allDiagnostics = [];
@@ -72534,12 +72811,13 @@ const spawnLintBatches = async (input) => {
72534
72811
  const batchArgs = [...baseArgs, ...batch];
72535
72812
  try {
72536
72813
  const spawnBatch = () => {
72537
- if (isPastDeadline()) {
72814
+ const effectiveSpawnTimeoutMs = resolveSpawnTimeoutMs();
72815
+ if (effectiveSpawnTimeoutMs === 0) {
72538
72816
  deadlineSkippedFiles.push(...batch);
72539
72817
  batchState.deadlineSkippedFileCount += batch.length;
72540
72818
  return Promise.resolve(null);
72541
72819
  }
72542
- return spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal, () => {
72820
+ return spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, effectiveSpawnTimeoutMs, outputMaxBytes, signal, () => {
72543
72821
  if (batchState.didStart) return;
72544
72822
  batchState.didStart = true;
72545
72823
  startedFileCount += batchState.initialFileCount;
@@ -72694,11 +72972,36 @@ const validateRuleRegistration = () => {
72694
72972
  ].filter((entry) => entry !== null).join("; ");
72695
72973
  console.warn(`[react-doctor] rule-registration drift: ${detail}`);
72696
72974
  };
72975
+ const PROJECT_INDEX_MODULE_SOURCES = [
72976
+ "next/og",
72977
+ "@vercel/og",
72978
+ "satori",
72979
+ "remotion"
72980
+ ];
72981
+ const collectProjectIndexModuleSources = async (rootDirectory, candidateFiles) => {
72982
+ const foundModuleSources = /* @__PURE__ */ new Set();
72983
+ let sliceStartedAt = performance.now();
72984
+ for (const candidateFile of candidateFiles) {
72985
+ let sourceBuffer;
72986
+ try {
72987
+ sourceBuffer = NFS.readFileSync(Path.isAbsolute(candidateFile) ? candidateFile : Path.resolve(rootDirectory, candidateFile));
72988
+ } catch {
72989
+ continue;
72990
+ }
72991
+ for (const moduleSource of PROJECT_INDEX_MODULE_SOURCES) if (sourceBuffer.includes(`"${moduleSource}"`) || sourceBuffer.includes(`'${moduleSource}'`)) foundModuleSources.add(moduleSource);
72992
+ if (foundModuleSources.size === PROJECT_INDEX_MODULE_SOURCES.length) break;
72993
+ if (performance.now() - sliceStartedAt >= 12) {
72994
+ await yieldToEventLoop();
72995
+ sliceStartedAt = performance.now();
72996
+ }
72997
+ }
72998
+ return [...foundModuleSources].sort();
72999
+ };
72697
73000
  /**
72698
73001
  * Balanced LPT (longest-processing-time-first) lint batch planner. Where
72699
- * `batchIncludePaths` greedily fills 100-file batches and leaves a small
72700
- * remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
72701
- * is set by whichever 100-file chunk happened to collect the most work while
73002
+ * `batchIncludePaths` greedily fills fixed-size batches and leaves a small
73003
+ * remainder batch, so the parallel pool's wall clock is set by whichever
73004
+ * full chunk happened to collect the most work while
72702
73005
  * the remainder's worker idles), this planner keeps the SAME mandatory batch
72703
73006
  * count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
72704
73007
  * perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
@@ -72727,7 +73030,7 @@ const validateRuleRegistration = () => {
72727
73030
  */
72728
73031
  const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
72729
73032
  if (files.length === 0) return [];
72730
- const batchCount = Math.ceil(files.length / 100);
73033
+ const batchCount = Math.ceil(files.length / 200);
72731
73034
  const maxFilesPerBatch = Math.ceil(files.length / batchCount);
72732
73035
  const baseArgsLengthChars = estimateArgsLength(baseArgs);
72733
73036
  const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
@@ -73256,6 +73559,7 @@ const runOxlint = async (options) => {
73256
73559
  await yieldToEventLoop();
73257
73560
  const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
73258
73561
  const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
73562
+ const projectIndexModuleSources = includePaths === void 0 && options.deadlineEpochMs === void 0 ? await collectProjectIndexModuleSources(rootDirectory, candidateFiles) : void 0;
73259
73563
  unpluginAutoImportGlobalScopes = collectUnpluginAutoImportGlobalScopes({
73260
73564
  rootDirectory,
73261
73565
  candidateFiles
@@ -73278,6 +73582,7 @@ const runOxlint = async (options) => {
73278
73582
  runtimeGlobals,
73279
73583
  unpluginAutoImportGlobalScopes,
73280
73584
  serverAuthFunctionNames,
73585
+ projectIndexModuleSources,
73281
73586
  severityControls,
73282
73587
  userPlugins,
73283
73588
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
@@ -73745,10 +74050,10 @@ const translateProjectInfoError = (cause, directory) => {
73745
74050
  }) });
73746
74051
  };
73747
74052
  var Project = class Project extends Service()("react-doctor/Project") {
73748
- static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (directory) {
74053
+ static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (input) {
73749
74054
  return yield* try_({
73750
- try: () => discoverProject(directory),
73751
- catch: (cause) => translateProjectInfoError(cause, directory)
74055
+ try: () => discoverProject(input.directory, { sourceFileCount: input.sourceFileCount }),
74056
+ catch: (cause) => translateProjectInfoError(cause, input.directory)
73752
74057
  });
73753
74058
  }) }));
73754
74059
  static layerOf = (projectInfo) => succeed$3(Project, Project.of({ discover: () => succeed$2(projectInfo) }));
@@ -74333,9 +74638,10 @@ const checkSupplyChain = (input) => gen(function* () {
74333
74638
  * outage.
74334
74639
  */
74335
74640
  var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
74336
- static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
74337
- ...input,
74338
- totalTimeoutMs
74641
+ static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (configuredTimeoutMs) => checkSupplyChain({
74642
+ rootDirectory: input.rootDirectory,
74643
+ userConfig: input.userConfig,
74644
+ totalTimeoutMs: input.timeoutMs ?? configuredTimeoutMs
74339
74645
  })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
74340
74646
  static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
74341
74647
  };
@@ -74370,10 +74676,6 @@ const NO_HOOKS = {
74370
74676
  beforeLint: () => void_,
74371
74677
  afterLint: () => void_
74372
74678
  };
74373
- const filterMapNullable = (transform) => fromPredicateOption((value) => {
74374
- const result = transform(value);
74375
- return result === null ? none() : some(result);
74376
- });
74377
74679
  const fileReader = (filesService, rootDirectory) => (filePath) => {
74378
74680
  const lines = runSync(filesService.readLines({
74379
74681
  filePath,
@@ -74397,7 +74699,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
74397
74699
  * The GitHub viewer-permission lookup is forked onto a background
74398
74700
  * fiber here and joined late (it feeds score metadata, not
74399
74701
  * diagnostics).
74400
- * 2. beforeLint hook (e.g. CLI renders the project-detection block)
74702
+ * 2. beforeLint hook (e.g. CLI records project telemetry)
74401
74703
  * 3. environment checks (reduced-motion + pnpm hardening +
74402
74704
  * expo/react-native), collected synchronously. The heavier
74403
74705
  * content-regex security scan is forked instead (like supply-chain
@@ -74410,13 +74712,11 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
74410
74712
  * `SupplyChainOverlapTimeoutMs` (measured from fork) so a hung
74411
74713
  * socket can't drag out its join; on timeout it fails open to no
74412
74714
  * diagnostics — the same outcome class as a Socket outage.
74413
- * 5. Linter.run runs; DeadCode.run runs concurrently (forked child
74414
- * fiber) ONLY when the memory gate has headroom to run the 8 GB
74415
- * dead-code child alongside the oxlint workers — or when overlap is
74416
- * forced via REACT_DOCTOR_DEAD_CODE_OVERLAP. Otherwise dead-code
74417
- * runs sequentially after lint, exactly as it did pre-overlap. The
74418
- * fiber is joined (or interrupted, SIGKILLing its worker, on lint
74419
- * failure) before diagnostics are concatenated. The afterLint hook
74715
+ * 5. Linter.run runs; DeadCode.run forks only when
74716
+ * REACT_DOCTOR_DEAD_CODE_OVERLAP=on. The default "auto" and explicit
74717
+ * "off" paths stay sequential. The fiber is joined (or interrupted,
74718
+ * SIGKILLing its worker, on lint failure) before diagnostics are
74719
+ * concatenated. The afterLint hook
74420
74720
  * fires between lint and dead-code. Progress spinner labels AND the
74421
74721
  * final diagnostic / score order stay independent of execution
74422
74722
  * order, so terminal output is identical either way; supply-chain
@@ -74451,8 +74751,28 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74451
74751
  const partialFailuresRef = yield* LintPartialFailures;
74452
74752
  const resolvedConfig = yield* configService.resolve(input.directory);
74453
74753
  const scanDirectory = resolvedConfig.resolvedDirectory;
74454
- const project = yield* projectService.discover(scanDirectory);
74754
+ const excludedProjectDirectories = (input.excludedProjectDirectories ?? []).map((excludedDirectory) => Path.resolve(excludedDirectory)).filter((excludedDirectory) => isPathInsideDirectory(excludedDirectory, scanDirectory));
74755
+ const excludedProjectRelativePaths = excludedProjectDirectories.map((excludedDirectory) => Path.relative(scanDirectory, excludedDirectory).replaceAll("\\", "/"));
74756
+ const isExcludedProjectDiagnostic = (diagnostic) => {
74757
+ const relativeFilePath = toNormalizedRelativePath(diagnostic.filePath, scanDirectory);
74758
+ return excludedProjectRelativePaths.some((excludedRelativePath) => relativeFilePath === excludedRelativePath || relativeFilePath.startsWith(`${excludedRelativePath}/`));
74759
+ };
74760
+ const precomputedSourceFiles = input.suppressScanSummary === true || excludedProjectDirectories.length > 0 ? yield* filesService.listSourceFilesCooperative({
74761
+ rootDirectory: scanDirectory,
74762
+ signal: input.signal
74763
+ }) : null;
74764
+ const includedPrecomputedSourceFiles = precomputedSourceFiles !== null && excludedProjectDirectories.length > 0 ? filterPathsOutsideDirectories({
74765
+ rootDirectory: scanDirectory,
74766
+ relativePaths: precomputedSourceFiles,
74767
+ excludedDirectories: excludedProjectDirectories
74768
+ }) : precomputedSourceFiles;
74769
+ const sourceFileCount = excludedProjectDirectories.length > 0 ? includedPrecomputedSourceFiles?.length : input.precomputedSourceFileCount ?? includedPrecomputedSourceFiles?.length;
74770
+ const project = yield* projectService.discover({
74771
+ directory: scanDirectory,
74772
+ sourceFileCount
74773
+ });
74455
74774
  if (!isAnalyzableProject(project)) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
74775
+ const deadCodeSourceFileCount = input.retainExcludedProjectDeadCodeDiagnostics === true && precomputedSourceFiles !== null ? precomputedSourceFiles.length : project.sourceFileCount;
74456
74776
  const [repo, sha, defaultBranch] = yield* all([
74457
74777
  gitService.githubRepo(scanDirectory).pipe(orElseSucceed(() => null)),
74458
74778
  gitService.headSha(scanDirectory).pipe(orElseSucceed(() => null)),
@@ -74463,11 +74783,16 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74463
74783
  directory: scanDirectory,
74464
74784
  repo
74465
74785
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
74466
- const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
74467
- const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
74786
+ let lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, includedPrecomputedSourceFiles ?? void 0);
74787
+ if (excludedProjectDirectories.length > 0) lintIncludePaths = filterPathsOutsideDirectories({
74788
+ rootDirectory: scanDirectory,
74789
+ relativePaths: lintIncludePaths ?? includedPrecomputedSourceFiles ?? (yield* filesService.listSourceFiles(scanDirectory)),
74790
+ excludedDirectories: excludedProjectDirectories
74791
+ });
74792
+ const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? includedPrecomputedSourceFiles ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
74468
74793
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
74469
74794
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
74470
- yield* beforeLint(project, lintIncludePaths ?? void 0);
74795
+ yield* beforeLint(project);
74471
74796
  const isDiffMode = input.includePaths.length > 0;
74472
74797
  const showWarnings = input.warnings ?? resolvedConfig.config?.warnings ?? true;
74473
74798
  const transform = buildDiagnosticPipeline({
@@ -74477,7 +74802,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74477
74802
  respectInlineDisables: input.respectInlineDisables,
74478
74803
  showWarnings
74479
74804
  });
74480
- const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
74805
+ const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(fromPredicateOption((diagnostic) => {
74806
+ const filteredDiagnostic = transform.apply(diagnostic);
74807
+ return filteredDiagnostic === null ? none() : some(filteredDiagnostic);
74808
+ })));
74481
74809
  const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
74482
74810
  const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
74483
74811
  ...checkReducedMotion(scanDirectory),
@@ -74487,17 +74815,31 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74487
74815
  ...checkReactNativeProject(scanDirectory, project)
74488
74816
  ])));
74489
74817
  const securityScanFailedRef = yield* make$13(false);
74490
- const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
74818
+ let didSecurityScanReachDeadline = false;
74819
+ const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise((signal) => checkSecurityScanCooperative(scanDirectory, {
74491
74820
  project,
74492
74821
  ignoredTags: input.ignoredTags,
74493
74822
  includedTags: input.includedTags,
74494
- includeTagDefaults: input.includeTagDefaults
74823
+ includeTagDefaults: input.includeTagDefaults,
74824
+ excludedDirectories: new Set(excludedProjectDirectories),
74825
+ deadlineEpochMs: input.deadlineEpochMs,
74826
+ signal,
74827
+ onDeadlineExceeded: () => {
74828
+ didSecurityScanReachDeadline = true;
74829
+ }
74495
74830
  })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
74831
+ const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
74832
+ const capOptionalToDeadline = (phaseTimeoutMs) => {
74833
+ if (input.deadlineEpochMs === void 0) return phaseTimeoutMs;
74834
+ const remainingBudgetMs = remainingDeadlineBudgetMs(input.deadlineEpochMs);
74835
+ return phaseTimeoutMs === null ? remainingBudgetMs : Math.min(phaseTimeoutMs, remainingBudgetMs);
74836
+ };
74496
74837
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
74497
- const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
74838
+ const supplyChainOverlapTimeout = capToDeadline(yield* SupplyChainOverlapTimeoutMs);
74498
74839
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
74499
74840
  rootDirectory: scanDirectory,
74500
- userConfig: resolvedConfig.config
74841
+ userConfig: resolvedConfig.config,
74842
+ timeoutMs: supplyChainOverlapTimeout
74501
74843
  }))).pipe(map$3((diagnostics) => ({
74502
74844
  diagnostics,
74503
74845
  timedOut: false
@@ -74521,46 +74863,49 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74521
74863
  const scanConcurrency = resolveScanConcurrency(yield* OxlintConcurrency);
74522
74864
  const lintPhaseTimeoutMs = yield* LintPhaseTimeoutMs;
74523
74865
  const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
74524
- const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
74525
74866
  const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
74526
- const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
74527
74867
  const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
74528
74868
  const deadCodeOverlapMode = yield* DeadCodeOverlap;
74529
74869
  const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
74530
74870
  const deadCodeParseConcurrency = shouldOverlapDeadCode ? Math.max(1, Math.floor(scanConcurrency * DEAD_CODE_OVERLAP_PARSE_SHARE)) : void 0;
74531
74871
  const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
74532
- const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
74533
- rootDirectory: scanDirectory,
74534
- parseConcurrency: deadCodeParseConcurrency,
74535
- workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
74536
- onCacheOutcome: (didHitCache) => {
74537
- deadCodeCacheHit = didHitCache;
74538
- },
74539
- onSummaryCacheStats: (stats) => {
74540
- deadCodeSummaryCacheHits = stats.hits;
74541
- deadCodeSummaryCacheMisses = stats.misses;
74542
- }
74543
- }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
74544
- yield* set(deadCodeFailure, {
74545
- didFail: true,
74546
- reason: error.message
74547
- });
74548
- return empty$4;
74549
- })))))).pipe(timeoutOption(deadCodeTimeout.phaseTimeoutMs), flatMap$2(match$3({
74550
- onNone: () => set(deadCodeFailure, {
74551
- didFail: true,
74552
- reason: `Dead-code analysis exceeded ${Math.round(deadCodeTimeout.phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
74553
- }).pipe(as([])),
74554
- onSome: succeed$2
74555
- })));
74872
+ const buildCollectDeadCode = (deadCodeTimeout) => {
74873
+ const collectDeadCode = runCollect(applyPerElementPipeline(deadCodeService.run({
74874
+ rootDirectory: scanDirectory,
74875
+ parseConcurrency: deadCodeParseConcurrency,
74876
+ workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
74877
+ onCacheOutcome: (didHitCache) => {
74878
+ deadCodeCacheHit = didHitCache;
74879
+ },
74880
+ onSummaryCacheStats: (stats) => {
74881
+ deadCodeSummaryCacheHits = stats.hits;
74882
+ deadCodeSummaryCacheMisses = stats.misses;
74883
+ }
74884
+ }).pipe(filter((diagnostic) => input.retainExcludedProjectDeadCodeDiagnostics === true || !isExcludedProjectDiagnostic(diagnostic)), catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
74885
+ yield* set(deadCodeFailure, {
74886
+ didFail: true,
74887
+ reason: error.message
74888
+ });
74889
+ return empty$4;
74890
+ }))))));
74891
+ const phaseTimeoutMs = deadCodeTimeout.phaseTimeoutMs;
74892
+ if (phaseTimeoutMs === null) return collectDeadCode;
74893
+ return collectDeadCode.pipe(timeoutOption(phaseTimeoutMs), flatMap$2(match$3({
74894
+ onNone: () => set(deadCodeFailure, {
74895
+ didFail: true,
74896
+ reason: `Dead-code analysis exceeded ${Math.round(phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
74897
+ }).pipe(as([])),
74898
+ onSome: succeed$2
74899
+ })));
74900
+ };
74556
74901
  const overlapDeadCodeTimeout = resolveDeadCodeTimeout({
74557
- sourceFileCount: project.sourceFileCount,
74902
+ sourceFileCount: deadCodeSourceFileCount,
74558
74903
  deadCodeConcurrency: deadCodeParseConcurrency ?? scanConcurrency,
74559
74904
  fullConcurrency: scanConcurrency
74560
74905
  });
74561
74906
  const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
74562
74907
  workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
74563
- phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs))
74908
+ phaseTimeoutMs: capOptionalToDeadline(deadCodePhaseTimeoutMs)
74564
74909
  })) : null;
74565
74910
  const scanProgress = yield* progressService.start("Scanning...");
74566
74911
  const scanStartTime = Date.now();
@@ -74611,7 +74956,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74611
74956
  });
74612
74957
  return empty$4;
74613
74958
  }))));
74614
- const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
74959
+ const collectLintDiagnostics = runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream));
74960
+ const lintCollected = dedupeRelatedDiagnostics(yield* lintPhaseTimeoutMs === null ? collectLintDiagnostics : collectLintDiagnostics.pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
74615
74961
  onNone: () => set(lintFailure, {
74616
74962
  didFail: true,
74617
74963
  reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
@@ -74641,13 +74987,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74641
74987
  } else {
74642
74988
  yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
74643
74989
  const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
74644
- sourceFileCount: totalFileCount,
74990
+ sourceFileCount: input.retainExcludedProjectDeadCodeDiagnostics === true ? deadCodeSourceFileCount : totalFileCount,
74645
74991
  deadCodeConcurrency: scanConcurrency,
74646
74992
  fullConcurrency: scanConcurrency
74647
74993
  });
74648
74994
  deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
74649
74995
  workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
74650
- phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs))
74996
+ phaseTimeoutMs: capOptionalToDeadline(deadCodePhaseTimeoutMs)
74651
74997
  });
74652
74998
  }
74653
74999
  const deadCodeFailureState = lintFailureState.didFail ? {
@@ -74691,7 +75037,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74691
75037
  metadata: scoreMetadata
74692
75038
  });
74693
75039
  const lintPartialFailures = yield* get$2(partialFailuresRef);
74694
- const securityScanFailed = yield* get$2(securityScanFailedRef);
75040
+ const didSecurityScanFail = yield* get$2(securityScanFailedRef);
75041
+ const securityScanFailed = didSecurityScanFail || didSecurityScanReachDeadline;
75042
+ let securityScanFailureReason = null;
75043
+ if (didSecurityScanReachDeadline) securityScanFailureReason = "Security scan reached the max scan duration; findings collected before the deadline were preserved.";
75044
+ else if (didSecurityScanFail) securityScanFailureReason = "Security scan failed and was skipped.";
74695
75045
  return {
74696
75046
  project,
74697
75047
  userConfig: resolvedConfig.config,
@@ -74714,6 +75064,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74714
75064
  scanConcurrency,
74715
75065
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
74716
75066
  securityScanFailed,
75067
+ securityScanFailureReason,
74717
75068
  lintCacheHitFileCount,
74718
75069
  lintCacheTotalFileCount,
74719
75070
  lintSidecarReplayedFileCount,
@@ -74729,7 +75080,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74729
75080
  "inspect.runDeadCode": input.runDeadCode,
74730
75081
  "inspect.isCi": input.isCi,
74731
75082
  "inspect.scoreSurface": input.scoreSurface ?? "score"
74732
- } }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })))));
75083
+ } }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => {
75084
+ if (scanDeadlineMs === null) return scanProgram;
75085
+ return scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })));
75086
+ }));
74733
75087
  const parseNodeVersion = (versionString) => {
74734
75088
  const [major = 0, minor = 0, patch = 0] = versionString.replace(/^v/, "").trim().split(".").map(Number);
74735
75089
  return {
@@ -74830,16 +75184,6 @@ const resolveNodeForOxlint = () => {
74830
75184
  });
74831
75185
  });
74832
75186
  /**
74833
- * Zip-Slip defense: relative paths come from git (`diff --name-only`), which
74834
- * normalizes during ordinary adds, but a crafted index/pack/symlinked tree can
74835
- * smuggle `..` segments that escape the temp root. Resolve against the temp dir
74836
- * and reject anything that lands outside before writing.
74837
- */
74838
- const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
74839
- const relative = path.relative(parentAbsolutePath, childAbsolutePath);
74840
- return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
74841
- };
74842
- /**
74843
75187
  * Writes a set of source files (supplied by `readContent` — e.g.
74844
75188
  * `git show <ref>:<path>` for a baseline tree, or `git show :<path>` for the
74845
75189
  * index) into a temp tree that mirrors the project layout, copying the
@@ -75691,10 +76035,7 @@ const createProjectGraph = (options) => {
75691
76035
  for (const root of roots) try {
75692
76036
  for (const workspacePackage of discoverReactSubprojects(root)) {
75693
76037
  const directory = normalizeDirectory(workspacePackage.directory);
75694
- if (!seen.has(directory)) seen.set(directory, {
75695
- directory,
75696
- name: workspacePackage.name
75697
- });
76038
+ if (!seen.has(directory)) seen.set(directory, { directory });
75698
76039
  }
75699
76040
  } catch (error) {
75700
76041
  logger.warn(`Project discovery failed for ${root}: ${messageFromUnknown(error)}`);
@@ -75719,6 +76060,7 @@ const createProjectGraph = (options) => {
75719
76060
  clearProjectCache();
75720
76061
  clearConfigCache();
75721
76062
  clearPackageJsonCache();
76063
+ clearPackageRoleCache();
75722
76064
  clearIgnorePatternsCache();
75723
76065
  clearAutoSuppressionCaches();
75724
76066
  clearMinifiedFileCache();
@@ -76116,11 +76458,9 @@ const createScheduler = (options) => {
76116
76458
  timers.clear();
76117
76459
  queue.length = 0;
76118
76460
  };
76119
- const pendingCount = () => queue.length + running + timers.size;
76120
76461
  return {
76121
76462
  enqueue,
76122
76463
  cancelProject,
76123
- pendingCount,
76124
76464
  dispose
76125
76465
  };
76126
76466
  };
@@ -76767,8 +77107,11 @@ const METRIC = {
76767
77107
  scanPhaseDuration: "scan.phase_duration",
76768
77108
  scanFiles: "scan.files",
76769
77109
  scanScore: "scan.score",
77110
+ scanScoreRetry: "scan.score_retry",
76770
77111
  scanClean: "scan.clean",
76771
77112
  scanCheckSkipped: "scan.check_skipped",
77113
+ scanProjectSkipped: "scan.project_skipped",
77114
+ scanWorkspaceDeadCodeShared: "scan.workspace_deadcode_shared",
76772
77115
  scanNoReactDetected: "scan.no_react_detected",
76773
77116
  baselineDegraded: "baseline.degraded",
76774
77117
  ruleFired: "rule.fired",
@@ -76799,8 +77142,10 @@ const METRIC = {
76799
77142
  tuiFindingNavigated: "tui.finding_navigated",
76800
77143
  tuiIssueStreamShown: "tui.issue_stream_shown",
76801
77144
  tuiProjectPathContextShown: "tui.project_path_context_shown",
77145
+ tuiProjectSelectInlineShown: "tui.project_select_inline_shown",
76802
77146
  tuiReportActionSelected: "tui.report_action_selected",
76803
77147
  tuiCancelled: "tui.cancelled",
77148
+ tuiScanInlineShown: "tui.scan_inline_shown",
76804
77149
  tuiStackedReportCapped: "tui.stacked_report_capped",
76805
77150
  aiTrainingWarningShown: "ai.training.warning_shown",
76806
77151
  jsonOutUsed: "json.out_used"
@@ -77103,5 +77448,5 @@ const startLanguageServer = () => {
77103
77448
  };
77104
77449
  //#endregion
77105
77450
  export { startLanguageServer };
77106
- !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]="84561d6a-3814-52df-a86e-cf40018cdc87")}catch(e){}}();
77107
- //# debugId=84561d6a-3814-52df-a86e-cf40018cdc87
77451
+ !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]="9c497322-df37-5162-a092-8ebb7ef33f3a")}catch(e){}}();
77452
+ //# debugId=9c497322-df37-5162-a092-8ebb7ef33f3a