react-doctor 0.9.4-dev.da0647e → 0.9.5-dev.2b8bc5c

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.
@@ -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]="07705956-10ec-5419-acea-25f57c6a08c5")}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]="ebadd5f4-f19b-552c-b2da-27e8355f567a")}catch(e){}}();
3
3
  import { n as __require, r as __toESM$1, t as __commonJSMin$1 } from "./chunk-q7NCDQ7-.js";
4
4
  import { createRequire } from "node:module";
5
5
  import * as NodeChildProcess from "node:child_process";
@@ -15,11 +15,11 @@ import crypto, { createHash, randomUUID } from "node:crypto";
15
15
  import { ResolverFactory } from "oxc-resolver";
16
16
  import ts from "typescript";
17
17
  import * as NodeUrl from "node:url";
18
- import { fileURLToPath, pathToFileURL } from "node:url";
18
+ import { fileURLToPath } from "node:url";
19
19
  import * as OS from "node:os";
20
20
  import os, { tmpdir } from "node:os";
21
- import { parseJSON5 } from "confbox";
22
21
  import { createJiti } from "jiti";
22
+ import { parseJSON5 } from "confbox";
23
23
  import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
24
24
  import { convertToTSX } from "@astrojs/compiler/sync";
25
25
  import { gzipSync } from "node:zlib";
@@ -28,9 +28,9 @@ import { performance as performance$1 } from "node:perf_hooks";
28
28
  import { promisify, stripVTControlCharacters } from "node:util";
29
29
  import tty from "node:tty";
30
30
  import basePrompts from "prompts";
31
- import { codeFrameColumns } from "@babel/code-frame";
32
- import Conf from "conf";
33
31
  import { detectInstalledSkillAgents, getSkillAgentTypes } from "agent-install";
32
+ import Conf from "conf";
33
+ import { codeFrameColumns } from "@babel/code-frame";
34
34
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
35
35
  /**
36
36
  * The `Pipeable` module defines the shared interface and implementation helpers
@@ -62453,7 +62453,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
62453
62453
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
62454
62454
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
62455
62455
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
62456
- const CORE_PACKAGE_VERSION = "0.9.4";
62456
+ const CORE_PACKAGE_VERSION = "0.9.5";
62457
62457
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
62458
62458
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
62459
62459
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -66965,6 +66965,11 @@ var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeR
66965
66965
  if (CACHE_DISABLED_VALUES$1.has(noDeadCodeCache)) return false;
66966
66966
  return true;
66967
66967
  } }) {};
66968
+ const importDefaultExport = async (jitiInstance, filePath) => {
66969
+ const imported = await jitiInstance.import(filePath);
66970
+ return imported?.default ?? imported;
66971
+ };
66972
+ const readJson5File = (filePath) => parseJSON5(NFS.readFileSync(filePath, "utf-8"));
66968
66973
  const DIAGNOSTIC_SURFACES = [
66969
66974
  "cli",
66970
66975
  "prComment",
@@ -67195,11 +67200,7 @@ const DATA_CONFIG_EXTENSIONS = new Set(["json", "jsonc"]);
67195
67200
  const PACKAGE_JSON_FILENAME = "package.json";
67196
67201
  const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
67197
67202
  const LEGACY_CONFIG_FILENAME = "react-doctor.config.json";
67198
- const jiti = createJiti(import.meta.url);
67199
- const importDefaultExport = async (jitiInstance, filePath) => {
67200
- const imported = await jitiInstance.import(filePath);
67201
- return imported?.default ?? imported;
67202
- };
67203
+ const jiti$1 = createJiti(import.meta.url);
67203
67204
  const SELF_PACKAGE_IMPORT_SPECIFIER = "react-doctor/api";
67204
67205
  const SELF_PACKAGE_RESOLVE_TARGETS = ["react-doctor/api", "@react-doctor/core"];
67205
67206
  const MODULE_NOT_FOUND_ERROR_CODES = new Set(["MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND"]);
@@ -67207,7 +67208,7 @@ let selfAliasJiti;
67207
67208
  const getSelfAliasJiti = () => {
67208
67209
  if (selfAliasJiti !== void 0) return selfAliasJiti;
67209
67210
  for (const resolveTarget of SELF_PACKAGE_RESOLVE_TARGETS) try {
67210
- const aliasTargetPath = fileURLToPath(jiti.esmResolve(resolveTarget));
67211
+ const aliasTargetPath = fileURLToPath(jiti$1.esmResolve(resolveTarget));
67211
67212
  selfAliasJiti = createJiti(import.meta.url, { alias: { [SELF_PACKAGE_IMPORT_SPECIFIER]: aliasTargetPath } });
67212
67213
  return selfAliasJiti;
67213
67214
  } catch {
@@ -67219,7 +67220,7 @@ const getSelfAliasJiti = () => {
67219
67220
  const isSelfPackageResolutionError = (error) => error instanceof Error && "code" in error && typeof error.code === "string" && MODULE_NOT_FOUND_ERROR_CODES.has(error.code) && error.message.includes(SELF_PACKAGE_IMPORT_SPECIFIER);
67220
67221
  const loadModuleConfig = async (filePath) => {
67221
67222
  try {
67222
- return await importDefaultExport(jiti, filePath);
67223
+ return await importDefaultExport(jiti$1, filePath);
67223
67224
  } catch (error) {
67224
67225
  if (!isSelfPackageResolutionError(error)) throw error;
67225
67226
  const aliasJiti = getSelfAliasJiti();
@@ -67231,12 +67232,11 @@ const loadModuleConfig = async (filePath) => {
67231
67232
  }
67232
67233
  }
67233
67234
  };
67234
- const readDataConfig = (filePath) => parseJSON5(NFS.readFileSync(filePath, "utf-8"));
67235
67235
  const readEmbeddedPackageJsonConfig = (directory) => {
67236
67236
  const packageJsonPath = Path.join(directory, PACKAGE_JSON_FILENAME);
67237
67237
  if (!isFile(packageJsonPath)) return null;
67238
67238
  try {
67239
- const packageJson = parseJSON5(NFS.readFileSync(packageJsonPath, "utf-8"));
67239
+ const packageJson = readJson5File(packageJsonPath);
67240
67240
  if (isPlainObject(packageJson)) {
67241
67241
  const embeddedConfig = packageJson[PACKAGE_JSON_CONFIG_KEY];
67242
67242
  if (isPlainObject(embeddedConfig)) return embeddedConfig;
@@ -67261,7 +67261,7 @@ const loadLegacyConfig = (directory) => {
67261
67261
  loaded: null
67262
67262
  };
67263
67263
  try {
67264
- const parsed = readDataConfig(legacyFilePath);
67264
+ const parsed = readJson5File(legacyFilePath);
67265
67265
  if (isPlainObject(parsed)) {
67266
67266
  warn(`${LEGACY_CONFIG_FILENAME} is deprecated — rename it to ${CONFIG_BASENAME}.json (or author a ${CONFIG_BASENAME}.ts). It is still read for now.`);
67267
67267
  return {
@@ -67290,7 +67290,7 @@ const loadConfigFromDirectory = async (directory) => {
67290
67290
  if (!isFile(filePath)) continue;
67291
67291
  const isDataFile = DATA_CONFIG_EXTENSIONS.has(extension);
67292
67292
  try {
67293
- const parsed = isDataFile ? readDataConfig(filePath) : await loadModuleConfig(filePath);
67293
+ const parsed = isDataFile ? readJson5File(filePath) : await loadModuleConfig(filePath);
67294
67294
  if (isPlainObject(parsed)) return {
67295
67295
  status: "found",
67296
67296
  loaded: {
@@ -69536,20 +69536,45 @@ const collectIgnorePatterns = (rootDirectory) => {
69536
69536
  cachedPatternsByRoot.set(rootDirectory, patterns);
69537
69537
  return patterns;
69538
69538
  };
69539
- const failOpenReadJson = (filePath, fallback) => {
69539
+ const KNIP_DATA_CONFIG_FILENAMES = new Set([
69540
+ "knip.json",
69541
+ "knip.jsonc",
69542
+ ".knip.json",
69543
+ ".knip.jsonc"
69544
+ ]);
69545
+ const KNIP_CONFIG_FILENAMES = [
69546
+ ...KNIP_DATA_CONFIG_FILENAMES,
69547
+ "knip.ts",
69548
+ "knip.js",
69549
+ "knip.config.ts",
69550
+ "knip.config.js"
69551
+ ];
69552
+ const jiti = createJiti(import.meta.url, { moduleCache: false });
69553
+ const resolveKnipConfigExport = async (configExport) => {
69554
+ const config = typeof configExport === "function" ? await configExport() : await configExport;
69555
+ return isRecord$1(config) ? config : null;
69556
+ };
69557
+ const loadKnipConfigFile = async (configFilename, filePath) => {
69540
69558
  try {
69541
- return JSON.parse(NFS.readFileSync(filePath, "utf8"));
69559
+ return await resolveKnipConfigExport(KNIP_DATA_CONFIG_FILENAMES.has(configFilename) ? readJson5File(filePath) : await importDefaultExport(jiti, filePath));
69542
69560
  } catch {
69543
- return fallback;
69561
+ return null;
69544
69562
  }
69545
69563
  };
69546
- const KNIP_JSON_FILENAME = "knip.json";
69547
- const readKnipConfig = (rootDirectory) => {
69548
- const knipJson = failOpenReadJson(path.join(rootDirectory, KNIP_JSON_FILENAME), null);
69549
- if (isRecord$1(knipJson)) return knipJson;
69550
- const packageJson = failOpenReadJson(path.join(rootDirectory, "package.json"), null);
69551
- const packageKnipConfig = isRecord$1(packageJson) ? packageJson.knip : null;
69552
- return isRecord$1(packageKnipConfig) ? packageKnipConfig : null;
69564
+ const loadKnipConfig = async (rootDirectory) => {
69565
+ for (const configFilename of KNIP_CONFIG_FILENAMES) {
69566
+ const filePath = path.join(rootDirectory, configFilename);
69567
+ if (!isFile(filePath)) continue;
69568
+ const config = await loadKnipConfigFile(configFilename, filePath);
69569
+ if (config) return config;
69570
+ }
69571
+ try {
69572
+ const packageJson = readJson5File(path.join(rootDirectory, "package.json"));
69573
+ const packageKnipConfig = isRecord$1(packageJson) ? packageJson.knip : null;
69574
+ return isRecord$1(packageKnipConfig) ? packageKnipConfig : null;
69575
+ } catch {
69576
+ return null;
69577
+ }
69553
69578
  };
69554
69579
  const normalizePatternList = (value) => {
69555
69580
  if (typeof value === "string" && value.length > 0) return [value];
@@ -69569,22 +69594,28 @@ const collectKnipWorkspacePatterns = (workspaces, settingName) => {
69569
69594
  }
69570
69595
  return patterns;
69571
69596
  };
69572
- const collectKnipPatterns = (rootDirectory, settingName) => {
69573
- const config = readKnipConfig(rootDirectory);
69597
+ const collectKnipPatterns = (config, settingName) => {
69574
69598
  if (!config) return [];
69575
69599
  return [...normalizePatternList(config[settingName]), ...collectKnipWorkspacePatterns(config.workspaces, settingName)];
69576
69600
  };
69577
- const collectDeadCodeIgnorePatterns = (rootDirectory) => {
69601
+ const collectDeadCodeIgnorePatterns = (rootDirectory, config) => {
69578
69602
  const seen = /* @__PURE__ */ new Set();
69579
69603
  const sources = [
69580
69604
  readIgnoreFile(path.join(rootDirectory, ".gitignore")),
69581
69605
  collectIgnorePatterns(rootDirectory),
69582
- collectKnipPatterns(rootDirectory, "ignore")
69606
+ collectKnipPatterns(config, "ignore")
69583
69607
  ];
69584
69608
  for (const source of sources) for (const pattern of source) seen.add(pattern);
69585
69609
  return [...seen].filter((pattern) => pattern.length > 0);
69586
69610
  };
