react-doctor 0.9.2 → 0.9.3-dev.02e3188

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/cli.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]="c86de9ea-b858-5528-89a8-6ff4d377c873")}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]="e4075e6f-4128-5af6-bc97-656272450282")}catch(e){}}();
3
3
  import { createRequire } from "node:module";
4
4
  import * as NodeChildProcess from "node:child_process";
5
5
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
@@ -8,18 +8,19 @@ import path from "node:path";
8
8
  import * as NFS from "node:fs";
9
9
  import fs, { mkdtempSync, rmSync } from "node:fs";
10
10
  import process$1 from "node:process";
11
- import reactDoctorPlugin, { 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, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
11
+ 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";
12
12
  import * as Crypto from "node:crypto";
13
13
  import crypto, { createHash, randomUUID } from "node:crypto";
14
14
  import { ResolverFactory } from "oxc-resolver";
15
15
  import ts from "typescript";
16
+ import * as NodeUrl from "node:url";
17
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
18
  import * as OS from "node:os";
17
19
  import os, { tmpdir } from "node:os";
18
20
  import { parseJSON5 } from "confbox";
19
- import * as NodeUrl from "node:url";
20
- import { fileURLToPath, pathToFileURL } from "node:url";
21
21
  import { createJiti } from "jiti";
22
22
  import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
23
+ import { convertToTSX } from "@astrojs/compiler/sync";
23
24
  import { gzipSync } from "node:zlib";
24
25
  import * as Sentry from "@sentry/node";
25
26
  import { performance as performance$1 } from "node:perf_hooks";
@@ -13342,7 +13343,7 @@ const Number$4 = globalThis.Number;
13342
13343
  * @category constructors
13343
13344
  * @since 2.0.0
13344
13345
  */
13345
- const parse = (s) => {
13346
+ const parse$1 = (s) => {
13346
13347
  if (s === "NaN") return some(NaN);
13347
13348
  if (s === "Infinity") return some(Infinity);
13348
13349
  if (s === "-Infinity") return some(-Infinity);
@@ -17405,12 +17406,12 @@ TaggedError("EncodingError");
17405
17406
  //#endregion
17406
17407
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/internal/schema/annotations.js
17407
17408
  /** @internal */
17408
- function resolve(ast) {
17409
+ function resolve$1(ast) {
17409
17410
  return ast.checks ? ast.checks[ast.checks.length - 1].annotations : ast.annotations;
17410
17411
  }
17411
17412
  /** @internal */
17412
17413
  function resolveAt(key) {
17413
- return (ast) => resolve(ast)?.[key];
17414
+ return (ast) => resolve$1(ast)?.[key];
17414
17415
  }
17415
17416
  /** @internal */
17416
17417
  const resolveIdentifier = /* @__PURE__ */ resolveAt("identifier");
@@ -21162,7 +21163,7 @@ function asSync(parser) {
21162
21163
  }
21163
21164
  const recur$1 = /* @__PURE__ */ memoize((ast) => {
21164
21165
  let parser;
21165
- const astOptions = resolve(ast)?.["parseOptions"];
21166
+ const astOptions = resolve$1(ast)?.["parseOptions"];
21166
21167
  if (!ast.context && !ast.encoding && !ast.checks) return (ou, options) => {
21167
21168
  parser ??= ast.getParser(recur$1);
21168
21169
  if (astOptions) options = {
@@ -60767,7 +60768,7 @@ var UrlParamsError = class extends TaggedError("UrlParamsError") {};
60767
60768
  * @category converting
60768
60769
  * @since 4.0.0
60769
60770
  */
60770
- const makeUrl = (url, params, hash) => {
60771
+ const makeUrl$1 = (url, params, hash) => {
60771
60772
  try {
60772
60773
  const urlInstance = new URL(url, baseUrl());
60773
60774
  for (let i = 0; i < params.params.length; i++) {
@@ -61401,7 +61402,7 @@ const Proto$2 = {
61401
61402
  const make$10 = (f) => makeWith$1((effect) => flatMap$2(effect, (request) => withFiber((fiber) => {
61402
61403
  const scopedController = scopedRequests.get(request);
61403
61404
  const controller = scopedController ?? new AbortController();
61404
- const urlResult = makeUrl(request.url, request.urlParams, getOrUndefined(request.hash));
61405
+ const urlResult = makeUrl$1(request.url, request.urlParams, getOrUndefined(request.hash));
61405
61406
  if (isFailure(urlResult)) return fail$4(new HttpClientError({ reason: new InvalidUrlError({
61406
61407
  request,
61407
61408
  cause: urlResult.failure
@@ -61840,7 +61841,7 @@ const layer$8 = (loggers, options) => effect(CurrentLoggers, withFiber$1(fnUntra
61840
61841
  * @since 4.0.0
61841
61842
  */
61842
61843
  const policy = /* @__PURE__ */ forever$1.pipe(passthrough$2, /* @__PURE__ */ addDelay((error) => {
61843
- if (isHttpClientError(error) && error.reason._tag === "StatusCodeError" && error.reason.response.status === 429) return succeed$2(seconds(fromUndefinedOr(error.reason.response.headers["retry-after"]).pipe(flatMap$5(parse), getOrElse$1(() => 5))));
61844
+ if (isHttpClientError(error) && error.reason._tag === "StatusCodeError" && error.reason.response.status === 429) return succeed$2(seconds(fromUndefinedOr(error.reason.response.headers["retry-after"]).pipe(flatMap$5(parse$1), getOrElse$1(() => 5))));
61844
61845
  return succeed$2(seconds(1));
61845
61846
  }));
61846
61847
  /**
@@ -64549,6 +64550,428 @@ const layer$1 = /* @__PURE__ */ succeed$3(Path$1)({
64549
64550
  toFileUrl
64550
64551
  });
64551
64552
  //#endregion
64553
+ //#region ../../node_modules/.pnpm/@jridgewell+sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs
64554
+ var comma = ",".charCodeAt(0);
64555
+ ";".charCodeAt(0);
64556
+ var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
64557
+ var intToChar = new Uint8Array(64);
64558
+ var charToInt = new Uint8Array(128);
64559
+ for (let i = 0; i < chars.length; i++) {
64560
+ const c = chars.charCodeAt(i);
64561
+ intToChar[i] = c;
64562
+ charToInt[c] = i;
64563
+ }
64564
+ function decodeInteger(reader, relative) {
64565
+ let value = 0;
64566
+ let shift = 0;
64567
+ let integer = 0;
64568
+ do {
64569
+ integer = charToInt[reader.next()];
64570
+ value |= (integer & 31) << shift;
64571
+ shift += 5;
64572
+ } while (integer & 32);
64573
+ const shouldNegate = value & 1;
64574
+ value >>>= 1;
64575
+ if (shouldNegate) value = -2147483648 | -value;
64576
+ return relative + value;
64577
+ }
64578
+ function hasMoreVlq(reader, max) {
64579
+ if (reader.pos >= max) return false;
64580
+ return reader.peek() !== comma;
64581
+ }
64582
+ var StringReader = class {
64583
+ constructor(buffer) {
64584
+ this.pos = 0;
64585
+ this.buffer = buffer;
64586
+ }
64587
+ next() {
64588
+ return this.buffer.charCodeAt(this.pos++);
64589
+ }
64590
+ peek() {
64591
+ return this.buffer.charCodeAt(this.pos);
64592
+ }
64593
+ indexOf(char) {
64594
+ const { buffer, pos } = this;
64595
+ const idx = buffer.indexOf(char, pos);
64596
+ return idx === -1 ? buffer.length : idx;
64597
+ }
64598
+ };
64599
+ function decode(mappings) {
64600
+ const { length } = mappings;
64601
+ const reader = new StringReader(mappings);
64602
+ const decoded = [];
64603
+ let genColumn = 0;
64604
+ let sourcesIndex = 0;
64605
+ let sourceLine = 0;
64606
+ let sourceColumn = 0;
64607
+ let namesIndex = 0;
64608
+ do {
64609
+ const semi = reader.indexOf(";");
64610
+ const line = [];
64611
+ let sorted = true;
64612
+ let lastCol = 0;
64613
+ genColumn = 0;
64614
+ while (reader.pos < semi) {
64615
+ let seg;
64616
+ genColumn = decodeInteger(reader, genColumn);
64617
+ if (genColumn < lastCol) sorted = false;
64618
+ lastCol = genColumn;
64619
+ if (hasMoreVlq(reader, semi)) {
64620
+ sourcesIndex = decodeInteger(reader, sourcesIndex);
64621
+ sourceLine = decodeInteger(reader, sourceLine);
64622
+ sourceColumn = decodeInteger(reader, sourceColumn);
64623
+ if (hasMoreVlq(reader, semi)) {
64624
+ namesIndex = decodeInteger(reader, namesIndex);
64625
+ seg = [
64626
+ genColumn,
64627
+ sourcesIndex,
64628
+ sourceLine,
64629
+ sourceColumn,
64630
+ namesIndex
64631
+ ];
64632
+ } else seg = [
64633
+ genColumn,
64634
+ sourcesIndex,
64635
+ sourceLine,
64636
+ sourceColumn
64637
+ ];
64638
+ } else seg = [genColumn];
64639
+ line.push(seg);
64640
+ reader.pos++;
64641
+ }
64642
+ if (!sorted) sort(line);
64643
+ decoded.push(line);
64644
+ reader.pos = semi + 1;
64645
+ } while (reader.pos <= length);
64646
+ return decoded;
64647
+ }
64648
+ function sort(line) {
64649
+ line.sort(sortComparator$1);
64650
+ }
64651
+ function sortComparator$1(a, b) {
64652
+ return a[0] - b[0];
64653
+ }
64654
+ //#endregion
64655
+ //#region ../../node_modules/.pnpm/@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs
64656
+ const schemeRegex = /^[\w+.-]+:\/\//;
64657
+ /**
64658
+ * Matches the parts of a URL:
64659
+ * 1. Scheme, including ":", guaranteed.
64660
+ * 2. User/password, including "@", optional.
64661
+ * 3. Host, guaranteed.
64662
+ * 4. Port, including ":", optional.
64663
+ * 5. Path, including "/", optional.
64664
+ * 6. Query, including "?", optional.
64665
+ * 7. Hash, including "#", optional.
64666
+ */
64667
+ const urlRegex = /^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/;
64668
+ /**
64669
+ * File URLs are weird. They dont' need the regular `//` in the scheme, they may or may not start
64670
+ * with a leading `/`, they can have a domain (but only if they don't start with a Windows drive).
64671
+ *
64672
+ * 1. Host, optional.
64673
+ * 2. Path, which may include "/", guaranteed.
64674
+ * 3. Query, including "?", optional.
64675
+ * 4. Hash, including "#", optional.
64676
+ */
64677
+ const fileRegex = /^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i;
64678
+ function isAbsoluteUrl(input) {
64679
+ return schemeRegex.test(input);
64680
+ }
64681
+ function isSchemeRelativeUrl(input) {
64682
+ return input.startsWith("//");
64683
+ }
64684
+ function isAbsolutePath(input) {
64685
+ return input.startsWith("/");
64686
+ }
64687
+ function isFileUrl(input) {
64688
+ return input.startsWith("file:");
64689
+ }
64690
+ function isRelative(input) {
64691
+ return /^[.?#]/.test(input);
64692
+ }
64693
+ function parseAbsoluteUrl(input) {
64694
+ const match = urlRegex.exec(input);
64695
+ return makeUrl(match[1], match[2] || "", match[3], match[4] || "", match[5] || "/", match[6] || "", match[7] || "");
64696
+ }
64697
+ function parseFileUrl(input) {
64698
+ const match = fileRegex.exec(input);
64699
+ const path = match[2];
64700
+ return makeUrl("file:", "", match[1] || "", "", isAbsolutePath(path) ? path : "/" + path, match[3] || "", match[4] || "");
64701
+ }
64702
+ function makeUrl(scheme, user, host, port, path, query, hash) {
64703
+ return {
64704
+ scheme,
64705
+ user,
64706
+ host,
64707
+ port,
64708
+ path,
64709
+ query,
64710
+ hash,
64711
+ type: 7
64712
+ };
64713
+ }
64714
+ function parseUrl(input) {
64715
+ if (isSchemeRelativeUrl(input)) {
64716
+ const url = parseAbsoluteUrl("http:" + input);
64717
+ url.scheme = "";
64718
+ url.type = 6;
64719
+ return url;
64720
+ }
64721
+ if (isAbsolutePath(input)) {
64722
+ const url = parseAbsoluteUrl("http://foo.com" + input);
64723
+ url.scheme = "";
64724
+ url.host = "";
64725
+ url.type = 5;
64726
+ return url;
64727
+ }
64728
+ if (isFileUrl(input)) return parseFileUrl(input);
64729
+ if (isAbsoluteUrl(input)) return parseAbsoluteUrl(input);
64730
+ const url = parseAbsoluteUrl("http://foo.com/" + input);
64731
+ url.scheme = "";
64732
+ url.host = "";
64733
+ url.type = input ? input.startsWith("?") ? 3 : input.startsWith("#") ? 2 : 4 : 1;
64734
+ return url;
64735
+ }
64736
+ function stripPathFilename(path) {
64737
+ if (path.endsWith("/..")) return path;
64738
+ const index = path.lastIndexOf("/");
64739
+ return path.slice(0, index + 1);
64740
+ }
64741
+ function mergePaths(url, base) {
64742
+ normalizePath(base, base.type);
64743
+ if (url.path === "/") url.path = base.path;
64744
+ else url.path = stripPathFilename(base.path) + url.path;
64745
+ }
64746
+ /**
64747
+ * The path can have empty directories "//", unneeded parents "foo/..", or current directory
64748
+ * "foo/.". We need to normalize to a standard representation.
64749
+ */
64750
+ function normalizePath(url, type) {
64751
+ const rel = type <= 4;
64752
+ const pieces = url.path.split("/");
64753
+ let pointer = 1;
64754
+ let positive = 0;
64755
+ let addTrailingSlash = false;
64756
+ for (let i = 1; i < pieces.length; i++) {
64757
+ const piece = pieces[i];
64758
+ if (!piece) {
64759
+ addTrailingSlash = true;
64760
+ continue;
64761
+ }
64762
+ addTrailingSlash = false;
64763
+ if (piece === ".") continue;
64764
+ if (piece === "..") {
64765
+ if (positive) {
64766
+ addTrailingSlash = true;
64767
+ positive--;
64768
+ pointer--;
64769
+ } else if (rel) pieces[pointer++] = piece;
64770
+ continue;
64771
+ }
64772
+ pieces[pointer++] = piece;
64773
+ positive++;
64774
+ }
64775
+ let path = "";
64776
+ for (let i = 1; i < pointer; i++) path += "/" + pieces[i];
64777
+ if (!path || addTrailingSlash && !path.endsWith("/..")) path += "/";
64778
+ url.path = path;
64779
+ }
64780
+ /**
64781
+ * Attempts to resolve `input` URL/path relative to `base`.
64782
+ */
64783
+ function resolve(input, base) {
64784
+ if (!input && !base) return "";
64785
+ const url = parseUrl(input);
64786
+ let inputType = url.type;
64787
+ if (base && inputType !== 7) {
64788
+ const baseUrl = parseUrl(base);
64789
+ const baseType = baseUrl.type;
64790
+ switch (inputType) {
64791
+ case 1: url.hash = baseUrl.hash;
64792
+ case 2: url.query = baseUrl.query;
64793
+ case 3:
64794
+ case 4: mergePaths(url, baseUrl);
64795
+ case 5:
64796
+ url.user = baseUrl.user;
64797
+ url.host = baseUrl.host;
64798
+ url.port = baseUrl.port;
64799
+ case 6: url.scheme = baseUrl.scheme;
64800
+ }
64801
+ if (baseType > inputType) inputType = baseType;
64802
+ }
64803
+ normalizePath(url, inputType);
64804
+ const queryHash = url.query + url.hash;
64805
+ switch (inputType) {
64806
+ case 2:
64807
+ case 3: return queryHash;
64808
+ case 4: {
64809
+ const path = url.path.slice(1);
64810
+ if (!path) return queryHash || ".";
64811
+ if (isRelative(base || input) && !isRelative(path)) return "./" + path + queryHash;
64812
+ return path + queryHash;
64813
+ }
64814
+ case 5: return url.path + queryHash;
64815
+ default: return url.scheme + "//" + url.user + url.host + url.port + url.path + queryHash;
64816
+ }
64817
+ }
64818
+ //#endregion
64819
+ //#region ../../node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs
64820
+ function stripFilename(path) {
64821
+ if (!path) return "";
64822
+ const index = path.lastIndexOf("/");
64823
+ return path.slice(0, index + 1);
64824
+ }
64825
+ function resolver(mapUrl, sourceRoot) {
64826
+ const from = stripFilename(mapUrl);
64827
+ const prefix = sourceRoot ? sourceRoot + "/" : "";
64828
+ return (source) => resolve(prefix + (source || ""), from);
64829
+ }
64830
+ var COLUMN = 0;
64831
+ var SOURCES_INDEX = 1;
64832
+ var SOURCE_LINE = 2;
64833
+ var SOURCE_COLUMN = 3;
64834
+ var NAMES_INDEX = 4;
64835
+ function maybeSort(mappings, owned) {
64836
+ const unsortedIndex = nextUnsortedSegmentLine(mappings, 0);
64837
+ if (unsortedIndex === mappings.length) return mappings;
64838
+ if (!owned) mappings = mappings.slice();
64839
+ for (let i = unsortedIndex; i < mappings.length; i = nextUnsortedSegmentLine(mappings, i + 1)) mappings[i] = sortSegments(mappings[i], owned);
64840
+ return mappings;
64841
+ }
64842
+ function nextUnsortedSegmentLine(mappings, start) {
64843
+ for (let i = start; i < mappings.length; i++) if (!isSorted(mappings[i])) return i;
64844
+ return mappings.length;
64845
+ }
64846
+ function isSorted(line) {
64847
+ for (let j = 1; j < line.length; j++) if (line[j][COLUMN] < line[j - 1][COLUMN]) return false;
64848
+ return true;
64849
+ }
64850
+ function sortSegments(line, owned) {
64851
+ if (!owned) line = line.slice();
64852
+ return line.sort(sortComparator);
64853
+ }
64854
+ function sortComparator(a, b) {
64855
+ return a[COLUMN] - b[COLUMN];
64856
+ }
64857
+ var found = false;
64858
+ function binarySearch(haystack, needle, low, high) {
64859
+ while (low <= high) {
64860
+ const mid = low + (high - low >> 1);
64861
+ const cmp = haystack[mid][COLUMN] - needle;
64862
+ if (cmp === 0) {
64863
+ found = true;
64864
+ return mid;
64865
+ }
64866
+ if (cmp < 0) low = mid + 1;
64867
+ else high = mid - 1;
64868
+ }
64869
+ found = false;
64870
+ return low - 1;
64871
+ }
64872
+ function upperBound(haystack, needle, index) {
64873
+ for (let i = index + 1; i < haystack.length; index = i++) if (haystack[i][COLUMN] !== needle) break;
64874
+ return index;
64875
+ }
64876
+ function lowerBound(haystack, needle, index) {
64877
+ for (let i = index - 1; i >= 0; index = i--) if (haystack[i][COLUMN] !== needle) break;
64878
+ return index;
64879
+ }
64880
+ function memoizedState() {
64881
+ return {
64882
+ lastKey: -1,
64883
+ lastNeedle: -1,
64884
+ lastIndex: -1
64885
+ };
64886
+ }
64887
+ function memoizedBinarySearch(haystack, needle, state, key) {
64888
+ const { lastKey, lastNeedle, lastIndex } = state;
64889
+ let low = 0;
64890
+ let high = haystack.length - 1;
64891
+ if (key === lastKey) {
64892
+ if (needle === lastNeedle) {
64893
+ found = lastIndex !== -1 && haystack[lastIndex][COLUMN] === needle;
64894
+ return lastIndex;
64895
+ }
64896
+ if (needle >= lastNeedle) low = lastIndex === -1 ? 0 : lastIndex;
64897
+ else high = lastIndex;
64898
+ }
64899
+ state.lastKey = key;
64900
+ state.lastNeedle = needle;
64901
+ return state.lastIndex = binarySearch(haystack, needle, low, high);
64902
+ }
64903
+ function parse(map) {
64904
+ return typeof map === "string" ? JSON.parse(map) : map;
64905
+ }
64906
+ var LINE_GTR_ZERO = "`line` must be greater than 0 (lines start at line 1)";
64907
+ var COL_GTR_EQ_ZERO = "`column` must be greater than or equal to 0 (columns start at column 0)";
64908
+ var TraceMap = class {
64909
+ constructor(map, mapUrl) {
64910
+ const isString = typeof map === "string";
64911
+ if (!isString && map._decodedMemo) return map;
64912
+ const parsed = parse(map);
64913
+ const { version, file, names, sourceRoot, sources, sourcesContent } = parsed;
64914
+ this.version = version;
64915
+ this.file = file;
64916
+ this.names = names || [];
64917
+ this.sourceRoot = sourceRoot;
64918
+ this.sources = sources;
64919
+ this.sourcesContent = sourcesContent;
64920
+ this.ignoreList = parsed.ignoreList || parsed.x_google_ignoreList || void 0;
64921
+ const resolve = resolver(mapUrl, sourceRoot);
64922
+ this.resolvedSources = sources.map(resolve);
64923
+ const { mappings } = parsed;
64924
+ if (typeof mappings === "string") {
64925
+ this._encoded = mappings;
64926
+ this._decoded = void 0;
64927
+ } else if (Array.isArray(mappings)) {
64928
+ this._encoded = void 0;
64929
+ this._decoded = maybeSort(mappings, isString);
64930
+ } else if (parsed.sections) throw new Error(`TraceMap passed sectioned source map, please use FlattenMap export instead`);
64931
+ else throw new Error(`invalid source map: ${JSON.stringify(parsed)}`);
64932
+ this._decodedMemo = memoizedState();
64933
+ this._bySources = void 0;
64934
+ this._bySourceMemos = void 0;
64935
+ }
64936
+ };
64937
+ function cast(map) {
64938
+ return map;
64939
+ }
64940
+ function decodedMappings(map) {
64941
+ var _a;
64942
+ return (_a = cast(map))._decoded || (_a._decoded = decode(cast(map)._encoded));
64943
+ }
64944
+ function originalPositionFor(map, needle) {
64945
+ let { line, column, bias } = needle;
64946
+ line--;
64947
+ if (line < 0) throw new Error(LINE_GTR_ZERO);
64948
+ if (column < 0) throw new Error(COL_GTR_EQ_ZERO);
64949
+ const decoded = decodedMappings(map);
64950
+ if (line >= decoded.length) return OMapping(null, null, null, null);
64951
+ const segments = decoded[line];
64952
+ const index = traceSegmentInternal(segments, cast(map)._decodedMemo, line, column, bias || 1);
64953
+ if (index === -1) return OMapping(null, null, null, null);
64954
+ const segment = segments[index];
64955
+ if (segment.length === 1) return OMapping(null, null, null, null);
64956
+ const { names, resolvedSources } = map;
64957
+ return OMapping(resolvedSources[segment[SOURCES_INDEX]], segment[SOURCE_LINE] + 1, segment[SOURCE_COLUMN], segment.length === 5 ? names[segment[NAMES_INDEX]] : null);
64958
+ }
64959
+ function OMapping(source, line, column, name) {
64960
+ return {
64961
+ source,
64962
+ line,
64963
+ column,
64964
+ name
64965
+ };
64966
+ }
64967
+ function traceSegmentInternal(segments, memo, line, column, bias) {
64968
+ let index = memoizedBinarySearch(segments, column, memo, line);
64969
+ if (found) index = (bias === -1 ? upperBound : lowerBound)(segments, column, index);
64970
+ else if (bias === -1) index++;
64971
+ if (index === -1 || index === segments.length) return -1;
64972
+ return index;
64973
+ }
64974
+ //#endregion
64552
64975
  //#region ../core/dist/index.js
64553
64976
  var __create = Object.create;
64554
64977
  var __defProp = Object.defineProperty;
@@ -64618,7 +65041,7 @@ var AmbiguousProjectError = class extends Error {
64618
65041
  };
64619
65042
  const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
64620
65043
  const HTML_FILE_PATTERN = /\.html$/i;
64621
- const SOURCE_FILE_PATTERN = /\.(?:tsx?|jsx?|mts|mjs|[hH][tT][mM][lL])$/;
65044
+ const SOURCE_FILE_PATTERN = /\.(?:astro|tsx?|jsx?|mts|mjs|[hH][tT][mM][lL])$/;
64622
65045
  const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
64623
65046
  const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
64624
65047
  const MINIFIED_SNIFF_BYTES = 65536;
@@ -64996,7 +65419,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
64996
65419
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
64997
65420
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
64998
65421
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
64999
- const CORE_PACKAGE_VERSION = "0.9.2";
65422
+ const CORE_PACKAGE_VERSION = "0.9.3";
65000
65423
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
65001
65424
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
65002
65425
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -65375,6 +65798,7 @@ const FRAMEWORK_PACKAGES = {
65375
65798
  "@tanstack/react-start": "tanstack-start",
65376
65799
  "@remix-run/react": "remix",
65377
65800
  gatsby: "gatsby",
65801
+ astro: "astro",
65378
65802
  vite: "vite",
65379
65803
  "react-scripts": "cra",
65380
65804
  expo: "expo",
@@ -65596,10 +66020,16 @@ const getTopLevelBinding = (sourceFile, bindingName) => {
65596
66020
  }
65597
66021
  return null;
65598
66022
  };
65599
- const getFunctionLocalBindings = (functionNode, argumentsList) => {
66023
+ const getFunctionLocalBindings = (functionNode, argumentsList, argumentAnalysis, functionAnalysis) => {
65600
66024
  const localBindings = /* @__PURE__ */ new Map();
65601
66025
  functionNode.parameters.forEach((parameter, parameterIndex) => {
65602
- if (ts.isIdentifier(parameter.name)) localBindings.set(parameter.name.text, argumentsList[parameterIndex] ?? parameter.initializer ?? null);
66026
+ if (ts.isIdentifier(parameter.name)) {
66027
+ const argument = argumentsList[parameterIndex];
66028
+ localBindings.set(parameter.name.text, {
66029
+ expression: argument ?? parameter.initializer ?? null,
66030
+ analysis: argument ? argumentAnalysis : functionAnalysis
66031
+ });
66032
+ }
65603
66033
  });
65604
66034
  return localBindings;
65605
66035
  };
@@ -65735,8 +66165,8 @@ const getStaticConfigValueState = (expression, analysis, visitedExpressions = /*
65735
66165
  };
65736
66166
  if (ts.isIdentifier(unwrappedExpression)) {
65737
66167
  if (analysis.localBindings.has(unwrappedExpression.text)) {
65738
- const localInitializer = analysis.localBindings.get(unwrappedExpression.text);
65739
- return localInitializer ? getStaticConfigValueState(localInitializer, analysis, nextVisitedExpressions) : null;
66168
+ const localReference = analysis.localBindings.get(unwrappedExpression.text);
66169
+ return localReference?.expression ? getStaticConfigValueState(localReference.expression, localReference.analysis, nextVisitedExpressions) : null;
65740
66170
  }
65741
66171
  const topLevelBinding = getTopLevelBinding(analysis.sourceFile, unwrappedExpression.text);
65742
66172
  return topLevelBinding && ts.isExpression(topLevelBinding) ? getStaticConfigValueState(topLevelBinding, analysis, nextVisitedExpressions) : null;
@@ -65778,8 +66208,8 @@ const getReactCompilerFlagState = (expression, analysis, visitedExpressions = /*
65778
66208
  const assignedBinding = getAssignedPropertyInitializer(unwrappedExpression, "reactCompiler");
65779
66209
  if (assignedBinding.wasFound && assignedBinding.initializer) return { isEnabled: !isStaticallyDisabledConfigExpression(assignedBinding.initializer, analysis) };
65780
66210
  if (analysis.localBindings.has(unwrappedExpression.text)) {
65781
- const localInitializer = analysis.localBindings.get(unwrappedExpression.text);
65782
- return localInitializer ? getReactCompilerFlagState(localInitializer, analysis, nextVisitedExpressions) : null;
66211
+ const localReference = analysis.localBindings.get(unwrappedExpression.text);
66212
+ return localReference?.expression ? getReactCompilerFlagState(localReference.expression, localReference.analysis, nextVisitedExpressions) : null;
65783
66213
  }
65784
66214
  const topLevelBinding = getTopLevelBinding(analysis.sourceFile, unwrappedExpression.text);
65785
66215
  return topLevelBinding && ts.isExpression(topLevelBinding) ? getReactCompilerFlagState(topLevelBinding, analysis, nextVisitedExpressions) : null;
@@ -65802,8 +66232,8 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
65802
66232
  visitedExpressions.add(resolvedExpression);
65803
66233
  if (ts.isIdentifier(resolvedExpression)) {
65804
66234
  if (analysis.localBindings.has(resolvedExpression.text)) {
65805
- const localInitializer = analysis.localBindings.get(resolvedExpression.text);
65806
- return localInitializer ? getSelectedObjectProperty(localInitializer, propertyName, analysis, visitedExpressions) : null;
66235
+ const localReference = analysis.localBindings.get(resolvedExpression.text);
66236
+ return localReference?.expression ? getSelectedObjectProperty(localReference.expression, propertyName, localReference.analysis, visitedExpressions) : null;
65807
66237
  }
65808
66238
  const scopedBinding = getScopedConfigBinding(resolvedExpression);
65809
66239
  if (scopedBinding.wasFound) return scopedBinding.initializer ? getSelectedObjectProperty(scopedBinding.initializer, propertyName, analysis, visitedExpressions) : null;
@@ -65812,9 +66242,18 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
65812
66242
  }
65813
66243
  if (!ts.isObjectLiteralExpression(resolvedExpression)) return null;
65814
66244
  for (const property of [...resolvedExpression.properties].reverse()) {
65815
- if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === propertyName) return property.initializer;
65816
- if (ts.isMethodDeclaration(property) && getStaticPropertyName$2(property.name) === propertyName) return property;
65817
- if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) return property.name;
66245
+ if (ts.isPropertyAssignment(property) && getStaticPropertyName$2(property.name) === propertyName) return {
66246
+ node: property.initializer,
66247
+ analysis
66248
+ };
66249
+ if (ts.isMethodDeclaration(property) && getStaticPropertyName$2(property.name) === propertyName) return {
66250
+ node: property,
66251
+ analysis
66252
+ };
66253
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) return {
66254
+ node: property.name,
66255
+ analysis
66256
+ };
65818
66257
  if (ts.isSpreadAssignment(property)) {
65819
66258
  const spreadProperty = getSelectedObjectProperty(property.expression, propertyName, analysis, visitedExpressions);
65820
66259
  if (spreadProperty) return spreadProperty;
@@ -65827,8 +66266,8 @@ const configExpressionMayDefineProperty = (expression, propertyName, analysis) =
65827
66266
  while (ts.isParenthesizedExpression(resolvedExpression) || ts.isAsExpression(resolvedExpression) || ts.isTypeAssertionExpression(resolvedExpression) || ts.isSatisfiesExpression(resolvedExpression) || ts.isNonNullExpression(resolvedExpression)) resolvedExpression = resolvedExpression.expression;
65828
66267
  if (ts.isIdentifier(resolvedExpression)) {
65829
66268
  if (analysis.localBindings.has(resolvedExpression.text)) {
65830
- const localInitializer = analysis.localBindings.get(resolvedExpression.text);
65831
- return localInitializer ? configExpressionMayDefineProperty(localInitializer, propertyName, analysis) : true;
66269
+ const localReference = analysis.localBindings.get(resolvedExpression.text);
66270
+ return localReference?.expression ? configExpressionMayDefineProperty(localReference.expression, propertyName, localReference.analysis) : true;
65832
66271
  }
65833
66272
  const scopedBinding = getScopedConfigBinding(resolvedExpression);
65834
66273
  if (scopedBinding.wasFound) return scopedBinding.initializer ? configExpressionMayDefineProperty(scopedBinding.initializer, propertyName, analysis) : true;
@@ -65912,15 +66351,15 @@ const getReExportedConfigModules = (sourceFile, exportName) => {
65912
66351
  }
65913
66352
  return bindings;
65914
66353
  };
65915
- const analyzeConfigModuleExport = (filePath, exportName, allowCompilerTransform, importDepth, visitedModules, argumentsList = []) => {
66354
+ const analyzeConfigModuleExport = (filePath, exportName, allowCompilerTransform, importDepth, visitedModules, argumentsList = [], argumentAnalysis) => {
65916
66355
  if (!isFile(filePath) || importDepth > 4) return false;
65917
66356
  const moduleVisitKey = `${filePath}:${exportName}:${allowCompilerTransform}`;
65918
66357
  if (visitedModules.has(moduleVisitKey)) return false;
65919
66358
  const nextVisitedModules = new Set(visitedModules);
65920
66359
  nextVisitedModules.add(moduleVisitKey);
65921
- return analyzeConfigSourceFileExport(parseConfigSourceFile(filePath, NFS.readFileSync(filePath, "utf-8")), filePath, exportName, allowCompilerTransform, importDepth, nextVisitedModules, argumentsList);
66360
+ return analyzeConfigSourceFileExport(parseConfigSourceFile(filePath, NFS.readFileSync(filePath, "utf-8")), filePath, exportName, allowCompilerTransform, importDepth, nextVisitedModules, argumentsList, argumentAnalysis);
65922
66361
  };
65923
- const analyzeConfigSourceFileExport = (sourceFile, filePath, exportName, allowCompilerTransform, importDepth, visitedModules, argumentsList = []) => {
66362
+ const analyzeConfigSourceFileExport = (sourceFile, filePath, exportName, allowCompilerTransform, importDepth, visitedModules, argumentsList = [], argumentAnalysis) => {
65924
66363
  const analysis = {
65925
66364
  filePath,
65926
66365
  sourceFile,
@@ -65930,14 +66369,27 @@ const analyzeConfigSourceFileExport = (sourceFile, filePath, exportName, allowCo
65930
66369
  localBindings: /* @__PURE__ */ new Map(),
65931
66370
  activeFunctions: /* @__PURE__ */ new Set()
65932
66371
  };
66372
+ const resolvedArgumentAnalysis = argumentAnalysis ?? analysis;
65933
66373
  const exportedConfigNodes = getExportedConfigNodes(sourceFile, exportName);
65934
- return exportedConfigNodes.some((node) => ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node) ? analyzeConfigFunction(node, analysis, allowCompilerTransform, argumentsList) : analyzeConfigNode(node, analysis, allowCompilerTransform)) || getReExportedConfigModules(sourceFile, exportName).some((binding) => {
66374
+ return exportedConfigNodes.some((node) => {
66375
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) return analyzeConfigFunction(node, analysis, allowCompilerTransform, argumentsList, resolvedArgumentAnalysis);
66376
+ if (argumentsList.length > 0 && ts.isIdentifier(node)) {
66377
+ const topLevelBinding = getTopLevelBinding(sourceFile, node.text);
66378
+ if (topLevelBinding && ts.isFunctionLike(topLevelBinding)) return analyzeConfigFunction(topLevelBinding, analysis, allowCompilerTransform, argumentsList, resolvedArgumentAnalysis);
66379
+ const importBinding = getImportBinding(sourceFile, node.text);
66380
+ if (importBinding) {
66381
+ const importedFilePath = resolveImportedConfigFile(filePath, importBinding.moduleSpecifier);
66382
+ return Boolean(importedFilePath && analyzeConfigModuleExport(importedFilePath, importBinding.exportName, allowCompilerTransform, importDepth + 1, visitedModules, argumentsList, resolvedArgumentAnalysis));
66383
+ }
66384
+ }
66385
+ return analyzeConfigNode(node, analysis, allowCompilerTransform);
66386
+ }) || getReExportedConfigModules(sourceFile, exportName).some((binding) => {
65935
66387
  if (exportedConfigNodes.length > 0 && binding.isNamespace) return false;
65936
66388
  const importedFilePath = resolveImportedConfigFile(filePath, binding.moduleSpecifier);
65937
- return Boolean(importedFilePath && analyzeConfigModuleExport(importedFilePath, binding.exportName, allowCompilerTransform, importDepth + 1, visitedModules, argumentsList));
66389
+ return Boolean(importedFilePath && analyzeConfigModuleExport(importedFilePath, binding.exportName, allowCompilerTransform, importDepth + 1, visitedModules, argumentsList, resolvedArgumentAnalysis));
65938
66390
  });
65939
66391
  };
65940
- const analyzeConfigFunction = (functionNode, analysis, allowCompilerTransform, argumentsList = []) => {
66392
+ const analyzeConfigFunction = (functionNode, analysis, allowCompilerTransform, argumentsList = [], argumentAnalysis = analysis) => {
65941
66393
  if (!functionNode.body) return false;
65942
66394
  if (analysis.activeFunctions.has(functionNode.pos)) return false;
65943
66395
  const activeFunctions = new Set(analysis.activeFunctions);
@@ -65945,7 +66397,7 @@ const analyzeConfigFunction = (functionNode, analysis, allowCompilerTransform, a
65945
66397
  const functionAnalysis = {
65946
66398
  ...analysis,
65947
66399
  visitedNodes: /* @__PURE__ */ new Set(),
65948
- localBindings: getFunctionLocalBindings(functionNode, argumentsList),
66400
+ localBindings: getFunctionLocalBindings(functionNode, argumentsList, argumentAnalysis, analysis),
65949
66401
  activeFunctions
65950
66402
  };
65951
66403
  if (!ts.isBlock(functionNode.body)) return analyzeConfigNode(functionNode.body, functionAnalysis, allowCompilerTransform);
@@ -65964,7 +66416,7 @@ const analyzeConfigFunction = (functionNode, analysis, allowCompilerTransform, a
65964
66416
  const analyzeImportedConfig = ({ analysis, moduleSpecifier, exportName, allowCompilerTransform, argumentsList }) => {
65965
66417
  const importedFilePath = resolveImportedConfigFile(analysis.filePath, moduleSpecifier);
65966
66418
  if (!importedFilePath) return null;
65967
- return analyzeConfigModuleExport(importedFilePath, exportName, allowCompilerTransform, analysis.importDepth + 1, analysis.visitedModules, argumentsList);
66419
+ return analyzeConfigModuleExport(importedFilePath, exportName, allowCompilerTransform, analysis.importDepth + 1, analysis.visitedModules, argumentsList, analysis);
65968
66420
  };
65969
66421
  const analyzeConfigIdentifier = (identifier, analysis, allowCompilerTransform, isCompilerTransformCollection, excludedPropertyNames) => {
65970
66422
  const assignedReactCompiler = getAssignedPropertyInitializer(identifier, "reactCompiler");
@@ -65978,8 +66430,8 @@ const analyzeConfigIdentifier = (identifier, analysis, allowCompilerTransform, i
65978
66430
  if (assignedPlugins.wasFound) overriddenPropertyNames.add("plugins");
65979
66431
  if (assignedPresets.wasFound) overriddenPropertyNames.add("presets");
65980
66432
  if (analysis.localBindings.has(identifier.text)) {
65981
- const localInitializer = analysis.localBindings.get(identifier.text);
65982
- return Boolean(localInitializer && analyzeConfigNode(localInitializer, analysis, allowCompilerTransform, isCompilerTransformCollection, overriddenPropertyNames));
66433
+ const localReference = analysis.localBindings.get(identifier.text);
66434
+ return Boolean(localReference?.expression && analyzeConfigNode(localReference.expression, localReference.analysis, allowCompilerTransform, isCompilerTransformCollection, overriddenPropertyNames));
65983
66435
  }
65984
66436
  const scopedBinding = getScopedConfigBinding(identifier);
65985
66437
  if (scopedBinding.wasFound) return Boolean(scopedBinding.initializer && analyzeConfigNode(scopedBinding.initializer, analysis, allowCompilerTransform, isCompilerTransformCollection, overriddenPropertyNames));
@@ -66045,7 +66497,7 @@ const analyzeConfigCallTarget = (callExpression, analysis, allowCompilerTransfor
66045
66497
  }
66046
66498
  const selectedProperty = getSelectedObjectProperty(target.expression, propertyName, analysis);
66047
66499
  if (selectedProperty === null) return null;
66048
- return ts.isFunctionLike(selectedProperty) ? analyzeConfigFunction(selectedProperty, analysis, allowCompilerTransform, callExpression.arguments) : analyzeConfigNode(selectedProperty, analysis, allowCompilerTransform);
66500
+ return ts.isFunctionLike(selectedProperty.node) ? analyzeConfigFunction(selectedProperty.node, selectedProperty.analysis, allowCompilerTransform, callExpression.arguments, analysis) : analyzeConfigNode(selectedProperty.node, selectedProperty.analysis, allowCompilerTransform);
66049
66501
  };
66050
66502
  const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTransformCollection = false, excludedPropertyNames) => {
66051
66503
  const excludedPropertiesVisitKey = excludedPropertyNames ? [...excludedPropertyNames].sort().join(",") : "";
@@ -66145,7 +66597,7 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
66145
66597
  if (ts.isIdentifier(node.expression)) {
66146
66598
  if (analysis.localBindings.has(node.expression.text) || getScopedConfigBinding(node.expression).wasFound) {
66147
66599
  const selectedProperty = getSelectedObjectProperty(node.expression, node.name.text, analysis);
66148
- return Boolean(selectedProperty && analyzeConfigNode(selectedProperty, analysis, allowCompilerTransform));
66600
+ return Boolean(selectedProperty && analyzeConfigNode(selectedProperty.node, selectedProperty.analysis, allowCompilerTransform));
66149
66601
  }
66150
66602
  const importBinding = getImportBinding(analysis.sourceFile, node.expression.text);
66151
66603
  if (importBinding?.isNamespace) {
@@ -66159,7 +66611,7 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
66159
66611
  return false;
66160
66612
  }
66161
66613
  const selectedProperty = getSelectedObjectProperty(node.expression, node.name.text, analysis);
66162
- if (selectedProperty) return analyzeConfigNode(selectedProperty, analysis, allowCompilerTransform);
66614
+ if (selectedProperty) return analyzeConfigNode(selectedProperty.node, selectedProperty.analysis, allowCompilerTransform);
66163
66615
  return false;
66164
66616
  }
66165
66617
  return analyzeConfigNode(node.expression, analysis, allowCompilerTransform);
@@ -66168,7 +66620,7 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
66168
66620
  if (ts.isIdentifier(node.expression)) {
66169
66621
  if (analysis.localBindings.has(node.expression.text) || getScopedConfigBinding(node.expression).wasFound) {
66170
66622
  const selectedProperty = getSelectedObjectProperty(node.expression, node.argumentExpression.text, analysis);
66171
- return Boolean(selectedProperty && analyzeConfigNode(selectedProperty, analysis, allowCompilerTransform));
66623
+ return Boolean(selectedProperty && analyzeConfigNode(selectedProperty.node, selectedProperty.analysis, allowCompilerTransform));
66172
66624
  }
66173
66625
  const importBinding = getImportBinding(analysis.sourceFile, node.expression.text);
66174
66626
  if (importBinding?.isNamespace) {
@@ -66181,7 +66633,7 @@ const analyzeConfigNode = (node, analysis, allowCompilerTransform, isCompilerTra
66181
66633
  }));
66182
66634
  }
66183
66635
  const selectedProperty = getSelectedObjectProperty(node.expression, node.argumentExpression.text, analysis);
66184
- if (selectedProperty) return analyzeConfigNode(selectedProperty, analysis, allowCompilerTransform);
66636
+ if (selectedProperty) return analyzeConfigNode(selectedProperty.node, selectedProperty.analysis, allowCompilerTransform);
66185
66637
  return false;
66186
66638
  }
66187
66639
  return analyzeConfigNode(node.expression, analysis, allowCompilerTransform);
@@ -68422,7 +68874,7 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
68422
68874
  const getDiagnosticRuleIdentity = (diagnostic) => ({
68423
68875
  ruleKey: `${diagnostic.plugin}/${diagnostic.rule}`,
68424
68876
  category: diagnostic.category,
68425
- tags: diagnostic.plugin === "react-doctor" ? reactDoctorPlugin.rules[diagnostic.rule]?.tags ?? [] : []
68877
+ tags: diagnostic.plugin === "react-doctor" ? REACT_DOCTOR_RULE_REGISTRY[diagnostic.rule]?.tags ?? [] : []
68426
68878
  });
68427
68879
  const compileIgnoredFilePatterns = (userConfig) => {
68428
68880
  const files = userConfig?.ignore?.files;
@@ -68558,6 +69010,7 @@ const classifyPackageRole = (filename) => {
68558
69010
  * Resolves the absolute path to read for a diagnostic's `filePath`,
68559
69011
  * accounting for the various shapes oxlint emits:
68560
69012
  *
69013
+ * - `file://` URL — decode to an absolute filesystem path.
68561
69014
  * - Absolute POSIX (`/abs/path/file.tsx`) — pass through.
68562
69015
  * - Absolute Windows (`C:/...` or `C:\...`) — pass through.
68563
69016
  * - `./relative` or bare relative — join against `rootDirectory`.
@@ -68567,6 +69020,7 @@ const classifyPackageRole = (filename) => {
68567
69020
  * use one canonical resolution path.
68568
69021
  */
68569
69022
  const resolveCandidateReadPath = (rootDirectory, filePath) => {
69023
+ if (filePath.startsWith("file://")) return fileURLToPath(filePath);
68570
69024
  const normalizedFile = filePath.replace(/\\/g, "/");
68571
69025
  if (normalizedFile.startsWith("/") || /^[a-zA-Z]:\//.test(normalizedFile) || /^[a-zA-Z]:\\/.test(filePath)) return filePath;
68572
69026
  return `${rootDirectory.replace(/\\/g, "/").replace(/\/$/, "")}/${normalizedFile.replace(/^\.\//, "")}`;
@@ -68768,7 +69222,7 @@ const buildDiagnosticPipeline = (input) => {
68768
69222
  };
68769
69223
  const shouldAutoSuppress = (diagnostic) => {
68770
69224
  if (diagnostic.plugin !== "react-doctor") return false;
68771
- const rule = reactDoctorPlugin.rules[diagnostic.rule];
69225
+ const rule = REACT_DOCTOR_RULE_REGISTRY[diagnostic.rule];
68772
69226
  if (!rule?.tags?.includes("test-noise")) return false;
68773
69227
  if (rule.tags.includes("migration-hint")) return false;
68774
69228
  return getFileContext(diagnostic.filePath) !== "production";
@@ -69200,6 +69654,7 @@ var OxlintConcurrency = class extends Reference("react-doctor/OxlintConcurrency"
69200
69654
  if (Number.isInteger(parsed) && parsed > 0) return resolveScanConcurrency(parsed);
69201
69655
  return resolveAutoScanConcurrency();
69202
69656
  } }) {};
69657
+ var OxlintSpawnSlots = class extends Reference("react-doctor/OxlintSpawnSlots", { defaultValue: () => null }) {};
69203
69658
  /**
69204
69659
  * Three-state control for overlapping the dead-code pass with the lint pass —
69205
69660
  * forking dead-code as a child fiber that runs DURING lint instead of strictly
@@ -71550,8 +72005,8 @@ const getCapabilities = (project) => {
71550
72005
  const cached = capabilitiesByProject.get(project);
71551
72006
  if (cached !== void 0) return cached;
71552
72007
  const capabilities = new Set(buildCapabilities(project));
71553
- const packageJson = readPackageJson$1(Path.join(project.rootDirectory, "package.json"));
71554
- const targetBlankOpenerProtection = detectTargetBlankOpenerProtection(project.rootDirectory, packageJson);
72008
+ const capabilityRootDirectory = isFile(Path.join(project.rootDirectory, "package.json")) ? project.rootDirectory : findNearestAncestorPackageJson(project.rootDirectory) ?? project.rootDirectory;
72009
+ const targetBlankOpenerProtection = detectTargetBlankOpenerProtection(capabilityRootDirectory, readPackageJson$1(Path.join(capabilityRootDirectory, "package.json")));
71555
72010
  if (targetBlankOpenerProtection !== void 0) capabilities.add("target-blank-needs-explicit-protection");
71556
72011
  if (targetBlankOpenerProtection === "noreferrer") capabilities.add("target-blank-needs-noreferrer");
71557
72012
  capabilitiesByProject.set(project, capabilities);
@@ -71581,7 +72036,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
71581
72036
  const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
71582
72037
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
71583
72038
  const includedTags = options.includedTags ?? /* @__PURE__ */ new Set();
71584
- const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
72039
+ const enabledScanRules = REACT_DOCTOR_SCAN_RULES.flatMap((entry) => {
71585
72040
  const rule = entry.rule;
71586
72041
  const scan = rule.scan;
71587
72042
  if (typeof scan !== "function") return [];
@@ -72042,47 +72497,55 @@ const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
72042
72497
  const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
72043
72498
  return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
72044
72499
  };
72045
- let availableSlots = -1;
72046
- const waiters = [];
72047
- const releaseSlot = () => {
72048
- const nextWaiter = waiters.shift();
72049
- if (nextWaiter !== void 0) nextWaiter();
72050
- else availableSlots += 1;
72500
+ const createWorkerSlots = (input) => {
72501
+ let availableSlotCount = input.slotCount;
72502
+ const waiters = [];
72503
+ const releaseSlot = () => {
72504
+ const nextWaiter = waiters.shift();
72505
+ if (nextWaiter === void 0) {
72506
+ availableSlotCount += 1;
72507
+ return;
72508
+ }
72509
+ nextWaiter.abortSignal?.removeEventListener("abort", nextWaiter.onAbort);
72510
+ nextWaiter.resolve();
72511
+ };
72512
+ const acquireSlot = async (abortSignal) => {
72513
+ if (abortSignal?.aborted) throw input.createAbortError();
72514
+ if (availableSlotCount > 0) {
72515
+ availableSlotCount -= 1;
72516
+ return;
72517
+ }
72518
+ await new Promise((resolve, reject) => {
72519
+ const onAbort = () => {
72520
+ const waiterIndex = waiters.indexOf(waiter);
72521
+ if (waiterIndex !== -1) waiters.splice(waiterIndex, 1);
72522
+ reject(input.createAbortError());
72523
+ };
72524
+ const waiter = {
72525
+ resolve,
72526
+ abortSignal,
72527
+ onAbort
72528
+ };
72529
+ waiters.push(waiter);
72530
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
72531
+ });
72532
+ };
72533
+ return { run: async (task, abortSignal) => {
72534
+ await acquireSlot(abortSignal);
72535
+ try {
72536
+ return await task();
72537
+ } finally {
72538
+ releaseSlot();
72539
+ }
72540
+ } };
72051
72541
  };
72052
- /**
72053
- * Runs `task` once a dead-code worker slot is free, releasing the slot when the
72054
- * task settles (success or failure). With a high cap (roomy machine) every
72055
- * caller proceeds immediately; with a low cap (constrained runner) callers
72056
- * queue and run as slots free.
72057
- *
72058
- * `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
72059
- * this caller is queued, the call rejects without acquiring a slot or running
72060
- * `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
72061
- * then spawn a child only to tear it down. A queued caller that aborts removes
72062
- * its own waiter so a later release never hands a slot to a dead request.
72063
- */
72542
+ let deadCodeWorkerSlots = null;
72064
72543
  const withDeadCodeWorkerSlot = async (task, abortSignal) => {
72065
- if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
72066
- if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
72067
- if (availableSlots > 0) availableSlots -= 1;
72068
- else await new Promise((resolve, reject) => {
72069
- const waiter = () => {
72070
- abortSignal?.removeEventListener("abort", onAbort);
72071
- resolve();
72072
- };
72073
- const onAbort = () => {
72074
- const queuedIndex = waiters.indexOf(waiter);
72075
- if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
72076
- reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
72077
- };
72078
- waiters.push(waiter);
72079
- abortSignal?.addEventListener("abort", onAbort, { once: true });
72544
+ deadCodeWorkerSlots ??= createWorkerSlots({
72545
+ slotCount: resolveDeadCodeConcurrency(),
72546
+ createAbortError: () => /* @__PURE__ */ new Error("Dead-code worker aborted.")
72080
72547
  });
72081
- try {
72082
- return await task();
72083
- } finally {
72084
- releaseSlot();
72085
- }
72548
+ return deadCodeWorkerSlots.run(task, abortSignal);
72086
72549
  };
72087
72550
  const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
72088
72551
  const resolveReactDoctorCacheDir = (projectDirectory) => {
@@ -72875,7 +73338,10 @@ var Git = class Git extends Service()("react-doctor/Git") {
72875
73338
  if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
72876
73339
  const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
72877
73340
  cwd: input.directory,
72878
- env: input.env,
73341
+ env: input.command === "git" ? {
73342
+ ...input.env,
73343
+ GIT_DIR: void 0
73344
+ } : input.env,
72879
73345
  extendEnv: true
72880
73346
  }));
72881
73347
  const maxStdoutBytes = input.maxStdoutBytes;
@@ -73148,7 +73614,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
73148
73614
  "--diff-filter=ACMR",
73149
73615
  "--relative"
73150
73616
  ]).pipe(map$3((result) => {
73151
- if (result.status !== 0) return [];
73617
+ if (result.status !== 0) return null;
73152
73618
  return splitNullSeparated(result.stdout);
73153
73619
  })),
73154
73620
  showStagedContent: (directory, relativePath, options) => runCommand({
@@ -73606,12 +74072,12 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
73606
74072
  const settingsRootDirectory = resolveSettingsRootDirectory(project.rootDirectory);
73607
74073
  const enabledReactDoctorRules = {};
73608
74074
  for (const registryEntry of REACT_DOCTOR_RULES) {
73609
- const rule = reactDoctorPlugin.rules[registryEntry.id];
74075
+ const rule = REACT_DOCTOR_RULE_REGISTRY[registryEntry.id];
73610
74076
  if (!rule) continue;
73611
74077
  if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
73612
74078
  if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
73613
74079
  if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
73614
- if (rule.scan !== void 0) continue;
74080
+ if (rule.isScanRule) continue;
73615
74081
  if (customRulesOnly && registryEntry.originallyExternal) continue;
73616
74082
  if (rule.framework !== "global" && !rule.requires) continue;
73617
74083
  if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen, includedTags)) continue;
@@ -74236,6 +74702,50 @@ const columnOfUtf8Offset = (sourceBuffer, utf8Offset) => {
74236
74702
  while (lineStartOffset > 0 && sourceBuffer[lineStartOffset - 1] !== 10) lineStartOffset--;
74237
74703
  return sourceBuffer.subarray(lineStartOffset, boundedOffset).toString("utf8").length + 1;
74238
74704
  };
74705
+ const utf8OffsetFromSourcePosition = (sourceBuffer, lineNumber, zeroBasedColumn) => {
74706
+ if (lineNumber < 1 || zeroBasedColumn < 0) return null;
74707
+ const sourceLines = sourceBuffer.toString("utf8").split("\n");
74708
+ const sourceLine = sourceLines[lineNumber - 1];
74709
+ if (sourceLine === void 0 || zeroBasedColumn > sourceLine.length) return null;
74710
+ const precedingLines = sourceLines.slice(0, lineNumber - 1).join("\n");
74711
+ return (lineNumber === 1 ? 0 : Buffer.byteLength(precedingLines) + Buffer.byteLength("\n")) + Buffer.byteLength(sourceLine.slice(0, zeroBasedColumn));
74712
+ };
74713
+ const mapPreparedSourceSpan = (span, preparedSourceMap) => {
74714
+ const generatedLine = lineOfUtf8Offset(preparedSourceMap.generatedBuffer, span.offset);
74715
+ const generatedColumn = columnOfUtf8Offset(preparedSourceMap.generatedBuffer, span.offset) - 1;
74716
+ const originalStart = originalPositionFor(preparedSourceMap.traceMap, {
74717
+ line: generatedLine,
74718
+ column: generatedColumn
74719
+ });
74720
+ if (originalStart.source === null || originalStart.line === null || originalStart.column === null) return null;
74721
+ const originalOffset = utf8OffsetFromSourcePosition(preparedSourceMap.sourceBuffer, originalStart.line, originalStart.column);
74722
+ if (originalOffset === null) return null;
74723
+ const generatedSpan = preparedSourceMap.generatedBuffer.subarray(span.offset, span.offset + span.length);
74724
+ return {
74725
+ offset: originalOffset,
74726
+ length: preparedSourceMap.sourceBuffer.subarray(originalOffset, originalOffset + generatedSpan.length).equals(generatedSpan) ? generatedSpan.length : 0,
74727
+ line: originalStart.line,
74728
+ column: originalStart.column + 1
74729
+ };
74730
+ };
74731
+ const mapPreparedSourceLabels = (labels, preparedSourceMap) => {
74732
+ const primaryLabel = labels[0];
74733
+ if (primaryLabel === void 0) return null;
74734
+ const primarySpan = mapPreparedSourceSpan(primaryLabel.span, preparedSourceMap);
74735
+ if (primarySpan === null) return null;
74736
+ const mappedLabels = [{
74737
+ ...primaryLabel,
74738
+ span: primarySpan
74739
+ }];
74740
+ for (const relatedLabel of labels.slice(1)) {
74741
+ const relatedSpan = mapPreparedSourceSpan(relatedLabel.span, preparedSourceMap);
74742
+ if (relatedSpan !== null) mappedLabels.push({
74743
+ ...relatedLabel,
74744
+ span: relatedSpan
74745
+ });
74746
+ }
74747
+ return mappedLabels;
74748
+ };
74239
74749
  const REDACTED_PLACEHOLDER = "<redacted>";
74240
74750
  const KEEP_PREFIX = `$1${REDACTED_PLACEHOLDER}`;
74241
74751
  const KNOWN_SECRET_RULES = [
@@ -74878,7 +75388,7 @@ const PLUGIN_CATEGORY_MAP = {
74878
75388
  };
74879
75389
  const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
74880
75390
  const getRuleRecommendation = (ruleName, project) => {
74881
- const rule = reactDoctorPlugin.rules[ruleName];
75391
+ const rule = REACT_DOCTOR_RULE_REGISTRY[ruleName];
74882
75392
  if (!rule) return void 0;
74883
75393
  if (rule.recommendationFor) {
74884
75394
  const capabilities = getCapabilities(project);
@@ -74887,8 +75397,8 @@ const getRuleRecommendation = (ruleName, project) => {
74887
75397
  }
74888
75398
  return rule.recommendation;
74889
75399
  };
74890
- const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
74891
- const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
75400
+ const getRuleCategory = (ruleName) => REACT_DOCTOR_RULE_REGISTRY[ruleName]?.category;
75401
+ const getRuleTitle = (ruleName) => REACT_DOCTOR_RULE_REGISTRY[ruleName]?.title;
74892
75402
  const resolveDiagnosticTitle = (plugin, rule) => {
74893
75403
  if (plugin !== "react-hooks-js") return getRuleTitle(rule);
74894
75404
  if (rule === "error-boundaries") return REACT_ERROR_BOUNDARY_TITLE;
@@ -74943,7 +75453,7 @@ const resolveDiagnosticCategory = (plugin, rule) => {
74943
75453
  if (plugin === "react-hooks-js" && rule === "error-boundaries") return "Bugs";
74944
75454
  return getRuleCategory(rule) ?? lookupOwnString(PLUGIN_CATEGORY_MAP, plugin) ?? "Bugs";
74945
75455
  };
74946
- const resolveMatchByOccurrence = (rule, category) => OCCURRENCE_MATCHED_CATEGORIES.has(category) || Boolean(reactDoctorPlugin.rules[rule]?.matchByOccurrence);
75456
+ const resolveMatchByOccurrence = (rule, category) => OCCURRENCE_MATCHED_CATEGORIES.has(category) || Boolean(REACT_DOCTOR_RULE_REGISTRY[rule]?.matchByOccurrence);
74947
75457
  /**
74948
75458
  * Maps oxlint's non-primary labels (`labels[1..]`) into related source
74949
75459
  * locations. Editors surface these as a diagnostic's
@@ -74980,7 +75490,7 @@ const isOxlintOutput = (value) => isRecord$1(value) && Array.isArray(value.diagn
74980
75490
  * to the first `{`. Raises `OxlintOutputUnparseable` when the
74981
75491
  * stdout isn't valid JSON or doesn't carry a `diagnostics` array.
74982
75492
  */
74983
- const parseOxlintOutput = (stdout, project, rootDirectory, sourcePathByLintPath) => {
75493
+ const parseOxlintOutput = (stdout, project, rootDirectory, sourcePathByLintPath, sourceMapByLintPath) => {
74984
75494
  if (!stdout) return [];
74985
75495
  const jsonStart = stdout.indexOf("{");
74986
75496
  const sanitizedStdout = jsonStart > 0 ? stdout.slice(jsonStart) : stdout;
@@ -74998,6 +75508,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory, sourcePathByLintPath)
74998
75508
  const sourceLinesCache = /* @__PURE__ */ new Map();
74999
75509
  const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
75000
75510
  const resolveMappedSourceFilename = (filename) => sourcePathByLintPath?.get(Path.normalize(resolveAbsolutePath(filename)));
75511
+ const resolvePreparedSourceMap = (filename) => sourceMapByLintPath?.get(Path.normalize(resolveAbsolutePath(filename)));
75001
75512
  const resolveSourceFilename = (filename) => resolveMappedSourceFilename(filename) ?? filename;
75002
75513
  const readSourceBuffer = (filename) => {
75003
75514
  const absolutePath = resolveAbsolutePath(resolveSourceFilename(filename));
@@ -75029,7 +75540,19 @@ const parseOxlintOutput = (stdout, project, rootDirectory, sourcePathByLintPath)
75029
75540
  minifiedFileCache.set(absolutePath, minified);
75030
75541
  return minified;
75031
75542
  };
75032
- return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(resolveSourceFilename(diagnostic.filename)) && !(TYPESCRIPT_DECLARATION_FILE_PATTERN.test(diagnostic.filename) && OXLINT_IGNORED_DECLARATION_DIAGNOSTIC_CODES.has(diagnostic.code)) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
75543
+ return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.flatMap((diagnostic) => {
75544
+ const preparedSourceMap = resolvePreparedSourceMap(diagnostic.filename);
75545
+ if (preparedSourceMap === void 0) return [diagnostic];
75546
+ const { plugin, rule } = parseRuleCode(diagnostic.code);
75547
+ if (plugin !== "react-doctor" || !REACT_DOCTOR_RULE_REGISTRY[rule]?.tags?.includes("design")) return [];
75548
+ const labels = mapPreparedSourceLabels(diagnostic.labels, preparedSourceMap);
75549
+ const sourceFilename = resolveMappedSourceFilename(diagnostic.filename);
75550
+ return labels === null || sourceFilename === void 0 ? [] : [{
75551
+ ...diagnostic,
75552
+ filename: sourceFilename,
75553
+ labels
75554
+ }];
75555
+ }).filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(resolveSourceFilename(diagnostic.filename)) && !(TYPESCRIPT_DECLARATION_FILE_PATTERN.test(diagnostic.filename) && OXLINT_IGNORED_DECLARATION_DIAGNOSTIC_CODES.has(diagnostic.code)) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
75033
75556
  const { plugin, rule } = parseRuleCode(diagnostic.code);
75034
75557
  const primaryLabel = diagnostic.labels[0];
75035
75558
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
@@ -75107,11 +75630,12 @@ const SANITIZED_ENV = buildOxlintChildEnv(process.env);
75107
75630
  * The first three are splittable (the caller's binary-split retry
75108
75631
  * shrinks the batch and re-spawns); the fourth isn't.
75109
75632
  */
75110
- const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES, abortSignal) => new Promise((resolve, reject) => {
75633
+ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES, abortSignal, onSpawn) => new Promise((resolve, reject) => {
75111
75634
  if (abortSignal?.aborted) {
75112
75635
  reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
75113
75636
  return;
75114
75637
  }
75638
+ onSpawn?.();
75115
75639
  const child = spawn(nodeBinaryPath, buildProfiledNodeArguments({
75116
75640
  argumentsList: args,
75117
75641
  cpuProfileDirectory: SANITIZED_ENV.REACT_DOCTOR_CPU_PROFILE_DIR,
@@ -75247,7 +75771,7 @@ const isParallelismRelatedSpawnError = (error) => {
75247
75771
  * loop with a slimmer config in that case.
75248
75772
  */
75249
75773
  const spawnLintBatches = async (input) => {
75250
- const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
75774
+ const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, sourcePathByLintPath, sourceMapByLintPath, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
75251
75775
  const resolveSourcePath = (filePath) => {
75252
75776
  const absoluteFilePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory, filePath);
75253
75777
  return sourcePathByLintPath?.get(Path.normalize(absoluteFilePath)) ?? filePath;
@@ -75271,7 +75795,21 @@ const spawnLintBatches = async (input) => {
75271
75795
  }
75272
75796
  const batchArgs = [...baseArgs, ...batch];
75273
75797
  try {
75274
- return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory, sourcePathByLintPath);
75798
+ const spawnBatch = () => {
75799
+ if (isPastDeadline()) {
75800
+ deadlineSkippedFiles.push(...batch);
75801
+ batchState.deadlineSkippedFileCount += batch.length;
75802
+ return Promise.resolve(null);
75803
+ }
75804
+ return spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal, () => {
75805
+ if (batchState.didStart) return;
75806
+ batchState.didStart = true;
75807
+ startedFileCount += batchState.initialFileCount;
75808
+ });
75809
+ };
75810
+ const stdout = input.spawnSlots === void 0 ? await spawnBatch() : await input.spawnSlots.run(spawnBatch, signal);
75811
+ if (stdout === null) return [];
75812
+ return parseOxlintOutput(stdout, project, rootDirectory, sourcePathByLintPath, sourceMapByLintPath);
75275
75813
  } catch (error) {
75276
75814
  if (!isSplittableReactDoctorError(error)) throw error;
75277
75815
  if (isPastDeadline()) {
@@ -75315,14 +75853,15 @@ const spawnLintBatches = async (input) => {
75315
75853
  deadlineSkippedFiles.push(...batch);
75316
75854
  return [];
75317
75855
  }
75318
- startedFileCount += batch.length;
75319
75856
  const batchState = {
75320
75857
  deadlineMs: null,
75321
- deadlineSkippedFileCount: 0
75858
+ deadlineSkippedFileCount: 0,
75859
+ didStart: false,
75860
+ initialFileCount: batch.length
75322
75861
  };
75323
75862
  const batchDiagnostics = await spawnLintBatch(batch, 0, batchState);
75324
75863
  scannedFileCount += batch.length - batchState.deadlineSkippedFileCount;
75325
- if (passOnFileProgress) {
75864
+ if (passOnFileProgress && batchState.didStart) {
75326
75865
  displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
75327
75866
  passOnFileProgress(displayedFileCount, totalFileCount);
75328
75867
  }
@@ -75406,8 +75945,8 @@ const validateRuleRegistration = () => {
75406
75945
  for (const fullKey of ALL_REACT_DOCTOR_RULE_KEYS) {
75407
75946
  const ruleName = fullKey.replace(/^react-doctor\//, "");
75408
75947
  if (!getRuleCategory(ruleName)) missingCategory.push(fullKey);
75409
- if (!reactDoctorPlugin.rules[ruleName]?.recommendation) missingHelp.push(fullKey);
75410
- if (FRAMEWORK_SPECIFIC_RULE_KEYS.has(fullKey) && !reactDoctorPlugin.rules[ruleName]?.requires) missingMetadata.push(fullKey);
75948
+ if (!REACT_DOCTOR_RULE_REGISTRY[ruleName]?.recommendation) missingHelp.push(fullKey);
75949
+ if (FRAMEWORK_SPECIFIC_RULE_KEYS.has(fullKey) && !REACT_DOCTOR_RULE_REGISTRY[ruleName]?.requires) missingMetadata.push(fullKey);
75411
75950
  }
75412
75951
  if (missingCategory.length === 0 && missingHelp.length === 0 && missingMetadata.length === 0) return;
75413
75952
  const detail = [
@@ -75710,20 +76249,47 @@ const prepareHtmlScriptSource = (sourceBuffer) => {
75710
76249
  lintBuffer: maskedBuffer ?? sourceBuffer
75711
76250
  };
75712
76251
  };
75713
- const prepareHtmlLintSources = (rootDirectory, temporaryDirectory, candidateFiles) => {
76252
+ const ASTRO_FILE_PATTERN = /\.astro$/;
76253
+ const prepareLintSources = (rootDirectory, temporaryDirectory, candidateFiles) => {
75714
76254
  const lintFiles = [];
75715
76255
  const sourcePathByLintPath = /* @__PURE__ */ new Map();
76256
+ const sourceMapByLintPath = /* @__PURE__ */ new Map();
75716
76257
  const sizeByLintPath = /* @__PURE__ */ new Map();
75717
76258
  const htmlSourcesDirectory = Path.join(temporaryDirectory, "html-sources");
76259
+ const astroSourcesDirectory = Path.join(temporaryDirectory, "astro-sources");
75718
76260
  let htmlFileIndex = 0;
76261
+ let astroFileIndex = 0;
75719
76262
  let hasThreeModuleImport = false;
75720
76263
  for (const candidateFile of candidateFiles) {
75721
- if (!HTML_FILE_PATTERN.test(candidateFile)) {
76264
+ if (!HTML_FILE_PATTERN.test(candidateFile) && !ASTRO_FILE_PATTERN.test(candidateFile)) {
75722
76265
  lintFiles.push(candidateFile);
75723
76266
  continue;
75724
76267
  }
75725
76268
  const absoluteSourcePath = Path.isAbsolute(candidateFile) ? candidateFile : Path.resolve(rootDirectory, candidateFile);
75726
76269
  const sourceBuffer = NFS.readFileSync(absoluteSourcePath);
76270
+ if (ASTRO_FILE_PATTERN.test(candidateFile)) {
76271
+ const compilerSourcePath = absoluteSourcePath.replaceAll("\\", "/");
76272
+ const convertedSource = convertToTSX(sourceBuffer.toString("utf8"), {
76273
+ filename: compilerSourcePath,
76274
+ sourcemap: "external",
76275
+ includeScripts: true
76276
+ });
76277
+ if (astroFileIndex === 0) NFS.mkdirSync(astroSourcesDirectory, { recursive: true });
76278
+ const lintPath = Path.join(astroSourcesDirectory, `${astroFileIndex}.tsx`);
76279
+ const generatedBuffer = Buffer.from(convertedSource.code);
76280
+ NFS.writeFileSync(lintPath, generatedBuffer);
76281
+ lintFiles.push(candidateFile);
76282
+ lintFiles.push(lintPath);
76283
+ sourcePathByLintPath.set(Path.resolve(lintPath), candidateFile);
76284
+ sourceMapByLintPath.set(Path.resolve(lintPath), {
76285
+ traceMap: new TraceMap(JSON.stringify(convertedSource.map)),
76286
+ generatedBuffer,
76287
+ sourceBuffer
76288
+ });
76289
+ sizeByLintPath.set(lintPath, sourceBuffer.length);
76290
+ astroFileIndex++;
76291
+ continue;
76292
+ }
75727
76293
  const { executableScriptBodies, lintBuffer } = prepareHtmlScriptSource(sourceBuffer);
75728
76294
  if (htmlFileIndex === 0) NFS.mkdirSync(htmlSourcesDirectory, { recursive: true });
75729
76295
  const lintPath = Path.join(htmlSourcesDirectory, `${htmlFileIndex}.astro`);
@@ -75737,6 +76303,7 @@ const prepareHtmlLintSources = (rootDirectory, temporaryDirectory, candidateFile
75737
76303
  return {
75738
76304
  lintFiles,
75739
76305
  sourcePathByLintPath,
76306
+ sourceMapByLintPath,
75740
76307
  sizeByLintPath,
75741
76308
  hasThreeModuleImport
75742
76309
  };
@@ -75955,10 +76522,10 @@ const runOxlint = async (options) => {
75955
76522
  rootDirectory,
75956
76523
  candidateFiles
75957
76524
  });
75958
- const preparedHtmlSources = prepareHtmlLintSources(rootDirectory, configDirectory, candidateFiles);
75959
- const lintFiles = preparedHtmlSources.lintFiles;
75960
- const hasHtmlFiles = preparedHtmlSources.sourcePathByLintPath.size > 0;
75961
- const lintProject = !project.hasThree && preparedHtmlSources.hasThreeModuleImport ? {
76525
+ const preparedLintSources = prepareLintSources(rootDirectory, configDirectory, candidateFiles);
76526
+ const lintFiles = preparedLintSources.lintFiles;
76527
+ const hasPreparedFiles = preparedLintSources.sourcePathByLintPath.size > 0;
76528
+ const lintProject = !project.hasThree && preparedLintSources.hasThreeModuleImport ? {
75962
76529
  ...project,
75963
76530
  hasThree: true
75964
76531
  } : project;
@@ -75980,7 +76547,7 @@ const runOxlint = async (options) => {
75980
76547
  sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
75981
76548
  });
75982
76549
  const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
75983
- if (sizeByFile !== null) for (const [lintPath, sizeBytes] of preparedHtmlSources.sizeByLintPath) sizeByFile.set(lintPath, sizeBytes);
76550
+ if (sizeByFile !== null) for (const [lintPath, sizeBytes] of preparedLintSources.sizeByLintPath) sizeByFile.set(lintPath, sizeBytes);
75984
76551
  const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
75985
76552
  baseArgs: passBaseArgs,
75986
76553
  files: passFiles,
@@ -76010,7 +76577,8 @@ const runOxlint = async (options) => {
76010
76577
  rootDirectory,
76011
76578
  nodeBinaryPath,
76012
76579
  project: lintProject,
76013
- sourcePathByLintPath: preparedHtmlSources.sourcePathByLintPath,
76580
+ sourcePathByLintPath: preparedLintSources.sourcePathByLintPath,
76581
+ sourceMapByLintPath: preparedLintSources.sourceMapByLintPath,
76014
76582
  onPartialFailure: reportPartialFailure,
76015
76583
  onAnalyzedFiles: (filePaths) => {
76016
76584
  analyzedFiles = filePaths;
@@ -76019,6 +76587,7 @@ const runOxlint = async (options) => {
76019
76587
  spawnTimeoutMs,
76020
76588
  outputMaxBytes,
76021
76589
  concurrency: options.concurrency,
76590
+ spawnSlots: options.spawnSlots,
76022
76591
  signal: options.signal,
76023
76592
  deadlineEpochMs: options.deadlineEpochMs
76024
76593
  });
@@ -76045,7 +76614,7 @@ const runOxlint = async (options) => {
76045
76614
  };
76046
76615
  }
76047
76616
  };
76048
- if (perFileLintCacheEnabled && !project.hasReactCompiler && project.hasReactCompilerLintPlugin !== true && extendsPaths.length === 0 && userPlugins.length === 0 && !hasHtmlFiles) {
76617
+ if (perFileLintCacheEnabled && !project.hasReactCompiler && project.hasReactCompilerLintPlugin !== true && extendsPaths.length === 0 && userPlugins.length === 0 && !hasPreparedFiles) {
76049
76618
  const rulesetHash = computeRulesetHash({
76050
76619
  config: buildConfig({
76051
76620
  extendsPaths: [],
@@ -76211,7 +76780,8 @@ const runOxlint = async (options) => {
76211
76780
  rootDirectory,
76212
76781
  nodeBinaryPath,
76213
76782
  project: lintProject,
76214
- sourcePathByLintPath: preparedHtmlSources.sourcePathByLintPath,
76783
+ sourcePathByLintPath: preparedLintSources.sourcePathByLintPath,
76784
+ sourceMapByLintPath: preparedLintSources.sourceMapByLintPath,
76215
76785
  onPartialFailure,
76216
76786
  onAnalyzedFiles: (filePaths) => {
76217
76787
  analyzedFiles = filePaths;
@@ -76220,6 +76790,7 @@ const runOxlint = async (options) => {
76220
76790
  spawnTimeoutMs,
76221
76791
  outputMaxBytes,
76222
76792
  concurrency: options.concurrency,
76793
+ spawnSlots: options.spawnSlots,
76223
76794
  signal: options.signal,
76224
76795
  deadlineEpochMs: options.deadlineEpochMs
76225
76796
  });
@@ -76314,6 +76885,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
76314
76885
  const spawnTimeoutMs = yield* OxlintSpawnTimeoutMs;
76315
76886
  const outputMaxBytes = yield* OxlintOutputMaxBytes;
76316
76887
  const concurrency = yield* OxlintConcurrency;
76888
+ const spawnSlots = yield* OxlintSpawnSlots;
76317
76889
  const lintBatchOrdering = yield* LintBatchOrdering;
76318
76890
  const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
76319
76891
  const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
@@ -76343,6 +76915,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
76343
76915
  onSidecarStats: input.onSidecarStats,
76344
76916
  spawnTimeoutMs,
76345
76917
  outputMaxBytes,
76918
+ spawnSlots: spawnSlots ?? void 0,
76346
76919
  concurrency,
76347
76920
  signal,
76348
76921
  lintBatchOrdering,
@@ -77571,10 +78144,14 @@ const materializeSourceTree = (input) => gen(function* () {
77571
78144
  materializedFiles.push(relativePath);
77572
78145
  }
77573
78146
  yield* sync(() => {
77574
- for (const configFilename of STAGED_FILES_PROJECT_CONFIG_FILENAMES) {
77575
- const sourcePath = path.join(input.directory, configFilename);
77576
- const targetPath = path.join(resolvedTempDirectory, configFilename);
77577
- if (fs.existsSync(sourcePath) && !fs.existsSync(targetPath)) fs.cpSync(sourcePath, targetPath);
78147
+ for (const subdirectory of ["", ...input.configSubdirectories ?? []]) for (const configFilename of STAGED_FILES_PROJECT_CONFIG_FILENAMES) {
78148
+ const sourcePath = path.join(input.directory, subdirectory, configFilename);
78149
+ const targetPath = path.resolve(resolvedTempDirectory, subdirectory, configFilename);
78150
+ if (!isPathInsideDirectory(targetPath, resolvedTempDirectory)) continue;
78151
+ if (fs.existsSync(sourcePath) && !fs.existsSync(targetPath)) {
78152
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
78153
+ fs.cpSync(sourcePath, targetPath);
78154
+ }
77578
78155
  }
77579
78156
  });
77580
78157
  return {
@@ -77607,15 +78184,17 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
77607
78184
  static layerNode = effect(StagedFiles, gen(function* () {
77608
78185
  const git = yield* Git;
77609
78186
  return StagedFiles.of({
77610
- discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile)), withSpan("StagedFiles.discoverSourceFiles")),
77611
- materialize: ({ directory, stagedFiles, tempDirectory }) => materializeSourceTree({
78187
+ discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries === null ? null : entries.filter(isLintableSourceFile)), withSpan("StagedFiles.discoverSourceFiles")),
78188
+ materialize: ({ directory, stagedFiles, tempDirectory, configSubdirectories }) => materializeSourceTree({
77612
78189
  directory,
77613
78190
  files: stagedFiles,
77614
78191
  tempDirectory,
78192
+ configSubdirectories,
77615
78193
  readContent: (relativePath) => git.showStagedContent(directory, relativePath, { maxBufferBytes: GIT_SHOW_MAX_BUFFER_BYTES })
77616
78194
  }).pipe(map$3((tree) => ({
77617
78195
  tempDirectory: tree.tempDirectory,
77618
78196
  stagedFiles: tree.materializedFiles,
78197
+ unmaterializedFiles: tree.unmaterializedFiles,
77619
78198
  cleanup: tree.cleanup
77620
78199
  })), withSpan("StagedFiles.materialize"))
77621
78200
  });
@@ -77630,6 +78209,7 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
77630
78209
  materialize: ({ tempDirectory }) => succeed$2({
77631
78210
  tempDirectory,
77632
78211
  stagedFiles: snapshot.materializedFiles ?? snapshot.sourceFiles ?? [],
78212
+ unmaterializedFiles: [],
77633
78213
  cleanup: () => {}
77634
78214
  })
77635
78215
  }));
@@ -77998,6 +78578,11 @@ const computeConfigFingerprint = (projectDirectory, version) => {
77998
78578
  }
77999
78579
  return crypto.createHash("sha1").update(parts.join("|")).digest("hex");
78000
78580
  };
78581
+ const createLintPhaseAbortError = () => new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) });
78582
+ const createOxlintSpawnSlots = (concurrency) => createWorkerSlots({
78583
+ slotCount: resolveScanConcurrency(concurrency),
78584
+ createAbortError: createLintPhaseAbortError
78585
+ });
78001
78586
  const AI_TRAINING_ENV_VARS = [
78002
78587
  ["HF_DATASETS_CACHE", "huggingface"],
78003
78588
  ["HF_HOME", "huggingface"],
@@ -78077,7 +78662,7 @@ const groupBy = (items, keyFn) => {
78077
78662
  * recipe, so advertising "fetch the fix recipe" for them sends agents to
78078
78663
  * a 404. Gate the directive on this predicate.
78079
78664
  */
78080
- const hasPublishedFixRecipe = (diagnostic) => diagnostic.plugin === "react-doctor" && Object.hasOwn(reactDoctorPlugin.rules, diagnostic.rule);
78665
+ const hasPublishedFixRecipe = (diagnostic) => diagnostic.plugin === "react-doctor" && Object.hasOwn(REACT_DOCTOR_RULE_REGISTRY, diagnostic.rule);
78081
78666
  const unionValues = (baseValues, overrideValues) => [...new Set([...baseValues, ...overrideValues])];
78082
78667
  const mergeIgnores = (baseIgnore, overrideIgnore) => {
78083
78668
  const mergedIgnore = {
@@ -78326,6 +78911,7 @@ const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
78326
78911
  const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
78327
78912
  const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
78328
78913
  const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
78914
+ const TUI_REPORT_COMPACT_MAX_ROWS = 23;
78329
78915
  const TUI_REPORT_DETAIL_WIDTH_FRACTION = .6;
78330
78916
  const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
78331
78917
  const WELCOME_EXPLANATION_HOLD_MS = 1e3;
@@ -78338,11 +78924,13 @@ const METRIC = {
78338
78924
  cliError: "cli.error",
78339
78925
  cliEnvironmentError: "cli.env_error",
78340
78926
  stagedSnapshotDivergence: "staged.snapshot_divergence",
78927
+ stagedPerProject: "staged.per_project",
78341
78928
  projectDetected: "project.detected",
78342
78929
  projectPathSelected: "project.path_selected",
78343
78930
  projectConfigSelected: "project.config_selected",
78344
78931
  scanCompleted: "scan.completed",
78345
78932
  scanDuration: "scan.duration",
78933
+ scanFeedbackDelay: "scan.feedback_delay",
78346
78934
  scanPhaseDuration: "scan.phase_duration",
78347
78935
  scanFiles: "scan.files",
78348
78936
  scanScore: "scan.score",
@@ -78374,6 +78962,13 @@ const METRIC = {
78374
78962
  lspScanCompleted: "lsp.scan.completed",
78375
78963
  lspScanDuration: "lsp.scan.duration",
78376
78964
  lspScanDiagnostics: "lsp.scan.diagnostics",
78965
+ tuiCompactReportShown: "tui.compact_report_shown",
78966
+ tuiFindingNavigated: "tui.finding_navigated",
78967
+ tuiIssueStreamShown: "tui.issue_stream_shown",
78968
+ tuiProjectPathContextShown: "tui.project_path_context_shown",
78969
+ tuiReportActionSelected: "tui.report_action_selected",
78970
+ tuiCancelled: "tui.cancelled",
78971
+ tuiStackedReportCapped: "tui.stacked_report_capped",
78377
78972
  aiTrainingWarningShown: "ai.training.warning_shown",
78378
78973
  jsonOutUsed: "json.out_used"
78379
78974
  };
@@ -78418,7 +79013,7 @@ const makeNoopConsole = () => ({
78418
79013
  });
78419
79014
  //#endregion
78420
79015
  //#region src/cli/utils/version.ts
78421
- const VERSION = "0.9.2";
79016
+ const VERSION = "0.9.3-dev.02e3188";
78422
79017
  //#endregion
78423
79018
  //#region src/cli/utils/json-mode.ts
78424
79019
  let context = null;
@@ -78776,13 +79371,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
78776
79371
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
78777
79372
  * standard `SENTRY_RELEASE` override.
78778
79373
  */
78779
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.2`;
79374
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.3-dev.02e3188`;
78780
79375
  /**
78781
79376
  * Deployment environment shown in Sentry's environment filter. Defaults to
78782
79377
  * `production` for tagged releases and `development` for dev/unbuilt versions,
78783
79378
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
78784
79379
  */
78785
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.2") ? "development" : "production");
79380
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.3-dev.02e3188") ? "development" : "production");
78786
79381
  /**
78787
79382
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
78788
79383
  * (set to `0` to disable tracing) and falls back to
@@ -90636,7 +91231,8 @@ const buildRuntimeLayers = (input) => {
90636
91231
  resolvedDirectory: input.directory,
90637
91232
  configSourceDirectory: input.configSourceDirectory
90638
91233
  }) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, deadCodeLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
90639
- return input.oxlintConcurrency === void 0 ? baseLayers : mergeAll$1(baseLayers, succeed$3(OxlintConcurrency, input.oxlintConcurrency));
91234
+ const layersWithConcurrency = input.oxlintConcurrency === void 0 ? baseLayers : mergeAll$1(baseLayers, succeed$3(OxlintConcurrency, input.oxlintConcurrency));
91235
+ return input.oxlintSpawnSlots === void 0 ? layersWithConcurrency : mergeAll$1(layersWithConcurrency, succeed$3(OxlintSpawnSlots, input.oxlintSpawnSlots));
90640
91236
  };
90641
91237
  //#endregion
90642
91238
  //#region src/cli/utils/with-sentry-run-span.ts
@@ -91942,6 +92538,14 @@ const buildNoScoreMessage = (isScoreDisabled, disabledMessage) => {
91942
92538
  return `${isScoreDisabled ? disabledMessage ?? "Score disabled by --no-score." : "Score unavailable (could not reach the score API)."} ${ENTERPRISE_CONTACT_HINT}`;
91943
92539
  };
91944
92540
  //#endregion
92541
+ //#region src/cli/utils/build-empty-report-message.ts
92542
+ const buildEmptyReportMessage = (input) => {
92543
+ const categoryFilters = [...input.categoryFilters];
92544
+ if (categoryFilters.length > 0) return `No issues found in category ${categoryFilters.join(", ")}!`;
92545
+ if (input.demotedDiagnosticCount > 0) return `No issues found! (${input.demotedDiagnosticCount} demoted from the ${input.outputSurface} surface — see config.surfaces.)`;
92546
+ return "No issues found!";
92547
+ };
92548
+ //#endregion
91945
92549
  //#region src/cli/utils/render-agent-guidance.ts
91946
92550
  const AGENT_GUIDANCE_LINES = [
91947
92551
  "Treat React Doctor diagnostics as starting hypotheses. Read the relevant code before confirming or suppressing each finding.",
@@ -92239,16 +92843,19 @@ const formatClusterLocation = (cluster, resolveSourceRoot, hyperlinks) => {
92239
92843
  if (!hyperlinks) return `${location}${contextTag}`;
92240
92844
  return `${formatHyperlink(location, pathToFileURL(resolveAbsolutePath(lead.filePath, resolveSourceRoot(lead))).href)}${contextTag}`;
92241
92845
  };
92242
- const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame, hyperlinks) => {
92846
+ const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, resolveFrameSourceRoot, renderCodeFrame, hyperlinks) => {
92243
92847
  const lead = cluster.diagnostics[0];
92244
92848
  const isMultiSite = cluster.diagnostics.length > 1;
92849
+ const sourceRoot = resolveSourceRoot(lead);
92850
+ const frameSourceRoot = resolveFrameSourceRoot(lead);
92851
+ const frameFilePath = Path.isAbsolute(lead.filePath) && sourceRoot !== frameSourceRoot ? Path.relative(sourceRoot, lead.filePath) : lead.filePath;
92245
92852
  const lines = ["", highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${formatClusterLocation(cluster, resolveSourceRoot, hyperlinks)}`)];
92246
92853
  const codeFrame = renderCodeFrame ? buildCodeFrame({
92247
- filePath: lead.filePath,
92854
+ filePath: frameFilePath,
92248
92855
  line: cluster.startLine,
92249
92856
  column: isMultiSite ? 0 : lead.column,
92250
92857
  endLine: isMultiSite ? cluster.endLine : void 0,
92251
- rootDirectory: resolveSourceRoot(lead)
92858
+ rootDirectory: frameSourceRoot
92252
92859
  }) : null;
92253
92860
  if (codeFrame) {
92254
92861
  const boxInnerWidth = resolveMeasureWidth(8);
@@ -92261,7 +92868,7 @@ const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame
92261
92868
  }
92262
92869
  return lines;
92263
92870
  };
92264
- const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, renderEverySite, isAgentEnvironment, hyperlinks) => {
92871
+ const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, resolveFrameSourceRoot, renderEverySite, isAgentEnvironment, hyperlinks) => {
92265
92872
  const representative = pickRepresentativeDiagnostic(ruleDiagnostics);
92266
92873
  const { severity } = representative;
92267
92874
  const trailingBadge = formatTrailingSiteBadge(ruleDiagnostics.length);
@@ -92283,7 +92890,7 @@ const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, rende
92283
92890
  }
92284
92891
  const renderCodeFrame = severity === "error";
92285
92892
  const sites = renderEverySite ? ruleDiagnostics : [representative];
92286
- if (!(isCollapsedWarningGroup && representative.help.includes(representative.filePath))) for (const cluster of clusterNearbyDiagnostics(sites)) lines.push(...buildDiagnosticClusterLines(cluster, resolveSourceRoot, renderCodeFrame, hyperlinks));
92893
+ if (!(isCollapsedWarningGroup && representative.help.includes(representative.filePath))) for (const cluster of clusterNearbyDiagnostics(sites)) lines.push(...buildDiagnosticClusterLines(cluster, resolveSourceRoot, resolveFrameSourceRoot, renderCodeFrame, hyperlinks));
92287
92894
  return lines;
92288
92895
  };
92289
92896
  const selectErrorRuleGroups = (diagnostics, rulePriority) => buildSortedRuleGroups(diagnostics.filter((diagnostic) => diagnostic.severity === "error"), rulePriority);
@@ -92309,7 +92916,7 @@ const buildMigrationScaleAdvisoryLines = (diagnostics) => {
92309
92916
  return lines;
92310
92917
  };
92311
92918
  const getTopErrorRuleKeys = (diagnostics, limit, rulePriority) => new Set(selectTopErrorRuleGroups(diagnostics, limit, rulePriority).map(([ruleKey]) => ruleKey));
92312
- const buildTopErrorsSection = (diagnostics, resolveSourceRoot, hyperlinks, rulePriority) => {
92919
+ const buildTopErrorsSection = (diagnostics, resolveSourceRoot, resolveFrameSourceRoot, hyperlinks, rulePriority) => {
92313
92920
  const topRuleGroups = selectErrorRuleGroups(diagnostics, rulePriority).slice(0, 3);
92314
92921
  if (topRuleGroups.length === 0) return {
92315
92922
  lines: [],
@@ -92319,7 +92926,7 @@ const buildTopErrorsSection = (diagnostics, resolveSourceRoot, hyperlinks, ruleP
92319
92926
  const blockOffsets = [];
92320
92927
  for (const [ruleKey, ruleDiagnostics] of topRuleGroups) {
92321
92928
  blockOffsets.push(lines.length);
92322
- lines.push(...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, false, false, hyperlinks));
92929
+ lines.push(...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, resolveFrameSourceRoot, false, false, hyperlinks));
92323
92930
  lines.push("");
92324
92931
  }
92325
92932
  return {
@@ -92357,18 +92964,19 @@ const buildOverviewHeaderLines = (diagnostics) => {
92357
92964
  * single Effect.forEach over Console.log so failures or fiber
92358
92965
  * interruption produce predictable partial output.
92359
92966
  */
92360
- const printDiagnostics = (diagnostics, isVerbose, sourceRoot, rulePriority, isAgentEnvironment = false, onboarding = {}, hyperlinks = false) => gen(function* () {
92967
+ const printDiagnostics = (diagnostics, isVerbose, sourceRoot, rulePriority, isAgentEnvironment = false, onboarding = {}, hyperlinks = false, frameSourceRoot = sourceRoot) => gen(function* () {
92361
92968
  const sectionPause = onboarding.sectionPause ?? void_;
92362
92969
  const animateCountUp = onboarding.animateCountUp ?? false;
92363
92970
  const resolveSourceRoot = typeof sourceRoot === "function" ? sourceRoot : () => sourceRoot;
92971
+ const resolveFrameSourceRoot = typeof frameSourceRoot === "function" ? frameSourceRoot : () => frameSourceRoot;
92364
92972
  let detailLines;
92365
92973
  let topErrorBlockOffsets = [];
92366
92974
  if (!isVerbose) {
92367
- const topErrors = buildTopErrorsSection(diagnostics, resolveSourceRoot, hyperlinks, rulePriority);
92975
+ const topErrors = buildTopErrorsSection(diagnostics, resolveSourceRoot, resolveFrameSourceRoot, hyperlinks, rulePriority);
92368
92976
  detailLines = topErrors.lines;
92369
92977
  topErrorBlockOffsets = topErrors.blockOffsets;
92370
92978
  } else detailLines = buildSortedRuleGroups(diagnostics, rulePriority).flatMap(([ruleKey, ruleDiagnostics]) => {
92371
- return [...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, true, isAgentEnvironment, hyperlinks), ""];
92979
+ return [...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, resolveFrameSourceRoot, true, isAgentEnvironment, hyperlinks), ""];
92372
92980
  });
92373
92981
  const overflowLine = isVerbose ? void 0 : buildOverflowSummaryLine(diagnostics, rulePriority);
92374
92982
  const categoryTallies = buildCategoryDiagnosticGroups(diagnostics, rulePriority).map(buildCategoryTally);
@@ -92898,6 +93506,26 @@ const printNoScoreHeader = (noScoreMessage) => gen(function* () {
92898
93506
  yield* log("");
92899
93507
  });
92900
93508
  //#endregion
93509
+ //#region src/cli/utils/build-footer-description-lines.ts
93510
+ const FOOTER_DESCRIPTION_INDENT = " ";
93511
+ const buildFooterDescriptionLines = (description) => {
93512
+ return wrapTextToWidth(description, resolveMeasureWidth(2)).map((line) => highlighter.dim(`${FOOTER_DESCRIPTION_INDENT}${line}`));
93513
+ };
93514
+ //#endregion
93515
+ //#region src/cli/utils/build-footer-link-lines.ts
93516
+ const buildFooterLinkLines = ({ shareUrl }) => [
93517
+ ...shareUrl === null ? [] : [
93518
+ ` ${highlighter.bold("Share:")} ${highlighter.info(shareUrl)}`,
93519
+ ...buildFooterDescriptionLines("Tell others how you did on socials"),
93520
+ ""
93521
+ ],
93522
+ ` ${highlighter.bold("Docs:")} ${highlighter.info(DOCS_URL)}`,
93523
+ ...buildFooterDescriptionLines("Learn more about fixing issues, setting up CI/CD, and configuring rules with a config file"),
93524
+ "",
93525
+ ` ${highlighter.bold("GitHub:")} ${highlighter.info(CANONICAL_GITHUB_URL)}`,
93526
+ ...buildFooterDescriptionLines("Report issues and star the repository!")
93527
+ ];
93528
+ //#endregion
92901
93529
  //#region src/cli/utils/write-diagnostics-directory.ts
92902
93530
  const ruleDumpFileName = (ruleKey) => ruleKey.replace(/\//g, "--") + ".txt";
92903
93531
  const readPreviousRuleDumpFileNames = (directory) => {
@@ -92918,11 +93546,6 @@ const writeDiagnosticsDirectory = (diagnostics, outputDirectory) => {
92918
93546
  };
92919
93547
  //#endregion
92920
93548
  //#region src/cli/utils/render-summary.ts
92921
- const FOOTER_DESCRIPTION_INDENT = " ";
92922
- const printFooterDescription = (description) => gen(function* () {
92923
- const wrapWidth = resolveMeasureWidth(2);
92924
- for (const line of wrapTextToWidth(description, wrapWidth)) yield* log(highlighter.dim(`${FOOTER_DESCRIPTION_INDENT}${line}`));
92925
- });
92926
93549
  const buildShareUrl = (diagnostics, scoreResult, projectName) => {
92927
93550
  const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
92928
93551
  const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
@@ -92939,17 +93562,8 @@ const printFooter = (input) => gen(function* () {
92939
93562
  yield* log("");
92940
93563
  yield* log(buildSectionDivider());
92941
93564
  yield* log("");
92942
- if (!input.isOffline) {
92943
- const shareUrl = buildShareUrl(input.diagnostics, input.scoreResult, input.projectName);
92944
- yield* log(` ${highlighter.bold("Share:")} ${highlighter.info(shareUrl)}`);
92945
- yield* printFooterDescription("Tell others how you did on socials");
92946
- yield* log("");
92947
- }
92948
- yield* log(` ${highlighter.bold("Docs:")} ${highlighter.info(DOCS_URL)}`);
92949
- yield* printFooterDescription("Learn more about fixing issues, setting up CI/CD, and configuring rules with a config file");
92950
- yield* log("");
92951
- yield* log(` ${highlighter.bold("GitHub:")} ${highlighter.info(CANONICAL_GITHUB_URL)}`);
92952
- yield* printFooterDescription("Report issues and star the repository!");
93565
+ const shareUrl = input.isOffline ? null : buildShareUrl(input.diagnostics, input.scoreResult, input.projectName);
93566
+ for (const line of buildFooterLinkLines({ shareUrl })) yield* log(line);
92953
93567
  });
92954
93568
  const printDiagnosticsDump = (diagnostics, outputDirectory, verbose, stream = "stdout") => gen(function* () {
92955
93569
  const writtenDirectory = yield* try_({
@@ -93058,7 +93672,6 @@ const recordOnboardingCompletion = (options) => {
93058
93672
  isNonInteractiveEnvironment: options.isNonInteractiveEnvironment
93059
93673
  })) markOnboardingComplete();
93060
93674
  };
93061
- const formatCategorySelection = (categoryFilters) => [...categoryFilters].join(", ");
93062
93675
  const buildChangedLineMatcher = (directory, changedLineRanges) => {
93063
93676
  const rangesByFile = /* @__PURE__ */ new Map();
93064
93677
  for (const entry of changedLineRanges) rangesByFile.set(toForwardSlashes(entry.file), entry.ranges);
@@ -93136,7 +93749,7 @@ const buildRunEventConfig = (options, userConfig, hasCustomConfig, resolvedWorke
93136
93749
  userConfig
93137
93750
  };
93138
93751
  };
93139
- const inspect = async (directory, inputOptions = {}) => {
93752
+ const inspectWithOxlintRuntime = async (directory, inputOptions, oxlintRuntime) => {
93140
93753
  const startTime = performance$1.now();
93141
93754
  const deadlineEpochMs = inputOptions.deadlineEpochMs ?? (inputOptions.maxDurationMs != null ? Date.now() + inputOptions.maxDurationMs : null);
93142
93755
  const isConcurrentScan = inputOptions.concurrentScan === true;
@@ -93162,7 +93775,7 @@ const inspect = async (directory, inputOptions = {}) => {
93162
93775
  try {
93163
93776
  const result = await withSentryRunSpan(async (rootSentrySpan) => {
93164
93777
  try {
93165
- return await runInspectWithRuntime(scanDirectory, options, userConfig, hasConfigOverride, configSourceDirectory, startTime, deadlineEpochMs, rootSentrySpan);
93778
+ return await runInspectWithRuntime(scanDirectory, options, userConfig, hasConfigOverride, configSourceDirectory, startTime, deadlineEpochMs, rootSentrySpan, oxlintRuntime);
93166
93779
  } catch (error) {
93167
93780
  recordRunEvent(rootSentrySpan, {
93168
93781
  ...buildRunEventConfig(options, userConfig, userConfig !== null),
@@ -93178,6 +93791,15 @@ const inspect = async (directory, inputOptions = {}) => {
93178
93791
  if (ownsSpinnerSilence) setSpinnerSilent(wasSpinnerSilent);
93179
93792
  }
93180
93793
  };
93794
+ const createInvocationInspect = (requestedOxlintConcurrency) => {
93795
+ const concurrency = resolveScanConcurrency(requestedOxlintConcurrency ?? runSync(OxlintConcurrency));
93796
+ const oxlintRuntime = {
93797
+ concurrency,
93798
+ spawnSlots: createOxlintSpawnSlots(concurrency)
93799
+ };
93800
+ return (directory, inputOptions = {}) => inspectWithOxlintRuntime(directory, inputOptions, oxlintRuntime);
93801
+ };
93802
+ const inspect = async (directory, inputOptions = {}) => createInvocationInspect(inputOptions.concurrency)(directory, inputOptions);
93181
93803
  const countIncompleteLintFiles = (lintPartialFailures) => countDroppedLintFiles(lintPartialFailures) + countDeadlineSkippedFiles(lintPartialFailures);
93182
93804
  /**
93183
93805
  * Runs a second, lint-only scan over the changed files as they existed at the
@@ -93231,7 +93853,8 @@ const runBaselineComparison = async (params) => {
93231
93853
  shouldRunSupplyChain: params.options.supplyChain,
93232
93854
  shouldComputeScore: false,
93233
93855
  shouldShowProgressSpinners: false,
93234
- oxlintConcurrency: params.options.concurrency
93856
+ oxlintConcurrency: params.oxlintRuntime.concurrency,
93857
+ oxlintSpawnSlots: params.oxlintRuntime.spawnSlots
93235
93858
  });
93236
93859
  const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
93237
93860
  directory: snapshot.tempDirectory,
@@ -93276,7 +93899,7 @@ const runBaselineComparison = async (params) => {
93276
93899
  snapshot.cleanup();
93277
93900
  }
93278
93901
  };
93279
- const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOverride, configSourceDirectory, startTime, deadlineEpochMs, rootSentrySpan) => {
93902
+ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOverride, configSourceDirectory, startTime, deadlineEpochMs, rootSentrySpan, oxlintRuntime) => {
93280
93903
  const isDiffMode = options.includePaths.length > 0;
93281
93904
  const resolvedNodeBinaryPath = await resolveOxlintNode(options.lint, options.scoreOnly || options.silent);
93282
93905
  const lintBindingMissing = options.lint && !resolvedNodeBinaryPath;
@@ -93326,7 +93949,8 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
93326
93949
  shouldRunSupplyChain: options.supplyChain,
93327
93950
  shouldComputeScore: !options.noScore,
93328
93951
  shouldShowProgressSpinners,
93329
- oxlintConcurrency: options.concurrency,
93952
+ oxlintConcurrency: oxlintRuntime.concurrency,
93953
+ oxlintSpawnSlots: oxlintRuntime.spawnSlots,
93330
93954
  reporterLayer: options.uiLayers?.reporter,
93331
93955
  progressLayer: options.uiLayers?.progress
93332
93956
  });
@@ -93383,7 +94007,8 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
93383
94007
  baseFiles: options.baseline.baseFiles,
93384
94008
  headFiles: options.baseline.headFiles,
93385
94009
  headAnalyzedFiles: output.analyzedFiles,
93386
- deadlineEpochMs
94010
+ deadlineEpochMs,
94011
+ oxlintRuntime
93387
94012
  });
93388
94013
  if (comparison) {
93389
94014
  inspectDiagnostics = comparison.displayDiagnostics;
@@ -93574,9 +94199,11 @@ const finalizeAndRender = (input) => gen(function* () {
93574
94199
  if (hasSkippedChecks) {
93575
94200
  const skippedLabel = skippedChecks.join(" and ");
93576
94201
  yield* warn$1(highlighter.warn(`No issues detected, but ${skippedLabel} checks failed — results are incomplete.`));
93577
- } else if (options.categoryFilters.size > 0) yield* log(highlighter.success(`No issues found in category ${formatCategorySelection(options.categoryFilters)}!`));
93578
- else if (demotedDiagnosticCount > 0) yield* log(highlighter.success(`No issues found! (${demotedDiagnosticCount} demoted from the ${options.outputSurface} surface — see config.surfaces.)`));
93579
- else yield* log(highlighter.success("No issues found!"));
94202
+ } else yield* log(highlighter.success(buildEmptyReportMessage({
94203
+ categoryFilters: options.categoryFilters,
94204
+ demotedDiagnosticCount,
94205
+ outputSurface: options.outputSurface
94206
+ })));
93580
94207
  yield* log("");
93581
94208
  yield* pause;
93582
94209
  if (hasSkippedChecks) {
@@ -93632,26 +94259,26 @@ const finalizeAndRender = (input) => gen(function* () {
93632
94259
  //#region src/cli/utils/get-staged-files.ts
93633
94260
  const stagedFilesLayer = StagedFiles.layerNode.pipe(provide$2(Git.layerNode));
93634
94261
  const getStagedSourceFiles = async (directory) => {
94262
+ let stagedSourceFiles;
93635
94263
  try {
93636
- return [...await runPromise(gen(function* () {
94264
+ stagedSourceFiles = await runPromise(gen(function* () {
93637
94265
  return yield* (yield* StagedFiles).discoverSourceFiles(directory);
93638
- }).pipe(provide(stagedFilesLayer)))];
94266
+ }).pipe(provide(stagedFilesLayer)));
93639
94267
  } catch (error) {
93640
94268
  cliLogger.warn(`Failed to discover staged files: ${messageFromUnknown(error)}`);
93641
94269
  return [];
93642
94270
  }
94271
+ if (stagedSourceFiles === null) throw new CliInputError("Could not read the git index to list staged files. Check that git can read the index in this directory.");
94272
+ return [...stagedSourceFiles];
93643
94273
  };
93644
- const materializeStagedFiles = async (directory, stagedFiles, tempDirectory) => {
94274
+ const materializeStagedFiles = async (input) => {
93645
94275
  const snapshot = await runPromise(gen(function* () {
93646
- return yield* (yield* StagedFiles).materialize({
93647
- directory,
93648
- stagedFiles,
93649
- tempDirectory
93650
- });
94276
+ return yield* (yield* StagedFiles).materialize(input);
93651
94277
  }).pipe(provide(stagedFilesLayer)));
93652
94278
  return {
93653
94279
  tempDirectory: snapshot.tempDirectory,
93654
94280
  stagedFiles: [...snapshot.stagedFiles],
94281
+ unmaterializedFiles: [...snapshot.unmaterializedFiles],
93655
94282
  cleanup: snapshot.cleanup
93656
94283
  };
93657
94284
  };
@@ -93667,7 +94294,7 @@ const buildHandoffPayload = (input) => {
93667
94294
  try {
93668
94295
  outputDirectory = writeDiagnosticsDirectory([...input.diagnostics], input.outputDirectory);
93669
94296
  } catch {}
93670
- const lines = [`Fix the top ${topGroups.length} React Doctor ${topGroups.length === 1 ? "issue" : "issues"} in ${input.projectName} on this pass — leave the rest for a follow-up.`, ""];
94297
+ const lines = [`Review and fix the top ${topGroups.length} React Doctor ${topGroups.length === 1 ? "finding" : "findings"} in ${input.projectName}. Leave the rest for a follow-up.`, ""];
93671
94298
  topGroups.forEach(([ruleKey, ruleDiagnostics], index) => {
93672
94299
  const representative = ruleDiagnostics[0];
93673
94300
  const severityLabel = representative.severity === "error" ? "ERROR" : "WARN";
@@ -93695,7 +94322,7 @@ const buildHandoffPayload = (input) => {
93695
94322
  const ruleSummaries = deferredMigrationBuckets.map((bucket) => `${bucket.title} (${bucket.fileCount} files)`).join(", ");
93696
94323
  lines.push(`Some of the rest are migration-scale (span dozens of files): ${ruleSummaries}. For each, fix a representative sample, confirm the recipe holds, and get the code owner's sign-off before changing the rest in one pass.`, "");
93697
94324
  }
93698
- lines.push("Then work through the rest from the full results above.");
94325
+ lines.push("Stop after this pass. Summarize the remaining findings for a follow-up.");
93699
94326
  return lines.join("\n");
93700
94327
  };
93701
94328
  //#endregion
@@ -93905,20 +94532,20 @@ const resolveOpenCommand = (url) => {
93905
94532
  args: [url]
93906
94533
  };
93907
94534
  };
93908
- const openUrl = (url) => {
94535
+ const openUrl = (url) => new Promise((resolve) => {
93909
94536
  try {
93910
94537
  const { command, args } = resolveOpenCommand(url);
93911
94538
  const child = spawn(command, args, {
93912
94539
  detached: true,
93913
94540
  stdio: "ignore"
93914
94541
  });
93915
- child.on("error", () => {});
94542
+ child.once("spawn", () => resolve(true));
94543
+ child.once("error", () => resolve(false));
93916
94544
  child.unref();
93917
- return true;
93918
94545
  } catch {
93919
- return false;
94546
+ resolve(false);
93920
94547
  }
93921
- };
94548
+ });
93922
94549
  //#endregion
93923
94550
  //#region src/cli/utils/ask-add-to-github-actions.ts
93924
94551
  const CI_YES_CHOICE = "ci-yes";
@@ -93959,7 +94586,7 @@ const askAddToGitHubActions = async (prompt = prompts) => {
93959
94586
  if (ciChoice === void 0) return "cancel";
93960
94587
  if (ciChoice === CI_YES_CHOICE) return "yes";
93961
94588
  if (ciChoice === CI_NO_CHOICE) return "no";
93962
- const opened = openUrl(GITHUB_ACTIONS_SETUP_URL);
94589
+ const opened = await openUrl(GITHUB_ACTIONS_SETUP_URL);
93963
94590
  cliLogger.log(opened ? `Opened ${highlighter.info(GITHUB_ACTIONS_SETUP_URL)} in your browser.` : `Visit ${highlighter.info(GITHUB_ACTIONS_SETUP_URL)} to learn more.`);
93964
94591
  cliLogger.break();
93965
94592
  }
@@ -95525,13 +96152,18 @@ const printMultiProjectSummary = (input) => gen(function* () {
95525
96152
  const scoreDiagnostics = new Set(filterScansForSurface(completedScans, "score"));
95526
96153
  const displayedScoreDiagnostics = displayDiagnostics.filter((diagnostic) => scoreDiagnostics.has(diagnostic));
95527
96154
  const projectRootByDiagnostic = /* @__PURE__ */ new WeakMap();
95528
- for (const scan of completedScans) for (const diagnostic of scan.result.diagnostics) projectRootByDiagnostic.set(diagnostic, scan.result.project.rootDirectory);
96155
+ const frameSourceRootByDiagnostic = /* @__PURE__ */ new WeakMap();
96156
+ for (const scan of completedScans) for (const diagnostic of scan.result.diagnostics) {
96157
+ projectRootByDiagnostic.set(diagnostic, scan.result.project.rootDirectory);
96158
+ frameSourceRootByDiagnostic.set(diagnostic, scan.frameSourceRoot ?? scan.result.project.rootDirectory);
96159
+ }
95529
96160
  const resolveDiagnosticSourceRoot = (diagnostic) => projectRootByDiagnostic.get(diagnostic) ?? "";
96161
+ const resolveDiagnosticFrameSourceRoot = (diagnostic) => frameSourceRootByDiagnostic.get(diagnostic) ?? resolveDiagnosticSourceRoot(diagnostic);
95530
96162
  const totalScannedFileCount = countUniqueScannedFiles(completedScans.map((scan) => scan.result));
95531
96163
  yield* log(`${highlighter.success("✔")} Scanned ${totalScannedFileCount} ${totalScannedFileCount === 1 ? "file" : "files"} in ${formatElapsedTime(totalElapsedMilliseconds)}`);
95532
96164
  if (displayDiagnostics.length > 0) {
95533
96165
  yield* log("");
95534
- yield* printDiagnostics(displayDiagnostics, verbose, resolveDiagnosticSourceRoot, buildRulePriorityMap(completedScans.map((scan) => scan.result.score)), isCodingAgentEnvironment(), { animateCountUp: animateRender }, shouldRenderHyperlinks(process.stdout));
96166
+ yield* printDiagnostics(displayDiagnostics, verbose, resolveDiagnosticSourceRoot, buildRulePriorityMap(completedScans.map((scan) => scan.result.score)), isCodingAgentEnvironment(), { animateCountUp: animateRender }, shouldRenderHyperlinks(process.stdout), resolveDiagnosticFrameSourceRoot);
95535
96167
  }
95536
96168
  const lowestScoredScan = findLowestScoredScan(completedScans);
95537
96169
  const aggregateScore = lowestScoredScan?.result.score ?? null;
@@ -95919,6 +96551,45 @@ const runExplain = async (fileLineArgument, context) => {
95919
96551
  }
95920
96552
  };
95921
96553
  //#endregion
96554
+ //#region src/cli/utils/run-project-scan-batch.ts
96555
+ /**
96556
+ * Run one scan per project through the same bounded pool as
96557
+ * `diagnose({ projects })`, with the batch spinner and its progress counter.
96558
+ *
96559
+ * Pool members must not toggle the module-level spinner-silent flag themselves —
96560
+ * overlapping save/restore pairs would race — so the batch owns that toggle once
96561
+ * around the whole run.
96562
+ *
96563
+ * A `scanProject` that returns `null` drops the project from the results: diff
96564
+ * mode skips projects with no changed source.
96565
+ */
96566
+ const runProjectScanBatch = async (input) => {
96567
+ const startTime = performance$1.now();
96568
+ const projectCount = input.projects.length;
96569
+ const isMultiProject = projectCount > 1;
96570
+ const batchSpinner = isMultiProject && !input.isQuiet ? spinner(`Scanning ${projectCount} projects…`).start() : null;
96571
+ const ownsBatchSpinnerSilence = isMultiProject && input.isSilent;
96572
+ const wasSpinnerSilent = isSpinnerSilent();
96573
+ if (ownsBatchSpinnerSilence) setSpinnerSilent(true);
96574
+ let finishedProjectCount = 0;
96575
+ let scanOutcomes;
96576
+ try {
96577
+ scanOutcomes = await mapWithConcurrency(input.projects, isMultiProject ? 4 : 1, async (project) => {
96578
+ const scanOutcome = await input.scanProject(project);
96579
+ finishedProjectCount += 1;
96580
+ batchSpinner?.update(`Scanning ${projectCount} projects… (${finishedProjectCount}/${projectCount})`);
96581
+ return scanOutcome;
96582
+ });
96583
+ } finally {
96584
+ if (ownsBatchSpinnerSilence) setSpinnerSilent(wasSpinnerSilent);
96585
+ batchSpinner?.stop();
96586
+ }
96587
+ return {
96588
+ completedScans: scanOutcomes.filter((scanOutcome) => scanOutcome !== null),
96589
+ elapsedMilliseconds: performance$1.now() - startTime
96590
+ };
96591
+ };
96592
+ //#endregion
95922
96593
  //#region src/cli/utils/project-manifest-changed.ts
95923
96594
  const PACKAGE_JSON = "package.json";
95924
96595
  /**
@@ -95935,6 +96606,29 @@ const projectManifestChanged = (rootDirectory, projectDirectory, diffInfo) => {
95935
96606
  return diffInfo.changedFiles.some((filePath) => toForwardSlashes(filePath) === manifestPath);
95936
96607
  };
95937
96608
  //#endregion
96609
+ //#region src/cli/utils/select-staged-projects.ts
96610
+ /**
96611
+ * Appended to the reason a `--staged` run could not scan per package. Shared so
96612
+ * both fallback sites word it identically.
96613
+ */
96614
+ const STAGED_PROJECT_FALLBACK_HINT = "Scanning the staged files at the scan root instead — fix the `projects` entries in your config to scan per package.";
96615
+ /**
96616
+ * Resolve the project directories a `--staged` scan owns. The interactive picker
96617
+ * is always skipped: a commit hook has nobody to answer it.
96618
+ */
96619
+ const selectStagedProjects = async (input) => {
96620
+ if (input.projectFlag) return selectProjects(input.rootDirectory, input.projectFlag, true, void 0);
96621
+ if (input.configProjects === void 0) return [input.rootDirectory];
96622
+ try {
96623
+ return await selectProjects(input.rootDirectory, void 0, true, input.configProjects);
96624
+ } catch (error) {
96625
+ if (!(error instanceof CliInputError)) throw error;
96626
+ cliLogger.warn(`${error.message} ${STAGED_PROJECT_FALLBACK_HINT}`);
96627
+ cliLogger.break();
96628
+ return [input.rootDirectory];
96629
+ }
96630
+ };
96631
+ //#endregion
95938
96632
  //#region src/cli/utils/should-fail-scan-gate.ts
95939
96633
  const shouldFailScanGate = (input) => {
95940
96634
  if (input.blockingLevel === "none") return false;
@@ -96100,6 +96794,7 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96100
96794
  const isQuiet = isScoreOnly || isJsonMode;
96101
96795
  const requestedDirectory = Path.resolve(directory);
96102
96796
  const startTime = performance$1.now();
96797
+ let scanStartupSpinner = null;
96103
96798
  if (isJsonMode) {
96104
96799
  enableJsonMode({
96105
96800
  compact: Boolean(flags.jsonCompact),
@@ -96148,6 +96843,7 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96148
96843
  if (!isQuiet) if (!flags.verbose && canAnimateOnboarding(process.stdout)) await runPromise(playWelcomeScene({ speedMultiplier: !isOnboardingForced() && hasCompletedOnboarding() ? 2 : 1 }));
96149
96844
  else runSync(printBrandedHeader);
96150
96845
  const scanOptions = resolveCliInspectOptions(flags, userConfig);
96846
+ const inspectProject = createInvocationInspect(scanOptions.concurrency);
96151
96847
  const scanDeadlineEpochMs = scanOptions.maxDurationMs !== void 0 ? Date.now() + scanOptions.maxDurationMs : void 0;
96152
96848
  const categoryFilters = new Set(scanOptions.categoryFilters ?? []);
96153
96849
  const skipPrompts = shouldSkipPrompts({
@@ -96155,8 +96851,13 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96155
96851
  json: flags.json
96156
96852
  });
96157
96853
  if (flags.staged) {
96158
- const stagedFiles = await getStagedSourceFiles(resolvedDirectory);
96159
- if (stagedFiles.length === 0) {
96854
+ const configProjectsApply = (userConfig?.projects ?? []).some((projectName) => projectName.trim().length > 0) && scanTarget.requestedDirectory === scanTarget.configSourceDirectory;
96855
+ const projectDirectories = await selectStagedProjects({
96856
+ rootDirectory: resolvedDirectory,
96857
+ projectFlag: flags.project,
96858
+ configProjects: configProjectsApply ? userConfig?.projects : void 0
96859
+ });
96860
+ const reportNothingToScan = (input) => {
96160
96861
  if (isJsonMode) writeJsonReport(buildJsonReport({
96161
96862
  version: VERSION,
96162
96863
  directory: resolvedDirectory,
@@ -96165,75 +96866,214 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96165
96866
  scans: [],
96166
96867
  totalElapsedMilliseconds: performance$1.now() - startTime
96167
96868
  }));
96168
- else if (!isScoreOnly) cliLogger.dim("No staged source files found.");
96869
+ else if (!isScoreOnly) if (input.severity === "warn") {
96870
+ cliLogger.warn(input.reason);
96871
+ cliLogger.break();
96872
+ } else cliLogger.dim(input.reason);
96873
+ };
96874
+ const rootStagedFiles = await getStagedSourceFiles(resolvedDirectory);
96875
+ if (rootStagedFiles.length === 0) {
96876
+ reportNothingToScan({ reason: "No staged source files found." });
96169
96877
  return;
96170
96878
  }
96171
- if (!isQuiet) {
96172
- cliLogger.log(`Scanning ${highlighter.info(`${stagedFiles.length}`)} staged files...`);
96879
+ const buildProjectScanContext = async (projectDirectory) => {
96880
+ const projectScanTarget = projectDirectory === resolvedDirectory ? scanTarget : await resolveScanTarget(projectDirectory, { allowAmbiguous: true });
96881
+ const scanDirectory = projectScanTarget.resolvedDirectory;
96882
+ const treeRelativeDirectory = resolveProjectRelativeDirectory(resolvedDirectory, scanDirectory);
96883
+ if (treeRelativeDirectory === null) return null;
96884
+ return {
96885
+ projectDirectory,
96886
+ scanDirectory,
96887
+ treeRelativeDirectory,
96888
+ projectConfig: projectDirectory === resolvedDirectory ? userConfig : mergeReactDoctorConfigs(userConfig, projectScanTarget.userConfig ?? void 0),
96889
+ projectConfigSourceDirectory: projectScanTarget.userConfig?.plugins === void 0 ? scanTarget.configSourceDirectory : projectScanTarget.configSourceDirectory
96890
+ };
96891
+ };
96892
+ const projectScanContexts = [];
96893
+ const seenScanDirectories = /* @__PURE__ */ new Set();
96894
+ for (const projectDirectory of projectDirectories) {
96895
+ const projectScanContext = await buildProjectScanContext(projectDirectory);
96896
+ if (projectScanContext === null) {
96897
+ if (flags.project) throw new CliInputError(`Project "${toRelativePath(projectDirectory, resolvedDirectory)}" is outside ${resolvedDirectory}, so it holds none of the staged files. Run --staged from a directory that contains the project.`);
96898
+ continue;
96899
+ }
96900
+ if (seenScanDirectories.has(projectScanContext.scanDirectory)) continue;
96901
+ seenScanDirectories.add(projectScanContext.scanDirectory);
96902
+ projectScanContexts.push(projectScanContext);
96903
+ }
96904
+ if (projectScanContexts.length === 0) {
96905
+ cliLogger.warn(`No configured project is inside ${resolvedDirectory}. ${STAGED_PROJECT_FALLBACK_HINT}`);
96173
96906
  cliLogger.break();
96907
+ const rootScanContext = await buildProjectScanContext(resolvedDirectory);
96908
+ if (rootScanContext !== null) projectScanContexts.push(rootScanContext);
96174
96909
  }
96175
- const tempDirectory = NFS.mkdtempSync(Path.join(tmpdir(), STAGED_FILES_TEMP_DIR_PREFIX));
96176
- const snapshot = await materializeStagedFiles(resolvedDirectory, stagedFiles, tempDirectory).catch((error) => {
96177
- NFS.rmSync(tempDirectory, {
96178
- recursive: true,
96179
- force: true
96910
+ const contextsByLongestPathFirst = [...projectScanContexts].sort((left, right) => right.treeRelativeDirectory.length - left.treeRelativeDirectory.length);
96911
+ const ownedStagedFiles = /* @__PURE__ */ new Map();
96912
+ for (const stagedFile of rootStagedFiles) {
96913
+ const owner = contextsByLongestPathFirst.find((context) => context.treeRelativeDirectory.length === 0 || stagedFile.startsWith(`${context.treeRelativeDirectory}/`));
96914
+ if (owner === void 0) continue;
96915
+ const ownedFiles = ownedStagedFiles.get(owner.scanDirectory);
96916
+ if (ownedFiles === void 0) ownedStagedFiles.set(owner.scanDirectory, [stagedFile]);
96917
+ else ownedFiles.push(stagedFile);
96918
+ }
96919
+ const stagedProjectScans = [];
96920
+ for (const projectScanContext of projectScanContexts) {
96921
+ const projectStagedFiles = ownedStagedFiles.get(projectScanContext.scanDirectory);
96922
+ if (projectStagedFiles === void 0) continue;
96923
+ stagedProjectScans.push({
96924
+ ...projectScanContext,
96925
+ stagedFiles: projectStagedFiles
96180
96926
  });
96181
- throw error;
96182
- });
96927
+ }
96928
+ if (stagedProjectScans.length === 0) {
96929
+ reportNothingToScan({ reason: "No staged source files in the selected projects." });
96930
+ return;
96931
+ }
96932
+ const selectedStagedFiles = stagedProjectScans.flatMap((projectScan) => projectScan.stagedFiles);
96933
+ const stagedFileCount = selectedStagedFiles.length;
96934
+ const unselectedStagedFileCount = rootStagedFiles.length - stagedFileCount;
96935
+ if (!isQuiet) {
96936
+ cliLogger.log(`Scanning ${highlighter.info(`${stagedFileCount}`)} staged files...`);
96937
+ if (unselectedStagedFileCount > 0) cliLogger.dim(`${unselectedStagedFileCount} more staged file${unselectedStagedFileCount === 1 ? "" : "s"} outside the selected projects.`);
96938
+ cliLogger.break();
96939
+ }
96183
96940
  const stagedWantsLines = resolveScope(flags, userConfig).scope === "lines";
96184
96941
  const stagedLineRanges = stagedWantsLines ? await getChangedLineRanges({
96185
96942
  directory: resolvedDirectory,
96186
96943
  cached: true,
96187
- files: snapshot.stagedFiles
96944
+ files: selectedStagedFiles
96188
96945
  }) : null;
96189
96946
  if (stagedWantsLines && stagedLineRanges === null && !isQuiet) {
96190
96947
  cliLogger.warn("Could not determine staged changed lines; reporting all issues in staged files.");
96191
96948
  cliLogger.break();
96192
96949
  }
96193
- try {
96194
- const scanResult = await inspect(snapshot.tempDirectory, {
96950
+ if (stagedProjectScans.some((projectScan) => projectScan.treeRelativeDirectory.length > 0)) recordCount(METRIC.stagedPerProject, 1, { projectCount: stagedProjectScans.length });
96951
+ const tempDirectory = NFS.mkdtempSync(Path.join(tmpdir(), STAGED_FILES_TEMP_DIR_PREFIX));
96952
+ const configSubdirectories = /* @__PURE__ */ new Set();
96953
+ for (const projectScan of stagedProjectScans) {
96954
+ configSubdirectories.add(projectScan.treeRelativeDirectory);
96955
+ const projectRelativeDirectory = resolveProjectRelativeDirectory(resolvedDirectory, projectScan.projectDirectory);
96956
+ if (projectRelativeDirectory !== null) configSubdirectories.add(projectRelativeDirectory);
96957
+ }
96958
+ const snapshot = await materializeStagedFiles({
96959
+ directory: resolvedDirectory,
96960
+ stagedFiles: selectedStagedFiles,
96961
+ tempDirectory,
96962
+ configSubdirectories: [...configSubdirectories]
96963
+ }).catch((error) => {
96964
+ NFS.rmSync(tempDirectory, {
96965
+ recursive: true,
96966
+ force: true
96967
+ });
96968
+ throw error;
96969
+ });
96970
+ const materializedStagedFiles = new Set(snapshot.stagedFiles);
96971
+ const stagedProjectRuns = stagedProjectScans.map((projectScan) => ({
96972
+ projectScan,
96973
+ includePaths: resolveProjectSourceFilePaths(resolvedDirectory, projectScan.scanDirectory, projectScan.stagedFiles.filter((stagedFile) => materializedStagedFiles.has(stagedFile)))
96974
+ })).filter((projectRun) => projectRun.includePaths.length > 0);
96975
+ const isMultiProject = stagedProjectRuns.length > 1;
96976
+ if (stagedProjectRuns.length === 0) {
96977
+ snapshot.cleanup();
96978
+ reportNothingToScan({
96979
+ reason: `Could not read any of the ${stagedFileCount} staged file${stagedFileCount === 1 ? "" : "s"} out of the index, so nothing was scanned. An unusually large staged file is the usual cause.`,
96980
+ severity: "warn"
96981
+ });
96982
+ return;
96983
+ }
96984
+ if (snapshot.unmaterializedFiles.length > 0 && !isQuiet) {
96985
+ const stagedFileLabel = `staged file${stagedFileCount === 1 ? "" : "s"}`;
96986
+ cliLogger.warn(`Skipped ${snapshot.unmaterializedFiles.length} of ${stagedFileCount} ${stagedFileLabel}; they could not be snapshotted from the index.`);
96987
+ cliLogger.break();
96988
+ }
96989
+ const scanStagedProject = async (projectRun) => {
96990
+ const { projectScan, includePaths } = projectRun;
96991
+ const projectTempDirectory = Path.join(snapshot.tempDirectory, projectScan.treeRelativeDirectory);
96992
+ const scanResult = await inspectProject(projectTempDirectory, {
96195
96993
  ...scanOptions,
96196
96994
  deadlineEpochMs: scanDeadlineEpochMs,
96197
- includePaths: snapshot.stagedFiles,
96198
- configOverride: userConfig,
96199
- configSourceDirectory: scanTarget.configSourceDirectory ?? void 0,
96200
- changedLineRanges: stagedLineRanges ?? void 0
96995
+ includePaths: [...includePaths],
96996
+ configOverride: projectScan.projectConfig,
96997
+ configSourceDirectory: projectScan.projectConfigSourceDirectory ?? void 0,
96998
+ changedLineRanges: stagedLineRanges === null ? void 0 : resolveProjectChangedLineRanges(resolvedDirectory, projectScan.scanDirectory, stagedLineRanges),
96999
+ suppressRendering: isMultiProject,
97000
+ concurrentScan: isMultiProject
96201
97001
  });
96202
97002
  const remappedDiagnostics = scanResult.diagnostics.map((diagnostic) => ({
96203
97003
  ...diagnostic,
96204
- filePath: Path.isAbsolute(diagnostic.filePath) ? diagnostic.filePath.replaceAll(snapshot.tempDirectory, () => resolvedDirectory) : diagnostic.filePath
97004
+ filePath: Path.isAbsolute(diagnostic.filePath) ? diagnostic.filePath.replaceAll(projectTempDirectory, () => projectScan.scanDirectory) : diagnostic.filePath
96205
97005
  }));
96206
- finalizeScans({
96207
- completedScans: [{
96208
- directory: resolvedDirectory,
96209
- result: {
96210
- ...scanResult,
96211
- diagnostics: remappedDiagnostics,
96212
- project: {
96213
- ...scanResult.project,
96214
- rootDirectory: resolvedDirectory
96215
- }
96216
- },
96217
- config: userConfig
96218
- }],
96219
- mode: "staged",
96220
- diff: null,
96221
- baselineIntended: false,
96222
- isJsonMode,
96223
- isScoreOnly,
96224
- flags,
96225
- categoryFilters,
96226
- userConfig,
96227
- resolvedDirectory,
96228
- startTime
96229
- });
97006
+ return {
97007
+ directory: projectScan.scanDirectory,
97008
+ result: {
97009
+ ...scanResult,
97010
+ diagnostics: remappedDiagnostics,
97011
+ project: {
97012
+ ...scanResult.project,
97013
+ rootDirectory: projectScan.scanDirectory
97014
+ }
97015
+ },
97016
+ config: projectScan.projectConfig,
97017
+ frameSourceRoot: projectTempDirectory
97018
+ };
97019
+ };
97020
+ const stagedBatch = await runProjectScanBatch({
97021
+ projects: stagedProjectRuns,
97022
+ isQuiet,
97023
+ isSilent: scanOptions.silent === true,
97024
+ scanProject: scanStagedProject
97025
+ }).catch((error) => {
97026
+ snapshot.cleanup();
97027
+ throw error;
97028
+ });
97029
+ const completedScans = stagedBatch.completedScans;
97030
+ try {
97031
+ if (!isQuiet && isMultiProject) {
97032
+ const showShareLink = !isShareOptedOut(completedScans, scanOptions.noScore) && !scanOptions.isCi;
97033
+ await runPromise(printMultiProjectSummary({
97034
+ completedScans,
97035
+ categoryFilters,
97036
+ verbose: Boolean(flags.verbose),
97037
+ outputDirectory: flags.outputDir,
97038
+ isOffline: !showShareLink,
97039
+ projectName: Path.basename(resolvedDirectory),
97040
+ totalElapsedMilliseconds: stagedBatch.elapsedMilliseconds
97041
+ }));
97042
+ }
97043
+ if (flags.outputDir && isMultiProject && isQuiet) await runPromise(printDiagnosticsDump(filterDiagnosticsByCategories(filterScansForSurface(completedScans, scanOptions.outputSurface ?? "cli"), categoryFilters), flags.outputDir, false, "stderr"));
96230
97044
  } finally {
96231
97045
  snapshot.cleanup();
96232
97046
  }
97047
+ finalizeScans({
97048
+ completedScans,
97049
+ mode: "staged",
97050
+ diff: null,
97051
+ baselineIntended: false,
97052
+ isJsonMode,
97053
+ isScoreOnly,
97054
+ flags,
97055
+ categoryFilters,
97056
+ userConfig,
97057
+ resolvedDirectory,
97058
+ startTime
97059
+ });
96233
97060
  return;
96234
97061
  }
96235
97062
  const projectDirectories = await selectProjects(resolvedDirectory, flags.project, skipPrompts, userConfig?.projects);
96236
- const changedFilesDiffInfo = flags.changedFilesFrom ? buildChangedFilesDiffInfo(readChangedFilesFrom(Path.resolve(flags.changedFilesFrom))) : null;
97063
+ const projectSelectionCompletedTime = performance$1.now();
97064
+ let changedFilesDiffInfo = flags.changedFilesFrom ? buildChangedFilesDiffInfo(readChangedFilesFrom(Path.resolve(flags.changedFilesFrom))) : null;
97065
+ if (changedFilesDiffInfo !== null && scanTarget.didRedirectViaRootDir) {
97066
+ const relativeProjectDirectory = resolveProjectRelativeDirectory(requestedDirectory, resolvedDirectory);
97067
+ if (relativeProjectDirectory) {
97068
+ const projectPrefix = `${relativeProjectDirectory}/`;
97069
+ changedFilesDiffInfo = {
97070
+ ...changedFilesDiffInfo,
97071
+ changedFiles: changedFilesDiffInfo.changedFiles.flatMap((filePath) => {
97072
+ return filePath.startsWith(projectPrefix) ? [filePath.slice(projectPrefix.length)] : [];
97073
+ })
97074
+ };
97075
+ }
97076
+ }
96237
97077
  const requestedScope = resolveScope(flags, userConfig);
96238
97078
  const includeUntracked = flags.includeUntracked ?? false;
96239
97079
  const scopeRequest = requestedScope.scope === void 0 && changedFilesDiffInfo !== null ? {
@@ -96241,15 +97081,20 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96241
97081
  scope: "changed"
96242
97082
  } : requestedScope;
96243
97083
  validateIncludeUntrackedScope(includeUntracked, scopeRequest.scope);
97084
+ scanStartupSpinner = !isQuiet ? spinner("Scanning...").start() : null;
97085
+ if (scanStartupSpinner !== null) recordDistribution(METRIC.scanFeedbackDelay, performance$1.now() - projectSelectionCompletedTime, { unit: "millisecond" });
96244
97086
  const wantsDiffMode = scopeRequest.scope !== void 0 && scopeRequest.scope !== "full";
96245
97087
  const shouldDetectDiff = changedFilesDiffInfo === null && (wantsDiffMode || scopeRequest.scope === void 0 && !skipPrompts && !isQuiet);
96246
97088
  const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base, includeUntracked) : null);
97089
+ scanStartupSpinner?.stop();
97090
+ scanStartupSpinner = null;
96247
97091
  const scope = await finalizeScope({
96248
97092
  requested: scopeRequest,
96249
97093
  diffInfo,
96250
97094
  skipPrompts,
96251
97095
  isQuiet
96252
97096
  });
97097
+ scanStartupSpinner = !isQuiet ? spinner("Scanning...").start() : null;
96253
97098
  const isDiffMode = scope !== "full";
96254
97099
  const comparisonBaseRef = isDiffMode && diffInfo && !diffInfo.isCurrentChanges ? diffInfo.baseSha ? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseSha) : diffInfo.diffBaseRef ?? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseBranch) : null;
96255
97100
  const baselineRef = scope === "changed" ? comparisonBaseRef : null;
@@ -96261,6 +97106,8 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96261
97106
  files: [...diffInfo.changedFiles],
96262
97107
  includeUntracked
96263
97108
  }) : null;
97109
+ scanStartupSpinner?.stop();
97110
+ scanStartupSpinner = null;
96264
97111
  if (scope === "lines" && changedLineRanges === null && !isQuiet) {
96265
97112
  cliLogger.warn("Could not determine changed lines (no base ref or git diff failed); reporting all issues in changed files.");
96266
97113
  cliLogger.break();
@@ -96274,7 +97121,6 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96274
97121
  }
96275
97122
  cliLogger.break();
96276
97123
  }
96277
- const completedScans = [];
96278
97124
  const isMultiProject = projectDirectories.length > 1;
96279
97125
  const scanProject = async (projectDirectory) => {
96280
97126
  const projectScanTarget = projectDirectory === resolvedDirectory ? scanTarget : await resolveScanTarget(projectDirectory, { allowAmbiguous: true });
@@ -96302,7 +97148,7 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96302
97148
  if (supplyChainManifestChanged) includePaths.push("package.json");
96303
97149
  }
96304
97150
  if (!isQuiet && !isMultiProject) cliLogger.dim(" ");
96305
- const scanResult = await inspect(scanDirectory, {
97151
+ const scanResult = await inspectProject(scanDirectory, {
96306
97152
  ...scanOptions,
96307
97153
  deadlineEpochMs: scanDeadlineEpochMs,
96308
97154
  includePaths,
@@ -96325,29 +97171,13 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96325
97171
  config: projectConfig
96326
97172
  };
96327
97173
  };
96328
- const scanLoopStartTime = performance$1.now();
96329
- const projectCount = projectDirectories.length;
96330
- const batchSpinner = isMultiProject && !isQuiet ? spinner(`Scanning ${projectCount} projects…`).start() : null;
96331
- const ownsBatchSpinnerSilence = isMultiProject && scanOptions.silent === true;
96332
- const wasSpinnerSilent = isSpinnerSilent();
96333
- if (ownsBatchSpinnerSilence) setSpinnerSilent(true);
96334
- let finishedProjectCount = 0;
96335
- let scanOutcomes;
96336
- try {
96337
- scanOutcomes = await mapWithConcurrency(projectDirectories, isMultiProject ? 4 : 1, async (projectDirectory) => {
96338
- const scanOutcome = await scanProject(projectDirectory);
96339
- finishedProjectCount += 1;
96340
- batchSpinner?.update(`Scanning ${projectCount} projects… (${finishedProjectCount}/${projectCount})`);
96341
- return scanOutcome;
96342
- });
96343
- } finally {
96344
- if (ownsBatchSpinnerSilence) setSpinnerSilent(wasSpinnerSilent);
96345
- batchSpinner?.stop();
96346
- }
96347
- for (const scanOutcome of scanOutcomes) {
96348
- if (scanOutcome === null) continue;
96349
- completedScans.push(scanOutcome);
96350
- }
97174
+ const projectBatch = await runProjectScanBatch({
97175
+ projects: projectDirectories,
97176
+ isQuiet,
97177
+ isSilent: scanOptions.silent === true,
97178
+ scanProject
97179
+ });
97180
+ const completedScans = projectBatch.completedScans;
96351
97181
  if (!isQuiet && isMultiProject && completedScans.length > 0) {
96352
97182
  const showShareLink = !isShareOptedOut(completedScans, scanOptions.noScore) && !scanOptions.isCi;
96353
97183
  await runPromise(printMultiProjectSummary({
@@ -96357,7 +97187,7 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96357
97187
  outputDirectory: flags.outputDir,
96358
97188
  isOffline: !showShareLink,
96359
97189
  projectName: Path.basename(resolvedDirectory),
96360
- totalElapsedMilliseconds: performance$1.now() - scanLoopStartTime
97190
+ totalElapsedMilliseconds: projectBatch.elapsedMilliseconds
96361
97191
  }));
96362
97192
  }
96363
97193
  const selectedSurfaceDiagnostics = filterDiagnosticsByCategories(filterScansForSurface(completedScans, scanOptions.outputSurface ?? "cli"), categoryFilters);
@@ -96404,6 +97234,7 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
96404
97234
  }
96405
97235
  }
96406
97236
  } catch (error) {
97237
+ scanStartupSpinner?.stop();
96407
97238
  const isUserError = isExpectedUserError(error);
96408
97239
  const sentryEventId = isUserError ? void 0 : await reportErrorToSentry(error);
96409
97240
  if (isDebugFlagEnabled()) await flushSentry();
@@ -96448,6 +97279,55 @@ const installAction = async (options, command) => {
96448
97279
  }
96449
97280
  };
96450
97281
  //#endregion
97282
+ //#region src/cli/utils/should-use-tui.ts
97283
+ const shouldUseTui = (input) => {
97284
+ if (input.isNonInteractiveEnvironment || !input.stdinIsTty || !input.stdoutIsTty || !input.supportsRawMode || input.nodeMajorVersion < 22 || input.terminalName === "dumb") return false;
97285
+ const flags = input.flags;
97286
+ let requiresStableConfiguredScope = false;
97287
+ if (input.userConfig?.scope !== void 0) requiresStableConfiguredScope = input.userConfig.scope !== "full";
97288
+ else if (input.userConfig?.diff !== void 0) requiresStableConfiguredScope = input.userConfig.diff !== false && input.userConfig.diff !== "false";
97289
+ else if (input.userConfig?.base !== void 0) requiresStableConfiguredScope = true;
97290
+ return !(flags.verbose === true || flags.outputDir !== void 0 || flags.score === true || flags.json === true || flags.jsonCompact === true || flags.jsonOut !== void 0 || flags.staged === true || flags.scope !== void 0 || flags.base !== void 0 || flags.includeUntracked === true || flags.diff !== void 0 || flags.changedFilesFrom !== void 0 || flags.debug === true || flags.failOn !== void 0 || requiresStableConfiguredScope);
97291
+ };
97292
+ //#endregion
97293
+ //#region src/cli/commands/scan.ts
97294
+ const runScanCommand = async (input) => {
97295
+ const nodeMajorVersion = Number(process.versions.node.split(".")[0]);
97296
+ const tuiEnvironment = {
97297
+ flags: input.flags,
97298
+ isNonInteractiveEnvironment: isNonInteractiveEnvironment(),
97299
+ nodeMajorVersion,
97300
+ stdinIsTty: process.stdin.isTTY === true,
97301
+ stdoutIsTty: process.stdout.isTTY === true,
97302
+ supportsRawMode: typeof process.stdin.setRawMode === "function",
97303
+ terminalName: process.env.TERM
97304
+ };
97305
+ if (!shouldUseTui(tuiEnvironment)) {
97306
+ await inspectAction(input.directory, input.flags, input.invocationCommand);
97307
+ return;
97308
+ }
97309
+ await runProjectMigrations(path.resolve(input.directory));
97310
+ const scanTarget = await resolveScanTarget(input.directory, { allowAmbiguous: true });
97311
+ if (!shouldUseTui({
97312
+ ...tuiEnvironment,
97313
+ userConfig: scanTarget.userConfig
97314
+ })) {
97315
+ await inspectAction(input.directory, input.flags, input.invocationCommand);
97316
+ return;
97317
+ }
97318
+ recordCount(METRIC.cliInvoked, 1, { command: input.invocationCommand });
97319
+ const { runScanApp } = await import("./run-scan-app-DtYeufgr.js");
97320
+ const { shouldFail } = await runScanApp({
97321
+ directory: input.directory,
97322
+ scanTarget,
97323
+ options: resolveCliInspectOptions(input.flags, null),
97324
+ projectFlag: input.flags.project,
97325
+ skipPrompts: input.flags.yes ?? false,
97326
+ blocking: input.flags.blocking
97327
+ });
97328
+ if (shouldFail) process.exitCode = 1;
97329
+ };
97330
+ //#endregion
96451
97331
  //#region src/cli/utils/rule-catalog.ts
96452
97332
  const buildRuleCatalog = () => REACT_DOCTOR_RULES.map((entry) => ({
96453
97333
  key: entry.key,
@@ -97089,12 +97969,22 @@ const ensureWindowsUtf8Console = () => {
97089
97969
  };
97090
97970
  //#endregion
97091
97971
  //#region src/cli/utils/exit-gracefully.ts
97972
+ let didStartExiting = false;
97092
97973
  const exitGracefully = () => {
97974
+ if (didStartExiting) return process.exit(130);
97975
+ didStartExiting = true;
97093
97976
  try {
97094
97977
  if (isJsonModeActive()) writeJsonErrorReport(/* @__PURE__ */ new Error("Scan cancelled by user (SIGINT/SIGTERM)"));
97095
- else console.log("\nCancelled.\n");
97978
+ else console.log([
97979
+ "",
97980
+ "Cancelled.",
97981
+ "",
97982
+ buildSectionDivider(),
97983
+ "",
97984
+ ...buildFooterLinkLines({ shareUrl: null })
97985
+ ].join("\n"));
97096
97986
  } catch {}
97097
- process.exit(130);
97987
+ flushSentry().finally(() => process.exit(130));
97098
97988
  };
97099
97989
  //#endregion
97100
97990
  //#region src/cli/utils/guard-stdin.ts
@@ -97338,6 +98228,7 @@ const COMMAND_FLAG_SPECS = new Map([
97338
98228
  "--no-color",
97339
98229
  "--no-dead-code",
97340
98230
  "--no-score",
98231
+ "--no-supply-chain",
97341
98232
  "--yes"
97342
98233
  ]),
97343
98234
  longOptionsWithRequiredValues: new Set(["--blocking", "--project"]),
@@ -97501,8 +98392,12 @@ ${highlighter.dim("Learn more:")}
97501
98392
  ${highlighter.info(CI_URL)}
97502
98393
  `;
97503
98394
  const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
97504
- const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS and inline HTML script source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
97505
- program.action(inspectAction);
98395
+ const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS and inline HTML script source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks (honors --project and config `projects`; exits 0 when nothing is staged)").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
98396
+ program.action((directory = ".", flags) => runScanCommand({
98397
+ directory,
98398
+ flags,
98399
+ invocationCommand: "inspect"
98400
+ }));
97506
98401
  program.command("design [directory]").description("Run only the focused UI design diagnostics").addHelpText("after", renderDesignHelpEpilog).action((directory, _options, command) => designAction(directory ?? ".", command.optsWithGlobals()));
97507
98402
  program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
97508
98403
  program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
@@ -97523,35 +98418,11 @@ rules.command("category <category> <severity>").description("Set the severity fo
97523
98418
  rules.command("ignore-tag <tag>").description("Skip a whole rule family by tag before linting (e.g. design)").option("-c, --cwd <cwd>", "working directory", process.cwd()).action((tag, _options, command) => rulesIgnoreTagAction(tag, command.optsWithGlobals()));
97524
98419
  rules.command("unignore-tag <tag>").description("Stop ignoring a tag previously skipped via ignore-tag").option("-c, --cwd <cwd>", "working directory", process.cwd()).action((tag, _options, command) => rulesUnignoreTagAction(tag, command.optsWithGlobals()));
97525
98420
  program.command("experimental-lsp", { hidden: false }).description("[experimental] run the React Doctor language server over stdio (for editors)").allowUnknownOption().action(() => {});
97526
- program.command("experimental-tui [directory]", { hidden: true }).description("[experimental] interactive, scrollable scan report").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").option("--no-dead-code", "skip dead-code analysis").option("--no-score", "skip the score API, the share URL, and crash reporting").option("-p, --project <names>", "scan specific workspace projects (comma-separated, or *)").option("-y, --yes", "skip the project prompt and scan every discovered project").action(async (directory = ".", _localOptions, command) => {
97527
- const options = command.optsWithGlobals();
97528
- const deadCode = options.deadCode === false ? false : void 0;
97529
- const noScore = options.score === false ? true : void 0;
97530
- const nodeMajorVersion = Number(process.versions.node.split(".")[0]);
97531
- if (process.stdout.isTTY !== true || process.stdin.isTTY !== true || isNonInteractiveEnvironment() || nodeMajorVersion < 22) {
97532
- await inspectAction(directory, {
97533
- deadCode,
97534
- score: options.score === false ? false : void 0,
97535
- project: options.project,
97536
- yes: options.yes,
97537
- blocking: options.blocking
97538
- });
97539
- return;
97540
- }
97541
- recordCount(METRIC.cliInvoked, 1, { command: "experimental-tui" });
97542
- const { runScanApp } = await import("./run-scan-app-DZ5jGWFf.js");
97543
- const { shouldFail } = await runScanApp({
97544
- directory,
97545
- options: {
97546
- deadCode,
97547
- noScore
97548
- },
97549
- projectFlag: options.project,
97550
- skipPrompts: options.yes ?? false,
97551
- blocking: options.blocking
97552
- });
97553
- if (shouldFail) process.exitCode = 1;
97554
- });
98421
+ program.command("experimental-tui [directory]", { hidden: true }).description("[experimental] interactive, scrollable scan report").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").option("--no-dead-code", "skip dead-code analysis").option("--no-supply-chain", "skip the dependency supply-chain scan").option("--score", "only print the numeric score (for scripts and CI)").option("--no-score", "skip the score API, the share URL, and crash reporting").option("-p, --project <names>", "scan specific workspace projects (comma-separated, or *)").option("-y, --yes", "skip the project prompt and scan every discovered project").action((directory = ".", _localOptions, command) => runScanCommand({
98422
+ directory,
98423
+ flags: command.optsWithGlobals(),
98424
+ invocationCommand: "experimental-tui"
98425
+ }));
97555
98426
  process.stdout.on("error", (error) => {
97556
98427
  if (error.code === "EPIPE") process.exit(0);
97557
98428
  });
@@ -97578,7 +98449,7 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
97578
98449
  handleError(error, { sentryEventId });
97579
98450
  });
97580
98451
  //#endregion
97581
- export { toForwardSlashes as A, highlighter as B, buildRulePriorityMap as C, resolveBlockingLevel as D, formatLearnMoreLine as E, isCiEnvironment as F, sync as G, mergeReactDoctorConfigs as H, DIAGNOSTIC_CATEGORY_BUCKETS as I, void_ as K, Progress as L, isUnicodeSupported as M, METRIC as N, isReactDoctorWorkflowInstalled as O, TUI_REPORT_DETAIL_WIDTH_FRACTION as P, Reporter as R, hasLintHardFailure as S, formatFixRecipeLine as T, resolveScanTarget as U, mapWithConcurrency as V, runPromise as W, canAnimateOnboarding as _, resolveCliInspectOptions as a, buildCodeFrame as b, detectLaunchableAgents as c, launchCliAgent as d, isShareOptedOut as f, doctorFace as g, easeOutCubic as h, selectProjects as i, findNearestPackageDirectory as j, recordCount as k, CLI_AGENT_BINARIES as l, printFooter as m, shouldFailScanGate as n, countUniqueScannedFiles as o, inspect as p, succeed$3 as q, discoverWorkspacePackages as r, setUpGitHubActions as s, exitGracefully as t, copyToClipboard as u, computeProjectedScore as v, buildSortedRuleGroups as w, buildNoScoreMessage as x, formatElapsedTime as y, getCategoryImpact as z };
98452
+ export { highlighter as $, hasLintHardFailure as A, isUnicodeSupported as B, shouldRecordOnboarding as C, buildCodeFrame as D, formatElapsedTime as E, resolveBlockingLevel as F, isCiEnvironment as G, METRIC as H, isReactDoctorWorkflowInstalled as I, Progress as J, DIAGNOSTIC_CATEGORY_BUCKETS as K, recordCount as L, buildSortedRuleGroups as M, formatFixRecipeLine as N, buildEmptyReportMessage as O, formatLearnMoreLine as P, hasPublishedFixRecipe as Q, toForwardSlashes as R, isOnboardingForced as S, computeProjectedScore as T, TUI_REPORT_COMPACT_MAX_ROWS as U, CI_TRUST_COMPANIES as V, TUI_REPORT_DETAIL_WIDTH_FRACTION as W, buildRuleDocsUrl as X, Reporter as Y, getCategoryImpact as Z, printFooter as _, resolveCliInspectOptions as a, void_ as at, markOnboardingComplete as b, setUpGitHubActions as c, CLI_AGENT_BINARIES as d, mapWithConcurrency as et, copyToClipboard as f, createInvocationInspect as g, isShareOptedOut as h, selectProjects as i, sync as it, buildRulePriorityMap as j, buildNoScoreMessage as k, openUrl as l, buildHandoffPayload as m, shouldFailScanGate as n, resolveScanTarget as nt, filterScansForSurface as o, succeed$3 as ot, launchCliAgent as p, GITHUB_ACTIONS_SETUP_URL as q, discoverWorkspacePackages as r, runPromise as rt, countUniqueScannedFiles as s, exitGracefully as t, mergeReactDoctorConfigs as tt, detectLaunchableAgents as u, easeOutCubic as v, filterDiagnosticsByCategories as w, canAnimateOnboarding as x, doctorFace as y, findNearestPackageDirectory as z };
97582
98453
 
97583
98454
  //# sourceMappingURL=cli.js.map
97584
- //# debugId=c86de9ea-b858-5528-89a8-6ff4d377c873
98455
+ //# debugId=e4075e6f-4128-5af6-bc97-656272450282