react-doctor 0.9.3-dev.9bb44e7 → 0.9.3-dev.afd60db

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,
@@ -66394,7 +66623,8 @@ const readSystemFacts$1 = () => ({
66394
66623
  /**
66395
66624
  * Auto lint-worker count: the smaller of the (cgroup-CPU-aware) core count and
66396
66625
  * the number of `PER_WORKER_MEM_BUDGET_BYTES` workers that fit in available
66397
- * memory, then clamped to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
66626
+ * memory, and the measured automatic parallel-efficiency ceiling, then clamped
66627
+ * to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
66398
66628
  *
66399
66629
  * `os.availableParallelism()` already respects cgroup CPU quotas, so the core
66400
66630
  * term needs no help. Available memory is `os.totalmem()` floored by the cgroup
@@ -66410,7 +66640,7 @@ const readSystemFacts$1 = () => ({
66410
66640
  const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
66411
66641
  const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
66412
66642
  const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
66413
- return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
66643
+ return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers, 10));
66414
66644
  };
66415
66645
  const resolveLintBatchOrdering = () => {
66416
66646
  return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
@@ -66423,24 +66653,22 @@ const resolveLintBatchOrdering = () => {
66423
66653
  */
66424
66654
  var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS", OXLINT_SPAWN_TIMEOUT_MS) }) {};
66425
66655
  /**
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.
66656
+ * Optional Effect-side cap on the lint phase. Unbounded unless explicitly
66657
+ * configured through the env var or a test layer.
66428
66658
  * Tests override via `Layer.succeed(LintPhaseTimeoutMs, ...)`.
66429
66659
  */
66430
- var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", LINT_PHASE_TIMEOUT_MS) }) {};
66660
+ var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", null) }) {};
66431
66661
  /**
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
66662
+ * Optional Effect-side cap on the dead-code phase. Unbounded unless explicitly
66663
+ * configured through the env var or a test layer. Tests override via
66435
66664
  * `Layer.succeed(DeadCodePhaseTimeoutMs, ...)`.
66436
66665
  */
66437
- var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", DEAD_CODE_PHASE_TIMEOUT_MS) }) {};
66666
+ var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", null) }) {};
66438
66667
  /**
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, ...)`.
66668
+ * Optional overall scan deadline. Unbounded unless explicitly configured
66669
+ * through the env var or a test layer.
66442
66670
  */
66443
- var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", SCAN_TOTAL_DEADLINE_MS) }) {};
66671
+ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", null) }) {};
66444
66672
  /**
66445
66673
  * Wall-clock budget for the supply-chain check when it runs on a background
66446
66674
  * fiber overlapping the lint pass. Reads from the env var on startup so the
@@ -66513,14 +66741,14 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
66513
66741
  /**
66514
66742
  * How the full-scan lint pass plans its file batches. `"cost"` (the default)
66515
66743
  * 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
66744
+ * batch count as greedy chunking, but every batch gets an even share of files
66745
+ * AND bytes, so no full chunk is a straggler while
66518
66746
  * the remainder-batch worker idles — and the heavy files are SPREAD across
66519
66747
  * batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
66520
66748
  * lacked (it packed the heaviest files into one wave-1 straggler batch,
66521
66749
  * measurably regressing size-skewed repos, which is why it never earned the
66522
66750
  * default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
66523
- * rollback hatch to plain greedy 100-file chunking in discovery order. Tests
66751
+ * rollback hatch to plain greedy fixed-size chunking in discovery order. Tests
66524
66752
  * override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
66525
66753
  * never reach this — they pass user-scoped `includePaths` that skip discovery
66526
66754
  * and stay in arrival order; only the full-scan branch reads it.
@@ -67010,6 +67238,10 @@ const dedupeRelatedDiagnostics = (diagnostics) => {
67010
67238
  }
67011
67239
  return uniqueDiagnostics;
67012
67240
  };
67241
+ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
67242
+ const relativePath = Path.relative(parentAbsolutePath, childAbsolutePath);
67243
+ return Boolean(relativePath) && !relativePath.startsWith("..") && !Path.isAbsolute(relativePath);
67244
+ };
67013
67245
  const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
67014
67246
  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
67247
  const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
@@ -68586,7 +68818,7 @@ const readScannedFile = (candidate) => {
68586
68818
  return null;
68587
68819
  }
68588
68820
  };
68589
- function* collectSecurityScanFiles(rootDirectory) {
68821
+ function* collectSecurityScanFiles(rootDirectory, excludedDirectories = /* @__PURE__ */ new Set()) {
68590
68822
  const priorityCandidates = [];
68591
68823
  const artifactCandidates = [];
68592
68824
  const otherCandidates = [];
@@ -68601,7 +68833,7 @@ function* collectSecurityScanFiles(rootDirectory) {
68601
68833
  for (const entry of readDirectoryEntries(current.absolutePath)) {
68602
68834
  const absolutePath = Path.join(current.absolutePath, entry.name);
68603
68835
  if (entry.isDirectory()) {
68604
- if (!SKIPPED_DIRECTORY_NAMES.has(entry.name)) stack.push({
68836
+ if (!SKIPPED_DIRECTORY_NAMES.has(entry.name) && !excludedDirectories.has(Path.resolve(absolutePath))) stack.push({
68605
68837
  absolutePath,
68606
68838
  depth: current.depth + 1
68607
68839
  });
@@ -68782,10 +69014,8 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhe
68782
69014
  }
68783
69015
  return true;
68784
69016
  };
69017
+ const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
68785
69018
  const shouldEnableRuleByDefaultStatus = (options) => options.defaultEnabled !== false || options.hasExplicitOverride === true || options.includeTagDefaults && options.hasIncludedTags;
68786
- const yieldToEventLoop = () => new Promise((resolve) => {
68787
- setImmediate(resolve);
68788
- });
68789
69019
  const createSecurityScanSession = (rootDirectory, options) => {
68790
69020
  const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
68791
69021
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
@@ -68838,19 +69068,35 @@ const createSecurityScanSession = (rootDirectory, options) => {
68838
69068
  };
68839
69069
  };
68840
69070
  const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
69071
+ const throwIfScanAborted = () => {
69072
+ options.signal?.throwIfAborted();
69073
+ };
69074
+ const didReachDeadline = () => {
69075
+ const hasDeadlineElapsed = options.deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(options.deadlineEpochMs) === 0;
69076
+ if (hasDeadlineElapsed) options.onDeadlineExceeded?.();
69077
+ return hasDeadlineElapsed;
69078
+ };
69079
+ throwIfScanAborted();
68841
69080
  const session = createSecurityScanSession(rootDirectory, options);
68842
69081
  if (session === null) return [];
69082
+ if (didReachDeadline()) return [];
68843
69083
  let sliceStartedAt = performance.now();
68844
- for (const file of collectSecurityScanFiles(rootDirectory)) {
69084
+ for (const file of collectSecurityScanFiles(rootDirectory, options.excludedDirectories)) {
69085
+ throwIfScanAborted();
69086
+ if (didReachDeadline()) return session.diagnostics;
68845
69087
  if (file === null) {
68846
69088
  if (performance.now() - sliceStartedAt >= 12) {
68847
69089
  await yieldToEventLoop();
69090
+ throwIfScanAborted();
69091
+ if (didReachDeadline()) return session.diagnostics;
68848
69092
  sliceStartedAt = performance.now();
68849
69093
  }
68850
69094
  continue;
68851
69095
  }
68852
69096
  for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
68853
69097
  await yieldToEventLoop();
69098
+ throwIfScanAborted();
69099
+ if (didReachDeadline()) return session.diagnostics;
68854
69100
  sliceStartedAt = performance.now();
68855
69101
  }
68856
69102
  }
@@ -69764,7 +70010,6 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
69764
70010
  return true;
69765
70011
  };
69766
70012
  const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
69767
- const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
69768
70013
  /**
69769
70014
  * Budget for the dead-code phase, scaled to the work. deslop's graph build is
69770
70015
  * CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
@@ -69772,25 +70017,28 @@ const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs
69772
70017
  * tipped over by any concurrent load — silently dropping every dead-code
69773
70018
  * finding. Scaling the budget with file count (and inversely with the core
69774
70019
  * 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.
70020
+ * reclaims a genuinely wedged worker.
69777
70021
  */
69778
70022
  const resolveDeadCodeTimeout = (input) => {
69779
70023
  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
- };
70024
+ return { workerTimeoutMs: Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor))) };
69785
70025
  };
