react-doctor 0.1.0 → 0.1.1

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 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
@@ -296,6 +298,28 @@ const runInstallSkill = async (options = {}) => {
296
298
  }
297
299
  };
298
300
  //#endregion
301
+ //#region src/utils/build-category-breakdown.ts
302
+ const buildCategoryBreakdown = (diagnostics) => {
303
+ const entriesByCategory = /* @__PURE__ */ new Map();
304
+ for (const diagnostic of diagnostics) {
305
+ const existingEntry = entriesByCategory.get(diagnostic.category) ?? {
306
+ category: diagnostic.category,
307
+ totalCount: 0,
308
+ errorCount: 0,
309
+ warningCount: 0
310
+ };
311
+ existingEntry.totalCount += 1;
312
+ if (diagnostic.severity === "error") existingEntry.errorCount += 1;
313
+ else existingEntry.warningCount += 1;
314
+ entriesByCategory.set(diagnostic.category, existingEntry);
315
+ }
316
+ return [...entriesByCategory.values()].sort((entryA, entryB) => {
317
+ if (entryA.errorCount !== entryB.errorCount) return entryB.errorCount - entryA.errorCount;
318
+ if (entryA.totalCount !== entryB.totalCount) return entryB.totalCount - entryA.totalCount;
319
+ return entryA.category.localeCompare(entryB.category);
320
+ });
321
+ };
322
+ //#endregion
299
323
  //#region src/utils/build-hidden-diagnostics-summary.ts