69587
- const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
69611
+ const collectDeadCodeEntryPatterns = (config) => [...new Set(collectKnipPatterns(config, "entry"))].filter((pattern) => pattern.length > 0);
69612
+ const collectDeadCodePatterns = async (rootDirectory) => {
69613
+ const config = await loadKnipConfig(rootDirectory);
69614
+ return {
69615
+ entryPatterns: collectDeadCodeEntryPatterns(config),
69616
+ ignorePatterns: collectDeadCodeIgnorePatterns(rootDirectory, config)
69617
+ };
69618
+ };
69588
69619
  const atomicWriteJson = (filePath, value) => {
69589
69620
  try {
69590
69621
  NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
@@ -69595,6 +69626,13 @@ const atomicWriteJson = (filePath, value) => {
69595
69626
  return;
69596
69627
  }
69597
69628
  };
69629
+ const failOpenReadJson = (filePath, fallback) => {
69630
+ try {
69631
+ return JSON.parse(NFS.readFileSync(filePath, "utf8"));
69632
+ } catch {
69633
+ return fallback;
69634
+ }
69635
+ };
69598
69636
  const hashFileContents = (filePath) => {
69599
69637
  try {
69600
69638
  return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
@@ -69603,7 +69641,7 @@ const hashFileContents = (filePath) => {
69603
69641
  }
69604
69642
  };
69605
69643
  const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
69606
- const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
69644
+ const CORE_SIDE_MANIFEST_NAMES = [...KNIP_CONFIG_FILENAMES, "deno.lock"];
69607
69645
  const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
69608
69646
  const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
69609
69647
  const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
@@ -70129,8 +70167,7 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs, abortSignal) => new Pro
70129
70167
  const checkDeadCode = async (options) => {
70130
70168
  const rootDirectory = toCanonicalPath(options.rootDirectory);
70131
70169
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
70132
- const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
70133
- const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
70170
+ const { entryPatterns, ignorePatterns } = await collectDeadCodePatterns(rootDirectory);
70134
70171
  const tsConfigPath = resolveTsConfigPath(rootDirectory);
70135
70172
  const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
70136
70173
  const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
@@ -76357,8 +76394,7 @@ const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
76357
76394
  const TUI_REPORT_COMPACT_MAX_ROWS = 23;
76358
76395
  const TUI_REPORT_DETAIL_WIDTH_FRACTION = .6;
76359
76396
  const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
76360
- const WELCOME_EXPLANATION_HOLD_MS = 1e3;
76361
- const CATEGORY_COUNTUP_SETTLE_HOLD_MS = 1e3;
76397
+ const MINIMUM_VTE_VERSION_FOR_HYPERLINKS = 5e3;
76362
76398
  const INTERNAL_ERROR_JSON_FALLBACK = "{\"schemaVersion\":3,\"version\":\"unknown\",\"ok\":false,\"directory\":\"\",\"mode\":\"full\",\"diff\":null,\"projects\":[],\"diagnostics\":[],\"summary\":{\"errorCount\":0,\"warningCount\":0,\"affectedFileCount\":0,\"totalDiagnosticCount\":0,\"score\":null,\"scoreLabel\":null},\"elapsedMilliseconds\":0,\"error\":{\"message\":\"Internal error\",\"name\":\"Error\",\"chain\":[]}}\n";
76363
76399
  const SENTRY_FLUSH_TIMEOUT_MS = 2e3;
76364
76400
  const NANOSECONDS_PER_SECOND = 1000000000n;
@@ -76412,7 +76448,7 @@ const METRIC = {
76412
76448
  tuiFindingNavigated: "tui.finding_navigated",
76413
76449
  tuiIssueStreamShown: "tui.issue_stream_shown",
76414
76450
  tuiProjectPathContextShown: "tui.project_path_context_shown",
76415
- tuiProjectSelectInlineShown: "tui.project_select_inline_shown",
76451
+ tuiProjectSelectShown: "tui.project_select_shown",
76416
76452
  tuiReportActionSelected: "tui.report_action_selected",
76417
76453
  tuiCancelled: "tui.cancelled",
76418
76454
  tuiScanInlineShown: "tui.scan_inline_shown",
@@ -76461,7 +76497,7 @@ const makeNoopConsole = () => ({
76461
76497
  });
76462
76498
  //#endregion
76463
76499
  //#region src/cli/utils/version.ts
76464
- const VERSION = "0.9.4-dev.da0647e";
76500
+ const VERSION = "0.9.5-dev.2b8bc5c";
76465
76501
  //#endregion
76466
76502
  //#region src/cli/utils/json-mode.ts
76467
76503
  let context = null;
@@ -76922,13 +76958,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
76922
76958
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
76923
76959
  * standard `SENTRY_RELEASE` override.
76924
76960
  */
76925
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.4-dev.da0647e`;
76961
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.5-dev.2b8bc5c`;
76926
76962
  /**
76927
76963
  * Deployment environment shown in Sentry's environment filter. Defaults to
76928
76964
  * `production` for tagged releases and `development` for dev/unbuilt versions,
76929
76965
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
76930
76966
  */
76931
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.4-dev.da0647e") ? "development" : "production");
76967
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.5-dev.2b8bc5c") ? "development" : "production");
76932
76968
  /**
76933
76969
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
76934
76970
  * (set to `0` to disable tracing) and falls back to
@@ -82449,88 +82485,68 @@ const printAgentGuidance = () => gen(function* () {
82449
82485
  yield* log("");
82450
82486
  });
82451
82487
  //#endregion
82452
- //#region src/cli/utils/box-text.ts
82453
- const ESCAPE = String.fromCharCode(27);
82454
- const ANSI_ESCAPE_PATTERN = new RegExp(`${ESCAPE}\\[[0-9;]*m`, "g");
82455
- const ANSI_LEADING_ESCAPE_PATTERN = new RegExp(`^${ESCAPE}\\[[0-9;]*m`);
82456
- const ANSI_RESET = `${ESCAPE}[0m`;
82457
- const ELLIPSIS = "…";
82458
- const visibleWidth = (line) => line.replace(ANSI_ESCAPE_PATTERN, "").length;
82459
- const truncateToVisibleWidth = (line, maxVisibleWidth) => {
82460
- if (visibleWidth(line) <= maxVisibleWidth) return line;
82461
- const keepWidth = Math.max(0, maxVisibleWidth - 1);
82462
- let result = "";
82463
- let keptVisible = 0;
82464
- let cursor = 0;
82465
- while (cursor < line.length && keptVisible < keepWidth) {
82466
- const escapeMatch = ANSI_LEADING_ESCAPE_PATTERN.exec(line.slice(cursor));
82467
- if (escapeMatch) {
82468
- result += escapeMatch[0];
82469
- cursor += escapeMatch[0].length;
82470
- continue;
82471
- }
82472
- result += line[cursor];
82473
- keptVisible += 1;
82474
- cursor += 1;
82488
+ //#region src/cli/utils/filter-diagnostics-by-categories.ts
82489
+ const filterDiagnosticsByCategories = (diagnostics, categories) => categories.size === 0 ? [...diagnostics] : diagnostics.filter((diagnostic) => categories.has(diagnostic.category));
82490
+ //#endregion
82491
+ //#region src/cli/utils/format-diagnostic-site.ts
82492
+ const formatDiagnosticSite = (site) => site.line > 0 ? `${site.filePath}:${site.line}` : site.filePath;
82493
+ //#endregion
82494
+ //#region src/cli/utils/format-rule-summary.ts
82495
+ const formatFileContextTag = (diagnostic) => diagnostic.fileContext ? ` (${diagnostic.fileContext} file)` : "";
82496
+ const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
82497
+ const firstDiagnostic = ruleDiagnostics[0];
82498
+ if (firstDiagnostic === void 0) return "";
82499
+ const distinctMessages = [...new Set(ruleDiagnostics.map((diagnostic) => diagnostic.message))];
82500
+ const sections = [
82501
+ `Rule: ${ruleKey}`,
82502
+ `Severity: ${firstDiagnostic.severity}`,
82503
+ `Category: ${firstDiagnostic.category}`,
82504
+ `Count: ${ruleDiagnostics.length}`,
82505
+ "",
82506
+ distinctMessages.join("\n\n")
82507
+ ];
82508
+ if (firstDiagnostic.help) sections.push("", `Suggestion: ${firstDiagnostic.help}`);
82509
+ if (firstDiagnostic.url) sections.push("", `Docs: ${firstDiagnostic.url}`);
82510
+ const fixRecipeLine = formatFixRecipeLine(firstDiagnostic);
82511
+ if (fixRecipeLine) sections.push("", fixRecipeLine);
82512
+ sections.push("", "Files:");
82513
+ for (const diagnostic of ruleDiagnostics) {
82514
+ sections.push(` ${formatDiagnosticSite(diagnostic)}${formatFileContextTag(diagnostic)}`);
82515
+ if (diagnostic.suppressionHint) sections.push(` ${diagnostic.suppressionHint}`);
82475
82516
  }
82476
- return `${result}${ANSI_RESET}${ELLIPSIS}`;
82477
- };
82478
- const boxText = (content, innerWidth) => {
82479
- const horizontalRule = highlighter.dim("─".repeat(innerWidth + 2));
82480
- const side = highlighter.dim("│");
82481
- const body = content.split("\n").map((rawLine) => {
82482
- const line = truncateToVisibleWidth(rawLine, innerWidth);
82483
- return `${side} ${line}${" ".repeat(innerWidth - visibleWidth(line))} ${side}`;
82484
- });
82485
- return [
82486
- `${highlighter.dim("┌")}${horizontalRule}${highlighter.dim("┐")}`,
82487
- ...body,
82488
- `${highlighter.dim("└")}${horizontalRule}${highlighter.dim("┘")}`
82489
- ].join("\n");
82517
+ return `${sections.join("\n")}\n`;
82490
82518
  };
82491
82519
  //#endregion
82492
- //#region src/cli/utils/resolve-absolute-path.ts
82493
- /**
82494
- * Resolves a diagnostic's `filePath` (relative to its project root, or
82495
- * already absolute) to an absolute path. Shared by the code-frame reader and
82496
- * the terminal hyperlink builder so both turn a relative path into the same
82497
- * on-disk location.
82498
- */
82499
- const resolveAbsolutePath = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
82500
- //#endregion
82501
- //#region src/cli/utils/build-code-frame.ts
82502
- /**
82503
- * Renders a syntax-highlighted source excerpt around a diagnostic site
82504
- * with a caret pointing at the offending column. Returns null when the
82505
- * file can't be read (e.g. multi-project summaries where paths are
82506
- * resolved against a different cwd), so callers can fall back to the
82507
- * bare `file:line` reference instead of failing the whole render.
82508
- */
82509
- const buildCodeFrame = (input) => {
82510
- if (input.line <= 0) return null;
82511
- const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
82512
- let source;
82520
+ //#region src/cli/utils/write-diagnostics-directory.ts
82521
+ const ruleDumpFileName = (ruleKey) => ruleKey.replace(/\//g, "--") + ".txt";
82522
+ const readPreviousRuleDumpFileNames = (directory) => {
82513
82523
  try {
82514
- source = NFS.readFileSync(absolutePath, "utf8");
82524
+ const previous = JSON.parse(NFS.readFileSync(Path.join(directory, "diagnostics.json"), "utf8"));
82525
+ return new Set(previous.filter((d) => typeof d?.plugin === "string" && typeof d?.rule === "string").map((d) => ruleDumpFileName(`${d.plugin}/${d.rule}`)));
82515
82526
  } catch {
82516
- return null;
82527
+ return /* @__PURE__ */ new Set();
82517
82528
  }
82518
- if ((source.split("\n", input.line)[input.line - 1] ?? "").length > 200) return null;
82519
- const location = input.endLine != null && input.endLine > input.line ? {
82520
- start: { line: input.line },
82521
- end: { line: input.endLine }
82522
- } : { start: {
82523
- line: input.line,
82524
- column: input.column > 0 ? input.column : void 0
82525
- } };
82526
- return codeFrameColumns(source, location, {
82527
- highlightCode: true,
82528
- linesAbove: 1,
82529
- linesBelow: 1,
82530
- ...input.message ? { message: input.message } : {}
82531
- });
82529
+ };
82530
+ const writeDiagnosticsDirectory = (diagnostics, outputDirectory) => {
82531
+ const directory = outputDirectory ? Path.resolve(outputDirectory) : Path.join(tmpdir(), `react-doctor-${randomUUID()}`);
82532
+ if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) NFS.rmSync(Path.join(directory, fileName), { force: true });
82533
+ NFS.mkdirSync(directory, { recursive: true });
82534
+ for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) NFS.writeFileSync(Path.join(directory, ruleDumpFileName(ruleKey)), formatRuleSummary(ruleKey, ruleDiagnostics));
82535
+ NFS.writeFileSync(Path.join(directory, "diagnostics.json"), JSON.stringify(diagnostics));
82536
+ return directory;
82532
82537
  };
82533
82538
  //#endregion
82539
+ //#region src/cli/utils/print-diagnostics-dump.ts
82540
+ const printDiagnosticsDump = (diagnostics, outputDirectory, verbose, stream = "stdout") => gen(function* () {
82541
+ const writtenDirectory = yield* try_({
82542
+ try: () => writeDiagnosticsDirectory(diagnostics, outputDirectory),
82543
+ catch: (cause) => cause
82544
+ }).pipe(orElseSucceed(() => null));
82545
+ if (writtenDirectory === null || !verbose && !outputDirectory) return;
82546
+ const pathLine = highlighter.gray(` Full diagnostics written to ${writtenDirectory}`);
82547
+ yield* stream === "stderr" || Boolean(outputDirectory) && isJsonModeActive() ? sync(() => process.stderr.write(`${pathLine}\n`)) : log(pathLine);
82548
+ });
82549
+ //#endregion
82534
82550
  //#region src/cli/utils/resolve-measure-width.ts
82535
82551
  const resolveClampedWidth = (input) => {
82536
82552
  const terminalColumns = process.stdout.columns;
@@ -82544,20 +82560,6 @@ const resolveMeasureWidth = (reservedColumns = 0) => resolveClampedWidth({
82544
82560
  minWidth: 24
82545
82561
  });
82546
82562
  //#endregion
82547
- //#region src/cli/utils/build-section-divider.ts
82548
- const DIVIDER_INDENT = " ";
82549
- const buildSectionDivider = () => highlighter.dim(`${DIVIDER_INDENT}${"─".repeat(resolveMeasureWidth(2))}`);
82550
- //#endregion
82551
- //#region src/cli/utils/format-hyperlink.ts
82552
- const OSC = "\x1B]";
82553
- const ST = "\x1B\\";
82554
- /**
82555
- * Wraps `text` in an OSC 8 hyperlink pointing at `uri`. The visible characters
82556
- * are exactly `text`; the link is carried in escape sequences a capable
82557
- * terminal turns into a click target.
82558
- */
82559
- const formatHyperlink = (text, uri) => `${OSC}8;;${uri}${ST}${text}${OSC}8;;${ST}`;
82560
- //#endregion
82561
82563
  //#region src/cli/utils/indent-multiline-text.ts
82562
82564
  const indentMultilineText = (text, linePrefix) => text.split("\n").map((lineText) => `${linePrefix}${lineText}`).join("\n");
82563
82565
  //#endregion
@@ -82597,793 +82599,6 @@ const wrapTextToWidth = (text, width, options = {}) => {
82597
82599
  return text.split("\n").flatMap((lineText) => wrapLine(lineText, width, breakLongWords));
82598
82600
  };
82599
82601
  //#endregion
82600
- //#region src/cli/utils/write-stdout.ts
82601
- const writeStdout = (text) => sync(() => {
82602
- process.stdout.write(text);
82603
- });
82604
- //#endregion
82605
- //#region src/cli/utils/render-diagnostics.ts
82606
- const POINTER = isUnicodeSupported() ? "›" : ">";
82607
- const colorizeBySeverity = (text, severity) => severity === "error" ? highlighter.error(text) : highlighter.warn(text);
82608
- const collectAffectedFiles = (diagnostics) => new Set(diagnostics.map((diagnostic) => diagnostic.filePath));
82609
- const formatFileContextTag = (diagnostic) => diagnostic.fileContext ? ` (${diagnostic.fileContext} file)` : "";
82610
- const buildVerboseFileEntries = (diagnostics) => {
82611
- const fileEntries = /* @__PURE__ */ new Map();
82612
- for (const diagnostic of diagnostics) {
82613
- let entry = fileEntries.get(diagnostic.filePath);
82614
- if (entry === void 0) {
82615
- entry = {
82616
- contextTag: formatFileContextTag(diagnostic),
82617
- sites: []
82618
- };
82619
- fileEntries.set(diagnostic.filePath, entry);
82620
- }
82621
- if (diagnostic.line > 0) entry.sites.push({
82622
- line: diagnostic.line,
82623
- suppressionHint: diagnostic.suppressionHint
82624
- });
82625
- }
82626
- return fileEntries;
82627
- };
82628
- const formatSiteCountBadge = (count) => count > 1 ? `×${count}` : "";
82629
- const formatTrailingSiteBadge = (count) => {
82630
- const badge = formatSiteCountBadge(count);
82631
- return badge.length > 0 ? ` ${highlighter.gray(badge)}` : "";
82632
- };
82633
- const CATEGORY_DISPLAY_RANK = new Map(DIAGNOSTIC_CATEGORY_BUCKETS.map((category, index) => [category, index]));
82634
- const compareCategoriesByDisplayRank = (categoryA, categoryB) => {
82635
- const rankA = CATEGORY_DISPLAY_RANK.get(categoryA);
82636
- const rankB = CATEGORY_DISPLAY_RANK.get(categoryB);
82637
- if (rankA !== void 0 && rankB !== void 0) return rankA - rankB;
82638
- if (rankA !== void 0) return -1;
82639
- if (rankB !== void 0) return 1;
82640
- return categoryA.localeCompare(categoryB);
82641
- };
82642
- const buildCategoryDiagnosticGroups = (diagnostics, rulePriority) => {
82643
- return [...groupBy(diagnostics, (diagnostic) => diagnostic.category).entries()].map(([category, categoryDiagnostics]) => ({
82644
- category,
82645
- diagnostics: categoryDiagnostics,
82646
- ruleGroups: buildSortedRuleGroups(categoryDiagnostics, rulePriority)
82647
- })).toSorted((categoryGroupA, categoryGroupB) => compareCategoriesByDisplayRank(categoryGroupA.category, categoryGroupB.category));
82648
- };
82649
- const formatCategoryTallyLine = (tally, errorShown, warningShown) => {
82650
- const parts = [];
82651
- if (tally.errorCount > 0) parts.push(highlighter.error(`${errorShown} ${errorShown === 1 ? "error" : "errors"}`));
82652
- if (tally.warningCount > 0) parts.push(highlighter.warn(highlighter.dim(`${warningShown} ${warningShown === 1 ? "warning" : "warnings"}`)));
82653
- return ` ${highlighter.bold(tally.category)} ${highlighter.dim(POINTER)} ${parts.join(highlighter.dim(", "))}`;
82654
- };
82655
- const buildCategoryTally = (categoryGroup) => ({
82656
- category: categoryGroup.category,
82657
- errorCount: categoryGroup.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length,
82658
- warningCount: categoryGroup.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length
82659
- });
82660
- const buildCategoryTallyLines = (tallies) => tallies.map((tally) => formatCategoryTallyLine(tally, tally.errorCount, tally.warningCount));
82661
- const buildPartiallyRevealedTallyLines = (tallies, revealedUnitCount) => {
82662
- let remainingToReveal = revealedUnitCount;
82663
- return tallies.map((tally) => {
82664
- const errorShown = Math.min(tally.errorCount, remainingToReveal);
82665
- remainingToReveal -= errorShown;
82666
- const warningShown = Math.min(tally.warningCount, remainingToReveal);
82667
- remainingToReveal -= warningShown;
82668
- return formatCategoryTallyLine(tally, errorShown, warningShown);
82669
- });
82670
- };
82671
- const printCategoryCountUp = (tallies) => gen(function* () {
82672
- const totalUnitCount = tallies.reduce((sum, tally) => sum + tally.errorCount + tally.warningCount, 0);
82673
- const unitsPerStep = Math.max(1, Math.ceil(totalUnitCount / 24));
82674
- for (let revealedUnitCount = 0; revealedUnitCount < totalUnitCount; revealedUnitCount += unitsPerStep) {
82675
- const lines = buildPartiallyRevealedTallyLines(tallies, revealedUnitCount);
82676
- yield* writeStdout(`${revealedUnitCount === 0 ? "" : `\x1b[${tallies.length}A`}\r${lines.join("\n\r")}\n`);
82677
- yield* sleep(70);
82678
- }
82679
- yield* writeStdout(`${totalUnitCount === 0 ? "" : `\x1b[${tallies.length}A`}\r${buildCategoryTallyLines(tallies).join("\n\r")}\n`);
82680
- yield* sleep(CATEGORY_COUNTUP_SETTLE_HOLD_MS);
82681
- });
82682
- const TOP_ERROR_DETAIL_INDENT = " ";
82683
- const pickRepresentativeDiagnostic = (ruleDiagnostics) => ruleDiagnostics.find((diagnostic) => diagnostic.line > 0) ?? ruleDiagnostics[0];
82684
- const hasIndistinctSiteLocations = (ruleDiagnostics) => {
82685
- const firstDiagnostic = ruleDiagnostics[0];
82686
- if (firstDiagnostic === void 0) return false;
82687
- return ruleDiagnostics.every((diagnostic) => diagnostic.line <= 0 && diagnostic.filePath === firstDiagnostic.filePath);
82688
- };
82689
- const FRAME_CONTEXT_REACH_LINES = 3;
82690
- const clusterNearbyDiagnostics = (diagnostics) => {
82691
- const byFile = groupBy(diagnostics, (diagnostic) => diagnostic.filePath);
82692
- const clusters = [];
82693
- for (const fileDiagnostics of byFile.values()) {
82694
- const sorted = [...fileDiagnostics].sort((left, right) => left.line - right.line);
82695
- let current = [];
82696
- const flush = () => {
82697
- if (current.length === 0) return;
82698
- clusters.push({
82699
- diagnostics: current,
82700
- startLine: current[0].line,
82701
- endLine: current[current.length - 1].line
82702
- });
82703
- current = [];
82704
- };
82705
- for (const diagnostic of sorted) {
82706
- const previous = current[current.length - 1];
82707
- if (previous != null && (diagnostic.line - previous.line > FRAME_CONTEXT_REACH_LINES || diagnostic.line - current[0].line > 20)) flush();
82708
- current.push(diagnostic);
82709
- }
82710
- flush();
82711
- }
82712
- return clusters;
82713
- };
82714
- const formatClusterLocationText = (cluster) => {
82715
- const { filePath } = cluster.diagnostics[0];
82716
- if (cluster.startLine <= 0) return filePath;
82717
- if (cluster.endLine > cluster.startLine) return `${filePath}:${cluster.startLine}-${cluster.endLine}`;
82718
- return `${filePath}:${cluster.startLine}`;
82719
- };
82720
- const formatClusterLocation = (cluster, resolveSourceRoot, hyperlinks) => {
82721
- const lead = cluster.diagnostics[0];
82722
- const contextTag = formatFileContextTag(lead);
82723
- const location = formatClusterLocationText(cluster);
82724
- if (!hyperlinks) return `${location}${contextTag}`;
82725
- return `${formatHyperlink(location, pathToFileURL(resolveAbsolutePath(lead.filePath, resolveSourceRoot(lead))).href)}${contextTag}`;
82726
- };
82727
- const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, resolveFrameSourceRoot, renderCodeFrame, hyperlinks) => {
82728
- const lead = cluster.diagnostics[0];
82729
- const isMultiSite = cluster.diagnostics.length > 1;
82730
- const sourceRoot = resolveSourceRoot(lead);
82731
- const frameSourceRoot = resolveFrameSourceRoot(lead);
82732
- const frameFilePath = Path.isAbsolute(lead.filePath) && sourceRoot !== frameSourceRoot ? Path.relative(sourceRoot, lead.filePath) : lead.filePath;
82733
- const lines = ["", highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${formatClusterLocation(cluster, resolveSourceRoot, hyperlinks)}`)];
82734
- const codeFrame = renderCodeFrame ? buildCodeFrame({
82735
- filePath: frameFilePath,
82736
- line: cluster.startLine,
82737
- column: isMultiSite ? 0 : lead.column,
82738
- endLine: isMultiSite ? cluster.endLine : void 0,
82739
- rootDirectory: frameSourceRoot
82740
- }) : null;
82741
- if (codeFrame) {
82742
- const boxInnerWidth = resolveMeasureWidth(8);
82743
- lines.push(indentMultilineText(boxText(codeFrame, boxInnerWidth), TOP_ERROR_DETAIL_INDENT));
82744
- }
82745
- const seenHints = /* @__PURE__ */ new Set();
82746
- for (const diagnostic of cluster.diagnostics) if (diagnostic.suppressionHint && !seenHints.has(diagnostic.suppressionHint)) {
82747
- seenHints.add(diagnostic.suppressionHint);
82748
- lines.push(highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}↳ ${diagnostic.suppressionHint}`));
82749
- }
82750
- return lines;
82751
- };
82752
- const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, resolveFrameSourceRoot, renderEverySite, isAgentEnvironment, hyperlinks) => {
82753
- const representative = pickRepresentativeDiagnostic(ruleDiagnostics);
82754
- const { severity } = representative;
82755
- const trailingBadge = formatTrailingSiteBadge(ruleDiagnostics.length);
82756
- const headline = colorizeBySeverity(`${representative.category}: ${representative.title ?? ruleKey}`, severity);
82757
- const lines = [` ${colorizeBySeverity(severity === "error" ? "✖" : "⚠", severity)} ${headline}${trailingBadge}`];
82758
- if (renderEverySite && !isAgentEnvironment) {
82759
- const learnMoreLine = formatLearnMoreLine(representative);
82760
- if (learnMoreLine) lines.push(`${TOP_ERROR_DETAIL_INDENT}${highlighter.info(learnMoreLine)}`);
82761
- }
82762
- const isCollapsedWarningGroup = severity === "warning" && hasIndistinctSiteLocations(ruleDiagnostics);
82763
- const impactMessages = isCollapsedWarningGroup ? [...new Set(ruleDiagnostics.map((diagnostic) => diagnostic.message))] : [representative.message];
82764
- for (const impactMessage of impactMessages) for (const explanationLine of wrapTextToWidth(impactMessage, resolveMeasureWidth(4), { breakLongWords: false })) lines.push(`${TOP_ERROR_DETAIL_INDENT}${explanationLine}`);
82765
- if (representative.help) for (const fixLine of wrapTextToWidth(`→ ${representative.help}`, resolveMeasureWidth(4), { breakLongWords: false })) lines.push(highlighter.dim(`${TOP_ERROR_DETAIL_INDENT}${fixLine}`));
82766
- const sharedFixSiteCount = getSharedFixSiteCount(ruleDiagnostics);
82767
- if (sharedFixSiteCount > 0) lines.push(highlighter.dim(`${TOP_ERROR_DETAIL_INDENT}↳ One fix clears all ${sharedFixSiteCount} findings.`));
82768
- if (renderEverySite && isAgentEnvironment) {
82769
- const fixRecipeLine = formatFixRecipeLine(representative);
82770
- if (fixRecipeLine) lines.push(highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${fixRecipeLine}`));
82771
- }
82772
- const renderCodeFrame = severity === "error";
82773
- const sites = renderEverySite ? ruleDiagnostics : [representative];
82774
- if (!(isCollapsedWarningGroup && representative.help.includes(representative.filePath))) for (const cluster of clusterNearbyDiagnostics(sites)) lines.push(...buildDiagnosticClusterLines(cluster, resolveSourceRoot, resolveFrameSourceRoot, renderCodeFrame, hyperlinks));
82775
- return lines;
82776
- };
82777
- const selectErrorRuleGroups = (diagnostics, rulePriority) => buildSortedRuleGroups(diagnostics.filter((diagnostic) => diagnostic.severity === "error"), rulePriority);
82778
- const selectTopErrorRuleGroups = (diagnostics, limit, rulePriority) => selectErrorRuleGroups(diagnostics, rulePriority).slice(0, limit);
82779
- const buildOverflowSummaryLine = (diagnostics, rulePriority) => {
82780
- const errorRuleGroups = selectErrorRuleGroups(diagnostics, rulePriority);
82781
- const shownErrorRuleCount = Math.min(3, errorRuleGroups.length);
82782
- if (diagnostics.length <= shownErrorRuleCount) return void 0;
82783
- const command = highlighter.bold(highlighter.info("npx react-doctor@latest --verbose"));
82784
- return ` ${highlighter.dim("Run")} ${command} ${highlighter.dim("to list every error and warning")}`;
82785
- };
82786
- const formatMigrationBucketLine = (bucket) => `${TOP_ERROR_DETAIL_INDENT}${bucket.title} ${highlighter.gray(`×${bucket.siteCount} across ${bucket.fileCount} files`)}`;
82787
- const buildMigrationScaleAdvisoryLines = (diagnostics) => {
82788
- const buckets = findMigrationScaleBuckets(diagnostics);
82789
- if (buckets.length === 0) return [];
82790
- const shownBuckets = buckets.slice(0, 3);
82791
- const lines = [` ${highlighter.warn("⚠")} ${highlighter.bold("Migration-scale change")}${highlighter.dim(": sample before you sweep")}`, ...shownBuckets.map(formatMigrationBucketLine)];
82792
- const remainingBuckets = buckets.length - shownBuckets.length;
82793
- if (remainingBuckets > 0) lines.push(highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}+${remainingBuckets} more ${remainingBuckets === 1 ? "rule" : "rules"} at this scale`));
82794
- for (const guidanceLine of wrapTextToWidth("Fixing all of them at once is hard to review and prone to subtle mistakes across the whole repo. Fix a representative few first and confirm the recipe holds. Then get the code owner's sign-off before changing the rest.", resolveMeasureWidth(4), { breakLongWords: false })) lines.push(highlighter.dim(`${TOP_ERROR_DETAIL_INDENT}${guidanceLine}`));
82795
- const command = highlighter.info("npx react-doctor@latest <path>");
82796
- lines.push(`${TOP_ERROR_DETAIL_INDENT}${highlighter.dim("Scope it down one area at a time:")} ${command}`);
82797
- return lines;
82798
- };
82799
- const getTopErrorRuleKeys = (diagnostics, limit, rulePriority) => new Set(selectTopErrorRuleGroups(diagnostics, limit, rulePriority).map(([ruleKey]) => ruleKey));
82800
- const buildTopErrorsSection = (diagnostics, resolveSourceRoot, resolveFrameSourceRoot, hyperlinks, rulePriority) => {
82801
- const topRuleGroups = selectErrorRuleGroups(diagnostics, rulePriority).slice(0, 3);
82802
- if (topRuleGroups.length === 0) return {
82803
- lines: [],
82804
- blockOffsets: []
82805
- };
82806
- const lines = [` ${highlighter.bold(`Top ${topRuleGroups.length} ${topRuleGroups.length === 1 ? "error" : "errors"} you should fix`)}`, ""];
82807
- const blockOffsets = [];
82808
- for (const [ruleKey, ruleDiagnostics] of topRuleGroups) {
82809
- blockOffsets.push(lines.length);
82810
- lines.push(...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, resolveFrameSourceRoot, false, false, hyperlinks));
82811
- lines.push("");
82812
- }
82813
- return {
82814
- lines,
82815
- blockOffsets
82816
- };
82817
- };
82818
- const joinSections = (...sections) => {
82819
- const lines = [];
82820
- const sectionStarts = [];
82821
- for (const section of sections) {
82822
- if (section.length === 0) {
82823
- sectionStarts.push(null);
82824
- continue;
82825
- }
82826
- if (lines.length > 0) lines.push("");
82827
- sectionStarts.push(lines.length);
82828
- lines.push(...section);
82829
- }
82830
- if (lines.length > 0 && lines[lines.length - 1] !== "") lines.push("");
82831
- return {
82832
- lines,
82833
- sectionStarts
82834
- };
82835
- };
82836
- const buildOverviewHeaderLines = (diagnostics) => {
82837
- const totalIssueCount = diagnostics.length;
82838
- if (totalIssueCount === 0) return [];
82839
- const issueNoun = totalIssueCount === 1 ? "issue" : "issues";
82840
- return [` ${highlighter.bold(`All ${totalIssueCount} ${issueNoun}`)}`];
82841
- };
82842
- /**
82843
- * Effect-typed diagnostics renderer. Internal helpers build the
82844
- * line array purely; the IO happens once at the boundary with a
82845
- * single Effect.forEach over Console.log so failures or fiber
82846
- * interruption produce predictable partial output.
82847
- */
82848
- const printDiagnostics = (diagnostics, isVerbose, sourceRoot, rulePriority, isAgentEnvironment = false, onboarding = {}, hyperlinks = false, frameSourceRoot = sourceRoot) => gen(function* () {
82849
- const sectionPause = onboarding.sectionPause ?? void_;
82850
- const animateCountUp = onboarding.animateCountUp ?? false;
82851
- const resolveSourceRoot = typeof sourceRoot === "function" ? sourceRoot : () => sourceRoot;
82852
- const resolveFrameSourceRoot = typeof frameSourceRoot === "function" ? frameSourceRoot : () => frameSourceRoot;
82853
- let detailLines;
82854
- let topErrorBlockOffsets = [];
82855
- if (!isVerbose) {
82856
- const topErrors = buildTopErrorsSection(diagnostics, resolveSourceRoot, resolveFrameSourceRoot, hyperlinks, rulePriority);
82857
- detailLines = topErrors.lines;
82858
- topErrorBlockOffsets = topErrors.blockOffsets;
82859
- } else detailLines = buildSortedRuleGroups(diagnostics, rulePriority).flatMap(([ruleKey, ruleDiagnostics]) => {
82860
- return [...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, resolveFrameSourceRoot, true, isAgentEnvironment, hyperlinks), ""];
82861
- });
82862
- const overflowLine = isVerbose ? void 0 : buildOverflowSummaryLine(diagnostics, rulePriority);
82863
- const categoryTallies = buildCategoryDiagnosticGroups(diagnostics, rulePriority).map(buildCategoryTally);
82864
- const categoryLines = buildCategoryTallyLines(categoryTallies);
82865
- const overviewDividerLines = detailLines.length > 0 && categoryLines.length > 0 ? [buildSectionDivider()] : [];
82866
- const { lines, sectionStarts } = joinSections(detailLines, overviewDividerLines, buildOverviewHeaderLines(diagnostics), categoryLines, overflowLine ? [overflowLine] : [], buildMigrationScaleAdvisoryLines(diagnostics));
82867
- const [detailStart, , , categoryStart] = sectionStarts;
82868
- const pauseBeforeLineIndices = detailStart == null ? /* @__PURE__ */ new Set() : new Set(topErrorBlockOffsets.map((offset) => detailStart + offset));
82869
- let lineIndex = 0;
82870
- while (lineIndex < lines.length) {
82871
- if (animateCountUp && lineIndex === categoryStart && categoryLines.length > 0) {
82872
- yield* printCategoryCountUp(categoryTallies);
82873
- lineIndex += categoryLines.length;
82874
- continue;
82875
- }
82876
- if (pauseBeforeLineIndices.has(lineIndex)) yield* sectionPause;
82877
- yield* log(lines[lineIndex]);
82878
- lineIndex += 1;
82879
- }
82880
- });
82881
- const formatElapsedTime = (elapsedMilliseconds) => {
82882
- if (elapsedMilliseconds < 1e3) return `${Math.round(elapsedMilliseconds)}ms`;
82883
- return `${(elapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1)}s`;
82884
- };
82885
- const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
82886
- const firstDiagnostic = ruleDiagnostics[0];
82887
- const distinctMessages = [...new Set(ruleDiagnostics.map((diagnostic) => diagnostic.message))];
82888
- const sections = [
82889
- `Rule: ${ruleKey}`,
82890
- `Severity: ${firstDiagnostic.severity}`,
82891
- `Category: ${firstDiagnostic.category}`,
82892
- `Count: ${ruleDiagnostics.length}`,
82893
- "",
82894
- distinctMessages.join("\n\n")
82895
- ];
82896
- if (firstDiagnostic.help) sections.push("", `Suggestion: ${firstDiagnostic.help}`);
82897
- if (firstDiagnostic.url) sections.push("", `Docs: ${firstDiagnostic.url}`);
82898
- const fixRecipeLine = formatFixRecipeLine(firstDiagnostic);
82899
- if (fixRecipeLine) sections.push("", fixRecipeLine);
82900
- sections.push("", "Files:");
82901
- const fileEntries = buildVerboseFileEntries(ruleDiagnostics);
82902
- for (const [filePath, { contextTag, sites }] of fileEntries) if (sites.length > 0) for (const site of sites) {
82903
- sections.push(` ${filePath}:${site.line}${contextTag}`);
82904
- if (site.suppressionHint) sections.push(` ${site.suppressionHint}`);
82905
- }
82906
- else sections.push(` ${filePath}${contextTag}`);
82907
- return sections.join("\n") + "\n";
82908
- };
82909
- //#endregion
82910
- //#region src/cli/utils/compute-score-projection.ts
82911
- const computeProjectedScore = async (topErrorSource, rescoreSource, currentScore) => {
82912
- const topErrorRuleKeys = getTopErrorRuleKeys(topErrorSource, 3, buildRulePriorityMap([currentScore]));
82913
- if (topErrorRuleKeys.size === 0) return null;
82914
- const potentialScore = await calculateScore(rescoreSource.filter((diagnostic) => !topErrorRuleKeys.has(`${diagnostic.plugin}/${diagnostic.rule}`)));
82915
- if (!potentialScore || potentialScore.score <= currentScore.score) return null;
82916
- return potentialScore.score;
82917
- };
82918
- //#endregion
82919
- //#region src/cli/utils/filter-diagnostics-by-categories.ts
82920
- const filterDiagnosticsByCategories = (diagnostics, categories) => categories.size === 0 ? [...diagnostics] : diagnostics.filter((diagnostic) => categories.has(diagnostic.category));
82921
- //#endregion
82922
- //#region src/cli/utils/supports-hyperlinks.ts
82923
- const HYPERLINK_CAPABLE_TERM_PROGRAMS = new Set([
82924
- "iTerm.app",
82925
- "WezTerm",
82926
- "vscode",
82927
- "Hyper",
82928
- "ghostty",
82929
- "Tabby",
82930
- "rio"
82931
- ]);
82932
- const parseVteVersion = (raw) => {
82933
- const parsed = Number.parseInt(raw ?? "", 10);
82934
- return Number.isNaN(parsed) ? 0 : parsed;
82935
- };
82936
- /**
82937
- * Whether `stream` is a terminal that renders OSC 8 hyperlinks. Auto-detected
82938
- * from terminal-identity env vars; the de-facto `FORCE_HYPERLINK` env var
82939
- * overrides detection (`FORCE_HYPERLINK=0`/`false` forces off, any other value
82940
- * forces on), mirroring how the ecosystem's terminal libraries gate the same
82941
- * feature. Off for non-TTYs, `TERM=dumb`, and CI (whose log viewers render the
82942
- * raw escape rather than a link). Unknown terminals default to off.
82943
- */
82944
- const supportsHyperlinks = (stream = process.stdout, env = process.env) => {
82945
- const forced = env.FORCE_HYPERLINK;
82946
- if (forced !== void 0 && forced !== "") return forced !== "0" && forced.toLowerCase() !== "false";
82947
- if (stream.isTTY !== true) return false;
82948
- if (env.TERM === "dumb") return false;
82949
- if (isCiEnvironment(env)) return false;
82950
- if (env.WT_SESSION) return true;
82951
- if (env.KITTY_WINDOW_ID || env.TERM === "xterm-kitty") return true;
82952
- if (parseVteVersion(env.VTE_VERSION) >= 5e3) return true;
82953
- return Boolean(env.TERM_PROGRAM && HYPERLINK_CAPABLE_TERM_PROGRAMS.has(env.TERM_PROGRAM));
82954
- };
82955
- //#endregion
82956
- //#region src/cli/utils/should-render-hyperlinks.ts
82957
- /**
82958
- * Whether to emit OSC 8 clickable `file:line` locations for this run: a
82959
- * hyperlink-capable terminal AND not a coding agent (whose output parsers
82960
- * would choke on the escape sequences).
82961
- */
82962
- const shouldRenderHyperlinks = (stream = process.stdout) => supportsHyperlinks(stream) && !isCodingAgentEnvironment();
82963
- //#endregion
82964
- //#region src/cli/utils/should-show-share-link.ts
82965
- const shouldShowShareLink = ({ noScore, share, isCi }) => !noScore && share && !isCi;
82966
- const FORCE_ONBOARDING_ENV_VAR = "REACT_DOCTOR_FORCE_ONBOARDING";
82967
- const FALSY_FLAG_VALUES = new Set([
82968
- "",
82969
- "0",
82970
- "false"
82971
- ]);
82972
- const isOnboardingForced = (environment = process.env) => {
82973
- const value = environment[FORCE_ONBOARDING_ENV_VAR];
82974
- return value !== void 0 && !FALSY_FLAG_VALUES.has(value.toLowerCase());
82975
- };
82976
- const onboardingSectionPause = (shouldPace) => shouldPace ? sleep(850) : void_;
82977
- const shouldRecordOnboarding = (input) => input.paceOnboardingSections && !input.forceOnboarding && !input.verbose && !input.isNonInteractiveEnvironment;
82978
- const canAnimateOnboarding = (stream = process.stdout) => {
82979
- if (!(stream.isTTY === true && (stream.columns ?? 0) > 0 && process.env.TERM !== "dumb")) return false;
82980
- if (isOnboardingForced()) return true;
82981
- return !isGitHookEnvironment() && !isCiEnvironment();
82982
- };
82983
- //#endregion
82984
- //#region src/cli/utils/now-iso.ts
82985
- const nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
82986
- const ONBOARDING_EVENT = "onboarding";
82987
- const CI_PITCH_EVENT = "ci-pitch";
82988
- const ACTION_UPGRADE_EVENT = "action-upgrade-v2";
82989
- const SETUP_HINT_EVENT = "setup-hint";
82990
- const HANDOFF_TARGET_PREFERENCE_ID = "handoff-target";
82991
- const INSTALL_AGENTS_PREFERENCE_ID = "install-agents";
82992
- const foldLegacyDecisions = (projects, legacy, eventId) => {
82993
- for (const [hash, record] of Object.entries(legacy ?? {})) {
82994
- const existing = projects[hash] ?? { rootDirectory: record.rootDirectory ?? "" };
82995
- projects[hash] = {
82996
- ...existing,
82997
- events: {
82998
- ...existing.events,
82999
- [eventId]: {
83000
- firedAt: record.at ?? nowIso(),
83001
- version: 1,
83002
- ...record.outcome ? { outcome: record.outcome } : {}
83003
- }
83004
- }
83005
- };
83006
- }
83007
- };
83008
- const migrateCliState = (state) => {
83009
- if (typeof state.schemaVersion === "number" && state.schemaVersion >= 2) return state;
83010
- const projects = {};
83011
- for (const [hash, record] of Object.entries(state.projects ?? {})) {
83012
- const carried = {
83013
- rootDirectory: record.rootDirectory,
83014
- ...record.events ? { events: record.events } : {},
83015
- ...record.migrations ? { migrations: record.migrations } : {}
83016
- };
83017
- projects[hash] = record.setupPrompt === false ? {
83018
- ...carried,
83019
- events: {
83020
- ...carried.events,
83021
- [SETUP_HINT_EVENT]: {
83022
- firedAt: nowIso(),
83023
- version: 1,
83024
- outcome: "declined"
83025
- }
83026
- }
83027
- } : carried;
83028
- }
83029
- foldLegacyDecisions(projects, state.ciPrompts, CI_PITCH_EVENT);
83030
- foldLegacyDecisions(projects, state.actionUpgrades, ACTION_UPGRADE_EVENT);
83031
- return {
83032
- schemaVersion: 2,
83033
- global: typeof state.onboardedAt === "string" ? { events: { [ONBOARDING_EVENT]: {
83034
- firedAt: state.onboardedAt,
83035
- version: 1
83036
- } } } : {},
83037
- projects
83038
- };
83039
- };
83040
- const resolveConfigDir = (options) => options.cwd ?? (process.env["REACT_DOCTOR_CONFIG_DIR"] || void 0);
83041
- const openStore = (options = {}) => new Conf({
83042
- projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
83043
- cwd: resolveConfigDir(options)
83044
- });
83045
- const openMigratedStore = (options) => {
83046
- const store = openStore(options);
83047
- const state = store.store;
83048
- const migrated = migrateCliState(state);
83049
- if (migrated !== state) store.store = migrated;
83050
- return store;
83051
- };
83052
- const readCliState = (select, fallback, options = {}) => {
83053
- try {
83054
- return select(openMigratedStore(options).store);
83055
- } catch {
83056
- return fallback;
83057
- }
83058
- };
83059
- const updateCliState = (update, options = {}) => {
83060
- try {
83061
- const store = openMigratedStore(options);
83062
- store.store = update(store.store);
83063
- return true;
83064
- } catch {
83065
- return false;
83066
- }
83067
- };
83068
- //#endregion
83069
- //#region src/cli/utils/hash-project-root.ts
83070
- const hashProjectRoot = (projectRoot) => createHash("sha256").update(Path.resolve(projectRoot)).digest("hex");
83071
- //#endregion
83072
- //#region src/cli/utils/cli-lifecycle.ts
83073
- const versionOf = (item) => item.version ?? 1;
83074
- const selectScope = (state, scoped, projectRoot) => scoped.scope === "global" ? state.global : projectRoot === void 0 ? void 0 : state.projects?.[hashProjectRoot(projectRoot)];
83075
- const updateScope = (state, scoped, projectRoot, updateScopeState) => {
83076
- if (scoped.scope === "global") return {
83077
- ...state,
83078
- global: updateScopeState(state.global ?? {})
83079
- };
83080
- if (projectRoot === void 0) return state;
83081
- const projectKey = hashProjectRoot(projectRoot);
83082
- const base = state.projects?.[projectKey] ?? { rootDirectory: Path.resolve(projectRoot) };
83083
- return {
83084
- ...state,
83085
- projects: {
83086
- ...state.projects,
83087
- [projectKey]: {
83088
- ...base,
83089
- ...updateScopeState(base)
83090
- }
83091
- }
83092
- };
83093
- };
83094
- const isGatePending = (gate, target = {}, options = {}) => {
83095
- if (gate.scope === "project" && target.projectRoot === void 0) return false;
83096
- return readCliState((state) => {
83097
- const record = selectScope(state, gate, target.projectRoot)?.events?.[gate.id];
83098
- return !record || record.version < versionOf(gate);
83099
- }, gate.fireWhenUnknown ?? false, options);
83100
- };
83101
- const recordGate = (gate, target = {}, options = {}) => updateCliState((state) => updateScope(state, gate, target.projectRoot, (scope) => ({
83102
- ...scope,
83103
- events: {
83104
- ...scope.events,
83105
- [gate.id]: {
83106
- firedAt: nowIso(),
83107
- version: versionOf(gate),
83108
- ...target.outcome ? { outcome: target.outcome } : {}
83109
- }
83110
- }
83111
- })), options);
83112
- const readPreference = (preference, target = {}, options = {}) => readCliState((state) => selectScope(state, preference, target.projectRoot)?.preferences?.[preference.id] ?? null, null, options);
83113
- const writePreference = (preference, value, target = {}, options = {}) => updateCliState((state) => updateScope(state, preference, target.projectRoot, (scope) => ({
83114
- ...scope,
83115
- preferences: {
83116
- ...scope.preferences,
83117
- [preference.id]: value
83118
- }
83119
- })), options);
83120
- const isMigrationPending = (migration, target = {}, options = {}) => {
83121
- if (migration.scope === "project" && target.projectRoot === void 0) return false;
83122
- return readCliState((state) => {
83123
- const record = selectScope(state, migration, target.projectRoot)?.migrations?.[migration.id];
83124
- return !record || record.version < versionOf(migration);
83125
- }, false, options);
83126
- };
83127
- const recordMigration = (migration, projectRoot, record, options) => updateCliState((state) => updateScope(state, migration, projectRoot, (scope) => ({
83128
- ...scope,
83129
- migrations: {
83130
- ...scope.migrations,
83131
- [migration.id]: record
83132
- }
83133
- })), options);
83134
- const runMigrations = async (migrations, target = {}, options = {}) => {
83135
- const results = [];
83136
- for (const migration of migrations) {
83137
- if (!isMigrationPending(migration, target, options)) {
83138
- results.push({
83139
- id: migration.id,
83140
- ran: false,
83141
- applied: true
83142
- });
83143
- continue;
83144
- }
83145
- let applied = false;
83146
- try {
83147
- applied = await migration.run({ projectRoot: target.projectRoot });
83148
- } catch {
83149
- applied = false;
83150
- }
83151
- if (applied) recordMigration(migration, target.projectRoot, {
83152
- ranAt: nowIso(),
83153
- version: versionOf(migration)
83154
- }, options);
83155
- results.push({
83156
- id: migration.id,
83157
- ran: true,
83158
- applied
83159
- });
83160
- }
83161
- return results;
83162
- };
83163
- //#endregion
83164
- //#region src/cli/utils/onboarding-state.ts
83165
- const ONBOARDING_GATE = {
83166
- id: ONBOARDING_EVENT,
83167
- scope: "global"
83168
- };
83169
- const hasCompletedOnboarding = (options = {}) => !isGatePending(ONBOARDING_GATE, {}, options);
83170
- const markOnboardingComplete = (options = {}) => {
83171
- if (isGatePending(ONBOARDING_GATE, {}, options)) recordGate(ONBOARDING_GATE, {}, options);
83172
- };
83173
- //#endregion
83174
- //#region src/cli/utils/colorize-by-score.ts
83175
- const colorizeByScore = (text, score) => {
83176
- if (score >= 75) return highlighter.success(text);
83177
- if (score >= 50) return highlighter.warn(text);
83178
- return highlighter.error(text);
83179
- };
83180
- //#endregion
83181
- //#region src/cli/utils/doctor-face.ts
83182
- const doctorFace = (score) => {
83183
- if (score >= 75) return ["◠ ◠", " ▽ "];
83184
- if (score >= 50) return ["• •", " ─ "];
83185
- return ["x x", " ▽ "];
83186
- };
83187
- //#endregion
83188
- //#region src/cli/utils/ease-out-cubic.ts
83189
- const easeOutCubic = (progress) => 1 - (1 - progress) ** 3;
83190
- //#endregion
83191
- //#region src/cli/utils/render-score-header.ts
83192
- const RAINBOW_HUE_SHIFT_PER_FRAME = 9;
83193
- const RAINBOW_GRADIENT_WIDTH = 80;
83194
- const RAINBOW_OKLCH_LIGHTNESS = .638;
83195
- const RAINBOW_OKLCH_CHROMA = .129;
83196
- const FACE_BOX_TOP_BORDER = "┌─────┐";
83197
- const SCORE_RIGHT_COLUMN_OFFSET = 11;
83198
- const getScoreBarWidth = () => resolveClampedWidth({
83199
- reservedColumns: SCORE_RIGHT_COLUMN_OFFSET + 1,
83200
- fullWidth: 50,
83201
- minWidth: 10
83202
- });
83203
- const buildScoreBarSegments = (filledCount) => {
83204
- const emptyCount = Math.max(0, getScoreBarWidth() - filledCount);
83205
- return {
83206
- filledSegment: "█".repeat(filledCount),
83207
- emptySegment: "░".repeat(emptyCount)
83208
- };
83209
- };
83210
- const getFilledCount = (score) => Math.round(score / 100 * getScoreBarWidth());
83211
- const joinScoreHeaderFrame = (lines) => `${lines[0]}\n\r${lines[1]}\n\r${lines[2]}\n\r${lines[3]}\n`;
83212
- const buildRawScoreBar = (displayScore) => {
83213
- const { filledSegment, emptySegment } = buildScoreBarSegments(getFilledCount(displayScore));
83214
- return filledSegment + emptySegment;
83215
- };
83216
- const buildScoreHeaderLine = (faceLine, rightColumnContent) => {
83217
- return ` ${faceLine}${rightColumnContent.length > 0 ? " " : ""}${rightColumnContent}`;
83218
- };
83219
- const getRightColumnOffset = (faceLine) => ` ${faceLine} `.length;
83220
- const clampColorChannel = (value) => Math.max(0, Math.min(255, Math.round(value)));
83221
- const encodeSrgb = (value) => value <= .0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - .055;
83222
- const oklchToRgb = (lightness, chroma, hue) => {
83223
- const hueRadians = hue * Math.PI / 180;
83224
- const labA = chroma * Math.cos(hueRadians);
83225
- const labB = chroma * Math.sin(hueRadians);
83226
- const longCone = (lightness + .3963377774 * labA + .2158037573 * labB) ** 3;
83227
- const mediumCone = (lightness - .1055613458 * labA - .0638541728 * labB) ** 3;
83228
- const shortCone = (lightness - .0894841775 * labA - 1.291485548 * labB) ** 3;
83229
- return {
83230
- red: clampColorChannel(encodeSrgb(4.0767416621 * longCone - 3.3077115913 * mediumCone + .2309699292 * shortCone) * 255),
83231
- green: clampColorChannel(encodeSrgb(-1.2684380046 * longCone + 2.6097574011 * mediumCone - .3413193965 * shortCone) * 255),
83232
- blue: clampColorChannel(encodeSrgb(-.0041960863 * longCone - .7034186147 * mediumCone + 1.707614701 * shortCone) * 255)
83233
- };
83234
- };
83235
- const colorizeTrueColor = (text, { red, green, blue }) => `\x1b[38;2;${red};${green};${blue}m${text}\x1b[39m`;
83236
- const colorizeRainbowText = (text, frame, offset = 0) => [...text].map((character, index) => {
83237
- if (character === " ") return character;
83238
- return colorizeTrueColor(character, oklchToRgb(RAINBOW_OKLCH_LIGHTNESS, RAINBOW_OKLCH_CHROMA, ((index + offset) / RAINBOW_GRADIENT_WIDTH * 360 + frame * RAINBOW_HUE_SHIFT_PER_FRAME) % 360));
83239
- }).join("");
83240
- const buildRainbowHeaderLine = (faceLine, rightColumnContent, frame) => colorizeRainbowText(buildScoreHeaderLine(faceLine, rightColumnContent), frame);
83241
- const buildScoreBar = (displayScore, colorScore = displayScore) => {
83242
- const { filledSegment, emptySegment } = buildScoreBarSegments(getFilledCount(displayScore));
83243
- return colorizeByScore(filledSegment, colorScore) + highlighter.dim(emptySegment);
83244
- };
83245
- const buildProjectedScoreBar = (currentScore, potentialScore) => {
83246
- const barWidth = getScoreBarWidth();
83247
- const currentFill = getFilledCount(currentScore);
83248
- const potentialFill = Math.min(getFilledCount(potentialScore), barWidth);
83249
- const gainCount = Math.max(0, potentialFill - currentFill);
83250
- const emptyCount = Math.max(0, barWidth - currentFill - gainCount);
83251
- return colorizeByScore("█".repeat(currentFill), currentScore) + highlighter.dim(colorizeByScore("▓".repeat(gainCount), currentScore)) + highlighter.dim("░".repeat(emptyCount));
83252
- };
83253
- const BRANDING_LINE = `React Doctor ${highlighter.dim("(https://react.doctor)")}`;
83254
- const RAW_BRANDING_LINE = "React Doctor (https://react.doctor)";
83255
- const buildRawFaceLines = (score) => {
83256
- const [eyes, mouth] = doctorFace(score);
83257
- return [
83258
- FACE_BOX_TOP_BORDER,
83259
- `│ ${eyes} │`,
83260
- `│ ${mouth} │`,
83261
- "└─────┘"
83262
- ];
83263
- };
83264
- const buildFaceRenderedLines = (score) => {
83265
- const colorize = (text) => colorizeByScore(text, score);
83266
- return buildRawFaceLines(score).map(colorize);
83267
- };
83268
- const buildScoreLine = (displayScore, finalScore, label) => {
83269
- const scoreNumber = colorizeByScore(`${displayScore}`, finalScore);
83270
- const scoreLabel = colorizeByScore(label, finalScore);
83271
- return `${scoreNumber} ${highlighter.dim(`/ 100`)} ${scoreLabel}`;
83272
- };
83273
- const buildRawScoreLine = (displayScore, label) => `${displayScore} / 100 ${label}`;
83274
- const buildRainbowScoreHeaderFrame = ({ score, displayScore, label, frame }) => {
83275
- const rawFaceLines = buildRawFaceLines(score);
83276
- return joinScoreHeaderFrame([
83277
- buildRainbowHeaderLine(rawFaceLines[0] ?? "", buildRawScoreLine(displayScore, label), frame),
83278
- buildRainbowHeaderLine(rawFaceLines[1] ?? "", buildRawScoreBar(displayScore), frame),
83279
- buildRainbowHeaderLine(rawFaceLines[2] ?? "", RAW_BRANDING_LINE, frame),
83280
- buildRainbowHeaderLine(rawFaceLines[3] ?? "", "", frame)
83281
- ]);
83282
- };
83283
- const buildFinalPerfectScoreHeaderFrame = (score, label, frame) => {
83284
- const rawFaceLines = buildRawFaceLines(score);
83285
- const renderedFaceLines = buildFaceRenderedLines(score);
83286
- const rainbowBarLine = colorizeRainbowText(buildRawScoreBar(score), frame, getRightColumnOffset(rawFaceLines[1] ?? ""));
83287
- return joinScoreHeaderFrame([
83288
- buildScoreHeaderLine(renderedFaceLines[0] ?? "", buildScoreLine(score, score, label)),
83289
- buildScoreHeaderLine(renderedFaceLines[1] ?? "", rainbowBarLine),
83290
- buildScoreHeaderLine(renderedFaceLines[2] ?? "", BRANDING_LINE),
83291
- buildScoreHeaderLine(renderedFaceLines[3] ?? "", "")
83292
- ]);
83293
- };
83294
- const buildInitialScoreHeaderLine = ({ isPerfectScore, shouldAnimate, lineIndex, renderedFaceLine, rawFaceLine, rightColumnContent, rawRightColumnContent, score }) => {
83295
- if (!isPerfectScore) return buildScoreHeaderLine(renderedFaceLine, rightColumnContent);
83296
- if (shouldAnimate) return buildRainbowHeaderLine(rawFaceLine, rawRightColumnContent, 0);
83297
- if (lineIndex !== 1) return buildScoreHeaderLine(renderedFaceLine, rightColumnContent);
83298
- return buildScoreHeaderLine(renderedFaceLine, colorizeRainbowText(buildRawScoreBar(score), 0, getRightColumnOffset(rawFaceLine)));
83299
- };
83300
- const printAnimatedScore = (scoreFaceLine, barFaceLine, score, label, potentialScore) => gen(function* () {
83301
- const isPerfectScore = score === 100;
83302
- for (let frame = 0; frame <= 40; frame += 1) {
83303
- const progress = easeOutCubic(frame / 40);
83304
- const animatedScore = Math.round(score * progress);
83305
- if (isPerfectScore) {
83306
- yield* writeStdout(`${frame === 0 ? "" : "\x1B[4A"}\r${buildRainbowScoreHeaderFrame({
83307
- score,
83308
- displayScore: animatedScore,
83309
- label,
83310
- frame
83311
- })}`);
83312
- if (frame < 40) yield* sleep(50);
83313
- continue;
83314
- }
83315
- const animatedScoreLine = buildScoreLine(animatedScore, score, label);
83316
- const animatedBarLine = frame === 40 && potentialScore !== void 0 ? buildProjectedScoreBar(score, potentialScore) : buildScoreBar(animatedScore, score);
83317
- yield* writeStdout(`${frame === 0 ? "" : "\x1B[2A"}\r${buildScoreHeaderLine(scoreFaceLine, animatedScoreLine)}\n\r${buildScoreHeaderLine(barFaceLine, animatedBarLine)}\n`);
83318
- if (frame < 40) yield* sleep(50);
83319
- }
83320
- if (!isPerfectScore) return;
83321
- for (let frame = 0; frame < 16; frame += 1) {
83322
- yield* writeStdout(`\x1b[4A\r${buildRainbowScoreHeaderFrame({
83323
- score,
83324
- displayScore: score,
83325
- label,
83326
- frame
83327
- })}`);
83328
- yield* sleep(50);
83329
- }
83330
- yield* writeStdout(`\x1b[4A\r${buildFinalPerfectScoreHeaderFrame(score, label, 16)}\x1b[2A`);
83331
- });
83332
- const printScoreHeader = (scoreResult, potentialScore) => gen(function* () {
83333
- const isPerfectScore = scoreResult.score === 100;
83334
- const renderedFaceLines = buildFaceRenderedLines(scoreResult.score);
83335
- const rawFaceLines = buildRawFaceLines(scoreResult.score);
83336
- const shouldAnimate = !isSpinnerSilent() && canAnimateOnboarding(process.stdout);
83337
- const displayScore = shouldAnimate ? 0 : scoreResult.score;
83338
- const rightColumnLines = [
83339
- buildScoreLine(displayScore, scoreResult.score, scoreResult.label),
83340
- shouldAnimate ? buildScoreBar(0, scoreResult.score) : potentialScore !== void 0 ? buildProjectedScoreBar(scoreResult.score, potentialScore) : buildScoreBar(scoreResult.score),
83341
- BRANDING_LINE,
83342
- ""
83343
- ];
83344
- const rawRightColumnLines = [
83345
- buildRawScoreLine(displayScore, scoreResult.label),
83346
- buildRawScoreBar(displayScore),
83347
- RAW_BRANDING_LINE,
83348
- ""
83349
- ];
83350
- for (let lineIndex = 0; lineIndex < renderedFaceLines.length; lineIndex += 1) yield* log(buildInitialScoreHeaderLine({
83351
- isPerfectScore,
83352
- shouldAnimate,
83353
- lineIndex,
83354
- renderedFaceLine: renderedFaceLines[lineIndex] ?? "",
83355
- rawFaceLine: rawFaceLines[lineIndex] ?? "",
83356
- rightColumnContent: rightColumnLines[lineIndex] ?? "",
83357
- rawRightColumnContent: rawRightColumnLines[lineIndex] ?? "",
83358
- score: scoreResult.score
83359
- }));
83360
- yield* log("");
83361
- if (shouldAnimate) {
83362
- yield* writeStdout("\x1B[5A");
83363
- yield* printAnimatedScore(renderedFaceLines[0], renderedFaceLines[1], scoreResult.score, scoreResult.label, potentialScore);
83364
- yield* writeStdout("\x1B[3B");
83365
- }
83366
- });
83367
- const animateScoreProjection = (scoreResult, potentialScore, linesBelowBar) => gen(function* () {
83368
- if (scoreResult.score === 100 || potentialScore <= scoreResult.score) return;
83369
- const barFaceLine = buildFaceRenderedLines(scoreResult.score)[1] ?? "";
83370
- for (let frame = 1; frame <= 16; frame += 1) {
83371
- const progress = easeOutCubic(frame / 16);
83372
- const displayedPotential = scoreResult.score + (potentialScore - scoreResult.score) * progress;
83373
- yield* writeStdout(`\x1b[${linesBelowBar}A\r${buildScoreHeaderLine(barFaceLine, buildProjectedScoreBar(scoreResult.score, displayedPotential))}\x1b[${linesBelowBar}B\r`);
83374
- if (frame < 16) yield* sleep(35);
83375
- }
83376
- });
83377
- const printBrandingOnlyHeader = gen(function* () {
83378
- yield* log(` ${BRANDING_LINE}`);
83379
- yield* log("");
83380
- });
83381
- const printNoScoreHeader = (noScoreMessage) => gen(function* () {
83382
- yield* log(` ${BRANDING_LINE}`);
83383
- yield* log(` ${highlighter.gray(noScoreMessage)}`);
83384
- yield* log("");
83385
- });
83386
- //#endregion
83387
82602
  //#region src/cli/utils/build-footer-description-lines.ts
