react-doctor 0.5.8-dev.c2ce298 → 0.5.8-dev.c72b560

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.d.ts CHANGED
@@ -9726,7 +9726,7 @@ interface InspectResult {
9726
9726
  * Per-file lint cache outcome: files served from cache, and total files
9727
9727
  * considered. Both absent when the cache was off or bypassed (audit mode,
9728
9728
  * adopted `extends`, user plugins). The CLI projects these onto the Sentry
9729
- * wide event as `lintCacheHitRatio`.
9729
+ * wide event as `lint.cacheHitRatio`.
9730
9730
  */
9731
9731
  lintCacheHitFileCount?: number | null;
9732
9732
  lintCacheTotalFileCount?: number | null;
@@ -10123,7 +10123,7 @@ declare const summarizeDiagnostics: (diagnostics: Diagnostic[], worstScore?: num
10123
10123
  * untouched so the loader sees the same plain object either way.
10124
10124
  */
10125
10125
  declare const defineConfig: (config: ReactDoctorConfig) => ReactDoctorConfig; //#endregion
10126
- //#region src/utils/group-by.d.ts
10126
+ //#region src/utils/detect-ai-training-environment.d.ts
10127
10127
  //#endregion
10128
10128
  //#region ../api/dist/index.d.ts
10129
10129
  //#region src/diagnose.d.ts
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]="fa4e432e-2004-53cc-816a-025aebf1c752")}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]="9807b1bf-1e62-5421-ad35-d1f4129c1538")}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;
@@ -35368,7 +35370,7 @@ const resolveScanConcurrency = (requested) => {
35368
35370
  if (!Number.isFinite(requested) || requested < 1) return 1;
35369
35371
  return Math.min(Math.floor(requested), 32);
35370
35372
  };
