react-doctor 0.5.8-dev.31c0657 → 0.5.8-dev.5774deb

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.
Files changed (4) hide show
  1. package/dist/cli.js +299 -91
  2. package/dist/index.js +179 -66
  3. package/dist/lsp.js +180 -66
  4. package/package.json +2 -2
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]="33508ee6-c977-5b5f-8585-9939928ce74d")}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]="b9d1a98e-f5ec-5357-a08b-f84864fdfa20")}catch(e){}}();
3
3
  import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import * as NFS from "node:fs";
@@ -5996,7 +5996,7 @@ const composePassthrough = /* @__PURE__ */ dual(2, (left, right) => (input) => {
5996
5996
  * @since 2.0.0
5997
5997
  */
5998
5998
  const Scheduler = /* @__PURE__ */ Reference("effect/Scheduler", { defaultValue: () => new MixedScheduler() });
5999
- const setImmediate = "setImmediate" in globalThis ? (f) => {
5999
+ const setImmediate$1 = "setImmediate" in globalThis ? (f) => {
6000
6000
  const timer = globalThis.setImmediate(f);
6001
6001
  return () => globalThis.clearImmediate(timer);
6002
6002
  } : (f) => {
@@ -6040,7 +6040,7 @@ var PriorityBuckets = class {
6040
6040
  var MixedScheduler = class {
6041
6041
  executionMode;
6042
6042
  setImmediate;
6043
- constructor(executionMode = "async", setImmediateFn = setImmediate) {
6043
+ constructor(executionMode = "async", setImmediateFn = setImmediate$1) {
6044
6044
  this.executionMode = executionMode;
6045
6045
  this.setImmediate = setImmediateFn;
6046
6046
  }
@@ -6065,7 +6065,7 @@ var MixedSchedulerDispatcher = class {
6065
6065
  tasks = /* @__PURE__ */ new PriorityBuckets();
6066
6066
  running = void 0;
6067
6067
  setImmediate;
6068
- constructor(setImmediateFn = setImmediate) {
6068
+ constructor(setImmediateFn = setImmediate$1) {
6069
6069
  this.setImmediate = setImmediateFn;
6070
6070
  }
6071
6071
  /**
@@ -33694,6 +33694,7 @@ const MILLISECONDS_PER_SECOND = 1e3;
33694
33694
  const SCORE_API_URL = "https://www.react.doctor/api/score";
33695
33695
  const FETCH_TIMEOUT_MS = 1e4;
33696
33696
  const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
33697
+ const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
33697
33698
  const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
33698
33699
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
33699
33700
  const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
@@ -33751,6 +33752,7 @@ const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
33751
33752
  const LINT_PHASE_TIMEOUT_MS = 3e5;
33752
33753
  const SCAN_TOTAL_DEADLINE_MS = 9e5;
33753
33754
  const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
33755
+ const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
33754
33756
  const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
33755
33757
  const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
33756
33758
  const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
@@ -34487,7 +34489,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
34487
34489
  NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
34488
34490
  }
34489
34491
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
34490
- const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
34492
+ const canonicalizeRuleKey = (ruleKey) => {
34493
+ const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
34494
+ return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
34495
+ };
34491
34496
  const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
34492
34497
  const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
34493
34498
  const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
@@ -35365,7 +35370,7 @@ const resolveScanConcurrency = (requested) => {
35365
35370
  if (!Number.isFinite(requested) || requested < 1) return 1;
35366
35371
  return Math.min(Math.floor(requested), 32);
35367
35372
  };
35368
- const readSystemFacts = () => ({
35373
+ const readSystemFacts$1 = () => ({
35369
35374
  availableCores: os.availableParallelism(),
35370
35375
  totalMemoryBytes: os.totalmem(),
35371
35376
  cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
@@ -35386,7 +35391,7 @@ const readSystemFacts = () => ({
35386
35391
  * `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
35387
35392
  * limited, and ceiling cases without mocking `os` or the filesystem.
35388
35393
  */
35389
- const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
35394
+ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
35390
35395
  const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
35391
35396
  const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
35392
35397
  return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
@@ -35577,6 +35582,12 @@ const BOOLEAN_FIELD_NAMES = [
35577
35582
  "adoptExistingLintConfig"
35578
35583
  ];
35579
35584
  const STRING_FIELD_NAMES = ["rootDir"];
35585
+ const STRING_ARRAY_FIELD_NAMES = [
35586
+ "projects",
35587
+ "textComponents",
35588
+ "rawTextWrapperComponents",
35589
+ "serverAuthFunctionNames"
35590
+ ];
35580
35591
  const SURFACE_CONTROL_FIELD_NAMES = [
35581
35592
  "includeTags",
35582
35593
  "excludeTags",
@@ -35678,6 +35689,7 @@ const validateConfigTypes = (config) => {
35678
35689
  const validated = { ...config };
35679
35690
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
35680
35691
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
35692
+ for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
35681
35693
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
35682
35694
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
35683
35695
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -36912,7 +36924,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
36912
36924
  }
36913
36925
  return true;
36914
36926
  };
36915
- const checkSecurityScan = (rootDirectory, options = {}) => {
36927
+ const yieldToEventLoop = () => new Promise((resolve) => {
36928
+ setImmediate(resolve);
36929
+ });
36930
+ const createSecurityScanSession = (rootDirectory, options) => {
36916
36931
  const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
36917
36932
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
36918
36933
  const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
@@ -36927,7 +36942,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
36927
36942
  committedFilesOnly: rule.committedFilesOnly === true
36928
36943
  }];
36929
36944
  });
36930
- if (enabledScanRules.length === 0) return [];
36945
+ if (enabledScanRules.length === 0) return null;
36931
36946
  const diagnostics = [];
36932
36947
  const seen = /* @__PURE__ */ new Set();
36933
36948
  const gitIgnoredCache = /* @__PURE__ */ new Map();
@@ -36939,15 +36954,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
36939
36954
  }
36940
36955
  return status === true;
36941
36956
  };
36942
- for (const file of collectSecurityScanFiles(rootDirectory)) for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
36943
- if (committedFilesOnly && isFileGitIgnored(file)) continue;
36944
- const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
36945
- const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
36946
- if (seen.has(key)) continue;
36947
- seen.add(key);
36948
- diagnostics.push(diagnostic);
36957
+ const scanFile = (file) => {
36958
+ for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
36959
+ if (committedFilesOnly && isFileGitIgnored(file)) continue;
36960
+ const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
36961
+ const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
36962
+ if (seen.has(key)) continue;
36963
+ seen.add(key);
36964
+ diagnostics.push(diagnostic);
36965
+ }
36966
+ };
36967
+ return {
36968
+ scanFile,
36969
+ diagnostics
36970
+ };
36971
+ };
36972
+ const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
36973
+ const session = createSecurityScanSession(rootDirectory, options);
36974
+ if (session === null) return [];
36975
+ let filesSinceYield = 0;
36976
+ for (const file of collectSecurityScanFiles(rootDirectory)) {
36977
+ session.scanFile(file);
36978
+ filesSinceYield += 1;
36979
+ if (filesSinceYield >= 16) {
36980
+ filesSinceYield = 0;
36981
+ await yieldToEventLoop();
36982
+ }
36949
36983
  }
36950
- return diagnostics;
36984
+ return session.diagnostics;
36951
36985
  };
36952
36986
  var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
36953
36987
  let p = process || {}, argv = p.argv || [], env = p.env || {};
@@ -37164,6 +37198,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
37164
37198
  return [...seen].filter((pattern) => pattern.length > 0);
37165
37199
  };
37166
37200
  const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
37201
+ const readSystemFacts = () => ({
37202
+ availableCores: os.availableParallelism(),
37203
+ totalMemoryBytes: os.totalmem(),
37204
+ cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
37205
+ });
37206
+ /**
37207
+ * How many real deslop dead-code child processes may run at once, across the
37208
+ * concurrent per-project `runInspect` fibers of one CLI run. The cap is the
37209
+ * smaller of the core count and the number of `DEAD_CODE_WORKER_MEM_BUDGET_BYTES`
37210
+ * workers that fit in available memory, floored at 1.
37211
+ *
37212
+ * On a roomy dev box / CI runner this resolves high enough that every
37213
+ * concurrently-scanned project still spawns its own worker (no serialization vs
37214
+ * the prior uncapped behavior); on a memory-constrained runner it collapses
37215
+ * toward 1, so the `withDeadCodeWorkerSlot` semaphore serializes the spawns
37216
+ * instead of oversubscribing memory with N simultaneous children — the global
37217
+ * cap the per-project spawn path lacked.
37218
+ *
37219
+ * Mirrors `resolveAutoScanConcurrency` (lint), but budgets memory per the
37220
+ * heavier dead-code worker. `facts` is injectable for tests.
37221
+ */
37222
+ const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
37223
+ const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
37224
+ const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
37225
+ return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
37226
+ };
37227
+ let availableSlots = -1;
37228
+ const waiters = [];
37229
+ const releaseSlot = () => {
37230
+ const nextWaiter = waiters.shift();
37231
+ if (nextWaiter !== void 0) nextWaiter();
37232
+ else availableSlots += 1;
37233
+ };
37234
+ /**
37235
+ * Runs `task` once a dead-code worker slot is free, releasing the slot when the
37236
+ * task settles (success or failure). With a high cap (roomy machine) every
37237
+ * caller proceeds immediately; with a low cap (constrained runner) callers
37238
+ * queue and run as slots free.
37239
+ *
37240
+ * `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
37241
+ * this caller is queued, the call rejects without acquiring a slot or running
37242
+ * `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
37243
+ * then spawn a child only to tear it down. A queued caller that aborts removes
37244
+ * its own waiter so a later release never hands a slot to a dead request.
37245
+ */
37246
+ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
37247
+ if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
37248
+ if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
37249
+ if (availableSlots > 0) availableSlots -= 1;
37250
+ else await new Promise((resolve, reject) => {
37251
+ const waiter = () => {
37252
+ abortSignal?.removeEventListener("abort", onAbort);
37253
+ resolve();
37254
+ };
37255
+ const onAbort = () => {
37256
+ const queuedIndex = waiters.indexOf(waiter);
37257
+ if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
37258
+ reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
37259
+ };
37260
+ waiters.push(waiter);
37261
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
37262
+ });
37263
+ try {
37264
+ return await task();
37265
+ } finally {
37266
+ releaseSlot();
37267
+ }
37268
+ };
37167
37269
  /**
37168
37270
  * Resolves a path to its canonical, symlink-free form, falling back to
37169
37271
  * the input when it cannot be realpath'd (broken symlink, permission
@@ -37454,14 +37556,17 @@ const checkDeadCode = async (options) => {
37454
37556
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37455
37557
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37456
37558
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37457
- const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37458
- rootDirectory,
37459
- entryPatterns,
37460
- tsConfigPath: resolveTsConfigPath(rootDirectory),
37461
- ignorePatterns,
37462
- deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
37463
- parseConcurrency: options.parseConcurrency
37464
- }), options.workerTimeoutMs ?? 12e4, options.abortSignal));
37559
+ const spawnAndRun = () => {
37560
+ return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37561
+ rootDirectory,
37562
+ entryPatterns,
37563
+ tsConfigPath: resolveTsConfigPath(rootDirectory),
37564
+ ignorePatterns,
37565
+ deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
37566
+ parseConcurrency: options.parseConcurrency
37567
+ }), options.workerTimeoutMs ?? 12e4, options.abortSignal);
37568
+ };
37569
+ const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
37465
37570
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
37466
37571
  const diagnostics = [];
37467
37572
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -37862,43 +37967,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
37862
37967
  * reason: GitInvocationFailed })` so the rest of the codebase
37863
37968
  * sees a single failure channel.
37864
37969
  */