300
324
  const buildHiddenDiagnosticsSummary = (hiddenDiagnostics) => {
301
325
  const errorCount = hiddenDiagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
@@ -1462,32 +1486,6 @@ const formatErrorMessage = (error) => error instanceof Error ? error.message ||
1462
1486
  const formatErrorChain = (rootError) => collectErrorChain(rootError).map(formatErrorMessage).join(" → ");
1463
1487
  const getErrorChainMessages = (rootError) => collectErrorChain(rootError).map(formatErrorMessage);
1464
1488
  //#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
1489
  //#region src/utils/group-by.ts
1492
1490
  const groupBy = (items, keyFn) => {
1493
1491
  const groups = /* @__PURE__ */ new Map();
@@ -3162,8 +3160,6 @@ const runOxlint = async (options) => {
3162
3160
  return await spawnLintBatches();
3163
3161
  } catch (error) {
3164
3162
  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
3163
  writeOxlintConfig(createOxlintConfig({
3168
3164
  pluginPath,
3169
3165
  framework,
@@ -3208,33 +3204,71 @@ const buildVerboseSiteMap = (diagnostics) => {
3208
3204
  }
3209
3205
  return fileSites;
3210
3206
  };
3211
- const printDiagnostics = (diagnostics, isVerbose) => {
3207
+ const formatSiteCountBadge = (count) => count > 1 ? `×${count}` : "";
3208
+ const computeRuleNameColumnWidth = (ruleKeys) => {
3209
+ const longestRuleNameLength = ruleKeys.reduce((longest, ruleKey) => Math.max(longest, ruleKey.length), 0);
3210
+ return Math.max(36, longestRuleNameLength);
3211
+ };
3212
+ const padRuleNameToColumn = (ruleName, columnWidth) => {
3213
+ if (ruleName.length >= columnWidth) return ruleName;
3214
+ return ruleName + " ".repeat(columnWidth - ruleName.length);
3215
+ };
3216
+ const grayLine = (text) => {
3217
+ logger.log(highlighter.gray(text));
3218
+ };
3219
+ const printCompactRuleGroupLine = (ruleKey, ruleDiagnostics, ruleNameColumnWidth) => {
3220
+ const firstDiagnostic = ruleDiagnostics[0];
3221
+ const icon = colorizeBySeverity(firstDiagnostic.severity === "error" ? "✗" : "⚠", firstDiagnostic.severity);
3222
+ const siteCountBadge = formatSiteCountBadge(ruleDiagnostics.length);
3223
+ const ruleNameRendering = siteCountBadge.length > 0 ? colorizeBySeverity(padRuleNameToColumn(ruleKey, ruleNameColumnWidth), firstDiagnostic.severity) : colorizeBySeverity(ruleKey, firstDiagnostic.severity);
3224
+ const trailingBadge = siteCountBadge.length > 0 ? ` ${highlighter.gray(siteCountBadge)}` : "";
3225
+ logger.log(` ${icon} ${ruleNameRendering}${trailingBadge}`);
3226
+ };
3227
+ const printDetailedRuleGroup = (ruleKey, ruleDiagnostics, rootDirectory, ruleNameColumnWidth) => {
3228
+ printCompactRuleGroupLine(ruleKey, ruleDiagnostics, ruleNameColumnWidth);
3229
+ const firstDiagnostic = ruleDiagnostics[0];
3230
+ grayLine(indentMultilineText(firstDiagnostic.message, " "));
3231
+ if (firstDiagnostic.help) grayLine(indentMultilineText(`→ ${firstDiagnostic.help}`, " "));
3232
+ const firstLocation = ruleDiagnostics.find((diagnostic) => diagnostic.line > 0);
3233
+ if (firstLocation) grayLine(` ${toRelativePath(firstLocation.filePath, rootDirectory)}:${firstLocation.line}`);
3234
+ logger.break();
3235
+ };
3236
+ const printVerboseRuleGroup = (ruleKey, ruleDiagnostics, ruleNameColumnWidth) => {
3237
+ printCompactRuleGroupLine(ruleKey, ruleDiagnostics, ruleNameColumnWidth);
3238
+ const firstDiagnostic = ruleDiagnostics[0];
3239
+ grayLine(indentMultilineText(firstDiagnostic.message, " "));
3240
+ if (firstDiagnostic.help) grayLine(indentMultilineText(`→ ${firstDiagnostic.help}`, " "));
3241
+ const fileSites = buildVerboseSiteMap(ruleDiagnostics);
3242
+ for (const [filePath, sites] of fileSites) if (sites.length > 0) for (const site of sites) {
3243
+ grayLine(` ${filePath}:${site.line}`);
3244
+ if (site.suppressionHint) grayLine(` ↳ ${site.suppressionHint}`);
3245
+ }
3246
+ else grayLine(` ${filePath}`);
3247
+ logger.break();
3248
+ };
3249
+ const printDiagnostics = (diagnostics, isVerbose, rootDirectory) => {
3212
3250
  const sortedRuleGroups = sortByImportance([...groupBy(diagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()]);
3213
- const visibleRuleGroups = isVerbose ? sortedRuleGroups : sortedRuleGroups.slice(0, 3);
3214
- const hiddenRuleGroups = isVerbose ? [] : sortedRuleGroups.slice(3);
3215
- for (const [, ruleDiagnostics] of visibleRuleGroups) {
3216
- const firstDiagnostic = ruleDiagnostics[0];
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, " "));
3251
+ const visibleRuleGroups = isVerbose ? sortedRuleGroups : sortedRuleGroups.slice(0, 5);
3252
+ const hiddenRuleGroups = isVerbose ? [] : sortedRuleGroups.slice(5);
3253
+ const ruleNameColumnWidth = computeRuleNameColumnWidth(visibleRuleGroups.map(([ruleKey]) => ruleKey));
3254
+ visibleRuleGroups.forEach(([ruleKey, ruleDiagnostics], visibleIndex) => {
3222
3255
  if (isVerbose) {
3223
- const fileSites = buildVerboseSiteMap(ruleDiagnostics);
3224
- for (const [filePath, sites] of fileSites) if (sites.length > 0) for (const site of sites) {
3225
- logger.dim(` ${filePath}:${site.line}`);
3226
- if (site.suppressionHint) logger.dim(` ↳ ${site.suppressionHint}`);
3227
- }
3228
- else logger.dim(` ${filePath}`);
3256
+ printVerboseRuleGroup(ruleKey, ruleDiagnostics, ruleNameColumnWidth);
3257
+ return;
3229
3258
  }
3230
- logger.break();
3231
- }
3259
+ if (visibleIndex < 1) {
3260
+ printDetailedRuleGroup(ruleKey, ruleDiagnostics, rootDirectory, ruleNameColumnWidth);
3261
+ return;
3262
+ }
3263
+ printCompactRuleGroupLine(ruleKey, ruleDiagnostics, ruleNameColumnWidth);
3264
+ });
3265
+ if (visibleRuleGroups.length > 1 && !isVerbose) logger.break();
3232
3266
  if (hiddenRuleGroups.length > 0) printHiddenDiagnosticsSummary(hiddenRuleGroups);
3233
3267
  };
3234
3268
  const printHiddenDiagnosticsSummary = (hiddenRuleGroups) => {
3235
3269
  const renderedParts = buildHiddenDiagnosticsSummary(hiddenRuleGroups.flatMap(([, ruleDiagnostics]) => ruleDiagnostics)).map((part) => colorizeBySeverity(part.text, part.severity));
3236
3270
  logger.log(` ${renderedParts.join(" ")}`);
3237
- logger.dim(" Run `npx react-doctor@latest . --verbose` to get all details");
3271
+ grayLine(" Run `npx react-doctor@latest . --verbose` to get all details");
3238
3272
  logger.break();
3239
3273
  };
3240
3274
  const formatElapsedTime = (elapsedMilliseconds) => {
@@ -3277,37 +3311,76 @@ const buildScoreBarSegments = (score) => {
3277
3311
  emptySegment: "░".repeat(emptyCount)
3278
3312
  };
3279
3313
  };
3280
- const buildPlainScoreBar = (score) => {
3281
- const { filledSegment, emptySegment } = buildScoreBarSegments(score);
3282
- return `${filledSegment}${emptySegment}`;
3283
- };
3284
3314
  const buildScoreBar = (score) => {
3285
3315
  const { filledSegment, emptySegment } = buildScoreBarSegments(score);
3286
3316
  return colorizeByScore(filledSegment, score) + highlighter.dim(emptySegment);
3287
3317
  };
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
3318
  const getDoctorFace = (score) => {
3297
3319
  if (score >= 75) return ["◠ ◠", " ▽ "];
3298
3320
  if (score >= 50) return ["• •", " ─ "];
3299
3321
  return ["x x", " ▽ "];
3300
3322
  };
3301
- const printBranding = (score) => {
3302
- if (score !== void 0) {
3303
- const [eyes, mouth] = getDoctorFace(score);
3304
- const colorize = (text) => colorizeByScore(text, score);
3305
- logger.log(colorize(" ┌─────┐"));
3306
- logger.log(colorize(` │ ${eyes} │`));
3307
- logger.log(colorize(` │ ${mouth} │`));
3308
- logger.log(colorize(" └─────┘"));
3323
+ const BRANDING_LINE = `React Doctor ${highlighter.dim("(www.react.doctor)")}`;
3324
+ const buildFaceRenderedLines = (score) => {
3325
+ const [eyes, mouth] = getDoctorFace(score);
3326
+ const colorize = (text) => colorizeByScore(text, score);
3327
+ return [
3328
+ "┌─────┐",
3329
+ `│ ${eyes} │`,
3330
+ `│ ${mouth} │`,
3331
+ "└─────┘"
3332
+ ].map(colorize);
3333
+ };
3334
+ const printScoreHeader = (scoreResult) => {
3335
+ const renderedFaceLines = buildFaceRenderedLines(scoreResult.score);
3336
+ const scoreNumber = colorizeByScore(`${scoreResult.score}`, scoreResult.score);
3337
+ const scoreLabel = colorizeByScore(scoreResult.label, scoreResult.score);
3338
+ const rightColumnLines = [
3339
+ `${scoreNumber} ${highlighter.dim(`/ 100`)} ${scoreLabel}`,
3340
+ buildScoreBar(scoreResult.score),
3341
+ BRANDING_LINE,
3342
+ ""
3343
+ ];
3344
+ for (let lineIndex = 0; lineIndex < renderedFaceLines.length; lineIndex += 1) {
3345
+ const rightColumnContent = rightColumnLines[lineIndex] ?? "";
3346
+ const separator = rightColumnContent.length > 0 ? " " : "";
3347
+ logger.log(` ${renderedFaceLines[lineIndex]}${separator}${rightColumnContent}`);
3348
+ }
3349
+ logger.break();
3350
+ };
3351
+ const printBrandingOnlyHeader = () => {
3352
+ logger.log(` ${BRANDING_LINE}`);
3353
+ logger.break();
3354
+ };
3355
+ const printNoScoreHeader = (noScoreMessage) => {
3356
+ logger.log(` ${BRANDING_LINE}`);
3357
+ logger.log(` ${highlighter.gray(noScoreMessage)}`);
3358
+ logger.break();
3359
+ };
3360
+ const buildCategoryBar = (count, maximumCount, useErrorColor) => {
3361
+ if (maximumCount === 0) return highlighter.dim("░".repeat(16));
3362
+ const filledCount = Math.max(1, Math.round(count / maximumCount * 16));
3363
+ const cappedFilledCount = Math.min(filledCount, 16);
3364
+ const emptyCount = 16 - cappedFilledCount;
3365
+ const filledSegment = "█".repeat(cappedFilledCount);
3366
+ const emptySegment = "░".repeat(emptyCount);
3367
+ return `${useErrorColor ? highlighter.error(filledSegment) : highlighter.warn(filledSegment)}${highlighter.dim(emptySegment)}`;
3368
+ };
3369
+ const padCategoryLabel = (categoryLabel) => {
3370
+ if (categoryLabel.length >= 18) return categoryLabel;
3371
+ return categoryLabel + " ".repeat(18 - categoryLabel.length);
3372
+ };
3373
+ const printCategoryBreakdown = (entries) => {
3374
+ if (entries.length === 0) return;
3375
+ const maximumCount = Math.max(...entries.map((entry) => entry.totalCount));
3376
+ logger.dim(" By category");
3377
+ for (const entry of entries) {
3378
+ const paddedLabel = padCategoryLabel(entry.category);
3379
+ const categoryBar = buildCategoryBar(entry.totalCount, maximumCount, entry.errorCount > 0);
3380
+ const totalCountDisplay = String(entry.totalCount);
3381
+ const errorBadge = entry.errorCount > 0 ? ` ${highlighter.error(`${entry.errorCount}×`)}` : "";
3382
+ logger.log(` ${paddedLabel}${categoryBar} ${totalCountDisplay}${errorBadge}`);
3309
3383
  }
3310
- logger.log(` React Doctor ${highlighter.dim("(www.react.doctor)")}`);
3311
3384
  logger.break();
3312
3385
  };
3313
3386
  const buildShareUrl = (diagnostics, scoreResult, projectName) => {
@@ -3322,67 +3395,31 @@ const buildShareUrl = (diagnostics, scoreResult, projectName) => {
3322
3395
  if (affectedFileCount > 0) params.set("f", String(affectedFileCount));
3323
3396
  return `${SHARE_BASE_URL}?${params.toString()}`;
3324
3397
  };
3325
- const buildBrandingLines = (scoreResult, noScoreMessage) => {
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) => {
3398
+ const printCountsSummaryLine = (diagnostics, totalSourceFileCount, elapsedMilliseconds) => {
3351
3399
  const errorCount = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
3352
3400
  const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
3353
3401
  const affectedFileCount = collectAffectedFiles(diagnostics).size;
3354
- const elapsed = formatElapsedTime(elapsedMilliseconds);
3355
- const plainParts = [];
3356
- const renderedParts = [];
3357
- if (errorCount > 0) {
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
- }
3402
+ const totalIssueCount = diagnostics.length;
3403
+ const elapsedTimeLabel = formatElapsedTime(elapsedMilliseconds);
3404
+ const issueCountColor = errorCount > 0 ? highlighter.error : warningCount > 0 ? highlighter.warn : highlighter.dim;
3405
+ const issueCountText = `${totalIssueCount} ${totalIssueCount === 1 ? "issue" : "issues"}`;
3367
3406
  const fileCountText = totalSourceFileCount > 0 ? `across ${affectedFileCount}/${totalSourceFileCount} files` : `across ${affectedFileCount} file${affectedFileCount === 1 ? "" : "s"}`;
3368
- const elapsedTimeText = `in ${elapsed}`;
3369
- plainParts.push(fileCountText, elapsedTimeText);
3370
- renderedParts.push(highlighter.dim(fileCountText), highlighter.dim(elapsedTimeText));
3371
- return createFramedLine(plainParts.join(" "), renderedParts.join(" "));
3407
+ const elapsedTimeText = `in ${elapsedTimeLabel}`;
3408
+ logger.log(` ${issueCountColor(issueCountText)} ${highlighter.dim(`${fileCountText} ${elapsedTimeText}`)}`);
3372
3409
  };
3373
3410
  const printSummary = (diagnostics, elapsedMilliseconds, scoreResult, projectName, totalSourceFileCount, noScoreMessage, isOffline) => {
3374
- printFramedBox([...buildBrandingLines(scoreResult, noScoreMessage), buildCountsSummaryLine(diagnostics, totalSourceFileCount, elapsedMilliseconds)]);
3411
+ printCategoryBreakdown(buildCategoryBreakdown(diagnostics));
3412
+ if (scoreResult) printScoreHeader(scoreResult);
3413
+ else printNoScoreHeader(noScoreMessage);
3414
+ printCountsSummaryLine(diagnostics, totalSourceFileCount, elapsedMilliseconds);
3375
3415
  try {
3376
3416
  const diagnosticsDirectory = writeDiagnosticsDirectory(diagnostics);
3377
- logger.break();
3378
- logger.dim(` Full diagnostics written to ${diagnosticsDirectory}`);
3379
- } catch {
3380
- logger.break();
3381
- }
3417
+ logger.log(highlighter.gray(` Full diagnostics written to ${diagnosticsDirectory}`));
3418
+ } catch {}
3382
3419
  if (!isOffline) {
3383
- const shareUrl = buildShareUrl(diagnostics, scoreResult, projectName);
3384
3420
  logger.break();
3385
- logger.dim(` Share your results: ${highlighter.info(shareUrl)}`);
3421
+ const shareUrl = buildShareUrl(diagnostics, scoreResult, projectName);
3422
+ logger.log(` ${highlighter.bold("→ Share your results:")} ${highlighter.info(shareUrl)}`);
3386
3423
  }
3387
3424
  };
3388
3425
  const resolveOxlintNode = async (isLintEnabled, isQuiet) => {
@@ -3565,15 +3602,13 @@ const runScan = async (directory, options, userConfig, startTime) => {
3565
3602
  } else logger.success("No issues found!");
3566
3603
  logger.break();
3567
3604
  if (hasSkippedChecks) {
3568
- printBranding();
3569
- logger.dim(" Score not shown — some checks could not complete.");
3570
- } else if (scoreResult) {
3571
- printBranding(scoreResult.score);
3572
- printScoreGauge(scoreResult.score, scoreResult.label);
3573
- } else logger.dim(` ${noScoreMessage}`);
3605
+ printBrandingOnlyHeader();
3606
+ logger.log(highlighter.gray(" Score not shown — some checks could not complete."));
3607
+ } else if (scoreResult) printScoreHeader(scoreResult);
3608
+ else printNoScoreHeader(noScoreMessage);
3574
3609
  return buildResult();
3575
3610
  }
3576
- printDiagnostics(diagnostics, options.verbose);
3611
+ printDiagnostics(diagnostics, options.verbose, directory);
3577
3612
  const displayedSourceFileCount = isDiffMode ? includePaths.length : lintSourceFileCount;
3578
3613
  const shouldShowShareLink = !options.offline && options.share;
3579
3614
  printSummary(diagnostics, elapsedMilliseconds, scoreResult, projectInfo.projectName, displayedSourceFileCount, noScoreMessage, !shouldShowShareLink);
@@ -3991,7 +4026,7 @@ const promptProjectSelection = async (workspacePackages, rootDirectory) => {
3991
4026
  };
3992
4027
  //#endregion
3993
4028
  //#region src/cli.ts
3994
- const VERSION = "0.1.0";
4029
+ const VERSION = "0.1.1";
3995
4030
  const VALID_FAIL_ON_LEVELS = new Set([
3996
4031
  "error",
3997
4032
  "warning",
@@ -6946,7 +6946,7 @@ const ALL_RULES_AT_RECOMMENDED_SEVERITY = {
6946
6946
  const eslintPlugin = {
6947
6947
  meta: {
6948
6948
  name: PLUGIN_NAMESPACE,
6949
- version: "0.1.0"
6949
+ version: "0.1.1"
6950
6950
  },
6951
6951
  rules: eslintShapedRules,
6952
6952
  configs: {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",