react-doctor 0.1.0 → 0.1.2
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 +23 -0
- package/dist/cli.js +167 -130
- package/dist/eslint-plugin.js +1 -1
- package/dist/index.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -197,6 +197,29 @@ const counts = summarizeDiagnostics(result.diagnostics);
|
|
|
197
197
|
|
|
198
198
|
`react-doctor/api` re-exports `JsonReport`, `JsonReportSummary`, `JsonReportProjectEntry`, `JsonReportMode`, plus the lower-level `buildJsonReport` and `buildJsonReportError` builders. See [`packages/react-doctor/src/api.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/api.ts) for the full types.
|
|
199
199
|
|
|
200
|
+
## Leaderboard
|
|
201
|
+
|
|
202
|
+
Top React codebases scanned by React Doctor, ranked by score. Updated automatically from [millionco/react-doctor-benchmarks](https://github.com/millionco/react-doctor-benchmarks).
|
|
203
|
+
|
|
204
|
+
<!-- LEADERBOARD:START -->
|
|
205
|
+
<!-- prettier-ignore -->
|
|
206
|
+
| # | Repo | Score |
|
|
207
|
+
| -- | ---- | ----: |
|
|
208
|
+
| 1 | [executor](https://github.com/RhysSullivan/executor) | 96 |
|
|
209
|
+
| 2 | [nodejs.org](https://github.com/nodejs/nodejs.org) | 87 |
|
|
210
|
+
| 3 | [tldraw](https://github.com/tldraw/tldraw) | 76 |
|
|
211
|
+
| 4 | [t3code](https://github.com/pingdotgg/t3code) | 75 |
|
|
212
|
+
| 5 | [mastra](https://github.com/mastra-ai/mastra) | 70 |
|
|
213
|
+
| 6 | [excalidraw](https://github.com/excalidraw/excalidraw) | 69 |
|
|
214
|
+
| 7 | [payload](https://github.com/payloadcms/payload) | 69 |
|
|
215
|
+
| 8 | [better-auth](https://github.com/better-auth/better-auth) | 69 |
|
|
216
|
+
| 9 | [rocket.chat](https://github.com/RocketChat/Rocket.Chat) | 67 |
|
|
217
|
+
| 10 | [typebot](https://github.com/baptisteArno/typebot.io) | 66 |
|
|
218
|
+
|
|
219
|
+
<!-- LEADERBOARD:END -->
|
|
220
|
+
|
|
221
|
+
See the [full leaderboard](https://www.react.doctor/leaderboard).
|
|
222
|
+
|
|
200
223
|
## Resources & Contributing Back
|
|
201
224
|
|
|
202
225
|
Want to try it out? Check out [the demo](https://react.doctor).
|
package/dist/cli.js
CHANGED
|
@@ -89,7 +89,9 @@ const highlighter = {
|
|
|
89
89
|
warn: pc.yellow,
|
|
90
90
|
info: pc.cyan,
|
|
91
91
|
success: pc.green,
|
|
92
|
-
dim: pc.dim
|
|
92
|
+
dim: pc.dim,
|
|
93
|
+
gray: pc.gray,
|
|
94
|
+
bold: pc.bold
|
|
93
95
|
};
|
|
94
96
|
//#endregion
|
|
95
97
|
//#region src/utils/logger.ts
|
|
@@ -210,7 +212,10 @@ const finalize = (method, originalText, displayText) => {
|
|
|
210
212
|
return;
|
|
211
213
|
}
|
|
212
214
|
sharedInstance.stop();
|
|
213
|
-
ora(
|
|
215
|
+
ora({
|
|
216
|
+
text: displayText,
|
|
217
|
+
indent: 2
|
|
218
|
+
}).start()[method](displayText);
|
|
214
219
|
const [remainingText] = pendingTexts;
|
|
215
220
|
if (remainingText) sharedInstance.text = remainingText;
|
|
216
221
|
sharedInstance.start();
|
|
@@ -219,7 +224,10 @@ const spinner = (text) => ({ start() {
|
|
|
219
224
|
if (isSilent) return noopHandle;
|
|
220
225
|
activeCount++;
|
|
221
226
|
pendingTexts.add(text);
|
|
222
|
-
if (!sharedInstance) sharedInstance = ora({
|
|
227
|
+
if (!sharedInstance) sharedInstance = ora({
|
|
228
|
+
text,
|
|
229
|
+
indent: 2
|
|
230
|
+
}).start();
|
|
223
231
|
else sharedInstance.text = text;
|
|
224
232
|
const handle = {
|
|
225
233
|
succeed: (displayText) => {
|
|
@@ -296,6 +304,28 @@ const runInstallSkill = async (options = {}) => {
|
|
|
296
304
|
}
|
|
297
305
|
};
|
|
298
306
|
//#endregion
|
|
307
|
+
//#region src/utils/build-category-breakdown.ts
|
|
308
|
+
const buildCategoryBreakdown = (diagnostics) => {
|
|
309
|
+
const entriesByCategory = /* @__PURE__ */ new Map();
|
|
310
|
+
for (const diagnostic of diagnostics) {
|
|
311
|
+
const existingEntry = entriesByCategory.get(diagnostic.category) ?? {
|
|
312
|
+
category: diagnostic.category,
|
|
313
|
+
totalCount: 0,
|
|
314
|
+
errorCount: 0,
|
|
315
|
+
warningCount: 0
|
|
316
|
+
};
|
|
317
|
+
existingEntry.totalCount += 1;
|
|
318
|
+
if (diagnostic.severity === "error") existingEntry.errorCount += 1;
|
|
319
|
+
else existingEntry.warningCount += 1;
|
|
320
|
+
entriesByCategory.set(diagnostic.category, existingEntry);
|
|
321
|
+
}
|
|
322
|
+
return [...entriesByCategory.values()].sort((entryA, entryB) => {
|
|
323
|
+
if (entryA.errorCount !== entryB.errorCount) return entryB.errorCount - entryA.errorCount;
|
|
324
|
+
if (entryA.totalCount !== entryB.totalCount) return entryB.totalCount - entryA.totalCount;
|
|
325
|
+
return entryA.category.localeCompare(entryB.category);
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
//#endregion
|
|
299
329
|
//#region src/utils/build-hidden-diagnostics-summary.ts
|
|
300
330
|
const buildHiddenDiagnosticsSummary = (hiddenDiagnostics) => {
|
|
301
331
|
const errorCount = hiddenDiagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
@@ -1462,32 +1492,6 @@ const formatErrorMessage = (error) => error instanceof Error ? error.message ||
|
|
|
1462
1492
|
const formatErrorChain = (rootError) => collectErrorChain(rootError).map(formatErrorMessage).join(" → ");
|
|
1463
1493
|
const getErrorChainMessages = (rootError) => collectErrorChain(rootError).map(formatErrorMessage);
|
|
1464
1494
|
//#endregion
|
|
1465
|
-
//#region src/utils/framed-box.ts
|
|
1466
|
-
const createFramedLine = (plainText, renderedText = plainText) => ({
|
|
1467
|
-
plainText,
|
|
1468
|
-
renderedText
|
|
1469
|
-
});
|
|
1470
|
-
const renderFramedBoxString = (framedLines) => {
|
|
1471
|
-
if (framedLines.length === 0) return "";
|
|
1472
|
-
const borderColorizer = highlighter.dim;
|
|
1473
|
-
const outerIndent = " ".repeat(2);
|
|
1474
|
-
const horizontalPadding = " ".repeat(1);
|
|
1475
|
-
const maximumLineLength = Math.max(...framedLines.map((framedLine) => framedLine.plainText.length));
|
|
1476
|
-
const borderLine = "─".repeat(maximumLineLength + 2);
|
|
1477
|
-
const lines = [];
|
|
1478
|
-
lines.push(`${outerIndent}${borderColorizer(`┌${borderLine}┐`)}`);
|
|
1479
|
-
for (const framedLine of framedLines) {
|
|
1480
|
-
const trailingSpaces = " ".repeat(maximumLineLength - framedLine.plainText.length);
|
|
1481
|
-
lines.push(`${outerIndent}${borderColorizer("│")}${horizontalPadding}${framedLine.renderedText}${trailingSpaces}${horizontalPadding}${borderColorizer("│")}`);
|
|
1482
|
-
}
|
|
1483
|
-
lines.push(`${outerIndent}${borderColorizer(`└${borderLine}┘`)}`);
|
|
1484
|
-
return lines.join("\n");
|
|
1485
|
-
};
|
|
1486
|
-
const printFramedBox = (framedLines) => {
|
|
1487
|
-
const rendered = renderFramedBoxString(framedLines);
|
|
1488
|
-
if (rendered) logger.log(rendered);
|
|
1489
|
-
};
|
|
1490
|
-
//#endregion
|
|
1491
1495
|
//#region src/utils/group-by.ts
|
|
1492
1496
|
const groupBy = (items, keyFn) => {
|
|
1493
1497
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -3162,8 +3166,6 @@ const runOxlint = async (options) => {
|
|
|
3162
3166
|
return await spawnLintBatches();
|
|
3163
3167
|
} catch (error) {
|
|
3164
3168
|
if (extendsPaths.length === 0) throw error;
|
|
3165
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
3166
|
-
process.stderr.write(`[react-doctor] could not adopt existing lint config (${reason.split("\n")[0]}); retrying without extends. Set "adoptExistingLintConfig": false to silence.\n`);
|
|
3167
3169
|
writeOxlintConfig(createOxlintConfig({
|
|
3168
3170
|
pluginPath,
|
|
3169
3171
|
framework,
|
|
@@ -3208,33 +3210,66 @@ const buildVerboseSiteMap = (diagnostics) => {
|
|
|
3208
3210
|
}
|
|
3209
3211
|
return fileSites;
|
|
3210
3212
|
};
|
|
3211
|
-
const
|
|
3213
|
+
const formatSiteCountBadge = (count) => count > 1 ? `×${count}` : "";
|
|
3214
|
+
const computeRuleNameColumnWidth = (ruleKeys) => {
|
|
3215
|
+
const longestRuleNameLength = ruleKeys.reduce((longest, ruleKey) => Math.max(longest, ruleKey.length), 0);
|
|
3216
|
+
return Math.max(36, longestRuleNameLength);
|
|
3217
|
+
};
|
|
3218
|
+
const padRuleNameToColumn = (ruleName, columnWidth) => {
|
|
3219
|
+
if (ruleName.length >= columnWidth) return ruleName;
|
|
3220
|
+
return ruleName + " ".repeat(columnWidth - ruleName.length);
|
|
3221
|
+
};
|
|
3222
|
+
const grayLine = (text) => {
|
|
3223
|
+
logger.log(highlighter.gray(text));
|
|
3224
|
+
};
|
|
3225
|
+
const printCompactRuleGroupLine = (ruleKey, ruleDiagnostics, ruleNameColumnWidth) => {
|
|
3226
|
+
const firstDiagnostic = ruleDiagnostics[0];
|
|
3227
|
+
const icon = colorizeBySeverity(firstDiagnostic.severity === "error" ? "✗" : "⚠", firstDiagnostic.severity);
|
|
3228
|
+
const siteCountBadge = formatSiteCountBadge(ruleDiagnostics.length);
|
|
3229
|
+
const ruleNameRendering = siteCountBadge.length > 0 ? colorizeBySeverity(padRuleNameToColumn(ruleKey, ruleNameColumnWidth), firstDiagnostic.severity) : colorizeBySeverity(ruleKey, firstDiagnostic.severity);
|
|
3230
|
+
const trailingBadge = siteCountBadge.length > 0 ? ` ${highlighter.gray(siteCountBadge)}` : "";
|
|
3231
|
+
logger.log(` ${icon} ${ruleNameRendering}${trailingBadge}`);
|
|
3232
|
+
};
|
|
3233
|
+
const printDetailedRuleGroup = (ruleKey, ruleDiagnostics, rootDirectory, ruleNameColumnWidth) => {
|
|
3234
|
+
printCompactRuleGroupLine(ruleKey, ruleDiagnostics, ruleNameColumnWidth);
|
|
3235
|
+
const firstDiagnostic = ruleDiagnostics[0];
|
|
3236
|
+
grayLine(indentMultilineText(firstDiagnostic.message, " "));
|
|
3237
|
+
if (firstDiagnostic.help) grayLine(indentMultilineText(`→ ${firstDiagnostic.help}`, " "));
|
|
3238
|
+
const firstLocation = ruleDiagnostics.find((diagnostic) => diagnostic.line > 0);
|
|
3239
|
+
if (firstLocation) grayLine(` ${toRelativePath(firstLocation.filePath, rootDirectory)}:${firstLocation.line}`);
|
|
3240
|
+
logger.break();
|
|
3241
|
+
};
|
|
3242
|
+
const printVerboseRuleGroup = (ruleKey, ruleDiagnostics, ruleNameColumnWidth) => {
|
|
3243
|
+
printCompactRuleGroupLine(ruleKey, ruleDiagnostics, ruleNameColumnWidth);
|
|
3244
|
+
const firstDiagnostic = ruleDiagnostics[0];
|
|
3245
|
+
grayLine(indentMultilineText(firstDiagnostic.message, " "));
|
|
3246
|
+
if (firstDiagnostic.help) grayLine(indentMultilineText(`→ ${firstDiagnostic.help}`, " "));
|
|
3247
|
+
const fileSites = buildVerboseSiteMap(ruleDiagnostics);
|
|
3248
|
+
for (const [filePath, sites] of fileSites) if (sites.length > 0) for (const site of sites) {
|
|
3249
|
+
grayLine(` ${filePath}:${site.line}`);
|
|
3250
|
+
if (site.suppressionHint) grayLine(` ↳ ${site.suppressionHint}`);
|
|
3251
|
+
}
|
|
3252
|
+
else grayLine(` ${filePath}`);
|
|
3253
|
+
logger.break();
|
|
3254
|
+
};
|
|
3255
|
+
const printDiagnostics = (diagnostics, isVerbose, rootDirectory) => {
|
|
3212
3256
|
const sortedRuleGroups = sortByImportance([...groupBy(diagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()]);
|
|
3213
|
-
const visibleRuleGroups = isVerbose ? sortedRuleGroups : sortedRuleGroups.slice(0,
|
|
3214
|
-
const hiddenRuleGroups = isVerbose ? [] : sortedRuleGroups.slice(
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
const icon = colorizeBySeverity(firstDiagnostic.severity === "error" ? "✗" : "⚠", firstDiagnostic.severity);
|
|
3218
|
-
const count = ruleDiagnostics.length;
|
|
3219
|
-
const countLabel = count > 1 ? colorizeBySeverity(` (${count})`, firstDiagnostic.severity) : "";
|
|
3220
|
-
logger.log(` ${icon} ${firstDiagnostic.message}${countLabel}`);
|
|
3221
|
-
if (firstDiagnostic.help) logger.dim(indentMultilineText(firstDiagnostic.help, " "));
|
|
3257
|
+
const visibleRuleGroups = isVerbose ? sortedRuleGroups : sortedRuleGroups.slice(0, 5);
|
|
3258
|
+
const hiddenRuleGroups = isVerbose ? [] : sortedRuleGroups.slice(5);
|
|
3259
|
+
const ruleNameColumnWidth = computeRuleNameColumnWidth(visibleRuleGroups.map(([ruleKey]) => ruleKey));
|
|
3260
|
+
visibleRuleGroups.forEach(([ruleKey, ruleDiagnostics]) => {
|
|
3222
3261
|
if (isVerbose) {
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
logger.dim(` ${filePath}:${site.line}`);
|
|
3226
|
-
if (site.suppressionHint) logger.dim(` ↳ ${site.suppressionHint}`);
|
|
3227
|
-
}
|
|
3228
|
-
else logger.dim(` ${filePath}`);
|
|
3262
|
+
printVerboseRuleGroup(ruleKey, ruleDiagnostics, ruleNameColumnWidth);
|
|
3263
|
+
return;
|
|
3229
3264
|
}
|
|
3230
|
-
|
|
3231
|
-
}
|
|
3265
|
+
printDetailedRuleGroup(ruleKey, ruleDiagnostics, rootDirectory, ruleNameColumnWidth);
|
|
3266
|
+
});
|
|
3232
3267
|
if (hiddenRuleGroups.length > 0) printHiddenDiagnosticsSummary(hiddenRuleGroups);
|
|
3233
3268
|
};
|
|
3234
3269
|
const printHiddenDiagnosticsSummary = (hiddenRuleGroups) => {
|
|
3235
3270
|
const renderedParts = buildHiddenDiagnosticsSummary(hiddenRuleGroups.flatMap(([, ruleDiagnostics]) => ruleDiagnostics)).map((part) => colorizeBySeverity(part.text, part.severity));
|
|
3236
3271
|
logger.log(` ${renderedParts.join(" ")}`);
|
|
3237
|
-
|
|
3272
|
+
grayLine(" Run `npx react-doctor@latest . --verbose` to get all details");
|
|
3238
3273
|
logger.break();
|
|
3239
3274
|
};
|
|
3240
3275
|
const formatElapsedTime = (elapsedMilliseconds) => {
|
|
@@ -3277,37 +3312,76 @@ const buildScoreBarSegments = (score) => {
|
|
|
3277
3312
|
emptySegment: "░".repeat(emptyCount)
|
|
3278
3313
|
};
|
|
3279
3314
|
};
|
|
3280
|
-
const buildPlainScoreBar = (score) => {
|
|
3281
|
-
const { filledSegment, emptySegment } = buildScoreBarSegments(score);
|
|
3282
|
-
return `${filledSegment}${emptySegment}`;
|
|
3283
|
-
};
|
|
3284
3315
|
const buildScoreBar = (score) => {
|
|
3285
3316
|
const { filledSegment, emptySegment } = buildScoreBarSegments(score);
|
|
3286
3317
|
return colorizeByScore(filledSegment, score) + highlighter.dim(emptySegment);
|
|
3287
3318
|
};
|
|
3288
|
-
const printScoreGauge = (score, label) => {
|
|
3289
|
-
const scoreDisplay = colorizeByScore(`${score}`, score);
|
|
3290
|
-
const labelDisplay = colorizeByScore(label, score);
|
|
3291
|
-
logger.log(` ${scoreDisplay} / 100 ${labelDisplay}`);
|
|
3292
|
-
logger.break();
|
|
3293
|
-
logger.log(` ${buildScoreBar(score)}`);
|
|
3294
|
-
logger.break();
|
|
3295
|
-
};
|
|
3296
3319
|
const getDoctorFace = (score) => {
|
|
3297
3320
|
if (score >= 75) return ["◠ ◠", " ▽ "];
|
|
3298
3321
|
if (score >= 50) return ["• •", " ─ "];
|
|
3299
3322
|
return ["x x", " ▽ "];
|
|
3300
3323
|
};
|
|
3301
|
-
const
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3324
|
+
const BRANDING_LINE = `React Doctor ${highlighter.dim("(www.react.doctor)")}`;
|
|
3325
|
+
const buildFaceRenderedLines = (score) => {
|
|
3326
|
+
const [eyes, mouth] = getDoctorFace(score);
|
|
3327
|
+
const colorize = (text) => colorizeByScore(text, score);
|
|
3328
|
+
return [
|
|
3329
|
+
"┌─────┐",
|
|
3330
|
+
`│ ${eyes} │`,
|
|
3331
|
+
`│ ${mouth} │`,
|
|
3332
|
+
"└─────┘"
|
|
3333
|
+
].map(colorize);
|
|
3334
|
+
};
|
|
3335
|
+
const printScoreHeader = (scoreResult) => {
|
|
3336
|
+
const renderedFaceLines = buildFaceRenderedLines(scoreResult.score);
|
|
3337
|
+
const scoreNumber = colorizeByScore(`${scoreResult.score}`, scoreResult.score);
|
|
3338
|
+
const scoreLabel = colorizeByScore(scoreResult.label, scoreResult.score);
|
|
3339
|
+
const rightColumnLines = [
|
|
3340
|
+
`${scoreNumber} ${highlighter.dim(`/ 100`)} ${scoreLabel}`,
|
|
3341
|
+
buildScoreBar(scoreResult.score),
|
|
3342
|
+
BRANDING_LINE,
|
|
3343
|
+
""
|
|
3344
|
+
];
|
|
3345
|
+
for (let lineIndex = 0; lineIndex < renderedFaceLines.length; lineIndex += 1) {
|
|
3346
|
+
const rightColumnContent = rightColumnLines[lineIndex] ?? "";
|
|
3347
|
+
const separator = rightColumnContent.length > 0 ? " " : "";
|
|
3348
|
+
logger.log(` ${renderedFaceLines[lineIndex]}${separator}${rightColumnContent}`);
|
|
3349
|
+
}
|
|
3350
|
+
logger.break();
|
|
3351
|
+
};
|
|
3352
|
+
const printBrandingOnlyHeader = () => {
|
|
3353
|
+
logger.log(` ${BRANDING_LINE}`);
|
|
3354
|
+
logger.break();
|
|
3355
|
+
};
|
|
3356
|
+
const printNoScoreHeader = (noScoreMessage) => {
|
|
3357
|
+
logger.log(` ${BRANDING_LINE}`);
|
|
3358
|
+
logger.log(` ${highlighter.gray(noScoreMessage)}`);
|
|
3359
|
+
logger.break();
|
|
3360
|
+
};
|
|
3361
|
+
const buildCategoryBar = (count, maximumCount, useErrorColor) => {
|
|
3362
|
+
if (maximumCount === 0) return highlighter.dim("░".repeat(16));
|
|
3363
|
+
const filledCount = Math.max(1, Math.round(count / maximumCount * 16));
|
|
3364
|
+
const cappedFilledCount = Math.min(filledCount, 16);
|
|
3365
|
+
const emptyCount = 16 - cappedFilledCount;
|
|
3366
|
+
const filledSegment = "█".repeat(cappedFilledCount);
|
|
3367
|
+
const emptySegment = "░".repeat(emptyCount);
|
|
3368
|
+
return `${useErrorColor ? highlighter.error(filledSegment) : highlighter.warn(filledSegment)}${highlighter.dim(emptySegment)}`;
|
|
3369
|
+
};
|
|
3370
|
+
const padCategoryLabel = (categoryLabel) => {
|
|
3371
|
+
if (categoryLabel.length >= 18) return categoryLabel;
|
|
3372
|
+
return categoryLabel + " ".repeat(18 - categoryLabel.length);
|
|
3373
|
+
};
|
|
3374
|
+
const printCategoryBreakdown = (entries) => {
|
|
3375
|
+
if (entries.length === 0) return;
|
|
3376
|
+
const maximumCount = Math.max(...entries.map((entry) => entry.totalCount));
|
|
3377
|
+
logger.dim(" By category");
|
|
3378
|
+
for (const entry of entries) {
|
|
3379
|
+
const paddedLabel = padCategoryLabel(entry.category);
|
|
3380
|
+
const categoryBar = buildCategoryBar(entry.totalCount, maximumCount, entry.errorCount > 0);
|
|
3381
|
+
const totalCountDisplay = String(entry.totalCount);
|
|
3382
|
+
const errorBadge = entry.errorCount > 0 ? ` ${highlighter.error(`${entry.errorCount}×`)}` : "";
|
|
3383
|
+
logger.log(` ${paddedLabel}${categoryBar} ${totalCountDisplay}${errorBadge}`);
|
|
3309
3384
|
}
|
|
3310
|
-
logger.log(` React Doctor ${highlighter.dim("(www.react.doctor)")}`);
|
|
3311
3385
|
logger.break();
|
|
3312
3386
|
};
|
|
3313
3387
|
const buildShareUrl = (diagnostics, scoreResult, projectName) => {
|
|
@@ -3322,67 +3396,31 @@ const buildShareUrl = (diagnostics, scoreResult, projectName) => {
|
|
|
3322
3396
|
if (affectedFileCount > 0) params.set("f", String(affectedFileCount));
|
|
3323
3397
|
return `${SHARE_BASE_URL}?${params.toString()}`;
|
|
3324
3398
|
};
|
|
3325
|
-
const
|
|
3326
|
-
const lines = [];
|
|
3327
|
-
if (scoreResult) {
|
|
3328
|
-
const [eyes, mouth] = getDoctorFace(scoreResult.score);
|
|
3329
|
-
const scoreColorizer = (text) => colorizeByScore(text, scoreResult.score);
|
|
3330
|
-
lines.push(createFramedLine("┌─────┐", scoreColorizer("┌─────┐")));
|
|
3331
|
-
lines.push(createFramedLine(`│ ${eyes} │`, scoreColorizer(`│ ${eyes} │`)));
|
|
3332
|
-
lines.push(createFramedLine(`│ ${mouth} │`, scoreColorizer(`│ ${mouth} │`)));
|
|
3333
|
-
lines.push(createFramedLine("└─────┘", scoreColorizer("└─────┘")));
|
|
3334
|
-
lines.push(createFramedLine("React Doctor (www.react.doctor)", `React Doctor ${highlighter.dim("(www.react.doctor)")}`));
|
|
3335
|
-
lines.push(createFramedLine(""));
|
|
3336
|
-
const scoreLinePlainText = `${scoreResult.score} / 100 ${scoreResult.label}`;
|
|
3337
|
-
const scoreLineRenderedText = `${colorizeByScore(String(scoreResult.score), scoreResult.score)} / 100 ${colorizeByScore(scoreResult.label, scoreResult.score)}`;
|
|
3338
|
-
lines.push(createFramedLine(scoreLinePlainText, scoreLineRenderedText));
|
|
3339
|
-
lines.push(createFramedLine(""));
|
|
3340
|
-
lines.push(createFramedLine(buildPlainScoreBar(scoreResult.score), buildScoreBar(scoreResult.score)));
|
|
3341
|
-
lines.push(createFramedLine(""));
|
|
3342
|
-
} else {
|
|
3343
|
-
lines.push(createFramedLine("React Doctor (www.react.doctor)", `React Doctor ${highlighter.dim("(www.react.doctor)")}`));
|
|
3344
|
-
lines.push(createFramedLine(""));
|
|
3345
|
-
lines.push(createFramedLine(noScoreMessage, highlighter.dim(noScoreMessage)));
|
|
3346
|
-
lines.push(createFramedLine(""));
|
|
3347
|
-
}
|
|
3348
|
-
return lines;
|
|
3349
|
-
};
|
|
3350
|
-
const buildCountsSummaryLine = (diagnostics, totalSourceFileCount, elapsedMilliseconds) => {
|
|
3399
|
+
const printCountsSummaryLine = (diagnostics, totalSourceFileCount, elapsedMilliseconds) => {
|
|
3351
3400
|
const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
3352
3401
|
const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
|
|
3353
3402
|
const affectedFileCount = collectAffectedFiles(diagnostics).size;
|
|
3354
|
-
const
|
|
3355
|
-
const
|
|
3356
|
-
const
|
|
3357
|
-
|
|
3358
|
-
const errorText = `✗ ${errorCount} error${errorCount === 1 ? "" : "s"}`;
|
|
3359
|
-
plainParts.push(errorText);
|
|
3360
|
-
renderedParts.push(highlighter.error(errorText));
|
|
3361
|
-
}
|
|
3362
|
-
if (warningCount > 0) {
|
|
3363
|
-
const warningText = `⚠ ${warningCount} warning${warningCount === 1 ? "" : "s"}`;
|
|
3364
|
-
plainParts.push(warningText);
|
|
3365
|
-
renderedParts.push(highlighter.warn(warningText));
|
|
3366
|
-
}
|
|
3403
|
+
const totalIssueCount = diagnostics.length;
|
|
3404
|
+
const elapsedTimeLabel = formatElapsedTime(elapsedMilliseconds);
|
|
3405
|
+
const issueCountColor = errorCount > 0 ? highlighter.error : warningCount > 0 ? highlighter.warn : highlighter.dim;
|
|
3406
|
+
const issueCountText = `${totalIssueCount} ${totalIssueCount === 1 ? "issue" : "issues"}`;
|
|
3367
3407
|
const fileCountText = totalSourceFileCount > 0 ? `across ${affectedFileCount}/${totalSourceFileCount} files` : `across ${affectedFileCount} file${affectedFileCount === 1 ? "" : "s"}`;
|
|
3368
|
-
const elapsedTimeText = `in ${
|
|
3369
|
-
|
|
3370
|
-
renderedParts.push(highlighter.dim(fileCountText), highlighter.dim(elapsedTimeText));
|
|
3371
|
-
return createFramedLine(plainParts.join(" "), renderedParts.join(" "));
|
|
3408
|
+
const elapsedTimeText = `in ${elapsedTimeLabel}`;
|
|
3409
|
+
logger.log(` ${issueCountColor(issueCountText)} ${highlighter.dim(`${fileCountText} ${elapsedTimeText}`)}`);
|
|
3372
3410
|
};
|
|
3373
3411
|
const printSummary = (diagnostics, elapsedMilliseconds, scoreResult, projectName, totalSourceFileCount, noScoreMessage, isOffline) => {
|
|
3374
|
-
|
|
3412
|
+
printCategoryBreakdown(buildCategoryBreakdown(diagnostics));
|
|
3413
|
+
if (scoreResult) printScoreHeader(scoreResult);
|
|
3414
|
+
else printNoScoreHeader(noScoreMessage);
|
|
3415
|
+
printCountsSummaryLine(diagnostics, totalSourceFileCount, elapsedMilliseconds);
|
|
3375
3416
|
try {
|
|
3376
3417
|
const diagnosticsDirectory = writeDiagnosticsDirectory(diagnostics);
|
|
3377
|
-
logger.
|
|
3378
|
-
|
|
3379
|
-
} catch {
|
|
3380
|
-
logger.break();
|
|
3381
|
-
}
|
|
3418
|
+
logger.log(highlighter.gray(` Full diagnostics written to ${diagnosticsDirectory}`));
|
|
3419
|
+
} catch {}
|
|
3382
3420
|
if (!isOffline) {
|
|
3383
|
-
const shareUrl = buildShareUrl(diagnostics, scoreResult, projectName);
|
|
3384
3421
|
logger.break();
|
|
3385
|
-
|
|
3422
|
+
const shareUrl = buildShareUrl(diagnostics, scoreResult, projectName);
|
|
3423
|
+
logger.log(` ${highlighter.bold("→ Share your results:")} ${highlighter.info(shareUrl)}`);
|
|
3386
3424
|
}
|
|
3387
3425
|
};
|
|
3388
3426
|
const resolveOxlintNode = async (isLintEnabled, isQuiet) => {
|
|
@@ -3565,15 +3603,14 @@ const runScan = async (directory, options, userConfig, startTime) => {
|
|
|
3565
3603
|
} else logger.success("No issues found!");
|
|
3566
3604
|
logger.break();
|
|
3567
3605
|
if (hasSkippedChecks) {
|
|
3568
|
-
|
|
3569
|
-
logger.
|
|
3570
|
-
} else if (scoreResult)
|
|
3571
|
-
|
|
3572
|
-
printScoreGauge(scoreResult.score, scoreResult.label);
|
|
3573
|
-
} else logger.dim(` ${noScoreMessage}`);
|
|
3606
|
+
printBrandingOnlyHeader();
|
|
3607
|
+
logger.log(highlighter.gray(" Score not shown — some checks could not complete."));
|
|
3608
|
+
} else if (scoreResult) printScoreHeader(scoreResult);
|
|
3609
|
+
else printNoScoreHeader(noScoreMessage);
|
|
3574
3610
|
return buildResult();
|
|
3575
3611
|
}
|
|
3576
|
-
|
|
3612
|
+
logger.break();
|
|
3613
|
+
printDiagnostics(diagnostics, options.verbose, directory);
|
|
3577
3614
|
const displayedSourceFileCount = isDiffMode ? includePaths.length : lintSourceFileCount;
|
|
3578
3615
|
const shouldShowShareLink = !options.offline && options.share;
|
|
3579
3616
|
printSummary(diagnostics, elapsedMilliseconds, scoreResult, projectInfo.projectName, displayedSourceFileCount, noScoreMessage, !shouldShowShareLink);
|
|
@@ -3991,7 +4028,7 @@ const promptProjectSelection = async (workspacePackages, rootDirectory) => {
|
|
|
3991
4028
|
};
|
|
3992
4029
|
//#endregion
|
|
3993
4030
|
//#region src/cli.ts
|
|
3994
|
-
const VERSION = "0.1.
|
|
4031
|
+
const VERSION = "0.1.2";
|
|
3995
4032
|
const VALID_FAIL_ON_LEVELS = new Set([
|
|
3996
4033
|
"error",
|
|
3997
4034
|
"warning",
|
package/dist/eslint-plugin.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -487,7 +487,9 @@ const highlighter = {
|
|
|
487
487
|
warn: pc.yellow,
|
|
488
488
|
info: pc.cyan,
|
|
489
489
|
success: pc.green,
|
|
490
|
-
dim: pc.dim
|
|
490
|
+
dim: pc.dim,
|
|
491
|
+
gray: pc.gray,
|
|
492
|
+
bold: pc.bold
|
|
491
493
|
};
|
|
492
494
|
const logger = {
|
|
493
495
|
error(...args) {
|
|
@@ -2831,8 +2833,6 @@ const runOxlint = async (options) => {
|
|
|
2831
2833
|
return await spawnLintBatches();
|
|
2832
2834
|
} catch (error) {
|
|
2833
2835
|
if (extendsPaths.length === 0) throw error;
|
|
2834
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
2835
|
-
process.stderr.write(`[react-doctor] could not adopt existing lint config (${reason.split("\n")[0]}); retrying without extends. Set "adoptExistingLintConfig": false to silence.\n`);
|
|
2836
2836
|
writeOxlintConfig(createOxlintConfig({
|
|
2837
2837
|
pluginPath,
|
|
2838
2838
|
framework,
|
package/package.json
CHANGED