react-doctor 0.5.6-dev.5b742fa → 0.5.6-dev.5d1347e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +399 -250
- package/dist/index.js +95 -76
- package/dist/lsp.js +114 -98
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
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]="37f4074d-83ec-54d9-a9f3-5a8967531f6b")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -14,7 +14,7 @@ import * as OS from "node:os";
|
|
|
14
14
|
import os, { tmpdir } from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
16
16
|
import * as NodeUrl from "node:url";
|
|
17
|
-
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
18
18
|
import { createJiti } from "jiti";
|
|
19
19
|
import * as Crypto from "node:crypto";
|
|
20
20
|
import crypto, { createHash, randomUUID } from "node:crypto";
|
|
@@ -35893,6 +35893,7 @@ const isLargeMinifiedFile = (absolutePath) => {
|
|
|
35893
35893
|
if (sizeBytes < 2e4) return false;
|
|
35894
35894
|
return isMinifiedSource(absolutePath);
|
|
35895
35895
|
};
|
|
35896
|
+
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
35896
35897
|
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
35897
35898
|
"EACCES",
|
|
35898
35899
|
"EPERM",
|
|
@@ -35902,11 +35903,7 @@ const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
|
35902
35903
|
"ELOOP",
|
|
35903
35904
|
"ENAMETOOLONG"
|
|
35904
35905
|
]);
|
|
35905
|
-
const isIgnorableReaddirError = (error) =>
|
|
35906
|
-
if (typeof error !== "object" || error === null) return false;
|
|
35907
|
-
const errorCode = error.code;
|
|
35908
|
-
return typeof errorCode === "string" && IGNORABLE_READDIR_ERROR_CODES.has(errorCode);
|
|
35909
|
-
};
|
|
35906
|
+
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
35910
35907
|
const readDirectoryEntries = (directoryPath) => {
|
|
35911
35908
|
try {
|
|
35912
35909
|
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
@@ -35953,7 +35950,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
|
|
|
35953
35950
|
return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
35954
35951
|
} catch (error) {
|
|
35955
35952
|
if (error instanceof SyntaxError) return {};
|
|
35956
|
-
if (error
|
|
35953
|
+
if (isErrnoException(error)) {
|
|
35957
35954
|
const { code } = error;
|
|
35958
35955
|
if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
|
|
35959
35956
|
}
|
|
@@ -36678,17 +36675,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
36678
36675
|
return false;
|
|
36679
36676
|
};
|
|
36680
36677
|
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
36681
|
-
const
|
|
36682
|
-
const spec = packageJson.dependencies?.
|
|
36678
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
36679
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
36683
36680
|
return typeof spec === "string" ? spec : null;
|
|
36684
36681
|
};
|
|
36685
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson,
|
|
36682
|
+
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
36686
36683
|
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
36687
|
-
const
|
|
36688
|
-
const spec = packageJson.dependencies?.["@shopify/flash-list"] ?? packageJson.devDependencies?.["@shopify/flash-list"] ?? packageJson.peerDependencies?.["@shopify/flash-list"] ?? packageJson.optionalDependencies?.["@shopify/flash-list"];
|
|
36689
|
-
return typeof spec === "string" ? spec : null;
|
|
36690
|
-
};
|
|
36691
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getShopifyFlashListDependencySpec);
|
|
36684
|
+
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
36692
36685
|
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
36693
36686
|
if (version === null || !isCatalogReference(version)) return version;
|
|
36694
36687
|
const catalogName = extractCatalogName(version);
|
|
@@ -36700,11 +36693,7 @@ const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson,
|
|
|
36700
36693
|
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
36701
36694
|
return resolveCatalogVersion(readPackageJson$1(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
36702
36695
|
};
|
|
36703
|
-
const
|
|
36704
|
-
const spec = packageJson.dependencies?.next ?? packageJson.devDependencies?.next ?? packageJson.peerDependencies?.next ?? packageJson.optionalDependencies?.next;
|
|
36705
|
-
return typeof spec === "string" ? spec : null;
|
|
36706
|
-
};
|
|
36707
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getNextjsDependencySpec);
|
|
36696
|
+
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
36708
36697
|
const getPreactVersion = (packageJson) => {
|
|
36709
36698
|
return {
|
|
36710
36699
|
...packageJson.peerDependencies,
|
|
@@ -36793,6 +36782,11 @@ const ES_TARGET_YEAR_BY_NAME = {
|
|
|
36793
36782
|
esnext: 9999
|
|
36794
36783
|
};
|
|
36795
36784
|
/**
|
|
36785
|
+
* tsconfig filenames probed when resolving a project's TypeScript
|
|
36786
|
+
* compiler options — the root config first, then a monorepo base config.
|
|
36787
|
+
*/
|
|
36788
|
+
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
36789
|
+
/**
|
|
36796
36790
|
* Project-config files that `StagedFiles.materialize` copies into
|
|
36797
36791
|
* the temp directory alongside staged sources so oxlint resolves
|
|
36798
36792
|
* `tsconfig` / `package.json` / lint configs the same way it would
|
|
@@ -37314,6 +37308,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
37314
37308
|
if (detected.major !== required.major) return detected.major > required.major;
|
|
37315
37309
|
return detected.minor >= required.minor;
|
|
37316
37310
|
};
|
|
37311
|
+
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
37317
37312
|
var InvalidGlobPatternError = class extends Error {
|
|
37318
37313
|
pattern;
|
|
37319
37314
|
reason;
|
|
@@ -37342,7 +37337,7 @@ const compileGlobPattern = (rawPattern) => {
|
|
|
37342
37337
|
try {
|
|
37343
37338
|
return import_picomatch.default.makeRe(normalizeGlobPattern(rawPattern), PICOMATCH_OPTIONS);
|
|
37344
37339
|
} catch (caughtError) {
|
|
37345
|
-
throw new InvalidGlobPatternError(rawPattern,
|
|
37340
|
+
throw new InvalidGlobPatternError(rawPattern, messageFromUnknown(caughtError));
|
|
37346
37341
|
}
|
|
37347
37342
|
};
|
|
37348
37343
|
const compileGlobPatternsLenient = (patterns, onInvalid) => {
|
|
@@ -38520,7 +38515,6 @@ const PACKAGE_JSON_FILENAME = "package.json";
|
|
|
38520
38515
|
const PACKAGE_JSON_CONFIG_KEY$1 = "reactDoctor";
|
|
38521
38516
|
const LEGACY_CONFIG_FILENAME = "react-doctor.config.json";
|
|
38522
38517
|
const jiti = createJiti(import.meta.url);
|
|
38523
|
-
const formatError = (error) => error instanceof Error ? error.message : String(error);
|
|
38524
38518
|
const importDefaultExport = async (jitiInstance, filePath) => {
|
|
38525
38519
|
const imported = await jitiInstance.import(filePath);
|
|
38526
38520
|
return imported?.default ?? imported;
|
|
@@ -38552,7 +38546,7 @@ const loadModuleConfig = async (filePath) => {
|
|
|
38552
38546
|
try {
|
|
38553
38547
|
return await importDefaultExport(aliasJiti, filePath);
|
|
38554
38548
|
} catch (retryError) {
|
|
38555
|
-
throw new Error(`${
|
|
38549
|
+
throw new Error(`${messageFromUnknown(error)} (retry with ${SELF_PACKAGE_IMPORT_SPECIFIER} aliased to the running react-doctor package also failed: ${messageFromUnknown(retryError)})`, { cause: retryError });
|
|
38556
38550
|
}
|
|
38557
38551
|
}
|
|
38558
38552
|
};
|
|
@@ -38601,7 +38595,7 @@ const loadLegacyConfig = (directory) => {
|
|
|
38601
38595
|
}
|
|
38602
38596
|
warn(`${LEGACY_CONFIG_FILENAME} must contain an object, ignoring.`);
|
|
38603
38597
|
} catch (error) {
|
|
38604
|
-
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${
|
|
38598
|
+
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${messageFromUnknown(error)}`);
|
|
38605
38599
|
}
|
|
38606
38600
|
return {
|
|
38607
38601
|
status: "invalid",
|
|
@@ -38628,7 +38622,7 @@ const loadConfigFromDirectory = async (directory) => {
|
|
|
38628
38622
|
warn(`${CONFIG_BASENAME}.${extension} must export an object, ignoring.`);
|
|
38629
38623
|
sawBrokenConfigFile = true;
|
|
38630
38624
|
} catch (error) {
|
|
38631
|
-
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${
|
|
38625
|
+
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${messageFromUnknown(error)}`);
|
|
38632
38626
|
sawBrokenConfigFile = true;
|
|
38633
38627
|
}
|
|
38634
38628
|
}
|
|
@@ -39692,15 +39686,10 @@ const buildCapabilities = (project) => {
|
|
|
39692
39686
|
}
|
|
39693
39687
|
if (project.tailwindVersion !== null) {
|
|
39694
39688
|
capabilities.add("tailwind");
|
|
39695
|
-
|
|
39696
|
-
if (isTailwindAtLeast(tailwind, {
|
|
39689
|
+
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
39697
39690
|
major: 3,
|
|
39698
39691
|
minor: 4
|
|
39699
39692
|
})) capabilities.add("tailwind:3.4");
|
|
39700
|
-
if (tailwind !== null && isTailwindAtLeast(tailwind, {
|
|
39701
|
-
major: 4,
|
|
39702
|
-
minor: 0
|
|
39703
|
-
})) capabilities.add("tailwind:4");
|
|
39704
39693
|
}
|
|
39705
39694
|
if (project.zodVersion !== null) {
|
|
39706
39695
|
capabilities.add("zod");
|
|
@@ -39908,7 +39897,7 @@ const readIgnoreFile = (filePath) => {
|
|
|
39908
39897
|
try {
|
|
39909
39898
|
content = NFS.readFileSync(filePath, "utf-8");
|
|
39910
39899
|
} catch (error) {
|
|
39911
|
-
const errnoCode = error
|
|
39900
|
+
const errnoCode = isErrnoException(error) ? error.code : void 0;
|
|
39912
39901
|
if (errnoCode && errnoCode !== "ENOENT") runSync(warn$1(`Could not read ignore file ${filePath}: ${errnoCode}`));
|
|
39913
39902
|
return [];
|
|
39914
39903
|
}
|
|
@@ -39946,8 +39935,8 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
39946
39935
|
cachedPatternsByRoot.set(rootDirectory, patterns);
|
|
39947
39936
|
return patterns;
|
|
39948
39937
|
};
|
|
39938
|
+
const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39949
39939
|
const KNIP_JSON_FILENAME = "knip.json";
|
|
39950
|
-
const isRecord$1$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39951
39940
|
const readJsonFileSafe = (filePath) => {
|
|
39952
39941
|
let rawContents;
|
|
39953
39942
|
try {
|
|
@@ -39963,10 +39952,10 @@ const readJsonFileSafe = (filePath) => {
|
|
|
39963
39952
|
};
|
|
39964
39953
|
const readKnipConfig = (rootDirectory) => {
|
|
39965
39954
|
const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
|
|
39966
|
-
if (isRecord$
|
|
39955
|
+
if (isRecord$2(knipJson)) return knipJson;
|
|
39967
39956
|
const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
|
|
39968
|
-
const packageKnipConfig = isRecord$
|
|
39969
|
-
return isRecord$
|
|
39957
|
+
const packageKnipConfig = isRecord$2(packageJson) ? packageJson.knip : null;
|
|
39958
|
+
return isRecord$2(packageKnipConfig) ? packageKnipConfig : null;
|
|
39970
39959
|
};
|
|
39971
39960
|
const normalizePatternList = (value) => {
|
|
39972
39961
|
if (typeof value === "string" && value.length > 0) return [value];
|
|
@@ -39978,10 +39967,10 @@ const prefixWorkspacePatterns = (workspacePattern, patterns) => {
|
|
|
39978
39967
|
return patterns.map((pattern) => pattern.startsWith("!") ? `!${normalizedWorkspacePattern}/${pattern.slice(1)}` : `${normalizedWorkspacePattern}/${pattern}`);
|
|
39979
39968
|
};
|
|
39980
39969
|
const collectKnipWorkspacePatterns = (workspaces, settingName) => {
|
|
39981
|
-
if (!isRecord$
|
|
39970
|
+
if (!isRecord$2(workspaces)) return [];
|
|
39982
39971
|
const patterns = [];
|
|
39983
39972
|
for (const [workspacePattern, workspaceConfig] of Object.entries(workspaces)) {
|
|
39984
|
-
if (!isRecord$
|
|
39973
|
+
if (!isRecord$2(workspaceConfig)) continue;
|
|
39985
39974
|
patterns.push(...prefixWorkspacePatterns(workspacePattern, normalizePatternList(workspaceConfig[settingName])));
|
|
39986
39975
|
}
|
|
39987
39976
|
return patterns;
|
|
@@ -40026,8 +40015,6 @@ const toCanonicalPath = (filePath) => {
|
|
|
40026
40015
|
};
|
|
40027
40016
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
40028
40017
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
40029
|
-
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
40030
|
-
const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40031
40018
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
40032
40019
|
const inputChunks = [];
|
|
40033
40020
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -40085,7 +40072,7 @@ process.stdin.on("end", () => {
|
|
|
40085
40072
|
});
|
|
40086
40073
|
`;
|
|
40087
40074
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
40088
|
-
for (const filename of TSCONFIG_FILENAMES
|
|
40075
|
+
for (const filename of TSCONFIG_FILENAMES) {
|
|
40089
40076
|
const candidate = Path.join(rootDirectory, filename);
|
|
40090
40077
|
if (NFS.existsSync(candidate)) return candidate;
|
|
40091
40078
|
}
|
|
@@ -40466,15 +40453,13 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
40466
40453
|
})()) }));
|
|
40467
40454
|
static layerOf = (diagnostics) => succeed$3(DeadCode, DeadCode.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40468
40455
|
};
|
|
40469
|
-
const createNodeReadFileLinesSync = (rootDirectory) => {
|
|
40470
|
-
|
|
40471
|
-
|
|
40472
|
-
|
|
40473
|
-
|
|
40474
|
-
|
|
40475
|
-
|
|
40476
|
-
}
|
|
40477
|
-
};
|
|
40456
|
+
const createNodeReadFileLinesSync = (rootDirectory) => (filePath) => {
|
|
40457
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
|
|
40458
|
+
try {
|
|
40459
|
+
return NFS.readFileSync(absolutePath, "utf-8").split("\n");
|
|
40460
|
+
} catch {
|
|
40461
|
+
return null;
|
|
40462
|
+
}
|
|
40478
40463
|
};
|
|
40479
40464
|
var Files = class Files extends Service()("react-doctor/Files") {
|
|
40480
40465
|
static layerNode = succeed$3(Files, Files.of({
|
|
@@ -40685,7 +40670,10 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40685
40670
|
directory: input.directory,
|
|
40686
40671
|
cause
|
|
40687
40672
|
}) });
|
|
40688
|
-
})
|
|
40673
|
+
}), withSpan("git.exec", { attributes: {
|
|
40674
|
+
"git.command": input.command,
|
|
40675
|
+
"git.subcommand": input.args[0] ?? ""
|
|
40676
|
+
} }));
|
|
40689
40677
|
const runGit = (directory, args) => runCommand({
|
|
40690
40678
|
command: "git",
|
|
40691
40679
|
args,
|
|
@@ -40713,7 +40701,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40713
40701
|
]);
|
|
40714
40702
|
if (candidates.status !== 0) return null;
|
|
40715
40703
|
return trimOrNull(candidates.stdout.split("\n")[0] ?? "");
|
|
40716
|
-
});
|
|
40704
|
+
}).pipe(withSpan("Git.defaultBranch"));
|
|
40717
40705
|
const branchExists = (directory, branch) => runGit(directory, [
|
|
40718
40706
|
"rev-parse",
|
|
40719
40707
|
"--verify",
|
|
@@ -40760,7 +40748,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40760
40748
|
const result = resultOption.value;
|
|
40761
40749
|
if (result.status !== 0) return null;
|
|
40762
40750
|
return parseGithubViewerPermission(result.stdout);
|
|
40763
|
-
}).pipe(catch_$1(() => succeed$2(null)));
|
|
40751
|
+
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.githubViewerPermission"));
|
|
40764
40752
|
/**
|
|
40765
40753
|
* Resolves a `--diff A..B` / `A...B` commit range into a changed-file
|
|
40766
40754
|
* selection. Each endpoint is validated with `isSafeGitRevision`
|
|
@@ -40874,7 +40862,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40874
40862
|
changedFiles: splitNullSeparated(diff.stdout),
|
|
40875
40863
|
isCurrentChanges: false
|
|
40876
40864
|
};
|
|
40877
|
-
}),
|
|
40865
|
+
}).pipe(withSpan("Git.diffSelection")),
|
|
40878
40866
|
stagedFilePaths: (directory) => runGit(directory, [
|
|
40879
40867
|
"diff",
|
|
40880
40868
|
"--cached",
|
|
@@ -40916,7 +40904,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40916
40904
|
status: result.status,
|
|
40917
40905
|
stdout: result.stdout
|
|
40918
40906
|
};
|
|
40919
|
-
}),
|
|
40907
|
+
}).pipe(withSpan("Git.grep")),
|
|
40920
40908
|
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
40921
40909
|
if (files.length === 0) return [];
|
|
40922
40910
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
@@ -40932,7 +40920,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40932
40920
|
]);
|
|
40933
40921
|
if (result.status !== 0) return null;
|
|
40934
40922
|
return parseChangedLineRanges(result.stdout);
|
|
40935
|
-
})
|
|
40923
|
+
}).pipe(withSpan("Git.changedLineRanges"))
|
|
40936
40924
|
});
|
|
40937
40925
|
})).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
|
|
40938
40926
|
/**
|
|
@@ -41147,7 +41135,7 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
|
|
|
41147
41135
|
for (const [absolutePath, originalContent] of originalContents) try {
|
|
41148
41136
|
NFS.writeFileSync(absolutePath, originalContent);
|
|
41149
41137
|
} catch (error) {
|
|
41150
|
-
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${
|
|
41138
|
+
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
|
|
41151
41139
|
}
|
|
41152
41140
|
};
|
|
41153
41141
|
const onExit = () => restore();
|
|
@@ -41253,7 +41241,7 @@ const resolveUserPlugin = (spec, configSourceDirectory) => {
|
|
|
41253
41241
|
try {
|
|
41254
41242
|
resolvedSpecifier = isRelative ? Path.resolve(configSourceDirectory, spec) : candidateRequire.resolve(spec);
|
|
41255
41243
|
} catch (error) {
|
|
41256
|
-
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${
|
|
41244
|
+
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${messageFromUnknown(error)}`);
|
|
41257
41245
|
return null;
|
|
41258
41246
|
}
|
|
41259
41247
|
const { name, ruleNames } = readPluginShape(resolvedSpecifier, (target) => candidateRequire(target));
|
|
@@ -41325,8 +41313,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
41325
41313
|
}
|
|
41326
41314
|
return enabled;
|
|
41327
41315
|
};
|
|
41328
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
|
|
41329
|
-
const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
41316
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
|
|
41317
|
+
const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
41330
41318
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
41331
41319
|
const jsPlugins = [];
|
|
41332
41320
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
@@ -41386,7 +41374,6 @@ const resolveOxlintBinary = () => {
|
|
|
41386
41374
|
return Path.join(oxlintPackageDirectory, "bin", "oxlint");
|
|
41387
41375
|
};
|
|
41388
41376
|
const resolvePluginPath = () => esmRequire.resolve("oxlint-plugin-react-doctor");
|
|
41389
|
-
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
41390
41377
|
const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
41391
41378
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
41392
41379
|
return null;
|
|
@@ -41758,7 +41745,7 @@ const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
|
|
|
41758
41745
|
const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
|
|
41759
41746
|
let currentNode = identifier.parent;
|
|
41760
41747
|
while (currentNode) {
|
|
41761
|
-
if (
|
|
41748
|
+
if (isScopeBoundary(currentNode)) {
|
|
41762
41749
|
if (scopeContainsNonImportBinding(currentNode, currentNode, identifier.text)) return true;
|
|
41763
41750
|
}
|
|
41764
41751
|
if (currentNode === sourceFile) return false;
|
|
@@ -41849,11 +41836,10 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
|
|
|
41849
41836
|
});
|
|
41850
41837
|
return resolution;
|
|
41851
41838
|
};
|
|
41852
|
-
const isScopeNode = isScopeBoundary;
|
|
41853
41839
|
const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, visitedDeclarations = /* @__PURE__ */ new Set()) => {
|
|
41854
41840
|
let currentNode = identifier.parent;
|
|
41855
41841
|
while (currentNode) {
|
|
41856
|
-
if (
|
|
41842
|
+
if (isScopeBoundary(currentNode)) {
|
|
41857
41843
|
const resolution = findResolutionInScope(currentNode, identifier.text, reactImportBindings, sourceFile, visitedDeclarations);
|
|
41858
41844
|
if (resolution) return resolution;
|
|
41859
41845
|
}
|
|
@@ -42023,9 +42009,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
42023
42009
|
try {
|
|
42024
42010
|
parsed = JSON.parse(sanitizedStdout);
|
|
42025
42011
|
} catch {
|
|
42026
|
-
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0,
|
|
42012
|
+
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
42027
42013
|
}
|
|
42028
|
-
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0,
|
|
42014
|
+
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
42029
42015
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
42030
42016
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
42031
42017
|
const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
@@ -42101,7 +42087,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
42101
42087
|
child.kill("SIGKILL");
|
|
42102
42088
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
42103
42089
|
kind: "timeout",
|
|
42104
|
-
detail: `${spawnTimeoutMs /
|
|
42090
|
+
detail: `${spawnTimeoutMs / MILLISECONDS_PER_SECOND}s budget exceeded`
|
|
42105
42091
|
}) }));
|
|
42106
42092
|
}, spawnTimeoutMs);
|
|
42107
42093
|
timeoutHandle.unref?.();
|
|
@@ -42316,6 +42302,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
42316
42302
|
NFS.closeSync(fileHandle);
|
|
42317
42303
|
}
|
|
42318
42304
|
};
|
|
42305
|
+
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
42306
|
+
/**
|
|
42307
|
+
* Detects an oxlint config-load crash caused by the optional
|
|
42308
|
+
* `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
|
|
42309
|
+
* builds the partial-failure note for it; returns `null` when the failure
|
|
42310
|
+
* was anything else.
|
|
42311
|
+
*
|
|
42312
|
+
* oxlint prints a framed error to stdout (not stderr) and exits non-zero
|
|
42313
|
+
* when a `jsPlugins` entry can't be imported; that non-JSON stdout
|
|
42314
|
+
* surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
|
|
42315
|
+
* config load on it, leaving the plugin in would drop every curated
|
|
42316
|
+
* react-doctor diagnostic too — so the caller retries with the plugin
|
|
42317
|
+
* stripped (issue #833). Both markers sit at the start of oxlint's
|
|
42318
|
+
* message, so they survive the `preview` slice even for deep pnpm paths.
|
|
42319
|
+
*/
|
|
42320
|
+
const reactHooksJsPluginDropNote = (error) => {
|
|
42321
|
+
if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
|
|
42322
|
+
const { preview } = error.reason;
|
|
42323
|
+
if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
|
|
42324
|
+
const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
|
|
42325
|
+
return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
|
|
42326
|
+
};
|
|
42319
42327
|
/**
|
|
42320
42328
|
* The oxlint runner. Composed of three pieces in `runners/oxlint/`:
|
|
42321
42329
|
*
|
|
@@ -42343,15 +42351,16 @@ const runOxlint = async (options) => {
|
|
|
42343
42351
|
const pluginPath = resolvePluginPath();
|
|
42344
42352
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
42345
42353
|
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
42346
|
-
const buildConfig = (
|
|
42354
|
+
const buildConfig = (overrides) => createOxlintConfig({
|
|
42347
42355
|
pluginPath,
|
|
42348
42356
|
project,
|
|
42349
42357
|
customRulesOnly,
|
|
42350
|
-
extendsPaths:
|
|
42358
|
+
extendsPaths: overrides.extendsPaths,
|
|
42351
42359
|
ignoredTags,
|
|
42352
42360
|
serverAuthFunctionNames,
|
|
42353
42361
|
severityControls,
|
|
42354
|
-
userPlugins
|
|
42362
|
+
userPlugins,
|
|
42363
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
42355
42364
|
});
|
|
42356
42365
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
42357
42366
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -42387,12 +42396,22 @@ const runOxlint = async (options) => {
|
|
|
42387
42396
|
outputMaxBytes,
|
|
42388
42397
|
concurrency: options.concurrency
|
|
42389
42398
|
});
|
|
42390
|
-
writeOxlintConfig(configPath, buildConfig(extendsPaths));
|
|
42399
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
42391
42400
|
try {
|
|
42392
42401
|
return await runBatches();
|
|
42393
42402
|
} catch (error) {
|
|
42403
|
+
const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
|
|
42404
|
+
if (reactHooksJsDropNote !== null) {
|
|
42405
|
+
writeOxlintConfig(configPath, buildConfig({
|
|
42406
|
+
extendsPaths,
|
|
42407
|
+
disableReactHooksJsPlugin: true
|
|
42408
|
+
}));
|
|
42409
|
+
const diagnostics = await runBatches();
|
|
42410
|
+
onPartialFailure?.(reactHooksJsDropNote);
|
|
42411
|
+
return diagnostics;
|
|
42412
|
+
}
|
|
42394
42413
|
if (extendsPaths.length === 0) throw error;
|
|
42395
|
-
writeOxlintConfig(configPath, buildConfig([]));
|
|
42414
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
|
|
42396
42415
|
return await runBatches();
|
|
42397
42416
|
}
|
|
42398
42417
|
} finally {
|
|
@@ -43190,7 +43209,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
43190
43209
|
}))))))));
|
|
43191
43210
|
const deadCodeFailureState = yield* get$2(deadCodeFailure);
|
|
43192
43211
|
const scanElapsedMilliseconds = Date.now() - scanStartTime;
|
|
43193
|
-
const scanElapsedSeconds = (scanElapsedMilliseconds /
|
|
43212
|
+
const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
|
|
43194
43213
|
if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
|
|
43195
43214
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
43196
43215
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
@@ -43427,7 +43446,7 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
|
|
|
43427
43446
|
static layerNode = effect(StagedFiles, gen(function* () {
|
|
43428
43447
|
const git = yield* Git;
|
|
43429
43448
|
return StagedFiles.of({
|
|
43430
|
-
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile))),
|
|
43449
|
+
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile)), withSpan("StagedFiles.discoverSourceFiles")),
|
|
43431
43450
|
materialize: ({ directory, stagedFiles, tempDirectory }) => materializeSourceTree({
|
|
43432
43451
|
directory,
|
|
43433
43452
|
files: stagedFiles,
|
|
@@ -43437,7 +43456,7 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
|
|
|
43437
43456
|
tempDirectory: tree.tempDirectory,
|
|
43438
43457
|
stagedFiles: tree.materializedFiles,
|
|
43439
43458
|
cleanup: tree.cleanup
|
|
43440
|
-
})))
|
|
43459
|
+
})), withSpan("StagedFiles.materialize"))
|
|
43441
43460
|
});
|
|
43442
43461
|
}));
|
|
43443
43462
|
/**
|
|
@@ -43844,7 +43863,7 @@ const FALSY_CI_FLAG_VALUES = new Set([
|
|
|
43844
43863
|
"false"
|
|
43845
43864
|
]);
|
|
43846
43865
|
const isCiFlagSet = (value) => value !== void 0 && !FALSY_CI_FLAG_VALUES.has(value.toLowerCase());
|
|
43847
|
-
const isCiEnvironment = () => CI_ENVIRONMENT_VARIABLES.some((environmentVariable) => Boolean(
|
|
43866
|
+
const isCiEnvironment = (env = process.env) => CI_ENVIRONMENT_VARIABLES.some((environmentVariable) => Boolean(env[environmentVariable])) || isCiFlagSet(env.CI);
|
|
43848
43867
|
const detectCiProvider = () => {
|
|
43849
43868
|
for (const [environmentVariable, provider] of CI_PROVIDER_BY_ENVIRONMENT_VARIABLE) if (process.env[environmentVariable]) return provider;
|
|
43850
43869
|
return isCiFlagSet(process.env.CI) ? "unknown" : null;
|
|
@@ -43869,6 +43888,42 @@ const detectCodingAgent = () => {
|
|
|
43869
43888
|
const isCodingAgentEnvironment = () => detectCodingAgent() !== null;
|
|
43870
43889
|
const isCiOrCodingAgentEnvironment = () => isCiEnvironment() || isCodingAgentEnvironment();
|
|
43871
43890
|
//#endregion
|
|
43891
|
+
//#region src/cli/utils/detect-terminal-kind.ts
|
|
43892
|
+
const TERMINAL_BY_TERM_PROGRAM = [
|
|
43893
|
+
["vscode", "vscode"],
|
|
43894
|
+
["iTerm.app", "iterm"],
|
|
43895
|
+
["Apple_Terminal", "apple-terminal"],
|
|
43896
|
+
["WezTerm", "wezterm"],
|
|
43897
|
+
["ghostty", "ghostty"],
|
|
43898
|
+
["Hyper", "hyper"],
|
|
43899
|
+
["Tabby", "tabby"],
|
|
43900
|
+
["rio", "rio"]
|
|
43901
|
+
];
|
|
43902
|
+
/**
|
|
43903
|
+
* Best-effort label for the terminal emulator / editor hosting the CLI,
|
|
43904
|
+
* derived from terminal-identity env vars. Recorded as the `terminalKind` run
|
|
43905
|
+
* tag so we can see where React Doctor is actually run (nvim, VS Code, iTerm,
|
|
43906
|
+
* …) — the split Sentry can't otherwise see. Low-cardinality and free of any
|
|
43907
|
+
* username/path/secret, so it's safe as a tag. Editor terminals (nvim/vim)
|
|
43908
|
+
* win over the outer emulator because that's the surface a user is reading in;
|
|
43909
|
+
* "ci" marks a run with no interactive terminal; "unknown" when nothing matches.
|
|
43910
|
+
*/
|
|
43911
|
+
const detectTerminalKind = (env = process.env) => {
|
|
43912
|
+
if (env.NVIM) return "neovim";
|
|
43913
|
+
if (env.VIM_TERMINAL) return "vim";
|
|
43914
|
+
const termProgram = env.TERM_PROGRAM;
|
|
43915
|
+
if (termProgram) {
|
|
43916
|
+
for (const [marker, label] of TERMINAL_BY_TERM_PROGRAM) if (termProgram === marker) return label;
|
|
43917
|
+
}
|
|
43918
|
+
if (env.KITTY_WINDOW_ID || env.TERM === "xterm-kitty") return "kitty";
|
|
43919
|
+
if (env.WT_SESSION) return "windows-terminal";
|
|
43920
|
+
if (env.ALACRITTY_WINDOW_ID || env.TERM === "alacritty") return "alacritty";
|
|
43921
|
+
if (env.VTE_VERSION) return "vte";
|
|
43922
|
+
if (env.TMUX) return "tmux";
|
|
43923
|
+
if (isCiEnvironment(env)) return "ci";
|
|
43924
|
+
return "unknown";
|
|
43925
|
+
};
|
|
43926
|
+
//#endregion
|
|
43872
43927
|
//#region src/cli/utils/is-git-hook-environment.ts
|
|
43873
43928
|
const isGitHookEnvironment = () => Boolean(process.env.GIT_DIR);
|
|
43874
43929
|
//#endregion
|
|
@@ -43891,6 +43946,7 @@ const NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
|
|
|
43891
43946
|
const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some((envVariable) => Boolean(process.env[envVariable])) || isCodingAgentEnvironment();
|
|
43892
43947
|
//#endregion
|
|
43893
43948
|
//#region src/cli/utils/constants.ts
|
|
43949
|
+
const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
|
|
43894
43950
|
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
43895
43951
|
const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
|
|
43896
43952
|
const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
|
|
@@ -43975,7 +44031,7 @@ const makeNoopConsole = () => ({
|
|
|
43975
44031
|
});
|
|
43976
44032
|
//#endregion
|
|
43977
44033
|
//#region src/cli/utils/version.ts
|
|
43978
|
-
const VERSION = "0.5.6-dev.
|
|
44034
|
+
const VERSION = "0.5.6-dev.5d1347e";
|
|
43979
44035
|
//#endregion
|
|
43980
44036
|
//#region src/cli/utils/json-mode.ts
|
|
43981
44037
|
let context = null;
|
|
@@ -44125,6 +44181,7 @@ const buildRunContext = () => {
|
|
|
44125
44181
|
viaAction: isOfficialGithubAction(),
|
|
44126
44182
|
codingAgent: detectCodingAgent(),
|
|
44127
44183
|
interactive: !isNonInteractiveEnvironment(),
|
|
44184
|
+
terminalKind: detectTerminalKind(),
|
|
44128
44185
|
jsonMode: isJsonModeActive(),
|
|
44129
44186
|
invokedVia: detectInvokedVia()
|
|
44130
44187
|
};
|
|
@@ -44195,6 +44252,7 @@ const buildSentryScope = (runContext = buildRunContext()) => {
|
|
|
44195
44252
|
viaAction: runContext.viaAction,
|
|
44196
44253
|
codingAgent: runContext.codingAgent,
|
|
44197
44254
|
interactive: runContext.interactive,
|
|
44255
|
+
terminalKind: runContext.terminalKind,
|
|
44198
44256
|
jsonMode: runContext.jsonMode,
|
|
44199
44257
|
invokedVia: runContext.invokedVia,
|
|
44200
44258
|
nodeMajor: runContext.nodeMajor
|
|
@@ -44333,13 +44391,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
44333
44391
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
44334
44392
|
* standard `SENTRY_RELEASE` override.
|
|
44335
44393
|
*/
|
|
44336
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.
|
|
44394
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.5d1347e`;
|
|
44337
44395
|
/**
|
|
44338
44396
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
44339
44397
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
44340
44398
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
44341
44399
|
*/
|
|
44342
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.
|
|
44400
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.5d1347e") ? "development" : "production");
|
|
44343
44401
|
/**
|
|
44344
44402
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
44345
44403
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -48196,6 +48254,15 @@ const boxText = (content, innerWidth) => {
|
|
|
48196
48254
|
].join("\n");
|
|
48197
48255
|
};
|
|
48198
48256
|
//#endregion
|
|
48257
|
+
//#region src/cli/utils/resolve-absolute-path.ts
|
|
48258
|
+
/**
|
|
48259
|
+
* Resolves a diagnostic's `filePath` (relative to its project root, or
|
|
48260
|
+
* already absolute) to an absolute path. Shared by the code-frame reader and
|
|
48261
|
+
* the terminal hyperlink builder so both turn a relative path into the same
|
|
48262
|
+
* on-disk location.
|
|
48263
|
+
*/
|
|
48264
|
+
const resolveAbsolutePath = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
48265
|
+
//#endregion
|
|
48199
48266
|
//#region src/cli/utils/build-code-frame.ts
|
|
48200
48267
|
/**
|
|
48201
48268
|
* Renders a syntax-highlighted source excerpt around a diagnostic site
|
|
@@ -48206,7 +48273,7 @@ const boxText = (content, innerWidth) => {
|
|
|
48206
48273
|
*/
|
|
48207
48274
|
const buildCodeFrame = (input) => {
|
|
48208
48275
|
if (input.line <= 0) return null;
|
|
48209
|
-
const absolutePath =
|
|
48276
|
+
const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
|
|
48210
48277
|
let source;
|
|
48211
48278
|
try {
|
|
48212
48279
|
source = NFS.readFileSync(absolutePath, "utf8");
|
|
@@ -48246,6 +48313,16 @@ const resolveMeasureWidth = (reservedColumns = 0) => resolveClampedWidth({
|
|
|
48246
48313
|
const DIVIDER_INDENT = " ";
|
|
48247
48314
|
const buildSectionDivider = () => highlighter.dim(`${DIVIDER_INDENT}${"─".repeat(resolveMeasureWidth(2))}`);
|
|
48248
48315
|
//#endregion
|
|
48316
|
+
//#region src/cli/utils/format-hyperlink.ts
|
|
48317
|
+
const OSC = "\x1B]";
|
|
48318
|
+
const ST = "\x1B\\";
|
|
48319
|
+
/**
|
|
48320
|
+
* Wraps `text` in an OSC 8 hyperlink pointing at `uri`. The visible characters
|
|
48321
|
+
* are exactly `text`; the link is carried in escape sequences a capable
|
|
48322
|
+
* terminal turns into a click target.
|
|
48323
|
+
*/
|
|
48324
|
+
const formatHyperlink = (text, uri) => `${OSC}8;;${uri}${ST}${text}${OSC}8;;${ST}`;
|
|
48325
|
+
//#endregion
|
|
48249
48326
|
//#region src/cli/utils/indent-multiline-text.ts
|
|
48250
48327
|
const indentMultilineText = (text, linePrefix) => text.split("\n").map((lineText) => `${linePrefix}${lineText}`).join("\n");
|
|
48251
48328
|
//#endregion
|
|
@@ -48399,17 +48476,23 @@ const clusterNearbyDiagnostics = (diagnostics) => {
|
|
|
48399
48476
|
}
|
|
48400
48477
|
return clusters;
|
|
48401
48478
|
};
|
|
48402
|
-
const
|
|
48479
|
+
const formatClusterLocationText = (cluster) => {
|
|
48480
|
+
const { filePath } = cluster.diagnostics[0];
|
|
48481
|
+
if (cluster.startLine <= 0) return filePath;
|
|
48482
|
+
if (cluster.endLine > cluster.startLine) return `${filePath}:${cluster.startLine}-${cluster.endLine}`;
|
|
48483
|
+
return `${filePath}:${cluster.startLine}`;
|
|
48484
|
+
};
|
|
48485
|
+
const formatClusterLocation = (cluster, resolveSourceRoot, hyperlinks) => {
|
|
48403
48486
|
const lead = cluster.diagnostics[0];
|
|
48404
48487
|
const contextTag = formatFileContextTag(lead);
|
|
48405
|
-
|
|
48406
|
-
if (
|
|
48407
|
-
return `${lead.filePath
|
|
48488
|
+
const location = formatClusterLocationText(cluster);
|
|
48489
|
+
if (!hyperlinks) return `${location}${contextTag}`;
|
|
48490
|
+
return `${formatHyperlink(location, pathToFileURL(resolveAbsolutePath(lead.filePath, resolveSourceRoot(lead))).href)}${contextTag}`;
|
|
48408
48491
|
};
|
|
48409
|
-
const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame) => {
|
|
48492
|
+
const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame, hyperlinks) => {
|
|
48410
48493
|
const lead = cluster.diagnostics[0];
|
|
48411
48494
|
const isMultiSite = cluster.diagnostics.length > 1;
|
|
48412
|
-
const lines = ["", highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${formatClusterLocation(cluster)}`)];
|
|
48495
|
+
const lines = ["", highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${formatClusterLocation(cluster, resolveSourceRoot, hyperlinks)}`)];
|
|
48413
48496
|
const codeFrame = renderCodeFrame ? buildCodeFrame({
|
|
48414
48497
|
filePath: lead.filePath,
|
|
48415
48498
|
line: cluster.startLine,
|
|
@@ -48428,7 +48511,7 @@ const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame
|
|
|
48428
48511
|
}
|
|
48429
48512
|
return lines;
|
|
48430
48513
|
};
|
|
48431
|
-
const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, renderEverySite, isAgentEnvironment) => {
|
|
48514
|
+
const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, renderEverySite, isAgentEnvironment, hyperlinks) => {
|
|
48432
48515
|
const representative = pickRepresentativeDiagnostic(ruleDiagnostics);
|
|
48433
48516
|
const { severity } = representative;
|
|
48434
48517
|
const trailingBadge = formatTrailingSiteBadge(ruleDiagnostics.length);
|
|
@@ -48448,7 +48531,7 @@ const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, rende
|
|
|
48448
48531
|
}
|
|
48449
48532
|
const renderCodeFrame = severity === "error";
|
|
48450
48533
|
const sites = renderEverySite ? ruleDiagnostics : [representative];
|
|
48451
|
-
if (!(isCollapsedWarningGroup && representative.help.includes(representative.filePath))) for (const cluster of clusterNearbyDiagnostics(sites)) lines.push(...buildDiagnosticClusterLines(cluster, resolveSourceRoot, renderCodeFrame));
|
|
48534
|
+
if (!(isCollapsedWarningGroup && representative.help.includes(representative.filePath))) for (const cluster of clusterNearbyDiagnostics(sites)) lines.push(...buildDiagnosticClusterLines(cluster, resolveSourceRoot, renderCodeFrame, hyperlinks));
|
|
48452
48535
|
return lines;
|
|
48453
48536
|
};
|
|
48454
48537
|
const selectErrorRuleGroups = (diagnostics, rulePriority) => buildSortedRuleGroups(diagnostics.filter((diagnostic) => diagnostic.severity === "error"), rulePriority);
|
|
@@ -48461,7 +48544,7 @@ const buildOverflowSummaryLine = (diagnostics, rulePriority) => {
|
|
|
48461
48544
|
return ` ${highlighter.dim("Run")} ${command} ${highlighter.dim("to list every error and warning")}`;
|
|
48462
48545
|
};
|
|
48463
48546
|
const getTopErrorRuleKeys = (diagnostics, limit, rulePriority) => new Set(selectTopErrorRuleGroups(diagnostics, limit, rulePriority).map(([ruleKey]) => ruleKey));
|
|
48464
|
-
const buildTopErrorsSection = (diagnostics, resolveSourceRoot, rulePriority) => {
|
|
48547
|
+
const buildTopErrorsSection = (diagnostics, resolveSourceRoot, hyperlinks, rulePriority) => {
|
|
48465
48548
|
const topRuleGroups = selectErrorRuleGroups(diagnostics, rulePriority).slice(0, 3);
|
|
48466
48549
|
if (topRuleGroups.length === 0) return {
|
|
48467
48550
|
lines: [],
|
|
@@ -48471,7 +48554,7 @@ const buildTopErrorsSection = (diagnostics, resolveSourceRoot, rulePriority) =>
|
|
|
48471
48554
|
const blockOffsets = [];
|
|
48472
48555
|
for (const [ruleKey, ruleDiagnostics] of topRuleGroups) {
|
|
48473
48556
|
blockOffsets.push(lines.length);
|
|
48474
|
-
lines.push(...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, false, false));
|
|
48557
|
+
lines.push(...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, false, false, hyperlinks));
|
|
48475
48558
|
lines.push("");
|
|
48476
48559
|
}
|
|
48477
48560
|
return {
|
|
@@ -48509,18 +48592,18 @@ const buildOverviewHeaderLines = (diagnostics) => {
|
|
|
48509
48592
|
* single Effect.forEach over Console.log so failures or fiber
|
|
48510
48593
|
* interruption produce predictable partial output.
|
|
48511
48594
|
*/
|
|
48512
|
-
const printDiagnostics = (diagnostics, isVerbose, sourceRoot, rulePriority, isAgentEnvironment = false, onboarding = {}) => gen(function* () {
|
|
48595
|
+
const printDiagnostics = (diagnostics, isVerbose, sourceRoot, rulePriority, isAgentEnvironment = false, onboarding = {}, hyperlinks = false) => gen(function* () {
|
|
48513
48596
|
const sectionPause = onboarding.sectionPause ?? void_;
|
|
48514
48597
|
const animateCountUp = onboarding.animateCountUp ?? false;
|
|
48515
48598
|
const resolveSourceRoot = typeof sourceRoot === "function" ? sourceRoot : () => sourceRoot;
|
|
48516
48599
|
let detailLines;
|
|
48517
48600
|
let topErrorBlockOffsets = [];
|
|
48518
48601
|
if (!isVerbose) {
|
|
48519
|
-
const topErrors = buildTopErrorsSection(diagnostics, resolveSourceRoot, rulePriority);
|
|
48602
|
+
const topErrors = buildTopErrorsSection(diagnostics, resolveSourceRoot, hyperlinks, rulePriority);
|
|
48520
48603
|
detailLines = topErrors.lines;
|
|
48521
48604
|
topErrorBlockOffsets = topErrors.blockOffsets;
|
|
48522
48605
|
} else detailLines = buildSortedRuleGroups(diagnostics, rulePriority).flatMap(([ruleKey, ruleDiagnostics]) => {
|
|
48523
|
-
return [...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, true, isAgentEnvironment), ""];
|
|
48606
|
+
return [...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, true, isAgentEnvironment, hyperlinks), ""];
|
|
48524
48607
|
});
|
|
48525
48608
|
const overflowLine = isVerbose ? void 0 : buildOverflowSummaryLine(diagnostics, rulePriority);
|
|
48526
48609
|
const categoryTallies = buildCategoryDiagnosticGroups(diagnostics, rulePriority).map(buildCategoryTally);
|
|
@@ -48581,6 +48664,48 @@ const computeProjectedScore = async (topErrorSource, rescoreSource, currentScore
|
|
|
48581
48664
|
//#endregion
|
|
48582
48665
|
//#region src/cli/utils/filter-diagnostics-by-categories.ts
|
|
48583
48666
|
const filterDiagnosticsByCategories = (diagnostics, categories) => categories.size === 0 ? [...diagnostics] : diagnostics.filter((diagnostic) => categories.has(diagnostic.category));
|
|
48667
|
+
//#endregion
|
|
48668
|
+
//#region src/cli/utils/supports-hyperlinks.ts
|
|
48669
|
+
const HYPERLINK_CAPABLE_TERM_PROGRAMS = new Set([
|
|
48670
|
+
"iTerm.app",
|
|
48671
|
+
"WezTerm",
|
|
48672
|
+
"vscode",
|
|
48673
|
+
"Hyper",
|
|
48674
|
+
"ghostty",
|
|
48675
|
+
"Tabby",
|
|
48676
|
+
"rio"
|
|
48677
|
+
]);
|
|
48678
|
+
const parseVteVersion = (raw) => {
|
|
48679
|
+
const parsed = Number.parseInt(raw ?? "", 10);
|
|
48680
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
48681
|
+
};
|
|
48682
|
+
/**
|
|
48683
|
+
* Whether `stream` is a terminal that renders OSC 8 hyperlinks. Auto-detected
|
|
48684
|
+
* from terminal-identity env vars; the de-facto `FORCE_HYPERLINK` env var
|
|
48685
|
+
* overrides detection (`FORCE_HYPERLINK=0`/`false` forces off, any other value
|
|
48686
|
+
* forces on), mirroring how the ecosystem's terminal libraries gate the same
|
|
48687
|
+
* feature. Off for non-TTYs, `TERM=dumb`, and CI (whose log viewers render the
|
|
48688
|
+
* raw escape rather than a link). Unknown terminals default to off.
|
|
48689
|
+
*/
|
|
48690
|
+
const supportsHyperlinks = (stream = process.stdout, env = process.env) => {
|
|
48691
|
+
const forced = env.FORCE_HYPERLINK;
|
|
48692
|
+
if (forced !== void 0 && forced !== "") return forced !== "0" && forced.toLowerCase() !== "false";
|
|
48693
|
+
if (stream.isTTY !== true) return false;
|
|
48694
|
+
if (env.TERM === "dumb") return false;
|
|
48695
|
+
if (isCiEnvironment(env)) return false;
|
|
48696
|
+
if (env.WT_SESSION) return true;
|
|
48697
|
+
if (env.KITTY_WINDOW_ID || env.TERM === "xterm-kitty") return true;
|
|
48698
|
+
if (parseVteVersion(env.VTE_VERSION) >= 5e3) return true;
|
|
48699
|
+
return Boolean(env.TERM_PROGRAM && HYPERLINK_CAPABLE_TERM_PROGRAMS.has(env.TERM_PROGRAM));
|
|
48700
|
+
};
|
|
48701
|
+
//#endregion
|
|
48702
|
+
//#region src/cli/utils/should-render-hyperlinks.ts
|
|
48703
|
+
/**
|
|
48704
|
+
* Whether to emit OSC 8 clickable `file:line` locations for this run: a
|
|
48705
|
+
* hyperlink-capable terminal AND not a coding agent (whose output parsers
|
|
48706
|
+
* would choke on the escape sequences).
|
|
48707
|
+
*/
|
|
48708
|
+
const shouldRenderHyperlinks = (stream = process.stdout) => supportsHyperlinks(stream) && !isCodingAgentEnvironment();
|
|
48584
48709
|
const FORCE_ONBOARDING_ENV_VAR = "REACT_DOCTOR_FORCE_ONBOARDING";
|
|
48585
48710
|
const FALSY_FLAG_VALUES = new Set([
|
|
48586
48711
|
"",
|
|
@@ -48600,10 +48725,9 @@ const canAnimateOnboarding = (stream = process.stdout) => {
|
|
|
48600
48725
|
};
|
|
48601
48726
|
//#endregion
|
|
48602
48727
|
//#region src/cli/utils/onboarding-state.ts
|
|
48603
|
-
const GLOBAL_CONFIG_PROJECT_NAME$2 = "react-doctor";
|
|
48604
48728
|
const ONBOARDED_AT_KEY = "onboardedAt";
|
|
48605
48729
|
const getOnboardingStore = (options = {}) => new Conf({
|
|
48606
|
-
projectName:
|
|
48730
|
+
projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
|
|
48607
48731
|
cwd: options.cwd
|
|
48608
48732
|
});
|
|
48609
48733
|
const hasCompletedOnboarding = (options = {}) => {
|
|
@@ -49059,6 +49183,78 @@ const resolveCliCategories = (categoryFlag) => {
|
|
|
49059
49183
|
return resolvedCategories.length > 0 ? resolvedCategories : void 0;
|
|
49060
49184
|
};
|
|
49061
49185
|
//#endregion
|
|
49186
|
+
//#region src/cli/utils/git-hook-shared.ts
|
|
49187
|
+
const HOOK_FILE_NAME = "pre-commit";
|
|
49188
|
+
const HOOK_RELATIVE_PATH = "hooks/pre-commit";
|
|
49189
|
+
const LEGACY_HOOK_RUNNER_RELATIVE_PATH = ".react-doctor/hooks/pre-commit";
|
|
49190
|
+
const HUSKY_HOOKS_PATH = ".husky";
|
|
49191
|
+
const VITE_PLUS_HOOKS_PATH = ".vite-hooks";
|
|
49192
|
+
const LEFTHOOK_CONFIG_FILES = ["lefthook.yml", "lefthook.yaml"];
|
|
49193
|
+
const PRE_COMMIT_CONFIG_FILE = ".pre-commit-config.yaml";
|
|
49194
|
+
const OVERCOMMIT_CONFIG_FILE = ".overcommit.yml";
|
|
49195
|
+
const REACT_DOCTOR_COMMAND = "react-doctor --staged --blocking warning";
|
|
49196
|
+
const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
49197
|
+
"react_doctor_output=$(mktemp \"${TMPDIR:-/tmp}/react-doctor-hook.XXXXXX\");",
|
|
49198
|
+
`if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
|
|
49199
|
+
"rm -f \"$react_doctor_output\";",
|
|
49200
|
+
"else",
|
|
49201
|
+
"rm -f \"$react_doctor_output\";",
|
|
49202
|
+
`printf "%s\\n" "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2;`,
|
|
49203
|
+
"fi"
|
|
49204
|
+
].join(" ");
|
|
49205
|
+
const PACKAGE_JSON_FILE_NAME = "package.json";
|
|
49206
|
+
const runGit = (projectRoot, args) => {
|
|
49207
|
+
try {
|
|
49208
|
+
return execFileSync("git", [...args], {
|
|
49209
|
+
cwd: projectRoot,
|
|
49210
|
+
encoding: "utf8",
|
|
49211
|
+
stdio: [
|
|
49212
|
+
"ignore",
|
|
49213
|
+
"pipe",
|
|
49214
|
+
"ignore"
|
|
49215
|
+
]
|
|
49216
|
+
}).trim();
|
|
49217
|
+
} catch {
|
|
49218
|
+
return null;
|
|
49219
|
+
}
|
|
49220
|
+
};
|
|
49221
|
+
const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
|
|
49222
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49223
|
+
const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
|
|
49224
|
+
const readPackageJson = (projectRoot) => {
|
|
49225
|
+
try {
|
|
49226
|
+
return JSON.parse(NFS.readFileSync(getPackageJsonPath(projectRoot), "utf8"));
|
|
49227
|
+
} catch {
|
|
49228
|
+
return null;
|
|
49229
|
+
}
|
|
49230
|
+
};
|
|
49231
|
+
const writeJsonFile$1 = (filePath, value) => {
|
|
49232
|
+
NFS.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
49233
|
+
};
|
|
49234
|
+
const packageHasDependency = (projectRoot, dependencyName) => {
|
|
49235
|
+
const packageJson = readPackageJson(projectRoot);
|
|
49236
|
+
if (!isRecord$1(packageJson)) return false;
|
|
49237
|
+
return [
|
|
49238
|
+
"dependencies",
|
|
49239
|
+
"devDependencies",
|
|
49240
|
+
"optionalDependencies"
|
|
49241
|
+
].some((fieldName) => {
|
|
49242
|
+
const dependencies = packageJson[fieldName];
|
|
49243
|
+
return isRecord$1(dependencies) && typeof dependencies[dependencyName] === "string";
|
|
49244
|
+
});
|
|
49245
|
+
};
|
|
49246
|
+
const packageHasRecordKey = (projectRoot, key) => {
|
|
49247
|
+
const packageJson = readPackageJson(projectRoot);
|
|
49248
|
+
return isRecord$1(packageJson) && isRecord$1(packageJson[key]);
|
|
49249
|
+
};
|
|
49250
|
+
const packageHasNestedRecordKey = (projectRoot, key, nestedKey) => {
|
|
49251
|
+
const packageJson = readPackageJson(projectRoot);
|
|
49252
|
+
if (!isRecord$1(packageJson)) return false;
|
|
49253
|
+
const value = packageJson[key];
|
|
49254
|
+
return isRecord$1(value) && isRecord$1(value[nestedKey]);
|
|
49255
|
+
};
|
|
49256
|
+
const ensureTrailingNewline = (content) => content.endsWith("\n") ? content : `${content}\n`;
|
|
49257
|
+
//#endregion
|
|
49062
49258
|
//#region src/cli/utils/scan-result-cache.ts
|
|
49063
49259
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
49064
49260
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
@@ -49069,7 +49265,7 @@ const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
|
49069
49265
|
"eslint-plugin-react-hooks/package.json"
|
|
49070
49266
|
];
|
|
49071
49267
|
const bundledRequire = createRequire(import.meta.url);
|
|
49072
|
-
const isRecord
|
|
49268
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49073
49269
|
const normalizeForStableJson = (value) => {
|
|
49074
49270
|
if (value === null) return null;
|
|
49075
49271
|
if (value === void 0) return void 0;
|
|
@@ -49098,24 +49294,9 @@ const stringifyStableJson = (value) => {
|
|
|
49098
49294
|
}
|
|
49099
49295
|
};
|
|
49100
49296
|
const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
|
|
49101
|
-
const
|
|
49102
|
-
try {
|
|
49103
|
-
return execFileSync("git", [...args], {
|
|
49104
|
-
cwd: directory,
|
|
49105
|
-
encoding: "utf8",
|
|
49106
|
-
stdio: [
|
|
49107
|
-
"ignore",
|
|
49108
|
-
"pipe",
|
|
49109
|
-
"ignore"
|
|
49110
|
-
]
|
|
49111
|
-
}).trim();
|
|
49112
|
-
} catch {
|
|
49113
|
-
return null;
|
|
49114
|
-
}
|
|
49115
|
-
};
|
|
49116
|
-
const readHeadSha = (projectDirectory) => runGit$1(projectDirectory, ["rev-parse", "HEAD"]);
|
|
49297
|
+
const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
|
|
49117
49298
|
const isWorktreeClean = (projectDirectory) => {
|
|
49118
|
-
const status = runGit
|
|
49299
|
+
const status = runGit(projectDirectory, [
|
|
49119
49300
|
"status",
|
|
49120
49301
|
"--porcelain=v1",
|
|
49121
49302
|
"--untracked-files=normal"
|
|
@@ -49123,7 +49304,7 @@ const isWorktreeClean = (projectDirectory) => {
|
|
|
49123
49304
|
return status !== null && status.length === 0;
|
|
49124
49305
|
};
|
|
49125
49306
|
const hasHiddenTrackedFileState = (projectDirectory) => {
|
|
49126
|
-
const output = runGit
|
|
49307
|
+
const output = runGit(projectDirectory, ["ls-files", "-v"]);
|
|
49127
49308
|
if (output === null) return true;
|
|
49128
49309
|
return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
|
|
49129
49310
|
};
|
|
@@ -49136,7 +49317,7 @@ const resolveCacheFilePath = (projectDirectory) => {
|
|
|
49136
49317
|
const readPersistedCache = (cacheFilePath) => {
|
|
49137
49318
|
try {
|
|
49138
49319
|
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
49139
|
-
if (!isRecord
|
|
49320
|
+
if (!isRecord(parsed) || parsed.version !== 1) return {
|
|
49140
49321
|
version: 1,
|
|
49141
49322
|
entries: []
|
|
49142
49323
|
};
|
|
@@ -49146,8 +49327,8 @@ const readPersistedCache = (cacheFilePath) => {
|
|
|
49146
49327
|
};
|
|
49147
49328
|
const entries = [];
|
|
49148
49329
|
for (const entry of parsed.entries) {
|
|
49149
|
-
if (!isRecord
|
|
49150
|
-
if (!isRecord
|
|
49330
|
+
if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.createdAtMs !== "number") continue;
|
|
49331
|
+
if (!isRecord(entry.payload) || !Array.isArray(entry.payload.diagnostics)) continue;
|
|
49151
49332
|
entries.push(entry);
|
|
49152
49333
|
}
|
|
49153
49334
|
return {
|
|
@@ -49679,6 +49860,7 @@ const finalizeAndRender = (input) => gen(function* () {
|
|
|
49679
49860
|
}
|
|
49680
49861
|
const animateRender = !options.silent && !options.verbose && canAnimateOnboarding(process.stdout);
|
|
49681
49862
|
const pause = onboardingSectionPause(animateRender);
|
|
49863
|
+
const useHyperlinks = shouldRenderHyperlinks(process.stdout);
|
|
49682
49864
|
const demotedDiagnosticCount = diagnostics.length - surfaceDiagnostics.length;
|
|
49683
49865
|
const lintSourceFileCount = options.includePaths.length > 0 ? options.includePaths.length : project.sourceFileCount;
|
|
49684
49866
|
if (printedDiagnostics.length === 0) {
|
|
@@ -49704,7 +49886,7 @@ const finalizeAndRender = (input) => gen(function* () {
|
|
|
49704
49886
|
yield* printDiagnostics([...printedDiagnostics], options.verbose, directory, buildRulePriorityMap([score]), isCodingAgentEnvironment(), {
|
|
49705
49887
|
sectionPause: pause,
|
|
49706
49888
|
animateCountUp: animateRender
|
|
49707
|
-
});
|
|
49889
|
+
}, useHyperlinks);
|
|
49708
49890
|
if (options.isNonInteractiveEnvironment && options.outputSurface !== "prComment") yield* printAgentGuidance();
|
|
49709
49891
|
if (options.categoryFilters.size === 0 && demotedDiagnosticCount > 0) {
|
|
49710
49892
|
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.`));
|
|
@@ -49897,16 +50079,7 @@ const buildHandoffPayload = (input) => {
|
|
|
49897
50079
|
try {
|
|
49898
50080
|
outputDirectory = writeDiagnosticsDirectory([...input.diagnostics], input.outputDirectory);
|
|
49899
50081
|
} catch {}
|
|
49900
|
-
const lines = [
|
|
49901
|
-
"Before fixing anything, first ask me whether I'd like to add React Doctor to CI so every pull request is scanned automatically. Explain that:",
|
|
49902
|
-
`- It's used by teams at ${CI_TRUST_COMPANIES}.`,
|
|
49903
|
-
"- I don't have to fix everything at once; CI keeps new PRs clean while I incrementally pay down the existing issues.",
|
|
49904
|
-
`- Setup guide: ${CI_URL}`,
|
|
49905
|
-
"If I say yes, run `npx react-doctor install` (or add the GitHub Actions workflow). Either way, then continue with the fixes below.",
|
|
49906
|
-
"",
|
|
49907
|
-
`Fix the top ${topGroups.length} React Doctor ${topGroups.length === 1 ? "issue" : "issues"} in ${input.projectName} on this pass — leave the rest for a follow-up.`,
|
|
49908
|
-
""
|
|
49909
|
-
];
|
|
50082
|
+
const lines = [`Fix the top ${topGroups.length} React Doctor ${topGroups.length === 1 ? "issue" : "issues"} in ${input.projectName} on this pass — leave the rest for a follow-up.`, ""];
|
|
49910
50083
|
topGroups.forEach(([ruleKey, ruleDiagnostics], index) => {
|
|
49911
50084
|
const representative = ruleDiagnostics[0];
|
|
49912
50085
|
const severityLabel = representative.severity === "error" ? "ERROR" : "WARN";
|
|
@@ -49967,78 +50140,6 @@ const detectAvailableAgents = async () => {
|
|
|
49967
50140
|
return getSkillAgentTypes().filter((agent) => agent !== "universal" && detected.has(agent));
|
|
49968
50141
|
};
|
|
49969
50142
|
//#endregion
|
|
49970
|
-
//#region src/cli/utils/git-hook-shared.ts
|
|
49971
|
-
const HOOK_FILE_NAME = "pre-commit";
|
|
49972
|
-
const HOOK_RELATIVE_PATH = "hooks/pre-commit";
|
|
49973
|
-
const LEGACY_HOOK_RUNNER_RELATIVE_PATH = ".react-doctor/hooks/pre-commit";
|
|
49974
|
-
const HUSKY_HOOKS_PATH = ".husky";
|
|
49975
|
-
const VITE_PLUS_HOOKS_PATH = ".vite-hooks";
|
|
49976
|
-
const LEFTHOOK_CONFIG_FILES = ["lefthook.yml", "lefthook.yaml"];
|
|
49977
|
-
const PRE_COMMIT_CONFIG_FILE = ".pre-commit-config.yaml";
|
|
49978
|
-
const OVERCOMMIT_CONFIG_FILE = ".overcommit.yml";
|
|
49979
|
-
const REACT_DOCTOR_COMMAND = "react-doctor --staged --blocking warning";
|
|
49980
|
-
const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
49981
|
-
"react_doctor_output=$(mktemp \"${TMPDIR:-/tmp}/react-doctor-hook.XXXXXX\");",
|
|
49982
|
-
`if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
|
|
49983
|
-
"rm -f \"$react_doctor_output\";",
|
|
49984
|
-
"else",
|
|
49985
|
-
"rm -f \"$react_doctor_output\";",
|
|
49986
|
-
`printf "%s\\n" "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2;`,
|
|
49987
|
-
"fi"
|
|
49988
|
-
].join(" ");
|
|
49989
|
-
const PACKAGE_JSON_FILE_NAME = "package.json";
|
|
49990
|
-
const runGit = (projectRoot, args) => {
|
|
49991
|
-
try {
|
|
49992
|
-
return execFileSync("git", [...args], {
|
|
49993
|
-
cwd: projectRoot,
|
|
49994
|
-
encoding: "utf8",
|
|
49995
|
-
stdio: [
|
|
49996
|
-
"ignore",
|
|
49997
|
-
"pipe",
|
|
49998
|
-
"ignore"
|
|
49999
|
-
]
|
|
50000
|
-
}).trim();
|
|
50001
|
-
} catch {
|
|
50002
|
-
return null;
|
|
50003
|
-
}
|
|
50004
|
-
};
|
|
50005
|
-
const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
|
|
50006
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50007
|
-
const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
|
|
50008
|
-
const readPackageJson = (projectRoot) => {
|
|
50009
|
-
try {
|
|
50010
|
-
return JSON.parse(NFS.readFileSync(getPackageJsonPath(projectRoot), "utf8"));
|
|
50011
|
-
} catch {
|
|
50012
|
-
return null;
|
|
50013
|
-
}
|
|
50014
|
-
};
|
|
50015
|
-
const writeJsonFile$1 = (filePath, value) => {
|
|
50016
|
-
NFS.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
50017
|
-
};
|
|
50018
|
-
const packageHasDependency = (projectRoot, dependencyName) => {
|
|
50019
|
-
const packageJson = readPackageJson(projectRoot);
|
|
50020
|
-
if (!isRecord(packageJson)) return false;
|
|
50021
|
-
return [
|
|
50022
|
-
"dependencies",
|
|
50023
|
-
"devDependencies",
|
|
50024
|
-
"optionalDependencies"
|
|
50025
|
-
].some((fieldName) => {
|
|
50026
|
-
const dependencies = packageJson[fieldName];
|
|
50027
|
-
return isRecord(dependencies) && typeof dependencies[dependencyName] === "string";
|
|
50028
|
-
});
|
|
50029
|
-
};
|
|
50030
|
-
const packageHasRecordKey = (projectRoot, key) => {
|
|
50031
|
-
const packageJson = readPackageJson(projectRoot);
|
|
50032
|
-
return isRecord(packageJson) && isRecord(packageJson[key]);
|
|
50033
|
-
};
|
|
50034
|
-
const packageHasNestedRecordKey = (projectRoot, key, nestedKey) => {
|
|
50035
|
-
const packageJson = readPackageJson(projectRoot);
|
|
50036
|
-
if (!isRecord(packageJson)) return false;
|
|
50037
|
-
const value = packageJson[key];
|
|
50038
|
-
return isRecord(value) && isRecord(value[nestedKey]);
|
|
50039
|
-
};
|
|
50040
|
-
const ensureTrailingNewline = (content) => content.endsWith("\n") ? content : `${content}\n`;
|
|
50041
|
-
//#endregion
|
|
50042
50143
|
//#region src/cli/utils/install-doctor-script.ts
|
|
50043
50144
|
const DOCTOR_SCRIPT_NAME = "doctor";
|
|
50044
50145
|
const FALLBACK_DOCTOR_SCRIPT_NAME = "react-doctor";
|
|
@@ -50064,31 +50165,31 @@ const findNearestPackageDirectory = (startDirectory, stopDirectory) => {
|
|
|
50064
50165
|
};
|
|
50065
50166
|
const hasDoctorScript = (projectRoot) => {
|
|
50066
50167
|
const packageJson = readPackageJson(findNearestPackageDirectory(projectRoot) ?? projectRoot);
|
|
50067
|
-
if (!isRecord(packageJson)) return false;
|
|
50168
|
+
if (!isRecord$1(packageJson)) return false;
|
|
50068
50169
|
const scripts = packageJson.scripts;
|
|
50069
|
-
if (!isRecord(scripts)) return false;
|
|
50170
|
+
if (!isRecord$1(scripts)) return false;
|
|
50070
50171
|
return isReactDoctorScriptCommand(scripts[DOCTOR_SCRIPT_NAME]) || isReactDoctorScriptCommand(scripts[FALLBACK_DOCTOR_SCRIPT_NAME]);
|
|
50071
50172
|
};
|
|
50072
50173
|
const hasDoctorDependency = (packageJson) => DEPENDENCY_FIELD_NAMES.some((fieldName) => {
|
|
50073
50174
|
const dependencies = packageJson[fieldName];
|
|
50074
|
-
return isRecord(dependencies) && Object.hasOwn(dependencies, "react-doctor");
|
|
50175
|
+
return isRecord$1(dependencies) && Object.hasOwn(dependencies, "react-doctor");
|
|
50075
50176
|
});
|
|
50076
50177
|
const installDoctorScript = (options) => {
|
|
50077
50178
|
const packageDirectory = findNearestPackageDirectory(options.projectRoot) ?? options.projectRoot;
|
|
50078
50179
|
const packageJsonPath = getPackageJsonPath(packageDirectory);
|
|
50079
50180
|
const packageJson = readPackageJson(packageDirectory);
|
|
50080
|
-
if (!isRecord(packageJson)) return {
|
|
50181
|
+
if (!isRecord$1(packageJson)) return {
|
|
50081
50182
|
packageJsonPath,
|
|
50082
50183
|
scriptStatus: "skipped",
|
|
50083
50184
|
scriptReason: "missing-or-invalid-package-json"
|
|
50084
50185
|
};
|
|
50085
50186
|
const scripts = packageJson.scripts;
|
|
50086
50187
|
const scriptTarget = (() => {
|
|
50087
|
-
if (scripts !== void 0 && !isRecord(scripts)) return {
|
|
50188
|
+
if (scripts !== void 0 && !isRecord$1(scripts)) return {
|
|
50088
50189
|
status: "skipped",
|
|
50089
50190
|
reason: "invalid-scripts"
|
|
50090
50191
|
};
|
|
50091
|
-
const scriptRecord = isRecord(scripts) ? scripts : {};
|
|
50192
|
+
const scriptRecord = isRecord$1(scripts) ? scripts : {};
|
|
50092
50193
|
if (isReactDoctorScriptCommand(scriptRecord[DOCTOR_SCRIPT_NAME])) return {
|
|
50093
50194
|
scriptName: DOCTOR_SCRIPT_NAME,
|
|
50094
50195
|
status: "existing"
|
|
@@ -50122,7 +50223,7 @@ const installDoctorScript = (options) => {
|
|
|
50122
50223
|
if (scriptStatus === "created") writeJsonFile$1(packageJsonPath, {
|
|
50123
50224
|
...packageJson,
|
|
50124
50225
|
scripts: {
|
|
50125
|
-
...isRecord(scripts) ? scripts : {},
|
|
50226
|
+
...isRecord$1(scripts) ? scripts : {},
|
|
50126
50227
|
[scriptTarget.scriptName ?? DOCTOR_SCRIPT_NAME]: DOCTOR_SCRIPT_COMMAND
|
|
50127
50228
|
}
|
|
50128
50229
|
});
|
|
@@ -50276,38 +50377,52 @@ const upgradeReactDoctorWorkflowInPlace = (projectRoot) => {
|
|
|
50276
50377
|
//#region src/cli/utils/hash-project-root.ts
|
|
50277
50378
|
const hashProjectRoot = (projectRoot) => createHash("sha256").update(Path.resolve(projectRoot)).digest("hex");
|
|
50278
50379
|
//#endregion
|
|
50279
|
-
//#region src/cli/utils/
|
|
50280
|
-
const
|
|
50281
|
-
const
|
|
50282
|
-
|
|
50283
|
-
|
|
50284
|
-
});
|
|
50285
|
-
|
|
50286
|
-
|
|
50287
|
-
|
|
50288
|
-
|
|
50289
|
-
|
|
50290
|
-
|
|
50291
|
-
|
|
50292
|
-
};
|
|
50293
|
-
const recordActionUpgradeDecision = (projectRoot, outcome, storeOptions = {}) => {
|
|
50294
|
-
try {
|
|
50295
|
-
const store = getActionUpgradeStore(storeOptions);
|
|
50296
|
-
const upgrades = store.get("actionUpgrades", {});
|
|
50297
|
-
store.set("actionUpgrades", {
|
|
50298
|
-
...upgrades,
|
|
50299
|
-
[hashProjectRoot(projectRoot)]: {
|
|
50300
|
-
rootDirectory: Path.resolve(projectRoot),
|
|
50301
|
-
outcome,
|
|
50302
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
50380
|
+
//#region src/cli/utils/project-decision-store.ts
|
|
50381
|
+
const createProjectDecisionStore = (storeKey) => {
|
|
50382
|
+
const getStore = (options = {}) => new Conf({
|
|
50383
|
+
projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
|
|
50384
|
+
cwd: options.cwd
|
|
50385
|
+
});
|
|
50386
|
+
return {
|
|
50387
|
+
getConfigPath: (options = {}) => getStore(options).path,
|
|
50388
|
+
hasHandled: (projectRoot, options = {}) => {
|
|
50389
|
+
try {
|
|
50390
|
+
return Boolean(getStore(options).get(storeKey, {})[hashProjectRoot(projectRoot)]);
|
|
50391
|
+
} catch {
|
|
50392
|
+
return true;
|
|
50303
50393
|
}
|
|
50304
|
-
}
|
|
50305
|
-
|
|
50306
|
-
|
|
50307
|
-
|
|
50308
|
-
|
|
50394
|
+
},
|
|
50395
|
+
record: (projectRoot, outcome, options = {}) => {
|
|
50396
|
+
try {
|
|
50397
|
+
const store = getStore(options);
|
|
50398
|
+
store.set(storeKey, {
|
|
50399
|
+
...store.get(storeKey, {}),
|
|
50400
|
+
[hashProjectRoot(projectRoot)]: {
|
|
50401
|
+
rootDirectory: Path.resolve(projectRoot),
|
|
50402
|
+
outcome,
|
|
50403
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
50404
|
+
}
|
|
50405
|
+
});
|
|
50406
|
+
return true;
|
|
50407
|
+
} catch {
|
|
50408
|
+
return false;
|
|
50409
|
+
}
|
|
50410
|
+
}
|
|
50411
|
+
};
|
|
50309
50412
|
};
|
|
50310
50413
|
//#endregion
|
|
50414
|
+
//#region src/cli/utils/action-upgrade-prompt.ts
|
|
50415
|
+
const store$1 = createProjectDecisionStore("actionUpgrades");
|
|
50416
|
+
store$1.getConfigPath;
|
|
50417
|
+
const hasHandledActionUpgrade = store$1.hasHandled;
|
|
50418
|
+
const recordActionUpgradeDecision = store$1.record;
|
|
50419
|
+
//#endregion
|
|
50420
|
+
//#region src/cli/utils/ci-prompt-decision.ts
|
|
50421
|
+
const store = createProjectDecisionStore("ciPrompts");
|
|
50422
|
+
store.getConfigPath;
|
|
50423
|
+
const hasHandledCiPrompt = store.hasHandled;
|
|
50424
|
+
const recordCiPromptDecision = store.record;
|
|
50425
|
+
//#endregion
|
|
50311
50426
|
//#region src/cli/utils/open-url.ts
|
|
50312
50427
|
const resolveOpenCommand = (url) => {
|
|
50313
50428
|
if (process$1.platform === "darwin") return {
|
|
@@ -50936,13 +51051,13 @@ const installPackageJsonHook = (options, strategy) => {
|
|
|
50936
51051
|
const packageJsonPath = getPackageJsonPath(options.projectRoot);
|
|
50937
51052
|
const didHookExist = NFS.existsSync(packageJsonPath);
|
|
50938
51053
|
const packageJson = readPackageJson(options.projectRoot);
|
|
50939
|
-
const nextPackageJson = isRecord(packageJson) ? { ...packageJson } : {};
|
|
51054
|
+
const nextPackageJson = isRecord$1(packageJson) ? { ...packageJson } : {};
|
|
50940
51055
|
const parentKeys = strategy.path.slice(0, -1);
|
|
50941
51056
|
const leafKey = strategy.path[strategy.path.length - 1];
|
|
50942
51057
|
let parent = nextPackageJson;
|
|
50943
51058
|
for (const key of parentKeys) {
|
|
50944
51059
|
const existing = parent[key];
|
|
50945
|
-
const cloned = isRecord(existing) ? { ...existing } : {};
|
|
51060
|
+
const cloned = isRecord$1(existing) ? { ...existing } : {};
|
|
50946
51061
|
parent[key] = cloned;
|
|
50947
51062
|
parent = cloned;
|
|
50948
51063
|
}
|
|
@@ -51113,7 +51228,7 @@ const isHuskyProject = (projectRoot) => NFS.existsSync(Path.join(projectRoot, ".
|
|
|
51113
51228
|
const isVitePlusProject = (projectRoot) => packageHasDependency(projectRoot, "vite-plus");
|
|
51114
51229
|
const isSimpleGitHooksProject = (projectRoot) => {
|
|
51115
51230
|
const packageJson = readPackageJson(projectRoot);
|
|
51116
|
-
return isRecord(packageJson) && isRecord(packageJson["simple-git-hooks"]) || packageHasDependency(projectRoot, "simple-git-hooks") || NFS.existsSync(Path.join(projectRoot, ".simple-git-hooks.cjs"));
|
|
51231
|
+
return isRecord$1(packageJson) && isRecord$1(packageJson["simple-git-hooks"]) || packageHasDependency(projectRoot, "simple-git-hooks") || NFS.existsSync(Path.join(projectRoot, ".simple-git-hooks.cjs"));
|
|
51117
51232
|
};
|
|
51118
51233
|
const getLefthookConfigPath = (projectRoot) => {
|
|
51119
51234
|
for (const fileName of LEFTHOOK_CONFIG_FILES) {
|
|
@@ -51279,7 +51394,7 @@ const detectPackageManager = (projectRoot) => {
|
|
|
51279
51394
|
let currentDirectory = Path.resolve(projectRoot);
|
|
51280
51395
|
while (true) {
|
|
51281
51396
|
const packageJson = readPackageJson(currentDirectory);
|
|
51282
|
-
if (isRecord(packageJson) && typeof packageJson.packageManager === "string") {
|
|
51397
|
+
if (isRecord$1(packageJson) && typeof packageJson.packageManager === "string") {
|
|
51283
51398
|
const packageManagerName = packageJson.packageManager.split("@")[0];
|
|
51284
51399
|
if (packageManagerName === "pnpm" || packageManagerName === "yarn" || packageManagerName === "bun" || packageManagerName === "npm") return packageManagerName;
|
|
51285
51400
|
}
|
|
@@ -51355,12 +51470,12 @@ const isSupplyChainTrustError = (error) => {
|
|
|
51355
51470
|
const formatInstallCommand = (input) => [input.command, ...input.args].join(" ");
|
|
51356
51471
|
const installReactDoctorDependency = async (options) => {
|
|
51357
51472
|
const packageJson = readPackageJson(options.projectRoot);
|
|
51358
|
-
if (!isRecord(packageJson)) return {
|
|
51473
|
+
if (!isRecord$1(packageJson)) return {
|
|
51359
51474
|
dependencyStatus: "skipped",
|
|
51360
51475
|
dependencyReason: "missing-or-invalid-package-json"
|
|
51361
51476
|
};
|
|
51362
51477
|
if (hasDoctorDependency(packageJson)) return { dependencyStatus: "existing" };
|
|
51363
|
-
if (packageJson.devDependencies !== void 0 && !isRecord(packageJson.devDependencies)) return {
|
|
51478
|
+
if (packageJson.devDependencies !== void 0 && !isRecord$1(packageJson.devDependencies)) return {
|
|
51364
51479
|
dependencyStatus: "skipped",
|
|
51365
51480
|
dependencyReason: "invalid-dev-dependencies"
|
|
51366
51481
|
};
|
|
@@ -51524,10 +51639,12 @@ const runInstallReactDoctor = async (options = {}) => {
|
|
|
51524
51639
|
const existingWorkflow = readReactDoctorWorkflow(projectRoot);
|
|
51525
51640
|
const canInstallWorkflow = !NFS.existsSync(workflowTargetPath);
|
|
51526
51641
|
const canUpgradeWorkflow = existingWorkflow !== null && workflowUsesV1Action(existingWorkflow.content) && !hasHandledActionUpgrade(projectRoot);
|
|
51527
|
-
const
|
|
51642
|
+
const ciPromptOutcome = canInstallWorkflow && !options.yes && !skipPrompts && !hasHandledCiPrompt(projectRoot) ? await askAddToGitHubActions(prompt) : null;
|
|
51643
|
+
const shouldInstallWorkflow = canInstallWorkflow && (Boolean(options.yes) || ciPromptOutcome === "yes");
|
|
51528
51644
|
const upgradePromptOutcome = canUpgradeWorkflow && !options.yes && !skipPrompts ? await askUpgradeActionVersion(prompt) : null;
|
|
51529
51645
|
const shouldUpgradeWorkflow = canUpgradeWorkflow && (Boolean(options.yes) || upgradePromptOutcome === "yes");
|
|
51530
51646
|
if (upgradePromptOutcome === "no" && !options.dryRun) recordActionUpgradeDecision(projectRoot, "declined");
|
|
51647
|
+
if ((ciPromptOutcome === "yes" || ciPromptOutcome === "no") && !options.dryRun) recordCiPromptDecision(projectRoot, ciPromptOutcome === "yes" ? "accepted" : "declined");
|
|
51531
51648
|
const selectedAgents = skipPrompts ? detectedAgents : (await prompt({
|
|
51532
51649
|
type: "multiselect",
|
|
51533
51650
|
name: "agents",
|
|
@@ -51774,18 +51891,24 @@ const handoffToAgent = async (input) => {
|
|
|
51774
51891
|
if (!input.interactive || input.diagnostics.length === 0) return;
|
|
51775
51892
|
cliLogger.break();
|
|
51776
51893
|
const projectRootForCi = findNearestPackageDirectory(input.rootDirectory) ?? input.rootDirectory;
|
|
51777
|
-
|
|
51894
|
+
const isGitHubActionsConfigured = isReactDoctorWorkflowInstalled(projectRootForCi);
|
|
51895
|
+
if (!isGitHubActionsConfigured && !hasHandledCiPrompt(projectRootForCi)) {
|
|
51778
51896
|
const ciOutcome = await askAddToGitHubActions();
|
|
51779
51897
|
recordCount(METRIC.agentHandoff, 1, {
|
|
51780
51898
|
outcome: `ci-${ciOutcome}`,
|
|
51781
51899
|
diagnosticsCount: input.diagnostics.length
|
|
51782
51900
|
});
|
|
51783
51901
|
if (ciOutcome === "cancel") return;
|
|
51902
|
+
recordCiPromptDecision(projectRootForCi, ciOutcome === "yes" ? "accepted" : "declined");
|
|
51784
51903
|
if (ciOutcome === "yes") {
|
|
51785
51904
|
await setUpGitHubActions({ rootDirectory: input.rootDirectory });
|
|
51786
51905
|
cliLogger.break();
|
|
51787
51906
|
}
|
|
51788
|
-
} else await maybeOfferActionUpgrade(projectRootForCi);
|
|
51907
|
+
} else if (isGitHubActionsConfigured) await maybeOfferActionUpgrade(projectRootForCi);
|
|
51908
|
+
else recordCount(METRIC.agentHandoff, 1, {
|
|
51909
|
+
outcome: "ci-suppressed",
|
|
51910
|
+
diagnosticsCount: input.diagnostics.length
|
|
51911
|
+
});
|
|
51789
51912
|
const { handoffTarget } = await prompts({
|
|
51790
51913
|
type: "select",
|
|
51791
51914
|
name: "handoffTarget",
|
|
@@ -52091,7 +52214,7 @@ const printMultiProjectSummary = (input) => gen(function* () {
|
|
|
52091
52214
|
yield* log(`${highlighter.success("✔")} Scanned ${totalScannedFileCount} ${totalScannedFileCount === 1 ? "file" : "files"} in ${formatElapsedTime(totalElapsedMilliseconds)}`);
|
|
52092
52215
|
if (displayDiagnostics.length > 0) {
|
|
52093
52216
|
yield* log("");
|
|
52094
|
-
yield* printDiagnostics(displayDiagnostics, verbose, resolveDiagnosticSourceRoot, buildRulePriorityMap(completedScans.map((scan) => scan.result.score)), isCodingAgentEnvironment(), { animateCountUp: animateRender });
|
|
52217
|
+
yield* printDiagnostics(displayDiagnostics, verbose, resolveDiagnosticSourceRoot, buildRulePriorityMap(completedScans.map((scan) => scan.result.score)), isCodingAgentEnvironment(), { animateCountUp: animateRender }, shouldRenderHyperlinks(process.stdout));
|
|
52095
52218
|
}
|
|
52096
52219
|
const lowestScoredScan = findLowestScoredScan(completedScans);
|
|
52097
52220
|
const aggregateScore = lowestScoredScan?.result.score ?? null;
|
|
@@ -52129,9 +52252,8 @@ const printMultiProjectSummary = (input) => gen(function* () {
|
|
|
52129
52252
|
});
|
|
52130
52253
|
//#endregion
|
|
52131
52254
|
//#region src/cli/utils/prompt-install-setup.ts
|
|
52132
|
-
const GLOBAL_CONFIG_PROJECT_NAME = "react-doctor";
|
|
52133
52255
|
const getSetupPromptStore = (options = {}) => new Conf({
|
|
52134
|
-
projectName:
|
|
52256
|
+
projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
|
|
52135
52257
|
cwd: options.cwd
|
|
52136
52258
|
});
|
|
52137
52259
|
const getSetupPromptProjectKey = (projectRoot) => hashProjectRoot(projectRoot);
|
|
@@ -52142,6 +52264,24 @@ const hasDisabledSetupPrompt = (projectRoot, storeOptions = {}) => {
|
|
|
52142
52264
|
return false;
|
|
52143
52265
|
}
|
|
52144
52266
|
};
|
|
52267
|
+
const disableSetupPrompt = (projectRoot, storeOptions = {}) => {
|
|
52268
|
+
try {
|
|
52269
|
+
const store = getSetupPromptStore(storeOptions);
|
|
52270
|
+
const projects = store.get("projects", {});
|
|
52271
|
+
const projectKey = getSetupPromptProjectKey(projectRoot);
|
|
52272
|
+
store.set("projects", {
|
|
52273
|
+
...projects,
|
|
52274
|
+
[projectKey]: {
|
|
52275
|
+
...projects[projectKey] ?? {},
|
|
52276
|
+
rootDirectory: Path.resolve(projectRoot),
|
|
52277
|
+
setupPrompt: false
|
|
52278
|
+
}
|
|
52279
|
+
});
|
|
52280
|
+
return true;
|
|
52281
|
+
} catch {
|
|
52282
|
+
return false;
|
|
52283
|
+
}
|
|
52284
|
+
};
|
|
52145
52285
|
const resolveInstallSetupProjectRoot = (options) => {
|
|
52146
52286
|
if (options.scanDirectories.length === 0) return findNearestPackageDirectory(options.scanRoot) ?? options.scanRoot;
|
|
52147
52287
|
const packageDirectories = /* @__PURE__ */ new Set();
|
|
@@ -52548,6 +52688,14 @@ const runExplain = async (fileLineArgument, context) => {
|
|
|
52548
52688
|
const colorizedRule = colorizeRuleByDiagnostic(ruleIdentifier, diagnostic.severity);
|
|
52549
52689
|
const severityLabel = colorizeRuleByDiagnostic(diagnostic.severity, diagnostic.severity);
|
|
52550
52690
|
cliLogger.log(`${severitySymbol} ${colorizedRule} ${highlighter.dim(`(${severityLabel})`)} — ${diagnostic.message}`);
|
|
52691
|
+
const codeFrame = buildCodeFrame({
|
|
52692
|
+
filePath: diagnostic.filePath,
|
|
52693
|
+
line: diagnostic.line,
|
|
52694
|
+
column: diagnostic.column,
|
|
52695
|
+
endLine: diagnostic.endLine,
|
|
52696
|
+
rootDirectory: targetDirectory
|
|
52697
|
+
});
|
|
52698
|
+
if (codeFrame) cliLogger.log(indentMultilineText(codeFrame, " "));
|
|
52551
52699
|
if (diagnostic.category) cliLogger.dim(` Category: ${diagnostic.category}`);
|
|
52552
52700
|
if (diagnostic.help) cliLogger.dim(` ${diagnostic.help}`);
|
|
52553
52701
|
cliLogger.dim(` If this needs follow-up or looks like a false positive, open: ${buildDiagnosticIssueUrl({
|
|
@@ -52944,6 +53092,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
52944
53092
|
})) {
|
|
52945
53093
|
printAgentInstallHint();
|
|
52946
53094
|
recordCount(METRIC.agentInstallHintShown, 1);
|
|
53095
|
+
disableSetupPrompt(setupProjectRoot);
|
|
52947
53096
|
}
|
|
52948
53097
|
}
|
|
52949
53098
|
} catch (error) {
|
|
@@ -53951,4 +54100,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
53951
54100
|
export {};
|
|
53952
54101
|
|
|
53953
54102
|
//# sourceMappingURL=cli.js.map
|
|
53954
|
-
//# debugId=
|
|
54103
|
+
//# debugId=37f4074d-83ec-54d9-a9f3-5a8967531f6b
|