69786
- const resolveLintIncludePaths = (rootDirectory, userConfig) => {
70026
+ const resolveLintIncludePaths = (rootDirectory, userConfig, sourceFiles) => {
69787
70027
  if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
69788
70028
  const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
69789
- return listSourceFiles(rootDirectory).filter((filePath) => {
70029
+ return (sourceFiles ?? listSourceFiles(rootDirectory)).filter((filePath) => {
69790
70030
  if (!isLintableSourceFile(filePath)) return false;
69791
70031
  return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
69792
70032
  });
69793
70033
  };
70034
+ const filterPathsOutsideDirectories = (input) => {
70035
+ 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}/`)));
70036
+ if (excludedPrefixes.length === 0) return [...input.relativePaths];
70037
+ return input.relativePaths.filter((relativePath) => {
70038
+ const normalizedRelativePath = relativePath.replaceAll("\\", "/").replace(/^\.\//, "");
70039
+ return !excludedPrefixes.some((excludedPrefix) => normalizedRelativePath === excludedPrefix || normalizedRelativePath.startsWith(`${excludedPrefix}/`));
70040
+ });
70041
+ };
69794
70042
  var Config = class Config extends Service()("react-doctor/Config") {
69795
70043
  static layerNode = effect(Config, gen(function* () {
69796
70044
  const cache = yield* make$3({
@@ -69852,6 +70100,7 @@ var Files = class Files extends Service()("react-doctor/Files") {
69852
70100
  static layerNode = succeed$3(Files, Files.of({
69853
70101
  readLines: (input) => sync(() => createNodeReadFileLinesSync(input.rootDirectory)(input.filePath)),
69854
70102
  listSourceFiles: (rootDirectory) => sync(() => listSourceFiles(rootDirectory)),
70103
+ listSourceFilesCooperative: (input) => promise(() => listSourceFilesCooperative(input.rootDirectory, input.signal)),
69855
70104
  isFile: (filePath) => sync(() => isFile(filePath)),
69856
70105
  isDirectory: (filePath) => sync(() => isDirectory(filePath))
69857
70106
  }));
@@ -69878,6 +70127,16 @@ var Files = class Files extends Service()("react-doctor/Files") {
69878
70127
  }
69879
70128
  return files;
69880
70129
  }),
70130
+ listSourceFilesCooperative: (input) => sync(() => {
70131
+ const rootDirectory = input.rootDirectory;
70132
+ const prefix = rootDirectory.endsWith("/") ? rootDirectory : `${rootDirectory}/`;
70133
+ const files = [];
70134
+ for (const absolute of tree.keys()) {
70135
+ if (!absolute.startsWith(prefix)) continue;
70136
+ files.push(absolute.slice(prefix.length));
70137
+ }
70138
+ return files;
70139
+ }),
69881
70140
  isFile: (filePath) => sync(() => tree.has(filePath)),
69882
70141
  isDirectory: (filePath) => sync(() => {
69883
70142
  const prefix = filePath.endsWith("/") ? filePath : `${filePath}/`;
@@ -70453,7 +70712,7 @@ const batchIncludePaths = (baseArgs, includePaths) => {
70453
70712
  for (const filePath of includePaths) {
70454
70713
  const entryLength = filePath.length + 1;
70455
70714
  const exceedsArgLength = currentBatch.length > 0 && currentBatchLength + entryLength > 24e3;
70456
- const exceedsFileCount = currentBatch.length >= 100;
70715
+ const exceedsFileCount = currentBatch.length >= 200;
70457
70716
  if (exceedsArgLength || exceedsFileCount) {
70458
70717
  batches.push(currentBatch);
70459
70718
  currentBatch = [];
@@ -70800,7 +71059,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
70800
71059
  }
70801
71060
  return enabled;
70802
71061
  };
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 }) => {
71062
+ 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
71063
  const hasIncludedTags = includedTags.size > 0;
70805
71064
  const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" || hasIncludedTags ? null : resolveReactHooksJsPlugin(project.hasReactCompiler || project.hasReactCompilerLintPlugin === true, customRulesOnly);
70806
71065
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
@@ -70864,7 +71123,8 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
70864
71123
  }))
70865
71124
  } : {},
70866
71125
  ...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
70867
- ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
71126
+ ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {},
71127
+ ...projectIndexModuleSources !== void 0 ? { projectIndexModuleSources: [...projectIndexModuleSources] } : {}
70868
71128
  } },
70869
71129
  rules: {
70870
71130
  ...reactCompilerRules,
@@ -72349,6 +72609,12 @@ const buildProfiledNodeArguments = (input) => [
72349
72609
  ...input.heapProfileDirectory ? ["--heap-prof", `--heap-prof-dir=${input.heapProfileDirectory}`] : [],
72350
72610
  ...input.argumentsList
72351
72611
  ];
72612
+ const lowerChildProcessPriority = (processId) => {
72613
+ if (processId === void 0) return;
72614
+ try {
72615
+ OS.setPriority(processId, OS.constants.priority.PRIORITY_BELOW_NORMAL);
72616
+ } catch {}
72617
+ };
72352
72618
  const SANITIZED_ENV = buildOxlintChildEnv(process.env);
72353
72619
  /**
72354
72620
  * Spawn one oxlint subprocess with hard ceilings on wall time and
@@ -72387,6 +72653,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
72387
72653
  "pipe"
72388
72654
  ]
72389
72655
  });
72656
+ lowerChildProcessPriority(child.pid);
72390
72657
  const onAbort = () => {
72391
72658
  child.kill("SIGKILL");
72392
72659
  reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
@@ -72509,7 +72776,7 @@ const isParallelismRelatedSpawnError = (error) => {
72509
72776
  * loop with a slimmer config in that case.
72510
72777
  */
72511
72778
  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;
72779
+ const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, sourceMapByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 9, signal, deadlineEpochMs } = input;
72513
72780
  const resolveSourcePath = (filePath) => {
72514
72781
  const absoluteFilePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory, filePath);
72515
72782
  return sourcePathByLintPath?.get(Path.normalize(absoluteFilePath)) ?? filePath;
@@ -72517,6 +72784,12 @@ const spawnLintBatches = async (input) => {
72517
72784
  const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
72518
72785
  let rescueDeadlineEpochMs;
72519
72786
  const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0 || rescueDeadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(rescueDeadlineEpochMs) === 0;
72787
+ const resolveSpawnTimeoutMs = () => {
72788
+ const deadlineBudgets = [deadlineEpochMs, rescueDeadlineEpochMs].filter((deadline) => deadline !== void 0).map(remainingDeadlineBudgetMs);
72789
+ if (deadlineBudgets.length === 0) return spawnTimeoutMs;
72790
+ const remainingDeadlineMs = Math.min(...deadlineBudgets);
72791
+ return spawnTimeoutMs === void 0 ? remainingDeadlineMs : Math.min(spawnTimeoutMs, remainingDeadlineMs);
72792
+ };
72520
72793
  const runBatchPass = async (concurrency, passBatches, passOnFileProgress) => {
72521
72794
  const totalFileCount = passBatches.reduce((sum, batch) => sum + batch.length, 0);
72522
72795
  const allDiagnostics = [];
@@ -72534,12 +72807,13 @@ const spawnLintBatches = async (input) => {
72534
72807
  const batchArgs = [...baseArgs, ...batch];
72535
72808
  try {
72536
72809
  const spawnBatch = () => {
72537
- if (isPastDeadline()) {
72810
+ const effectiveSpawnTimeoutMs = resolveSpawnTimeoutMs();
72811
+ if (effectiveSpawnTimeoutMs === 0) {
72538
72812
  deadlineSkippedFiles.push(...batch);
72539
72813
  batchState.deadlineSkippedFileCount += batch.length;
72540
72814
  return Promise.resolve(null);
72541
72815
  }
72542
- return spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal, () => {
72816
+ return spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, effectiveSpawnTimeoutMs, outputMaxBytes, signal, () => {
72543
72817
  if (batchState.didStart) return;
72544
72818
  batchState.didStart = true;
72545
72819
  startedFileCount += batchState.initialFileCount;
@@ -72694,11 +72968,36 @@ const validateRuleRegistration = () => {
72694
72968
  ].filter((entry) => entry !== null).join("; ");
72695
72969
  console.warn(`[react-doctor] rule-registration drift: ${detail}`);
72696
72970
  };
72971
+ const PROJECT_INDEX_MODULE_SOURCES = [
72972
+ "next/og",
72973
+ "@vercel/og",
72974
+ "satori",
72975
+ "remotion"
72976
+ ];
72977
+ const collectProjectIndexModuleSources = async (rootDirectory, candidateFiles) => {
72978
+ const foundModuleSources = /* @__PURE__ */ new Set();
72979
+ let sliceStartedAt = performance.now();
72980
+ for (const candidateFile of candidateFiles) {
72981
+ let sourceBuffer;
72982
+ try {
72983
+ sourceBuffer = NFS.readFileSync(Path.isAbsolute(candidateFile) ? candidateFile : Path.resolve(rootDirectory, candidateFile));
72984
+ } catch {
72985
+ continue;
72986
+ }
72987
+ for (const moduleSource of PROJECT_INDEX_MODULE_SOURCES) if (sourceBuffer.includes(`"${moduleSource}"`) || sourceBuffer.includes(`'${moduleSource}'`)) foundModuleSources.add(moduleSource);
72988
+ if (foundModuleSources.size === PROJECT_INDEX_MODULE_SOURCES.length) break;
72989
+ if (performance.now() - sliceStartedAt >= 12) {
72990
+ await yieldToEventLoop();
72991
+ sliceStartedAt = performance.now();
72992
+ }
72993
+ }
72994
+ return [...foundModuleSources].sort();
72995
+ };
72697
72996
  /**
72698
72997
  * 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
72998
+ * `batchIncludePaths` greedily fills fixed-size batches and leaves a small
72999
+ * remainder batch, so the parallel pool's wall clock is set by whichever
73000
+ * full chunk happened to collect the most work while
72702
73001
  * the remainder's worker idles), this planner keeps the SAME mandatory batch
72703
73002
  * count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
72704
73003
  * perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
@@ -72727,7 +73026,7 @@ const validateRuleRegistration = () => {
72727
73026
  */
72728
73027
  const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
72729
73028
  if (files.length === 0) return [];
72730
- const batchCount = Math.ceil(files.length / 100);
73029
+ const batchCount = Math.ceil(files.length / 200);
72731
73030
  const maxFilesPerBatch = Math.ceil(files.length / batchCount);
72732
73031
  const baseArgsLengthChars = estimateArgsLength(baseArgs);
72733
73032
  const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
@@ -73256,6 +73555,7 @@ const runOxlint = async (options) => {
73256
73555
  await yieldToEventLoop();
73257
73556
  const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
73258
73557
  const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
73558
+ const projectIndexModuleSources = includePaths === void 0 && options.deadlineEpochMs === void 0 ? await collectProjectIndexModuleSources(rootDirectory, candidateFiles) : void 0;
73259
73559
  unpluginAutoImportGlobalScopes = collectUnpluginAutoImportGlobalScopes({
73260
73560
  rootDirectory,
73261
73561
  candidateFiles
@@ -73278,6 +73578,7 @@ const runOxlint = async (options) => {
73278
73578
  runtimeGlobals,
73279
73579
  unpluginAutoImportGlobalScopes,
73280
73580
  serverAuthFunctionNames,
73581
+ projectIndexModuleSources,
73281
73582
  severityControls,
73282
73583
  userPlugins,
73283
73584
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
@@ -73745,10 +74046,10 @@ const translateProjectInfoError = (cause, directory) => {
73745
74046
  }) });
73746
74047
  };
73747
74048
  var Project = class Project extends Service()("react-doctor/Project") {
73748
- static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (directory) {
74049
+ static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (input) {
73749
74050
  return yield* try_({
73750
- try: () => discoverProject(directory),
73751
- catch: (cause) => translateProjectInfoError(cause, directory)
74051
+ try: () => discoverProject(input.directory, { sourceFileCount: input.sourceFileCount }),
74052
+ catch: (cause) => translateProjectInfoError(cause, input.directory)
73752
74053
  });
73753
74054
  }) }));
73754
74055
  static layerOf = (projectInfo) => succeed$3(Project, Project.of({ discover: () => succeed$2(projectInfo) }));
@@ -74333,9 +74634,10 @@ const checkSupplyChain = (input) => gen(function* () {
74333
74634
  * outage.
74334
74635
  */
74335
74636
  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
74637
+ static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (configuredTimeoutMs) => checkSupplyChain({
74638
+ rootDirectory: input.rootDirectory,
74639
+ userConfig: input.userConfig,
74640
+ totalTimeoutMs: input.timeoutMs ?? configuredTimeoutMs
74339
74641
  })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
74340
74642
  static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
74341
74643
  };
@@ -74451,8 +74753,28 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74451
74753
  const partialFailuresRef = yield* LintPartialFailures;
74452
74754
  const resolvedConfig = yield* configService.resolve(input.directory);
74453
74755
  const scanDirectory = resolvedConfig.resolvedDirectory;
74454
- const project = yield* projectService.discover(scanDirectory);
74756
+ const excludedProjectDirectories = (input.excludedProjectDirectories ?? []).map((excludedDirectory) => Path.resolve(excludedDirectory)).filter((excludedDirectory) => isPathInsideDirectory(excludedDirectory, scanDirectory));
74757
+ const excludedProjectRelativePaths = excludedProjectDirectories.map((excludedDirectory) => Path.relative(scanDirectory, excludedDirectory).replaceAll("\\", "/"));
74758
+ const isExcludedProjectDiagnostic = (diagnostic) => {
74759
+ const relativeFilePath = toNormalizedRelativePath(diagnostic.filePath, scanDirectory);
74760
+ return excludedProjectRelativePaths.some((excludedRelativePath) => relativeFilePath === excludedRelativePath || relativeFilePath.startsWith(`${excludedRelativePath}/`));
74761
+ };
74762
+ const precomputedSourceFiles = input.suppressScanSummary === true || excludedProjectDirectories.length > 0 ? yield* filesService.listSourceFilesCooperative({
74763
+ rootDirectory: scanDirectory,
74764
+ signal: input.signal
74765
+ }) : null;
74766
+ const includedPrecomputedSourceFiles = precomputedSourceFiles !== null && excludedProjectDirectories.length > 0 ? filterPathsOutsideDirectories({
74767
+ rootDirectory: scanDirectory,
74768
+ relativePaths: precomputedSourceFiles,
74769
+ excludedDirectories: excludedProjectDirectories
74770
+ }) : precomputedSourceFiles;
74771
+ const sourceFileCount = excludedProjectDirectories.length > 0 ? includedPrecomputedSourceFiles?.length : input.precomputedSourceFileCount ?? includedPrecomputedSourceFiles?.length;
74772
+ const project = yield* projectService.discover({
74773
+ directory: scanDirectory,
74774
+ sourceFileCount
74775
+ });
74455
74776
  if (!isAnalyzableProject(project)) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
74777
+ const deadCodeSourceFileCount = input.retainExcludedProjectDeadCodeDiagnostics === true && precomputedSourceFiles !== null ? precomputedSourceFiles.length : project.sourceFileCount;
74456
74778
  const [repo, sha, defaultBranch] = yield* all([
74457
74779
  gitService.githubRepo(scanDirectory).pipe(orElseSucceed(() => null)),
74458
74780
  gitService.headSha(scanDirectory).pipe(orElseSucceed(() => null)),
@@ -74463,8 +74785,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74463
74785
  directory: scanDirectory,
74464
74786
  repo
74465
74787
  }).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)) : [];
74788
+ let lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, includedPrecomputedSourceFiles ?? void 0);
74789
+ if (excludedProjectDirectories.length > 0) lintIncludePaths = filterPathsOutsideDirectories({
74790
+ rootDirectory: scanDirectory,
74791
+ relativePaths: lintIncludePaths ?? includedPrecomputedSourceFiles ?? (yield* filesService.listSourceFiles(scanDirectory)),
74792
+ excludedDirectories: excludedProjectDirectories
74793
+ });
74794
+ const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? includedPrecomputedSourceFiles ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
74468
74795
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
74469
74796
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
74470
74797
  yield* beforeLint(project, lintIncludePaths ?? void 0);
@@ -74487,17 +74814,31 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74487
74814
  ...checkReactNativeProject(scanDirectory, project)
74488
74815
  ])));
74489
74816
  const securityScanFailedRef = yield* make$13(false);
74490
- const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
74817
+ let didSecurityScanReachDeadline = false;
74818
+ const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise((signal) => checkSecurityScanCooperative(scanDirectory, {
74491
74819
  project,
74492
74820
  ignoredTags: input.ignoredTags,
74493
74821
  includedTags: input.includedTags,
74494
- includeTagDefaults: input.includeTagDefaults
74822
+ includeTagDefaults: input.includeTagDefaults,
74823
+ excludedDirectories: new Set(excludedProjectDirectories),
74824
+ deadlineEpochMs: input.deadlineEpochMs,
74825
+ signal,
74826
+ onDeadlineExceeded: () => {
74827
+ didSecurityScanReachDeadline = true;
74828
+ }
74495
74829
  })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
74830
+ const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
74831
+ const capOptionalToDeadline = (phaseTimeoutMs) => {
74832
+ if (input.deadlineEpochMs === void 0) return phaseTimeoutMs;
74833
+ const remainingBudgetMs = remainingDeadlineBudgetMs(input.deadlineEpochMs);
74834
+ return phaseTimeoutMs === null ? remainingBudgetMs : Math.min(phaseTimeoutMs, remainingBudgetMs);
74835
+ };
74496
74836
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
74497
- const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
74837
+ const supplyChainOverlapTimeout = capToDeadline(yield* SupplyChainOverlapTimeoutMs);
74498
74838
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
74499
74839
  rootDirectory: scanDirectory,
74500
- userConfig: resolvedConfig.config
74840
+ userConfig: resolvedConfig.config,
74841
+ timeoutMs: supplyChainOverlapTimeout
74501
74842
  }))).pipe(map$3((diagnostics) => ({
74502
74843
  diagnostics,
74503
74844
  timedOut: false
@@ -74521,46 +74862,49 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74521
74862
  const scanConcurrency = resolveScanConcurrency(yield* OxlintConcurrency);
74522
74863
  const lintPhaseTimeoutMs = yield* LintPhaseTimeoutMs;
74523
74864
  const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
74524
- const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
74525
74865
  const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
74526
- const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
74527
74866
  const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
74528
74867
  const deadCodeOverlapMode = yield* DeadCodeOverlap;
74529
74868
  const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
74530
74869
  const deadCodeParseConcurrency = shouldOverlapDeadCode ? Math.max(1, Math.floor(scanConcurrency * DEAD_CODE_OVERLAP_PARSE_SHARE)) : void 0;
74531
74870
  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
- })));
74871
+ const buildCollectDeadCode = (deadCodeTimeout) => {
74872
+ const collectDeadCode = runCollect(applyPerElementPipeline(deadCodeService.run({
74873
+ rootDirectory: scanDirectory,
74874
+ parseConcurrency: deadCodeParseConcurrency,
74875
+ workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
74876
+ onCacheOutcome: (didHitCache) => {
74877
+ deadCodeCacheHit = didHitCache;
74878
+ },
74879
+ onSummaryCacheStats: (stats) => {
74880
+ deadCodeSummaryCacheHits = stats.hits;
74881
+ deadCodeSummaryCacheMisses = stats.misses;
74882
+ }
74883
+ }).pipe(filter((diagnostic) => input.retainExcludedProjectDeadCodeDiagnostics === true || !isExcludedProjectDiagnostic(diagnostic)), catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
74884
+ yield* set(deadCodeFailure, {
74885
+ didFail: true,
74886
+ reason: error.message
74887
+ });
74888
+ return empty$4;
74889
+ }))))));
74890
+ const phaseTimeoutMs = deadCodeTimeout.phaseTimeoutMs;
74891
+ if (phaseTimeoutMs === null) return collectDeadCode;
74892
+ return collectDeadCode.pipe(timeoutOption(phaseTimeoutMs), flatMap$2(match$3({
74893
+ onNone: () => set(deadCodeFailure, {
74894
+ didFail: true,
74895
+ reason: `Dead-code analysis exceeded ${Math.round(phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
74896
+ }).pipe(as([])),
74897
+ onSome: succeed$2
74898
+ })));
74899
+ };
74556
74900
  const overlapDeadCodeTimeout = resolveDeadCodeTimeout({
74557
- sourceFileCount: project.sourceFileCount,
74901
+ sourceFileCount: deadCodeSourceFileCount,
74558
74902
  deadCodeConcurrency: deadCodeParseConcurrency ?? scanConcurrency,
74559
74903
  fullConcurrency: scanConcurrency
74560
74904
  });