35371
- const readSystemFacts = () => ({
35373
+ const readSystemFacts$1 = () => ({
35372
35374
  availableCores: os.availableParallelism(),
35373
35375
  totalMemoryBytes: os.totalmem(),
35374
35376
  cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
@@ -35389,7 +35391,7 @@ const readSystemFacts = () => ({
35389
35391
  * `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
35390
35392
  * limited, and ceiling cases without mocking `os` or the filesystem.
35391
35393
  */
35392
- const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
35394
+ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
35393
35395
  const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
35394
35396
  const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
35395
35397
  return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
@@ -35580,6 +35582,12 @@ const BOOLEAN_FIELD_NAMES = [
35580
35582
  "adoptExistingLintConfig"
35581
35583
  ];
35582
35584
  const STRING_FIELD_NAMES = ["rootDir"];
35585
+ const STRING_ARRAY_FIELD_NAMES = [
35586
+ "projects",
35587
+ "textComponents",
35588
+ "rawTextWrapperComponents",
35589
+ "serverAuthFunctionNames"
35590
+ ];
35583
35591
  const SURFACE_CONTROL_FIELD_NAMES = [
35584
35592
  "includeTags",
35585
35593
  "excludeTags",
@@ -35681,6 +35689,7 @@ const validateConfigTypes = (config) => {
35681
35689
  const validated = { ...config };
35682
35690
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
35683
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));
35684
35693
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
35685
35694
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
35686
35695
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -36915,7 +36924,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
36915
36924
  }
36916
36925
  return true;
36917
36926
  };
36918
- const checkSecurityScan = (rootDirectory, options = {}) => {
36927
+ const yieldToEventLoop = () => new Promise((resolve) => {
36928
+ setImmediate(resolve);
36929
+ });
36930
+ const createSecurityScanSession = (rootDirectory, options) => {
36919
36931
  const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
36920
36932
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
36921
36933
  const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
@@ -36930,7 +36942,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
36930
36942
  committedFilesOnly: rule.committedFilesOnly === true
36931
36943
  }];
36932
36944
  });
36933
- if (enabledScanRules.length === 0) return [];
36945
+ if (enabledScanRules.length === 0) return null;
36934
36946
  const diagnostics = [];
36935
36947
  const seen = /* @__PURE__ */ new Set();
36936
36948
  const gitIgnoredCache = /* @__PURE__ */ new Map();
@@ -36942,15 +36954,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
36942
36954
  }
36943
36955
  return status === true;
36944
36956
  };
36945
- for (const file of collectSecurityScanFiles(rootDirectory)) for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
36946
- if (committedFilesOnly && isFileGitIgnored(file)) continue;
36947
- const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
36948
- const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
36949
- if (seen.has(key)) continue;
36950
- seen.add(key);
36951
- 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
+ }
36952
36983
  }
36953
- return diagnostics;
36984
+ return session.diagnostics;
36954
36985
  };
36955
36986
  var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
36956
36987
  let p = process || {}, argv = p.argv || [], env = p.env || {};
@@ -37167,6 +37198,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
37167
37198
  return [...seen].filter((pattern) => pattern.length > 0);
37168
37199
  };
37169
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
+ };
37170
37269
  /**
37171
37270
  * Resolves a path to its canonical, symlink-free form, falling back to
37172
37271
  * the input when it cannot be realpath'd (broken symlink, permission
@@ -37457,14 +37556,17 @@ const checkDeadCode = async (options) => {
37457
37556
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37458
37557
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37459
37558
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37460
- const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37461
- rootDirectory,
37462
- entryPatterns,
37463
- tsConfigPath: resolveTsConfigPath(rootDirectory),
37464
- ignorePatterns,
37465
- deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
37466
- parseConcurrency: options.parseConcurrency
37467
- }), 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());
37468
37570
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
37469
37571
  const diagnostics = [];
37470
37572
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -37865,43 +37967,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
37865
37967
  * reason: GitInvocationFailed })` so the rest of the codebase
37866
37968
  * sees a single failure channel.
37867
37969
  */
37868
- const runCommand = (input) => scoped(gen(function* () {
37869
- const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37870
- cwd: input.directory,
37871
- env: input.env,
37872
- extendEnv: true
37873
- }));
37874
- const maxStdoutBytes = input.maxStdoutBytes;
37875
- const stdoutByteCount = yield* make$13(0);
37876
- const [stdout, stderr, status] = yield* all([
37877
- 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({
37878
- args: [...input.args],
37879
- directory: input.directory,
37880
- cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37881
- }) })) : void_)))))),
37882
- mkString(decodeText(handle.stderr)),
37883
- handle.exitCode
37884
- ], { concurrency: 3 });
37885
- return {
37886
- status,
37887
- stdout,
37888
- stderr
37889
- };
37890
- })).pipe(catchTag$1("PlatformError", (cause) => {
37891
- if (input.command !== "git") return succeed$2({
37970
+ const runCommand = (input) => {
37971
+ const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
37892
37972
  status: 127,
37893
37973
  stdout: "",
37894
37974
  stderr: String(cause)
37895
- });
37896
- return new ReactDoctorError({ reason: new GitInvocationFailed({
37975
+ }) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37897
37976
  args: [...input.args],
37898
37977
  directory: input.directory,
37899
37978
  cause
37900
- }) });
37901
- }), withSpan("git.exec", { attributes: {
37902
- "git.command": input.command,
37903
- "git.subcommand": input.args[0] ?? ""
37904
- } }));
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
+ };
37905
38010
  const runGit = (directory, args) => runCommand({
37906
38011
  command: "git",
37907
38012
  args,
@@ -37934,7 +38039,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37934
38039
  "rev-parse",
37935
38040
  "--verify",
37936
38041
  branch
37937
- ]).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)));
37938
38043
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
37939
38044
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
37940
38045
  "merge-base",
@@ -38148,7 +38253,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38148
38253
  ]);
38149
38254
  if (result.status !== 0) return null;
38150
38255
  return parseChangedLineRanges(result.stdout);
38151
- }).pipe(withSpan("Git.changedLineRanges"))
38256
+ }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
38152
38257
  });