37865
- const runCommand = (input) => scoped(gen(function* () {
37866
- const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37867
- cwd: input.directory,
37868
- env: input.env,
37869
- extendEnv: true
37870
- }));
37871
- const maxStdoutBytes = input.maxStdoutBytes;
37872
- const stdoutByteCount = yield* make$13(0);
37873
- const [stdout, stderr, status] = yield* all([
37874
- mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37875
- args: [...input.args],
37876
- directory: input.directory,
37877
- cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37878
- }) })) : void_)))))),
37879
- mkString(decodeText(handle.stderr)),
37880
- handle.exitCode
37881
- ], { concurrency: 3 });
37882
- return {
37883
- status,
37884
- stdout,
37885
- stderr
37886
- };
37887
- })).pipe(catchTag$1("PlatformError", (cause) => {
37888
- if (input.command !== "git") return succeed$2({
37970
+ const runCommand = (input) => {
37971
+ const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
37889
37972
  status: 127,
37890
37973
  stdout: "",
37891
37974
  stderr: String(cause)
37892
- });
37893
- return new ReactDoctorError({ reason: new GitInvocationFailed({
37975
+ }) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37894
37976
  args: [...input.args],
37895
37977
  directory: input.directory,
37896
37978
  cause
37897
- }) });
37898
- }), withSpan("git.exec", { attributes: {
37899
- "git.command": input.command,
37900
- "git.subcommand": input.args[0] ?? ""
37901
- } }));
37979
+ }) }));
37980
+ return scoped(gen(function* () {
37981
+ if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
37982
+ const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
37983
+ if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
37984
+ const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37985
+ cwd: input.directory,
37986
+ env: input.env,
37987
+ extendEnv: true
37988
+ }));
37989
+ const maxStdoutBytes = input.maxStdoutBytes;
37990
+ const stdoutByteCount = yield* make$13(0);
37991
+ const [stdout, stderr, status] = yield* all([
37992
+ mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37993
+ args: [...input.args],
37994
+ directory: input.directory,
37995
+ cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37996
+ }) })) : void_)))))),
37997
+ mkString(decodeText(handle.stderr)),
37998
+ handle.exitCode
37999
+ ], { concurrency: 3 });
38000
+ return {
38001
+ status,
38002
+ stdout,
38003
+ stderr
38004
+ };
38005
+ })).pipe(catchTag$1("PlatformError", foldSpawnFailure), withSpan("git.exec", { attributes: {
38006
+ "git.command": input.command,
38007
+ "git.subcommand": input.args[0] ?? ""
38008
+ } }));
38009
+ };
37902
38010
  const runGit = (directory, args) => runCommand({
37903
38011
  command: "git",
37904
38012
  args,
@@ -37931,7 +38039,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37931
38039
  "rev-parse",
37932
38040
  "--verify",
37933
38041
  branch
37934
- ]).pipe(map$3((result) => result.status === 0));
38042
+ ]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
37935
38043
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
37936
38044
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
37937
38045
  "merge-base",