74561
74905
  const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
74562
74906
  workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
74563
- phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs))
74907
+ phaseTimeoutMs: capOptionalToDeadline(deadCodePhaseTimeoutMs)
74564
74908
  })) : null;
74565
74909
  const scanProgress = yield* progressService.start("Scanning...");
74566
74910
  const scanStartTime = Date.now();
@@ -74611,7 +74955,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74611
74955
  });
74612
74956
  return empty$4;
74613
74957
  }))));
74614
- const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
74958
+ const collectLintDiagnostics = runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream));
74959
+ const lintCollected = dedupeRelatedDiagnostics(yield* lintPhaseTimeoutMs === null ? collectLintDiagnostics : collectLintDiagnostics.pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
74615
74960
  onNone: () => set(lintFailure, {
74616
74961
  didFail: true,
74617
74962
  reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
@@ -74641,13 +74986,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74641
74986
  } else {
74642
74987
  yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
74643
74988
  const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
74644
- sourceFileCount: totalFileCount,
74989
+ sourceFileCount: input.retainExcludedProjectDeadCodeDiagnostics === true ? deadCodeSourceFileCount : totalFileCount,
74645
74990
  deadCodeConcurrency: scanConcurrency,
74646
74991
  fullConcurrency: scanConcurrency
74647
74992
  });
74648
74993
  deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
74649
74994
  workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
74650
- phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs))
74995
+ phaseTimeoutMs: capOptionalToDeadline(deadCodePhaseTimeoutMs)
74651
74996
  });
