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.
- package/README.md +0 -2
- package/dist/cli.js +60 -166
- package/dist/{exit-gracefully-BN2OivAV.js → exit-gracefully-Du1RQd8v.js} +451 -1045
- package/dist/index.d.ts +4 -4
- package/dist/index.js +70 -33
- package/dist/lsp.js +72 -35
- package/dist/{run-scan-app-_1xMQtKK.js → run-scan-app-ukc4pcdk.js} +127 -45
- package/package.json +6 -6
|
@@ -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]="
|
|
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
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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
|
|
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
|
|
69559
|
+
return await resolveKnipConfigExport(KNIP_DATA_CONFIG_FILENAMES.has(configFilename) ? readJson5File(filePath) : await importDefaultExport(jiti, filePath));
|
|
69542
69560
|
} catch {
|
|
69543
|
-
return
|
|
69561
|
+
return null;
|
|
69544
69562
|
}
|
|
69545
69563
|
};
|
|
69546
|
-
const
|
|
69547
|
-
const
|
|
69548
|
-
|
|
69549
|
-
|
|
69550
|
-
|
|
69551
|
-
|
|
69552
|
-
|
|
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 = (
|
|
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(
|
|
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 = (
|
|
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 = [
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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/
|
|
82453
|
-
const
|
|
82454
|
-
|
|
82455
|
-
|
|
82456
|
-
const
|
|
82457
|
-
|
|
82458
|
-
|
|
82459
|
-
const
|
|
82460
|
-
|
|
82461
|
-
const
|
|
82462
|
-
|
|
82463
|
-
|
|
82464
|
-
|
|
82465
|
-
|
|
82466
|
-
|
|
82467
|
-
|
|
82468
|
-
|
|
82469
|
-
|
|
82470
|
-
|
|
82471
|
-
|
|
82472
|
-
|
|
82473
|
-
|
|
82474
|
-
|
|
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 `${
|
|
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/
|
|
82493
|
-
|
|
82494
|
-
|
|
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
|
-
|
|
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
|
|
82527
|
+
return /* @__PURE__ */ new Set();
|
|
82517
82528
|
}
|
|
82518
|
-
|
|
82519
|
-
|
|
82520
|
-
|
|
82521
|
-
|
|
82522
|
-
|
|
82523
|
-
|
|
82524
|
-
|
|
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/
|
|
83408
|
-
const
|
|
83409
|
-
const
|
|
83410
|
-
|
|
83411
|
-
|
|
83412
|
-
|
|
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/
|
|
83427
|
-
const buildShareUrl = (
|
|
83428
|
-
const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
83429
|
-
const warningCount = diagnostics.
|
|
83430
|
-
const affectedFileCount = collectAffectedFiles(diagnostics).size;
|
|
83431
|
-
const
|
|
83432
|
-
|
|
83433
|
-
if (
|
|
83434
|
-
if (
|
|
83435
|
-
if (
|
|
83436
|
-
|
|
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
|
|
82645
|
+
const shareUrl = input.isOffline ? null : buildShareUrl(input);
|
|
83444
82646
|
for (const line of buildFooterLinkLines({ shareUrl })) yield* log(line);
|
|
83445
82647
|
});
|
|
83446
|
-
|
|
83447
|
-
|
|
83448
|
-
|
|
83449
|
-
|
|
83450
|
-
|
|
83451
|
-
|
|
83452
|
-
|
|
83453
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
84090
|
-
|
|
84091
|
-
|
|
84092
|
-
|
|
84093
|
-
|
|
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
|
-
|
|
84134
|
-
|
|
84135
|
-
|
|
83320
|
+
projectName: project.projectName,
|
|
83321
|
+
scannedFileCount,
|
|
83322
|
+
scoreResult: hasSkippedChecks ? null : score,
|
|
83323
|
+
skippedChecks
|
|
84136
83324
|
});
|
|
84137
|
-
if (
|
|
84138
|
-
|
|
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:
|
|
83331
|
+
diagnostics: printedDiagnostics,
|
|
84145
83332
|
scoreResult: score,
|
|
84146
83333
|
projectName: project.projectName,
|
|
84147
|
-
isOffline: !
|
|
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
|
|
84147
|
+
const preserveActiveTuiRendererOutput = () => {
|
|
84742
84148
|
const renderer = activeTuiRenderer;
|
|
84743
84149
|
activeTuiRenderer = null;
|
|
84744
|
-
renderer?.
|
|
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
|
-
|
|
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 {
|
|
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-
|
|
84771
|
-
//# debugId=
|
|
84176
|
+
//# sourceMappingURL=exit-gracefully-Du1RQd8v.js.map
|
|
84177
|
+
//# debugId=ebadd5f4-f19b-552c-b2da-27e8355f567a
|