83388
82603
  const FOOTER_DESCRIPTION_INDENT = " ";
83389
82604
  const buildFooterDescriptionLines = (description) => {
@@ -83404,67 +82619,90 @@ const buildFooterLinkLines = ({ shareUrl }) => [
83404
82619
  ...buildFooterDescriptionLines("Report issues and star the repository!")
83405
82620
  ];
83406
82621
  //#endregion
83407
- //#region src/cli/utils/write-diagnostics-directory.ts
83408
- const ruleDumpFileName = (ruleKey) => ruleKey.replace(/\//g, "--") + ".txt";
83409
- const readPreviousRuleDumpFileNames = (directory) => {
83410
- try {
83411
- const previous = JSON.parse(NFS.readFileSync(Path.join(directory, "diagnostics.json"), "utf8"));
83412
- return new Set(previous.filter((d) => typeof d?.plugin === "string" && typeof d?.rule === "string").map((d) => ruleDumpFileName(`${d.plugin}/${d.rule}`)));
83413
- } catch {
83414
- return /* @__PURE__ */ new Set();
83415
- }
83416
- };
83417
- const writeDiagnosticsDirectory = (diagnostics, outputDirectory) => {
83418
- const directory = outputDirectory ? Path.resolve(outputDirectory) : Path.join(tmpdir(), `react-doctor-${randomUUID()}`);
83419
- if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) NFS.rmSync(Path.join(directory, fileName), { force: true });
83420
- NFS.mkdirSync(directory, { recursive: true });
83421
- for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) NFS.writeFileSync(Path.join(directory, ruleDumpFileName(ruleKey)), formatRuleSummary(ruleKey, ruleDiagnostics));
83422
- NFS.writeFileSync(Path.join(directory, "diagnostics.json"), JSON.stringify(diagnostics));
83423
- return directory;
83424
- };
82622
+ //#region src/cli/utils/build-section-divider.ts
82623
+ const DIVIDER_INDENT = " ";
82624
+ const buildSectionDivider = () => highlighter.dim(`${DIVIDER_INDENT}${"─".repeat(resolveMeasureWidth(2))}`);
82625
+ //#endregion
82626
+ //#region src/cli/utils/collect-affected-files.ts
82627
+ const collectAffectedFiles = (diagnostics) => new Set(diagnostics.map((diagnostic) => diagnostic.filePath));
83425
82628
  //#endregion
