react-doctor 0.0.5 → 0.0.6
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 +79 -9
- package/dist/cli.js.map +1 -1
- package/dist/react-doctor-plugin.js +19 -6
- package/dist/react-doctor-plugin.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
3
4
|
import path, { join } from "node:path";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
import pc from "picocolors";
|
|
@@ -7,7 +8,6 @@ import { randomUUID } from "node:crypto";
|
|
|
7
8
|
import fs, { mkdirSync, writeFileSync } from "node:fs";
|
|
8
9
|
import os, { tmpdir } from "node:os";
|
|
9
10
|
import { performance } from "node:perf_hooks";
|
|
10
|
-
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import { main } from "knip";
|
|
12
12
|
import { createOptions } from "knip/session";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
@@ -954,6 +954,23 @@ const printScoreGauge = (score, label) => {
|
|
|
954
954
|
logger.log(` ${buildScoreBar(score)}`);
|
|
955
955
|
logger.break();
|
|
956
956
|
};
|
|
957
|
+
const getDoctorFace = (score) => {
|
|
958
|
+
if (score >= SCORE_GOOD_THRESHOLD) return ["◠ ◠", " ▽ "];
|
|
959
|
+
if (score >= SCORE_OK_THRESHOLD) return ["• •", " ─ "];
|
|
960
|
+
return ["x x", " ▽ "];
|
|
961
|
+
};
|
|
962
|
+
const printBranding = (score) => {
|
|
963
|
+
if (score !== void 0) {
|
|
964
|
+
const [eyes, mouth] = getDoctorFace(score);
|
|
965
|
+
const colorize = (text) => colorizeByScore(text, score);
|
|
966
|
+
logger.log(colorize(" ┌─────┐"));
|
|
967
|
+
logger.log(colorize(` │ ${eyes} │`));
|
|
968
|
+
logger.log(colorize(` │ ${mouth} │`));
|
|
969
|
+
logger.log(colorize(" └─────┘"));
|
|
970
|
+
}
|
|
971
|
+
logger.log(` React Doctor ${highlighter.dim("(www.react.doctor)")}`);
|
|
972
|
+
logger.break();
|
|
973
|
+
};
|
|
957
974
|
const printSummary = (diagnostics, elapsedMilliseconds, scoreResult) => {
|
|
958
975
|
const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
959
976
|
const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
|
|
@@ -961,6 +978,7 @@ const printSummary = (diagnostics, elapsedMilliseconds, scoreResult) => {
|
|
|
961
978
|
const elapsed = formatElapsedTime(elapsedMilliseconds);
|
|
962
979
|
logger.log("─".repeat(SEPARATOR_LENGTH_CHARS));
|
|
963
980
|
logger.break();
|
|
981
|
+
printBranding(scoreResult?.score);
|
|
964
982
|
if (scoreResult) printScoreGauge(scoreResult.score, scoreResult.label);
|
|
965
983
|
else {
|
|
966
984
|
logger.dim(` ${OFFLINE_MESSAGE}`);
|
|
@@ -1035,8 +1053,10 @@ const scan = async (directory, options) => {
|
|
|
1035
1053
|
if (diagnostics.length === 0) {
|
|
1036
1054
|
logger.success("No issues found!");
|
|
1037
1055
|
logger.break();
|
|
1038
|
-
if (scoreResult)
|
|
1039
|
-
|
|
1056
|
+
if (scoreResult) {
|
|
1057
|
+
printBranding(scoreResult.score);
|
|
1058
|
+
printScoreGauge(scoreResult.score, scoreResult.label);
|
|
1059
|
+
} else logger.dim(` ${OFFLINE_MESSAGE}`);
|
|
1040
1060
|
return;
|
|
1041
1061
|
}
|
|
1042
1062
|
printDiagnostics(diagnostics, options.verbose);
|
|
@@ -1058,8 +1078,8 @@ const prompts = (questions) => {
|
|
|
1058
1078
|
//#endregion
|
|
1059
1079
|
//#region src/utils/select-projects.ts
|
|
1060
1080
|
const selectProjects = async (rootDirectory, projectFlag, skipPrompts) => {
|
|
1061
|
-
|
|
1062
|
-
if (packages.length === 0) packages
|
|
1081
|
+
let packages = listWorkspacePackages(rootDirectory);
|
|
1082
|
+
if (packages.length === 0) packages = discoverReactSubprojects(rootDirectory);
|
|
1063
1083
|
if (packages.length === 0) return [rootDirectory];
|
|
1064
1084
|
if (projectFlag) return resolveProjectFlag(projectFlag, packages);
|
|
1065
1085
|
if (skipPrompts) {
|
|
@@ -1087,7 +1107,7 @@ const printDiscoveredProjects = (packages) => {
|
|
|
1087
1107
|
for (const workspacePackage of packages) logger.log(` ${highlighter.dim("─")} ${workspacePackage.directory}`);
|
|
1088
1108
|
logger.break();
|
|
1089
1109
|
logger.dim(`Run with a specific path to scan a project:`);
|
|
1090
|
-
logger.dim(` npx react-doctor@latest <path>`);
|
|
1110
|
+
logger.dim(` npx -y react-doctor@latest <path>`);
|
|
1091
1111
|
logger.break();
|
|
1092
1112
|
};
|
|
1093
1113
|
const promptProjectSelection = async (workspacePackages, rootDirectory) => {
|
|
@@ -1107,10 +1127,10 @@ const promptProjectSelection = async (workspacePackages, rootDirectory) => {
|
|
|
1107
1127
|
|
|
1108
1128
|
//#endregion
|
|
1109
1129
|
//#region src/cli.ts
|
|
1110
|
-
const VERSION = "0.0.
|
|
1130
|
+
const VERSION = "0.0.6";
|
|
1111
1131
|
process.on("SIGINT", () => process.exit(0));
|
|
1112
1132
|
process.on("SIGTERM", () => process.exit(0));
|
|
1113
|
-
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("--no-lint", "skip linting").option("--no-dead-code", "skip dead code detection").option("--
|
|
1133
|
+
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("--no-lint", "skip linting").option("--no-dead-code", "skip dead code detection").option("--verbose", "show file details per rule").option("--score", "output only the score").option("-y, --yes", "skip prompts, scan all workspace projects").option("--project <name>", "select workspace project (comma-separated for multiple)").option("--fix", "open Ami to auto-fix all issues").action(async (directory, flags) => {
|
|
1114
1134
|
try {
|
|
1115
1135
|
const resolvedDirectory = path.resolve(directory);
|
|
1116
1136
|
const isScoreOnly = flags.score;
|
|
@@ -1121,7 +1141,7 @@ const program = new Command().name("react-doctor").description("Diagnose React c
|
|
|
1121
1141
|
const scanOptions = {
|
|
1122
1142
|
lint: flags.lint,
|
|
1123
1143
|
deadCode: flags.deadCode,
|
|
1124
|
-
verbose: flags.verbose,
|
|
1144
|
+
verbose: Boolean(flags.verbose),
|
|
1125
1145
|
scoreOnly: isScoreOnly
|
|
1126
1146
|
};
|
|
1127
1147
|
const shouldSkipPrompts = flags.yes || Boolean(process.env.CI) || Boolean(process.env.CLAUDECODE) || Boolean(process.env.AMI) || !process.stdin.isTTY;
|
|
@@ -1134,6 +1154,7 @@ const program = new Command().name("react-doctor").description("Diagnose React c
|
|
|
1134
1154
|
await scan(projectDirectory, scanOptions);
|
|
1135
1155
|
if (!isScoreOnly) logger.break();
|
|
1136
1156
|
}
|
|
1157
|
+
if (flags.fix) openAmiToFix(resolvedDirectory);
|
|
1137
1158
|
} catch (error) {
|
|
1138
1159
|
handleError(error);
|
|
1139
1160
|
}
|
|
@@ -1141,6 +1162,55 @@ const program = new Command().name("react-doctor").description("Diagnose React c
|
|
|
1141
1162
|
${highlighter.dim("Learn more:")}
|
|
1142
1163
|
${highlighter.info("https://github.com/aidenybai/react-doctor")}
|
|
1143
1164
|
`);
|
|
1165
|
+
const AMI_INSTALL_URL = "https://ami.dev/install.sh";
|
|
1166
|
+
const AMI_FIX_PROMPT = "Run npx -y react-doctor@latest . --verbose, read every diagnostic, then fix all issues one by one. After fixing, re-run react-doctor to verify the score improved.";
|
|
1167
|
+
const buildAmiDeeplink = (projectDirectory) => {
|
|
1168
|
+
return `ami://new-chat?cwd=${encodeURIComponent(projectDirectory)}&prompt=${encodeURIComponent(AMI_FIX_PROMPT)}&mode=agent`;
|
|
1169
|
+
};
|
|
1170
|
+
const isAmiInstalled = () => {
|
|
1171
|
+
try {
|
|
1172
|
+
execSync("ls /Applications/Ami.app", { stdio: "ignore" });
|
|
1173
|
+
return true;
|
|
1174
|
+
} catch {
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
const installAmi = () => {
|
|
1179
|
+
logger.log("Ami not found. Installing...");
|
|
1180
|
+
logger.break();
|
|
1181
|
+
try {
|
|
1182
|
+
execSync(`curl -fsSL ${AMI_INSTALL_URL} | bash`, { stdio: "inherit" });
|
|
1183
|
+
} catch {
|
|
1184
|
+
logger.error("Failed to install Ami. Visit https://ami.dev to install manually.");
|
|
1185
|
+
process.exit(1);
|
|
1186
|
+
}
|
|
1187
|
+
logger.break();
|
|
1188
|
+
};
|
|
1189
|
+
const openAmiToFix = (directory) => {
|
|
1190
|
+
const resolvedDirectory = path.resolve(directory);
|
|
1191
|
+
if (!isAmiInstalled()) installAmi();
|
|
1192
|
+
logger.log("Opening Ami to fix react-doctor issues...");
|
|
1193
|
+
const deeplink = buildAmiDeeplink(resolvedDirectory);
|
|
1194
|
+
try {
|
|
1195
|
+
execSync(`open "${deeplink}"`, { stdio: "ignore" });
|
|
1196
|
+
logger.success("Opened Ami with react-doctor fix prompt.");
|
|
1197
|
+
} catch {
|
|
1198
|
+
logger.break();
|
|
1199
|
+
logger.dim("Could not open Ami automatically. Open this URL manually:");
|
|
1200
|
+
logger.info(deeplink);
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
const fixAction = (directory) => {
|
|
1204
|
+
try {
|
|
1205
|
+
openAmiToFix(directory);
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
handleError(error);
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
const fixCommand = new Command("fix").description("Open Ami to auto-fix react-doctor issues").argument("[directory]", "project directory", ".").action(fixAction);
|
|
1211
|
+
const installAmiCommand = new Command("install-ami").description("Open Ami to auto-fix react-doctor issues").argument("[directory]", "project directory", ".").action(fixAction);
|
|
1212
|
+
program.addCommand(fixCommand);
|
|
1213
|
+
program.addCommand(installAmiCommand);
|
|
1144
1214
|
const main$1 = async () => {
|
|
1145
1215
|
await program.parseAsync();
|
|
1146
1216
|
};
|