@@ -38145,7 +38253,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38145
38253
  ]);
38146
38254
  if (result.status !== 0) return null;
38147
38255
  return parseChangedLineRanges(result.stdout);
38148
- }).pipe(withSpan("Git.changedLineRanges"))
38256
+ }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
38149
38257
  });
38150
38258
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
38151
38259
  /**
@@ -40622,7 +40730,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
40622
40730
  * diagnostics).
40623
40731
  * 2. beforeLint hook (e.g. CLI renders the project-detection block)
40624
40732
  * 3. environment checks (reduced-motion + pnpm hardening +
40625
- * expo/react-native + security scan), collected synchronously
40733
+ * expo/react-native), collected synchronously. The heavier
40734
+ * content-regex security scan is forked instead (like supply-chain
40735
+ * below) and joined before the concat, so its CPU overlaps lint
40736
+ * rather than blocking the event loop before it.
40626
40737
  * 4. The supply-chain check (Socket.dev) is forked onto a background
40627
40738
  * fiber so its ~100% network-bound time overlaps the ~100%
40628
40739
  * CPU/subprocess-bound lint pass below, collapsing two serial
@@ -40642,7 +40753,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
40642
40753
  * order, so terminal output is identical either way; supply-chain
40643
40754
  * rides alongside without a spinner.
40644
40755
  * 6. Join the supply-chain fiber, then assemble the diagnostics in a
40645
- * FIXED order (env, supply-chain, lint, dead-code) so the output is
40756
+ * FIXED order (env, security-scan, supply-chain, lint, dead-code) so the output is
40646
40757
  * byte-identical regardless of which fiber settled first. The
40647
40758
  * viewer-permission fiber is joined later, during score-metadata
40648
40759
  * assembly (it feeds score metadata, not diagnostics). The per-element
@@ -40703,12 +40814,12 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40703
40814
  ...checkPnpmHardening(scanDirectory),
40704
40815
  ...checkReactServerComponentsAdvisory(scanDirectory, project),
40705
40816
  ...checkExpoProject(scanDirectory, project),
40706
- ...checkReactNativeProject(scanDirectory, project),
40707
- ...checkSecurityScan(scanDirectory, {
40708
- project,
40709
- ignoredTags: input.ignoredTags
40710
- })
40817
+ ...checkReactNativeProject(scanDirectory, project)
40711
40818
  ])));
40819
+ const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
40820
+ project,
40821
+ ignoredTags: input.ignoredTags
40822
+ })).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
40712
40823
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
40713
40824
  const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
40714
40825
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
@@ -40844,9 +40955,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40844
40955
  else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
40845
40956
  const supplyChainResult = yield* join(supplyChainFiber);
40846
40957
  const supplyChainCollected = supplyChainResult.diagnostics;
40958
+ const securityScanCollected = yield* join(securityScanFiber);
40847
40959
  yield* reporterService.finalize;
40848
40960
  const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
40849
40961
  ...envCollected,
40962
+ ...securityScanCollected,
40850
40963
  ...supplyChainCollected,
40851
40964
  ...lintCollected,
40852
40965
  ...deadCodeCollected
@@ -41469,4 +41582,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41469
41582
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41470
41583
 
41471
41584
  //# sourceMappingURL=index.js.map
41472
- //# debugId=33508ee6-c977-5b5f-8585-9939928ce74d
41585
+ //# debugId=b9d1a98e-f5ec-5357-a08b-f84864fdfa20