38153
38258
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
38154
38259
  /**
@@ -40625,7 +40730,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
40625
40730
  * diagnostics).
40626
40731
  * 2. beforeLint hook (e.g. CLI renders the project-detection block)
40627
40732
  * 3. environment checks (reduced-motion + pnpm hardening +
40628
- * 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.
40629
40737
  * 4. The supply-chain check (Socket.dev) is forked onto a background
40630
40738
  * fiber so its ~100% network-bound time overlaps the ~100%
40631
40739
  * CPU/subprocess-bound lint pass below, collapsing two serial
@@ -40645,7 +40753,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
40645
40753
  * order, so terminal output is identical either way; supply-chain
40646
40754
  * rides alongside without a spinner.
40647
40755
  * 6. Join the supply-chain fiber, then assemble the diagnostics in a
40648
- * 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
40649
40757
  * byte-identical regardless of which fiber settled first. The
40650
40758
  * viewer-permission fiber is joined later, during score-metadata
40651
40759
  * assembly (it feeds score metadata, not diagnostics). The per-element
@@ -40706,12 +40814,12 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40706
40814
  ...checkPnpmHardening(scanDirectory),
40707
40815
  ...checkReactServerComponentsAdvisory(scanDirectory, project),
40708
40816
  ...checkExpoProject(scanDirectory, project),
40709
- ...checkReactNativeProject(scanDirectory, project),
40710
- ...checkSecurityScan(scanDirectory, {
40711
- project,
40712
- ignoredTags: input.ignoredTags
40713
- })
40817
+ ...checkReactNativeProject(scanDirectory, project)
40714
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")));
40715
40823
  const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
40716
40824
  const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
40717
40825
  const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
@@ -40847,9 +40955,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
40847
40955
  else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
40848
40956
  const supplyChainResult = yield* join(supplyChainFiber);
40849
40957
  const supplyChainCollected = supplyChainResult.diagnostics;
40958
+ const securityScanCollected = yield* join(securityScanFiber);
40850
40959
  yield* reporterService.finalize;
40851
40960
  const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
40852
40961
  ...envCollected,
40962
+ ...securityScanCollected,
40853
40963
  ...supplyChainCollected,
40854
40964
  ...lintCollected,
40855
40965
  ...deadCodeCollected
@@ -41301,6 +41411,61 @@ const clearAutoSuppressionCaches = () => {};
41301
41411
  * untouched so the loader sees the same plain object either way.
41302
41412
  */
41303
41413
  const defineConfig = (config) => config;
41414
+ const AI_TRAINING_ENV_VARS = [
41415
+ ["HF_DATASETS_CACHE", "huggingface"],
41416
+ ["HF_HOME", "huggingface"],
41417
+ ["HUGGINGFACE_HUB_CACHE", "huggingface"],
41418
+ ["SPACE_ID", "huggingface-spaces"],
41419
+ ["CUDA_VISIBLE_DEVICES", "cuda"],
41420
+ ["NVIDIA_VISIBLE_DEVICES", "nvidia"],
41421
+ ["ROCR_VISIBLE_DEVICES", "rocm"],
41422
+ ["TPU_NAME", "google-tpu"],
41423
+ ["WANDB_RUN_ID", "wandb"],
41424
+ ["WANDB_SWEEP_ID", "wandb"],
41425
+ ["MLFLOW_RUN_ID", "mlflow"],
41426
+ ["MLFLOW_TRACKING_URI", "mlflow"],
41427
+ ["COMET_EXPERIMENT_KEY", "comet"],
41428
+ ["NEPTUNE_RUN_ID", "neptune"],
41429
+ ["CLEARML_TASK_ID", "clearml"],
41430
+ ["DVC_STAGE", "dvc"],
41431
+ ["RAY_WORKER_PROCESS", "ray"],
41432
+ ["RAY_ADDRESS", "ray"],
41433
+ ["MUJOCO_GL", "mujoco"],
41434
+ ["MUJOCO_PATH", "mujoco"],
41435
+ ["GYM_DISABLE_ENV_CHECKER", "gymnasium"],
41436
+ ["SM_TRAINING_ENV", "sagemaker"],
41437
+ ["TRAINING_JOB_ARN", "sagemaker"],
41438
+ ["SAGEMAKER_BASE_DIR", "sagemaker"],
41439
+ ["AZUREML_RUN_ID", "azure-ml"],
41440
+ ["AZURE_ML_MODEL_DIR", "azure-ml"],
41441
+ ["CLOUD_ML_PROJECT_ID", "vertex-ai"],
41442
+ ["VERTEX_AI_LOG_LEVEL", "vertex-ai"],
41443
+ ["DET_MASTER", "determined-ai"],
41444
+ ["LIGHTNING_USER_ID", "lightning-ai"],
41445
+ ["FLYTE_INTERNAL_EXECUTION_ID", "flyte"],
41446
+ ["ARGO_WORKFLOW_NAME", "argo-workflows"],
41447
+ ["KFP_POD_NAME", "kubeflow-pipelines"],
41448
+ ["KAGGLE_KERNEL_RUN_TYPE", "kaggle"],
41449
+ ["COLAB_BACKEND_VERSION", "google-colab"],
41450
+ ["DATABRICKS_RUNTIME_VERSION", "databricks"],
41451
+ ["DAYTONA_WS_ID", "daytona"],
41452
+ ["DAYTONA_WS_NAME", "daytona"],
41453
+ ["E2B_SANDBOX_ID", "e2b"],
41454
+ ["MODAL_FUNCTION_ID", "modal"],
41455
+ ["MODAL_TASK_ID", "modal"],
41456
+ ["RUNPOD_POD_ID", "runpod"],
41457
+ ["REPLICATE_USERNAME", "replicate"],
41458
+ ["VAST_CONTAINERLABEL", "vast-ai"],
41459
+ ["HARBOR_URL", "harbor"],
41460
+ ["HARBOR_HOSTNAME", "harbor"],
41461
+ ["SWE_BENCH_TASK", "swe-bench"],
41462
+ ["SWEBENCH_TASK", "swe-bench"],
41463
+ ["SWE_AGENT_MODEL", "swe-agent"]
41464
+ ];
41465
+ const detectAiTrainingEnvironment = () => {
41466
+ for (const [envVar, label] of AI_TRAINING_ENV_VARS) if (process.env[envVar]) return label;
41467
+ return null;
41468
+ };
41304
41469
  const unionValues = (baseValues, overrideValues) => [...new Set([...baseValues, ...overrideValues])];