74652
74997
  }
74653
74998
  const deadCodeFailureState = lintFailureState.didFail ? {
@@ -74691,7 +75036,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74691
75036
  metadata: scoreMetadata
74692
75037
  });
74693
75038
  const lintPartialFailures = yield* get$2(partialFailuresRef);
74694
- const securityScanFailed = yield* get$2(securityScanFailedRef);
75039
+ const didSecurityScanFail = yield* get$2(securityScanFailedRef);
75040
+ const securityScanFailed = didSecurityScanFail || didSecurityScanReachDeadline;
75041
+ let securityScanFailureReason = null;
75042
+ if (didSecurityScanReachDeadline) securityScanFailureReason = "Security scan reached the max scan duration; findings collected before the deadline were preserved.";
75043
+ else if (didSecurityScanFail) securityScanFailureReason = "Security scan failed and was skipped.";
74695
75044
  return {
74696
75045
  project,
74697
75046
  userConfig: resolvedConfig.config,
@@ -74714,6 +75063,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74714
75063
  scanConcurrency,
74715
75064
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
74716
75065
  securityScanFailed,
75066
+ securityScanFailureReason,
74717
75067
  lintCacheHitFileCount,
74718
75068
  lintCacheTotalFileCount,
74719
75069
  lintSidecarReplayedFileCount,
@@ -74729,7 +75079,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74729
75079
  "inspect.runDeadCode": input.runDeadCode,
74730
75080
  "inspect.isCi": input.isCi,
74731
75081
  "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` }) })))));
75082
+ } }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => {
75083
+ if (scanDeadlineMs === null) return scanProgram;
75084
+ return scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })));
75085
+ }));
74733
75086
  const parseNodeVersion = (versionString) => {
74734
75087
  const [major = 0, minor = 0, patch = 0] = versionString.replace(/^v/, "").trim().split(".").map(Number);
74735
75088
  return {
@@ -74830,16 +75183,6 @@ const resolveNodeForOxlint = () => {
74830
75183
  });
74831
75184
  });
