react-doctor 0.5.6-dev.6b8e756 → 0.5.6-dev.740211c
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 +465 -260
- package/dist/index.d.ts +10 -0
- package/dist/index.js +97 -76
- package/dist/lsp.js +115 -98
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="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
|
}
|
|
@@ -39692,15 +39687,10 @@ const buildCapabilities = (project) => {
|
|
|
39692
39687
|
}
|
|
39693
39688
|
if (project.tailwindVersion !== null) {
|
|
39694
39689
|
capabilities.add("tailwind");
|
|
39695
|
-
|
|
39696
|
-
if (isTailwindAtLeast(tailwind, {
|
|
39690
|
+
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
39697
39691
|
major: 3,
|
|
39698
39692
|
minor: 4
|
|
39699
39693
|
})) capabilities.add("tailwind:3.4");
|
|
39700
|
-
if (tailwind !== null && isTailwindAtLeast(tailwind, {
|
|
39701
|
-
major: 4,
|
|
39702
|
-
minor: 0
|
|
39703
|
-
})) capabilities.add("tailwind:4");
|
|
39704
39694
|
}
|
|
39705
39695
|
if (project.zodVersion !== null) {
|
|
39706
39696
|
capabilities.add("zod");
|
|
@@ -39908,7 +39898,7 @@ const readIgnoreFile = (filePath) => {
|
|
|
39908
39898
|
try {
|
|
39909
39899
|
content = NFS.readFileSync(filePath, "utf-8");
|
|
39910
39900
|
} catch (error) {
|
|
39911
|
-
const errnoCode = error
|
|
39901
|
+
const errnoCode = isErrnoException(error) ? error.code : void 0;
|
|
39912
39902
|
if (errnoCode && errnoCode !== "ENOENT") runSync(warn$1(`Could not read ignore file ${filePath}: ${errnoCode}`));
|
|
39913
39903
|
return [];
|
|
39914
39904
|
}
|
|
@@ -39946,8 +39936,8 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
39946
39936
|
cachedPatternsByRoot.set(rootDirectory, patterns);
|
|
39947
39937
|
return patterns;
|
|
39948
39938
|
};
|
|
39939
|
+
const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39949
39940
|
const KNIP_JSON_FILENAME = "knip.json";
|
|
39950
|
-
const isRecord$1$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39951
39941
|
const readJsonFileSafe = (filePath) => {
|
|
39952
39942
|
let rawContents;
|
|
39953
39943
|
try {
|
|
@@ -39963,10 +39953,10 @@ const readJsonFileSafe = (filePath) => {
|
|
|
39963
39953
|
};
|
|
39964
39954
|
const readKnipConfig = (rootDirectory) => {
|
|
39965
39955
|
const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
|
|
39966
|
-
if (isRecord$
|
|
39956
|
+
if (isRecord$2(knipJson)) return knipJson;
|
|
39967
39957
|
const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
|
|
39968
|
-
const packageKnipConfig = isRecord$
|
|
39969
|
-
return isRecord$
|
|
39958
|
+
const packageKnipConfig = isRecord$2(packageJson) ? packageJson.knip : null;
|
|
39959
|
+
return isRecord$2(packageKnipConfig) ? packageKnipConfig : null;
|
|
39970
39960
|
};
|
|
39971
39961
|
const normalizePatternList = (value) => {
|
|
39972
39962
|
if (typeof value === "string" && value.length > 0) return [value];
|
|
@@ -39978,10 +39968,10 @@ const prefixWorkspacePatterns = (workspacePattern, patterns) => {
|
|
|
39978
39968
|
return patterns.map((pattern) => pattern.startsWith("!") ? `!${normalizedWorkspacePattern}/${pattern.slice(1)}` : `${normalizedWorkspacePattern}/${pattern}`);
|
|
39979
39969
|
};
|
|
39980
39970
|
const collectKnipWorkspacePatterns = (workspaces, settingName) => {
|
|
39981
|
-
if (!isRecord$
|
|
39971
|
+
if (!isRecord$2(workspaces)) return [];
|
|
39982
39972
|
const patterns = [];
|
|
39983
39973
|
for (const [workspacePattern, workspaceConfig] of Object.entries(workspaces)) {
|
|
39984
|
-
if (!isRecord$
|
|
39974
|
+
if (!isRecord$2(workspaceConfig)) continue;
|
|
39985
39975
|
patterns.push(...prefixWorkspacePatterns(workspacePattern, normalizePatternList(workspaceConfig[settingName])));
|
|
39986
39976
|
}
|
|
39987
39977
|
return patterns;
|
|
@@ -40026,8 +40016,6 @@ const toCanonicalPath = (filePath) => {
|
|
|
40026
40016
|
};
|
|
40027
40017
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
40028
40018
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
40029
|
-
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
40030
|
-
const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40031
40019
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
40032
40020
|
const inputChunks = [];
|
|
40033
40021
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -40085,7 +40073,7 @@ process.stdin.on("end", () => {
|
|
|
40085
40073
|
});
|
|
40086
40074
|
`;
|
|
40087
40075
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
40088
|
-
for (const filename of TSCONFIG_FILENAMES
|
|
40076
|
+
for (const filename of TSCONFIG_FILENAMES) {
|
|
40089
40077
|
const candidate = Path.join(rootDirectory, filename);
|
|
40090
40078
|
if (NFS.existsSync(candidate)) return candidate;
|
|
40091
40079
|
}
|
|
@@ -40466,15 +40454,13 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
40466
40454
|
})()) }));
|
|
40467
40455
|
static layerOf = (diagnostics) => succeed$3(DeadCode, DeadCode.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40468
40456
|
};
|
|
40469
|
-
const createNodeReadFileLinesSync = (rootDirectory) => {
|
|
40470
|
-
|
|
40471
|
-
|
|
40472
|
-
|
|
40473
|
-
|
|
40474
|
-
|
|
40475
|
-
|
|
40476
|
-
}
|
|
40477
|
-
};
|
|
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
|
+
}
|
|
40478
40464
|
};
|
|
40479
40465
|
var Files = class Files extends Service()("react-doctor/Files") {
|
|
40480
40466
|
static layerNode = succeed$3(Files, Files.of({
|
|
@@ -40685,7 +40671,10 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40685
40671
|
directory: input.directory,
|
|
40686
40672
|
cause
|
|
40687
40673
|
}) });
|
|
40688
|
-
})
|
|
40674
|
+
}), withSpan("git.exec", { attributes: {
|
|
40675
|
+
"git.command": input.command,
|
|
40676
|
+
"git.subcommand": input.args[0] ?? ""
|
|
40677
|
+
} }));
|
|
40689
40678
|
const runGit = (directory, args) => runCommand({
|
|
40690
40679
|
command: "git",
|
|
40691
40680
|
args,
|
|
@@ -40713,7 +40702,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40713
40702
|
]);
|
|
40714
40703
|
if (candidates.status !== 0) return null;
|
|
40715
40704
|
return trimOrNull(candidates.stdout.split("\n")[0] ?? "");
|
|
40716
|
-
});
|
|
40705
|
+
}).pipe(withSpan("Git.defaultBranch"));
|
|
40717
40706
|
const branchExists = (directory, branch) => runGit(directory, [
|
|
40718
40707
|
"rev-parse",
|
|
40719
40708
|
"--verify",
|
|
@@ -40760,7 +40749,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40760
40749
|
const result = resultOption.value;
|
|
40761
40750
|
if (result.status !== 0) return null;
|
|
40762
40751
|
return parseGithubViewerPermission(result.stdout);
|
|
40763
|
-
}).pipe(catch_$1(() => succeed$2(null)));
|
|
40752
|
+
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.githubViewerPermission"));
|
|
40764
40753
|
/**
|
|
40765
40754
|
* Resolves a `--diff A..B` / `A...B` commit range into a changed-file
|
|
40766
40755
|
* selection. Each endpoint is validated with `isSafeGitRevision`
|
|
@@ -40874,7 +40863,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40874
40863
|
changedFiles: splitNullSeparated(diff.stdout),
|
|
40875
40864
|
isCurrentChanges: false
|
|
40876
40865
|
};
|
|
40877
|
-
}),
|
|
40866
|
+
}).pipe(withSpan("Git.diffSelection")),
|
|
40878
40867
|
stagedFilePaths: (directory) => runGit(directory, [
|
|
40879
40868
|
"diff",
|
|
40880
40869
|
"--cached",
|
|
@@ -40916,7 +40905,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40916
40905
|
status: result.status,
|
|
40917
40906
|
stdout: result.stdout
|
|
40918
40907
|
};
|
|
40919
|
-
}),
|
|
40908
|
+
}).pipe(withSpan("Git.grep")),
|
|
40920
40909
|
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
40921
40910
|
if (files.length === 0) return [];
|
|
40922
40911
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
@@ -40932,7 +40921,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40932
40921
|
]);
|
|
40933
40922
|
if (result.status !== 0) return null;
|
|
40934
40923
|
return parseChangedLineRanges(result.stdout);
|
|
40935
|
-
})
|
|
40924
|
+
}).pipe(withSpan("Git.changedLineRanges"))
|
|
40936
40925
|
});
|
|
40937
40926
|
})).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
|
|
40938
40927
|
/**
|
|
@@ -41147,7 +41136,7 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
|
|
|
41147
41136
|
for (const [absolutePath, originalContent] of originalContents) try {
|
|
41148
41137
|
NFS.writeFileSync(absolutePath, originalContent);
|
|
41149
41138
|
} catch (error) {
|
|
41150
|
-
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`);
|
|
41151
41140
|
}
|
|
41152
41141
|
};
|
|
41153
41142
|
const onExit = () => restore();
|
|
@@ -41253,7 +41242,7 @@ const resolveUserPlugin = (spec, configSourceDirectory) => {
|
|
|
41253
41242
|
try {
|
|
41254
41243
|
resolvedSpecifier = isRelative ? Path.resolve(configSourceDirectory, spec) : candidateRequire.resolve(spec);
|
|
41255
41244
|
} catch (error) {
|
|
41256
|
-
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)}`);
|
|
41257
41246
|
return null;
|
|
41258
41247
|
}
|
|
41259
41248
|
const { name, ruleNames } = readPluginShape(resolvedSpecifier, (target) => candidateRequire(target));
|
|
@@ -41325,8 +41314,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
41325
41314
|
}
|
|
41326
41315
|
return enabled;
|
|
41327
41316
|
};
|
|
41328
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
|
|
41329
|
-
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);
|
|
41330
41319
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
41331
41320
|
const jsPlugins = [];
|
|
41332
41321
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
@@ -41386,7 +41375,6 @@ const resolveOxlintBinary = () => {
|
|
|
41386
41375
|
return Path.join(oxlintPackageDirectory, "bin", "oxlint");
|
|
41387
41376
|
};
|
|
41388
41377
|
const resolvePluginPath = () => esmRequire.resolve("oxlint-plugin-react-doctor");
|
|
41389
|
-
const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
|
|
41390
41378
|
const resolveTsConfigRelativePath = (rootDirectory) => {
|
|
41391
41379
|
for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
|
|
41392
41380
|
return null;
|
|
@@ -41758,7 +41746,7 @@ const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
|
|
|
41758
41746
|
const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
|
|
41759
41747
|
let currentNode = identifier.parent;
|
|
41760
41748
|
while (currentNode) {
|
|
41761
|
-
if (
|
|
41749
|
+
if (isScopeBoundary(currentNode)) {
|
|
41762
41750
|
if (scopeContainsNonImportBinding(currentNode, currentNode, identifier.text)) return true;
|
|
41763
41751
|
}
|
|
41764
41752
|
if (currentNode === sourceFile) return false;
|
|
@@ -41849,11 +41837,10 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
|
|
|
41849
41837
|
});
|
|
41850
41838
|
return resolution;
|
|
41851
41839
|
};
|
|
41852
|
-
const isScopeNode = isScopeBoundary;
|
|
41853
41840
|
const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, visitedDeclarations = /* @__PURE__ */ new Set()) => {
|
|
41854
41841
|
let currentNode = identifier.parent;
|
|
41855
41842
|
while (currentNode) {
|
|
41856
|
-
if (
|
|
41843
|
+
if (isScopeBoundary(currentNode)) {
|
|
41857
41844
|
const resolution = findResolutionInScope(currentNode, identifier.text, reactImportBindings, sourceFile, visitedDeclarations);
|
|
41858
41845
|
if (resolution) return resolution;
|
|
41859
41846
|
}
|
|
@@ -42023,9 +42010,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
42023
42010
|
try {
|
|
42024
42011
|
parsed = JSON.parse(sanitizedStdout);
|
|
42025
42012
|
} catch {
|
|
42026
|
-
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0,
|
|
42013
|
+
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
42027
42014
|
}
|
|
42028
|
-
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) }) });
|
|
42029
42016
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
42030
42017
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
42031
42018
|
const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
@@ -42101,7 +42088,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
42101
42088
|
child.kill("SIGKILL");
|
|
42102
42089
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
42103
42090
|
kind: "timeout",
|
|
42104
|
-
detail: `${spawnTimeoutMs /
|
|
42091
|
+
detail: `${spawnTimeoutMs / MILLISECONDS_PER_SECOND}s budget exceeded`
|
|
42105
42092
|
}) }));
|
|
42106
42093
|
}, spawnTimeoutMs);
|
|
42107
42094
|
timeoutHandle.unref?.();
|
|
@@ -42316,6 +42303,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
42316
42303
|
NFS.closeSync(fileHandle);
|
|
42317
42304
|
}
|
|
42318
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
|
+
};
|
|
42319
42328
|
/**
|
|
42320
42329
|
* The oxlint runner. Composed of three pieces in `runners/oxlint/`:
|
|
42321
42330
|
*
|
|
@@ -42343,15 +42352,16 @@ const runOxlint = async (options) => {
|
|
|
42343
42352
|
const pluginPath = resolvePluginPath();
|
|
42344
42353
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
42345
42354
|
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
42346
|
-
const buildConfig = (
|
|
42355
|
+
const buildConfig = (overrides) => createOxlintConfig({
|
|
42347
42356
|
pluginPath,
|
|
42348
42357
|
project,
|
|
42349
42358
|
customRulesOnly,
|
|
42350
|
-
extendsPaths:
|
|
42359
|
+
extendsPaths: overrides.extendsPaths,
|
|
42351
42360
|
ignoredTags,
|
|
42352
42361
|
serverAuthFunctionNames,
|
|
42353
42362
|
severityControls,
|
|
42354
|
-
userPlugins
|
|
42363
|
+
userPlugins,
|
|
42364
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
42355
42365
|
});
|
|
42356
42366
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
42357
42367
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -42387,12 +42397,22 @@ const runOxlint = async (options) => {
|
|
|
42387
42397
|
outputMaxBytes,
|
|
42388
42398
|
concurrency: options.concurrency
|
|
42389
42399
|
});
|
|
42390
|
-
writeOxlintConfig(configPath, buildConfig(extendsPaths));
|
|
42400
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
42391
42401
|
try {
|
|
42392
42402
|
return await runBatches();
|
|
42393
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
|
+
}
|
|
42394
42414
|
if (extendsPaths.length === 0) throw error;
|
|
42395
|
-
writeOxlintConfig(configPath, buildConfig([]));
|
|
42415
|
+
writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
|
|
42396
42416
|
return await runBatches();
|
|
42397
42417
|
}
|
|
42398
42418
|
} finally {
|
|
@@ -43190,7 +43210,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
43190
43210
|
}))))))));
|
|
43191
43211
|
const deadCodeFailureState = yield* get$2(deadCodeFailure);
|
|
43192
43212
|
const scanElapsedMilliseconds = Date.now() - scanStartTime;
|
|
43193
|
-
const scanElapsedSeconds = (scanElapsedMilliseconds /
|
|
43213
|
+
const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
|
|
43194
43214
|
if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
|
|
43195
43215
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
43196
43216
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
@@ -43427,7 +43447,7 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
|
|
|
43427
43447
|
static layerNode = effect(StagedFiles, gen(function* () {
|
|
43428
43448
|
const git = yield* Git;
|
|
43429
43449
|
return StagedFiles.of({
|
|
43430
|
-
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")),
|
|
43431
43451
|
materialize: ({ directory, stagedFiles, tempDirectory }) => materializeSourceTree({
|
|
43432
43452
|
directory,
|
|
43433
43453
|
files: stagedFiles,
|
|
@@ -43437,7 +43457,7 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
|
|
|
43437
43457
|
tempDirectory: tree.tempDirectory,
|
|
43438
43458
|
stagedFiles: tree.materializedFiles,
|
|
43439
43459
|
cleanup: tree.cleanup
|
|
43440
|
-
})))
|
|
43460
|
+
})), withSpan("StagedFiles.materialize"))
|
|
43441
43461
|
});
|
|
43442
43462
|
}));
|
|
43443
43463
|
/**
|
|
@@ -43570,6 +43590,7 @@ const buildJsonReport = (input) => {
|
|
|
43570
43590
|
score: result.score,
|
|
43571
43591
|
skippedChecks: result.skippedChecks,
|
|
43572
43592
|
...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
|
|
43593
|
+
...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
|
|
43573
43594
|
elapsedMilliseconds: result.elapsedMilliseconds
|
|
43574
43595
|
}));
|
|
43575
43596
|
const flattenedDiagnostics = projects.flatMap((entry) => entry.diagnostics);
|
|
@@ -43844,7 +43865,7 @@ const FALSY_CI_FLAG_VALUES = new Set([
|
|
|
43844
43865
|
"false"
|
|
43845
43866
|
]);
|
|
43846
43867
|
const isCiFlagSet = (value) => value !== void 0 && !FALSY_CI_FLAG_VALUES.has(value.toLowerCase());
|
|
43847
|
-
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);
|
|
43848
43869
|
const detectCiProvider = () => {
|
|
43849
43870
|
for (const [environmentVariable, provider] of CI_PROVIDER_BY_ENVIRONMENT_VARIABLE) if (process.env[environmentVariable]) return provider;
|
|
43850
43871
|
return isCiFlagSet(process.env.CI) ? "unknown" : null;
|
|
@@ -43869,6 +43890,53 @@ const detectCodingAgent = () => {
|
|
|
43869
43890
|
const isCodingAgentEnvironment = () => detectCodingAgent() !== null;
|
|
43870
43891
|
const isCiOrCodingAgentEnvironment = () => isCiEnvironment() || isCodingAgentEnvironment();
|
|
43871
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
|
|
43872
43940
|
//#region src/cli/utils/is-git-hook-environment.ts
|
|
43873
43941
|
const isGitHookEnvironment = () => Boolean(process.env.GIT_DIR);
|
|
43874
43942
|
//#endregion
|
|
@@ -43891,6 +43959,7 @@ const NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
|
|
|
43891
43959
|
const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some((envVariable) => Boolean(process.env[envVariable])) || isCodingAgentEnvironment();
|
|
43892
43960
|
//#endregion
|
|
43893
43961
|
//#region src/cli/utils/constants.ts
|
|
43962
|
+
const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
|
|
43894
43963
|
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
43895
43964
|
const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
|
|
43896
43965
|
const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
|
|
@@ -43975,7 +44044,7 @@ const makeNoopConsole = () => ({
|
|
|
43975
44044
|
});
|
|
43976
44045
|
//#endregion
|
|
43977
44046
|
//#region src/cli/utils/version.ts
|
|
43978
|
-
const VERSION = "0.5.6-dev.
|
|
44047
|
+
const VERSION = "0.5.6-dev.740211c";
|
|
43979
44048
|
//#endregion
|
|
43980
44049
|
//#region src/cli/utils/json-mode.ts
|
|
43981
44050
|
let context = null;
|
|
@@ -44125,7 +44194,9 @@ const buildRunContext = () => {
|
|
|
44125
44194
|
viaAction: isOfficialGithubAction(),
|
|
44126
44195
|
codingAgent: detectCodingAgent(),
|
|
44127
44196
|
interactive: !isNonInteractiveEnvironment(),
|
|
44197
|
+
terminalKind: detectTerminalKind(),
|
|
44128
44198
|
jsonMode: isJsonModeActive(),
|
|
44199
|
+
debug: isDebugFlagEnabled(),
|
|
44129
44200
|
invokedVia: detectInvokedVia()
|
|
44130
44201
|
};
|
|
44131
44202
|
};
|
|
@@ -44195,7 +44266,9 @@ const buildSentryScope = (runContext = buildRunContext()) => {
|
|
|
44195
44266
|
viaAction: runContext.viaAction,
|
|
44196
44267
|
codingAgent: runContext.codingAgent,
|
|
44197
44268
|
interactive: runContext.interactive,
|
|
44269
|
+
terminalKind: runContext.terminalKind,
|
|
44198
44270
|
jsonMode: runContext.jsonMode,
|
|
44271
|
+
debug: runContext.debug,
|
|
44199
44272
|
invokedVia: runContext.invokedVia,
|
|
44200
44273
|
nodeMajor: runContext.nodeMajor
|
|
44201
44274
|
};
|
|
@@ -44333,13 +44406,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
44333
44406
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
44334
44407
|
* standard `SENTRY_RELEASE` override.
|
|
44335
44408
|
*/
|
|
44336
|
-
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.740211c`;
|
|
44337
44410
|
/**
|
|
44338
44411
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
44339
44412
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
44340
44413
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
44341
44414
|
*/
|
|
44342
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.
|
|
44415
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.740211c") ? "development" : "production");
|
|
44343
44416
|
/**
|
|
44344
44417
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
44345
44418
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -44403,7 +44476,7 @@ const flushSentry = async () => {
|
|
|
44403
44476
|
const initializeSentry = () => {
|
|
44404
44477
|
if (isInitialized || !shouldEnableSentry()) return;
|
|
44405
44478
|
isInitialized = true;
|
|
44406
|
-
resolvedTracesSampleRate = resolveTracesSampleRate();
|
|
44479
|
+
resolvedTracesSampleRate = isDebugFlagEnabled() ? 1 : resolveTracesSampleRate();
|
|
44407
44480
|
const { tags, contexts } = buildSentryScope();
|
|
44408
44481
|
Sentry.init({
|
|
44409
44482
|
dsn: process.env.SENTRY_DSN || "https://f253d570240a59b8dbd77b7a548ef133@o4510226365743104.ingest.us.sentry.io/4511487817809920",
|
|
@@ -47619,6 +47692,11 @@ const setActiveRunTrace = (trace) => {
|
|
|
47619
47692
|
activeRunTrace = trace;
|
|
47620
47693
|
};
|
|
47621
47694
|
const getActiveRunTrace = () => activeRunTrace;
|
|
47695
|
+
let lastRunTraceId = null;
|
|
47696
|
+
const recordRunTraceId = (traceId) => {
|
|
47697
|
+
lastRunTraceId = traceId;
|
|
47698
|
+
};
|
|
47699
|
+
const getLastRunTraceId = () => lastRunTraceId;
|
|
47622
47700
|
//#endregion
|
|
47623
47701
|
//#region src/cli/utils/to-span-attributes.ts
|
|
47624
47702
|
/**
|
|
@@ -47681,14 +47759,13 @@ const withSentryRunSpan = (run, options = {}) => {
|
|
|
47681
47759
|
op: "cli.inspect",
|
|
47682
47760
|
attributes: toSpanAttributes(tags)
|
|
47683
47761
|
}, (rootSpan) => {
|
|
47684
|
-
|
|
47685
|
-
|
|
47686
|
-
|
|
47687
|
-
|
|
47688
|
-
|
|
47689
|
-
|
|
47690
|
-
|
|
47691
|
-
}
|
|
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
|
+
});
|
|
47692
47769
|
return run(rootSpan);
|
|
47693
47770
|
});
|
|
47694
47771
|
};
|
|
@@ -48196,6 +48273,15 @@ const boxText = (content, innerWidth) => {
|
|
|
48196
48273
|
].join("\n");
|
|
48197
48274
|
};
|
|
48198
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
|
|
48199
48285
|
//#region src/cli/utils/build-code-frame.ts
|
|
48200
48286
|
/**
|
|
48201
48287
|
* Renders a syntax-highlighted source excerpt around a diagnostic site
|
|
@@ -48206,7 +48292,7 @@ const boxText = (content, innerWidth) => {
|
|
|
48206
48292
|
*/
|
|
48207
48293
|
const buildCodeFrame = (input) => {
|
|
48208
48294
|
if (input.line <= 0) return null;
|
|
48209
|
-
const absolutePath =
|
|
48295
|
+
const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
|
|
48210
48296
|
let source;
|
|
48211
48297
|
try {
|
|
48212
48298
|
source = NFS.readFileSync(absolutePath, "utf8");
|
|
@@ -48246,6 +48332,16 @@ const resolveMeasureWidth = (reservedColumns = 0) => resolveClampedWidth({
|
|
|
48246
48332
|
const DIVIDER_INDENT = " ";
|
|
48247
48333
|
const buildSectionDivider = () => highlighter.dim(`${DIVIDER_INDENT}${"─".repeat(resolveMeasureWidth(2))}`);
|
|
48248
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
|
|
48249
48345
|
//#region src/cli/utils/indent-multiline-text.ts
|
|
48250
48346
|
const indentMultilineText = (text, linePrefix) => text.split("\n").map((lineText) => `${linePrefix}${lineText}`).join("\n");
|
|
48251
48347
|
//#endregion
|
|
@@ -48399,17 +48495,23 @@ const clusterNearbyDiagnostics = (diagnostics) => {
|
|
|
48399
48495
|
}
|
|
48400
48496
|
return clusters;
|
|
48401
48497
|
};
|
|
48402
|
-
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) => {
|
|
48403
48505
|
const lead = cluster.diagnostics[0];
|
|
48404
48506
|
const contextTag = formatFileContextTag(lead);
|
|
48405
|
-
|
|
48406
|
-
if (
|
|
48407
|
-
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}`;
|
|
48408
48510
|
};
|
|
48409
|
-
const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame) => {
|
|
48511
|
+
const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame, hyperlinks) => {
|
|
48410
48512
|
const lead = cluster.diagnostics[0];
|
|
48411
48513
|
const isMultiSite = cluster.diagnostics.length > 1;
|
|
48412
|
-
const lines = ["", highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${formatClusterLocation(cluster)}`)];
|
|
48514
|
+
const lines = ["", highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${formatClusterLocation(cluster, resolveSourceRoot, hyperlinks)}`)];
|
|
48413
48515
|
const codeFrame = renderCodeFrame ? buildCodeFrame({
|
|
48414
48516
|
filePath: lead.filePath,
|
|
48415
48517
|
line: cluster.startLine,
|
|
@@ -48428,7 +48530,7 @@ const buildDiagnosticClusterLines = (cluster, resolveSourceRoot, renderCodeFrame
|
|
|
48428
48530
|
}
|
|
48429
48531
|
return lines;
|
|
48430
48532
|
};
|
|
48431
|
-
const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, renderEverySite, isAgentEnvironment) => {
|
|
48533
|
+
const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, renderEverySite, isAgentEnvironment, hyperlinks) => {
|
|
48432
48534
|
const representative = pickRepresentativeDiagnostic(ruleDiagnostics);
|
|
48433
48535
|
const { severity } = representative;
|
|
48434
48536
|
const trailingBadge = formatTrailingSiteBadge(ruleDiagnostics.length);
|
|
@@ -48448,7 +48550,7 @@ const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, rende
|
|
|
48448
48550
|
}
|
|
48449
48551
|
const renderCodeFrame = severity === "error";
|
|
48450
48552
|
const sites = renderEverySite ? ruleDiagnostics : [representative];
|
|
48451
|
-
if (!(isCollapsedWarningGroup && representative.help.includes(representative.filePath))) for (const cluster of clusterNearbyDiagnostics(sites)) lines.push(...buildDiagnosticClusterLines(cluster, resolveSourceRoot, renderCodeFrame));
|
|
48553
|
+
if (!(isCollapsedWarningGroup && representative.help.includes(representative.filePath))) for (const cluster of clusterNearbyDiagnostics(sites)) lines.push(...buildDiagnosticClusterLines(cluster, resolveSourceRoot, renderCodeFrame, hyperlinks));
|
|
48452
48554
|
return lines;
|
|
48453
48555
|
};
|
|
48454
48556
|
const selectErrorRuleGroups = (diagnostics, rulePriority) => buildSortedRuleGroups(diagnostics.filter((diagnostic) => diagnostic.severity === "error"), rulePriority);
|
|
@@ -48461,7 +48563,7 @@ const buildOverflowSummaryLine = (diagnostics, rulePriority) => {
|
|
|
48461
48563
|
return ` ${highlighter.dim("Run")} ${command} ${highlighter.dim("to list every error and warning")}`;
|
|
48462
48564
|
};
|
|
48463
48565
|
const getTopErrorRuleKeys = (diagnostics, limit, rulePriority) => new Set(selectTopErrorRuleGroups(diagnostics, limit, rulePriority).map(([ruleKey]) => ruleKey));
|
|
48464
|
-
const buildTopErrorsSection = (diagnostics, resolveSourceRoot, rulePriority) => {
|
|
48566
|
+
const buildTopErrorsSection = (diagnostics, resolveSourceRoot, hyperlinks, rulePriority) => {
|
|
48465
48567
|
const topRuleGroups = selectErrorRuleGroups(diagnostics, rulePriority).slice(0, 3);
|
|
48466
48568
|
if (topRuleGroups.length === 0) return {
|
|
48467
48569
|
lines: [],
|
|
@@ -48471,7 +48573,7 @@ const buildTopErrorsSection = (diagnostics, resolveSourceRoot, rulePriority) =>
|
|
|
48471
48573
|
const blockOffsets = [];
|
|
48472
48574
|
for (const [ruleKey, ruleDiagnostics] of topRuleGroups) {
|
|
48473
48575
|
blockOffsets.push(lines.length);
|
|
48474
|
-
lines.push(...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, false, false));
|
|
48576
|
+
lines.push(...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, false, false, hyperlinks));
|
|
48475
48577
|
lines.push("");
|
|
48476
48578
|
}
|
|
48477
48579
|
return {
|
|
@@ -48509,18 +48611,18 @@ const buildOverviewHeaderLines = (diagnostics) => {
|
|
|
48509
48611
|
* single Effect.forEach over Console.log so failures or fiber
|
|
48510
48612
|
* interruption produce predictable partial output.
|
|
48511
48613
|
*/
|
|
48512
|
-
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* () {
|
|
48513
48615
|
const sectionPause = onboarding.sectionPause ?? void_;
|
|
48514
48616
|
const animateCountUp = onboarding.animateCountUp ?? false;
|
|
48515
48617
|
const resolveSourceRoot = typeof sourceRoot === "function" ? sourceRoot : () => sourceRoot;
|
|
48516
48618
|
let detailLines;
|
|
48517
48619
|
let topErrorBlockOffsets = [];
|
|
48518
48620
|
if (!isVerbose) {
|
|
48519
|
-
const topErrors = buildTopErrorsSection(diagnostics, resolveSourceRoot, rulePriority);
|
|
48621
|
+
const topErrors = buildTopErrorsSection(diagnostics, resolveSourceRoot, hyperlinks, rulePriority);
|
|
48520
48622
|
detailLines = topErrors.lines;
|
|
48521
48623
|
topErrorBlockOffsets = topErrors.blockOffsets;
|
|
48522
48624
|
} else detailLines = buildSortedRuleGroups(diagnostics, rulePriority).flatMap(([ruleKey, ruleDiagnostics]) => {
|
|
48523
|
-
return [...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, true, isAgentEnvironment), ""];
|
|
48625
|
+
return [...buildRuleDetailBlock(ruleKey, ruleDiagnostics, resolveSourceRoot, true, isAgentEnvironment, hyperlinks), ""];
|
|
48524
48626
|
});
|
|
48525
48627
|
const overflowLine = isVerbose ? void 0 : buildOverflowSummaryLine(diagnostics, rulePriority);
|
|
48526
48628
|
const categoryTallies = buildCategoryDiagnosticGroups(diagnostics, rulePriority).map(buildCategoryTally);
|
|
@@ -48581,6 +48683,48 @@ const computeProjectedScore = async (topErrorSource, rescoreSource, currentScore
|
|
|
48581
48683
|
//#endregion
|
|
48582
48684
|
//#region src/cli/utils/filter-diagnostics-by-categories.ts
|
|
48583
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();
|
|
48584
48728
|
const FORCE_ONBOARDING_ENV_VAR = "REACT_DOCTOR_FORCE_ONBOARDING";
|
|
48585
48729
|
const FALSY_FLAG_VALUES = new Set([
|
|
48586
48730
|
"",
|
|
@@ -48600,10 +48744,9 @@ const canAnimateOnboarding = (stream = process.stdout) => {
|
|
|
48600
48744
|
};
|
|
48601
48745
|
//#endregion
|
|
48602
48746
|
//#region src/cli/utils/onboarding-state.ts
|
|
48603
|
-
const GLOBAL_CONFIG_PROJECT_NAME$2 = "react-doctor";
|
|
48604
48747
|
const ONBOARDED_AT_KEY = "onboardedAt";
|
|
48605
48748
|
const getOnboardingStore = (options = {}) => new Conf({
|
|
48606
|
-
projectName:
|
|
48749
|
+
projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
|
|
48607
48750
|
cwd: options.cwd
|
|
48608
48751
|
});
|
|
48609
48752
|
const hasCompletedOnboarding = (options = {}) => {
|
|
@@ -49059,6 +49202,78 @@ const resolveCliCategories = (categoryFlag) => {
|
|
|
49059
49202
|
return resolvedCategories.length > 0 ? resolvedCategories : void 0;
|
|
49060
49203
|
};
|
|
49061
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
|
|
49062
49277
|
//#region src/cli/utils/scan-result-cache.ts
|
|
49063
49278
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
49064
49279
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
@@ -49069,7 +49284,7 @@ const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
|
49069
49284
|
"eslint-plugin-react-hooks/package.json"
|
|
49070
49285
|
];
|
|
49071
49286
|
const bundledRequire = createRequire(import.meta.url);
|
|
49072
|
-
const isRecord
|
|
49287
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49073
49288
|
const normalizeForStableJson = (value) => {
|
|
49074
49289
|
if (value === null) return null;
|
|
49075
49290
|
if (value === void 0) return void 0;
|
|
@@ -49098,24 +49313,9 @@ const stringifyStableJson = (value) => {
|
|
|
49098
49313
|
}
|
|
49099
49314
|
};
|
|
49100
49315
|
const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
|
|
49101
|
-
const
|
|
49102
|
-
try {
|
|
49103
|
-
return execFileSync("git", [...args], {
|
|
49104
|
-
cwd: directory,
|
|
49105
|
-
encoding: "utf8",
|
|
49106
|
-
stdio: [
|
|
49107
|
-
"ignore",
|
|
49108
|
-
"pipe",
|
|
49109
|
-
"ignore"
|
|
49110
|
-
]
|
|
49111
|
-
}).trim();
|
|
49112
|
-
} catch {
|
|
49113
|
-
return null;
|
|
49114
|
-
}
|
|
49115
|
-
};
|
|
49116
|
-
const readHeadSha = (projectDirectory) => runGit$1(projectDirectory, ["rev-parse", "HEAD"]);
|
|
49316
|
+
const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
|
|
49117
49317
|
const isWorktreeClean = (projectDirectory) => {
|
|
49118
|
-
const status = runGit
|
|
49318
|
+
const status = runGit(projectDirectory, [
|
|
49119
49319
|
"status",
|
|
49120
49320
|
"--porcelain=v1",
|
|
49121
49321
|
"--untracked-files=normal"
|
|
@@ -49123,7 +49323,7 @@ const isWorktreeClean = (projectDirectory) => {
|
|
|
49123
49323
|
return status !== null && status.length === 0;
|
|
49124
49324
|
};
|
|
49125
49325
|
const hasHiddenTrackedFileState = (projectDirectory) => {
|
|
49126
|
-
const output = runGit
|
|
49326
|
+
const output = runGit(projectDirectory, ["ls-files", "-v"]);
|
|
49127
49327
|
if (output === null) return true;
|
|
49128
49328
|
return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
|
|
49129
49329
|
};
|
|
@@ -49136,7 +49336,7 @@ const resolveCacheFilePath = (projectDirectory) => {
|
|
|
49136
49336
|
const readPersistedCache = (cacheFilePath) => {
|
|
49137
49337
|
try {
|
|
49138
49338
|
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
49139
|
-
if (!isRecord
|
|
49339
|
+
if (!isRecord(parsed) || parsed.version !== 1) return {
|
|
49140
49340
|
version: 1,
|
|
49141
49341
|
entries: []
|
|
49142
49342
|
};
|
|
@@ -49146,8 +49346,8 @@ const readPersistedCache = (cacheFilePath) => {
|
|
|
49146
49346
|
};
|
|
49147
49347
|
const entries = [];
|
|
49148
49348
|
for (const entry of parsed.entries) {
|
|
49149
|
-
if (!isRecord
|
|
49150
|
-
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;
|
|
49151
49351
|
entries.push(entry);
|
|
49152
49352
|
}
|
|
49153
49353
|
return {
|
|
@@ -49679,6 +49879,7 @@ const finalizeAndRender = (input) => gen(function* () {
|
|
|
49679
49879
|
}
|
|
49680
49880
|
const animateRender = !options.silent && !options.verbose && canAnimateOnboarding(process.stdout);
|
|
49681
49881
|
const pause = onboardingSectionPause(animateRender);
|
|
49882
|
+
const useHyperlinks = shouldRenderHyperlinks(process.stdout);
|
|
49682
49883
|
const demotedDiagnosticCount = diagnostics.length - surfaceDiagnostics.length;
|
|
49683
49884
|
const lintSourceFileCount = options.includePaths.length > 0 ? options.includePaths.length : project.sourceFileCount;
|
|
49684
49885
|
if (printedDiagnostics.length === 0) {
|
|
@@ -49704,7 +49905,7 @@ const finalizeAndRender = (input) => gen(function* () {
|
|
|
49704
49905
|
yield* printDiagnostics([...printedDiagnostics], options.verbose, directory, buildRulePriorityMap([score]), isCodingAgentEnvironment(), {
|
|
49705
49906
|
sectionPause: pause,
|
|
49706
49907
|
animateCountUp: animateRender
|
|
49707
|
-
});
|
|
49908
|
+
}, useHyperlinks);
|
|
49708
49909
|
if (options.isNonInteractiveEnvironment && options.outputSurface !== "prComment") yield* printAgentGuidance();
|
|
49709
49910
|
if (options.categoryFilters.size === 0 && demotedDiagnosticCount > 0) {
|
|
49710
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.`));
|
|
@@ -49897,16 +50098,7 @@ const buildHandoffPayload = (input) => {
|
|
|
49897
50098
|
try {
|
|
49898
50099
|
outputDirectory = writeDiagnosticsDirectory([...input.diagnostics], input.outputDirectory);
|
|
49899
50100
|
} catch {}
|
|
49900
|
-
const lines = [
|
|
49901
|
-
"Before fixing anything, first ask me whether I'd like to add React Doctor to CI so every pull request is scanned automatically. Explain that:",
|
|
49902
|
-
`- It's used by teams at ${CI_TRUST_COMPANIES}.`,
|
|
49903
|
-
"- I don't have to fix everything at once; CI keeps new PRs clean while I incrementally pay down the existing issues.",
|
|
49904
|
-
`- Setup guide: ${CI_URL}`,
|
|
49905
|
-
"If I say yes, run `npx react-doctor install` (or add the GitHub Actions workflow). Either way, then continue with the fixes below.",
|
|
49906
|
-
"",
|
|
49907
|
-
`Fix the top ${topGroups.length} React Doctor ${topGroups.length === 1 ? "issue" : "issues"} in ${input.projectName} on this pass — leave the rest for a follow-up.`,
|
|
49908
|
-
""
|
|
49909
|
-
];
|
|
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.`, ""];
|
|
49910
50102
|
topGroups.forEach(([ruleKey, ruleDiagnostics], index) => {
|
|
49911
50103
|
const representative = ruleDiagnostics[0];
|
|
49912
50104
|
const severityLabel = representative.severity === "error" ? "ERROR" : "WARN";
|
|
@@ -49967,78 +50159,6 @@ const detectAvailableAgents = async () => {
|
|
|
49967
50159
|
return getSkillAgentTypes().filter((agent) => agent !== "universal" && detected.has(agent));
|
|
49968
50160
|
};
|
|
49969
50161
|
//#endregion
|
|
49970
|
-
//#region src/cli/utils/git-hook-shared.ts
|
|
49971
|
-
const HOOK_FILE_NAME = "pre-commit";
|
|
49972
|
-
const HOOK_RELATIVE_PATH = "hooks/pre-commit";
|
|
49973
|
-
const LEGACY_HOOK_RUNNER_RELATIVE_PATH = ".react-doctor/hooks/pre-commit";
|
|
49974
|
-
const HUSKY_HOOKS_PATH = ".husky";
|
|
49975
|
-
const VITE_PLUS_HOOKS_PATH = ".vite-hooks";
|
|
49976
|
-
const LEFTHOOK_CONFIG_FILES = ["lefthook.yml", "lefthook.yaml"];
|
|
49977
|
-
const PRE_COMMIT_CONFIG_FILE = ".pre-commit-config.yaml";
|
|
49978
|
-
const OVERCOMMIT_CONFIG_FILE = ".overcommit.yml";
|
|
49979
|
-
const REACT_DOCTOR_COMMAND = "react-doctor --staged --blocking warning";
|
|
49980
|
-
const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
49981
|
-
"react_doctor_output=$(mktemp \"${TMPDIR:-/tmp}/react-doctor-hook.XXXXXX\");",
|
|
49982
|
-
`if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
|
|
49983
|
-
"rm -f \"$react_doctor_output\";",
|
|
49984
|
-
"else",
|
|
49985
|
-
"rm -f \"$react_doctor_output\";",
|
|
49986
|
-
`printf "%s\\n" "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2;`,
|
|
49987
|
-
"fi"
|
|
49988
|
-
].join(" ");
|
|
49989
|
-
const PACKAGE_JSON_FILE_NAME = "package.json";
|
|
49990
|
-
const runGit = (projectRoot, args) => {
|
|
49991
|
-
try {
|
|
49992
|
-
return execFileSync("git", [...args], {
|
|
49993
|
-
cwd: projectRoot,
|
|
49994
|
-
encoding: "utf8",
|
|
49995
|
-
stdio: [
|
|
49996
|
-
"ignore",
|
|
49997
|
-
"pipe",
|
|
49998
|
-
"ignore"
|
|
49999
|
-
]
|
|
50000
|
-
}).trim();
|
|
50001
|
-
} catch {
|
|
50002
|
-
return null;
|
|
50003
|
-
}
|
|
50004
|
-
};
|
|
50005
|
-
const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
|
|
50006
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50007
|
-
const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
|
|
50008
|
-
const readPackageJson = (projectRoot) => {
|
|
50009
|
-
try {
|
|
50010
|
-
return JSON.parse(NFS.readFileSync(getPackageJsonPath(projectRoot), "utf8"));
|
|
50011
|
-
} catch {
|
|
50012
|
-
return null;
|
|
50013
|
-
}
|
|
50014
|
-
};
|
|
50015
|
-
const writeJsonFile$1 = (filePath, value) => {
|
|
50016
|
-
NFS.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
50017
|
-
};
|
|
50018
|
-
const packageHasDependency = (projectRoot, dependencyName) => {
|
|
50019
|
-
const packageJson = readPackageJson(projectRoot);
|
|
50020
|
-
if (!isRecord(packageJson)) return false;
|
|
50021
|
-
return [
|
|
50022
|
-
"dependencies",
|
|
50023
|
-
"devDependencies",
|
|
50024
|
-
"optionalDependencies"
|
|
50025
|
-
].some((fieldName) => {
|
|
50026
|
-
const dependencies = packageJson[fieldName];
|
|
50027
|
-
return isRecord(dependencies) && typeof dependencies[dependencyName] === "string";
|
|
50028
|
-
});
|
|
50029
|
-
};
|
|
50030
|
-
const packageHasRecordKey = (projectRoot, key) => {
|
|
50031
|
-
const packageJson = readPackageJson(projectRoot);
|
|
50032
|
-
return isRecord(packageJson) && isRecord(packageJson[key]);
|
|
50033
|
-
};
|
|
50034
|
-
const packageHasNestedRecordKey = (projectRoot, key, nestedKey) => {
|
|
50035
|
-
const packageJson = readPackageJson(projectRoot);
|
|
50036
|
-
if (!isRecord(packageJson)) return false;
|
|
50037
|
-
const value = packageJson[key];
|
|
50038
|
-
return isRecord(value) && isRecord(value[nestedKey]);
|
|
50039
|
-
};
|
|
50040
|
-
const ensureTrailingNewline = (content) => content.endsWith("\n") ? content : `${content}\n`;
|
|
50041
|
-
//#endregion
|
|
50042
50162
|
//#region src/cli/utils/install-doctor-script.ts
|
|
50043
50163
|
const DOCTOR_SCRIPT_NAME = "doctor";
|
|
50044
50164
|
const FALLBACK_DOCTOR_SCRIPT_NAME = "react-doctor";
|
|
@@ -50064,31 +50184,31 @@ const findNearestPackageDirectory = (startDirectory, stopDirectory) => {
|
|
|
50064
50184
|
};
|
|
50065
50185
|
const hasDoctorScript = (projectRoot) => {
|
|
50066
50186
|
const packageJson = readPackageJson(findNearestPackageDirectory(projectRoot) ?? projectRoot);
|
|
50067
|
-
if (!isRecord(packageJson)) return false;
|
|
50187
|
+
if (!isRecord$1(packageJson)) return false;
|
|
50068
50188
|
const scripts = packageJson.scripts;
|
|
50069
|
-
if (!isRecord(scripts)) return false;
|
|
50189
|
+
if (!isRecord$1(scripts)) return false;
|
|
50070
50190
|
return isReactDoctorScriptCommand(scripts[DOCTOR_SCRIPT_NAME]) || isReactDoctorScriptCommand(scripts[FALLBACK_DOCTOR_SCRIPT_NAME]);
|
|
50071
50191
|
};
|
|
50072
50192
|
const hasDoctorDependency = (packageJson) => DEPENDENCY_FIELD_NAMES.some((fieldName) => {
|
|
50073
50193
|
const dependencies = packageJson[fieldName];
|
|
50074
|
-
return isRecord(dependencies) && Object.hasOwn(dependencies, "react-doctor");
|
|
50194
|
+
return isRecord$1(dependencies) && Object.hasOwn(dependencies, "react-doctor");
|
|
50075
50195
|
});
|
|
50076
50196
|
const installDoctorScript = (options) => {
|
|
50077
50197
|
const packageDirectory = findNearestPackageDirectory(options.projectRoot) ?? options.projectRoot;
|
|
50078
50198
|
const packageJsonPath = getPackageJsonPath(packageDirectory);
|
|
50079
50199
|
const packageJson = readPackageJson(packageDirectory);
|
|
50080
|
-
if (!isRecord(packageJson)) return {
|
|
50200
|
+
if (!isRecord$1(packageJson)) return {
|
|
50081
50201
|
packageJsonPath,
|
|
50082
50202
|
scriptStatus: "skipped",
|
|
50083
50203
|
scriptReason: "missing-or-invalid-package-json"
|
|
50084
50204
|
};
|
|
50085
50205
|
const scripts = packageJson.scripts;
|
|
50086
50206
|
const scriptTarget = (() => {
|
|
50087
|
-
if (scripts !== void 0 && !isRecord(scripts)) return {
|
|
50207
|
+
if (scripts !== void 0 && !isRecord$1(scripts)) return {
|
|
50088
50208
|
status: "skipped",
|
|
50089
50209
|
reason: "invalid-scripts"
|
|
50090
50210
|
};
|
|
50091
|
-
const scriptRecord = isRecord(scripts) ? scripts : {};
|
|
50211
|
+
const scriptRecord = isRecord$1(scripts) ? scripts : {};
|
|
50092
50212
|
if (isReactDoctorScriptCommand(scriptRecord[DOCTOR_SCRIPT_NAME])) return {
|
|
50093
50213
|
scriptName: DOCTOR_SCRIPT_NAME,
|
|
50094
50214
|
status: "existing"
|
|
@@ -50122,7 +50242,7 @@ const installDoctorScript = (options) => {
|
|
|
50122
50242
|
if (scriptStatus === "created") writeJsonFile$1(packageJsonPath, {
|
|
50123
50243
|
...packageJson,
|
|
50124
50244
|
scripts: {
|
|
50125
|
-
...isRecord(scripts) ? scripts : {},
|
|
50245
|
+
...isRecord$1(scripts) ? scripts : {},
|
|
50126
50246
|
[scriptTarget.scriptName ?? DOCTOR_SCRIPT_NAME]: DOCTOR_SCRIPT_COMMAND
|
|
50127
50247
|
}
|
|
50128
50248
|
});
|
|
@@ -50276,38 +50396,52 @@ const upgradeReactDoctorWorkflowInPlace = (projectRoot) => {
|
|
|
50276
50396
|
//#region src/cli/utils/hash-project-root.ts
|
|
50277
50397
|
const hashProjectRoot = (projectRoot) => createHash("sha256").update(Path.resolve(projectRoot)).digest("hex");
|
|
50278
50398
|
//#endregion
|
|
50279
|
-
//#region src/cli/utils/
|
|
50280
|
-
const
|
|
50281
|
-
const
|
|
50282
|
-
|
|
50283
|
-
|
|
50284
|
-
});
|
|
50285
|
-
|
|
50286
|
-
|
|
50287
|
-
|
|
50288
|
-
|
|
50289
|
-
|
|
50290
|
-
|
|
50291
|
-
|
|
50292
|
-
};
|
|
50293
|
-
const recordActionUpgradeDecision = (projectRoot, outcome, storeOptions = {}) => {
|
|
50294
|
-
try {
|
|
50295
|
-
const store = getActionUpgradeStore(storeOptions);
|
|
50296
|
-
const upgrades = store.get("actionUpgrades", {});
|
|
50297
|
-
store.set("actionUpgrades", {
|
|
50298
|
-
...upgrades,
|
|
50299
|
-
[hashProjectRoot(projectRoot)]: {
|
|
50300
|
-
rootDirectory: Path.resolve(projectRoot),
|
|
50301
|
-
outcome,
|
|
50302
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
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;
|
|
50303
50412
|
}
|
|
50304
|
-
}
|
|
50305
|
-
|
|
50306
|
-
|
|
50307
|
-
|
|
50308
|
-
|
|
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
|
+
};
|
|
50309
50431
|
};
|
|
50310
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
|
|
50311
50445
|
//#region src/cli/utils/open-url.ts
|
|
50312
50446
|
const resolveOpenCommand = (url) => {
|
|
50313
50447
|
if (process$1.platform === "darwin") return {
|
|
@@ -50936,13 +51070,13 @@ const installPackageJsonHook = (options, strategy) => {
|
|
|
50936
51070
|
const packageJsonPath = getPackageJsonPath(options.projectRoot);
|
|
50937
51071
|
const didHookExist = NFS.existsSync(packageJsonPath);
|
|
50938
51072
|
const packageJson = readPackageJson(options.projectRoot);
|
|
50939
|
-
const nextPackageJson = isRecord(packageJson) ? { ...packageJson } : {};
|
|
51073
|
+
const nextPackageJson = isRecord$1(packageJson) ? { ...packageJson } : {};
|
|
50940
51074
|
const parentKeys = strategy.path.slice(0, -1);
|
|
50941
51075
|
const leafKey = strategy.path[strategy.path.length - 1];
|
|
50942
51076
|
let parent = nextPackageJson;
|
|
50943
51077
|
for (const key of parentKeys) {
|
|
50944
51078
|
const existing = parent[key];
|
|
50945
|
-
const cloned = isRecord(existing) ? { ...existing } : {};
|
|
51079
|
+
const cloned = isRecord$1(existing) ? { ...existing } : {};
|
|
50946
51080
|
parent[key] = cloned;
|
|
50947
51081
|
parent = cloned;
|
|
50948
51082
|
}
|
|
@@ -51113,7 +51247,7 @@ const isHuskyProject = (projectRoot) => NFS.existsSync(Path.join(projectRoot, ".
|
|
|
51113
51247
|
const isVitePlusProject = (projectRoot) => packageHasDependency(projectRoot, "vite-plus");
|
|
51114
51248
|
const isSimpleGitHooksProject = (projectRoot) => {
|
|
51115
51249
|
const packageJson = readPackageJson(projectRoot);
|
|
51116
|
-
return isRecord(packageJson) && isRecord(packageJson["simple-git-hooks"]) || packageHasDependency(projectRoot, "simple-git-hooks") || NFS.existsSync(Path.join(projectRoot, ".simple-git-hooks.cjs"));
|
|
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"));
|
|
51117
51251
|
};
|
|
51118
51252
|
const getLefthookConfigPath = (projectRoot) => {
|
|
51119
51253
|
for (const fileName of LEFTHOOK_CONFIG_FILES) {
|
|
@@ -51279,7 +51413,7 @@ const detectPackageManager = (projectRoot) => {
|
|
|
51279
51413
|
let currentDirectory = Path.resolve(projectRoot);
|
|
51280
51414
|
while (true) {
|
|
51281
51415
|
const packageJson = readPackageJson(currentDirectory);
|
|
51282
|
-
if (isRecord(packageJson) && typeof packageJson.packageManager === "string") {
|
|
51416
|
+
if (isRecord$1(packageJson) && typeof packageJson.packageManager === "string") {
|
|
51283
51417
|
const packageManagerName = packageJson.packageManager.split("@")[0];
|
|
51284
51418
|
if (packageManagerName === "pnpm" || packageManagerName === "yarn" || packageManagerName === "bun" || packageManagerName === "npm") return packageManagerName;
|
|
51285
51419
|
}
|
|
@@ -51355,12 +51489,12 @@ const isSupplyChainTrustError = (error) => {
|
|
|
51355
51489
|
const formatInstallCommand = (input) => [input.command, ...input.args].join(" ");
|
|
51356
51490
|
const installReactDoctorDependency = async (options) => {
|
|
51357
51491
|
const packageJson = readPackageJson(options.projectRoot);
|
|
51358
|
-
if (!isRecord(packageJson)) return {
|
|
51492
|
+
if (!isRecord$1(packageJson)) return {
|
|
51359
51493
|
dependencyStatus: "skipped",
|
|
51360
51494
|
dependencyReason: "missing-or-invalid-package-json"
|
|
51361
51495
|
};
|
|
51362
51496
|
if (hasDoctorDependency(packageJson)) return { dependencyStatus: "existing" };
|
|
51363
|
-
if (packageJson.devDependencies !== void 0 && !isRecord(packageJson.devDependencies)) return {
|
|
51497
|
+
if (packageJson.devDependencies !== void 0 && !isRecord$1(packageJson.devDependencies)) return {
|
|
51364
51498
|
dependencyStatus: "skipped",
|
|
51365
51499
|
dependencyReason: "invalid-dev-dependencies"
|
|
51366
51500
|
};
|
|
@@ -51524,10 +51658,12 @@ const runInstallReactDoctor = async (options = {}) => {
|
|
|
51524
51658
|
const existingWorkflow = readReactDoctorWorkflow(projectRoot);
|
|
51525
51659
|
const canInstallWorkflow = !NFS.existsSync(workflowTargetPath);
|
|
51526
51660
|
const canUpgradeWorkflow = existingWorkflow !== null && workflowUsesV1Action(existingWorkflow.content) && !hasHandledActionUpgrade(projectRoot);
|
|
51527
|
-
const
|
|
51661
|
+
const ciPromptOutcome = canInstallWorkflow && !options.yes && !skipPrompts && !hasHandledCiPrompt(projectRoot) ? await askAddToGitHubActions(prompt) : null;
|
|
51662
|
+
const shouldInstallWorkflow = canInstallWorkflow && (Boolean(options.yes) || ciPromptOutcome === "yes");
|
|
51528
51663
|
const upgradePromptOutcome = canUpgradeWorkflow && !options.yes && !skipPrompts ? await askUpgradeActionVersion(prompt) : null;
|
|
51529
51664
|
const shouldUpgradeWorkflow = canUpgradeWorkflow && (Boolean(options.yes) || upgradePromptOutcome === "yes");
|
|
51530
51665
|
if (upgradePromptOutcome === "no" && !options.dryRun) recordActionUpgradeDecision(projectRoot, "declined");
|
|
51666
|
+
if ((ciPromptOutcome === "yes" || ciPromptOutcome === "no") && !options.dryRun) recordCiPromptDecision(projectRoot, ciPromptOutcome === "yes" ? "accepted" : "declined");
|
|
51531
51667
|
const selectedAgents = skipPrompts ? detectedAgents : (await prompt({
|
|
51532
51668
|
type: "multiselect",
|
|
51533
51669
|
name: "agents",
|
|
@@ -51774,18 +51910,24 @@ const handoffToAgent = async (input) => {
|
|
|
51774
51910
|
if (!input.interactive || input.diagnostics.length === 0) return;
|
|
51775
51911
|
cliLogger.break();
|
|
51776
51912
|
const projectRootForCi = findNearestPackageDirectory(input.rootDirectory) ?? input.rootDirectory;
|
|
51777
|
-
|
|
51913
|
+
const isGitHubActionsConfigured = isReactDoctorWorkflowInstalled(projectRootForCi);
|
|
51914
|
+
if (!isGitHubActionsConfigured && !hasHandledCiPrompt(projectRootForCi)) {
|
|
51778
51915
|
const ciOutcome = await askAddToGitHubActions();
|
|
51779
51916
|
recordCount(METRIC.agentHandoff, 1, {
|
|
51780
51917
|
outcome: `ci-${ciOutcome}`,
|
|
51781
51918
|
diagnosticsCount: input.diagnostics.length
|
|
51782
51919
|
});
|
|
51783
51920
|
if (ciOutcome === "cancel") return;
|
|
51921
|
+
recordCiPromptDecision(projectRootForCi, ciOutcome === "yes" ? "accepted" : "declined");
|
|
51784
51922
|
if (ciOutcome === "yes") {
|
|
51785
51923
|
await setUpGitHubActions({ rootDirectory: input.rootDirectory });
|
|
51786
51924
|
cliLogger.break();
|
|
51787
51925
|
}
|
|
51788
|
-
} 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
|
+
});
|
|
51789
51931
|
const { handoffTarget } = await prompts({
|
|
51790
51932
|
type: "select",
|
|
51791
51933
|
name: "handoffTarget",
|
|
@@ -52008,6 +52150,7 @@ const reportErrorToSentry = async (error) => {
|
|
|
52008
52150
|
sampled: runTrace.sampled,
|
|
52009
52151
|
sampleRand: Math.random()
|
|
52010
52152
|
});
|
|
52153
|
+
recordRunTraceId(scope.getPropagationContext().traceId);
|
|
52011
52154
|
return Sentry.captureException(error);
|
|
52012
52155
|
});
|
|
52013
52156
|
await Sentry.flush(SENTRY_FLUSH_TIMEOUT_MS);
|
|
@@ -52091,7 +52234,7 @@ const printMultiProjectSummary = (input) => gen(function* () {
|
|
|
52091
52234
|
yield* log(`${highlighter.success("✔")} Scanned ${totalScannedFileCount} ${totalScannedFileCount === 1 ? "file" : "files"} in ${formatElapsedTime(totalElapsedMilliseconds)}`);
|
|
52092
52235
|
if (displayDiagnostics.length > 0) {
|
|
52093
52236
|
yield* log("");
|
|
52094
|
-
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));
|
|
52095
52238
|
}
|
|
52096
52239
|
const lowestScoredScan = findLowestScoredScan(completedScans);
|
|
52097
52240
|
const aggregateScore = lowestScoredScan?.result.score ?? null;
|
|
@@ -52129,9 +52272,8 @@ const printMultiProjectSummary = (input) => gen(function* () {
|
|
|
52129
52272
|
});
|
|
52130
52273
|
//#endregion
|
|
52131
52274
|
//#region src/cli/utils/prompt-install-setup.ts
|
|
52132
|
-
const GLOBAL_CONFIG_PROJECT_NAME = "react-doctor";
|
|
52133
52275
|
const getSetupPromptStore = (options = {}) => new Conf({
|
|
52134
|
-
projectName:
|
|
52276
|
+
projectName: REACT_DOCTOR_CONFIG_PROJECT_NAME,
|
|
52135
52277
|
cwd: options.cwd
|
|
52136
52278
|
});
|
|
52137
52279
|
const getSetupPromptProjectKey = (projectRoot) => hashProjectRoot(projectRoot);
|
|
@@ -52142,6 +52284,24 @@ const hasDisabledSetupPrompt = (projectRoot, storeOptions = {}) => {
|
|
|
52142
52284
|
return false;
|
|
52143
52285
|
}
|
|
52144
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
|
+
};
|
|
52145
52305
|
const resolveInstallSetupProjectRoot = (options) => {
|
|
52146
52306
|
if (options.scanDirectories.length === 0) return findNearestPackageDirectory(options.scanRoot) ?? options.scanRoot;
|
|
52147
52307
|
const packageDirectories = /* @__PURE__ */ new Set();
|
|
@@ -52548,6 +52708,14 @@ const runExplain = async (fileLineArgument, context) => {
|
|
|
52548
52708
|
const colorizedRule = colorizeRuleByDiagnostic(ruleIdentifier, diagnostic.severity);
|
|
52549
52709
|
const severityLabel = colorizeRuleByDiagnostic(diagnostic.severity, diagnostic.severity);
|
|
52550
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, " "));
|
|
52551
52719
|
if (diagnostic.category) cliLogger.dim(` Category: ${diagnostic.category}`);
|
|
52552
52720
|
if (diagnostic.help) cliLogger.dim(` ${diagnostic.help}`);
|
|
52553
52721
|
cliLogger.dim(` If this needs follow-up or looks like a false positive, open: ${buildDiagnosticIssueUrl({
|
|
@@ -52597,6 +52765,10 @@ const validateModeFlags = (flags) => {
|
|
|
52597
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.`);
|
|
52598
52766
|
if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
|
|
52599
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
|
+
}
|
|
52600
52772
|
};
|
|
52601
52773
|
//#endregion
|
|
52602
52774
|
//#region src/cli/commands/inspect.ts
|
|
@@ -52944,11 +53116,13 @@ const inspectAction = async (directory, flags) => {
|
|
|
52944
53116
|
})) {
|
|
52945
53117
|
printAgentInstallHint();
|
|
52946
53118
|
recordCount(METRIC.agentInstallHintShown, 1);
|
|
53119
|
+
disableSetupPrompt(setupProjectRoot);
|
|
52947
53120
|
}
|
|
52948
53121
|
}
|
|
52949
53122
|
} catch (error) {
|
|
52950
53123
|
const isUserError = isExpectedUserError(error);
|
|
52951
53124
|
const sentryEventId = isUserError ? void 0 : await reportErrorToSentry(error);
|
|
53125
|
+
if (isDebugFlagEnabled()) await flushSentry();
|
|
52952
53126
|
if (isJsonMode) {
|
|
52953
53127
|
writeJsonErrorReport(error, sentryEventId);
|
|
52954
53128
|
process.exitCode = 1;
|
|
@@ -53671,6 +53845,33 @@ const normalizeHelpInvocation = (argv, knownCommands) => {
|
|
|
53671
53845
|
return [...nodeArguments, "--help"];
|
|
53672
53846
|
};
|
|
53673
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
|
|
53674
53875
|
//#region src/cli/utils/removed-cli-flags.ts
|
|
53675
53876
|
const REMOVED_FLAGS = new Map([
|
|
53676
53877
|
["--full", "use `--diff false` to force a full scan"],
|
|
@@ -53697,6 +53898,7 @@ const ROOT_FLAG_SPEC = {
|
|
|
53697
53898
|
longOptionsWithoutValues: new Set([
|
|
53698
53899
|
"--color",
|
|
53699
53900
|
"--dead-code",
|
|
53901
|
+
"--debug",
|
|
53700
53902
|
"--help",
|
|
53701
53903
|
"--json",
|
|
53702
53904
|
"--json-compact",
|
|
@@ -53864,6 +54066,9 @@ const stripUnknownCliFlags = (argv) => {
|
|
|
53864
54066
|
initializeSentry();
|
|
53865
54067
|
process.on("SIGINT", exitGracefully);
|
|
53866
54068
|
process.on("SIGTERM", exitGracefully);
|
|
54069
|
+
process.on("exit", () => {
|
|
54070
|
+
if (isDebugFlagEnabled()) printDebugTrace();
|
|
54071
|
+
});
|
|
53867
54072
|
unrefStdin();
|
|
53868
54073
|
guardStdin();
|
|
53869
54074
|
const formatExampleLines = (examples) => {
|
|
@@ -53908,7 +54113,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
53908
54113
|
${highlighter.info(CANONICAL_GITHUB_URL)}
|
|
53909
54114
|
`;
|
|
53910
54115
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
53911
|
-
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);
|
|
53912
54117
|
program.action(inspectAction);
|
|
53913
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));
|
|
53914
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);
|
|
@@ -53951,4 +54156,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
53951
54156
|
export {};
|
|
53952
54157
|
|
|
53953
54158
|
//# sourceMappingURL=cli.js.map
|
|
53954
|
-
//# debugId=
|
|
54159
|
+
//# debugId=f029f05b-4c71-52d3-b523-0834d67de2d4
|