41305
41470
  const mergeIgnores = (baseIgnore, overrideIgnore) => {
41306
41471
  const mergedIgnore = {
@@ -41345,6 +41510,12 @@ const mergeReactDoctorConfigs = (baseConfig, overrideConfig) => {
41345
41510
  };
41346
41511
  //#endregion
41347
41512
  //#region ../api/dist/index.js
41513
+ let didWarnAiTraining = false;
41514
+ const warnIfAiTrainingEnvironment = () => {
41515
+ if (didWarnAiTraining || detectAiTrainingEnvironment() === null) return;
41516
+ didWarnAiTraining = true;
41517
+ console.warn("[react-doctor] Use in an AI or ML pipeline requires written permission under the react-doctor license. Contact founders@million.dev to request access.");
41518
+ };
41348
41519
  const buildDiagnoseLayer = (config, configOverrideTarget) => {
41349
41520
  const configLayer = configOverrideTarget === void 0 ? Config.layerNode : Config.layerOf({
41350
41521
  config,
@@ -41382,6 +41553,7 @@ const outputToDiagnoseResult = (output, elapsedMilliseconds) => {
41382
41553
  };
41383
41554
  };
41384
41555
  const diagnoseDirectory = async (directory, options) => {
41556
+ warnIfAiTrainingEnvironment();
41385
41557
  const startTime = globalThis.performance.now();
41386
41558
  const scanTarget = await resolveScanTarget(directory);
41387
41559
  return outputToDiagnoseResult(await runPromise(restoreLegacyThrow(buildInspectProgram(scanTarget, options).pipe(provide(buildDiagnoseLayer(scanTarget.userConfig)), provide(layerOtlp)))), globalThis.performance.now() - startTime);
@@ -41428,6 +41600,7 @@ const diagnoseProject = async (projectDefinition, baseOptions, batchConfig) => {
41428
41600
  }
41429
41601
  };
41430
41602
  const diagnoseProjectBatch = async (input) => {
41603
+ warnIfAiTrainingEnvironment();
41431
41604
  const startTime = globalThis.performance.now();
41432
41605
  const { projects, concurrency, config: batchConfig, ...baseOptions } = input;
41433
41606
  const projectResults = await mapWithConcurrency(projects, concurrency ?? 4, (projectDefinition) => diagnoseProject(projectDefinition, baseOptions, batchConfig));
@@ -41472,4 +41645,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41472
41645
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41473
41646
 
41474
41647
  //# sourceMappingURL=index.js.map
41475
- //# debugId=fa4e432e-2004-53cc-816a-025aebf1c752
41648
+ //# debugId=9807b1bf-1e62-5421-ad35-d1f4129c1538