react-doctor 0.0.20 → 0.0.22
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 +308 -127
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -27,24 +27,64 @@ const SEPARATOR_LENGTH_CHARS = 40;
|
|
|
27
27
|
const SUMMARY_BOX_HORIZONTAL_PADDING_CHARS = 1;
|
|
28
28
|
const SUMMARY_BOX_OUTER_INDENT_CHARS = 2;
|
|
29
29
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
30
|
+
const ESTIMATE_SCORE_API_URL = "https://www.react.doctor/api/estimate-score";
|
|
30
31
|
const SHARE_BASE_URL = "https://www.react.doctor/share";
|
|
32
|
+
const OPEN_BASE_URL = "https://www.react.doctor/open";
|
|
31
33
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
32
34
|
const OFFLINE_MESSAGE = "You are offline, could not calculate score. Reconnect to calculate.";
|
|
35
|
+
const OFFLINE_FLAG_MESSAGE = "Score not calculated. Remove --offline to calculate score.";
|
|
33
36
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
34
37
|
|
|
35
38
|
//#endregion
|
|
36
39
|
//#region src/utils/calculate-score.ts
|
|
40
|
+
const ERROR_RULE_PENALTY = 1.5;
|
|
41
|
+
const WARNING_RULE_PENALTY = .75;
|
|
42
|
+
const ERROR_ESTIMATED_FIX_RATE = .85;
|
|
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
|
+
const getScoreLabel = (score) => {
|
|
50
|
+
if (score >= SCORE_GOOD_THRESHOLD) return "Great";
|
|
51
|
+
if (score >= SCORE_OK_THRESHOLD) return "Needs work";
|
|
52
|
+
return "Critical";
|
|
53
|
+
};
|
|
54
|
+
const countUniqueRules = (diagnostics) => {
|
|
55
|
+
const errorRules = /* @__PURE__ */ new Set();
|
|
56
|
+
const warningRules = /* @__PURE__ */ new Set();
|
|
57
|
+
for (const diagnostic of diagnostics) {
|
|
58
|
+
const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
59
|
+
if (diagnostic.severity === "error") errorRules.add(ruleKey);
|
|
60
|
+
else warningRules.add(ruleKey);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
errorRuleCount: errorRules.size,
|
|
64
|
+
warningRuleCount: warningRules.size
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const scoreFromRuleCounts = (errorRuleCount, warningRuleCount) => {
|
|
68
|
+
const penalty = errorRuleCount * ERROR_RULE_PENALTY + warningRuleCount * WARNING_RULE_PENALTY;
|
|
69
|
+
return Math.max(0, Math.round(PERFECT_SCORE - penalty));
|
|
70
|
+
};
|
|
71
|
+
const estimateScoreLocally = (diagnostics) => {
|
|
72
|
+
const { errorRuleCount, warningRuleCount } = countUniqueRules(diagnostics);
|
|
73
|
+
const currentScore = scoreFromRuleCounts(errorRuleCount, warningRuleCount);
|
|
74
|
+
const estimatedScore = scoreFromRuleCounts(Math.round(errorRuleCount * (1 - ERROR_ESTIMATED_FIX_RATE)), Math.round(warningRuleCount * (1 - WARNING_ESTIMATED_FIX_RATE)));
|
|
75
|
+
return {
|
|
76
|
+
currentScore,
|
|
77
|
+
currentLabel: getScoreLabel(currentScore),
|
|
78
|
+
estimatedScore,
|
|
79
|
+
estimatedLabel: getScoreLabel(estimatedScore)
|
|
80
|
+
};
|
|
81
|
+
};
|
|
37
82
|
const calculateScore = async (diagnostics) => {
|
|
38
|
-
const payload = diagnostics.map((diagnostic) => ({
|
|
39
|
-
plugin: diagnostic.plugin,
|
|
40
|
-
rule: diagnostic.rule,
|
|
41
|
-
severity: diagnostic.severity
|
|
42
|
-
}));
|
|
43
83
|
try {
|
|
44
84
|
const response = await fetch(SCORE_API_URL, {
|
|
45
85
|
method: "POST",
|
|
46
86
|
headers: { "Content-Type": "application/json" },
|
|
47
|
-
body: JSON.stringify({ diagnostics:
|
|
87
|
+
body: JSON.stringify({ diagnostics: buildDiagnosticPayload(diagnostics) })
|
|
48
88
|
});
|
|
49
89
|
if (!response.ok) return null;
|
|
50
90
|
return await response.json();
|
|
@@ -52,6 +92,19 @@ const calculateScore = async (diagnostics) => {
|
|
|
52
92
|
return null;
|
|
53
93
|
}
|
|
54
94
|
};
|
|
95
|
+
const fetchEstimatedScore = async (diagnostics) => {
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch(ESTIMATE_SCORE_API_URL, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: { "Content-Type": "application/json" },
|
|
100
|
+
body: JSON.stringify({ diagnostics: buildDiagnosticPayload(diagnostics) })
|
|
101
|
+
});
|
|
102
|
+
if (!response.ok) return estimateScoreLocally(diagnostics);
|
|
103
|
+
return await response.json();
|
|
104
|
+
} catch {
|
|
105
|
+
return estimateScoreLocally(diagnostics);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
55
108
|
|
|
56
109
|
//#endregion
|
|
57
110
|
//#region src/plugin/constants.ts
|
|
@@ -399,19 +452,6 @@ const filterIgnoredDiagnostics = (diagnostics, config) => {
|
|
|
399
452
|
});
|
|
400
453
|
};
|
|
401
454
|
|
|
402
|
-
//#endregion
|
|
403
|
-
//#region src/utils/group-by.ts
|
|
404
|
-
const groupBy = (items, keyFn) => {
|
|
405
|
-
const groups = /* @__PURE__ */ new Map();
|
|
406
|
-
for (const item of items) {
|
|
407
|
-
const key = keyFn(item);
|
|
408
|
-
const existing = groups.get(key) ?? [];
|
|
409
|
-
existing.push(item);
|
|
410
|
-
groups.set(key, existing);
|
|
411
|
-
}
|
|
412
|
-
return groups;
|
|
413
|
-
};
|
|
414
|
-
|
|
415
455
|
//#endregion
|
|
416
456
|
//#region src/utils/highlighter.ts
|
|
417
457
|
const highlighter = {
|
|
@@ -422,40 +462,6 @@ const highlighter = {
|
|
|
422
462
|
dim: pc.dim
|
|
423
463
|
};
|
|
424
464
|
|
|
425
|
-
//#endregion
|
|
426
|
-
//#region src/utils/indent-multiline-text.ts
|
|
427
|
-
const indentMultilineText = (text, linePrefix) => text.split("\n").map((lineText) => `${linePrefix}${lineText}`).join("\n");
|
|
428
|
-
|
|
429
|
-
//#endregion
|
|
430
|
-
//#region src/utils/load-config.ts
|
|
431
|
-
const CONFIG_FILENAME = "react-doctor.config.json";
|
|
432
|
-
const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
|
|
433
|
-
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
434
|
-
const loadConfig = (rootDirectory) => {
|
|
435
|
-
const configFilePath = path.join(rootDirectory, CONFIG_FILENAME);
|
|
436
|
-
if (fs.existsSync(configFilePath)) try {
|
|
437
|
-
const fileContent = fs.readFileSync(configFilePath, "utf-8");
|
|
438
|
-
const parsed = JSON.parse(fileContent);
|
|
439
|
-
if (!isPlainObject(parsed)) {
|
|
440
|
-
console.warn(`Warning: ${CONFIG_FILENAME} must be a JSON object, ignoring.`);
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
return parsed;
|
|
444
|
-
} catch (error) {
|
|
445
|
-
console.warn(`Warning: Failed to parse ${CONFIG_FILENAME}: ${error instanceof Error ? error.message : String(error)}`);
|
|
446
|
-
return null;
|
|
447
|
-
}
|
|
448
|
-
const packageJsonPath = path.join(rootDirectory, "package.json");
|
|
449
|
-
if (fs.existsSync(packageJsonPath)) try {
|
|
450
|
-
const fileContent = fs.readFileSync(packageJsonPath, "utf-8");
|
|
451
|
-
const embeddedConfig = JSON.parse(fileContent)[PACKAGE_JSON_CONFIG_KEY];
|
|
452
|
-
if (isPlainObject(embeddedConfig)) return embeddedConfig;
|
|
453
|
-
} catch {
|
|
454
|
-
return null;
|
|
455
|
-
}
|
|
456
|
-
return null;
|
|
457
|
-
};
|
|
458
|
-
|
|
459
465
|
//#endregion
|
|
460
466
|
//#region src/utils/strip-ansi.ts
|
|
461
467
|
const ANSI_ESCAPE_SEQUENCE = String.raw`\u001B\[[0-9;]*m`;
|
|
@@ -510,6 +516,80 @@ const logger = {
|
|
|
510
516
|
}
|
|
511
517
|
};
|
|
512
518
|
|
|
519
|
+
//#endregion
|
|
520
|
+
//#region src/utils/framed-box.ts
|
|
521
|
+
const createFramedLine = (plainText, renderedText = plainText) => ({
|
|
522
|
+
plainText,
|
|
523
|
+
renderedText
|
|
524
|
+
});
|
|
525
|
+
const renderFramedBoxString = (framedLines) => {
|
|
526
|
+
if (framedLines.length === 0) return "";
|
|
527
|
+
const borderColorizer = highlighter.dim;
|
|
528
|
+
const outerIndent = " ".repeat(SUMMARY_BOX_OUTER_INDENT_CHARS);
|
|
529
|
+
const horizontalPadding = " ".repeat(SUMMARY_BOX_HORIZONTAL_PADDING_CHARS);
|
|
530
|
+
const maximumLineLength = Math.max(...framedLines.map((framedLine) => framedLine.plainText.length));
|
|
531
|
+
const borderLine = "─".repeat(maximumLineLength + SUMMARY_BOX_HORIZONTAL_PADDING_CHARS * 2);
|
|
532
|
+
const lines = [];
|
|
533
|
+
lines.push(`${outerIndent}${borderColorizer(`┌${borderLine}┐`)}`);
|
|
534
|
+
for (const framedLine of framedLines) {
|
|
535
|
+
const trailingSpaces = " ".repeat(maximumLineLength - framedLine.plainText.length);
|
|
536
|
+
lines.push(`${outerIndent}${borderColorizer("│")}${horizontalPadding}${framedLine.renderedText}${trailingSpaces}${horizontalPadding}${borderColorizer("│")}`);
|
|
537
|
+
}
|
|
538
|
+
lines.push(`${outerIndent}${borderColorizer(`└${borderLine}┘`)}`);
|
|
539
|
+
return lines.join("\n");
|
|
540
|
+
};
|
|
541
|
+
const printFramedBox = (framedLines) => {
|
|
542
|
+
const rendered = renderFramedBoxString(framedLines);
|
|
543
|
+
if (rendered) logger.log(rendered);
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/utils/group-by.ts
|
|
548
|
+
const groupBy = (items, keyFn) => {
|
|
549
|
+
const groups = /* @__PURE__ */ new Map();
|
|
550
|
+
for (const item of items) {
|
|
551
|
+
const key = keyFn(item);
|
|
552
|
+
const existing = groups.get(key) ?? [];
|
|
553
|
+
existing.push(item);
|
|
554
|
+
groups.set(key, existing);
|
|
555
|
+
}
|
|
556
|
+
return groups;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/utils/indent-multiline-text.ts
|
|
561
|
+
const indentMultilineText = (text, linePrefix) => text.split("\n").map((lineText) => `${linePrefix}${lineText}`).join("\n");
|
|
562
|
+
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/utils/load-config.ts
|
|
565
|
+
const CONFIG_FILENAME = "react-doctor.config.json";
|
|
566
|
+
const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
|
|
567
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
568
|
+
const loadConfig = (rootDirectory) => {
|
|
569
|
+
const configFilePath = path.join(rootDirectory, CONFIG_FILENAME);
|
|
570
|
+
if (fs.existsSync(configFilePath)) try {
|
|
571
|
+
const fileContent = fs.readFileSync(configFilePath, "utf-8");
|
|
572
|
+
const parsed = JSON.parse(fileContent);
|
|
573
|
+
if (!isPlainObject(parsed)) {
|
|
574
|
+
console.warn(`Warning: ${CONFIG_FILENAME} must be a JSON object, ignoring.`);
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
return parsed;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
console.warn(`Warning: Failed to parse ${CONFIG_FILENAME}: ${error instanceof Error ? error.message : String(error)}`);
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
const packageJsonPath = path.join(rootDirectory, "package.json");
|
|
583
|
+
if (fs.existsSync(packageJsonPath)) try {
|
|
584
|
+
const fileContent = fs.readFileSync(packageJsonPath, "utf-8");
|
|
585
|
+
const embeddedConfig = JSON.parse(fileContent)[PACKAGE_JSON_CONFIG_KEY];
|
|
586
|
+
if (isPlainObject(embeddedConfig)) return embeddedConfig;
|
|
587
|
+
} catch {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
};
|
|
592
|
+
|
|
513
593
|
//#endregion
|
|
514
594
|
//#region src/utils/run-knip.ts
|
|
515
595
|
const KNIP_CATEGORY_MAP = {
|
|
@@ -1136,15 +1216,11 @@ const writeDiagnosticsDirectory = (diagnostics) => {
|
|
|
1136
1216
|
writeFileSync(join(outputDirectory, "diagnostics.json"), JSON.stringify(diagnostics, null, 2));
|
|
1137
1217
|
return outputDirectory;
|
|
1138
1218
|
};
|
|
1139
|
-
const colorizeByScore = (text, score) => {
|
|
1219
|
+
const colorizeByScore$1 = (text, score) => {
|
|
1140
1220
|
if (score >= SCORE_GOOD_THRESHOLD) return highlighter.success(text);
|
|
1141
1221
|
if (score >= SCORE_OK_THRESHOLD) return highlighter.warn(text);
|
|
1142
1222
|
return highlighter.error(text);
|
|
1143
1223
|
};
|
|
1144
|
-
const createFramedLine = (plainText, renderedText = plainText) => ({
|
|
1145
|
-
plainText,
|
|
1146
|
-
renderedText
|
|
1147
|
-
});
|
|
1148
1224
|
const buildScoreBarSegments = (score) => {
|
|
1149
1225
|
const filledCount = Math.round(score / PERFECT_SCORE * SCORE_BAR_WIDTH_CHARS);
|
|
1150
1226
|
const emptyCount = SCORE_BAR_WIDTH_CHARS - filledCount;
|
|
@@ -1159,25 +1235,11 @@ const buildPlainScoreBar = (score) => {
|
|
|
1159
1235
|
};
|
|
1160
1236
|
const buildScoreBar = (score) => {
|
|
1161
1237
|
const { filledSegment, emptySegment } = buildScoreBarSegments(score);
|
|
1162
|
-
return colorizeByScore(filledSegment, score) + highlighter.dim(emptySegment);
|
|
1163
|
-
};
|
|
1164
|
-
const printFramedBox = (framedLines) => {
|
|
1165
|
-
if (framedLines.length === 0) return;
|
|
1166
|
-
const borderColorizer = highlighter.dim;
|
|
1167
|
-
const outerIndent = " ".repeat(SUMMARY_BOX_OUTER_INDENT_CHARS);
|
|
1168
|
-
const horizontalPadding = " ".repeat(SUMMARY_BOX_HORIZONTAL_PADDING_CHARS);
|
|
1169
|
-
const maximumLineLength = Math.max(...framedLines.map((framedLine) => framedLine.plainText.length));
|
|
1170
|
-
const borderLine = "─".repeat(maximumLineLength + SUMMARY_BOX_HORIZONTAL_PADDING_CHARS * 2);
|
|
1171
|
-
logger.log(`${outerIndent}${borderColorizer(`┌${borderLine}┐`)}`);
|
|
1172
|
-
for (const framedLine of framedLines) {
|
|
1173
|
-
const trailingSpaces = " ".repeat(maximumLineLength - framedLine.plainText.length);
|
|
1174
|
-
logger.log(`${outerIndent}${borderColorizer("│")}${horizontalPadding}${framedLine.renderedText}${trailingSpaces}${horizontalPadding}${borderColorizer("│")}`);
|
|
1175
|
-
}
|
|
1176
|
-
logger.log(`${outerIndent}${borderColorizer(`└${borderLine}┘`)}`);
|
|
1238
|
+
return colorizeByScore$1(filledSegment, score) + highlighter.dim(emptySegment);
|
|
1177
1239
|
};
|
|
1178
1240
|
const printScoreGauge = (score, label) => {
|
|
1179
|
-
const scoreDisplay = colorizeByScore(`${score}`, score);
|
|
1180
|
-
const labelDisplay = colorizeByScore(label, score);
|
|
1241
|
+
const scoreDisplay = colorizeByScore$1(`${score}`, score);
|
|
1242
|
+
const labelDisplay = colorizeByScore$1(label, score);
|
|
1181
1243
|
logger.log(` ${scoreDisplay} / ${PERFECT_SCORE} ${labelDisplay}`);
|
|
1182
1244
|
logger.break();
|
|
1183
1245
|
logger.log(` ${buildScoreBar(score)}`);
|
|
@@ -1191,7 +1253,7 @@ const getDoctorFace = (score) => {
|
|
|
1191
1253
|
const printBranding = (score) => {
|
|
1192
1254
|
if (score !== void 0) {
|
|
1193
1255
|
const [eyes, mouth] = getDoctorFace(score);
|
|
1194
|
-
const colorize = (text) => colorizeByScore(text, score);
|
|
1256
|
+
const colorize = (text) => colorizeByScore$1(text, score);
|
|
1195
1257
|
logger.log(colorize(" ┌─────┐"));
|
|
1196
1258
|
logger.log(colorize(` │ ${eyes} │`));
|
|
1197
1259
|
logger.log(colorize(` │ ${mouth} │`));
|
|
@@ -1212,7 +1274,7 @@ const buildShareUrl = (diagnostics, scoreResult, projectName) => {
|
|
|
1212
1274
|
if (affectedFileCount > 0) params.set("f", String(affectedFileCount));
|
|
1213
1275
|
return `${SHARE_BASE_URL}?${params.toString()}`;
|
|
1214
1276
|
};
|
|
1215
|
-
const printSummary = (diagnostics, elapsedMilliseconds, scoreResult, projectName, totalSourceFileCount) => {
|
|
1277
|
+
const printSummary = (diagnostics, elapsedMilliseconds, scoreResult, projectName, totalSourceFileCount, noScoreMessage) => {
|
|
1216
1278
|
const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
1217
1279
|
const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
|
|
1218
1280
|
const affectedFileCount = collectAffectedFiles(diagnostics).size;
|
|
@@ -1238,7 +1300,7 @@ const printSummary = (diagnostics, elapsedMilliseconds, scoreResult, projectName
|
|
|
1238
1300
|
const summaryFramedLines = [];
|
|
1239
1301
|
if (scoreResult) {
|
|
1240
1302
|
const [eyes, mouth] = getDoctorFace(scoreResult.score);
|
|
1241
|
-
const scoreColorizer = (text) => colorizeByScore(text, scoreResult.score);
|
|
1303
|
+
const scoreColorizer = (text) => colorizeByScore$1(text, scoreResult.score);
|
|
1242
1304
|
summaryFramedLines.push(createFramedLine("┌─────┐", scoreColorizer("┌─────┐")));
|
|
1243
1305
|
summaryFramedLines.push(createFramedLine(`│ ${eyes} │`, scoreColorizer(`│ ${eyes} │`)));
|
|
1244
1306
|
summaryFramedLines.push(createFramedLine(`│ ${mouth} │`, scoreColorizer(`│ ${mouth} │`)));
|
|
@@ -1246,7 +1308,7 @@ const printSummary = (diagnostics, elapsedMilliseconds, scoreResult, projectName
|
|
|
1246
1308
|
summaryFramedLines.push(createFramedLine("React Doctor (www.react.doctor)", `React Doctor ${highlighter.dim("(www.react.doctor)")}`));
|
|
1247
1309
|
summaryFramedLines.push(createFramedLine(""));
|
|
1248
1310
|
const scoreLinePlainText = `${scoreResult.score} / ${PERFECT_SCORE} ${scoreResult.label}`;
|
|
1249
|
-
const scoreLineRenderedText = `${colorizeByScore(String(scoreResult.score), scoreResult.score)} / ${PERFECT_SCORE} ${colorizeByScore(scoreResult.label, scoreResult.score)}`;
|
|
1311
|
+
const scoreLineRenderedText = `${colorizeByScore$1(String(scoreResult.score), scoreResult.score)} / ${PERFECT_SCORE} ${colorizeByScore$1(scoreResult.label, scoreResult.score)}`;
|
|
1250
1312
|
summaryFramedLines.push(createFramedLine(scoreLinePlainText, scoreLineRenderedText));
|
|
1251
1313
|
summaryFramedLines.push(createFramedLine(""));
|
|
1252
1314
|
summaryFramedLines.push(createFramedLine(buildPlainScoreBar(scoreResult.score), buildScoreBar(scoreResult.score)));
|
|
@@ -1254,7 +1316,7 @@ const printSummary = (diagnostics, elapsedMilliseconds, scoreResult, projectName
|
|
|
1254
1316
|
} else {
|
|
1255
1317
|
summaryFramedLines.push(createFramedLine("React Doctor (www.react.doctor)", `React Doctor ${highlighter.dim("(www.react.doctor)")}`));
|
|
1256
1318
|
summaryFramedLines.push(createFramedLine(""));
|
|
1257
|
-
summaryFramedLines.push(createFramedLine(
|
|
1319
|
+
summaryFramedLines.push(createFramedLine(noScoreMessage, highlighter.dim(noScoreMessage)));
|
|
1258
1320
|
summaryFramedLines.push(createFramedLine(""));
|
|
1259
1321
|
}
|
|
1260
1322
|
summaryFramedLines.push(createFramedLine(summaryLinePartsPlain.join(" "), summaryLineParts.join(" ")));
|
|
@@ -1279,6 +1341,7 @@ const scan = async (directory, inputOptions = {}) => {
|
|
|
1279
1341
|
deadCode: inputOptions.deadCode ?? userConfig?.deadCode ?? true,
|
|
1280
1342
|
verbose: inputOptions.verbose ?? userConfig?.verbose ?? false,
|
|
1281
1343
|
scoreOnly: inputOptions.scoreOnly ?? false,
|
|
1344
|
+
offline: inputOptions.offline ?? false,
|
|
1282
1345
|
includePaths: inputOptions.includePaths
|
|
1283
1346
|
};
|
|
1284
1347
|
const includePaths = options.includePaths ?? [];
|
|
@@ -1335,11 +1398,15 @@ const scan = async (directory, inputOptions = {}) => {
|
|
|
1335
1398
|
];
|
|
1336
1399
|
const diagnostics = userConfig ? filterIgnoredDiagnostics(allDiagnostics, userConfig) : allDiagnostics;
|
|
1337
1400
|
const elapsedMilliseconds = performance.now() - startTime;
|
|
1338
|
-
const scoreResult = await calculateScore(diagnostics);
|
|
1401
|
+
const scoreResult = options.offline ? null : await calculateScore(diagnostics);
|
|
1402
|
+
const noScoreMessage = options.offline ? OFFLINE_FLAG_MESSAGE : OFFLINE_MESSAGE;
|
|
1339
1403
|
if (options.scoreOnly) {
|
|
1340
1404
|
if (scoreResult) logger.log(`${scoreResult.score}`);
|
|
1341
|
-
else logger.dim(
|
|
1342
|
-
return
|
|
1405
|
+
else logger.dim(noScoreMessage);
|
|
1406
|
+
return {
|
|
1407
|
+
diagnostics,
|
|
1408
|
+
scoreResult
|
|
1409
|
+
};
|
|
1343
1410
|
}
|
|
1344
1411
|
if (diagnostics.length === 0) {
|
|
1345
1412
|
logger.success("No issues found!");
|
|
@@ -1347,12 +1414,19 @@ const scan = async (directory, inputOptions = {}) => {
|
|
|
1347
1414
|
if (scoreResult) {
|
|
1348
1415
|
printBranding(scoreResult.score);
|
|
1349
1416
|
printScoreGauge(scoreResult.score, scoreResult.label);
|
|
1350
|
-
} else logger.dim(` ${
|
|
1351
|
-
return
|
|
1417
|
+
} else logger.dim(` ${noScoreMessage}`);
|
|
1418
|
+
return {
|
|
1419
|
+
diagnostics,
|
|
1420
|
+
scoreResult
|
|
1421
|
+
};
|
|
1352
1422
|
}
|
|
1353
1423
|
printDiagnostics(diagnostics, options.verbose);
|
|
1354
1424
|
const displayedSourceFileCount = isDiffMode ? includePaths.length : projectInfo.sourceFileCount;
|
|
1355
|
-
printSummary(diagnostics, elapsedMilliseconds, scoreResult, projectInfo.projectName, displayedSourceFileCount);
|
|
1425
|
+
printSummary(diagnostics, elapsedMilliseconds, scoreResult, projectInfo.projectName, displayedSourceFileCount, noScoreMessage);
|
|
1426
|
+
return {
|
|
1427
|
+
diagnostics,
|
|
1428
|
+
scoreResult
|
|
1429
|
+
};
|
|
1356
1430
|
};
|
|
1357
1431
|
|
|
1358
1432
|
//#endregion
|
|
@@ -1514,11 +1588,21 @@ const shouldSelectAllChoices = (choiceStates) => {
|
|
|
1514
1588
|
//#region src/utils/prompts.ts
|
|
1515
1589
|
const require = createRequire(import.meta.url);
|
|
1516
1590
|
const PROMPTS_MULTISELECT_MODULE_PATH = "prompts/lib/elements/multiselect";
|
|
1591
|
+
const PROMPTS_SELECT_MODULE_PATH = "prompts/lib/elements/select";
|
|
1517
1592
|
let didPatchMultiselectToggleAll = false;
|
|
1518
1593
|
let didPatchMultiselectSubmit = false;
|
|
1594
|
+
let didPatchSelectBanner = false;
|
|
1595
|
+
const selectBannerMap = /* @__PURE__ */ new Map();
|
|
1596
|
+
const setSelectBanner = (banner, targetIndex) => {
|
|
1597
|
+
selectBannerMap.set(targetIndex, banner);
|
|
1598
|
+
};
|
|
1599
|
+
const clearSelectBanner = () => {
|
|
1600
|
+
selectBannerMap.clear();
|
|
1601
|
+
};
|
|
1519
1602
|
const onCancel = () => {
|
|
1520
1603
|
logger.break();
|
|
1521
1604
|
logger.log("Cancelled.");
|
|
1605
|
+
logger.dim("Run `npx react-doctor@latest --fix` to fix issues.");
|
|
1522
1606
|
logger.break();
|
|
1523
1607
|
process.exit(0);
|
|
1524
1608
|
};
|
|
@@ -1550,9 +1634,25 @@ const patchMultiselectSubmit = () => {
|
|
|
1550
1634
|
originalSubmit.call(this);
|
|
1551
1635
|
};
|
|
1552
1636
|
};
|
|
1637
|
+
const patchSelectBanner = () => {
|
|
1638
|
+
if (didPatchSelectBanner) return;
|
|
1639
|
+
didPatchSelectBanner = true;
|
|
1640
|
+
const selectConstructor = require(PROMPTS_SELECT_MODULE_PATH);
|
|
1641
|
+
const promptsClear = require("prompts/lib/util/clear");
|
|
1642
|
+
const originalRender = selectConstructor.prototype.render;
|
|
1643
|
+
selectConstructor.prototype.render = function() {
|
|
1644
|
+
originalRender.call(this);
|
|
1645
|
+
const banner = selectBannerMap.get(this.cursor);
|
|
1646
|
+
if (!banner || this.closed || this.done) return;
|
|
1647
|
+
this.out.write(promptsClear(this.outputText, this.out.columns));
|
|
1648
|
+
this.outputText = `${banner}\n\n${this.outputText}`;
|
|
1649
|
+
this.out.write(this.outputText);
|
|
1650
|
+
};
|
|
1651
|
+
};
|
|
1553
1652
|
const prompts = (questions) => {
|
|
1554
1653
|
patchMultiselectToggleAll();
|
|
1555
1654
|
patchMultiselectSubmit();
|
|
1655
|
+
patchSelectBanner();
|
|
1556
1656
|
return basePrompts(questions, { onCancel });
|
|
1557
1657
|
};
|
|
1558
1658
|
|
|
@@ -1644,7 +1744,7 @@ const maybePromptSkillInstall = async (shouldSkipPrompts) => {
|
|
|
1644
1744
|
const { shouldInstall } = await prompts({
|
|
1645
1745
|
type: "confirm",
|
|
1646
1746
|
name: "shouldInstall",
|
|
1647
|
-
message: "Install skill?",
|
|
1747
|
+
message: "Install skill? (recommended)",
|
|
1648
1748
|
initial: true
|
|
1649
1749
|
});
|
|
1650
1750
|
if (shouldInstall) {
|
|
@@ -1659,9 +1759,16 @@ const maybePromptSkillInstall = async (shouldSkipPrompts) => {
|
|
|
1659
1759
|
|
|
1660
1760
|
//#endregion
|
|
1661
1761
|
//#region src/cli.ts
|
|
1662
|
-
const VERSION = "0.0.
|
|
1663
|
-
|
|
1664
|
-
|
|
1762
|
+
const VERSION = "0.0.22";
|
|
1763
|
+
const exitWithFixHint = () => {
|
|
1764
|
+
logger.break();
|
|
1765
|
+
logger.log("Cancelled.");
|
|
1766
|
+
logger.dim("Run `npx react-doctor@latest --fix` to fix issues.");
|
|
1767
|
+
logger.break();
|
|
1768
|
+
process.exit(0);
|
|
1769
|
+
};
|
|
1770
|
+
process.on("SIGINT", exitWithFixHint);
|
|
1771
|
+
process.on("SIGTERM", exitWithFixHint);
|
|
1665
1772
|
const resolveDiffMode = async (diffInfo, effectiveDiff, shouldSkipPrompts, isScoreOnly) => {
|
|
1666
1773
|
if (effectiveDiff !== void 0 && effectiveDiff !== false) {
|
|
1667
1774
|
if (diffInfo) return true;
|
|
@@ -1684,10 +1791,10 @@ const resolveDiffMode = async (diffInfo, effectiveDiff, shouldSkipPrompts, isSco
|
|
|
1684
1791
|
});
|
|
1685
1792
|
return Boolean(shouldScanBranchOnly);
|
|
1686
1793
|
};
|
|
1687
|
-
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("--fix", "open Ami to auto-fix all issues").option("--prompt", "copy latest scan output to clipboard").action(async (directory, flags) => {
|
|
1794
|
+
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("--fix", "open Ami to auto-fix all issues").option("--prompt", "copy latest scan output to clipboard").action(async (directory, flags) => {
|
|
1688
1795
|
const isScoreOnly = flags.score && !flags.prompt;
|
|
1689
1796
|
const shouldCopyPromptOutput = flags.prompt;
|
|
1690
|
-
|
|
1797
|
+
startLoggerCapture();
|
|
1691
1798
|
try {
|
|
1692
1799
|
const resolvedDirectory = path.resolve(directory);
|
|
1693
1800
|
const userConfig = loadConfig(resolvedDirectory);
|
|
@@ -1700,7 +1807,8 @@ const program = new Command().name("react-doctor").description("Diagnose React c
|
|
|
1700
1807
|
lint: isCliOverride("lint") ? flags.lint : userConfig?.lint ?? flags.lint,
|
|
1701
1808
|
deadCode: isCliOverride("deadCode") ? flags.deadCode : userConfig?.deadCode ?? flags.deadCode,
|
|
1702
1809
|
verbose: flags.prompt || (isCliOverride("verbose") ? Boolean(flags.verbose) : userConfig?.verbose ?? false),
|
|
1703
|
-
scoreOnly: isScoreOnly
|
|
1810
|
+
scoreOnly: isScoreOnly,
|
|
1811
|
+
offline: flags.offline
|
|
1704
1812
|
};
|
|
1705
1813
|
const isAutomatedEnvironment = [
|
|
1706
1814
|
process.env.CI,
|
|
@@ -1721,6 +1829,7 @@ const program = new Command().name("react-doctor").description("Diagnose React c
|
|
|
1721
1829
|
logger.log(`Scanning changes: ${highlighter.info(diffInfo.currentBranch)} → ${highlighter.info(diffInfo.baseBranch)}`);
|
|
1722
1830
|
logger.break();
|
|
1723
1831
|
}
|
|
1832
|
+
const allDiagnostics = [];
|
|
1724
1833
|
for (const projectDirectory of projectDirectories) {
|
|
1725
1834
|
let includePaths;
|
|
1726
1835
|
if (isDiffMode) {
|
|
@@ -1741,28 +1850,38 @@ const program = new Command().name("react-doctor").description("Diagnose React c
|
|
|
1741
1850
|
logger.dim(`Scanning ${projectDirectory}...`);
|
|
1742
1851
|
logger.break();
|
|
1743
1852
|
}
|
|
1744
|
-
await scan(projectDirectory, {
|
|
1853
|
+
const scanResult = await scan(projectDirectory, {
|
|
1745
1854
|
...scanOptions,
|
|
1746
1855
|
includePaths
|
|
1747
1856
|
});
|
|
1857
|
+
allDiagnostics.push(...scanResult.diagnostics);
|
|
1748
1858
|
if (!isScoreOnly) logger.break();
|
|
1749
1859
|
}
|
|
1860
|
+
const capturedScanOutput = stopLoggerCapture();
|
|
1750
1861
|
if (flags.fix) openAmiToFix(resolvedDirectory);
|
|
1751
|
-
if (
|
|
1862
|
+
if (shouldCopyPromptOutput) copyPromptToClipboard(capturedScanOutput, !isScoreOnly);
|
|
1863
|
+
else if (!isScoreOnly) {
|
|
1752
1864
|
await maybePromptSkillInstall(shouldSkipPrompts);
|
|
1753
|
-
if (!shouldSkipPrompts && !flags.fix) await
|
|
1865
|
+
if (!shouldSkipPrompts && !flags.fix) await maybePromptFix(resolvedDirectory, allDiagnostics, flags.offline ? null : await fetchEstimatedScore(allDiagnostics), capturedScanOutput);
|
|
1754
1866
|
}
|
|
1755
1867
|
} catch (error) {
|
|
1756
1868
|
handleError(error, { shouldExit: !shouldCopyPromptOutput });
|
|
1757
1869
|
} finally {
|
|
1758
|
-
|
|
1870
|
+
const remainingOutput = stopLoggerCapture();
|
|
1871
|
+
if (shouldCopyPromptOutput && remainingOutput) copyPromptToClipboard(remainingOutput, !isScoreOnly);
|
|
1759
1872
|
}
|
|
1760
1873
|
}).addHelpText("after", `
|
|
1761
1874
|
${highlighter.dim("Learn more:")}
|
|
1762
1875
|
${highlighter.info("https://github.com/millionco/react-doctor")}
|
|
1763
1876
|
`);
|
|
1764
|
-
const
|
|
1877
|
+
const AMI_WEBSITE_URL = "https://ami.dev";
|
|
1878
|
+
const AMI_INSTALL_URL = `${AMI_WEBSITE_URL}/install.sh`;
|
|
1765
1879
|
const AMI_RELEASES_URL = "https://github.com/millionco/ami-releases/releases";
|
|
1880
|
+
const colorizeByScore = (text, score) => {
|
|
1881
|
+
if (score >= SCORE_GOOD_THRESHOLD) return highlighter.success(text);
|
|
1882
|
+
if (score >= SCORE_OK_THRESHOLD) return highlighter.warn(text);
|
|
1883
|
+
return highlighter.error(text);
|
|
1884
|
+
};
|
|
1766
1885
|
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.";
|
|
1767
1886
|
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.";
|
|
1768
1887
|
const REACT_DOCTOR_OUTPUT_LABEL = "react-doctor output";
|
|
@@ -1781,12 +1900,12 @@ const isAmiInstalled = () => {
|
|
|
1781
1900
|
}
|
|
1782
1901
|
};
|
|
1783
1902
|
const installAmi = () => {
|
|
1784
|
-
logger.log("Ami
|
|
1903
|
+
logger.log("Installing Ami...");
|
|
1785
1904
|
logger.break();
|
|
1786
1905
|
try {
|
|
1787
1906
|
execSync(`curl -fsSL ${AMI_INSTALL_URL} | bash`, { stdio: "inherit" });
|
|
1788
1907
|
} catch {
|
|
1789
|
-
logger.error(
|
|
1908
|
+
logger.error(`Failed to install Ami. Visit ${AMI_WEBSITE_URL} to install manually.`);
|
|
1790
1909
|
process.exit(1);
|
|
1791
1910
|
}
|
|
1792
1911
|
logger.break();
|
|
@@ -1798,33 +1917,41 @@ const openUrl = (url) => {
|
|
|
1798
1917
|
}
|
|
1799
1918
|
execSync(process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`, { stdio: "ignore" });
|
|
1800
1919
|
};
|
|
1801
|
-
const
|
|
1802
|
-
|
|
1803
|
-
|
|
1920
|
+
const buildDeeplinkParams = (directory) => {
|
|
1921
|
+
const params = new URLSearchParams();
|
|
1922
|
+
params.set("cwd", path.resolve(directory));
|
|
1923
|
+
params.set("prompt", DEEPLINK_FIX_PROMPT);
|
|
1924
|
+
params.set("mode", "agent");
|
|
1925
|
+
params.set("autoSubmit", "true");
|
|
1926
|
+
return params;
|
|
1927
|
+
};
|
|
1928
|
+
const buildDeeplink = (directory) => `ami://open-project?${buildDeeplinkParams(directory).toString()}`;
|
|
1929
|
+
const buildWebDeeplink = (directory) => `${OPEN_BASE_URL}?${buildDeeplinkParams(directory).toString()}`;
|
|
1804
1930
|
const openAmiToFix = (directory) => {
|
|
1805
1931
|
const isInstalled = isAmiInstalled();
|
|
1806
1932
|
const deeplink = buildDeeplink(directory);
|
|
1933
|
+
const webDeeplink = buildWebDeeplink(directory);
|
|
1807
1934
|
if (!isInstalled) {
|
|
1808
1935
|
if (process.platform === "darwin") {
|
|
1809
1936
|
installAmi();
|
|
1810
|
-
logger.success("Ami
|
|
1937
|
+
logger.success("Ami installed successfully.");
|
|
1811
1938
|
} else {
|
|
1812
1939
|
logger.error("Ami is not installed.");
|
|
1813
|
-
logger.dim(`Download
|
|
1940
|
+
logger.dim(`Download at ${highlighter.info(AMI_RELEASES_URL)}`);
|
|
1814
1941
|
}
|
|
1815
1942
|
logger.break();
|
|
1816
|
-
logger.dim("
|
|
1817
|
-
logger.info(
|
|
1943
|
+
logger.dim("Open this link to start fixing:");
|
|
1944
|
+
logger.info(webDeeplink);
|
|
1818
1945
|
return;
|
|
1819
1946
|
}
|
|
1820
|
-
logger.log("Opening Ami
|
|
1947
|
+
logger.log("Opening Ami...");
|
|
1821
1948
|
try {
|
|
1822
1949
|
openUrl(deeplink);
|
|
1823
|
-
logger.success("
|
|
1950
|
+
logger.success("Ami opened. Fixing your issues now.");
|
|
1824
1951
|
} catch {
|
|
1825
1952
|
logger.break();
|
|
1826
|
-
logger.dim("Could not open Ami automatically. Open this
|
|
1827
|
-
logger.info(
|
|
1953
|
+
logger.dim("Could not open Ami automatically. Open this link instead:");
|
|
1954
|
+
logger.info(webDeeplink);
|
|
1828
1955
|
}
|
|
1829
1956
|
};
|
|
1830
1957
|
const buildPromptWithOutput = (reactDoctorOutput) => {
|
|
@@ -1843,24 +1970,78 @@ const copyPromptToClipboard = (reactDoctorOutput, shouldLogResult) => {
|
|
|
1843
1970
|
logger.warn("Could not copy prompt to clipboard automatically. Use this prompt:");
|
|
1844
1971
|
logger.info(promptWithOutput);
|
|
1845
1972
|
};
|
|
1846
|
-
const
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1973
|
+
const FIX_METHOD_AMI = "ami";
|
|
1974
|
+
const FIX_METHOD_CLIPBOARD = "clipboard";
|
|
1975
|
+
const FIX_COMMAND_HINT = "npx react-doctor@latest --fix";
|
|
1976
|
+
const buildAmiBanner = (issueCount, currentScore, estimatedScore) => {
|
|
1977
|
+
const currentScoreDisplay = colorizeByScore(String(currentScore), currentScore);
|
|
1978
|
+
const estimatedScoreDisplay = colorizeByScore(`~${estimatedScore}`, estimatedScore);
|
|
1979
|
+
const issueLabel = issueCount === 1 ? "issue" : "issues";
|
|
1980
|
+
return renderFramedBoxString([
|
|
1981
|
+
createFramedLine(`Score: ${currentScore} → ~${estimatedScore}`, `Score: ${currentScoreDisplay} ${highlighter.dim("→")} ${estimatedScoreDisplay}`),
|
|
1982
|
+
createFramedLine(""),
|
|
1983
|
+
createFramedLine(`Ami is a coding agent built for React. It reads`, `${highlighter.info("Ami")} is a coding agent built for React. It reads`),
|
|
1984
|
+
createFramedLine("your react-doctor report, understands your codebase,"),
|
|
1985
|
+
createFramedLine(`and fixes ${issueCount} ${issueLabel} one by one — then re-runs the`, `and fixes ${highlighter.warn(String(issueCount))} ${issueLabel} one by one — then re-runs the`),
|
|
1986
|
+
createFramedLine("scan to verify the score improved."),
|
|
1987
|
+
createFramedLine(""),
|
|
1988
|
+
createFramedLine(`Free to use. ${AMI_WEBSITE_URL}`, `Free to use. ${highlighter.info(AMI_WEBSITE_URL)}`)
|
|
1989
|
+
]);
|
|
1990
|
+
};
|
|
1991
|
+
const buildClipboardWarningBanner = () => renderFramedBoxString([
|
|
1992
|
+
createFramedLine("⚠ Other agents may not fix these issues well.", `${highlighter.warn("⚠")} Other agents may not fix these issues well.`),
|
|
1993
|
+
createFramedLine(""),
|
|
1994
|
+
createFramedLine("react-doctor diagnostics require React-specific context"),
|
|
1995
|
+
createFramedLine("that general-purpose agents often miss, leading to"),
|
|
1996
|
+
createFramedLine("incomplete or incorrect fixes.")
|
|
1997
|
+
]);
|
|
1998
|
+
const buildSkipBanner = (issueCount, estimatedScore) => {
|
|
1999
|
+
const issueLabel = issueCount === 1 ? "issue" : "issues";
|
|
2000
|
+
const estimatedScoreDisplay = colorizeByScore(`~${estimatedScore}`, estimatedScore);
|
|
2001
|
+
return renderFramedBoxString([
|
|
2002
|
+
createFramedLine(`Skip fixing ${issueCount} ${issueLabel} and reaching ~${estimatedScore}?`, `Skip fixing ${highlighter.warn(String(issueCount))} ${issueLabel} and reaching ${estimatedScoreDisplay}?`),
|
|
2003
|
+
createFramedLine(""),
|
|
2004
|
+
createFramedLine(`Run ${FIX_COMMAND_HINT} anytime to come back.`, `Run ${highlighter.info(FIX_COMMAND_HINT)} anytime to come back.`)
|
|
2005
|
+
]);
|
|
2006
|
+
};
|
|
2007
|
+
const configureFixBanners = (issueCount, estimatedScoreResult) => {
|
|
2008
|
+
const { currentScore, estimatedScore } = estimatedScoreResult;
|
|
2009
|
+
setSelectBanner(buildAmiBanner(issueCount, currentScore, estimatedScore), 0);
|
|
2010
|
+
setSelectBanner(buildClipboardWarningBanner(), 1);
|
|
2011
|
+
setSelectBanner(buildSkipBanner(issueCount, estimatedScore), 2);
|
|
2012
|
+
};
|
|
2013
|
+
const maybePromptFix = async (directory, diagnostics, estimatedScoreResult, capturedScanOutput) => {
|
|
2014
|
+
if (diagnostics.length === 0) return;
|
|
1852
2015
|
logger.break();
|
|
1853
|
-
if (
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2016
|
+
if (estimatedScoreResult) configureFixBanners(diagnostics.length, estimatedScoreResult);
|
|
2017
|
+
const { fixMethod } = await prompts({
|
|
2018
|
+
type: "select",
|
|
2019
|
+
name: "fixMethod",
|
|
2020
|
+
message: "Fix issues?",
|
|
2021
|
+
choices: [
|
|
2022
|
+
{
|
|
2023
|
+
title: "Use ami.dev (recommended)",
|
|
2024
|
+
description: "Optimized coding agent for React Doctor",
|
|
2025
|
+
value: FIX_METHOD_AMI
|
|
2026
|
+
},
|
|
2027
|
+
{
|
|
2028
|
+
title: "Copy to clipboard",
|
|
2029
|
+
description: "Other agents may lack context for react-doctor fixes",
|
|
2030
|
+
value: FIX_METHOD_CLIPBOARD
|
|
2031
|
+
},
|
|
2032
|
+
{
|
|
2033
|
+
title: "Skip",
|
|
2034
|
+
value: "skip"
|
|
2035
|
+
}
|
|
2036
|
+
]
|
|
1862
2037
|
});
|
|
1863
|
-
|
|
2038
|
+
clearSelectBanner();
|
|
2039
|
+
if (fixMethod === FIX_METHOD_AMI) openAmiToFix(directory);
|
|
2040
|
+
else if (fixMethod === FIX_METHOD_CLIPBOARD) copyPromptToClipboard(capturedScanOutput, true);
|
|
2041
|
+
else {
|
|
2042
|
+
logger.break();
|
|
2043
|
+
logger.dim(` Run ${highlighter.info(FIX_COMMAND_HINT)} anytime to fix issues.`);
|
|
2044
|
+
}
|
|
1864
2045
|
};
|
|
1865
2046
|
const fixAction = (directory) => {
|
|
1866
2047
|
try {
|