react-doctor 0.9.3-dev.3a93a34 → 0.9.3-dev.3acb41b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0c9da9c7-4847-5807-983e-a57b7eec97cd")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="76d09448-ef0d-52d4-8c22-005bcb8715c5")}catch(e){}}();
3
3
  import { i as __toESM$1, r as __require, t as __commonJSMin$1 } from "./chunk-ChKA3wi6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, FRAMEWORK_TOKENS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, REACT_DOCTOR_RULE_REGISTRY, REACT_DOCTOR_SCAN_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor/core";
@@ -10,7 +10,7 @@ import fs from "node:fs";
10
10
  import * as Path from "node:path";
11
11
  import path from "node:path";
12
12
  import * as NodeChildProcess from "node:child_process";
13
- import { execFileSync, spawn, spawnSync } from "node:child_process";
13
+ import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
14
14
  import { ResolverFactory } from "oxc-resolver";
15
15
  import ts from "typescript";
16
16
  import * as NodeUrl from "node:url";
@@ -3621,7 +3621,7 @@ const flatMap$5 = /* @__PURE__ */ dual(2, (self, f) => isNone(self) ? none() : f
3621
3621
  * @category filtering
3622
3622
  * @since 2.0.0
3623
3623
  */
3624
- const filter = /* @__PURE__ */ dual(2, (self, predicate) => isNone(self) ? none() : predicate(self.value) ? some(self.value) : none());
3624
+ const filter$1 = /* @__PURE__ */ dual(2, (self, predicate) => isNone(self) ? none() : predicate(self.value) ? some(self.value) : none());
3625
3625
  //#endregion
3626
3626
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Result.js
3627
3627
  /**
@@ -15556,7 +15556,7 @@ function transformOptional(f) {
15556
15556
  */
15557
15557
  function withDefault$1(defaultValue) {
15558
15558
  return new Getter((o) => {
15559
- const filtered = filter(o, isNotUndefined);
15559
+ const filtered = filter$1(o, isNotUndefined);
15560
15560
  return isSome(filtered) ? succeed$2(filtered) : mapEager(defaultValue, some);
15561
15561
  });
15562
15562
  }
@@ -19404,6 +19404,10 @@ var JsonReportProjectEntryV3 = class extends Class("JsonReportProjectEntryV3")({
19404
19404
  scannedFileCount: optional(Number$1),
19405
19405
  elapsedMilliseconds: Number$1
19406
19406
  }) {};
19407
+ var JsonReportSkippedProject = class extends Class("JsonReportSkippedProject")({
19408
+ directory: String$1,
19409
+ reason: Literal("max-duration")
19410
+ }) {};
19407
19411
  /** Original full, diff, and staged report contract. */
19408
19412
  var JsonReportV1 = class extends Class("JsonReportV1")({
19409
19413
  schemaVersion: Literal(1),
@@ -19432,6 +19436,7 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
19432
19436
  reactDetected: optional(Boolean$1),
19433
19437
  diff: NullOr(JsonReportDiffInfo),
19434
19438
  projects: ArraySchema(JsonReportProjectEntry),
19439
+ skippedProjects: optional(ArraySchema(JsonReportSkippedProject)),
19435
19440
  diagnostics: ArraySchema(Diagnostic),
19436
19441
  summary: JsonReportSummary,
19437
19442
  elapsedMilliseconds: Number$1,
@@ -19460,6 +19465,7 @@ var JsonReportV2 = class extends Class("JsonReportV2")({
19460
19465
  diff: NullOr(JsonReportDiffInfo),
19461
19466
  baseline: JsonReportBaseline,
19462
19467
  projects: ArraySchema(JsonReportProjectEntry),
19468
+ skippedProjects: optional(ArraySchema(JsonReportSkippedProject)),
19463
19469
  diagnostics: ArraySchema(Diagnostic),
19464
19470
  summary: JsonReportSummary,
19465
19471
  elapsedMilliseconds: Number$1,
@@ -19476,6 +19482,7 @@ var JsonReportV3 = class extends Class("JsonReportV3")({
19476
19482
  diff: NullOr(JsonReportDiffInfo),
19477
19483
  baseline: optional(JsonReportBaseline),
19478
19484
  projects: ArraySchema(JsonReportProjectEntryV3),
19485
+ skippedProjects: optional(ArraySchema(JsonReportSkippedProject)),
19479
19486
  diagnostics: ArraySchema(JsonReportDiagnosticV3),
19480
19487
  summary: JsonReportSummary,
19481
19488
  elapsedMilliseconds: Number$1,
@@ -53458,6 +53465,47 @@ const flattenArray = (self) => transformPull$1(self, (pull) => {
53458
53465
  }));
53459
53466
  });
53460
53467
  /**
53468
+ * Filters arrays of elements emitted by a channel, applying the filter
53469
+ * to each element within the arrays and only emitting non-empty filtered arrays.
53470
+ *
53471
+ * **Example** (Filtering array output)
53472
+ *
53473
+ * ```ts
53474
+ * import { Array, Channel } from "effect"
53475
+ *
53476
+ * const nonEmptyArrayPredicate = Array.isReadonlyArrayNonEmpty
53477
+ *
53478
+ * // Create a channel that outputs arrays of mixed data
53479
+ * const arrayChannel = Channel.fromIterable([
53480
+ * Array.make(1, 2, 3, 4, 5),
53481
+ * Array.make(6, 7, 8, 9, 10),
53482
+ * Array.make(11, 12, 13, 14, 15)
53483
+ * ]).pipe(Channel.filter(nonEmptyArrayPredicate))
53484
+ *
53485
+ * // Filter arrays to keep only even numbers
53486
+ * const evenArraysChannel = Channel.filterArray(arrayChannel, (n) => n % 2 === 0)
53487
+ * // Outputs: [2, 4], [6, 8, 10], [12, 14]
53488
+ * // Note: Only non-empty filtered arrays are emitted
53489
+ *
53490
+ * // Arrays that would become empty after filtering are discarded entirely
53491
+ * const oddChannel = Channel.fromIterable([
53492
+ * Array.make(1, 3, 5),
53493
+ * Array.make(2, 4),
53494
+ * Array.make(7, 9)
53495
+ * ]).pipe(Channel.filter(nonEmptyArrayPredicate))
53496
+ * const filteredOddChannel = Channel.filterArray(oddChannel, (n) => n % 2 === 0)
53497
+ * // Outputs: [2, 4] (the arrays [1,3,5] and [7,9] are discarded)
53498
+ * ```
53499
+ *
53500
+ * @category filtering
53501
+ * @since 4.0.0
53502
+ */
53503
+ const filterArray = /* @__PURE__ */ dual(2, (self, predicate) => transformPull$1(self, (pull) => succeed$2(flatMap$2(pull, function loop(arr) {
53504
+ const passes = [];
53505
+ for (let i = 0; i < arr.length; i++) if (predicate(arr[i])) passes.push(arr[i]);
53506
+ return isReadonlyArrayNonEmpty(passes) ? succeed$2(passes) : flatMap$2(pull, loop);
53507
+ }))));
53508
+ /**
53461
53509
  * Filters and maps each element inside emitted non-empty arrays using a
53462
53510
  * `Filter`.
53463
53511
  *
@@ -55095,6 +55143,30 @@ const concat = /* @__PURE__ */ dual(2, (self, that) => flatten(fromArray([self,
55095
55143
  */
55096
55144
  const merge$1 = /* @__PURE__ */ dual((args) => isStream(args[0]) && isStream(args[1]), (self, that, options) => fromChannel(merge$2(toChannel(self), toChannel(that), options)));
55097
55145
  /**
55146
+ * Filters a stream to the elements that satisfy a predicate.
55147
+ *
55148
+ * **Example** (Filtering stream values)
55149
+ *
55150
+ * ```ts
55151
+ * import { Console, Effect, Stream } from "effect"
55152
+ *
55153
+ * const program = Effect.gen(function*() {
55154
+ * const stream = Stream.make(1, 2, 3, 4).pipe(
55155
+ * Stream.filter((n) => n % 2 === 0)
55156
+ * )
55157
+ * const values = yield* Stream.runCollect(stream)
55158
+ * yield* Console.log(values)
55159
+ * })
55160
+ *
55161
+ * Effect.runPromise(program)
55162
+ * // Output: [ 2, 4 ]
55163
+ * ```
55164
+ *
55165
+ * @category filtering
55166
+ * @since 2.0.0
55167
+ */
55168
+ const filter = /* @__PURE__ */ dual(2, (self, predicate) => fromChannel(filterArray(toChannel(self), predicate)));
55169
+ /**
55098
55170
  * Filters and maps stream elements in one pass using a `Filter`.
55099
55171
  *
55100
55172
  * @category filtering
@@ -61878,6 +61950,7 @@ const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
61878
61950
  const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
61879
61951
  const MINIFIED_SNIFF_BYTES = 65536;
61880
61952
  const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
61953
+ const GIT_CHECK_ATTR_MAX_BUFFER_BYTES = GIT_LS_FILES_MAX_BUFFER_BYTES;
61881
61954
  const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
61882
61955
  const IGNORED_DIRECTORIES = new Set([
61883
61956
  ".angular",
@@ -62163,13 +62236,9 @@ const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
62163
62236
  const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
62164
62237
  const ABORT_EXIT_CODES = new Set([134, 3221226505]);
62165
62238
  const OXLINT_OOM_RESCUE_BUDGET_MS = 6e4;
62166
- const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
62167
- const LINT_PHASE_TIMEOUT_MS = 3e5;
62168
- const SCAN_TOTAL_DEADLINE_MS = 9e5;
62169
62239
  const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
62170
62240
  const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
62171
62241
  const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
62172
- const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
62173
62242
  const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
62174
62243
  const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
62175
62244
  const REACT_SERVER_DOM_PACKAGES = [
@@ -62264,6 +62333,66 @@ const isPlainObject = (value) => {
62264
62333
  const prototype = Object.getPrototypeOf(value);
62265
62334
  return prototype === null || prototype === Object.prototype;
62266
62335
  };
62336
+ const LINGUIST_ATTRIBUTES = ["linguist-generated", "linguist-vendored"];
62337
+ const DISABLED_ATTRIBUTE_VALUES = new Set([
62338
+ "unspecified",
62339
+ "unset",
62340
+ "false",
62341
+ "0",
62342
+ "off",
62343
+ "no"
62344
+ ]);
62345
+ const buildInput = (relativePaths) => `${relativePaths.join("\0")}\0`;
62346
+ const parseIgnoredPaths = (output) => {
62347
+ const ignoredPaths = /* @__PURE__ */ new Set();
62348
+ const fields = output.split("\0");
62349
+ for (let fieldIndex = 0; fieldIndex + 2 < fields.length; fieldIndex += 3) {
62350
+ const filePath = fields[fieldIndex];
62351
+ const value = fields[fieldIndex + 2]?.toLowerCase();
62352
+ if (filePath && value && !DISABLED_ATTRIBUTE_VALUES.has(value)) ignoredPaths.add(filePath);
62353
+ }
62354
+ return ignoredPaths;
62355
+ };
62356
+ const collectGitLinguistIgnoredPaths = (rootDirectory, relativePaths) => {
62357
+ if (relativePaths.length === 0) return /* @__PURE__ */ new Set();
62358
+ const result = spawnSync("git", [
62359
+ "check-attr",
62360
+ "-z",
62361
+ "--stdin",
62362
+ ...LINGUIST_ATTRIBUTES
62363
+ ], {
62364
+ cwd: rootDirectory,
62365
+ encoding: "utf-8",
62366
+ input: buildInput(relativePaths),
62367
+ maxBuffer: GIT_CHECK_ATTR_MAX_BUFFER_BYTES
62368
+ });
62369
+ if (result.error || result.status !== 0) return /* @__PURE__ */ new Set();
62370
+ return parseIgnoredPaths(result.stdout);
62371
+ };
62372
+ const collectGitLinguistIgnoredPathsCooperative = (rootDirectory, relativePaths, signal) => {
62373
+ if (relativePaths.length === 0) return Promise.resolve(/* @__PURE__ */ new Set());
62374
+ return new Promise((resolve, reject) => {
62375
+ signal?.throwIfAborted();
62376
+ execFile("git", [
62377
+ "check-attr",
62378
+ "-z",
62379
+ "--stdin",
62380
+ ...LINGUIST_ATTRIBUTES
62381
+ ], {
62382
+ cwd: rootDirectory,
62383
+ encoding: "utf-8",
62384
+ killSignal: "SIGKILL",
62385
+ maxBuffer: GIT_CHECK_ATTR_MAX_BUFFER_BYTES,
62386
+ signal
62387
+ }, (error, stdout) => {
62388
+ if (signal?.aborted) {
62389
+ reject(signal.reason);
62390
+ return;
62391
+ }
62392
+ resolve(error ? /* @__PURE__ */ new Set() : parseIgnoredPaths(stdout));
62393
+ }).stdin?.end(buildInput(relativePaths));
62394
+ });
62395
+ };
62267
62396
  const lastSourceMappingUrl = (fileContent) => {
62268
62397
  const lastReference = [...fileContent.matchAll(/\/\/# sourceMappingURL=(\S+)/g)].at(-1);
62269
62398
  return lastReference ? lastReference[1] : null;
@@ -62368,6 +62497,9 @@ function* walkSourceTreeFiles(rootDirectory) {
62368
62497
  }
62369
62498
  }
62370
62499
  }
62500
+ const yieldToEventLoop = () => new Promise((resolve) => {
62501
+ setImmediate(resolve);
62502
+ });
62371
62503
  const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
62372
62504
  const entries = [];
62373
62505
  for (const relativePath of relativePaths) {
@@ -62381,23 +62513,55 @@ const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
62381
62513
  }
62382
62514
  return entries;
62383
62515
  };
62384
- const gitLsFiles = (rootDirectory, flags) => {
62516
+ const collectSizedSourceFilesCooperative = async (rootDirectory, relativePaths, signal) => {
62517
+ const entries = [];
62518
+ let sliceStartedAt = performance.now();
62519
+ for (const relativePath of relativePaths) {
62520
+ signal?.throwIfAborted();
62521
+ const absolutePath = Path.resolve(rootDirectory, relativePath);
62522
+ const sizeBytes = statSourceFileSize(absolutePath);
62523
+ if (!isLargeMinifiedFile(absolutePath, sizeBytes)) entries.push({
62524
+ path: relativePath,
62525
+ sizeBytes: sizeBytes ?? 0
62526
+ });
62527
+ if (performance.now() - sliceStartedAt >= 12) {
62528
+ await yieldToEventLoop();
62529
+ sliceStartedAt = performance.now();
62530
+ }
62531
+ }
62532
+ return entries;
62533
+ };
62534
+ const parseGitSourceFilePaths = (output) => {
62535
+ const trackedPaths = /* @__PURE__ */ new Set();
62536
+ const untrackedPaths = [];
62537
+ for (const entry of output.split("\0")) {
62538
+ if (entry.length === 0) continue;
62539
+ const trackedPath = /^[0-7]{6} [0-9a-f]+ [0-3]\t([\s\S]+)$/.exec(entry)?.[1];
62540
+ if (trackedPath === void 0) untrackedPaths.push(entry);
62541
+ else trackedPaths.add(trackedPath);
62542
+ }
62543
+ return {
62544
+ trackedPaths: [...trackedPaths],
62545
+ untrackedPaths
62546
+ };
62547
+ };
62548
+ const listGitSourceFilePaths = (rootDirectory) => {
62385
62549
  const result = spawnSync("git", [
62386
62550
  "ls-files",
62387
62551
  "-z",
62388
- ...flags
62552
+ "--stage",
62553
+ "--others",
62554
+ "--exclude-standard"
62389
62555
  ], {
62390
62556
  cwd: rootDirectory,
62391
62557
  encoding: "utf-8",
62392
62558
  maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES
62393
62559
  });
62394
62560
  if (result.error || result.status !== 0) return null;
62395
- return result.stdout.split("\0").filter((filePath) => filePath.length > 0);
62561
+ return parseGitSourceFilePaths(result.stdout);
62396
62562
  };
62397
- const listSourceFilesViaGit = (rootDirectory) => {
62398
- const trackedPaths = gitLsFiles(rootDirectory, ["--cached"]);
62399
- const untrackedPaths = gitLsFiles(rootDirectory, ["--others", "--exclude-standard"]);
62400
- if (trackedPaths === null || untrackedPaths === null) return null;
62563
+ const collectGitCandidateSourceFilePaths = (rootDirectory, paths) => {
62564
+ const { trackedPaths, untrackedPaths } = paths;
62401
62565
  const emitDuplicateJsPaths = collectTypeScriptEmitDuplicateJsPaths({
62402
62566
  trackedPaths: new Set(trackedPaths),
62403
62567
  untrackedPaths,
@@ -62405,12 +62569,65 @@ const listSourceFilesViaGit = (rootDirectory) => {
62405
62569
  });
62406
62570
  return [...trackedPaths, ...untrackedPaths].filter((filePath) => isLintableSourceFile(filePath) && !hasIgnoredPathSegment(filePath) && !emitDuplicateJsPaths.has(filePath));
62407
62571
  };
62572
+ const filterGitSourceFilePaths = (rootDirectory, paths) => {
62573
+ const candidatePaths = collectGitCandidateSourceFilePaths(rootDirectory, paths);
62574
+ const linguistIgnoredPaths = collectGitLinguistIgnoredPaths(rootDirectory, candidatePaths);
62575
+ return candidatePaths.filter((filePath) => !linguistIgnoredPaths.has(filePath));
62576
+ };
62577
+ const listSourceFilesViaGit = (rootDirectory) => {
62578
+ const paths = listGitSourceFilePaths(rootDirectory);
62579
+ return paths === null ? null : filterGitSourceFilePaths(rootDirectory, paths);
62580
+ };
62581
+ const listSourceFilesViaGitCooperative = async (rootDirectory, signal) => {
62582
+ const paths = await new Promise((resolve, reject) => {
62583
+ signal?.throwIfAborted();
62584
+ execFile("git", [
62585
+ "ls-files",
62586
+ "-z",
62587
+ "--stage",
62588
+ "--others",
62589
+ "--exclude-standard"
62590
+ ], {
62591
+ cwd: rootDirectory,
62592
+ encoding: "utf-8",
62593
+ killSignal: "SIGKILL",
62594
+ maxBuffer: GIT_LS_FILES_MAX_BUFFER_BYTES,
62595
+ signal
62596
+ }, (error, stdout) => {
62597
+ if (signal?.aborted) {
62598
+ reject(signal.reason);
62599
+ return;
62600
+ }
62601
+ resolve(error ? null : parseGitSourceFilePaths(stdout));
62602
+ });
62603
+ });
62604
+ if (paths === null) return null;
62605
+ const candidatePaths = collectGitCandidateSourceFilePaths(rootDirectory, paths);
62606
+ const linguistIgnoredPaths = await collectGitLinguistIgnoredPathsCooperative(rootDirectory, candidatePaths, signal);
62607
+ return candidatePaths.filter((filePath) => !linguistIgnoredPaths.has(filePath));
62608
+ };
62408
62609
  const listSourceFilesViaFilesystem = (rootDirectory) => {
62409
62610
  const filePaths = [];
62410
62611
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) if (isLintableSourceFile(name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
62411
62612
  return filePaths;
62412
62613
  };
62413
- const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
62614
+ const listSourceFilesViaFilesystemCooperative = async (rootDirectory, signal) => {
62615
+ const filePaths = [];
62616
+ let sliceStartedAt = performance.now();
62617
+ for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
62618
+ signal?.throwIfAborted();
62619
+ if (isLintableSourceFile(name)) filePaths.push(Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/"));
62620
+ if (performance.now() - sliceStartedAt >= 12) {
62621
+ await yieldToEventLoop();
62622
+ sliceStartedAt = performance.now();
62623
+ }
62624
+ }
62625
+ return filePaths;
62626
+ };
62627
+ const listSourceFilePaths = (rootDirectory) => (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort();
62628
+ const listSourceFilePathsCooperative = async (rootDirectory, signal) => (await listSourceFilesViaGitCooperative(rootDirectory, signal) ?? await listSourceFilesViaFilesystemCooperative(rootDirectory, signal)).sort();
62629
+ const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, listSourceFilePaths(rootDirectory));
62630
+ const listSourceFilesCooperative = async (rootDirectory, signal) => (await collectSizedSourceFilesCooperative(rootDirectory, await listSourceFilePathsCooperative(rootDirectory, signal), signal)).map((entry) => entry.path);
62414
62631
  const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
62415
62632
  const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
62416
62633
  const UTF8_BOM_CHARACTER = "";
@@ -64739,7 +64956,16 @@ const getBrowserslistProtectionRequirement = (directory) => {
64739
64956
  if (!queries) return void 0;
64740
64957
  let requirement;
64741
64958
  let hasUnsupportedTarget = false;
64742
- for (const browserTarget of (0, import_browserslist.default)(queries)) {
64959
+ const previousIgnoreOldData = process.env.BROWSERSLIST_IGNORE_OLD_DATA;
64960
+ let browserTargets = [];
64961
+ try {
64962
+ process.env.BROWSERSLIST_IGNORE_OLD_DATA = "true";
64963
+ browserTargets = (0, import_browserslist.default)(queries);
64964
+ } finally {
64965
+ if (previousIgnoreOldData === void 0) delete process.env.BROWSERSLIST_IGNORE_OLD_DATA;
64966
+ else process.env.BROWSERSLIST_IGNORE_OLD_DATA = previousIgnoreOldData;
64967
+ }
64968
+ for (const browserTarget of browserTargets) {
64743
64969
  const browserRequirement = getBrowserProtectionRequirement(browserTarget);
64744
64970
  if (browserRequirement === "unsupported") hasUnsupportedTarget = true;
64745
64971
  if (browserRequirement === "noreferrer") requirement = "noreferrer";
@@ -64849,8 +65075,8 @@ const clearProjectCache = () => {
64849
65075
  * framework-agnostic rules. Throws only when the directory has nothing
64850
65076
  * to scan (no enclosing project and no source files of its own).
64851
65077
  */
64852
- const discoverProjectWithoutPackageJson = (directory) => {
64853
- const sourceFileCount = countSourceFiles(directory);
65078
+ const discoverProjectWithoutPackageJson = (directory, options) => {
65079
+ const sourceFileCount = options.sourceFileCount ?? countSourceFiles(directory);
64854
65080
  const hasOwnTsConfig = NFS.existsSync(Path.join(directory, "tsconfig.json"));
64855
65081
  const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
64856
65082
  const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
@@ -64917,12 +65143,15 @@ const discoverProjectWithoutPackageJson = (directory) => {
64917
65143
  sourceFileCount
64918
65144
  };
64919
65145
  };
64920
- const discoverProject = (directory) => {
65146
+ const discoverProject = (directory, options = {}) => {
64921
65147
  const cached = cachedProjectInfos.get(directory);
64922
- if (cached !== void 0) return cached;
65148
+ if (cached !== void 0) return options.sourceFileCount === void 0 ? cached : {
65149
+ ...cached,
65150
+ sourceFileCount: options.sourceFileCount
65151
+ };
64923
65152
  const packageJsonPath = Path.join(directory, "package.json");
64924
65153
  if (!isFile(packageJsonPath)) {
64925
- const synthesized = discoverProjectWithoutPackageJson(directory);
65154
+ const synthesized = discoverProjectWithoutPackageJson(directory, options);
64926
65155
  cachedProjectInfos.set(directory, synthesized);
64927
65156
  return synthesized;
64928
65157
  }
@@ -64985,7 +65214,7 @@ const discoverProject = (directory) => {
64985
65214
  const zodVersion = zod.version;
64986
65215
  const projectName = packageJson.name ?? Path.basename(directory);
64987
65216
  const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
64988
- const sourceFileCount = countSourceFiles(directory);
65217
+ const sourceFileCount = options.sourceFileCount ?? countSourceFiles(directory);
64989
65218
  const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
64990
65219
  const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
64991
65220
  rootDirectory: directory,
@@ -66367,7 +66596,8 @@ const readSystemFacts$1 = () => ({
66367
66596
  /**
66368
66597
  * Auto lint-worker count: the smaller of the (cgroup-CPU-aware) core count and
66369
66598
  * the number of `PER_WORKER_MEM_BUDGET_BYTES` workers that fit in available
66370
- * memory, then clamped to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
66599
+ * memory, and the measured automatic parallel-efficiency ceiling, then clamped
66600
+ * to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
66371
66601
  *
66372
66602
  * `os.availableParallelism()` already respects cgroup CPU quotas, so the core
66373
66603
  * term needs no help. Available memory is `os.totalmem()` floored by the cgroup
@@ -66383,7 +66613,7 @@ const readSystemFacts$1 = () => ({
66383
66613
  const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
66384
66614
  const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
66385
66615
  const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
66386
- return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
66616
+ return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers, 10));
66387
66617
  };
66388
66618
  const resolveLintBatchOrdering = () => {
66389
66619
  return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
@@ -66396,24 +66626,22 @@ const resolveLintBatchOrdering = () => {
66396
66626
  */
66397
66627
  var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS", OXLINT_SPAWN_TIMEOUT_MS) }) {};
66398
66628
  /**
66399
- * Effect-side cap on the lint phase. The env var lets CI / eval runners
66400
- * raise the phase budget for slow large repos without recompiling.
66629
+ * Optional Effect-side cap on the lint phase. Unbounded unless explicitly
66630
+ * configured through the env var or a test layer.
66401
66631
  * Tests override via `Layer.succeed(LintPhaseTimeoutMs, ...)`.
66402
66632
  */
66403
- var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", LINT_PHASE_TIMEOUT_MS) }) {};
66633
+ var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", null) }) {};
66404
66634
  /**
66405
- * Effect-side cap on the dead-code phase, sitting above the in-worker
66406
- * timeout as a runtime-independent backstop. The env var raises it for
66407
- * type-heavy projects; tests override via
66635
+ * Optional Effect-side cap on the dead-code phase. Unbounded unless explicitly
66636
+ * configured through the env var or a test layer. Tests override via
66408
66637
  * `Layer.succeed(DeadCodePhaseTimeoutMs, ...)`.
66409
66638
  */
66410
- var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", DEAD_CODE_PHASE_TIMEOUT_MS) }) {};
66639
+ var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", null) }) {};
66411
66640
  /**
66412
- * Overall scan deadline backstop, bounding everything the per-phase
66413
- * timeouts don't (wedged git / IO). The env var raises it for very
66414
- * large repos; tests override via `Layer.succeed(ScanDeadlineMs, ...)`.
66641
+ * Optional overall scan deadline. Unbounded unless explicitly configured
66642
+ * through the env var or a test layer.
66415
66643
  */
66416
- var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", SCAN_TOTAL_DEADLINE_MS) }) {};
66644
+ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", null) }) {};
66417
66645
  /**
66418
66646
  * Wall-clock budget for the supply-chain check when it runs on a background
66419
66647
  * fiber overlapping the lint pass. Reads from the env var on startup so the
@@ -66486,14 +66714,14 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
66486
66714
  /**
66487
66715
  * How the full-scan lint pass plans its file batches. `"cost"` (the default)
66488
66716
  * builds size-balanced LPT batches (`planLintBatches`): the same mandatory
66489
- * batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
66490
- * an even share of files AND bytes, so no 100-file chunk is a straggler while
66717
+ * batch count as greedy chunking, but every batch gets an even share of files
66718
+ * AND bytes, so no full chunk is a straggler while
66491
66719
  * the remainder-batch worker idles — and the heavy files are SPREAD across
66492
66720
  * batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
66493
66721
  * lacked (it packed the heaviest files into one wave-1 straggler batch,
66494
66722
  * measurably regressing size-skewed repos, which is why it never earned the
66495
66723
  * default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
66496
- * rollback hatch to plain greedy 100-file chunking in discovery order. Tests
66724
+ * rollback hatch to plain greedy fixed-size chunking in discovery order. Tests
66497
66725
  * override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
66498
66726
  * never reach this — they pass user-scoped `includePaths` that skip discovery
66499
66727
  * and stay in arrival order; only the full-scan branch reads it.
@@ -67031,6 +67259,10 @@ const dedupeRelatedDiagnostics = (diagnostics) => {
67031
67259
  }
67032
67260
  return uniqueDiagnostics;
67033
67261
  };
67262
+ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
67263
+ const relativePath = Path.relative(parentAbsolutePath, childAbsolutePath);
67264
+ return Boolean(relativePath) && !relativePath.startsWith("..") && !Path.isAbsolute(relativePath);
67265
+ };
67034
67266
  const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
67035
67267
  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));
67036
67268
  const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
@@ -68607,7 +68839,7 @@ const readScannedFile = (candidate) => {
68607
68839
  return null;
68608
68840
  }
68609
68841
  };
68610
- function* collectSecurityScanFiles(rootDirectory) {
68842
+ function* collectSecurityScanFiles(rootDirectory, excludedDirectories = /* @__PURE__ */ new Set()) {
68611
68843
  const priorityCandidates = [];
68612
68844
  const artifactCandidates = [];
68613
68845
  const otherCandidates = [];
@@ -68622,7 +68854,7 @@ function* collectSecurityScanFiles(rootDirectory) {
68622
68854
  for (const entry of readDirectoryEntries(current.absolutePath)) {
68623
68855
  const absolutePath = Path.join(current.absolutePath, entry.name);
68624
68856
  if (entry.isDirectory()) {
68625
- if (!SKIPPED_DIRECTORY_NAMES.has(entry.name)) stack.push({
68857
+ if (!SKIPPED_DIRECTORY_NAMES.has(entry.name) && !excludedDirectories.has(Path.resolve(absolutePath))) stack.push({
68626
68858
  absolutePath,
68627
68859
  depth: current.depth + 1
68628
68860
  });
@@ -68803,10 +69035,8 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhe
68803
69035
  }
68804
69036
  return true;
68805
69037
  };
69038
+ const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
68806
69039
  const shouldEnableRuleByDefaultStatus = (options) => options.defaultEnabled !== false || options.hasExplicitOverride === true || options.includeTagDefaults && options.hasIncludedTags;
68807
- const yieldToEventLoop = () => new Promise((resolve) => {
68808
- setImmediate(resolve);
68809
- });
68810
69040
  const createSecurityScanSession = (rootDirectory, options) => {
68811
69041
  const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
68812
69042
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
@@ -68859,19 +69089,35 @@ const createSecurityScanSession = (rootDirectory, options) => {
68859
69089
  };
68860
69090
  };
68861
69091
  const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
69092
+ const throwIfScanAborted = () => {
69093
+ options.signal?.throwIfAborted();
69094
+ };
69095
+ const didReachDeadline = () => {
69096
+ const hasDeadlineElapsed = options.deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(options.deadlineEpochMs) === 0;
69097
+ if (hasDeadlineElapsed) options.onDeadlineExceeded?.();
69098
+ return hasDeadlineElapsed;
69099
+ };
69100
+ throwIfScanAborted();
68862
69101
  const session = createSecurityScanSession(rootDirectory, options);
68863
69102
  if (session === null) return [];
69103
+ if (didReachDeadline()) return [];
68864
69104
  let sliceStartedAt = performance.now();
68865
- for (const file of collectSecurityScanFiles(rootDirectory)) {
69105
+ for (const file of collectSecurityScanFiles(rootDirectory, options.excludedDirectories)) {
69106
+ throwIfScanAborted();
69107
+ if (didReachDeadline()) return session.diagnostics;
68866
69108
  if (file === null) {
68867
69109
  if (performance.now() - sliceStartedAt >= 12) {
68868
69110
  await yieldToEventLoop();
69111
+ throwIfScanAborted();
69112
+ if (didReachDeadline()) return session.diagnostics;
68869
69113
  sliceStartedAt = performance.now();
68870
69114
  }
68871
69115
  continue;
68872
69116
  }
68873
69117
  for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
68874
69118
  await yieldToEventLoop();
69119
+ throwIfScanAborted();
69120
+ if (didReachDeadline()) return session.diagnostics;
68875
69121
  sliceStartedAt = performance.now();
68876
69122
  }
68877
69123
  }
@@ -69785,7 +70031,6 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
69785
70031
  return true;
69786
70032
  };
69787
70033
  const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
69788
- const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs - Date.now(), 0);
69789
70034
  /**
69790
70035
  * Budget for the dead-code phase, scaled to the work. deslop's graph build is
69791
70036
  * CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
@@ -69793,25 +70038,28 @@ const remainingDeadlineBudgetMs = (deadlineEpochMs) => Math.max(deadlineEpochMs
69793
70038
  * tipped over by any concurrent load — silently dropping every dead-code
69794
70039
  * finding. Scaling the budget with file count (and inversely with the core
69795
70040
  * share when overlapped) lets the pass complete, while the ceiling still
69796
- * reclaims a genuinely wedged worker. Returns the in-worker SIGKILL deadline
69797
- * and the Effect-side phase backstop that sits a margin above it.
70041
+ * reclaims a genuinely wedged worker.
69798
70042
  */
69799
70043
  const resolveDeadCodeTimeout = (input) => {
69800
70044
  const coreShareFactor = Math.max(1, input.fullConcurrency / Math.max(1, input.deadCodeConcurrency));
69801
- const workerTimeoutMs = Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor)));
69802
- return {
69803
- workerTimeoutMs,
69804
- phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
69805
- };
70045
+ return { workerTimeoutMs: Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor))) };
69806
70046
  };
69807
- const resolveLintIncludePaths = (rootDirectory, userConfig) => {
70047
+ const resolveLintIncludePaths = (rootDirectory, userConfig, sourceFiles) => {
69808
70048
  if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
69809
70049
  const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
69810
- return listSourceFiles(rootDirectory).filter((filePath) => {
70050
+ return (sourceFiles ?? listSourceFiles(rootDirectory)).filter((filePath) => {
69811
70051
  if (!isLintableSourceFile(filePath)) return false;
69812
70052
  return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
69813
70053
  });
69814
70054
  };
70055
+ const filterPathsOutsideDirectories = (input) => {
70056
+ 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}/`)));
70057
+ if (excludedPrefixes.length === 0) return [...input.relativePaths];
70058
+ return input.relativePaths.filter((relativePath) => {
70059
+ const normalizedRelativePath = relativePath.replaceAll("\\", "/").replace(/^\.\//, "");
70060
+ return !excludedPrefixes.some((excludedPrefix) => normalizedRelativePath === excludedPrefix || normalizedRelativePath.startsWith(`${excludedPrefix}/`));
70061
+ });
70062
+ };
69815
70063
  var Config = class Config extends Service()("react-doctor/Config") {
69816
70064
  static layerNode = effect(Config, gen(function* () {
69817
70065
  const cache = yield* make$3({
@@ -69873,6 +70121,7 @@ var Files = class Files extends Service()("react-doctor/Files") {
69873
70121
  static layerNode = succeed$3(Files, Files.of({
69874
70122
  readLines: (input) => sync(() => createNodeReadFileLinesSync(input.rootDirectory)(input.filePath)),
69875
70123
  listSourceFiles: (rootDirectory) => sync(() => listSourceFiles(rootDirectory)),
70124
+ listSourceFilesCooperative: (input) => promise(() => listSourceFilesCooperative(input.rootDirectory, input.signal)),
69876
70125
  isFile: (filePath) => sync(() => isFile(filePath)),
69877
70126
  isDirectory: (filePath) => sync(() => isDirectory(filePath))
69878
70127
  }));
@@ -69899,6 +70148,16 @@ var Files = class Files extends Service()("react-doctor/Files") {
69899
70148
  }
69900
70149
  return files;
69901
70150
  }),
70151
+ listSourceFilesCooperative: (input) => sync(() => {
70152
+ const rootDirectory = input.rootDirectory;
70153
+ const prefix = rootDirectory.endsWith("/") ? rootDirectory : `${rootDirectory}/`;
70154
+ const files = [];
70155
+ for (const absolute of tree.keys()) {
70156
+ if (!absolute.startsWith(prefix)) continue;
70157
+ files.push(absolute.slice(prefix.length));
70158
+ }
70159
+ return files;
70160
+ }),
69902
70161
  isFile: (filePath) => sync(() => tree.has(filePath)),
69903
70162
  isDirectory: (filePath) => sync(() => {
69904
70163
  const prefix = filePath.endsWith("/") ? filePath : `${filePath}/`;
@@ -70474,7 +70733,7 @@ const batchIncludePaths = (baseArgs, includePaths) => {
70474
70733
  for (const filePath of includePaths) {
70475
70734
  const entryLength = filePath.length + 1;
70476
70735
  const exceedsArgLength = currentBatch.length > 0 && currentBatchLength + entryLength > 24e3;
70477
- const exceedsFileCount = currentBatch.length >= 100;
70736
+ const exceedsFileCount = currentBatch.length >= 200;
70478
70737
  if (exceedsArgLength || exceedsFileCount) {
70479
70738
  batches.push(currentBatch);
70480
70739
  currentBatch = [];
@@ -70821,7 +71080,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
70821
71080
  }
70822
71081
  return enabled;
70823
71082
  };
70824
- 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 }) => {
71083
+ 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 }) => {
70825
71084
  const hasIncludedTags = includedTags.size > 0;
70826
71085
  const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" || hasIncludedTags ? null : resolveReactHooksJsPlugin(project.hasReactCompiler || project.hasReactCompilerLintPlugin === true, customRulesOnly);
70827
71086
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
@@ -70885,7 +71144,8 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
70885
71144
  }))
70886
71145
  } : {},
70887
71146
  ...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
70888
- ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
71147
+ ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {},
71148
+ ...projectIndexModuleSources !== void 0 ? { projectIndexModuleSources: [...projectIndexModuleSources] } : {}
70889
71149
  } },
70890
71150
  rules: {
70891
71151
  ...reactCompilerRules,
@@ -72370,6 +72630,12 @@ const buildProfiledNodeArguments = (input) => [
72370
72630
  ...input.heapProfileDirectory ? ["--heap-prof", `--heap-prof-dir=${input.heapProfileDirectory}`] : [],
72371
72631
  ...input.argumentsList
72372
72632
  ];
72633
+ const lowerChildProcessPriority = (processId) => {
72634
+ if (processId === void 0) return;
72635
+ try {
72636
+ OS.setPriority(processId, OS.constants.priority.PRIORITY_BELOW_NORMAL);
72637
+ } catch {}
72638
+ };
72373
72639
  const SANITIZED_ENV = buildOxlintChildEnv(process.env);
72374
72640
  /**
72375
72641
  * Spawn one oxlint subprocess with hard ceilings on wall time and
@@ -72408,6 +72674,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
72408
72674
  "pipe"
72409
72675
  ]
72410
72676
  });
72677
+ lowerChildProcessPriority(child.pid);
72411
72678
  const onAbort = () => {
72412
72679
  child.kill("SIGKILL");
72413
72680
  reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
@@ -72530,7 +72797,7 @@ const isParallelismRelatedSpawnError = (error) => {
72530
72797
  * loop with a slimmer config in that case.
72531
72798
  */
72532
72799
  const spawnLintBatches = async (input) => {
72533
- const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, sourceMapByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
72800
+ const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, sourceMapByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 9, signal, deadlineEpochMs } = input;
72534
72801
  const resolveSourcePath = (filePath) => {
72535
72802
  const absoluteFilePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory, filePath);
72536
72803
  return sourcePathByLintPath?.get(Path.normalize(absoluteFilePath)) ?? filePath;
@@ -72538,6 +72805,12 @@ const spawnLintBatches = async (input) => {
72538
72805
  const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
72539
72806
  let rescueDeadlineEpochMs;
72540
72807
  const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0 || rescueDeadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(rescueDeadlineEpochMs) === 0;
72808
+ const resolveSpawnTimeoutMs = () => {
72809
+ const deadlineBudgets = [deadlineEpochMs, rescueDeadlineEpochMs].filter((deadline) => deadline !== void 0).map(remainingDeadlineBudgetMs);
72810
+ if (deadlineBudgets.length === 0) return spawnTimeoutMs;
72811
+ const remainingDeadlineMs = Math.min(...deadlineBudgets);
72812
+ return spawnTimeoutMs === void 0 ? remainingDeadlineMs : Math.min(spawnTimeoutMs, remainingDeadlineMs);
72813
+ };
72541
72814
  const runBatchPass = async (concurrency, passBatches, passOnFileProgress) => {
72542
72815
  const totalFileCount = passBatches.reduce((sum, batch) => sum + batch.length, 0);
72543
72816
  const allDiagnostics = [];
@@ -72555,12 +72828,13 @@ const spawnLintBatches = async (input) => {
72555
72828
  const batchArgs = [...baseArgs, ...batch];
72556
72829
  try {
72557
72830
  const spawnBatch = () => {
72558
- if (isPastDeadline()) {
72831
+ const effectiveSpawnTimeoutMs = resolveSpawnTimeoutMs();
72832
+ if (effectiveSpawnTimeoutMs === 0) {
72559
72833
  deadlineSkippedFiles.push(...batch);
72560
72834
  batchState.deadlineSkippedFileCount += batch.length;
72561
72835
  return Promise.resolve(null);
72562
72836
  }
72563
- return spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal, () => {
72837
+ return spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, effectiveSpawnTimeoutMs, outputMaxBytes, signal, () => {
72564
72838
  if (batchState.didStart) return;
72565
72839
  batchState.didStart = true;
72566
72840
  startedFileCount += batchState.initialFileCount;
@@ -72715,11 +72989,36 @@ const validateRuleRegistration = () => {
72715
72989
  ].filter((entry) => entry !== null).join("; ");
72716
72990
  console.warn(`[react-doctor] rule-registration drift: ${detail}`);
72717
72991
  };
72992
+ const PROJECT_INDEX_MODULE_SOURCES = [
72993
+ "next/og",
72994
+ "@vercel/og",
72995
+ "satori",
72996
+ "remotion"
72997
+ ];
72998
+ const collectProjectIndexModuleSources = async (rootDirectory, candidateFiles) => {
72999
+ const foundModuleSources = /* @__PURE__ */ new Set();
73000
+ let sliceStartedAt = performance.now();
73001
+ for (const candidateFile of candidateFiles) {
73002
+ let sourceBuffer;
73003
+ try {
73004
+ sourceBuffer = NFS.readFileSync(Path.isAbsolute(candidateFile) ? candidateFile : Path.resolve(rootDirectory, candidateFile));
73005
+ } catch {
73006
+ continue;
73007
+ }
73008
+ for (const moduleSource of PROJECT_INDEX_MODULE_SOURCES) if (sourceBuffer.includes(`"${moduleSource}"`) || sourceBuffer.includes(`'${moduleSource}'`)) foundModuleSources.add(moduleSource);
73009
+ if (foundModuleSources.size === PROJECT_INDEX_MODULE_SOURCES.length) break;
73010
+ if (performance.now() - sliceStartedAt >= 12) {
73011
+ await yieldToEventLoop();
73012
+ sliceStartedAt = performance.now();
73013
+ }
73014
+ }
73015
+ return [...foundModuleSources].sort();
73016
+ };
72718
73017
  /**
72719
73018
  * Balanced LPT (longest-processing-time-first) lint batch planner. Where
72720
- * `batchIncludePaths` greedily fills 100-file batches and leaves a small
72721
- * remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
72722
- * is set by whichever 100-file chunk happened to collect the most work while
73019
+ * `batchIncludePaths` greedily fills fixed-size batches and leaves a small
73020
+ * remainder batch, so the parallel pool's wall clock is set by whichever
73021
+ * full chunk happened to collect the most work while
72723
73022
  * the remainder's worker idles), this planner keeps the SAME mandatory batch
72724
73023
  * count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
72725
73024
  * perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
@@ -72748,7 +73047,7 @@ const validateRuleRegistration = () => {
72748
73047
  */
72749
73048
  const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
72750
73049
  if (files.length === 0) return [];
72751
- const batchCount = Math.ceil(files.length / 100);
73050
+ const batchCount = Math.ceil(files.length / 200);
72752
73051
  const maxFilesPerBatch = Math.ceil(files.length / batchCount);
72753
73052
  const baseArgsLengthChars = estimateArgsLength(baseArgs);
72754
73053
  const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
@@ -73277,6 +73576,7 @@ const runOxlint = async (options) => {
73277
73576
  await yieldToEventLoop();
73278
73577
  const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
73279
73578
  const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
73579
+ const projectIndexModuleSources = includePaths === void 0 && options.deadlineEpochMs === void 0 ? await collectProjectIndexModuleSources(rootDirectory, candidateFiles) : void 0;
73280
73580
  unpluginAutoImportGlobalScopes = collectUnpluginAutoImportGlobalScopes({
73281
73581
  rootDirectory,
73282
73582
  candidateFiles
@@ -73299,6 +73599,7 @@ const runOxlint = async (options) => {
73299
73599
  runtimeGlobals,
73300
73600
  unpluginAutoImportGlobalScopes,
73301
73601
  serverAuthFunctionNames,
73602
+ projectIndexModuleSources,
73302
73603
  severityControls,
73303
73604
  userPlugins,
73304
73605
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
@@ -73766,10 +74067,10 @@ const translateProjectInfoError = (cause, directory) => {
73766
74067
  }) });
73767
74068
  };
73768
74069
  var Project = class Project extends Service()("react-doctor/Project") {
73769
- static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (directory) {
74070
+ static layerNode = succeed$3(Project, Project.of({ discover: fn("Project.discover")(function* (input) {
73770
74071
  return yield* try_({
73771
- try: () => discoverProject(directory),
73772
- catch: (cause) => translateProjectInfoError(cause, directory)
74072
+ try: () => discoverProject(input.directory, { sourceFileCount: input.sourceFileCount }),
74073
+ catch: (cause) => translateProjectInfoError(cause, input.directory)
73773
74074
  });
73774
74075
  }) }));
73775
74076
  static layerOf = (projectInfo) => succeed$3(Project, Project.of({ discover: () => succeed$2(projectInfo) }));
@@ -74354,9 +74655,10 @@ const checkSupplyChain = (input) => gen(function* () {
74354
74655
  * outage.
74355
74656
  */
74356
74657
  var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
74357
- static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
74358
- ...input,
74359
- totalTimeoutMs
74658
+ static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (configuredTimeoutMs) => checkSupplyChain({
74659
+ rootDirectory: input.rootDirectory,
74660
+ userConfig: input.userConfig,
74661
+ totalTimeoutMs: input.timeoutMs ?? configuredTimeoutMs
74360
74662
  })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
74361
74663
  static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
74362
74664
  };
@@ -74391,10 +74693,6 @@ const NO_HOOKS = {
74391
74693
  beforeLint: () => void_,
74392
74694
  afterLint: () => void_
74393
74695
  };
74394
- const filterMapNullable = (transform) => fromPredicateOption((value) => {
74395
- const result = transform(value);
74396
- return result === null ? none() : some(result);
74397
- });
74398
74696
  const fileReader = (filesService, rootDirectory) => (filePath) => {
74399
74697
  const lines = runSync(filesService.readLines({
74400
74698
  filePath,
@@ -74418,7 +74716,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
74418
74716
  * The GitHub viewer-permission lookup is forked onto a background
74419
74717
  * fiber here and joined late (it feeds score metadata, not
74420
74718
  * diagnostics).
74421
- * 2. beforeLint hook (e.g. CLI renders the project-detection block)
74719
+ * 2. beforeLint hook (e.g. CLI records project telemetry)
74422
74720
  * 3. environment checks (reduced-motion + pnpm hardening +
74423
74721
  * expo/react-native), collected synchronously. The heavier
74424
74722
  * content-regex security scan is forked instead (like supply-chain
@@ -74431,13 +74729,11 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
74431
74729
  * `SupplyChainOverlapTimeoutMs` (measured from fork) so a hung
74432
74730
  * socket can't drag out its join; on timeout it fails open to no
74433
74731
  * diagnostics — the same outcome class as a Socket outage.
74434
- * 5. Linter.run runs; DeadCode.run runs concurrently (forked child
74435
- * fiber) ONLY when the memory gate has headroom to run the 8 GB
74436
- * dead-code child alongside the oxlint workers — or when overlap is
74437
- * forced via REACT_DOCTOR_DEAD_CODE_OVERLAP. Otherwise dead-code
74438
- * runs sequentially after lint, exactly as it did pre-overlap. The
74439
- * fiber is joined (or interrupted, SIGKILLing its worker, on lint
74440
- * failure) before diagnostics are concatenated. The afterLint hook
74732
+ * 5. Linter.run runs; DeadCode.run forks only when
74733
+ * REACT_DOCTOR_DEAD_CODE_OVERLAP=on. The default "auto" and explicit
74734
+ * "off" paths stay sequential. The fiber is joined (or interrupted,
74735
+ * SIGKILLing its worker, on lint failure) before diagnostics are
74736
+ * concatenated. The afterLint hook
74441
74737
  * fires between lint and dead-code. Progress spinner labels AND the
74442
74738
  * final diagnostic / score order stay independent of execution
74443
74739
  * order, so terminal output is identical either way; supply-chain
@@ -74472,8 +74768,28 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74472
74768
  const partialFailuresRef = yield* LintPartialFailures;
74473
74769
  const resolvedConfig = yield* configService.resolve(input.directory);
74474
74770
  const scanDirectory = resolvedConfig.resolvedDirectory;
74475
- const project = yield* projectService.discover(scanDirectory);
74771
+ const excludedProjectDirectories = (input.excludedProjectDirectories ?? []).map((excludedDirectory) => Path.resolve(excludedDirectory)).filter((excludedDirectory) => isPathInsideDirectory(excludedDirectory, scanDirectory));
74772
+ const excludedProjectRelativePaths = excludedProjectDirectories.map((excludedDirectory) => Path.relative(scanDirectory, excludedDirectory).replaceAll("\\", "/"));
74773
+ const isExcludedProjectDiagnostic = (diagnostic) => {
74774
+ const relativeFilePath = toNormalizedRelativePath(diagnostic.filePath, scanDirectory);
74775
+ return excludedProjectRelativePaths.some((excludedRelativePath) => relativeFilePath === excludedRelativePath || relativeFilePath.startsWith(`${excludedRelativePath}/`));
74776
+ };
74777
+ const precomputedSourceFiles = input.suppressScanSummary === true || excludedProjectDirectories.length > 0 ? yield* filesService.listSourceFilesCooperative({
74778
+ rootDirectory: scanDirectory,
74779
+ signal: input.signal
74780
+ }) : null;
74781
+ const includedPrecomputedSourceFiles = precomputedSourceFiles !== null && excludedProjectDirectories.length > 0 ? filterPathsOutsideDirectories({
74782
+ rootDirectory: scanDirectory,
74783
+ relativePaths: precomputedSourceFiles,
74784
+ excludedDirectories: excludedProjectDirectories
74785
+ }) : precomputedSourceFiles;
74786
+ const sourceFileCount = excludedProjectDirectories.length > 0 ? includedPrecomputedSourceFiles?.length : input.precomputedSourceFileCount ?? includedPrecomputedSourceFiles?.length;
74787
+ const project = yield* projectService.discover({
74788
+ directory: scanDirectory,
74789
+ sourceFileCount
74790
+ });
74476
74791
  if (!isAnalyzableProject(project)) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
74792
+ const deadCodeSourceFileCount = input.retainExcludedProjectDeadCodeDiagnostics === true && precomputedSourceFiles !== null ? precomputedSourceFiles.length : project.sourceFileCount;
74477
74793
  const [repo, sha, defaultBranch] = yield* all([
74478
74794
  gitService.githubRepo(scanDirectory).pipe(orElseSucceed(() => null)),
74479
74795
  gitService.headSha(scanDirectory).pipe(orElseSucceed(() => null)),
@@ -74484,11 +74800,16 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74484
74800
  directory: scanDirectory,
74485
74801
  repo
74486
74802
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
74487
- const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
74488
- const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
74803
+ let lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, includedPrecomputedSourceFiles ?? void 0);
74804
+ if (excludedProjectDirectories.length > 0) lintIncludePaths = filterPathsOutsideDirectories({
74805
+ rootDirectory: scanDirectory,
74806
+ relativePaths: lintIncludePaths ?? includedPrecomputedSourceFiles ?? (yield* filesService.listSourceFiles(scanDirectory)),
74807
+ excludedDirectories: excludedProjectDirectories
74808
+ });
74809
+ const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? includedPrecomputedSourceFiles ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
74489
74810
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
74490
74811
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
74491
- yield* beforeLint(project, lintIncludePaths ?? void 0);
74812
+ yield* beforeLint(project);
74492
74813
  const isDiffMode = input.includePaths.length > 0;
74493
74814
  const showWarnings = input.warnings ?? resolvedConfig.config?.warnings ?? true;
74494
74815
  const transform = buildDiagnosticPipeline({
@@ -74498,7 +74819,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74498
74819
  respectInlineDisables: input.respectInlineDisables,
74499
74820
  showWarnings
74500
74821
  });
74501
- const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(filterMapNullable(transform.apply)));
74822
+ const filterPerElementPipeline = (rawStream) => rawStream.pipe(filterMap(fromPredicateOption((diagnostic) => {
74823
+ const filteredDiagnostic = transform.apply(diagnostic);
74824
+ return filteredDiagnostic === null ? none() : some(filteredDiagnostic);
74825
+ })));
74502
74826
  const applyPerElementPipeline = (rawStream) => filterPerElementPipeline(rawStream).pipe(tap((diagnostic) => reporterService.emit(diagnostic)));
74503
74827
  const envCollected = yield* runCollect(applyPerElementPipeline(fromIterable$1(isDiffMode ? [] : [
74504
74828
  ...checkReducedMotion(scanDirectory),
@@ -74508,17 +74832,31 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74508
74832
  ...checkReactNativeProject(scanDirectory, project)
74509
74833
  ])));
74510
74834
  const securityScanFailedRef = yield* make$13(false);
74511
- const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
74835
+ let didSecurityScanReachDeadline = false;
74836
+ const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise((signal) => checkSecurityScanCooperative(scanDirectory, {
74512
74837
  project,
74513
74838
  ignoredTags: input.ignoredTags,
74514
74839
  includedTags: input.includedTags,
74515
- includeTagDefaults: input.includeTagDefaults
74840
+ includeTagDefaults: input.includeTagDefaults,
74841
+ excludedDirectories: new Set(excludedProjectDirectories),
74842
+ deadlineEpochMs: input.deadlineEpochMs,
74843
+ signal,
74844
+ onDeadlineExceeded: () => {
74845
+ didSecurityScanReachDeadline = true;
74846
+ }
74516
74847
  })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
74848
+ const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
74849
+ const capOptionalToDeadline = (phaseTimeoutMs) => {
74850
+ if (input.deadlineEpochMs === void 0) return phaseTimeoutMs;
74851
+ const remainingBudgetMs = remainingDeadlineBudgetMs(input.deadlineEpochMs);
74852
+ return phaseTimeoutMs === null ? remainingBudgetMs : Math.min(phaseTimeoutMs, remainingBudgetMs);
74853
+ };
74517
74854
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
74518
- const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
74855
+ const supplyChainOverlapTimeout = capToDeadline(yield* SupplyChainOverlapTimeoutMs);
74519
74856
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
74520
74857
  rootDirectory: scanDirectory,
74521
- userConfig: resolvedConfig.config
74858
+ userConfig: resolvedConfig.config,
74859
+ timeoutMs: supplyChainOverlapTimeout
74522
74860
  }))).pipe(map$3((diagnostics) => ({
74523
74861
  diagnostics,
74524
74862
  timedOut: false
@@ -74542,46 +74880,49 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74542
74880
  const scanConcurrency = resolveScanConcurrency(yield* OxlintConcurrency);
74543
74881
  const lintPhaseTimeoutMs = yield* LintPhaseTimeoutMs;
74544
74882
  const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
74545
- const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
74546
74883
  const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
74547
- const capToDeadline = (phaseTimeoutMs) => input.deadlineEpochMs === void 0 ? phaseTimeoutMs : Math.min(phaseTimeoutMs, remainingDeadlineBudgetMs(input.deadlineEpochMs));
74548
74884
  const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
74549
74885
  const deadCodeOverlapMode = yield* DeadCodeOverlap;
74550
74886
  const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
74551
74887
  const deadCodeParseConcurrency = shouldOverlapDeadCode ? Math.max(1, Math.floor(scanConcurrency * DEAD_CODE_OVERLAP_PARSE_SHARE)) : void 0;
74552
74888
  const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
74553
- const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
74554
- rootDirectory: scanDirectory,
74555
- parseConcurrency: deadCodeParseConcurrency,
74556
- workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
74557
- onCacheOutcome: (didHitCache) => {
74558
- deadCodeCacheHit = didHitCache;
74559
- },
74560
- onSummaryCacheStats: (stats) => {
74561
- deadCodeSummaryCacheHits = stats.hits;
74562
- deadCodeSummaryCacheMisses = stats.misses;
74563
- }
74564
- }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
74565
- yield* set(deadCodeFailure, {
74566
- didFail: true,
74567
- reason: error.message
74568
- });
74569
- return empty$4;
74570
- })))))).pipe(timeoutOption(deadCodeTimeout.phaseTimeoutMs), flatMap$2(match$3({
74571
- onNone: () => set(deadCodeFailure, {
74572
- didFail: true,
74573
- reason: `Dead-code analysis exceeded ${Math.round(deadCodeTimeout.phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
74574
- }).pipe(as([])),
74575
- onSome: succeed$2
74576
- })));
74889
+ const buildCollectDeadCode = (deadCodeTimeout) => {
74890
+ const collectDeadCode = runCollect(applyPerElementPipeline(deadCodeService.run({
74891
+ rootDirectory: scanDirectory,
74892
+ parseConcurrency: deadCodeParseConcurrency,
74893
+ workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
74894
+ onCacheOutcome: (didHitCache) => {
74895
+ deadCodeCacheHit = didHitCache;
74896
+ },
74897
+ onSummaryCacheStats: (stats) => {
74898
+ deadCodeSummaryCacheHits = stats.hits;
74899
+ deadCodeSummaryCacheMisses = stats.misses;
74900
+ }
74901
+ }).pipe(filter((diagnostic) => input.retainExcludedProjectDeadCodeDiagnostics === true || !isExcludedProjectDiagnostic(diagnostic)), catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
74902
+ yield* set(deadCodeFailure, {
74903
+ didFail: true,
74904
+ reason: error.message
74905
+ });
74906
+ return empty$4;
74907
+ }))))));
74908
+ const phaseTimeoutMs = deadCodeTimeout.phaseTimeoutMs;
74909
+ if (phaseTimeoutMs === null) return collectDeadCode;
74910
+ return collectDeadCode.pipe(timeoutOption(phaseTimeoutMs), flatMap$2(match$3({
74911
+ onNone: () => set(deadCodeFailure, {
74912
+ didFail: true,
74913
+ reason: `Dead-code analysis exceeded ${Math.round(phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
74914
+ }).pipe(as([])),
74915
+ onSome: succeed$2
74916
+ })));
74917
+ };
74577
74918
  const overlapDeadCodeTimeout = resolveDeadCodeTimeout({
74578
- sourceFileCount: project.sourceFileCount,
74919
+ sourceFileCount: deadCodeSourceFileCount,
74579
74920
  deadCodeConcurrency: deadCodeParseConcurrency ?? scanConcurrency,
74580
74921
  fullConcurrency: scanConcurrency
74581
74922
  });
74582
74923
  const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
74583
74924
  workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
74584
- phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs))
74925
+ phaseTimeoutMs: capOptionalToDeadline(deadCodePhaseTimeoutMs)
74585
74926
  })) : null;
74586
74927
  const scanProgress = yield* progressService.start("Scanning...");
74587
74928
  const scanStartTime = Date.now();
@@ -74632,7 +74973,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74632
74973
  });
74633
74974
  return empty$4;
74634
74975
  }))));
74635
- const lintCollected = dedupeRelatedDiagnostics(yield* runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
74976
+ const collectLintDiagnostics = runCollect(filterPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream));
74977
+ const lintCollected = dedupeRelatedDiagnostics(yield* lintPhaseTimeoutMs === null ? collectLintDiagnostics : collectLintDiagnostics.pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
74636
74978
  onNone: () => set(lintFailure, {
74637
74979
  didFail: true,
74638
74980
  reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
@@ -74662,13 +75004,13 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74662
75004
  } else {
74663
75005
  yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
74664
75006
  const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
74665
- sourceFileCount: totalFileCount,
75007
+ sourceFileCount: input.retainExcludedProjectDeadCodeDiagnostics === true ? deadCodeSourceFileCount : totalFileCount,
74666
75008
  deadCodeConcurrency: scanConcurrency,
74667
75009
  fullConcurrency: scanConcurrency
74668
75010
  });
74669
75011
  deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
74670
75012
  workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
74671
- phaseTimeoutMs: capToDeadline(resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs))
75013
+ phaseTimeoutMs: capOptionalToDeadline(deadCodePhaseTimeoutMs)
74672
75014
  });
74673
75015
  }
74674
75016
  const deadCodeFailureState = lintFailureState.didFail ? {
@@ -74712,7 +75054,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74712
75054
  metadata: scoreMetadata
74713
75055
  });
74714
75056
  const lintPartialFailures = yield* get$2(partialFailuresRef);
74715
- const securityScanFailed = yield* get$2(securityScanFailedRef);
75057
+ const didSecurityScanFail = yield* get$2(securityScanFailedRef);
75058
+ const securityScanFailed = didSecurityScanFail || didSecurityScanReachDeadline;
75059
+ let securityScanFailureReason = null;
75060
+ if (didSecurityScanReachDeadline) securityScanFailureReason = "Security scan reached the max scan duration; findings collected before the deadline were preserved.";
75061
+ else if (didSecurityScanFail) securityScanFailureReason = "Security scan failed and was skipped.";
74716
75062
  return {
74717
75063
  project,
74718
75064
  userConfig: resolvedConfig.config,
@@ -74735,6 +75081,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74735
75081
  scanConcurrency,
74736
75082
  supplyChainOverlapTimedOut: supplyChainResult.timedOut,
74737
75083
  securityScanFailed,
75084
+ securityScanFailureReason,
74738
75085
  lintCacheHitFileCount,
74739
75086
  lintCacheTotalFileCount,
74740
75087
  lintSidecarReplayedFileCount,
@@ -74750,7 +75097,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
74750
75097
  "inspect.runDeadCode": input.runDeadCode,
74751
75098
  "inspect.isCi": input.isCi,
74752
75099
  "inspect.scoreSurface": input.scoreSurface ?? "score"
74753
- } }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })))));
75100
+ } }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => {
75101
+ if (scanDeadlineMs === null) return scanProgram;
75102
+ return scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })));
75103
+ }));
74754
75104
  const parseNodeVersion = (versionString) => {
74755
75105
  const [major = 0, minor = 0, patch = 0] = versionString.replace(/^v/, "").trim().split(".").map(Number);
74756
75106
  return {
@@ -74851,16 +75201,6 @@ const resolveNodeForOxlint = () => {
74851
75201
  });
74852
75202
  });
74853
75203
  /**
74854
- * Zip-Slip defense: relative paths come from git (`diff --name-only`), which
74855
- * normalizes during ordinary adds, but a crafted index/pack/symlinked tree can
74856
- * smuggle `..` segments that escape the temp root. Resolve against the temp dir
74857
- * and reject anything that lands outside before writing.
74858
- */
74859
- const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
74860
- const relative = path.relative(parentAbsolutePath, childAbsolutePath);
74861
- return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
74862
- };
74863
- /**
74864
75204
  * Writes a set of source files (supplied by `readContent` — e.g.
74865
75205
  * `git show <ref>:<path>` for a baseline tree, or `git show :<path>` for the
74866
75206
  * index) into a temp tree that mirrors the project layout, copying the
@@ -75135,6 +75475,7 @@ const buildJsonReport = (input) => {
75135
75475
  directory: input.directory,
75136
75476
  diff: toJsonDiff(input.diff),
75137
75477
  projects,
75478
+ ...input.skippedProjects && input.skippedProjects.length > 0 ? { skippedProjects: [...input.skippedProjects] } : {},
75138
75479
  diagnostics: flattenedDiagnostics,
75139
75480
  summary,
75140
75481
  elapsedMilliseconds: input.totalElapsedMilliseconds,
@@ -75159,7 +75500,7 @@ const buildSkippedChecks = (input) => {
75159
75500
  else if (input.lintPartialFailures.length > 0) skippedCheckReasons["lint:partial"] = input.lintPartialFailures.join("; ");
75160
75501
  if (input.didDeadCodeFail && input.deadCodeFailureReason !== null) skippedCheckReasons["dead-code"] = input.deadCodeFailureReason;
75161
75502
  if (input.supplyChainOverlapTimedOut) skippedCheckReasons["supply-chain"] = "Supply-chain analysis timed out and was skipped.";
75162
- if (input.securityScanFailed) skippedCheckReasons["security-scan"] = "Security scan failed and was skipped.";
75503
+ if (input.securityScanFailed) skippedCheckReasons["security-scan"] = input.securityScanFailureReason ?? "Security scan failed and was skipped.";
75163
75504
  return {
75164
75505
  skippedChecks,
75165
75506
  skippedCheckReasons
@@ -75194,14 +75535,6 @@ const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runProm
75194
75535
  };
75195
75536
  }).pipe(provide(Git.layerNode)));
75196
75537
  const filterSourceFiles = (filePaths) => filePaths.filter(isLintableSourceFile);
75197
- /**
75198
- * Back-compat alias: the streaming pipeline holds its caches in
75199
- * per-pipeline closures that are garbage-collected when the pipeline
75200
- * goes out of scope, so there is nothing to clear at module scope. The
75201
- * public CLI's `clearCaches()` still calls this for symmetry with the
75202
- * other `clear*` helpers.
75203
- */
75204
- const clearAutoSuppressionCaches = () => {};
75205
75538
  const createLintPhaseAbortError = () => new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) });
75206
75539
  const createOxlintSpawnSlots = (concurrency) => createWorkerSlots({
75207
75540
  slotCount: resolveScanConcurrency(concurrency),
@@ -75460,7 +75793,6 @@ const clearCaches = () => {
75460
75793
  clearPackageJsonCache();
75461
75794
  clearIgnorePatternsCache();
75462
75795
  clearPackageRoleCache();
75463
- clearAutoSuppressionCaches();
75464
75796
  clearMinifiedFileCache();
75465
75797
  };
75466
75798
  const toJsonReport = (result, options) => buildJsonReport({
@@ -75487,4 +75819,4 @@ const toJsonReport = (result, options) => buildJsonReport({
75487
75819
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
75488
75820
 
75489
75821
  //# sourceMappingURL=index.js.map
75490
- //# debugId=0c9da9c7-4847-5807-983e-a57b7eec97cd
75822
+ //# debugId=76d09448-ef0d-52d4-8c22-005bcb8715c5