react-doctor 0.5.6-dev.451beeb → 0.5.6-dev.4e06b2a
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 +470 -260
- package/dist/index.d.ts +10 -0
- package/dist/index.js +96 -70
- package/dist/lsp.js +114 -92
- 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]="f029f05b-4c71-52d3-b523-0834d67de2d4")}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";
|
|
@@ -22400,6 +22400,7 @@ var JsonReportProjectEntry = class extends Class("JsonReportProjectEntry")({
|
|
|
22400
22400
|
score: Unknown,
|
|
22401
22401
|
skippedChecks: ArraySchema(String$1),
|
|
22402
22402
|
skippedCheckReasons: optional(Record$1(String$1, String$1)),
|
|
22403
|
+
scannedFileCount: optional(Number$1),
|
|
22403
22404
|
elapsedMilliseconds: Number$1
|
|
22404
22405
|
}) {};
|
|
22405
22406
|
/**
|
|
@@ -35893,6 +35894,7 @@ const isLargeMinifiedFile = (absolutePath) => {
|
|
|
35893
35894
|
if (sizeBytes < 2e4) return false;
|
|
35894
35895
|
return isMinifiedSource(absolutePath);
|
|
35895
35896
|
};
|
|
35897
|
+
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
35896
35898
|
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
35897
35899
|
"EACCES",
|
|
35898
35900
|
"EPERM",
|
|
@@ -35902,11 +35904,7 @@ const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
|
35902
35904
|
"ELOOP",
|
|
35903
35905
|
"ENAMETOOLONG"
|
|
35904
35906
|
]);
|
|
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
|
-
};
|
|
35907
|
+
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
35910
35908
|
const readDirectoryEntries = (directoryPath) => {
|
|
35911
35909
|
try {
|
|
35912
35910
|
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
@@ -35953,7 +35951,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
|
|
|
35953
35951
|
return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
35954
35952
|
} catch (error) {
|
|
35955
35953
|
if (error instanceof SyntaxError) return {};
|
|
35956
|
-
if (error
|
|
35954
|
+
if (isErrnoException(error)) {
|
|
35957
35955
|
const { code } = error;
|
|
35958
35956
|
if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
|
|
35959
35957
|
}
|
|
@@ -36678,17 +36676,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
36678
36676
|
return false;
|
|
36679
36677
|
};
|
|
36680
36678
|
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
36681
|
-
const
|
|
36682
|
-
const spec = packageJson.dependencies?.
|
|
36679
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
36680
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
36683
36681
|
return typeof spec === "string" ? spec : null;
|
|
36684
36682
|
};
|
|
36685
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson,
|
|
36683
|
+
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
36686
36684
|
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);
|
|
36685
|
+
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
36692
36686
|
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
36693
36687
|
if (version === null || !isCatalogReference(version)) return version;
|
|
36694
36688
|
const catalogName = extractCatalogName(version);
|
|
@@ -36700,11 +36694,7 @@ const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson,
|
|
|
36700
36694
|
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
36701
36695
|
return resolveCatalogVersion(readPackageJson$1(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
36702
36696
|
};
|
|
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);
|
|
36697
|
+
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
36708
36698
|
const getPreactVersion = (packageJson) => {
|
|
36709
36699
|
return {
|
|
36710
36700
|
...packageJson.peerDependencies,
|
|
@@ -36793,6 +36783,11 @@ const ES_TARGET_YEAR_BY_NAME = {
|
|
|
36793
36783
|
esnext: 9999
|
|
36794
36784
|
};
|
|
36795
36785
|
/**
|
|
36786
|
+
* tsconfig filenames probed when resolving a project's TypeScript
|
|
36787
|
+
* compiler options — the root config first, then a monorepo base config.
|
|
36788
|
+
*/
|
|
36789
|
+
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
36790
|
+
/**
|
|
36796
36791
|
* Project-config files that `StagedFiles.materialize` copies into
|
|
36797
36792
|
* the temp directory alongside staged sources so oxlint resolves
|
|
36798
36793
|
* `tsconfig` / `package.json` / lint configs the same way it would
|
|
@@ -37314,6 +37309,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
37314
37309
|
if (detected.major !== required.major) return detected.major > required.major;
|
|
37315
37310
|
return detected.minor >= required.minor;
|
|
37316
37311
|
};
|
|
37312
|
+
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
37317
37313
|
var InvalidGlobPatternError = class extends Error {
|
|
37318
37314
|
pattern;
|
|
37319
37315
|
reason;
|
|
@@ -37342,7 +37338,7 @@ const compileGlobPattern = (rawPattern) => {
|
|
|
37342
37338
|
try {
|
|
37343
37339
|
return import_picomatch.default.makeRe(normalizeGlobPattern(rawPattern), PICOMATCH_OPTIONS);
|
|
37344
37340
|
} catch (caughtError) {
|
|
37345
|
-
throw new InvalidGlobPatternError(rawPattern,
|
|
37341
|
+
throw new InvalidGlobPatternError(rawPattern, messageFromUnknown(caughtError));
|
|
37346
37342
|
}
|
|
37347
37343
|
};
|
|
37348
37344
|
const compileGlobPatternsLenient = (patterns, onInvalid) => {
|
|
@@ -38520,7 +38516,6 @@ const PACKAGE_JSON_FILENAME = "package.json";
|
|
|
38520
38516
|
const PACKAGE_JSON_CONFIG_KEY$1 = "reactDoctor";
|
|
38521
38517
|
const LEGACY_CONFIG_FILENAME = "react-doctor.config.json";
|
|
38522
38518
|
const jiti = createJiti(import.meta.url);
|
|
38523
|
-
const formatError = (error) => error instanceof Error ? error.message : String(error);
|
|
38524
38519
|
const importDefaultExport = async (jitiInstance, filePath) => {
|
|
38525
38520
|
const imported = await jitiInstance.import(filePath);
|
|
38526
38521
|
return imported?.default ?? imported;
|
|
@@ -38552,7 +38547,7 @@ const loadModuleConfig = async (filePath) => {
|
|
|
38552
38547
|
try {
|
|
38553
38548
|
return await importDefaultExport(aliasJiti, filePath);
|
|
38554
38549
|
} catch (retryError) {
|
|
38555
|
-
throw new Error(`${
|
|
38550
|
+
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
38551
|
}
|
|
38557
38552
|
}
|
|
38558
38553
|
};
|
|
@@ -38601,7 +38596,7 @@ const loadLegacyConfig = (directory) => {
|
|
|
38601
38596
|
}
|
|
38602
38597
|
warn(`${LEGACY_CONFIG_FILENAME} must contain an object, ignoring.`);
|
|
38603
38598
|
} catch (error) {
|
|
38604
|
-
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${
|
|
38599
|
+
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${messageFromUnknown(error)}`);
|
|
38605
38600
|
}
|
|
38606
38601
|
return {
|
|
38607
38602
|
status: "invalid",
|
|
@@ -38628,7 +38623,7 @@ const loadConfigFromDirectory = async (directory) => {
|
|
|
38628
38623
|
warn(`${CONFIG_BASENAME}.${extension} must export an object, ignoring.`);
|
|
38629
38624
|
sawBrokenConfigFile = true;
|
|
38630
38625
|
} catch (error) {
|
|
38631
|
-
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${
|
|
38626
|
+
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${messageFromUnknown(error)}`);
|
|
38632
38627
|
sawBrokenConfigFile = true;
|
|
38633
38628
|
}
|
|
38634
38629
|
}
|
|
@@ -39903,7 +39898,7 @@ const readIgnoreFile = (filePath) => {
|
|
|
39903
39898
|
try {
|
|
39904
39899
|
content = NFS.readFileSync(filePath, "utf-8");
|
|
39905
39900
|
} catch (error) {
|
|
39906
|
-
const errnoCode = error
|
|
39901
|
+
const errnoCode = isErrnoException(error) ? error.code : void 0;
|
|
39907
39902
|
if (errnoCode && errnoCode !== "ENOENT") runSync(warn$1(`Could not read ignore file ${filePath}: ${errnoCode}`));
|
|
39908
39903
|
return [];
|
|
39909
39904
|
}
|
|
@@ -39941,8 +39936,8 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
39941
39936
|
cachedPatternsByRoot.set(rootDirectory, patterns);
|
|
39942
39937
|
return patterns;
|
|
39943
39938
|
};
|
|
39939
|
+
const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39944
39940
|
const KNIP_JSON_FILENAME = "knip.json";
|
|
39945
|
-
const isRecord$1$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39946
39941
|
const readJsonFileSafe = (filePath) => {
|
|
39947
39942
|
let rawContents;
|
|
39948
39943
|
try {
|
|
@@ -39958,10 +39953,10 @@ const readJsonFileSafe = (filePath) => {
|
|
|
39958
39953
|
};
|
|
39959
39954
|
const readKnipConfig = (rootDirectory) => {
|
|
39960
39955
|
const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
|
|
39961
|
-
if (isRecord$
|
|
39956
|
+
if (isRecord$2(knipJson)) return knipJson;
|
|
39962
39957
|
const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
|
|
39963
|
-
const packageKnipConfig = isRecord$
|
|
39964
|
-
return isRecord$
|
|
39958
|
+
const packageKnipConfig = isRecord$2(packageJson) ? packageJson.knip : null;
|
|
39959
|
+
return isRecord$2(packageKnipConfig) ? packageKnipConfig : null;
|
|
39965
39960
|
};
|
|
39966
39961
|
const normalizePatternList = (value) => {
|
|
39967
39962
|
if (typeof value === "string" && value.length > 0) return [value];
|
|
@@ -39973,10 +39968,10 @@ const prefixWorkspacePatterns = (workspacePattern, patterns) => {
|
|
|
39973
39968
|
return patterns.map((pattern) => pattern.startsWith("!") ? `!${normalizedWorkspacePattern}/${pattern.slice(1)}` : `${normalizedWorkspacePattern}/${pattern}`);
|
|
39974
39969
|
};
|
|
39975
39970
|
const collectKnipWorkspacePatterns = (workspaces, settingName) => {
|
|
39976
|
-
if (!isRecord$
|
|
39971
|
+
if (!isRecord$2(workspaces)) return [];
|
|
39977
39972
|
const patterns = [];
|
|
39978
39973
|
for (const [workspacePattern, workspaceConfig] of Object.entries(workspaces)) {
|
|
39979
|
-
if (!isRecord$
|
|
39974
|
+
if (!isRecord$2(workspaceConfig)) continue;
|
|
39980
39975
|
patterns.push(...prefixWorkspacePatterns(workspacePattern, normalizePatternList(workspaceConfig[settingName])));
|
|
39981
39976
|
}
|
|
39982
39977
|
return patterns;
|
|
@@ -40021,8 +40016,6 @@ const toCanonicalPath = (filePath) => {
|
|
|
40021
40016
|
};
|
|
40022
40017
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
40023
40018
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
40024
|
-
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
40025
|
-
const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40026
40019
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
40027
40020
|
const inputChunks = [];
|
|
40028
40021
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -40080,7 +40073,7 @@ process.stdin.on("end", () => {
|
|
|
40080
40073
|
});
|
|
40081
40074
|
`;
|
|
40082
40075
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
40083
|
-
for (const filename of TSCONFIG_FILENAMES
|
|
40076
|
+
for (const filename of TSCONFIG_FILENAMES) {
|
|
40084
40077
|
const candidate = Path.join(rootDirectory, filename);
|
|
40085
40078
|
if (NFS.existsSync(candidate)) return candidate;
|
|
40086
40079
|
}
|
|
@@ -40461,15 +40454,13 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
40461
40454
|
})()) }));
|
|
40462
40455
|
static layerOf = (diagnostics) => succeed$3(DeadCode, DeadCode.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40463
40456
|
};
|
|
40464
|
-
const createNodeReadFileLinesSync = (rootDirectory) => {
|
|
40465
|
-
|
|
40466
|
-
|
|
40467
|
-
|
|
40468
|
-
|
|
40469
|
-
|
|
40470
|
-
|
|
40471
|
-
}
|
|
40472
|
-
};
|
|
40457
|
+
const createNodeReadFileLinesSync = (rootDirectory) => (filePath) => {
|
|
40458
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
|
|
40459
|
+
try {
|
|
40460
|
+
return NFS.readFileSync(absolutePath, "utf-8").split("\n");
|
|
40461
|
+
} catch {
|
|
40462
|
+
return null;
|
|
40463
|
+
}
|
|
40473
40464
|
};
|
|
40474
40465
|
var Files = class Files extends Service()("react-doctor/Files") {
|
|
40475
40466
|
static layerNode = succeed$3(Files, Files.of({
|
|
@@ -40680,7 +40671,10 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40680
40671
|
directory: input.directory,
|
|
40681
40672
|
cause
|
|
40682
40673
|
}) });
|
|
40683
|
-
})
|
|
40674
|
+
}), withSpan("git.exec", { attributes: {
|
|
40675
|
+
"git.command": input.command,
|
|
40676
|
+
"git.subcommand": input.args[0] ?? ""
|
|
40677
|
+
} }));
|
|
40684
40678
|
const runGit = (directory, args) => runCommand({
|
|
40685
40679
|
command: "git",
|
|
40686
40680
|
args,
|
|
@@ -40708,7 +40702,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40708
40702
|
]);
|
|
40709
40703
|
if (candidates.status !== 0) return null;
|
|
40710
40704
|
return trimOrNull(candidates.stdout.split("\n")[0] ?? "");
|
|
40711
|
-
});
|
|
40705
|
+
}).pipe(withSpan("Git.defaultBranch"));
|
|
40712
40706
|
const branchExists = (directory, branch) => runGit(directory, [
|
|
40713
40707
|
"rev-parse",
|
|
40714
40708
|
"--verify",
|
|
@@ -40755,7 +40749,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40755
40749
|
const result = resultOption.value;
|
|
40756
40750
|
if (result.status !== 0) return null;
|
|
40757
40751
|
return parseGithubViewerPermission(result.stdout);
|
|
40758
|
-
}).pipe(catch_$1(() => succeed$2(null)));
|
|
40752
|
+
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.githubViewerPermission"));
|
|
40759
40753
|
/**
|
|
40760
40754
|
* Resolves a `--diff A..B` / `A...B` commit range into a changed-file
|
|
40761
40755
|
* selection. Each endpoint is validated with `isSafeGitRevision`
|
|
@@ -40869,7 +40863,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40869
40863
|
changedFiles: splitNullSeparated(diff.stdout),
|
|
40870
40864
|
isCurrentChanges: false
|
|
40871
40865
|
};
|
|
40872
|
-
}),
|
|
40866
|
+
}).pipe(withSpan("Git.diffSelection")),
|
|
40873
40867
|
stagedFilePaths: (directory) => runGit(directory, [
|
|
40874
40868
|
"diff",
|
|
40875
40869
|
"--cached",
|
|
@@ -40911,7 +40905,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40911
40905
|
status: result.status,
|
|
40912
40906
|
stdout: result.stdout
|
|
40913
40907
|
};
|
|
40914
|
-
}),
|
|
40908
|
+
}).pipe(withSpan("Git.grep")),
|
|
40915
40909
|
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
40916
40910
|
if (files.length === 0) return [];
|
|
40917
40911
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
@@ -40927,7 +40921,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40927
40921
|
]);
|
|
40928
40922
|
if (result.status !== 0) return null;
|
|
40929
40923
|
return parseChangedLineRanges(result.stdout);
|
|
40930
|
-
})
|
|
40924
|
+
}).pipe(withSpan("Git.changedLineRanges"))
|
|
40931
40925
|
});
|
|
40932
40926
|
})).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
|
|
40933
40927
|
/**
|
|
@@ -41142,7 +41136,7 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
|
|
|
41142
41136
|
for (const [absolutePath, originalContent] of originalContents) try {
|
|
41143
41137
|
NFS.writeFileSync(absolutePath, originalContent);
|
|
41144
41138
|
} catch (error) {
|
|
41145
|
-
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${
|
|
41139
|
+
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
|
|
41146
41140
|
}
|
|
41147
41141
|
};
|
|
41148
41142
|
const onExit = () => restore();
|
|
@@ -41248,7 +41242,7 @@ const resolveUserPlugin = (spec, configSourceDirectory) => {
|
|
|
41248
41242
|
try {
|
|
41249
41243
|
resolvedSpecifier = isRelative ? Path.resolve(configSourceDirectory, spec) : candidateRequire.resolve(spec);
|
|
41250
41244
|
} catch (error) {
|
|
41251
|
-
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${
|
|
41245
|
+
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${messageFromUnknown(error)}`);
|
|
41252
41246
|
return null;
|
|
41253
41247
|
}
|
|
41254
41248
|
const { name, ruleNames } = readPluginShape(resolvedSpecifier, (target) => candidateRequire(target));
|
|
@@ -41320,8 +41314,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
41320
41314
|
}
|
|
41321
41315
|
return enabled;
|
|
41322
41316
|
};
|
|
41323
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
|
|
41324
|
-
const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
41317
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
|
|
41318
|
+
const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
41325
41319
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
41326
41320
|
const jsPlugins = [];
|
|
41327
41321
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
@@ -41381,7 +41375,6 @@ const resolveOxlintBinary = () => {
|
|
|
41381
41375
|
return Path.join(oxlintPackageDirectory, "bin", "oxlint");
|
|
41382
41376
|
};
|
|
41383
41377
|
const resolvePluginPath = () => esmRequire.resolve("oxlint-plugin-react-doctor");
|
|
41384
|
-
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
41385
41378
|
const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
41386
41379
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
41387
41380
|
return null;
|
|
@@ -41753,7 +41746,7 @@ const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
|
|
|
41753
41746
|
const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
|
|
41754
41747
|
let currentNode = identifier.parent;
|
|
41755
41748
|
while (currentNode) {
|
|
41756
|
-
if (
|
|
41749
|
+
if (isScopeBoundary(currentNode)) {
|
|
41757
41750
|
if (scopeContainsNonImportBinding(currentNode, currentNode, identifier.text)) return true;
|
|
41758
41751
|
}
|
|
41759
41752
|
if (currentNode === sourceFile) return false;
|
|
@@ -41844,11 +41837,10 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
|
|
|
41844
41837
|
});
|
|
41845
41838
|
return resolution;
|
|
41846
41839
|
};
|
|
41847
|
-
const isScopeNode = isScopeBoundary;
|
|
41848
41840
|
const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, visitedDeclarations = /* @__PURE__ */ new Set()) => {
|
|
41849
41841
|
let currentNode = identifier.parent;
|
|
41850
41842
|
while (currentNode) {
|
|
41851
|
-
if (
|
|
41843
|
+
if (isScopeBoundary(currentNode)) {
|
|
41852
41844
|
const resolution = findResolutionInScope(currentNode, identifier.text, reactImportBindings, sourceFile, visitedDeclarations);
|
|
41853
41845
|
if (resolution) return resolution;
|
|
41854
41846
|
}
|
|
@@ -42018,9 +42010,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
42018
42010
|
try {
|
|
42019
42011
|
parsed = JSON.parse(sanitizedStdout);
|
|
42020
42012
|
} catch {
|
|
42021
|
-
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0,
|
|
42013
|
+
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
42022
42014
|
}
|
|
42023
|
-
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0,
|
|
42015
|
+
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
42024
42016
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
42025
42017
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
42026
42018
|
const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
@@ -42096,7 +42088,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
42096
42088
|
child.kill("SIGKILL");
|
|
42097
42089
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
42098
42090
|
kind: "timeout",
|
|
42099
|
-
detail: `${spawnTimeoutMs /
|
|
42091
|
+
detail: `${spawnTimeoutMs / MILLISECONDS_PER_SECOND}s budget exceeded`
|
|
42100
42092
|
}) }));
|
|
42101
42093
|
}, spawnTimeoutMs);
|
|
42102
42094
|
timeoutHandle.unref?.();
|
|
@@ -42311,6 +42303,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
42311
42303
|
NFS.closeSync(fileHandle);
|
|
42312
42304
|
}
|
|
42313
42305
|
};
|
|
42306
|
+
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
42307
|
+
/**
|
|
42308
|
+
* Detects an oxlint config-load crash caused by the optional
|
|
42309
|
+
* `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
|
|
42310
|
+
* builds the partial-failure note for it; returns `null` when the failure
|
|
42311
|
+
* was anything else.
|
|
42312
|
+
*
|
|
42313
|
+
* oxlint prints a framed error to stdout (not stderr) and exits non-zero
|
|
42314
|
+
* when a `jsPlugins` entry can't be imported; that non-JSON stdout
|
|
42315
|
+
* surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
|
|
42316
|
+
* config load on it, leaving the plugin in would drop every curated
|
|
42317
|
+
* react-doctor diagnostic too — so the caller retries with the plugin
|
|
42318
|
+
* stripped (issue #833). Both markers sit at the start of oxlint's
|
|
42319
|
+
* message, so they survive the `preview` slice even for deep pnpm paths.
|
|
42320
|
+
*/
|
|
42321
|
+
const reactHooksJsPluginDropNote = (error) => {
|
|
42322
|
+
if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
|
|
42323
|
+
const { preview } = error.reason;
|
|
42324
|
+
if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
|
|
42325
|
+
const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
|
|
42326
|
+
return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
|
|
42327
|
+
};
|
|
42314
42328
|
/**
|
|
42315
42329
|
* The oxlint runner. Composed of three pieces in `runners/oxlint/`:
|
|
42316
42330
|
*
|
|
@@ -42338,15 +42352,16 @@ const runOxlint = async (options) => {
|
|
|
42338
42352
|
const pluginPath = resolvePluginPath();
|
|
42339
42353
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
42340
42354
|
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
42341
|
-
const buildConfig = (
|
|
42355
|
+
const buildConfig = (overrides) => createOxlintConfig({
|
|
42342
42356
|
pluginPath,
|
|
42343
42357
|
project,
|
|
42344
42358
|
customRulesOnly,
|
|
42345
|
-
extendsPaths:
|
|
42359
|
+
extendsPaths: overrides.extendsPaths,
|
|
42346
42360
|
ignoredTags,
|
|
42347
42361
|
serverAuthFunctionNames,
|
|
42348
42362
|
severityControls,
|
|
42349
|
-
userPlugins
|
|
42363
|
+
userPlugins,
|
|
42364
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
42350
42365
|
});
|
|
42351
42366
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
42352
42367
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -42382,12 +42397,22 @@ const runOxlint = async (options) => {
|
|
|
42382
42397
|
outputMaxBytes,
|
|
42383
42398
|
concurrency: options.concurrency
|
|
42384
42399
|
});
|
|
42385
|
-
writeOxlintConfig(configPath, buildConfig(extendsPaths));
|
|
42400
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
42386
42401
|
try {
|
|
42387
42402
|
return await runBatches();
|
|
42388
42403
|
} catch (error) {
|
|
42404
|
+
const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
|
|
42405
|
+
if (reactHooksJsDropNote !== null) {
|
|
42406
|
+
writeOxlintConfig(configPath, buildConfig({
|
|
42407
|
+
extendsPaths,
|
|
42408
|
+
disableReactHooksJsPlugin: true
|
|
42409
|
+
}));
|
|
42410
|
+
const diagnostics = await runBatches();
|
|
42411
|
+
onPartialFailure?.(reactHooksJsDropNote);
|
|
42412
|
+
return diagnostics;
|
|
42413
|
+
}
|
|
42389
42414
|
if (extendsPaths.length === 0) throw error;
|
|
42390
|
-
writeOxlintConfig(configPath, buildConfig([]));
|
|
42415
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
|
|
42391
42416
|
return await runBatches();
|
|
42392
42417
|
}
|
|
42393
42418
|
} finally {
|
|
@@ -43185,7 +43210,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
43185
43210
|
}))))))));
|
|
43186
43211
|
const deadCodeFailureState = yield* get$2(deadCodeFailure);
|
|
43187
43212
|
const scanElapsedMilliseconds = Date.now() - scanStartTime;
|
|
43188
|
-
const scanElapsedSeconds = (scanElapsedMilliseconds /
|
|
43213
|
+
const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
|
|
43189
43214
|
if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
|
|
43190
43215
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
43191
43216
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
@@ -43422,7 +43447,7 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
|
|
|
43422
43447
|
static layerNode = effect(StagedFiles, gen(function* () {
|
|
43423
43448
|
const git = yield* Git;
|
|
43424
43449
|
return StagedFiles.of({
|
|
43425
|
-
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile))),
|
|
43450
|
+
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile)), withSpan("StagedFiles.discoverSourceFiles")),
|
|
43426
43451
|
materialize: ({ directory, stagedFiles, tempDirectory }) => materializeSourceTree({
|
|
43427
43452
|
directory,
|
|
43428
43453
|
files: stagedFiles,
|
|
@@ -43432,7 +43457,7 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
|
|
|
43432
43457
|
tempDirectory: tree.tempDirectory,
|
|
43433
43458
|
stagedFiles: tree.materializedFiles,
|
|
43434
43459
|
cleanup: tree.cleanup
|
|
43435
|
-
})))
|
|
43460
|
+
})), withSpan("StagedFiles.materialize"))
|
|
43436
43461
|
});
|
|
43437
43462
|
}));
|
|
43438
43463
|
/**
|
|
@@ -43565,6 +43590,7 @@ const buildJsonReport = (input) => {
|
|
|
43565
43590
|
score: result.score,
|
|
43566
43591
|
skippedChecks: result.skippedChecks,
|
|
43567
43592
|
...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
|
|
43593
|
+
...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
|
|
43568
43594
|
elapsedMilliseconds: result.elapsedMilliseconds
|
|
43569
43595
|
}));
|
|
43570
43596
|
const flattenedDiagnostics = projects.flatMap((entry) => entry.diagnostics);
|
|
@@ -43839,7 +43865,7 @@ const FALSY_CI_FLAG_VALUES = new Set([
|
|
|
43839
43865
|
"false"
|
|
43840
43866
|
]);
|
|
43841
43867
|
const isCiFlagSet = (value) => value !== void 0 && !FALSY_CI_FLAG_VALUES.has(value.toLowerCase());
|
|
43842
|
-
const isCiEnvironment = () => CI_ENVIRONMENT_VARIABLES.some((environmentVariable) => Boolean(
|
|
43868
|
+
const isCiEnvironment = (env = process.env) => CI_ENVIRONMENT_VARIABLES.some((environmentVariable) => Boolean(env[environmentVariable])) || isCiFlagSet(env.CI);
|
|
43843
43869
|
const detectCiProvider = () => {
|
|
43844
43870
|
for (const [environmentVariable, provider] of CI_PROVIDER_BY_ENVIRONMENT_VARIABLE) if (process.env[environmentVariable]) return provider;
|
|
43845
43871
|
return isCiFlagSet(process.env.CI) ? "unknown" : null;
|
|
@@ -43864,6 +43890,53 @@ const detectCodingAgent = () => {
|
|
|
43864
43890
|
const isCodingAgentEnvironment = () => detectCodingAgent() !== null;
|
|
43865
43891
|
const isCiOrCodingAgentEnvironment = () => isCiEnvironment() || isCodingAgentEnvironment();
|
|
43866
43892
|
//#endregion
|
|
43893
|
+
//#region src/cli/utils/detect-terminal-kind.ts
|
|
43894
|
+
const TERMINAL_BY_TERM_PROGRAM = [
|
|
43895
|
+
["vscode", "vscode"],
|
|
43896
|
+
["iTerm.app", "iterm"],
|
|
43897
|
+
["Apple_Terminal", "apple-terminal"],
|
|
43898
|
+
["WezTerm", "wezterm"],
|
|
43899
|
+
["ghostty", "ghostty"],
|
|
43900
|
+
["Hyper", "hyper"],
|
|
43901
|
+
["Tabby", "tabby"],
|
|
43902
|
+
["rio", "rio"]
|
|
43903
|
+
];
|
|
43904
|
+
/**
|
|
43905
|
+
* Best-effort label for the terminal emulator / editor hosting the CLI,
|
|
43906
|
+
* derived from terminal-identity env vars. Recorded as the `terminalKind` run
|
|
43907
|
+
* tag so we can see where React Doctor is actually run (nvim, VS Code, iTerm,
|
|
43908
|
+
* …) — the split Sentry can't otherwise see. Low-cardinality and free of any
|
|
43909
|
+
* username/path/secret, so it's safe as a tag. Editor terminals (nvim/vim)
|
|
43910
|
+
* win over the outer emulator because that's the surface a user is reading in;
|
|
43911
|
+
* "ci" marks a run with no interactive terminal; "unknown" when nothing matches.
|
|
43912
|
+
*/
|
|
43913
|
+
const detectTerminalKind = (env = process.env) => {
|
|
43914
|
+
if (env.NVIM) return "neovim";
|
|
43915
|
+
if (env.VIM_TERMINAL) return "vim";
|
|
43916
|
+
const termProgram = env.TERM_PROGRAM;
|
|
43917
|
+
if (termProgram) {
|
|
43918
|
+
for (const [marker, label] of TERMINAL_BY_TERM_PROGRAM) if (termProgram === marker) return label;
|
|
43919
|
+
}
|
|
43920
|
+
if (env.KITTY_WINDOW_ID || env.TERM === "xterm-kitty") return "kitty";
|
|
43921
|
+
if (env.WT_SESSION) return "windows-terminal";
|
|
43922
|
+
if (env.ALACRITTY_WINDOW_ID || env.TERM === "alacritty") return "alacritty";
|
|
43923
|
+
if (env.VTE_VERSION) return "vte";
|
|
43924
|
+
if (env.TMUX) return "tmux";
|
|
43925
|
+
if (isCiEnvironment(env)) return "ci";
|
|
43926
|
+
return "unknown";
|
|
43927
|
+
};
|
|
43928
|
+
//#endregion
|
|
43929
|
+
//#region src/cli/utils/is-debug-flag.ts
|
|
43930
|
+
/**
|
|
43931
|
+
* Whether the user passed `--debug` (surface the run's Sentry trace id, and
|
|
43932
|
+
* force performance tracing on so there's a trace to surface). Read straight
|
|
43933
|
+
* from argv rather than Commander's parsed flags because `initializeSentry()`
|
|
43934
|
+
* runs before Commander parses — the same reason `shouldEnableSentry()` reads
|
|
43935
|
+
* `--no-score` from argv. Sharing this one reader keeps the init-time sampling
|
|
43936
|
+
* override and the end-of-run print in agreement.
|
|
43937
|
+
*/
|
|
43938
|
+
const isDebugFlagEnabled = (argv = process.argv) => argv.includes("--debug");
|
|
43939
|
+
//#endregion
|
|
43867
43940
|
//#region src/cli/utils/is-git-hook-environment.ts
|
|
43868
43941
|
const isGitHookEnvironment = () => Boolean(process.env.GIT_DIR);
|
|
43869
43942
|
//#endregion
|
|
@@ -43886,6 +43959,7 @@ const NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
|
|
|
43886
43959
|
const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some((envVariable) => Boolean(process.env[envVariable])) || isCodingAgentEnvironment();
|
|
43887
43960
|
//#endregion
|
|
43888
43961
|
//#region src/cli/utils/constants.ts
|
|
43962
|
+
const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
|
|
43889
43963
|
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
43890
43964
|
const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
|
|
43891
43965
|
const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
|
|
@@ -43970,7 +44044,7 @@ const makeNoopConsole = () => ({
|
|
|
43970
44044
|
});
|
|
43971
44045
|
//#endregion
|
|
43972
44046
|
//#region src/cli/utils/version.ts
|
|
43973
|
-
const VERSION = "0.5.6-dev.
|
|
44047
|
+
const VERSION = "0.5.6-dev.4e06b2a";
|
|
43974
44048
|
//#endregion
|
|
43975
44049
|
//#region src/cli/utils/json-mode.ts
|
|
43976
44050
|
let context = null;
|
|
@@ -44120,7 +44194,9 @@ const buildRunContext = () => {
|
|
|
44120
44194
|
viaAction: isOfficialGithubAction(),
|
|
44121
44195
|
codingAgent: detectCodingAgent(),
|
|
44122
44196
|
interactive: !isNonInteractiveEnvironment(),
|
|
44197
|
+
terminalKind: detectTerminalKind(),
|
|
44123
44198
|
jsonMode: isJsonModeActive(),
|
|
44199
|
+
debug: isDebugFlagEnabled(),
|
|
44124
44200
|
invokedVia: detectInvokedVia()
|
|
44125
44201
|
};
|
|
44126
44202
|
};
|
|
@@ -44190,7 +44266,9 @@ const buildSentryScope = (runContext = buildRunContext()) => {
|
|
|
44190
44266
|
viaAction: runContext.viaAction,
|
|
44191
44267
|
codingAgent: runContext.codingAgent,
|
|
44192
44268
|
interactive: runContext.interactive,
|
|
44269
|
+
terminalKind: runContext.terminalKind,
|
|
44193
44270
|
jsonMode: runContext.jsonMode,
|
|
44271
|
+
debug: runContext.debug,
|
|
44194
44272
|
invokedVia: runContext.invokedVia,
|
|
44195
44273
|
nodeMajor: runContext.nodeMajor
|
|
44196
44274
|
};
|
|
@@ -44328,13 +44406,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
44328
44406
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
44329
44407
|
* standard `SENTRY_RELEASE` override.
|
|
44330
44408
|
*/
|
|
44331
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.
|
|
44409
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.4e06b2a`;
|
|
44332
44410
|
/**
|
|
44333
44411
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
44334
44412
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
44335
44413
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
44336
44414
|
*/
|
|
44337
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.
|
|
44415
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.4e06b2a") ? "development" : "production");
|
|
44338
44416
|
/**
|
|
44339
44417
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
44340
44418
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -44398,7 +44476,7 @@ const flushSentry = async () => {
|
|
|
44398
44476
|
const initializeSentry = () => {
|
|
44399
44477
|
if (isInitialized || !shouldEnableSentry()) return;
|
|
44400
44478
|
isInitialized = true;
|
|
44401
|
-
resolvedTracesSampleRate = resolveTracesSampleRate();
|
|
44479
|
+
resolvedTracesSampleRate = isDebugFlagEnabled() ? 1 : resolveTracesSampleRate();
|
|
44402
44480
|
const { tags, contexts } = buildSentryScope();
|
|
44403
44481
|
Sentry.init({
|
|
44404
44482
|
dsn: process.env.SENTRY_DSN || "https://f253d570240a59b8dbd77b7a548ef133@o4510226365743104.ingest.us.sentry.io/4511487817809920",
|
|
@@ -47614,6 +47692,11 @@ const setActiveRunTrace = (trace) => {
|
|
|
47614
47692
|
activeRunTrace = trace;
|
|
47615
47693
|
};
|
|
47616
47694
|
const getActiveRunTrace = () => activeRunTrace;
|
|
47695
|
+
let lastRunTraceId = null;
|
|
47696
|
+
const recordRunTraceId = (traceId) => {
|
|
47697
|
+
lastRunTraceId = traceId;
|
|
47698
|
+
};
|
|
47699
|
+
const getLastRunTraceId = () => lastRunTraceId;
|
|
47617
47700
|
//#endregion
|
|
47618
47701
|
//#region src/cli/utils/to-span-attributes.ts
|
|
47619
47702
|
/**
|
|
@@ -47676,14 +47759,13 @@ const withSentryRunSpan = (run, options = {}) => {
|
|
|
47676
47759
|
op: "cli.inspect",
|
|
47677
47760
|
attributes: toSpanAttributes(tags)
|
|
47678
47761
|
}, (rootSpan) => {
|
|
47679
|
-
|
|
47680
|
-
|
|
47681
|
-
|
|
47682
|
-
|
|
47683
|
-
|
|
47684
|
-
|
|
47685
|
-
|
|
47686
|
-
}
|
|
47762
|
+
const spanContext = rootSpan.spanContext();
|
|
47763
|
+
recordRunTraceId(spanContext.traceId);
|
|
47764
|
+
if (options.concurrentScan !== true) setActiveRunTrace({
|
|
47765
|
+
traceId: spanContext.traceId,
|
|
47766
|
+
spanId: spanContext.spanId,
|
|
47767
|
+
sampled: (spanContext.traceFlags & 1) === 1
|
|
47768
|
+
});
|
|
47687
47769
|
return run(rootSpan);
|
|
47688
47770
|
});
|
|
47689
47771
|
};
|
|
@@ -48116,7 +48198,7 @@ const AGENT_GUIDANCE_LINES = [
|
|
|
48116
48198
|
"Investigate deeply where relevant: race conditions, security-sensitive flows, state propagation, multi-file refactors, and downstream dependency chains.",
|
|
48117
48199
|
"Ignore pure style preferences, theoretical issues without real impact, missing features, and unrelated pre-existing code.",
|
|
48118
48200
|
"Start with high-confidence fixes that preserve behavior. Leave low-confidence or product-dependent changes as notes.",
|
|
48119
|
-
"Run `npx react-doctor@latest --verbose --
|
|
48201
|
+
"Run `npx react-doctor@latest --verbose --scope changed` before and after changes, plus relevant tests after each focused batch.",
|
|
48120
48202
|
"When available, spawn subagents or isolated worktrees for independent rule families, then review and merge only the best safe fixes.",
|
|
48121
48203
|
"Split unrelated, broad, or behavior-changing work into separate PRs/branches instead of one large cleanup.",
|
|
48122
48204
|
"For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.",
|
|
@@ -48191,6 +48273,15 @@ const boxText = (content, innerWidth) => {
|
|
|
48191
48273
|
].join("\n");
|
|
48192
48274
|
};
|
|
48193
48275
|
//#endregion
|
|
48276
|
+
//#region src/cli/utils/resolve-absolute-path.ts
|
|
48277
|
+
/**
|
|
48278
|
+
* Resolves a diagnostic's `filePath` (relative to its project root, or
|
|
48279
|
+
* already absolute) to an absolute path. Shared by the code-frame reader and
|
|
48280
|
+
* the terminal hyperlink builder so both turn a relative path into the same
|
|
48281
|
+
* on-disk location.
|
|
48282
|
+
*/
|
|
48283
|
+
const resolveAbsolutePath = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
48284
|
+
//#endregion
|
|
48194
48285
|
//#region src/cli/utils/build-code-frame.ts
|
|
48195
48286
|
/**
|
|
48196
48287
|
* Renders a syntax-highlighted source excerpt around a diagnostic site
|
|
@@ -48201,7 +48292,7 @@ const boxText = (content, innerWidth) => {
|
|
|
48201
48292
|
*/
|
|
48202
48293
|
const buildCodeFrame = (input) => {
|
|
48203
48294
|
if (input.line <= 0) return null;
|
|
48204
|
-
const absolutePath =
|
|
48295
|
+
const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
|
|
48205
48296
|
let source;
|
|
48206
48297
|
try {
|
|
48207
48298
|
source = NFS.readFileSync(absolutePath, "utf8");
|
|
@@ -48241,6 +48332,16 @@ const resolveMeasureWidth = (reservedColumns = 0) => resolveClampedWidth({
|
|
|
48241
48332
|
const DIVIDER_INDENT = " ";
|
|
48242
48333
|
const buildSectionDivider = () => highlighter.dim(`${DIVIDER_INDENT}${"─".repeat(resolveMeasureWidth(2))}`);
|
|
48243
48334
|
//#endregion
|
|
48335
|
+
//#region src/cli/utils/format-hyperlink.ts
|
|
48336
|
+
const OSC = "\x1B]";
|
|
48337
|
+
const ST = "\x1B\\";
|
|
48338
|
+
/**
|
|
48339
|
+
* Wraps `text` in an OSC 8 hyperlink pointing at `uri`. The visible characters
|
|
48340
|
+
* are exactly `text`; the link is carried in escape sequences a capable
|
|
48341
|
+
* terminal turns into a click target.
|
|
48342
|
+
*/
|
|
48343
|
+
const formatHyperlink = (text, uri) => `${OSC}8;;${uri}${ST}${text}${OSC}8;;${ST}`;
|
|
48344
|
+
//#endregion
|
|
48244
48345
|
//#region src/cli/utils/indent-multiline-text.ts
|
|
48245
48346
|
const indentMultilineText = (text, linePrefix) => text.split("\n").map((lineText) => `${linePrefix}${lineText}`).join("\n");
|
|
48246
48347
|
//#endregion
|
|
@@ -48394,17 +48495,23 @@ const clusterNearbyDiagnostics = (diagnostics) => {
|
|
|
48394
48495
|
}
|
|
48395
48496
|
return clusters;
|
|
48396
48497
|
};
|
|
48397
|
-
const
|
|
48498
|
+
const formatClusterLocationText = (cluster) => {
|
|
48499
|
+
const { filePath } = cluster.diagnostics[0];
|
|
48500
|
+
if (cluster.startLine <= 0) return filePath;
|
|
48501
|
+
if (cluster.endLine > cluster.startLine) return `${filePath}:${cluster.startLine}-${cluster.endLine}`;
|
|
48502
|
+
return `${filePath}:${cluster.startLine}`;
|
|
48503
|
+
};
|
|
48504
|
+
const formatClusterLocation = (cluster, resolveSourceRoot, hyperlinks) => {
|
|
48398
48505
|
const lead = cluster.diagnostics[0];
|
|
48399
48506
|
const contextTag = formatFileContextTag(lead);
|
|
48400
|
-
|
|
48401
|
-
if (
|
|
48402
|
-
return `${lead.filePath
|
|
48507
|
+
const location = formatClusterLocationText(cluster);
|
|
48508
|
+
if (!hyperlinks) return `${location}${contextTag}`;
|
|
48509
|
+
return `${formatHyperlink(location, pathToFileURL(resolveAbsolutePath(lead.filePath, resolveSourceRoot(lead))).href)}${contextTag}`;
|
|
48403
48510
|
};
|
|
48404
|
-
const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame) => {
|
|
48511
|
+
const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame, hyperlinks) => {
|
|
48405
48512
|
const lead = cluster.diagnostics[0];
|
|
48406
48513
|
const isMultiSite = cluster.diagnostics.length > 1;
|
|
48407
|
-
const lines = ["", highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${formatClusterLocation(cluster)}`)];
|
|
48514
|
+
const lines = ["", highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${formatClusterLocation(cluster, resolveSourceRoot, hyperlinks)}`)];
|
|
48408
48515
|
const codeFrame = renderCodeFrame ? buildCodeFrame({
|
|
48409
48516
|
filePath: lead.filePath,
|
|
48410
48517
|
line: cluster.startLine,
|
|
@@ -48423,7 +48530,7 @@ const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame
|
|
|
48423
48530
|
}
|
|
48424
48531
|
return lines;
|
|
48425
48532
|
};
|
|
48426
|
-
const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, renderEverySite, isAgentEnvironment) => {
|
|
48533
|
+
const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, renderEverySite, isAgentEnvironment, hyperlinks) => {
|
|
48427
48534
|
const representative = pickRepresentativeDiagnostic(ruleDiagnostics);
|
|
48428
48535
|
const { severity } = representative;
|
|
48429
48536
|
const trailingBadge = formatTrailingSiteBadge(ruleDiagnostics.length);
|
|
@@ -48443,7 +48550,7 @@ const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, rende
|
|
|
48443
48550
|
}
|
|
48444
48551
|
const renderCodeFrame = severity === "error";
|
|
48445
48552
|
const sites = renderEverySite ? ruleDiagnostics : [representative];
|
|
48446
|
-
if (!(isCollapsedWarningGroup && representative.help.includes(representative.filePath))) for (const cluster of clusterNearbyDiagnostics(sites)) lines.push(...buildDiagnosticClusterLines(cluster, resolveSourceRoot, renderCodeFrame));
|
|
48553
|
+
if (!(isCollapsedWarningGroup && representative.help.includes(representative.filePath))) for (const cluster of clusterNearbyDiagnostics(sites)) lines.push(...buildDiagnosticClusterLines(cluster, resolveSourceRoot, renderCodeFrame, hyperlinks));
|
|
48447
48554
|
return lines;
|
|
48448
48555
|
};
|
|
48449
48556
|
const selectErrorRuleGroups = (diagnostics, rulePriority) => buildSortedRuleGroups(diagnostics.filter((diagnostic) => diagnostic.severity === "error"), rulePriority);
|
|
@@ -48456,7 +48563,7 @@ const buildOverflowSummaryLine = (diagnostics, rulePriority) => {
|
|
|
48456
48563
|
return ` ${highlighter.dim("Run")} ${command} ${highlighter.dim("to list every error and warning")}`;
|
|
48457
48564
|
};
|
|
48458
48565
|
const getTopErrorRuleKeys = (diagnostics, limit, rulePriority) => new Set(selectTopErrorRuleGroups(diagnostics, limit, rulePriority).map(([ruleKey]) => ruleKey));
|
|
48459
|
-
const buildTopErrorsSection = (diagnostics, resolveSourceRoot, rulePriority) => {
|
|
48566
|
+
const buildTopErrorsSection = (diagnostics, resolveSourceRoot, hyperlinks, rulePriority) => {
|
|
48460
48567
|
const topRuleGroups = selectErrorRuleGroups(diagnostics, rulePriority).slice(0, 3);
|
|
48461
48568
|
if (topRuleGroups.length === 0) return {
|
|
48462
48569
|
lines: [],
|
|
@@ -48466,7 +48573,7 @@ const buildTopErrorsSection = (diagnostics, resolveSourceRoot, rulePriority) =>
|
|
|
48466
48573
|
const blockOffsets = [];
|
|
48467
48574
|
for (const [ruleKey, ruleDiagnostics] of topRuleGroups) {
|
|
48468
48575
|
blockOffsets.push(lines.length);
|
|
48469
|
-
lines.push(...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, false, false));
|
|
48576
|
+
lines.push(...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, false, false, hyperlinks));
|
|
48470
48577
|
lines.push("");
|
|
48471
48578
|
}
|
|
48472
48579
|
return {
|
|
@@ -48504,18 +48611,18 @@ const buildOverviewHeaderLines = (diagnostics) => {
|
|
|
48504
48611
|
* single Effect.forEach over Console.log so failures or fiber
|
|
48505
48612
|
* interruption produce predictable partial output.
|
|
48506
48613
|
*/
|
|
48507
|
-
const printDiagnostics = (diagnostics, isVerbose, sourceRoot, rulePriority, isAgentEnvironment = false, onboarding = {}) => gen(function* () {
|
|
48614
|
+
const printDiagnostics = (diagnostics, isVerbose, sourceRoot, rulePriority, isAgentEnvironment = false, onboarding = {}, hyperlinks = false) => gen(function* () {
|
|
48508
48615
|
const sectionPause = onboarding.sectionPause ?? void_;
|
|
48509
48616
|
const animateCountUp = onboarding.animateCountUp ?? false;
|
|
48510
48617
|
const resolveSourceRoot = typeof sourceRoot === "function" ? sourceRoot : () => sourceRoot;
|
|
48511
48618
|
let detailLines;
|
|
48512
48619
|
let topErrorBlockOffsets = [];
|
|
48513
48620
|
if (!isVerbose) {
|
|
48514
|
-
const topErrors = buildTopErrorsSection(diagnostics, resolveSourceRoot, rulePriority);
|
|
48621
|
+
const topErrors = buildTopErrorsSection(diagnostics, resolveSourceRoot, hyperlinks, rulePriority);
|
|
48515
48622
|
detailLines = topErrors.lines;
|
|
48516
48623
|
topErrorBlockOffsets = topErrors.blockOffsets;
|
|
48517
48624
|
} else detailLines = buildSortedRuleGroups(diagnostics, rulePriority).flatMap(([ruleKey, ruleDiagnostics]) => {
|
|
48518
|
-
return [...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, true, isAgentEnvironment), ""];
|
|
48625
|
+
return [...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, true, isAgentEnvironment, hyperlinks), ""];
|
|
48519
48626
|
});
|
|
48520
48627
|
const overflowLine = isVerbose ? void 0 : buildOverflowSummaryLine(diagnostics, rulePriority);
|
|
48521
48628
|
const categoryTallies = buildCategoryDiagnosticGroups(diagnostics, rulePriority).map(buildCategoryTally);
|
|
@@ -48576,6 +48683,48 @@ const computeProjectedScore = async (topErrorSource, rescoreSource, currentScore
|
|
|
48576
48683
|
//#endregion
|
|
48577
48684
|
//#region src/cli/utils/filter-diagnostics-by-categories.ts
|
|
48578
48685
|
const filterDiagnosticsByCategories = (diagnostics, categories) => categories.size === 0 ? [...diagnostics] : diagnostics.filter((diagnostic) => categories.has(diagnostic.category));
|
|
48686
|
+
//#endregion
|
|
48687
|
+
//#region src/cli/utils/supports-hyperlinks.ts
|
|
48688
|
+
const HYPERLINK_CAPABLE_TERM_PROGRAMS = new Set([
|
|
48689
|
+
"iTerm.app",
|
|
48690
|
+
"WezTerm",
|
|
48691
|
+
"vscode",
|
|
48692
|
+
"Hyper",
|
|
48693
|
+
"ghostty",
|
|
48694
|
+
"Tabby",
|
|
48695
|
+
"rio"
|
|
48696
|
+
]);
|
|
48697
|
+
const parseVteVersion = (raw) => {
|
|
48698
|
+
const parsed = Number.parseInt(raw ?? "", 10);
|
|
48699
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
48700
|
+
};
|
|
48701
|
+
/**
|
|
48702
|
+
* Whether `stream` is a terminal that renders OSC 8 hyperlinks. Auto-detected
|
|
48703
|
+
* from terminal-identity env vars; the de-facto `FORCE_HYPERLINK` env var
|
|
48704
|
+
* overrides detection (`FORCE_HYPERLINK=0`/`false` forces off, any other value
|
|
48705
|
+
* forces on), mirroring how the ecosystem's terminal libraries gate the same
|
|
48706
|
+
* feature. Off for non-TTYs, `TERM=dumb`, and CI (whose log viewers render the
|
|
48707
|
+
* raw escape rather than a link). Unknown terminals default to off.
|
|
48708
|
+
*/
|
|
48709
|
+
const supportsHyperlinks = (stream = process.stdout, env = process.env) => {
|
|
48710
|
+
const forced = env.FORCE_HYPERLINK;
|
|
48711
|
+
if (forced !== void 0 && forced !== "") return forced !== "0" && forced.toLowerCase() !== "false";
|
|
48712
|
+
if (stream.isTTY !== true) return false;
|
|
48713
|
+
if (env.TERM === "dumb") return false;
|
|
48714
|
+
if (isCiEnvironment(env)) return false;
|
|
48715
|
+
if (env.WT_SESSION) return true;
|
|
48716
|
+
if (env.KITTY_WINDOW_ID || env.TERM === "xterm-kitty") return true;
|
|
48717
|
+
if (parseVteVersion(env.VTE_VERSION) >= 5e3) return true;
|
|
48718
|
+
return Boolean(env.TERM_PROGRAM && HYPERLINK_CAPABLE_TERM_PROGRAMS.has(env.TERM_PROGRAM));
|
|
48719
|
+
};
|
|
48720
|
+
//#endregion
|
|
48721
|
+
//#region src/cli/utils/should-render-hyperlinks.ts
|
|
48722
|
+
/**
|
|
48723
|
+
* Whether to emit OSC 8 clickable `file:line` locations for this run: a
|
|
48724
|
+
* hyperlink-capable terminal AND not a coding agent (whose output parsers
|
|
48725
|
+
* would choke on the escape sequences).
|
|
48726
|
+
*/
|
|
48727
|
+
const shouldRenderHyperlinks = (stream = process.stdout) => supportsHyperlinks(stream) && !isCodingAgentEnvironment();
|
|
48579
48728
|
const FORCE_ONBOARDING_ENV_VAR = "REACT_DOCTOR_FORCE_ONBOARDING";
|
|
48580
48729
|
const FALSY_FLAG_VALUES = new Set([
|
|
48581
48730
|
"",
|
|
@@ -48595,10 +48744,9 @@ const canAnimateOnboarding = (stream = process.stdout) => {
|
|
|
48595
48744
|
};
|
|
48596
48745
|
//#endregion
|
|
48597
48746
|
//#region src/cli/utils/onboarding-state.ts
|
|
48598
|
-
const GLOBAL_CONFIG_PROJECT_NAME$2 = "react-doctor";
|
|
48599
48747
|
const ONBOARDED_AT_KEY = "onboardedAt";
|
|
48600
48748
|
const getOnboardingStore = (options = {}) => new Conf({
|
|
48601
|
-
projectName:
|
|
48749
|
+
projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
|
|
48602
48750
|
cwd: options.cwd
|
|
48603
48751
|
});
|
|
48604
48752
|
const hasCompletedOnboarding = (options = {}) => {
|
|
@@ -49054,6 +49202,78 @@ const resolveCliCategories = (categoryFlag) => {
|
|
|
49054
49202
|
return resolvedCategories.length > 0 ? resolvedCategories : void 0;
|
|
49055
49203
|
};
|
|
49056
49204
|
//#endregion
|
|
49205
|
+
//#region src/cli/utils/git-hook-shared.ts
|
|
49206
|
+
const HOOK_FILE_NAME = "pre-commit";
|
|
49207
|
+
const HOOK_RELATIVE_PATH = "hooks/pre-commit";
|
|
49208
|
+
const LEGACY_HOOK_RUNNER_RELATIVE_PATH = ".react-doctor/hooks/pre-commit";
|
|
49209
|
+
const HUSKY_HOOKS_PATH = ".husky";
|
|
49210
|
+
const VITE_PLUS_HOOKS_PATH = ".vite-hooks";
|
|
49211
|
+
const LEFTHOOK_CONFIG_FILES = ["lefthook.yml", "lefthook.yaml"];
|
|
49212
|
+
const PRE_COMMIT_CONFIG_FILE = ".pre-commit-config.yaml";
|
|
49213
|
+
const OVERCOMMIT_CONFIG_FILE = ".overcommit.yml";
|
|
49214
|
+
const REACT_DOCTOR_COMMAND = "react-doctor --staged --blocking warning";
|
|
49215
|
+
const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
49216
|
+
"react_doctor_output=$(mktemp \"${TMPDIR:-/tmp}/react-doctor-hook.XXXXXX\");",
|
|
49217
|
+
`if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
|
|
49218
|
+
"rm -f \"$react_doctor_output\";",
|
|
49219
|
+
"else",
|
|
49220
|
+
"rm -f \"$react_doctor_output\";",
|
|
49221
|
+
`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;`,
|
|
49222
|
+
"fi"
|
|
49223
|
+
].join(" ");
|
|
49224
|
+
const PACKAGE_JSON_FILE_NAME = "package.json";
|
|
49225
|
+
const runGit = (projectRoot, args) => {
|
|
49226
|
+
try {
|
|
49227
|
+
return execFileSync("git", [...args], {
|
|
49228
|
+
cwd: projectRoot,
|
|
49229
|
+
encoding: "utf8",
|
|
49230
|
+
stdio: [
|
|
49231
|
+
"ignore",
|
|
49232
|
+
"pipe",
|
|
49233
|
+
"ignore"
|
|
49234
|
+
]
|
|
49235
|
+
}).trim();
|
|
49236
|
+
} catch {
|
|
49237
|
+
return null;
|
|
49238
|
+
}
|
|
49239
|
+
};
|
|
49240
|
+
const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
|
|
49241
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49242
|
+
const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
|
|
49243
|
+
const readPackageJson = (projectRoot) => {
|
|
49244
|
+
try {
|
|
49245
|
+
return JSON.parse(NFS.readFileSync(getPackageJsonPath(projectRoot), "utf8"));
|
|
49246
|
+
} catch {
|
|
49247
|
+
return null;
|
|
49248
|
+
}
|
|
49249
|
+
};
|
|
49250
|
+
const writeJsonFile$1 = (filePath, value) => {
|
|
49251
|
+
NFS.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
49252
|
+
};
|
|
49253
|
+
const packageHasDependency = (projectRoot, dependencyName) => {
|
|
49254
|
+
const packageJson = readPackageJson(projectRoot);
|
|
49255
|
+
if (!isRecord$1(packageJson)) return false;
|
|
49256
|
+
return [
|
|
49257
|
+
"dependencies",
|
|
49258
|
+
"devDependencies",
|
|
49259
|
+
"optionalDependencies"
|
|
49260
|
+
].some((fieldName) => {
|
|
49261
|
+
const dependencies = packageJson[fieldName];
|
|
49262
|
+
return isRecord$1(dependencies) && typeof dependencies[dependencyName] === "string";
|
|
49263
|
+
});
|
|
49264
|
+
};
|
|
49265
|
+
const packageHasRecordKey = (projectRoot, key) => {
|
|
49266
|
+
const packageJson = readPackageJson(projectRoot);
|
|
49267
|
+
return isRecord$1(packageJson) && isRecord$1(packageJson[key]);
|
|
49268
|
+
};
|
|
49269
|
+
const packageHasNestedRecordKey = (projectRoot, key, nestedKey) => {
|
|
49270
|
+
const packageJson = readPackageJson(projectRoot);
|
|
49271
|
+
if (!isRecord$1(packageJson)) return false;
|
|
49272
|
+
const value = packageJson[key];
|
|
49273
|
+
return isRecord$1(value) && isRecord$1(value[nestedKey]);
|
|
49274
|
+
};
|
|
49275
|
+
const ensureTrailingNewline = (content) => content.endsWith("\n") ? content : `${content}\n`;
|
|
49276
|
+
//#endregion
|
|
49057
49277
|
//#region src/cli/utils/scan-result-cache.ts
|
|
49058
49278
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
49059
49279
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
@@ -49064,7 +49284,7 @@ const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
|
49064
49284
|
"eslint-plugin-react-hooks/package.json"
|
|
49065
49285
|
];
|
|
49066
49286
|
const bundledRequire = createRequire(import.meta.url);
|
|
49067
|
-
const isRecord
|
|
49287
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49068
49288
|
const normalizeForStableJson = (value) => {
|
|
49069
49289
|
if (value === null) return null;
|
|
49070
49290
|
if (value === void 0) return void 0;
|
|
@@ -49093,24 +49313,9 @@ const stringifyStableJson = (value) => {
|
|
|
49093
49313
|
}
|
|
49094
49314
|
};
|
|
49095
49315
|
const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
|
|
49096
|
-
const
|
|
49097
|
-
try {
|
|
49098
|
-
return execFileSync("git", [...args], {
|
|
49099
|
-
cwd: directory,
|
|
49100
|
-
encoding: "utf8",
|
|
49101
|
-
stdio: [
|
|
49102
|
-
"ignore",
|
|
49103
|
-
"pipe",
|
|
49104
|
-
"ignore"
|
|
49105
|
-
]
|
|
49106
|
-
}).trim();
|
|
49107
|
-
} catch {
|
|
49108
|
-
return null;
|
|
49109
|
-
}
|
|
49110
|
-
};
|
|
49111
|
-
const readHeadSha = (projectDirectory) => runGit$1(projectDirectory, ["rev-parse", "HEAD"]);
|
|
49316
|
+
const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
|
|
49112
49317
|
const isWorktreeClean = (projectDirectory) => {
|
|
49113
|
-
const status = runGit
|
|
49318
|
+
const status = runGit(projectDirectory, [
|
|
49114
49319
|
"status",
|
|
49115
49320
|
"--porcelain=v1",
|
|
49116
49321
|
"--untracked-files=normal"
|
|
@@ -49118,7 +49323,7 @@ const isWorktreeClean = (projectDirectory) => {
|
|
|
49118
49323
|
return status !== null && status.length === 0;
|
|
49119
49324
|
};
|
|
49120
49325
|
const hasHiddenTrackedFileState = (projectDirectory) => {
|
|
49121
|
-
const output = runGit
|
|
49326
|
+
const output = runGit(projectDirectory, ["ls-files", "-v"]);
|
|
49122
49327
|
if (output === null) return true;
|
|
49123
49328
|
return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
|
|
49124
49329
|
};
|
|
@@ -49131,7 +49336,7 @@ const resolveCacheFilePath = (projectDirectory) => {
|
|
|
49131
49336
|
const readPersistedCache = (cacheFilePath) => {
|
|
49132
49337
|
try {
|
|
49133
49338
|
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
49134
|
-
if (!isRecord
|
|
49339
|
+
if (!isRecord(parsed) || parsed.version !== 1) return {
|
|
49135
49340
|
version: 1,
|
|
49136
49341
|
entries: []
|
|
49137
49342
|
};
|
|
@@ -49141,8 +49346,8 @@ const readPersistedCache = (cacheFilePath) => {
|
|
|
49141
49346
|
};
|
|
49142
49347
|
const entries = [];
|
|
49143
49348
|
for (const entry of parsed.entries) {
|
|
49144
|
-
if (!isRecord
|
|
49145
|
-
if (!isRecord
|
|
49349
|
+
if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.createdAtMs !== "number") continue;
|
|
49350
|
+
if (!isRecord(entry.payload) || !Array.isArray(entry.payload.diagnostics)) continue;
|
|
49146
49351
|
entries.push(entry);
|
|
49147
49352
|
}
|
|
49148
49353
|
return {
|
|
@@ -49674,6 +49879,7 @@ const finalizeAndRender = (input) => gen(function* () {
|
|
|
49674
49879
|
}
|
|
49675
49880
|
const animateRender = !options.silent && !options.verbose && canAnimateOnboarding(process.stdout);
|
|
49676
49881
|
const pause = onboardingSectionPause(animateRender);
|
|
49882
|
+
const useHyperlinks = shouldRenderHyperlinks(process.stdout);
|
|
49677
49883
|
const demotedDiagnosticCount = diagnostics.length - surfaceDiagnostics.length;
|
|
49678
49884
|
const lintSourceFileCount = options.includePaths.length > 0 ? options.includePaths.length : project.sourceFileCount;
|
|
49679
49885
|
if (printedDiagnostics.length === 0) {
|
|
@@ -49699,7 +49905,7 @@ const finalizeAndRender = (input) => gen(function* () {
|
|
|
49699
49905
|
yield* printDiagnostics([...printedDiagnostics], options.verbose, directory, buildRulePriorityMap([score]), isCodingAgentEnvironment(), {
|
|
49700
49906
|
sectionPause: pause,
|
|
49701
49907
|
animateCountUp: animateRender
|
|
49702
|
-
});
|
|
49908
|
+
}, useHyperlinks);
|
|
49703
49909
|
if (options.isNonInteractiveEnvironment && options.outputSurface !== "prComment") yield* printAgentGuidance();
|
|
49704
49910
|
if (options.categoryFilters.size === 0 && demotedDiagnosticCount > 0) {
|
|
49705
49911
|
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.`));
|
|
@@ -49892,16 +50098,7 @@ const buildHandoffPayload = (input) => {
|
|
|
49892
50098
|
try {
|
|
49893
50099
|
outputDirectory = writeDiagnosticsDirectory([...input.diagnostics], input.outputDirectory);
|
|
49894
50100
|
} catch {}
|
|
49895
|
-
const lines = [
|
|
49896
|
-
"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:",
|
|
49897
|
-
`- It's used by teams at ${CI_TRUST_COMPANIES}.`,
|
|
49898
|
-
"- I don't have to fix everything at once; CI keeps new PRs clean while I incrementally pay down the existing issues.",
|
|
49899
|
-
`- Setup guide: ${CI_URL}`,
|
|
49900
|
-
"If I say yes, run `npx react-doctor install` (or add the GitHub Actions workflow). Either way, then continue with the fixes below.",
|
|
49901
|
-
"",
|
|
49902
|
-
`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.`,
|
|
49903
|
-
""
|
|
49904
|
-
];
|
|
50101
|
+
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.`, ""];
|
|
49905
50102
|
topGroups.forEach(([ruleKey, ruleDiagnostics], index) => {
|
|
49906
50103
|
const representative = ruleDiagnostics[0];
|
|
49907
50104
|
const severityLabel = representative.severity === "error" ? "ERROR" : "WARN";
|
|
@@ -49962,78 +50159,6 @@ const detectAvailableAgents = async () => {
|
|
|
49962
50159
|
return getSkillAgentTypes().filter((agent) => agent !== "universal" && detected.has(agent));
|
|
49963
50160
|
};
|
|
49964
50161
|
//#endregion
|
|
49965
|
-
//#region src/cli/utils/git-hook-shared.ts
|
|
49966
|
-
const HOOK_FILE_NAME = "pre-commit";
|
|
49967
|
-
const HOOK_RELATIVE_PATH = "hooks/pre-commit";
|
|
49968
|
-
const LEGACY_HOOK_RUNNER_RELATIVE_PATH = ".react-doctor/hooks/pre-commit";
|
|
49969
|
-
const HUSKY_HOOKS_PATH = ".husky";
|
|
49970
|
-
const VITE_PLUS_HOOKS_PATH = ".vite-hooks";
|
|
49971
|
-
const LEFTHOOK_CONFIG_FILES = ["lefthook.yml", "lefthook.yaml"];
|
|
49972
|
-
const PRE_COMMIT_CONFIG_FILE = ".pre-commit-config.yaml";
|
|
49973
|
-
const OVERCOMMIT_CONFIG_FILE = ".overcommit.yml";
|
|
49974
|
-
const REACT_DOCTOR_COMMAND = "react-doctor --staged --blocking warning";
|
|
49975
|
-
const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
49976
|
-
"react_doctor_output=$(mktemp \"${TMPDIR:-/tmp}/react-doctor-hook.XXXXXX\");",
|
|
49977
|
-
`if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
|
|
49978
|
-
"rm -f \"$react_doctor_output\";",
|
|
49979
|
-
"else",
|
|
49980
|
-
"rm -f \"$react_doctor_output\";",
|
|
49981
|
-
`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;`,
|
|
49982
|
-
"fi"
|
|
49983
|
-
].join(" ");
|
|
49984
|
-
const PACKAGE_JSON_FILE_NAME = "package.json";
|
|
49985
|
-
const runGit = (projectRoot, args) => {
|
|
49986
|
-
try {
|
|
49987
|
-
return execFileSync("git", [...args], {
|
|
49988
|
-
cwd: projectRoot,
|
|
49989
|
-
encoding: "utf8",
|
|
49990
|
-
stdio: [
|
|
49991
|
-
"ignore",
|
|
49992
|
-
"pipe",
|
|
49993
|
-
"ignore"
|
|
49994
|
-
]
|
|
49995
|
-
}).trim();
|
|
49996
|
-
} catch {
|
|
49997
|
-
return null;
|
|
49998
|
-
}
|
|
49999
|
-
};
|
|
50000
|
-
const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
|
|
50001
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50002
|
-
const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
|
|
50003
|
-
const readPackageJson = (projectRoot) => {
|
|
50004
|
-
try {
|
|
50005
|
-
return JSON.parse(NFS.readFileSync(getPackageJsonPath(projectRoot), "utf8"));
|
|
50006
|
-
} catch {
|
|
50007
|
-
return null;
|
|
50008
|
-
}
|
|
50009
|
-
};
|
|
50010
|
-
const writeJsonFile$1 = (filePath, value) => {
|
|
50011
|
-
NFS.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
50012
|
-
};
|
|
50013
|
-
const packageHasDependency = (projectRoot, dependencyName) => {
|
|
50014
|
-
const packageJson = readPackageJson(projectRoot);
|
|
50015
|
-
if (!isRecord(packageJson)) return false;
|
|
50016
|
-
return [
|
|
50017
|
-
"dependencies",
|
|
50018
|
-
"devDependencies",
|
|
50019
|
-
"optionalDependencies"
|
|
50020
|
-
].some((fieldName) => {
|
|
50021
|
-
const dependencies = packageJson[fieldName];
|
|
50022
|
-
return isRecord(dependencies) && typeof dependencies[dependencyName] === "string";
|
|
50023
|
-
});
|
|
50024
|
-
};
|
|
50025
|
-
const packageHasRecordKey = (projectRoot, key) => {
|
|
50026
|
-
const packageJson = readPackageJson(projectRoot);
|
|
50027
|
-
return isRecord(packageJson) && isRecord(packageJson[key]);
|
|
50028
|
-
};
|
|
50029
|
-
const packageHasNestedRecordKey = (projectRoot, key, nestedKey) => {
|
|
50030
|
-
const packageJson = readPackageJson(projectRoot);
|
|
50031
|
-
if (!isRecord(packageJson)) return false;
|
|
50032
|
-
const value = packageJson[key];
|
|
50033
|
-
return isRecord(value) && isRecord(value[nestedKey]);
|
|
50034
|
-
};
|
|
50035
|
-
const ensureTrailingNewline = (content) => content.endsWith("\n") ? content : `${content}\n`;
|
|
50036
|
-
//#endregion
|
|
50037
50162
|
//#region src/cli/utils/install-doctor-script.ts
|
|
50038
50163
|
const DOCTOR_SCRIPT_NAME = "doctor";
|
|
50039
50164
|
const FALLBACK_DOCTOR_SCRIPT_NAME = "react-doctor";
|
|
@@ -50059,31 +50184,31 @@ const findNearestPackageDirectory = (startDirectory, stopDirectory) => {
|
|
|
50059
50184
|
};
|
|
50060
50185
|
const hasDoctorScript = (projectRoot) => {
|
|
50061
50186
|
const packageJson = readPackageJson(findNearestPackageDirectory(projectRoot) ?? projectRoot);
|
|
50062
|
-
if (!isRecord(packageJson)) return false;
|
|
50187
|
+
if (!isRecord$1(packageJson)) return false;
|
|
50063
50188
|
const scripts = packageJson.scripts;
|
|
50064
|
-
if (!isRecord(scripts)) return false;
|
|
50189
|
+
if (!isRecord$1(scripts)) return false;
|
|
50065
50190
|
return isReactDoctorScriptCommand(scripts[DOCTOR_SCRIPT_NAME]) || isReactDoctorScriptCommand(scripts[FALLBACK_DOCTOR_SCRIPT_NAME]);
|
|
50066
50191
|
};
|
|
50067
50192
|
const hasDoctorDependency = (packageJson) => DEPENDENCY_FIELD_NAMES.some((fieldName) => {
|
|
50068
50193
|
const dependencies = packageJson[fieldName];
|
|
50069
|
-
return isRecord(dependencies) && Object.hasOwn(dependencies, "react-doctor");
|
|
50194
|
+
return isRecord$1(dependencies) && Object.hasOwn(dependencies, "react-doctor");
|
|
50070
50195
|
});
|
|
50071
50196
|
const installDoctorScript = (options) => {
|
|
50072
50197
|
const packageDirectory = findNearestPackageDirectory(options.projectRoot) ?? options.projectRoot;
|
|
50073
50198
|
const packageJsonPath = getPackageJsonPath(packageDirectory);
|
|
50074
50199
|
const packageJson = readPackageJson(packageDirectory);
|
|
50075
|
-
if (!isRecord(packageJson)) return {
|
|
50200
|
+
if (!isRecord$1(packageJson)) return {
|
|
50076
50201
|
packageJsonPath,
|
|
50077
50202
|
scriptStatus: "skipped",
|
|
50078
50203
|
scriptReason: "missing-or-invalid-package-json"
|
|
50079
50204
|
};
|
|
50080
50205
|
const scripts = packageJson.scripts;
|
|
50081
50206
|
const scriptTarget = (() => {
|
|
50082
|
-
if (scripts !== void 0 && !isRecord(scripts)) return {
|
|
50207
|
+
if (scripts !== void 0 && !isRecord$1(scripts)) return {
|
|
50083
50208
|
status: "skipped",
|
|
50084
50209
|
reason: "invalid-scripts"
|
|
50085
50210
|
};
|
|
50086
|
-
const scriptRecord = isRecord(scripts) ? scripts : {};
|
|
50211
|
+
const scriptRecord = isRecord$1(scripts) ? scripts : {};
|
|
50087
50212
|
if (isReactDoctorScriptCommand(scriptRecord[DOCTOR_SCRIPT_NAME])) return {
|
|
50088
50213
|
scriptName: DOCTOR_SCRIPT_NAME,
|
|
50089
50214
|
status: "existing"
|
|
@@ -50117,7 +50242,7 @@ const installDoctorScript = (options) => {
|
|
|
50117
50242
|
if (scriptStatus === "created") writeJsonFile$1(packageJsonPath, {
|
|
50118
50243
|
...packageJson,
|
|
50119
50244
|
scripts: {
|
|
50120
|
-
...isRecord(scripts) ? scripts : {},
|
|
50245
|
+
...isRecord$1(scripts) ? scripts : {},
|
|
50121
50246
|
[scriptTarget.scriptName ?? DOCTOR_SCRIPT_NAME]: DOCTOR_SCRIPT_COMMAND
|
|
50122
50247
|
}
|
|
50123
50248
|
});
|
|
@@ -50271,38 +50396,52 @@ const upgradeReactDoctorWorkflowInPlace = (projectRoot) => {
|
|
|
50271
50396
|
//#region src/cli/utils/hash-project-root.ts
|
|
50272
50397
|
const hashProjectRoot = (projectRoot) => createHash("sha256").update(Path.resolve(projectRoot)).digest("hex");
|
|
50273
50398
|
//#endregion
|
|
50274
|
-
//#region src/cli/utils/
|
|
50275
|
-
const
|
|
50276
|
-
const
|
|
50277
|
-
|
|
50278
|
-
|
|
50279
|
-
});
|
|
50280
|
-
|
|
50281
|
-
|
|
50282
|
-
|
|
50283
|
-
|
|
50284
|
-
|
|
50285
|
-
|
|
50286
|
-
|
|
50287
|
-
};
|
|
50288
|
-
const recordActionUpgradeDecision = (projectRoot, outcome, storeOptions = {}) => {
|
|
50289
|
-
try {
|
|
50290
|
-
const store = getActionUpgradeStore(storeOptions);
|
|
50291
|
-
const upgrades = store.get("actionUpgrades", {});
|
|
50292
|
-
store.set("actionUpgrades", {
|
|
50293
|
-
...upgrades,
|
|
50294
|
-
[hashProjectRoot(projectRoot)]: {
|
|
50295
|
-
rootDirectory: Path.resolve(projectRoot),
|
|
50296
|
-
outcome,
|
|
50297
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
50399
|
+
//#region src/cli/utils/project-decision-store.ts
|
|
50400
|
+
const createProjectDecisionStore = (storeKey) => {
|
|
50401
|
+
const getStore = (options = {}) => new Conf({
|
|
50402
|
+
projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
|
|
50403
|
+
cwd: options.cwd
|
|
50404
|
+
});
|
|
50405
|
+
return {
|
|
50406
|
+
getConfigPath: (options = {}) => getStore(options).path,
|
|
50407
|
+
hasHandled: (projectRoot, options = {}) => {
|
|
50408
|
+
try {
|
|
50409
|
+
return Boolean(getStore(options).get(storeKey, {})[hashProjectRoot(projectRoot)]);
|
|
50410
|
+
} catch {
|
|
50411
|
+
return true;
|
|
50298
50412
|
}
|
|
50299
|
-
}
|
|
50300
|
-
|
|
50301
|
-
|
|
50302
|
-
|
|
50303
|
-
|
|
50413
|
+
},
|
|
50414
|
+
record: (projectRoot, outcome, options = {}) => {
|
|
50415
|
+
try {
|
|
50416
|
+
const store = getStore(options);
|
|
50417
|
+
store.set(storeKey, {
|
|
50418
|
+
...store.get(storeKey, {}),
|
|
50419
|
+
[hashProjectRoot(projectRoot)]: {
|
|
50420
|
+
rootDirectory: Path.resolve(projectRoot),
|
|
50421
|
+
outcome,
|
|
50422
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
50423
|
+
}
|
|
50424
|
+
});
|
|
50425
|
+
return true;
|
|
50426
|
+
} catch {
|
|
50427
|
+
return false;
|
|
50428
|
+
}
|
|
50429
|
+
}
|
|
50430
|
+
};
|
|
50304
50431
|
};
|
|
50305
50432
|
//#endregion
|
|
50433
|
+
//#region src/cli/utils/action-upgrade-prompt.ts
|
|
50434
|
+
const store$1 = createProjectDecisionStore("actionUpgrades");
|
|
50435
|
+
store$1.getConfigPath;
|
|
50436
|
+
const hasHandledActionUpgrade = store$1.hasHandled;
|
|
50437
|
+
const recordActionUpgradeDecision = store$1.record;
|
|
50438
|
+
//#endregion
|
|
50439
|
+
//#region src/cli/utils/ci-prompt-decision.ts
|
|
50440
|
+
const store = createProjectDecisionStore("ciPrompts");
|
|
50441
|
+
store.getConfigPath;
|
|
50442
|
+
const hasHandledCiPrompt = store.hasHandled;
|
|
50443
|
+
const recordCiPromptDecision = store.record;
|
|
50444
|
+
//#endregion
|
|
50306
50445
|
//#region src/cli/utils/open-url.ts
|
|
50307
50446
|
const resolveOpenCommand = (url) => {
|
|
50308
50447
|
if (process$1.platform === "darwin") return {
|
|
@@ -50758,22 +50897,22 @@ const buildAgentHookScript = () => [
|
|
|
50758
50897
|
"",
|
|
50759
50898
|
"run_react_doctor() {",
|
|
50760
50899
|
" if [ -x ./node_modules/.bin/react-doctor ]; then",
|
|
50761
|
-
" ./node_modules/.bin/react-doctor --verbose --
|
|
50900
|
+
" ./node_modules/.bin/react-doctor --verbose --scope changed --blocking warning --no-score",
|
|
50762
50901
|
" return",
|
|
50763
50902
|
" fi",
|
|
50764
50903
|
"",
|
|
50765
50904
|
" if command -v react-doctor >/dev/null 2>&1; then",
|
|
50766
|
-
" react-doctor --verbose --
|
|
50905
|
+
" react-doctor --verbose --scope changed --blocking warning --no-score",
|
|
50767
50906
|
" return",
|
|
50768
50907
|
" fi",
|
|
50769
50908
|
"",
|
|
50770
50909
|
" if command -v pnpm >/dev/null 2>&1; then",
|
|
50771
|
-
" pnpm dlx react-doctor@latest --verbose --
|
|
50910
|
+
" pnpm dlx react-doctor@latest --verbose --scope changed --blocking warning --no-score",
|
|
50772
50911
|
" return",
|
|
50773
50912
|
" fi",
|
|
50774
50913
|
"",
|
|
50775
50914
|
" if command -v npx >/dev/null 2>&1; then",
|
|
50776
|
-
" npx --yes react-doctor@latest --verbose --
|
|
50915
|
+
" npx --yes react-doctor@latest --verbose --scope changed --blocking warning --no-score",
|
|
50777
50916
|
" return",
|
|
50778
50917
|
" fi",
|
|
50779
50918
|
"",
|
|
@@ -50931,13 +51070,13 @@ const installPackageJsonHook = (options, strategy) => {
|
|
|
50931
51070
|
const packageJsonPath = getPackageJsonPath(options.projectRoot);
|
|
50932
51071
|
const didHookExist = NFS.existsSync(packageJsonPath);
|
|
50933
51072
|
const packageJson = readPackageJson(options.projectRoot);
|
|
50934
|
-
const nextPackageJson = isRecord(packageJson) ? { ...packageJson } : {};
|
|
51073
|
+
const nextPackageJson = isRecord$1(packageJson) ? { ...packageJson } : {};
|
|
50935
51074
|
const parentKeys = strategy.path.slice(0, -1);
|
|
50936
51075
|
const leafKey = strategy.path[strategy.path.length - 1];
|
|
50937
51076
|
let parent = nextPackageJson;
|
|
50938
51077
|
for (const key of parentKeys) {
|
|
50939
51078
|
const existing = parent[key];
|
|
50940
|
-
const cloned = isRecord(existing) ? { ...existing } : {};
|
|
51079
|
+
const cloned = isRecord$1(existing) ? { ...existing } : {};
|
|
50941
51080
|
parent[key] = cloned;
|
|
50942
51081
|
parent = cloned;
|
|
50943
51082
|
}
|
|
@@ -51108,7 +51247,7 @@ const isHuskyProject = (projectRoot) => NFS.existsSync(Path.join(projectRoot, ".
|
|
|
51108
51247
|
const isVitePlusProject = (projectRoot) => packageHasDependency(projectRoot, "vite-plus");
|
|
51109
51248
|
const isSimpleGitHooksProject = (projectRoot) => {
|
|
51110
51249
|
const packageJson = readPackageJson(projectRoot);
|
|
51111
|
-
return isRecord(packageJson) && isRecord(packageJson["simple-git-hooks"]) || packageHasDependency(projectRoot, "simple-git-hooks") || NFS.existsSync(Path.join(projectRoot, ".simple-git-hooks.cjs"));
|
|
51250
|
+
return isRecord$1(packageJson) && isRecord$1(packageJson["simple-git-hooks"]) || packageHasDependency(projectRoot, "simple-git-hooks") || NFS.existsSync(Path.join(projectRoot, ".simple-git-hooks.cjs"));
|
|
51112
51251
|
};
|
|
51113
51252
|
const getLefthookConfigPath = (projectRoot) => {
|
|
51114
51253
|
for (const fileName of LEFTHOOK_CONFIG_FILES) {
|
|
@@ -51274,7 +51413,7 @@ const detectPackageManager = (projectRoot) => {
|
|
|
51274
51413
|
let currentDirectory = Path.resolve(projectRoot);
|
|
51275
51414
|
while (true) {
|
|
51276
51415
|
const packageJson = readPackageJson(currentDirectory);
|
|
51277
|
-
if (isRecord(packageJson) && typeof packageJson.packageManager === "string") {
|
|
51416
|
+
if (isRecord$1(packageJson) && typeof packageJson.packageManager === "string") {
|
|
51278
51417
|
const packageManagerName = packageJson.packageManager.split("@")[0];
|
|
51279
51418
|
if (packageManagerName === "pnpm" || packageManagerName === "yarn" || packageManagerName === "bun" || packageManagerName === "npm") return packageManagerName;
|
|
51280
51419
|
}
|
|
@@ -51350,12 +51489,12 @@ const isSupplyChainTrustError = (error) => {
|
|
|
51350
51489
|
const formatInstallCommand = (input) => [input.command, ...input.args].join(" ");
|
|
51351
51490
|
const installReactDoctorDependency = async (options) => {
|
|
51352
51491
|
const packageJson = readPackageJson(options.projectRoot);
|
|
51353
|
-
if (!isRecord(packageJson)) return {
|
|
51492
|
+
if (!isRecord$1(packageJson)) return {
|
|
51354
51493
|
dependencyStatus: "skipped",
|
|
51355
51494
|
dependencyReason: "missing-or-invalid-package-json"
|
|
51356
51495
|
};
|
|
51357
51496
|
if (hasDoctorDependency(packageJson)) return { dependencyStatus: "existing" };
|
|
51358
|
-
if (packageJson.devDependencies !== void 0 && !isRecord(packageJson.devDependencies)) return {
|
|
51497
|
+
if (packageJson.devDependencies !== void 0 && !isRecord$1(packageJson.devDependencies)) return {
|
|
51359
51498
|
dependencyStatus: "skipped",
|
|
51360
51499
|
dependencyReason: "invalid-dev-dependencies"
|
|
51361
51500
|
};
|
|
@@ -51519,10 +51658,12 @@ const runInstallReactDoctor = async (options = {}) => {
|
|
|
51519
51658
|
const existingWorkflow = readReactDoctorWorkflow(projectRoot);
|
|
51520
51659
|
const canInstallWorkflow = !NFS.existsSync(workflowTargetPath);
|
|
51521
51660
|
const canUpgradeWorkflow = existingWorkflow !== null && workflowUsesV1Action(existingWorkflow.content) && !hasHandledActionUpgrade(projectRoot);
|
|
51522
|
-
const
|
|
51661
|
+
const ciPromptOutcome = canInstallWorkflow && !options.yes && !skipPrompts && !hasHandledCiPrompt(projectRoot) ? await askAddToGitHubActions(prompt) : null;
|
|
51662
|
+
const shouldInstallWorkflow = canInstallWorkflow && (Boolean(options.yes) || ciPromptOutcome === "yes");
|
|
51523
51663
|
const upgradePromptOutcome = canUpgradeWorkflow && !options.yes && !skipPrompts ? await askUpgradeActionVersion(prompt) : null;
|
|
51524
51664
|
const shouldUpgradeWorkflow = canUpgradeWorkflow && (Boolean(options.yes) || upgradePromptOutcome === "yes");
|
|
51525
51665
|
if (upgradePromptOutcome === "no" && !options.dryRun) recordActionUpgradeDecision(projectRoot, "declined");
|
|
51666
|
+
if ((ciPromptOutcome === "yes" || ciPromptOutcome === "no") && !options.dryRun) recordCiPromptDecision(projectRoot, ciPromptOutcome === "yes" ? "accepted" : "declined");
|
|
51526
51667
|
const selectedAgents = skipPrompts ? detectedAgents : (await prompt({
|
|
51527
51668
|
type: "multiselect",
|
|
51528
51669
|
name: "agents",
|
|
@@ -51769,18 +51910,24 @@ const handoffToAgent = async (input) => {
|
|
|
51769
51910
|
if (!input.interactive || input.diagnostics.length === 0) return;
|
|
51770
51911
|
cliLogger.break();
|
|
51771
51912
|
const projectRootForCi = findNearestPackageDirectory(input.rootDirectory) ?? input.rootDirectory;
|
|
51772
|
-
|
|
51913
|
+
const isGitHubActionsConfigured = isReactDoctorWorkflowInstalled(projectRootForCi);
|
|
51914
|
+
if (!isGitHubActionsConfigured && !hasHandledCiPrompt(projectRootForCi)) {
|
|
51773
51915
|
const ciOutcome = await askAddToGitHubActions();
|
|
51774
51916
|
recordCount(METRIC.agentHandoff, 1, {
|
|
51775
51917
|
outcome: `ci-${ciOutcome}`,
|
|
51776
51918
|
diagnosticsCount: input.diagnostics.length
|
|
51777
51919
|
});
|
|
51778
51920
|
if (ciOutcome === "cancel") return;
|
|
51921
|
+
recordCiPromptDecision(projectRootForCi, ciOutcome === "yes" ? "accepted" : "declined");
|
|
51779
51922
|
if (ciOutcome === "yes") {
|
|
51780
51923
|
await setUpGitHubActions({ rootDirectory: input.rootDirectory });
|
|
51781
51924
|
cliLogger.break();
|
|
51782
51925
|
}
|
|
51783
|
-
} else await maybeOfferActionUpgrade(projectRootForCi);
|
|
51926
|
+
} else if (isGitHubActionsConfigured) await maybeOfferActionUpgrade(projectRootForCi);
|
|
51927
|
+
else recordCount(METRIC.agentHandoff, 1, {
|
|
51928
|
+
outcome: "ci-suppressed",
|
|
51929
|
+
diagnosticsCount: input.diagnostics.length
|
|
51930
|
+
});
|
|
51784
51931
|
const { handoffTarget } = await prompts({
|
|
51785
51932
|
type: "select",
|
|
51786
51933
|
name: "handoffTarget",
|
|
@@ -52003,6 +52150,7 @@ const reportErrorToSentry = async (error) => {
|
|
|
52003
52150
|
sampled: runTrace.sampled,
|
|
52004
52151
|
sampleRand: Math.random()
|
|
52005
52152
|
});
|
|
52153
|
+
recordRunTraceId(scope.getPropagationContext().traceId);
|
|
52006
52154
|
return Sentry.captureException(error);
|
|
52007
52155
|
});
|
|
52008
52156
|
await Sentry.flush(SENTRY_FLUSH_TIMEOUT_MS);
|
|
@@ -52086,7 +52234,7 @@ const printMultiProjectSummary = (input) => gen(function* () {
|
|
|
52086
52234
|
yield* log(`${highlighter.success("✔")} Scanned ${totalScannedFileCount} ${totalScannedFileCount === 1 ? "file" : "files"} in ${formatElapsedTime(totalElapsedMilliseconds)}`);
|
|
52087
52235
|
if (displayDiagnostics.length > 0) {
|
|
52088
52236
|
yield* log("");
|
|
52089
|
-
yield* printDiagnostics(displayDiagnostics, verbose, resolveDiagnosticSourceRoot, buildRulePriorityMap(completedScans.map((scan) => scan.result.score)), isCodingAgentEnvironment(), { animateCountUp: animateRender });
|
|
52237
|
+
yield* printDiagnostics(displayDiagnostics, verbose, resolveDiagnosticSourceRoot, buildRulePriorityMap(completedScans.map((scan) => scan.result.score)), isCodingAgentEnvironment(), { animateCountUp: animateRender }, shouldRenderHyperlinks(process.stdout));
|
|
52090
52238
|
}
|
|
52091
52239
|
const lowestScoredScan = findLowestScoredScan(completedScans);
|
|
52092
52240
|
const aggregateScore = lowestScoredScan?.result.score ?? null;
|
|
@@ -52124,9 +52272,8 @@ const printMultiProjectSummary = (input) => gen(function* () {
|
|
|
52124
52272
|
});
|
|
52125
52273
|
//#endregion
|
|
52126
52274
|
//#region src/cli/utils/prompt-install-setup.ts
|
|
52127
|
-
const GLOBAL_CONFIG_PROJECT_NAME = "react-doctor";
|
|
52128
52275
|
const getSetupPromptStore = (options = {}) => new Conf({
|
|
52129
|
-
projectName:
|
|
52276
|
+
projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
|
|
52130
52277
|
cwd: options.cwd
|
|
52131
52278
|
});
|
|
52132
52279
|
const getSetupPromptProjectKey = (projectRoot) => hashProjectRoot(projectRoot);
|
|
@@ -52137,6 +52284,24 @@ const hasDisabledSetupPrompt = (projectRoot, storeOptions = {}) => {
|
|
|
52137
52284
|
return false;
|
|
52138
52285
|
}
|
|
52139
52286
|
};
|
|
52287
|
+
const disableSetupPrompt = (projectRoot, storeOptions = {}) => {
|
|
52288
|
+
try {
|
|
52289
|
+
const store = getSetupPromptStore(storeOptions);
|
|
52290
|
+
const projects = store.get("projects", {});
|
|
52291
|
+
const projectKey = getSetupPromptProjectKey(projectRoot);
|
|
52292
|
+
store.set("projects", {
|
|
52293
|
+
...projects,
|
|
52294
|
+
[projectKey]: {
|
|
52295
|
+
...projects[projectKey] ?? {},
|
|
52296
|
+
rootDirectory: Path.resolve(projectRoot),
|
|
52297
|
+
setupPrompt: false
|
|
52298
|
+
}
|
|
52299
|
+
});
|
|
52300
|
+
return true;
|
|
52301
|
+
} catch {
|
|
52302
|
+
return false;
|
|
52303
|
+
}
|
|
52304
|
+
};
|
|
52140
52305
|
const resolveInstallSetupProjectRoot = (options) => {
|
|
52141
52306
|
if (options.scanDirectories.length === 0) return findNearestPackageDirectory(options.scanRoot) ?? options.scanRoot;
|
|
52142
52307
|
const packageDirectories = /* @__PURE__ */ new Set();
|
|
@@ -52543,6 +52708,14 @@ const runExplain = async (fileLineArgument, context) => {
|
|
|
52543
52708
|
const colorizedRule = colorizeRuleByDiagnostic(ruleIdentifier, diagnostic.severity);
|
|
52544
52709
|
const severityLabel = colorizeRuleByDiagnostic(diagnostic.severity, diagnostic.severity);
|
|
52545
52710
|
cliLogger.log(`${severitySymbol} ${colorizedRule} ${highlighter.dim(`(${severityLabel})`)} — ${diagnostic.message}`);
|
|
52711
|
+
const codeFrame = buildCodeFrame({
|
|
52712
|
+
filePath: diagnostic.filePath,
|
|
52713
|
+
line: diagnostic.line,
|
|
52714
|
+
column: diagnostic.column,
|
|
52715
|
+
endLine: diagnostic.endLine,
|
|
52716
|
+
rootDirectory: targetDirectory
|
|
52717
|
+
});
|
|
52718
|
+
if (codeFrame) cliLogger.log(indentMultilineText(codeFrame, " "));
|
|
52546
52719
|
if (diagnostic.category) cliLogger.dim(` Category: ${diagnostic.category}`);
|
|
52547
52720
|
if (diagnostic.help) cliLogger.dim(` ${diagnostic.help}`);
|
|
52548
52721
|
cliLogger.dim(` If this needs follow-up or looks like a false positive, open: ${buildDiagnosticIssueUrl({
|
|
@@ -52592,6 +52765,10 @@ const validateModeFlags = (flags) => {
|
|
|
52592
52765
|
if (flags.staged && (flags.scope === "full" || flags.scope === "changed")) throw new CliInputError(`Cannot combine --staged with --scope ${flags.scope}; use --scope files or --scope lines, or drop --scope.`);
|
|
52593
52766
|
if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
|
|
52594
52767
|
if (flags.score && flags.telemetry === false) throw new CliInputError("Cannot combine --score with --no-telemetry; --score prints the score that --no-telemetry disables.");
|
|
52768
|
+
if (flags.debug && (flags.score === false || flags.telemetry === false)) {
|
|
52769
|
+
const disablingFlag = flags.score === false ? "--no-score" : "--no-telemetry";
|
|
52770
|
+
throw new CliInputError(`Cannot combine --debug with ${disablingFlag}; ${disablingFlag} disables the Sentry reporting --debug needs to capture a trace.`);
|
|
52771
|
+
}
|
|
52595
52772
|
};
|
|
52596
52773
|
//#endregion
|
|
52597
52774
|
//#region src/cli/commands/inspect.ts
|
|
@@ -52939,11 +53116,13 @@ const inspectAction = async (directory, flags) => {
|
|
|
52939
53116
|
})) {
|
|
52940
53117
|
printAgentInstallHint();
|
|
52941
53118
|
recordCount(METRIC.agentInstallHintShown, 1);
|
|
53119
|
+
disableSetupPrompt(setupProjectRoot);
|
|
52942
53120
|
}
|
|
52943
53121
|
}
|
|
52944
53122
|
} catch (error) {
|
|
52945
53123
|
const isUserError = isExpectedUserError(error);
|
|
52946
53124
|
const sentryEventId = isUserError ? void 0 : await reportErrorToSentry(error);
|
|
53125
|
+
if (isDebugFlagEnabled()) await flushSentry();
|
|
52947
53126
|
if (isJsonMode) {
|
|
52948
53127
|
writeJsonErrorReport(error, sentryEventId);
|
|
52949
53128
|
process.exitCode = 1;
|
|
@@ -53666,6 +53845,33 @@ const normalizeHelpInvocation = (argv, knownCommands) => {
|
|
|
53666
53845
|
return [...nodeArguments, "--help"];
|
|
53667
53846
|
};
|
|
53668
53847
|
//#endregion
|
|
53848
|
+
//#region src/cli/utils/print-debug-trace.ts
|
|
53849
|
+
/**
|
|
53850
|
+
* The `--debug` end-of-run line, pure so it's testable without the Sentry SDK.
|
|
53851
|
+
* Mirrors the crash-reference phrasing in `handle-error.ts` ("mention this when
|
|
53852
|
+
* reporting") so users learn one habit for both paths. A `null` trace says why,
|
|
53853
|
+
* so `--debug` never silently does nothing.
|
|
53854
|
+
*/
|
|
53855
|
+
const buildDebugTraceMessage = (traceId) => traceId === null ? "Sentry trace unavailable for this run (no trace was recorded)." : `Sentry trace (mention this when reporting): ${traceId}`;
|
|
53856
|
+
/**
|
|
53857
|
+
* Prints the run's Sentry trace id to stderr at the end of a `--debug` run, so
|
|
53858
|
+
* maintainers can pull the full trace from a pasted id. Runs from the process
|
|
53859
|
+
* `exit` handler, so it's the last line on both the success path and the error
|
|
53860
|
+
* funnels (which `process.exit()` before the promise chain could resume).
|
|
53861
|
+
*
|
|
53862
|
+
* Writes straight to `process.stderr` (not `Console`) for three reasons: the
|
|
53863
|
+
* exit handler is synchronous, JSON mode patches the global console to no-ops —
|
|
53864
|
+
* a diagnostic the user explicitly asked for must survive that — and stderr
|
|
53865
|
+
* keeps `--json` / `--score` stdout machine-clean. The write is wrapped because
|
|
53866
|
+
* a diagnostic must never throw out of an exit handler.
|
|
53867
|
+
*/
|
|
53868
|
+
const printDebugTrace = () => {
|
|
53869
|
+
if (!Sentry.isInitialized()) return;
|
|
53870
|
+
try {
|
|
53871
|
+
process.stderr.write(`${highlighter.dim(buildDebugTraceMessage(getLastRunTraceId()))}\n`);
|
|
53872
|
+
} catch {}
|
|
53873
|
+
};
|
|
53874
|
+
//#endregion
|
|
53669
53875
|
//#region src/cli/utils/removed-cli-flags.ts
|
|
53670
53876
|
const REMOVED_FLAGS = new Map([
|
|
53671
53877
|
["--full", "use `--diff false` to force a full scan"],
|
|
@@ -53692,6 +53898,7 @@ const ROOT_FLAG_SPEC = {
|
|
|
53692
53898
|
longOptionsWithoutValues: new Set([
|
|
53693
53899
|
"--color",
|
|
53694
53900
|
"--dead-code",
|
|
53901
|
+
"--debug",
|
|
53695
53902
|
"--help",
|
|
53696
53903
|
"--json",
|
|
53697
53904
|
"--json-compact",
|
|
@@ -53859,6 +54066,9 @@ const stripUnknownCliFlags = (argv) => {
|
|
|
53859
54066
|
initializeSentry();
|
|
53860
54067
|
process.on("SIGINT", exitGracefully);
|
|
53861
54068
|
process.on("SIGTERM", exitGracefully);
|
|
54069
|
+
process.on("exit", () => {
|
|
54070
|
+
if (isDebugFlagEnabled()) printDebugTrace();
|
|
54071
|
+
});
|
|
53862
54072
|
unrefStdin();
|
|
53863
54073
|
guardStdin();
|
|
53864
54074
|
const formatExampleLines = (examples) => {
|
|
@@ -53870,7 +54080,7 @@ ${highlighter.dim("Examples:")}
|
|
|
53870
54080
|
${formatExampleLines([
|
|
53871
54081
|
["react-doctor", "scan the current project"],
|
|
53872
54082
|
["react-doctor ./apps/web", "scan a specific directory"],
|
|
53873
|
-
["react-doctor --
|
|
54083
|
+
["react-doctor --scope changed --base main", "scan only new issues vs. main"],
|
|
53874
54084
|
["react-doctor --project modules/a,modules/b", "score each module separately (names or paths)"],
|
|
53875
54085
|
["react-doctor --staged", "scan staged files (pre-commit hook)"],
|
|
53876
54086
|
["react-doctor --category Security", "show only one diagnostic category"],
|
|
@@ -53903,7 +54113,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
53903
54113
|
${highlighter.info(CANONICAL_GITHUB_URL)}
|
|
53904
54114
|
`;
|
|
53905
54115
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
53906
|
-
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
54116
|
+
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
53907
54117
|
program.action(inspectAction);
|
|
53908
54118
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
53909
54119
|
program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
|
|
@@ -53946,4 +54156,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
53946
54156
|
export {};
|
|
53947
54157
|
|
|
53948
54158
|
//# sourceMappingURL=cli.js.map
|
|
53949
|
-
//# debugId=
|
|
54159
|
+
//# debugId=f029f05b-4c71-52d3-b523-0834d67de2d4
|