83426
- //#region src/cli/utils/render-summary.ts
83427
- const buildShareUrl = (diagnostics, scoreResult, projectName) => {
83428
- const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
83429
- const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
83430
- const affectedFileCount = collectAffectedFiles(diagnostics).size;
83431
- const params = new URLSearchParams();
83432
- params.set("p", projectName);
83433
- if (scoreResult) params.set("s", String(scoreResult.score));
83434
- if (errorCount > 0) params.set("e", String(errorCount));
83435
- if (warningCount > 0) params.set("w", String(warningCount));
83436
- if (affectedFileCount > 0) params.set("f", String(affectedFileCount));
83437
- return `${SHARE_BASE_URL}?${params.toString()}`;
82629
+ //#region src/cli/utils/print-footer.ts
82630
+ const buildShareUrl = (input) => {
82631
+ const errorCount = input.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
82632
+ const warningCount = input.diagnostics.length - errorCount;
82633
+ const affectedFileCount = collectAffectedFiles(input.diagnostics).size;
82634
+ const parameters = new URLSearchParams({ p: input.projectName });
82635
+ if (input.scoreResult) parameters.set("s", String(input.scoreResult.score));
82636
+ if (errorCount > 0) parameters.set("e", String(errorCount));
82637
+ if (warningCount > 0) parameters.set("w", String(warningCount));
82638
+ if (affectedFileCount > 0) parameters.set("f", String(affectedFileCount));
82639
+ return `${SHARE_BASE_URL}?${parameters.toString()}`;
83438
82640
  };
83439
82641
  const printFooter = (input) => gen(function* () {
83440
82642
  yield* log("");
83441
82643
  yield* log(buildSectionDivider());
83442
82644
  yield* log("");
83443
- const shareUrl = input.isOffline ? null : buildShareUrl(input.diagnostics, input.scoreResult, input.projectName);
82645
+ const shareUrl = input.isOffline ? null : buildShareUrl(input);
83444
82646
  for (const line of buildFooterLinkLines({ shareUrl })) yield* log(line);
83445
82647
  });
83446
- const printDiagnosticsDump = (diagnostics, outputDirectory, verbose, stream = "stdout") => gen(function* () {
83447
- const writtenDirectory = yield* try_({
83448
- try: () => writeDiagnosticsDirectory(diagnostics, outputDirectory),
83449
- catch: (cause) => cause
83450
- }).pipe(orElseSucceed(() => null));
83451
- if (writtenDirectory !== null && (verbose || outputDirectory)) {
83452
- const pathLine = highlighter.gray(` Full diagnostics written to ${writtenDirectory}`);
83453
- yield* stream === "stderr" || Boolean(outputDirectory) && isJsonModeActive() ? sync(() => process.stderr.write(`${pathLine}\n`)) : log(pathLine);
82648
+ //#endregion
82649
+ //#region src/cli/utils/format-elapsed-time.ts
82650
+ const formatElapsedTime = (elapsedMilliseconds) => {
82651
+ if (elapsedMilliseconds < 1e3) return `${Math.round(elapsedMilliseconds)}ms`;
82652
+ return `${(elapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1)}s`;
82653
+ };
82654
+ //#endregion
82655
+ //#region src/cli/utils/pluralize.ts
82656
+ const pluralize = (count, singularNoun) => `${count} ${singularNoun}${count === 1 ? "" : "s"}`;
82657
+ //#endregion
82658
+ //#region src/cli/utils/print-headless-report.ts
82659
+ const formatContextTag = (diagnostic) => diagnostic.fileContext ? ` (${diagnostic.fileContext} file)` : "";
82660
+ const printHeadlessReport = (input) => gen(function* () {
82661
+ yield* log(`${highlighter.success("✔")} Scanned ${pluralize(input.scannedFileCount, "file")} in ${formatElapsedTime(input.elapsedMilliseconds)}`);
82662
+ yield* log("");
82663
+ yield* log(highlighter.bold(`React Doctor — ${input.projectName}`));
82664
+ if (input.scoreResult) yield* log(`Score: ${input.scoreResult.score} / 100 ${input.scoreResult.label}`);
82665
+ else yield* log(highlighter.gray(input.noScoreMessage));
82666
+ if (input.diagnostics.length === 0) {
82667
+ yield* log("");
82668
+ yield* log(highlighter.success(`✔ ${input.emptyStateMessage}`));
82669
+ } else {
82670
+ yield* log("");
82671
+ yield* log(highlighter.bold(pluralize(input.diagnostics.length, "issue")));
82672
+ const categoryCounts = /* @__PURE__ */ new Map();
82673
+ for (const diagnostic of input.diagnostics) {
82674
+ const counts = categoryCounts.get(diagnostic.category) ?? {
82675
+ error: 0,
82676
+ warning: 0
82677
+ };
82678
+ categoryCounts.set(diagnostic.category, {
82679
+ error: counts.error + Number(diagnostic.severity === "error"),
82680
+ warning: counts.warning + Number(diagnostic.severity === "warning")
82681
+ });
82682
+ }
82683
+ for (const [category, counts] of categoryCounts) {
82684
+ const countParts = [];
82685
+ if (counts.error > 0) countParts.push(pluralize(counts.error, "error"));
82686
+ if (counts.warning > 0) countParts.push(pluralize(counts.warning, "warning"));
82687
+ yield* log(`${category}: ${countParts.join(", ")}`);
82688
+ }
82689
+ yield* log("");
82690
+ for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(input.diagnostics)) {
82691
+ const representativeDiagnostic = ruleDiagnostics[0];
82692
+ if (representativeDiagnostic === void 0) continue;
82693
+ const severityIcon = representativeDiagnostic.severity === "error" ? "✖" : "⚠";
82694
+ const colorize = representativeDiagnostic.severity === "error" ? highlighter.error : highlighter.warn;
82695
+ const occurrenceSuffix = ruleDiagnostics.length > 1 ? ` ×${ruleDiagnostics.length}` : "";
82696
+ yield* log(colorize(`${severityIcon} ${representativeDiagnostic.title ?? ruleKey}${occurrenceSuffix}`));
82697
+ yield* log(highlighter.gray(` ${ruleKey}`));
82698
+ for (const diagnostic of ruleDiagnostics) yield* log(` ${formatDiagnosticSite(diagnostic)}${formatContextTag(diagnostic)}`);
82699
+ }
82700
+ }
82701
+ if (input.skippedChecks.length > 0) {
82702
+ yield* log("");
82703
+ yield* warn$1(highlighter.warn(`Results are incomplete: ${input.skippedChecks.join(" and ")} checks failed.`));
83454
82704
  }
83455
82705
  });
83456
- const printSummary = (input) => gen(function* () {
83457
- if (input.scoreResult) {
83458
- const animateProjection = Boolean(input.animateProjection) && input.potentialScore != null && !input.verbose;
83459
- yield* printScoreHeader(input.scoreResult, animateProjection ? void 0 : input.potentialScore ?? void 0);
83460
- if (input.potentialScore != null) {
83461
- const improvement = input.potentialScore - input.scoreResult.score;
83462
- yield* log(highlighter.gray(" You could improve ") + colorizeByScore(`+${improvement}%`, input.potentialScore) + highlighter.gray(` by fixing the top 3 issues`));
83463
- if (animateProjection) yield* animateScoreProjection(input.scoreResult, input.potentialScore, 5);
83464
- }
83465
- } else yield* printNoScoreHeader(input.noScoreMessage);
83466
- yield* printDiagnosticsDump(input.diagnostics, input.outputDirectory, input.verbose);
83467
- });
83468
82706
  //#endregion
83469
82707
  //#region src/cli/utils/resolve-oxlint-node.ts
83470
82708
  const consoleWarn = (message) => warn$1(highlighter.warn(message));
@@ -83538,15 +82776,6 @@ const resolveCliCategories = (categoryFlag) => {
83538
82776
  //#endregion
83539
82777
  //#region src/inspect.ts
83540
82778
  const silentConsole = makeNoopConsole();
83541
- const recordOnboardingCompletion = (options) => {
83542
- const forceOnboarding = isOnboardingForced();
83543
- if (shouldRecordOnboarding({
83544
- paceOnboardingSections: !options.silent && !options.scoreOnly && !options.suppressRendering && !options.verbose && canAnimateOnboarding(process.stdout) && (forceOnboarding || !hasCompletedOnboarding()),
83545
- forceOnboarding,
83546
- verbose: options.verbose,
83547
- isNonInteractiveEnvironment: options.isNonInteractiveEnvironment
83548
- })) markOnboardingComplete();
83549
- };
83550
82779
  const buildChangedLineMatcher = (directory, changedLineRanges) => {
83551
82780
  const rangesByFile = /* @__PURE__ */ new Map();
83552
82781
  for (const entry of changedLineRanges) rangesByFile.set(toForwardSlashes(entry.file), entry.ranges);
@@ -83826,7 +83055,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
83826
83055
  recordSentryProjectContext(cachedPayload.project, rootSentrySpan, { concurrentScan: options.concurrentScan });
83827
83056
  recordCount(METRIC.projectDetected, 1);
83828
83057
  const baselineDegraded = Boolean(options.baseline) && isDiffMode && cachedPayload.baselineDelta === void 0;
83829
- const result = await renderAndRecordScan({
83058
+ return await renderAndRecordScan({
83830
83059
  payload: cachedPayload,
83831
83060
  options,
83832
83061
  userConfig,
@@ -83837,8 +83066,6 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
83837
83066
  baselineDegraded,
83838
83067
  wholeRepoCacheHit: true
83839
83068
  });
83840
- recordOnboardingCompletion(options);
83841
- return result;
83842
83069
  }
83843
83070
  const shouldShowProgressSpinners = !options.isCiOrCodingAgentEnvironment && !options.silent && !options.scoreOnly && !options.suppressRendering && options.lint && Boolean(resolvedNodeBinaryPath);
83844
83071
  const layers = buildRuntimeLayers({
@@ -83941,7 +83168,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
83941
83168
  suppressedRuleCounts: output.suppressedRuleCounts
83942
83169
  };
83943
83170
  if (cacheKey !== null && scanResultCache !== null && shouldStoreScanPayload(payload) && !baselineDegraded) scanResultCache.store(cacheKey, payload);
83944
- const result = await renderAndRecordScan({
83171
+ return await renderAndRecordScan({
83945
83172
  payload,
83946
83173
  options,
83947
83174
  userConfig,
@@ -83959,8 +83186,6 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
83959
83186
  deadCodeSummaryCacheHits: output.deadCodeSummaryCacheHits,
83960
83187
  deadCodeSummaryCacheMisses: output.deadCodeSummaryCacheMisses
83961
83188
  });
83962
- recordOnboardingCompletion(options);
83963
- return result;
83964
83189
  };
83965
83190
  const runMaybeSilent = (effect, silent) => silent ? effect.pipe(provideService$2(Console, silentConsole)) : effect;
83966
83191
  const renderAndRecordScan = async (input) => {
@@ -83979,7 +83204,6 @@ const renderAndRecordScan = async (input) => {
83979
83204
  supplyChainOverlapTimedOut: input.payload.supplyChainOverlapTimedOut,
83980
83205
  securityScanFailed: input.payload.securityScanFailed ?? false,
83981
83206
  securityScanFailureReason: input.payload.securityScanFailureReason ?? null,
83982
- directory: input.payload.directory,
83983
83207
  scannedFileCount: input.payload.scannedFileCount,
83984
83208
  scannedFilePaths: input.payload.scannedFilePaths,
83985
83209
  analyzedFiles: input.payload.analyzedFiles ?? [],
@@ -84030,7 +83254,7 @@ const renderAndRecordScan = async (input) => {
84030
83254
  return result;
84031
83255
  };
84032
83256
  const finalizeAndRender = (input) => gen(function* () {
84033
- const { options, elapsedMilliseconds, diagnostics, score, project, userConfig, didLintFail, lintFailureReason, lintPartialFailures, didDeadCodeFail, deadCodeFailureReason, supplyChainOverlapTimedOut, securityScanFailed, securityScanFailureReason, directory, scannedFileCount, scannedFilePaths, analyzedFiles, scanElapsedMilliseconds, lintCacheHitFileCount, lintCacheTotalFileCount, lintSidecarReplayedFileCount, lintSidecarTotalFileCount, deadCodeCacheHit, deadCodeSummaryCacheHits, deadCodeSummaryCacheMisses, baselineDelta } = input;
83257
+ const { options, elapsedMilliseconds, diagnostics, score, project, userConfig, didLintFail, lintFailureReason, lintPartialFailures, didDeadCodeFail, deadCodeFailureReason, supplyChainOverlapTimedOut, securityScanFailed, securityScanFailureReason, scannedFileCount, scannedFilePaths, analyzedFiles, scanElapsedMilliseconds, lintCacheHitFileCount, lintCacheTotalFileCount, lintSidecarReplayedFileCount, lintSidecarTotalFileCount, deadCodeCacheHit, deadCodeSummaryCacheHits, deadCodeSummaryCacheMisses, baselineDelta } = input;
84034
83258
  const { skippedChecks, skippedCheckReasons } = buildSkippedChecks({
84035
83259
  didLintFail,
84036
83260
  lintFailureReason,
@@ -84082,69 +83306,32 @@ const finalizeAndRender = (input) => gen(function* () {
84082
83306
  else yield* error$1(highlighter.gray(noScoreMessage));
84083
83307
  return buildResult();
84084
83308
  }
84085
- const animateRender = !options.silent && !options.verbose && canAnimateOnboarding(process.stdout);
84086
- const pause = onboardingSectionPause(animateRender);
84087
- const useHyperlinks = shouldRenderHyperlinks(process.stdout);
84088
83309
  const demotedDiagnosticCount = diagnostics.length - surfaceDiagnostics.length;
84089
- const lintSourceFileCount = options.includePaths.length > 0 ? options.includePaths.length : project.sourceFileCount;
84090
- if (printedDiagnostics.length === 0) {
84091
- yield* pause;
84092
- if (hasSkippedChecks) {
84093
- const skippedLabel = skippedChecks.join(" and ");
84094
- yield* warn$1(highlighter.warn(`No issues detected, but ${skippedLabel} checks failed — results are incomplete.`));
84095
- } else yield* log(highlighter.success(buildEmptyReportMessage({
83310
+ if (options.isNonInteractiveEnvironment && options.outputSurface !== "prComment") yield* printAgentGuidance();
83311
+ yield* printHeadlessReport({
83312
+ diagnostics: printedDiagnostics,
83313
+ elapsedMilliseconds,
83314
+ emptyStateMessage: buildEmptyReportMessage({
84096
83315
  categoryFilters: options.categoryFilters,
84097
83316
  demotedDiagnosticCount,
84098
83317
  outputSurface: options.outputSurface
84099
- })));
84100
- yield* log("");
84101
- yield* pause;
84102
- if (hasSkippedChecks) {
84103
- yield* printBrandingOnlyHeader;
84104
- yield* log(highlighter.gray(" Score not shown — some checks could not complete."));
84105
- } else if (score) yield* printScoreHeader(score);
84106
- else yield* printNoScoreHeader(noScoreMessage);
84107
- if (options.outputDirectory !== null) yield* printDiagnosticsDump(printedDiagnostics, options.outputDirectory);
84108
- return buildResult();
84109
- }
84110
- yield* pause;
84111
- yield* log("");
84112
- yield* printDiagnostics([...printedDiagnostics], options.verbose, directory, buildRulePriorityMap([score]), isCodingAgentEnvironment(), {
84113
- sectionPause: pause,
84114
- animateCountUp: animateRender
84115
- }, useHyperlinks);
84116
- if (options.isNonInteractiveEnvironment && options.outputSurface !== "prComment") yield* printAgentGuidance();
84117
- if (options.categoryFilters.size === 0 && demotedDiagnosticCount > 0) {
84118
- yield* log(highlighter.gray(` ${demotedDiagnosticCount} demoted from the ${options.outputSurface} surface (e.g. design cleanup) — run \`npx react-doctor@latest .\` locally for the full list.`));
84119
- yield* log("");
84120
- }
84121
- const scoreDiagnostics = filterDiagnosticsForSurface([...diagnostics], "score", userConfig);
84122
- const displayedScoreDiagnostics = filterDiagnosticsForSurface([...printedDiagnostics], "score", userConfig);
84123
- const potentialScore = score ? yield* promise(() => computeProjectedScore(displayedScoreDiagnostics, scoreDiagnostics, score)) : null;
84124
- const showShareLink = shouldShowShareLink(options);
84125
- yield* pause;
84126
- yield* printSummary({
84127
- diagnostics: [...printedDiagnostics],
84128
- elapsedMilliseconds,
84129
- scoreResult: score,
84130
- potentialScore,
84131
- totalSourceFileCount: lintSourceFileCount,
83318
+ }),
84132
83319
  noScoreMessage,
84133
- verbose: options.verbose,
84134
- outputDirectory: options.outputDirectory,
84135
- animateProjection: animateRender
83320
+ projectName: project.projectName,
83321
+ scannedFileCount,
83322
+ scoreResult: hasSkippedChecks ? null : score,
83323
+ skippedChecks
84136
83324
  });
84137
- if (hasSkippedChecks) {
84138
- const skippedLabel = skippedChecks.join(" and ");
83325
+ if (options.outputDirectory !== null || options.verbose) yield* printDiagnosticsDump(printedDiagnostics, options.outputDirectory, options.verbose);
83326
+ if (options.categoryFilters.size === 0 && demotedDiagnosticCount > 0) {
83327
+ yield* log(highlighter.gray(` ${demotedDiagnosticCount} demoted from the ${options.outputSurface} surface (e.g. design cleanup) — run \`npx react-doctor@latest .\` locally for the full list.`));
84139
83328
  yield* log("");
84140
- yield* warn$1(highlighter.warn(` Note: ${skippedLabel} checks failed — score may be incomplete.`));
84141
83329
  }
84142
- yield* pause;
84143
83330
  yield* printFooter({
84144
- diagnostics: [...printedDiagnostics],
83331
+ diagnostics: printedDiagnostics,
84145
83332
  scoreResult: score,
84146
83333
  projectName: project.projectName,
84147
- isOffline: !showShareLink
83334
+ isOffline: options.isCi || !options.share || score === null
84148
83335
  });
84149
83336
  return buildResult();
84150
83337
  });
@@ -84175,9 +83362,6 @@ const collectProjectSourceFileCounts = async (rootDirectory, projectDirectories)
84175
83362
  //#region src/cli/utils/format-skipped-projects-message.ts
84176
83363
  const formatSkippedProjectsMessage = (projectCount) => `${projectCount} ${projectCount === 1 ? "project was" : "projects were"} skipped because the max scan duration was reached.`;
84177
83364
  //#endregion
84178
- //#region src/cli/utils/is-share-opted-out.ts
84179
- const isShareOptedOut = (scans, flagNoScore) => Boolean(flagNoScore) || scans.some((scan) => Boolean(scan.config?.noScore) || scan.config?.share === false);
84180
- //#endregion
84181
83365
  //#region src/cli/utils/build-handoff-payload.ts
84182
83366
  const buildHandoffPayload = (input) => {
84183
83367
  const topGroups = buildSortedRuleGroups(input.diagnostics).slice(0, 3);
@@ -84374,6 +83558,186 @@ const detectLaunchableAgents = async () => {
84374
83558
  return CLI_AGENT_IDS.filter((agentId) => detected.has(agentId) && isCommandAvailable(CLI_AGENT_BINARIES[agentId]));
84375
83559
  };
84376
83560
  //#endregion
83561
+ //#region src/cli/utils/now-iso.ts
83562
+ const nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
83563
+ const ONBOARDING_EVENT = "onboarding";
83564
+ const CI_PITCH_EVENT = "ci-pitch";
83565
+ const ACTION_UPGRADE_EVENT = "action-upgrade-v2";
83566
+ const SETUP_HINT_EVENT = "setup-hint";
83567
+ const HANDOFF_TARGET_PREFERENCE_ID = "handoff-target";
83568
+ const INSTALL_AGENTS_PREFERENCE_ID = "install-agents";
83569
+ const foldLegacyDecisions = (projects, legacy, eventId) => {
83570
+ for (const [hash, record] of Object.entries(legacy ?? {})) {
83571
+ const existing = projects[hash] ?? { rootDirectory: record.rootDirectory ?? "" };
83572
+ projects[hash] = {
83573
+ ...existing,
83574
+ events: {
83575
+ ...existing.events,
83576
+ [eventId]: {
83577
+ firedAt: record.at ?? nowIso(),
83578
+ version: 1,
83579
+ ...record.outcome ? { outcome: record.outcome } : {}
83580
+ }
83581
+ }
83582
+ };
83583
+ }
83584
+ };
83585
+ const migrateCliState = (state) => {
83586
+ if (typeof state.schemaVersion === "number" && state.schemaVersion >= 2) return state;
83587
+ const projects = {};
83588
+ for (const [hash, record] of Object.entries(state.projects ?? {})) {
83589
+ const carried = {
83590
+ rootDirectory: record.rootDirectory,
83591
+ ...record.events ? { events: record.events } : {},
83592
+ ...record.migrations ? { migrations: record.migrations } : {}
83593
+ };
83594
+ projects[hash] = record.setupPrompt === false ? {
83595
+ ...carried,
83596
+ events: {
83597
+ ...carried.events,
83598
+ [SETUP_HINT_EVENT]: {
83599
+ firedAt: nowIso(),
83600
+ version: 1,
83601
+ outcome: "declined"
83602
+ }
83603
+ }
83604
+ } : carried;
83605
+ }
83606
+ foldLegacyDecisions(projects, state.ciPrompts, CI_PITCH_EVENT);
83607
+ foldLegacyDecisions(projects, state.actionUpgrades, ACTION_UPGRADE_EVENT);
83608
+ return {
83609
+ schemaVersion: 2,
83610
+ global: typeof state.onboardedAt === "string" ? { events: { [ONBOARDING_EVENT]: {
83611
+ firedAt: state.onboardedAt,
83612
+ version: 1
83613
+ } } } : {},
83614
+ projects
83615
+ };
83616
+ };
83617
+ const resolveConfigDir = (options) => options.cwd ?? (process.env["REACT_DOCTOR_CONFIG_DIR"] || void 0);
83618
+ const openStore = (options = {}) => new Conf({
83619
+ projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
83620
+ cwd: resolveConfigDir(options)
83621
+ });
83622
+ const openMigratedStore = (options) => {
83623
+ const store = openStore(options);
83624
+ const state = store.store;
83625
+ const migrated = migrateCliState(state);
83626
+ if (migrated !== state) store.store = migrated;
83627
+ return store;
83628
+ };
83629
+ const readCliState = (select, fallback, options = {}) => {
83630
+ try {
83631
+ return select(openMigratedStore(options).store);
83632
+ } catch {
83633
+ return fallback;
83634
+ }
83635
+ };
83636
+ const updateCliState = (update, options = {}) => {
83637
+ try {
83638
+ const store = openMigratedStore(options);
83639
+ store.store = update(store.store);
83640
+ return true;
83641
+ } catch {
83642
+ return false;
83643
+ }
83644
+ };
83645
+ //#endregion
83646
+ //#region src/cli/utils/hash-project-root.ts
83647
+ const hashProjectRoot = (projectRoot) => createHash("sha256").update(Path.resolve(projectRoot)).digest("hex");
83648
+ //#endregion
83649
+ //#region src/cli/utils/cli-lifecycle.ts
83650
+ const versionOf = (item) => item.version ?? 1;
83651
+ const selectScope = (state, scoped, projectRoot) => scoped.scope === "global" ? state.global : projectRoot === void 0 ? void 0 : state.projects?.[hashProjectRoot(projectRoot)];
83652
+ const updateScope = (state, scoped, projectRoot, updateScopeState) => {
83653
+ if (scoped.scope === "global") return {
83654
+ ...state,
83655
+ global: updateScopeState(state.global ?? {})
83656
+ };
83657
+ if (projectRoot === void 0) return state;
83658
+ const projectKey = hashProjectRoot(projectRoot);
83659
+ const base = state.projects?.[projectKey] ?? { rootDirectory: Path.resolve(projectRoot) };
83660
+ return {
83661
+ ...state,
83662
+ projects: {
83663
+ ...state.projects,
83664
+ [projectKey]: {
83665
+ ...base,
83666
+ ...updateScopeState(base)
83667
+ }
83668
+ }
83669
+ };
83670
+ };
83671
+ const isGatePending = (gate, target = {}, options = {}) => {
83672
+ if (gate.scope === "project" && target.projectRoot === void 0) return false;
83673
+ return readCliState((state) => {
83674
+ const record = selectScope(state, gate, target.projectRoot)?.events?.[gate.id];
83675
+ return !record || record.version < versionOf(gate);
83676
+ }, gate.fireWhenUnknown ?? false, options);
83677
+ };
83678
+ const recordGate = (gate, target = {}, options = {}) => updateCliState((state) => updateScope(state, gate, target.projectRoot, (scope) => ({
83679
+ ...scope,
83680
+ events: {
83681
+ ...scope.events,
83682
+ [gate.id]: {
83683
+ firedAt: nowIso(),
83684
+ version: versionOf(gate),
83685
+ ...target.outcome ? { outcome: target.outcome } : {}
83686
+ }
83687
+ }
83688
+ })), options);
83689
+ const readPreference = (preference, target = {}, options = {}) => readCliState((state) => selectScope(state, preference, target.projectRoot)?.preferences?.[preference.id] ?? null, null, options);
83690
+ const writePreference = (preference, value, target = {}, options = {}) => updateCliState((state) => updateScope(state, preference, target.projectRoot, (scope) => ({
83691
+ ...scope,
83692
+ preferences: {
83693
+ ...scope.preferences,
83694
+ [preference.id]: value
83695
+ }
83696
+ })), options);
83697
+ const isMigrationPending = (migration, target = {}, options = {}) => {
83698
+ if (migration.scope === "project" && target.projectRoot === void 0) return false;
83699
+ return readCliState((state) => {
83700
+ const record = selectScope(state, migration, target.projectRoot)?.migrations?.[migration.id];
83701
+ return !record || record.version < versionOf(migration);
83702
+ }, false, options);
83703
+ };
83704
+ const recordMigration = (migration, projectRoot, record, options) => updateCliState((state) => updateScope(state, migration, projectRoot, (scope) => ({
83705
+ ...scope,
83706
+ migrations: {
83707
+ ...scope.migrations,
83708
+ [migration.id]: record
83709
+ }
83710
+ })), options);
83711
+ const runMigrations = async (migrations, target = {}, options = {}) => {
83712
+ const results = [];
83713
+ for (const migration of migrations) {
83714
+ if (!isMigrationPending(migration, target, options)) {
83715
+ results.push({
83716
+ id: migration.id,
83717
+ ran: false,
83718
+ applied: true
83719
+ });
83720
+ continue;
83721
+ }
83722
+ let applied = false;
83723
+ try {
83724
+ applied = await migration.run({ projectRoot: target.projectRoot });
83725
+ } catch {
83726
+ applied = false;
83727
+ }
83728
+ if (applied) recordMigration(migration, target.projectRoot, {
83729
+ ranAt: nowIso(),
83730
+ version: versionOf(migration)
83731
+ }, options);
83732
+ results.push({
83733
+ id: migration.id,
83734
+ ran: true,
83735
+ applied
83736
+ });
83737
+ }
83738
+ return results;
83739
+ };
83740
+ //#endregion
84377
83741
  //#region src/cli/utils/open-url.ts
84378
83742
  const resolveOpenCommand = (url) => {
84379
83743
  if (process$1.platform === "darwin") return {
@@ -84619,6 +83983,48 @@ const resolveProjectScan = async (rootScanTarget, projectDirectory) => {
84619
83983
  };
84620
83984
  };
84621
83985
  //#endregion
83986
+ //#region src/cli/utils/resolve-absolute-path.ts
83987
+ /**
83988
+ * Resolves a diagnostic's `filePath` (relative to its project root, or
83989
+ * already absolute) to an absolute path. Shared by the code-frame reader and
83990
+ * the terminal hyperlink builder so both turn a relative path into the same
83991
+ * on-disk location.
83992
+ */
83993
+ const resolveAbsolutePath = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
83994
+ //#endregion
83995
+ //#region src/cli/utils/build-code-frame.ts
83996
+ /**
83997
+ * Renders a syntax-highlighted source excerpt around a diagnostic site
83998
+ * with a caret pointing at the offending column. Returns null when the
83999
+ * file can't be read (e.g. multi-project summaries where paths are
84000
+ * resolved against a different cwd), so callers can fall back to the
84001
+ * bare `file:line` reference instead of failing the whole render.
84002
+ */
84003
+ const buildCodeFrame = (input) => {
84004
+ if (input.line <= 0) return null;
84005
+ const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
84006
+ let source;
84007
+ try {
84008
+ source = NFS.readFileSync(absolutePath, "utf8");
84009
+ } catch {
84010
+ return null;
84011
+ }
84012
+ if ((source.split("\n", input.line)[input.line - 1] ?? "").length > 200) return null;
84013
+ const location = input.endLine != null && input.endLine > input.line ? {
84014
+ start: { line: input.line },
84015
+ end: { line: input.endLine }
84016
+ } : { start: {
84017
+ line: input.line,
84018
+ column: input.column > 0 ? input.column : void 0
84019
+ } };
84020
+ return codeFrameColumns(source, location, {
84021
+ highlightCode: true,
84022
+ linesAbove: 1,
84023
+ linesBelow: 1,
84024
+ ...input.message ? { message: input.message } : {}
84025
+ });
84026
+ };
84027
+ //#endregion
84622
84028
  //#region src/cli/utils/select-projects.ts
84623
84029
  const discoverWorkspacePackages = (rootDirectory) => {
84624
84030
  const hasRootPackageJson = isFile(Path.join(rootDirectory, "package.json"));
@@ -84738,10 +84144,10 @@ const registerActiveTuiRenderer = (renderer) => {
84738
84144
  if (activeTuiRenderer === renderer) activeTuiRenderer = null;
84739
84145
  };
84740
84146
  };
84741
- const clearActiveTuiRenderer = () => {
84147
+ const preserveActiveTuiRendererOutput = () => {
84742
84148
  const renderer = activeTuiRenderer;
84743
84149
  activeTuiRenderer = null;
84744
- renderer?.clear();
84150
+ renderer?.preserveOutput();
84745
84151
  };
84746
84152
  //#endregion
84747
84153
  //#region src/cli/utils/exit-gracefully.ts
@@ -84750,7 +84156,7 @@ const exitGracefully = () => {
84750
84156
  if (didStartExiting) return process.exit(130);
84751
84157
  didStartExiting = true;
84752
84158
  activeScanAbortRegistry.abortAll();
84753
- clearActiveTuiRenderer();
84159
+ preserveActiveTuiRendererOutput();
84754
84160
  try {
84755
84161
  if (isJsonModeActive()) writeJsonErrorReport(/* @__PURE__ */ new Error("Scan cancelled by user (SIGINT/SIGTERM)"));
84756
84162
  else console.log([
@@ -84765,7 +84171,7 @@ const exitGracefully = () => {
84765
84171
  flushSentry().finally(() => process.exit(130));
84766
84172
  };
84767
84173
  //#endregion
84768
- export { canAnimateOnboarding as $, isDebugFlagEnabled as $n, provide as $r, isFullWidth as $t, formatSkippedProjectsMessage as A, flushSentry as An, getEquivalentRuleKeys as Ar, buildWorkflowContent as At, colorizeByScore as B, writeJsonErrorReport as Bn, loadConfigWithSource as Br, prompts as Bt, CLI_AGENT_BINARIES as C, resolveGitPath as Cn, findLegacyConfig as Cr, recordRunTraceId as Ct, detectAvailableAgents as D, detectDefaultBranch as Dn, getCategoryImpact as Dr, warnDeprecatedDiff as Dt, computeDefaultSelectedAgents as E, writeJsonFile as En, getBaselineDiffPlan as Er, resolveScope as Et, printDiagnosticsDump as F, buildSentryScope as Fn, isPathInsideDirectory as Fr, upgradeReactDoctorWorkflowInPlace as Ft, recordGate as G, SENTRY_FLUSH_TIMEOUT_MS as Gn, setColorEnabled as Gr, DOCTOR_PACKAGE_NAME as Gt, markOnboardingComplete as H, VERSION as Hn, messageFromUnknown as Hr, openWorkflowPullRequest as Ht, printFooter as I, enableJsonMode as In, isPlainObject as Ir, upgradeWorkflowActionToV2 as It, ACTION_UPGRADE_EVENT as J, TUI_REPORT_COMPACT_MAX_ROWS as Jn, yieldToEventLoop as Jr, hasDoctorScript as Jt, runMigrations as K, STAGED_FILES_TEMP_DIR_PREFIX as Kn, toRelativePath as Kr, findNearestPackageDirectory as Kt, printSummary as L, isJsonModeActive as Ln, isReactDoctorError as Lr, workflowUsesV1Action as Lt, deduplicateProjectScans as M, isExpectedUserError as Mn, hasReactRuntime as Mr, installReactDoctorWorkflow as Mt, createInvocationInspect as N, formatEnvironmentError as Nn, highlighter as Nr, isReactDoctorWorkflowInstalled as Nt, buildHandoffPayload as O, runCommand as On, getChangedLineRanges as Or, isValidBlockingLevel as Ot, inspect as P, isEnvironmentError as Pn, isErrnoException as Pr, readReactDoctorWorkflow as Pt, SETUP_HINT_EVENT as Q, isNonInteractiveEnvironment as Qn, promise as Qr, spinner as Qt, easeOutCubic as R, setJsonReportDirectory as Rn, isSameRuleKey as Rr, recordCount as Rt, detectLaunchableAgents as S, readPackageJson as Sn, discoverReactSubprojects as Sr, getLastRunTraceId as St, launchCliAgent as T, runGitRaw as Tn, formatReactDoctorError as Tr, isScopeValue as Tt, isGatePending as U, CI_TRUST_COMPANIES as Un, remainingDeadlineBudgetMs as Ur, stageWorkflowFile as Ut, hasCompletedOnboarding as V, writeJsonReport as Vn, mapWithConcurrency as Vr, unrefStdin as Vt, readPreference as W, METRIC as Wn, resolveScanTarget as Wr, toForwardSlashes as Wt, HANDOFF_TARGET_PREFERENCE_ID as X, WELCOME_EXPLANATION_HOLD_MS as Xn, log as Xr, isSpinnerSilent as Xt, CI_PITCH_EVENT as Y, TUI_REPORT_DETAIL_WIDTH_FRACTION as Yn, error$1 as Yr, installReactDoctorScriptStep as Yt, INSTALL_AGENTS_PREFERENCE_ID as Z, CliInputError as Zn, gen as Zr, setSpinnerSilent as Zt, filterScansForSurface as _, getPackageJsonPath as _n, StagedFiles as _r, buildRulePriorityMap as _t, shouldFailScanGate as a, provide$2 as ai, HOOK_RELATIVE_PATH as an, CI_URL as ar, computeProjectedScore as at, reportWorkflowResult as b, packageHasNestedRecordKey as bn, clearConfigCache as br, formatLearnMoreLine as bt, selectProjects as c, LEGACY_HOOK_RUNNER_RELATIVE_PATH as cn, CONFIG_SCHEMA_URL as cr, writeStdout as ct, resolveProjectDiffIncludePaths as d, PRE_COMMIT_CONFIG_FILE as dn, Git as dr, buildCodeFrame as dt, runPromise as ei, isWide as en, isCiEnvironment as er, isOnboardingForced as et, resolveProjectSourceFilePaths as f, REACT_DOCTOR_COMMAND as fn, LEGACY_CONFIG_FILENAME as fr, buildEmptyReportMessage as ft, resolveCliInspectOptions as g, ensureTrailingNewline as gn, STAGED_FILES_PROJECT_CONFIG_FILENAMES as gr, hasLintHardFailure as gt, resolveWorkspaceDeadCodeOwner as h, VITE_PLUS_HOOKS_PATH as hn, SKILL_NAME as hr, resolveMergeBaseRef as ht, validateModeFlags as i, void_ as ii, HOOK_FILE_NAME as in, CANONICAL_GITHUB_URL as ir, filterDiagnosticsByCategories as it, collectProjectSourceFileCounts as j, initializeSentry as jn, hasPublishedFixRecipe as jr, getReactDoctorWorkflowPath as jt, isShareOptedOut as k, cliLogger as kn, getDiffInfo as kr, resolveBlockingLevel as kt, resolveProjectScan as l, NON_BLOCKING_REACT_DOCTOR_COMMAND as ln, DIAGNOSTIC_CATEGORY_BUCKETS as lr, indentMultilineText as lt, retryMissingProjectScores as m, SIMPLE_GIT_HOOKS_PACKAGE_JSON_KEY as mn, Reporter as mr, buildNoScoreMessage as mt, registerActiveTuiRenderer as n, sleep as ni, isUnicodeSupported as nn, isCodingAgentEnvironment as nr, shouldRenderHyperlinks as nt, projectManifestChanged as o, succeed$3 as oi, HUSKY_HOOKS_PATH as on, COMPILER_CLEANUP_BUCKET as or, formatElapsedTime as ot, resolveProjectRelativeDirectory as p, SIMPLE_GIT_HOOKS_CONFIG_FILE as pn, Progress as pr, hasIncompleteScoreAnalysis as pt, writePreference as q, STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES as qn, validateConfigTypes as qr, hasDoctorDependency as qt, validateIncludeUntrackedScope as r, sync as ri, chalk as rn, CANONICAL_DISCORD_URL as rr, supportsHyperlinks as rt, discoverWorkspacePackages as s, LEFTHOOK_CONFIG_FILES as sn, COMPILER_CLEANUP_RULE_KEYS as sr, printDiagnostics as st, exitGracefully as t, runSync as ti, stripAnsi as tn, isCiOrCodingAgentEnvironment as tr, shouldRecordOnboarding as tt, resolveProjectChangedLineRanges as u, OVERCOMMIT_CONFIG_FILE as un, GITHUB_ACTIONS_SETUP_URL as ur, formatHyperlink as ut, countUniqueScannedFiles as v, isRecord as vn, buildJsonReport as vr, buildSortedRuleGroups as vt, copyToClipboard as w, runGit as wn, formatErrorChain as wr, finalizeScope as wt, openUrl as x, packageHasRecordKey as xn, detectAiTrainingEnvironment as xr, getActiveRunTrace as xt, setUpGitHubActions as y, packageHasDependency as yn, buildRuleDocsUrl as yr, formatFixRecipeLine as yt, doctorFace as z, setJsonReportMode as zn, listWorkspacePackages as zr, recordDistribution as zt };
84174
+ export { filterDiagnosticsByCategories as $, GITHUB_ACTIONS_SETUP_URL as $n, PRE_COMMIT_CONFIG_FILE as $t, HANDOFF_TARGET_PREFERENCE_ID as A, VERSION as An, loadConfigWithSource as Ar, openWorkflowPullRequest as At, buildHandoffPayload as B, isNonInteractiveEnvironment as Bn, log as Br, spinner as Bt, isGatePending as C, buildSentryScope as Cn, highlighter as Cr, upgradeReactDoctorWorkflowInPlace as Ct, writePreference as D, setJsonReportMode as Dn, isReactDoctorError as Dr, recordDistribution as Dt, runMigrations as E, setJsonReportDirectory as En, isPlainObject as Er, recordCount as Et, CLI_AGENT_BINARIES as F, STAGED_FILES_TEMP_DIR_PREFIX as Fn, setColorEnabled as Fr, hasDoctorDependency as Ft, inspect as G, isCodingAgentEnvironment as Gn, sync as Gr, chalk as Gt, collectProjectSourceFileCounts as H, isDebugFlagEnabled as Hn, provide as Hr, isWide as Ht, copyToClipboard as I, STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES as In, toRelativePath as Ir, hasDoctorScript as It, formatElapsedTime as J, CI_URL as Jn, succeed$3 as Jr, HUSKY_HOOKS_PATH as Jt, printHeadlessReport as K, CANONICAL_DISCORD_URL as Kn, void_ as Kr, HOOK_FILE_NAME as Kt, launchCliAgent as L, TUI_REPORT_COMPACT_MAX_ROWS as Ln, validateConfigTypes as Lr, installReactDoctorScriptStep as Lt, ONBOARDING_EVENT as M, METRIC as Mn, messageFromUnknown as Mr, toForwardSlashes as Mt, SETUP_HINT_EVENT as N, MINIMUM_VTE_VERSION_FOR_HYPERLINKS as Nn, remainingDeadlineBudgetMs as Nr, DOCTOR_PACKAGE_NAME as Nt, ACTION_UPGRADE_EVENT as O, writeJsonErrorReport as On, isSameRuleKey as Or, prompts as Ot, detectLaunchableAgents as P, SENTRY_FLUSH_TIMEOUT_MS as Pn, resolveScanTarget as Pr, findNearestPackageDirectory as Pt, formatDiagnosticSite as Q, DIAGNOSTIC_CATEGORY_BUCKETS as Qn, OVERCOMMIT_CONFIG_FILE as Qt, computeDefaultSelectedAgents as R, TUI_REPORT_DETAIL_WIDTH_FRACTION as Rn, yieldToEventLoop as Rr, isSpinnerSilent as Rt, openUrl as S, isEnvironmentError as Sn, hasReactRuntime as Sr, readReactDoctorWorkflow as St, recordGate as T, isJsonModeActive as Tn, isPathInsideDirectory as Tr, workflowUsesV1Action as Tt, deduplicateProjectScans as U, isCiEnvironment as Un, runPromise as Ur, stripAnsi as Ut, formatSkippedProjectsMessage as V, isGitHookEnvironment as Vn, gen as Vr, isFullWidth as Vt, createInvocationInspect as W, isCiOrCodingAgentEnvironment as Wn, runSync as Wr, isUnicodeSupported as Wt, indentMultilineText as X, COMPILER_CLEANUP_RULE_KEYS as Xn, LEGACY_HOOK_RUNNER_RELATIVE_PATH as Xt, printFooter as Y, COMPILER_CLEANUP_BUCKET as Yn, LEFTHOOK_CONFIG_FILES as Yt, printDiagnosticsDump as Z, CONFIG_SCHEMA_URL as Zn, NON_BLOCKING_REACT_DOCTOR_COMMAND as Zt, resolveCliInspectOptions as _, cliLogger as _n, getCategoryImpact as _r, resolveBlockingLevel as _t, shouldFailScanGate as a, getPackageJsonPath as an, STAGED_FILES_PROJECT_CONFIG_FILENAMES as ar, buildRulePriorityMap as at, setUpGitHubActions as b, isExpectedUserError as bn, getEquivalentRuleKeys as br, installReactDoctorWorkflow as bt, selectProjects as c, packageHasNestedRecordKey as cn, buildRuleDocsUrl as cr, formatLearnMoreLine as ct, resolveProjectChangedLineRanges as d, resolveGitPath as dn, detectAiTrainingEnvironment as dr, recordRunTraceId as dt, REACT_DOCTOR_COMMAND as en, Git as er, buildEmptyReportMessage as et, resolveProjectDiffIncludePaths as f, runGit as fn, discoverReactSubprojects as fr, finalizeScope as ft, resolveWorkspaceDeadCodeOwner as g, runCommand as gn, getBaselineDiffPlan as gr, isValidBlockingLevel as gt, retryMissingProjectScores as h, detectDefaultBranch as hn, formatReactDoctorError as hr, warnDeprecatedDiff as ht, validateModeFlags as i, ensureTrailingNewline as in, SKILL_NAME as ir, hasLintHardFailure as it, INSTALL_AGENTS_PREFERENCE_ID as j, CI_TRUST_COMPANIES as jn, mapWithConcurrency as jr, stageWorkflowFile as jt, CI_PITCH_EVENT as k, writeJsonReport as kn, listWorkspacePackages as kr, unrefStdin as kt, buildCodeFrame as l, packageHasRecordKey as ln, calculateScore as lr, getActiveRunTrace as lt, resolveProjectRelativeDirectory as m, writeJsonFile as mn, formatErrorChain as mr, resolveScope as mt, registerActiveTuiRenderer as n, SIMPLE_GIT_HOOKS_PACKAGE_JSON_KEY as nn, Progress as nr, buildNoScoreMessage as nt, projectManifestChanged as o, isRecord as on, StagedFiles as or, buildSortedRuleGroups as ot, resolveProjectSourceFilePaths as p, runGitRaw as pn, findLegacyConfig as pr, isScopeValue as pt, pluralize as q, CANONICAL_GITHUB_URL as qn, provide$2 as qr, HOOK_RELATIVE_PATH as qt, validateIncludeUntrackedScope as r, VITE_PLUS_HOOKS_PATH as rn, Reporter as rr, resolveMergeBaseRef as rt, discoverWorkspacePackages as s, packageHasDependency as sn, buildJsonReport as sr, formatFixRecipeLine as st, exitGracefully as t, SIMPLE_GIT_HOOKS_CONFIG_FILE as tn, LEGACY_CONFIG_FILENAME as tr, hasIncompleteScoreAnalysis as tt, resolveProjectScan as u, readPackageJson as un, clearConfigCache as ur, getLastRunTraceId as ut, filterScansForSurface as v, flushSentry as vn, getChangedLineRanges as vr, buildWorkflowContent as vt, readPreference as w, enableJsonMode as wn, isErrnoException as wr, upgradeWorkflowActionToV2 as wt, reportWorkflowResult as x, formatEnvironmentError as xn, hasPublishedFixRecipe as xr, isReactDoctorWorkflowInstalled as xt, countUniqueScannedFiles as y, initializeSentry as yn, getDiffInfo as yr, getReactDoctorWorkflowPath as yt, detectAvailableAgents as z, CliInputError as zn, error$1 as zr, setSpinnerSilent as zt };
84769
84175
 
84770
- //# sourceMappingURL=exit-gracefully-BN2OivAV.js.map
84771
- //# debugId=07705956-10ec-5419-acea-25f57c6a08c5
84176
+ //# sourceMappingURL=exit-gracefully-Du1RQd8v.js.map
84177
+ //# debugId=ebadd5f4-f19b-552c-b2da-27e8355f567a