react-doctor 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/dist/cli.js +67 -192
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,15 +38,15 @@ Use `--verbose` to see affected files and line numbers:
|
|
|
38
38
|
npx -y react-doctor@latest . --verbose
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
## Install
|
|
41
|
+
## Install for your coding agent
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Teach your coding agent all 47+ React best practice rules:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
|
|
46
|
+
curl -fsSL https://react.doctor/install-skill.sh | bash
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Supports Cursor, Claude Code, Amp Code, Codex, Gemini CLI, OpenCode, Windsurf, and Antigravity.
|
|
50
50
|
|
|
51
51
|
## Options
|
|
52
52
|
|
|
@@ -64,7 +64,6 @@ Options:
|
|
|
64
64
|
--diff [base] scan only files changed vs base branch
|
|
65
65
|
--no-ami skip Ami-related prompts
|
|
66
66
|
--fix open Ami to auto-fix all issues
|
|
67
|
-
--prompt copy latest scan output to clipboard
|
|
68
67
|
-h, --help display help for command
|
|
69
68
|
```
|
|
70
69
|
|
package/dist/cli.js
CHANGED
|
@@ -23,13 +23,13 @@ const PERFECT_SCORE = 100;
|
|
|
23
23
|
const SCORE_GOOD_THRESHOLD = 75;
|
|
24
24
|
const SCORE_OK_THRESHOLD = 50;
|
|
25
25
|
const SCORE_BAR_WIDTH_CHARS = 50;
|
|
26
|
-
const SEPARATOR_LENGTH_CHARS = 40;
|
|
27
26
|
const SUMMARY_BOX_HORIZONTAL_PADDING_CHARS = 1;
|
|
28
27
|
const SUMMARY_BOX_OUTER_INDENT_CHARS = 2;
|
|
29
28
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
30
29
|
const ESTIMATE_SCORE_API_URL = "https://www.react.doctor/api/estimate-score";
|
|
31
30
|
const SHARE_BASE_URL = "https://www.react.doctor/share";
|
|
32
31
|
const OPEN_BASE_URL = "https://www.react.doctor/open";
|
|
32
|
+
const INSTALL_SKILL_URL = "https://www.react.doctor/install-skill";
|
|
33
33
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
34
34
|
const OFFLINE_MESSAGE = "You are offline, could not calculate score. Reconnect to calculate.";
|
|
35
35
|
const OFFLINE_FLAG_MESSAGE = "Score not calculated. Remove --offline to calculate score.";
|
|
@@ -41,11 +41,6 @@ const ERROR_RULE_PENALTY = 1.5;
|
|
|
41
41
|
const WARNING_RULE_PENALTY = .75;
|
|
42
42
|
const ERROR_ESTIMATED_FIX_RATE = .85;
|
|
43
43
|
const WARNING_ESTIMATED_FIX_RATE = .8;
|
|
44
|
-
const buildDiagnosticPayload = (diagnostics) => diagnostics.map((diagnostic) => ({
|
|
45
|
-
plugin: diagnostic.plugin,
|
|
46
|
-
rule: diagnostic.rule,
|
|
47
|
-
severity: diagnostic.severity
|
|
48
|
-
}));
|
|
49
44
|
const getScoreLabel = (score) => {
|
|
50
45
|
if (score >= SCORE_GOOD_THRESHOLD) return "Great";
|
|
51
46
|
if (score >= SCORE_OK_THRESHOLD) return "Needs work";
|
|
@@ -84,7 +79,7 @@ const calculateScore = async (diagnostics) => {
|
|
|
84
79
|
const response = await fetch(SCORE_API_URL, {
|
|
85
80
|
method: "POST",
|
|
86
81
|
headers: { "Content-Type": "application/json" },
|
|
87
|
-
body: JSON.stringify({ diagnostics
|
|
82
|
+
body: JSON.stringify({ diagnostics })
|
|
88
83
|
});
|
|
89
84
|
if (!response.ok) return null;
|
|
90
85
|
return await response.json();
|
|
@@ -97,7 +92,7 @@ const fetchEstimatedScore = async (diagnostics) => {
|
|
|
97
92
|
const response = await fetch(ESTIMATE_SCORE_API_URL, {
|
|
98
93
|
method: "POST",
|
|
99
94
|
headers: { "Content-Type": "application/json" },
|
|
100
|
-
body: JSON.stringify({ diagnostics
|
|
95
|
+
body: JSON.stringify({ diagnostics })
|
|
101
96
|
});
|
|
102
97
|
if (!response.ok) return estimateScoreLocally(diagnostics);
|
|
103
98
|
return await response.json();
|
|
@@ -468,57 +463,29 @@ const highlighter = {
|
|
|
468
463
|
dim: pc.dim
|
|
469
464
|
};
|
|
470
465
|
|
|
471
|
-
//#endregion
|
|
472
|
-
//#region src/utils/strip-ansi.ts
|
|
473
|
-
const ANSI_ESCAPE_SEQUENCE = String.raw`\u001B\[[0-9;]*m`;
|
|
474
|
-
const ANSI_ESCAPE_PATTERN = new RegExp(ANSI_ESCAPE_SEQUENCE, "g");
|
|
475
|
-
const stripAnsi = (text) => text.replace(ANSI_ESCAPE_PATTERN, "");
|
|
476
|
-
|
|
477
466
|
//#endregion
|
|
478
467
|
//#region src/utils/logger.ts
|
|
479
|
-
const loggerCaptureState = {
|
|
480
|
-
isEnabled: false,
|
|
481
|
-
lines: []
|
|
482
|
-
};
|
|
483
|
-
const captureLogLine = (text) => {
|
|
484
|
-
if (!loggerCaptureState.isEnabled) return;
|
|
485
|
-
loggerCaptureState.lines.push(stripAnsi(text));
|
|
486
|
-
};
|
|
487
|
-
const writeLogLine = (text) => {
|
|
488
|
-
console.log(text);
|
|
489
|
-
captureLogLine(text);
|
|
490
|
-
};
|
|
491
|
-
const startLoggerCapture = () => {
|
|
492
|
-
loggerCaptureState.isEnabled = true;
|
|
493
|
-
loggerCaptureState.lines = [];
|
|
494
|
-
};
|
|
495
|
-
const stopLoggerCapture = () => {
|
|
496
|
-
const capturedOutput = loggerCaptureState.lines.join("\n");
|
|
497
|
-
loggerCaptureState.isEnabled = false;
|
|
498
|
-
loggerCaptureState.lines = [];
|
|
499
|
-
return capturedOutput;
|
|
500
|
-
};
|
|
501
468
|
const logger = {
|
|
502
469
|
error(...args) {
|
|
503
|
-
|
|
470
|
+
console.log(highlighter.error(args.join(" ")));
|
|
504
471
|
},
|
|
505
472
|
warn(...args) {
|
|
506
|
-
|
|
473
|
+
console.log(highlighter.warn(args.join(" ")));
|
|
507
474
|
},
|
|
508
475
|
info(...args) {
|
|
509
|
-
|
|
476
|
+
console.log(highlighter.info(args.join(" ")));
|
|
510
477
|
},
|
|
511
478
|
success(...args) {
|
|
512
|
-
|
|
479
|
+
console.log(highlighter.success(args.join(" ")));
|
|
513
480
|
},
|
|
514
481
|
dim(...args) {
|
|
515
|
-
|
|
482
|
+
console.log(highlighter.dim(args.join(" ")));
|
|
516
483
|
},
|
|
517
484
|
log(...args) {
|
|
518
|
-
|
|
485
|
+
console.log(args.join(" "));
|
|
519
486
|
},
|
|
520
487
|
break() {
|
|
521
|
-
|
|
488
|
+
console.log("");
|
|
522
489
|
}
|
|
523
490
|
};
|
|
524
491
|
|
|
@@ -1435,46 +1402,6 @@ const scan = async (directory, inputOptions = {}) => {
|
|
|
1435
1402
|
};
|
|
1436
1403
|
};
|
|
1437
1404
|
|
|
1438
|
-
//#endregion
|
|
1439
|
-
//#region src/utils/copy-to-clipboard.ts
|
|
1440
|
-
const getClipboardCommands = () => {
|
|
1441
|
-
if (process.platform === "darwin") return [{
|
|
1442
|
-
command: "pbcopy",
|
|
1443
|
-
args: []
|
|
1444
|
-
}];
|
|
1445
|
-
if (process.platform === "win32") return [{
|
|
1446
|
-
command: "clip",
|
|
1447
|
-
args: []
|
|
1448
|
-
}];
|
|
1449
|
-
return [
|
|
1450
|
-
{
|
|
1451
|
-
command: "wl-copy",
|
|
1452
|
-
args: []
|
|
1453
|
-
},
|
|
1454
|
-
{
|
|
1455
|
-
command: "xclip",
|
|
1456
|
-
args: ["-selection", "clipboard"]
|
|
1457
|
-
},
|
|
1458
|
-
{
|
|
1459
|
-
command: "xsel",
|
|
1460
|
-
args: ["--clipboard", "--input"]
|
|
1461
|
-
}
|
|
1462
|
-
];
|
|
1463
|
-
};
|
|
1464
|
-
const copyToClipboard = (text) => {
|
|
1465
|
-
const clipboardCommands = getClipboardCommands();
|
|
1466
|
-
for (const clipboardCommand of clipboardCommands) if (spawnSync(clipboardCommand.command, clipboardCommand.args, {
|
|
1467
|
-
input: text,
|
|
1468
|
-
stdio: [
|
|
1469
|
-
"pipe",
|
|
1470
|
-
"ignore",
|
|
1471
|
-
"ignore"
|
|
1472
|
-
],
|
|
1473
|
-
encoding: "utf8"
|
|
1474
|
-
}).status === 0) return true;
|
|
1475
|
-
return false;
|
|
1476
|
-
};
|
|
1477
|
-
|
|
1478
1405
|
//#endregion
|
|
1479
1406
|
//#region src/utils/get-diff-files.ts
|
|
1480
1407
|
const getCurrentBranch = (directory) => {
|
|
@@ -1520,12 +1447,33 @@ const getChangedFilesSinceBranch = (directory, baseBranch) => {
|
|
|
1520
1447
|
return [];
|
|
1521
1448
|
}
|
|
1522
1449
|
};
|
|
1450
|
+
const getUncommittedChangedFiles = (directory) => {
|
|
1451
|
+
try {
|
|
1452
|
+
const output = execSync("git diff --name-only --diff-filter=ACMR --relative HEAD", {
|
|
1453
|
+
cwd: directory,
|
|
1454
|
+
stdio: "pipe"
|
|
1455
|
+
}).toString().trim();
|
|
1456
|
+
if (!output) return [];
|
|
1457
|
+
return output.split("\n").filter(Boolean);
|
|
1458
|
+
} catch {
|
|
1459
|
+
return [];
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1523
1462
|
const getDiffInfo = (directory, explicitBaseBranch) => {
|
|
1524
1463
|
const currentBranch = getCurrentBranch(directory);
|
|
1525
1464
|
if (!currentBranch) return null;
|
|
1526
1465
|
const baseBranch = explicitBaseBranch ?? detectDefaultBranch(directory);
|
|
1527
1466
|
if (!baseBranch) return null;
|
|
1528
|
-
if (currentBranch === baseBranch)
|
|
1467
|
+
if (currentBranch === baseBranch) {
|
|
1468
|
+
const uncommittedFiles = getUncommittedChangedFiles(directory);
|
|
1469
|
+
if (uncommittedFiles.length === 0) return null;
|
|
1470
|
+
return {
|
|
1471
|
+
currentBranch,
|
|
1472
|
+
baseBranch,
|
|
1473
|
+
changedFiles: uncommittedFiles,
|
|
1474
|
+
isCurrentChanges: true
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1529
1477
|
return {
|
|
1530
1478
|
currentBranch,
|
|
1531
1479
|
baseBranch,
|
|
@@ -1534,34 +1482,6 @@ const getDiffInfo = (directory, explicitBaseBranch) => {
|
|
|
1534
1482
|
};
|
|
1535
1483
|
const filterSourceFiles = (filePaths) => filePaths.filter((filePath) => SOURCE_FILE_PATTERN.test(filePath));
|
|
1536
1484
|
|
|
1537
|
-
//#endregion
|
|
1538
|
-
//#region src/utils/global-install.ts
|
|
1539
|
-
const isGloballyInstalled = () => {
|
|
1540
|
-
try {
|
|
1541
|
-
return !execSync("which react-doctor", {
|
|
1542
|
-
stdio: "pipe",
|
|
1543
|
-
encoding: "utf-8"
|
|
1544
|
-
}).trim().includes("/_npx/");
|
|
1545
|
-
} catch {
|
|
1546
|
-
return false;
|
|
1547
|
-
}
|
|
1548
|
-
};
|
|
1549
|
-
const maybeInstallGlobally = () => {
|
|
1550
|
-
try {
|
|
1551
|
-
if (isGloballyInstalled()) return;
|
|
1552
|
-
const child = spawn("npm", [
|
|
1553
|
-
"install",
|
|
1554
|
-
"-g",
|
|
1555
|
-
"react-doctor@latest"
|
|
1556
|
-
], {
|
|
1557
|
-
detached: true,
|
|
1558
|
-
stdio: "ignore"
|
|
1559
|
-
});
|
|
1560
|
-
child.on("error", () => {});
|
|
1561
|
-
child.unref();
|
|
1562
|
-
} catch {}
|
|
1563
|
-
};
|
|
1564
|
-
|
|
1565
1485
|
//#endregion
|
|
1566
1486
|
//#region src/utils/handle-error.ts
|
|
1567
1487
|
const DEFAULT_HANDLE_ERROR_OPTIONS = { shouldExit: true };
|
|
@@ -1714,8 +1634,7 @@ const promptProjectSelection = async (workspacePackages, rootDirectory) => {
|
|
|
1714
1634
|
//#region src/utils/skill-prompt.ts
|
|
1715
1635
|
const CONFIG_DIRECTORY = join(homedir(), ".react-doctor");
|
|
1716
1636
|
const CONFIG_FILE = join(CONFIG_DIRECTORY, "config.json");
|
|
1717
|
-
const
|
|
1718
|
-
const readConfig = () => {
|
|
1637
|
+
const readSkillPromptConfig = () => {
|
|
1719
1638
|
try {
|
|
1720
1639
|
if (!existsSync(CONFIG_FILE)) return {};
|
|
1721
1640
|
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
@@ -1723,7 +1642,7 @@ const readConfig = () => {
|
|
|
1723
1642
|
return {};
|
|
1724
1643
|
}
|
|
1725
1644
|
};
|
|
1726
|
-
const
|
|
1645
|
+
const writeSkillPromptConfig = (config) => {
|
|
1727
1646
|
try {
|
|
1728
1647
|
if (!existsSync(CONFIG_DIRECTORY)) mkdirSync(CONFIG_DIRECTORY, { recursive: true });
|
|
1729
1648
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
@@ -1731,21 +1650,21 @@ const writeConfig = (config) => {
|
|
|
1731
1650
|
};
|
|
1732
1651
|
const installSkill = () => {
|
|
1733
1652
|
try {
|
|
1734
|
-
execSync(`
|
|
1653
|
+
execSync(`curl -fsSL ${INSTALL_SKILL_URL} | bash`, { stdio: "inherit" });
|
|
1735
1654
|
} catch {
|
|
1736
1655
|
logger.break();
|
|
1737
1656
|
logger.dim("Skill install failed. You can install manually:");
|
|
1738
|
-
logger.dim(`
|
|
1657
|
+
logger.dim(` curl -fsSL ${INSTALL_SKILL_URL} | bash`);
|
|
1739
1658
|
}
|
|
1740
1659
|
};
|
|
1741
1660
|
const maybePromptSkillInstall = async (shouldSkipPrompts) => {
|
|
1742
|
-
const config =
|
|
1661
|
+
const config = readSkillPromptConfig();
|
|
1743
1662
|
if (config.skillPromptDismissed) return;
|
|
1744
1663
|
if (shouldSkipPrompts) return;
|
|
1745
1664
|
logger.break();
|
|
1746
1665
|
logger.log(`${highlighter.info("💡")} Have your coding agent fix these issues automatically?`);
|
|
1747
|
-
logger.dim(` Install the ${highlighter.info("react-doctor")} skill to teach Cursor, Claude Code
|
|
1748
|
-
logger.dim(" Ami, and other AI agents how to diagnose and fix
|
|
1666
|
+
logger.dim(` Install the ${highlighter.info("react-doctor")} skill to teach Cursor, Claude Code,`);
|
|
1667
|
+
logger.dim(" Ami, and other AI agents how to diagnose and fix React issues.");
|
|
1749
1668
|
logger.break();
|
|
1750
1669
|
const { shouldInstall } = await prompts({
|
|
1751
1670
|
type: "confirm",
|
|
@@ -1756,16 +1675,16 @@ const maybePromptSkillInstall = async (shouldSkipPrompts) => {
|
|
|
1756
1675
|
if (shouldInstall) {
|
|
1757
1676
|
logger.break();
|
|
1758
1677
|
installSkill();
|
|
1759
|
-
writeConfig({
|
|
1760
|
-
...config,
|
|
1761
|
-
skillPromptDismissed: true
|
|
1762
|
-
});
|
|
1763
1678
|
}
|
|
1679
|
+
writeSkillPromptConfig({
|
|
1680
|
+
...config,
|
|
1681
|
+
skillPromptDismissed: true
|
|
1682
|
+
});
|
|
1764
1683
|
};
|
|
1765
1684
|
|
|
1766
1685
|
//#endregion
|
|
1767
1686
|
//#region src/cli.ts
|
|
1768
|
-
const VERSION = "0.0.
|
|
1687
|
+
const VERSION = "0.0.26";
|
|
1769
1688
|
const exitWithFixHint = () => {
|
|
1770
1689
|
logger.break();
|
|
1771
1690
|
logger.log("Cancelled.");
|
|
@@ -1779,7 +1698,7 @@ const resolveDiffMode = async (diffInfo, effectiveDiff, shouldSkipPrompts, isSco
|
|
|
1779
1698
|
if (effectiveDiff !== void 0 && effectiveDiff !== false) {
|
|
1780
1699
|
if (diffInfo) return true;
|
|
1781
1700
|
if (!isScoreOnly) {
|
|
1782
|
-
logger.warn("
|
|
1701
|
+
logger.warn("No feature branch or uncommitted changes detected. Running full scan.");
|
|
1783
1702
|
logger.break();
|
|
1784
1703
|
}
|
|
1785
1704
|
return false;
|
|
@@ -1789,18 +1708,16 @@ const resolveDiffMode = async (diffInfo, effectiveDiff, shouldSkipPrompts, isSco
|
|
|
1789
1708
|
if (changedSourceFiles.length === 0) return false;
|
|
1790
1709
|
if (shouldSkipPrompts) return true;
|
|
1791
1710
|
if (isScoreOnly) return false;
|
|
1792
|
-
const {
|
|
1711
|
+
const { shouldScanChangedOnly } = await prompts({
|
|
1793
1712
|
type: "confirm",
|
|
1794
|
-
name: "
|
|
1795
|
-
message: `On branch ${diffInfo.currentBranch} (${changedSourceFiles.length} changed files vs ${diffInfo.baseBranch}). Only scan this branch?`,
|
|
1713
|
+
name: "shouldScanChangedOnly",
|
|
1714
|
+
message: diffInfo.isCurrentChanges ? `Found ${changedSourceFiles.length} uncommitted changed files. Only scan current changes?` : `On branch ${diffInfo.currentBranch} (${changedSourceFiles.length} changed files vs ${diffInfo.baseBranch}). Only scan this branch?`,
|
|
1796
1715
|
initial: true
|
|
1797
1716
|
});
|
|
1798
|
-
return Boolean(
|
|
1717
|
+
return Boolean(shouldScanChangedOnly);
|
|
1799
1718
|
};
|
|
1800
|
-
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("--diff [base]", "scan only files changed vs base branch").option("--offline", "skip telemetry (anonymous, not stored, only used to calculate score)").option("--no-ami", "skip Ami-related prompts").option("--fix", "open Ami to auto-fix all issues").
|
|
1801
|
-
const isScoreOnly = flags.score
|
|
1802
|
-
const shouldCopyPromptOutput = flags.prompt;
|
|
1803
|
-
startLoggerCapture();
|
|
1719
|
+
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("--diff [base]", "scan only files changed vs base branch").option("--offline", "skip telemetry (anonymous, not stored, only used to calculate score)").option("--no-ami", "skip Ami-related prompts").option("--fix", "open Ami to auto-fix all issues").action(async (directory, flags) => {
|
|
1720
|
+
const isScoreOnly = flags.score;
|
|
1804
1721
|
try {
|
|
1805
1722
|
const resolvedDirectory = path.resolve(directory);
|
|
1806
1723
|
const userConfig = loadConfig(resolvedDirectory);
|
|
@@ -1812,7 +1729,7 @@ const program = new Command().name("react-doctor").description("Diagnose React c
|
|
|
1812
1729
|
const scanOptions = {
|
|
1813
1730
|
lint: isCliOverride("lint") ? flags.lint : userConfig?.lint ?? flags.lint,
|
|
1814
1731
|
deadCode: isCliOverride("deadCode") ? flags.deadCode : userConfig?.deadCode ?? flags.deadCode,
|
|
1815
|
-
verbose:
|
|
1732
|
+
verbose: isCliOverride("verbose") ? Boolean(flags.verbose) : userConfig?.verbose ?? false,
|
|
1816
1733
|
scoreOnly: isScoreOnly,
|
|
1817
1734
|
offline: flags.offline
|
|
1818
1735
|
};
|
|
@@ -1833,7 +1750,8 @@ const program = new Command().name("react-doctor").description("Diagnose React c
|
|
|
1833
1750
|
const diffInfo = getDiffInfo(resolvedDirectory, explicitBaseBranch);
|
|
1834
1751
|
const isDiffMode = await resolveDiffMode(diffInfo, effectiveDiff, shouldSkipPrompts, isScoreOnly);
|
|
1835
1752
|
if (isDiffMode && diffInfo && !isScoreOnly) {
|
|
1836
|
-
|
|
1753
|
+
if (diffInfo.isCurrentChanges) logger.log("Scanning uncommitted changes");
|
|
1754
|
+
else logger.log(`Scanning changes: ${highlighter.info(diffInfo.currentBranch)} → ${highlighter.info(diffInfo.baseBranch)}`);
|
|
1837
1755
|
logger.break();
|
|
1838
1756
|
}
|
|
1839
1757
|
const allDiagnostics = [];
|
|
@@ -1864,18 +1782,13 @@ const program = new Command().name("react-doctor").description("Diagnose React c
|
|
|
1864
1782
|
allDiagnostics.push(...scanResult.diagnostics);
|
|
1865
1783
|
if (!isScoreOnly) logger.break();
|
|
1866
1784
|
}
|
|
1867
|
-
const capturedScanOutput = stopLoggerCapture();
|
|
1868
1785
|
if (flags.fix) openAmiToFix(resolvedDirectory);
|
|
1869
|
-
if (
|
|
1870
|
-
else if (!isScoreOnly) {
|
|
1786
|
+
if (!isScoreOnly && !shouldSkipAmiPrompts && !flags.fix) {
|
|
1871
1787
|
await maybePromptSkillInstall(shouldSkipAmiPrompts);
|
|
1872
|
-
|
|
1788
|
+
await maybePromptFix(resolvedDirectory, allDiagnostics, flags.offline ? null : await fetchEstimatedScore(allDiagnostics));
|
|
1873
1789
|
}
|
|
1874
1790
|
} catch (error) {
|
|
1875
|
-
handleError(error
|
|
1876
|
-
} finally {
|
|
1877
|
-
const remainingOutput = stopLoggerCapture();
|
|
1878
|
-
if (shouldCopyPromptOutput && remainingOutput) copyPromptToClipboard(remainingOutput, !isScoreOnly);
|
|
1791
|
+
handleError(error);
|
|
1879
1792
|
}
|
|
1880
1793
|
}).addHelpText("after", `
|
|
1881
1794
|
${highlighter.dim("Learn more:")}
|
|
@@ -1890,9 +1803,6 @@ const colorizeByScore = (text, score) => {
|
|
|
1890
1803
|
return highlighter.error(text);
|
|
1891
1804
|
};
|
|
1892
1805
|
const DEEPLINK_FIX_PROMPT = "Run `npx -y react-doctor@latest .` to diagnose issues, then fix all reported issues one by one. After applying fixes, run it again to verify the results improved.";
|
|
1893
|
-
const CLIPBOARD_FIX_PROMPT = "Fix all issues reported in the react-doctor diagnostics below, one by one. After applying fixes, run `npx -y react-doctor@latest .` again to verify the results improved.";
|
|
1894
|
-
const REACT_DOCTOR_OUTPUT_LABEL = "react-doctor output";
|
|
1895
|
-
const SCAN_SUMMARY_SEPARATOR = "─".repeat(SEPARATOR_LENGTH_CHARS);
|
|
1896
1806
|
const isAmiInstalled = () => {
|
|
1897
1807
|
if (process.platform === "darwin") return existsSync("/Applications/Ami.app") || existsSync(path.join(os.homedir(), "Applications", "Ami.app"));
|
|
1898
1808
|
if (process.platform === "win32") {
|
|
@@ -1961,24 +1871,7 @@ const openAmiToFix = (directory) => {
|
|
|
1961
1871
|
logger.info(webDeeplink);
|
|
1962
1872
|
}
|
|
1963
1873
|
};
|
|
1964
|
-
const buildPromptWithOutput = (reactDoctorOutput) => {
|
|
1965
|
-
const summaryStartIndex = reactDoctorOutput.indexOf(SCAN_SUMMARY_SEPARATOR);
|
|
1966
|
-
const normalizedReactDoctorOutput = (summaryStartIndex === -1 ? reactDoctorOutput : reactDoctorOutput.slice(0, summaryStartIndex).trimEnd()).trim();
|
|
1967
|
-
return `${CLIPBOARD_FIX_PROMPT}\n\n${REACT_DOCTOR_OUTPUT_LABEL}:\n\`\`\`\n${normalizedReactDoctorOutput.length > 0 ? normalizedReactDoctorOutput : "No output captured."}\n\`\`\``;
|
|
1968
|
-
};
|
|
1969
|
-
const copyPromptToClipboard = (reactDoctorOutput, shouldLogResult) => {
|
|
1970
|
-
const promptWithOutput = buildPromptWithOutput(reactDoctorOutput);
|
|
1971
|
-
const didCopyPromptToClipboard = copyToClipboard(promptWithOutput);
|
|
1972
|
-
if (!shouldLogResult) return;
|
|
1973
|
-
if (didCopyPromptToClipboard) {
|
|
1974
|
-
logger.success("Copied latest scan output to clipboard");
|
|
1975
|
-
return;
|
|
1976
|
-
}
|
|
1977
|
-
logger.warn("Could not copy prompt to clipboard automatically. Use this prompt:");
|
|
1978
|
-
logger.info(promptWithOutput);
|
|
1979
|
-
};
|
|
1980
1874
|
const FIX_METHOD_AMI = "ami";
|
|
1981
|
-
const FIX_METHOD_CLIPBOARD = "clipboard";
|
|
1982
1875
|
const FIX_COMMAND_HINT = "npx react-doctor@latest --fix";
|
|
1983
1876
|
const buildAmiBanner = (issueCount, currentScore, estimatedScore) => {
|
|
1984
1877
|
const currentScoreDisplay = colorizeByScore(String(currentScore), currentScore);
|
|
@@ -1995,13 +1888,6 @@ const buildAmiBanner = (issueCount, currentScore, estimatedScore) => {
|
|
|
1995
1888
|
createFramedLine(`Free to use. ${AMI_WEBSITE_URL}`, `Free to use. ${highlighter.info(AMI_WEBSITE_URL)}`)
|
|
1996
1889
|
]);
|
|
1997
1890
|
};
|
|
1998
|
-
const buildClipboardWarningBanner = () => renderFramedBoxString([
|
|
1999
|
-
createFramedLine("⚠ Other agents may not fix these issues well.", `${highlighter.warn("⚠")} Other agents may not fix these issues well.`),
|
|
2000
|
-
createFramedLine(""),
|
|
2001
|
-
createFramedLine("react-doctor diagnostics require React-specific context"),
|
|
2002
|
-
createFramedLine("that general-purpose agents often miss, leading to"),
|
|
2003
|
-
createFramedLine("incomplete or incorrect fixes.")
|
|
2004
|
-
]);
|
|
2005
1891
|
const buildSkipBanner = (issueCount, estimatedScore) => {
|
|
2006
1892
|
const issueLabel = issueCount === 1 ? "issue" : "issues";
|
|
2007
1893
|
const estimatedScoreDisplay = colorizeByScore(`~${estimatedScore}`, estimatedScore);
|
|
@@ -2014,10 +1900,9 @@ const buildSkipBanner = (issueCount, estimatedScore) => {
|
|
|
2014
1900
|
const configureFixBanners = (issueCount, estimatedScoreResult) => {
|
|
2015
1901
|
const { currentScore, estimatedScore } = estimatedScoreResult;
|
|
2016
1902
|
setSelectBanner(buildAmiBanner(issueCount, currentScore, estimatedScore), 0);
|
|
2017
|
-
setSelectBanner(
|
|
2018
|
-
setSelectBanner(buildSkipBanner(issueCount, estimatedScore), 2);
|
|
1903
|
+
setSelectBanner(buildSkipBanner(issueCount, estimatedScore), 1);
|
|
2019
1904
|
};
|
|
2020
|
-
const maybePromptFix = async (directory, diagnostics, estimatedScoreResult
|
|
1905
|
+
const maybePromptFix = async (directory, diagnostics, estimatedScoreResult) => {
|
|
2021
1906
|
if (diagnostics.length === 0) return;
|
|
2022
1907
|
logger.break();
|
|
2023
1908
|
if (estimatedScoreResult) configureFixBanners(diagnostics.length, estimatedScoreResult);
|
|
@@ -2025,26 +1910,17 @@ const maybePromptFix = async (directory, diagnostics, estimatedScoreResult, capt
|
|
|
2025
1910
|
type: "select",
|
|
2026
1911
|
name: "fixMethod",
|
|
2027
1912
|
message: "Fix issues?",
|
|
2028
|
-
choices: [
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
description: "Other agents may lack context for react-doctor fixes",
|
|
2037
|
-
value: FIX_METHOD_CLIPBOARD
|
|
2038
|
-
},
|
|
2039
|
-
{
|
|
2040
|
-
title: "Skip",
|
|
2041
|
-
value: "skip"
|
|
2042
|
-
}
|
|
2043
|
-
]
|
|
1913
|
+
choices: [{
|
|
1914
|
+
title: "Use ami.dev (recommended)",
|
|
1915
|
+
description: "Optimized coding agent for React Doctor",
|
|
1916
|
+
value: FIX_METHOD_AMI
|
|
1917
|
+
}, {
|
|
1918
|
+
title: "Skip",
|
|
1919
|
+
value: "skip"
|
|
1920
|
+
}]
|
|
2044
1921
|
});
|
|
2045
1922
|
clearSelectBanner();
|
|
2046
1923
|
if (fixMethod === FIX_METHOD_AMI) openAmiToFix(directory);
|
|
2047
|
-
else if (fixMethod === FIX_METHOD_CLIPBOARD) copyPromptToClipboard(capturedScanOutput, true);
|
|
2048
1924
|
else {
|
|
2049
1925
|
logger.break();
|
|
2050
1926
|
logger.dim(` Run ${highlighter.info(FIX_COMMAND_HINT)} anytime to fix issues.`);
|
|
@@ -2062,7 +1938,6 @@ const installAmiCommand = new Command("install-ami").description("Install Ami an
|
|
|
2062
1938
|
program.addCommand(fixCommand);
|
|
2063
1939
|
program.addCommand(installAmiCommand);
|
|
2064
1940
|
const main$1 = async () => {
|
|
2065
|
-
maybeInstallGlobally();
|
|
2066
1941
|
await program.parseAsync();
|
|
2067
1942
|
};
|
|
2068
1943
|
main$1();
|