74832
75185
  /**
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
75186
  * Writes a set of source files (supplied by `readContent` — e.g.
74844
75187
  * `git show <ref>:<path>` for a baseline tree, or `git show :<path>` for the
74845
75188
  * index) into a temp tree that mirrors the project layout, copying the
@@ -76767,8 +77110,11 @@ const METRIC = {
76767
77110
  scanPhaseDuration: "scan.phase_duration",
76768
77111
  scanFiles: "scan.files",
76769
77112
  scanScore: "scan.score",
77113
+ scanScoreRetry: "scan.score_retry",
76770
77114
  scanClean: "scan.clean",
76771
77115
  scanCheckSkipped: "scan.check_skipped",
77116
+ scanProjectSkipped: "scan.project_skipped",
77117
+ scanWorkspaceDeadCodeShared: "scan.workspace_deadcode_shared",
76772
77118
  scanNoReactDetected: "scan.no_react_detected",
76773
77119
  baselineDegraded: "baseline.degraded",
76774
77120
  ruleFired: "rule.fired",
@@ -76795,9 +77141,13 @@ const METRIC = {
76795
77141
  lspScanCompleted: "lsp.scan.completed",
76796
77142
  lspScanDuration: "lsp.scan.duration",
76797
77143
  lspScanDiagnostics: "lsp.scan.diagnostics",
76798
- tuiActionMenuOpened: "tui.action_menu_opened",
76799
77144
  tuiCompactReportShown: "tui.compact_report_shown",
76800
77145
  tuiFindingNavigated: "tui.finding_navigated",
77146
+ tuiIssueStreamShown: "tui.issue_stream_shown",
77147
+ tuiProjectPathContextShown: "tui.project_path_context_shown",
77148
+ tuiReportActionSelected: "tui.report_action_selected",
77149
+ tuiCancelled: "tui.cancelled",
77150
+ tuiScanInlineShown: "tui.scan_inline_shown",
76801
77151
  tuiStackedReportCapped: "tui.stacked_report_capped",
76802
77152
  aiTrainingWarningShown: "ai.training.warning_shown",
76803
77153
  jsonOutUsed: "json.out_used"
@@ -77100,5 +77450,5 @@ const startLanguageServer = () => {
77100
77450
  };
77101
77451
  //#endregion
77102
77452
  export { startLanguageServer };
77103
- !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]="a07beba2-2d2e-59f0-8edb-430554d791d1")}catch(e){}}();
77104
- //# debugId=a07beba2-2d2e-59f0-8edb-430554d791d1
77453
+ !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]="987837fa-d5be-5f58-a2e6-feaeda00b877")}catch(e){}}();
77454
+ //# debugId=987837fa-d5be-5f58-a2e6-feaeda00b877