react-doctor 0.2.7 → 0.2.9
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-logger-C35LXalM.js → cli-logger-BliQX9s8.js} +17 -7
- package/dist/cli.js +120 -14
- package/dist/index.d.ts +51 -5
- package/dist/index.js +36 -35
- package/package.json +5 -5
|
@@ -2045,6 +2045,14 @@ var PackageJsonNotFoundError = class extends Error {
|
|
|
2045
2045
|
this.directory = directory;
|
|
2046
2046
|
}
|
|
2047
2047
|
};
|
|
2048
|
+
var NotADirectoryError = class extends Error {
|
|
2049
|
+
name = "NotADirectoryError";
|
|
2050
|
+
resolvedPath;
|
|
2051
|
+
constructor(resolvedPath, options) {
|
|
2052
|
+
super(`Resolved scan target "${resolvedPath}" is not a directory. Ensure the path exists and points to a project directory, not a file.`, options);
|
|
2053
|
+
this.resolvedPath = resolvedPath;
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2048
2056
|
var AmbiguousProjectError = class extends Error {
|
|
2049
2057
|
name = "AmbiguousProjectError";
|
|
2050
2058
|
directory;
|
|
@@ -4132,8 +4140,10 @@ const resolveScanTarget = (requestedDirectory) => {
|
|
|
4132
4140
|
const configSourceDirectory = loadedConfig?.sourceDirectory ?? null;
|
|
4133
4141
|
const redirectedDirectory = resolveConfigRootDir(userConfig, configSourceDirectory);
|
|
4134
4142
|
const directoryAfterRedirect = redirectedDirectory ?? absoluteRequested;
|
|
4143
|
+
const resolvedDirectory = resolveDiagnoseTarget(directoryAfterRedirect) ?? directoryAfterRedirect;
|
|
4144
|
+
if (!isDirectory(resolvedDirectory)) throw existsSync(resolvedDirectory) ? new NotADirectoryError(resolvedDirectory) : new ProjectNotFoundError(resolvedDirectory);
|
|
4135
4145
|
return {
|
|
4136
|
-
resolvedDirectory
|
|
4146
|
+
resolvedDirectory,
|
|
4137
4147
|
requestedDirectory: absoluteRequested,
|
|
4138
4148
|
userConfig,
|
|
4139
4149
|
configSourceDirectory,
|
|
@@ -4614,7 +4624,7 @@ var Files = class Files extends Context.Service()("react-doctor/Files") {
|
|
|
4614
4624
|
* pattern in react-doctor-evals' test layers.
|
|
4615
4625
|
*/
|
|
4616
4626
|
static layerInMemory = (tree) => {
|
|
4617
|
-
const resolveAbsolute = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath :
|
|
4627
|
+
const resolveAbsolute = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath : `${rootDirectory}/${filePath}`;
|
|
4618
4628
|
return Layer.succeed(Files, Files.of({
|
|
4619
4629
|
readLines: (input) => Effect.sync(() => {
|
|
4620
4630
|
const absolute = resolveAbsolute(input.filePath, input.rootDirectory);
|
|
@@ -4622,17 +4632,17 @@ var Files = class Files extends Context.Service()("react-doctor/Files") {
|
|
|
4622
4632
|
return content === void 0 ? null : content.split("\n");
|
|
4623
4633
|
}),
|
|
4624
4634
|
listSourceFiles: (rootDirectory) => Effect.sync(() => {
|
|
4625
|
-
const prefix = rootDirectory.endsWith(
|
|
4635
|
+
const prefix = rootDirectory.endsWith("/") ? rootDirectory : `${rootDirectory}/`;
|
|
4626
4636
|
const files = [];
|
|
4627
4637
|
for (const absolute of tree.keys()) {
|
|
4628
4638
|
if (!absolute.startsWith(prefix)) continue;
|
|
4629
|
-
files.push(absolute.slice(prefix.length)
|
|
4639
|
+
files.push(absolute.slice(prefix.length));
|
|
4630
4640
|
}
|
|
4631
4641
|
return files;
|
|
4632
4642
|
}),
|
|
4633
4643
|
isFile: (filePath) => Effect.sync(() => tree.has(filePath)),
|
|
4634
4644
|
isDirectory: (filePath) => Effect.sync(() => {
|
|
4635
|
-
const prefix = filePath.endsWith(
|
|
4645
|
+
const prefix = filePath.endsWith("/") ? filePath : `${filePath}/`;
|
|
4636
4646
|
for (const absolute of tree.keys()) if (absolute.startsWith(prefix)) return true;
|
|
4637
4647
|
return false;
|
|
4638
4648
|
})
|
|
@@ -5769,7 +5779,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
5769
5779
|
const primaryLabel = diagnostic.labels[0];
|
|
5770
5780
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
5771
5781
|
return {
|
|
5772
|
-
filePath: diagnostic.filename,
|
|
5782
|
+
filePath: diagnostic.filename.replaceAll("\\", "/"),
|
|
5773
5783
|
plugin,
|
|
5774
5784
|
rule,
|
|
5775
5785
|
severity: diagnostic.severity,
|
|
@@ -7046,4 +7056,4 @@ const cliLogger = {
|
|
|
7046
7056
|
//#endregion
|
|
7047
7057
|
export { isReactDoctorError as A, filterSourceFiles as C, groupBy as D, getDiffInfo as E, runInspect as F, toRelativePath as I, listWorkspacePackages as M, resolveScanTarget as N, highlighter as O, restoreLegacyThrow as P, filterDiagnosticsForSurface as S, formatReactDoctorError as T, Score as _, DeadCode as a, buildJsonReportError as b, LintPartialFailures as c, OXLINT_NODE_REQUIREMENT as d, Progress as f, SKILL_NAME as g, SHARE_BASE_URL as h, Config as i, layerOtlp as j, isMonorepoRoot as k, Linter as l, Reporter as m, cli_logger_exports as n, Files as o, Project as p, CANONICAL_GITHUB_URL as r, Git as s, cliLogger as t, NodeResolver as u, StagedFiles as v, formatErrorChain as w, discoverReactSubprojects as x, buildJsonReport as y };
|
|
7048
7058
|
|
|
7049
|
-
//# sourceMappingURL=cli-logger-
|
|
7059
|
+
//# sourceMappingURL=cli-logger-BliQX9s8.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./rolldown-runtime-uZX_iqCz.js";
|
|
2
|
-
import { A as isReactDoctorError, C as filterSourceFiles, D as groupBy, E as getDiffInfo, F as runInspect, I as toRelativePath, M as listWorkspacePackages, N as resolveScanTarget, O as highlighter, P as restoreLegacyThrow, S as filterDiagnosticsForSurface, T as formatReactDoctorError, _ as Score, a as DeadCode, b as buildJsonReportError, c as LintPartialFailures, d as OXLINT_NODE_REQUIREMENT, f as Progress, g as SKILL_NAME, h as SHARE_BASE_URL, i as Config, j as layerOtlp, k as isMonorepoRoot, l as Linter, m as Reporter, o as Files, p as Project, r as CANONICAL_GITHUB_URL, s as Git, t as cliLogger, u as NodeResolver, v as StagedFiles, w as formatErrorChain, x as discoverReactSubprojects, y as buildJsonReport } from "./cli-logger-
|
|
2
|
+
import { A as isReactDoctorError, C as filterSourceFiles, D as groupBy, E as getDiffInfo, F as runInspect, I as toRelativePath, M as listWorkspacePackages, N as resolveScanTarget, O as highlighter, P as restoreLegacyThrow, S as filterDiagnosticsForSurface, T as formatReactDoctorError, _ as Score, a as DeadCode, b as buildJsonReportError, c as LintPartialFailures, d as OXLINT_NODE_REQUIREMENT, f as Progress, g as SKILL_NAME, h as SHARE_BASE_URL, i as Config, j as layerOtlp, k as isMonorepoRoot, l as Linter, m as Reporter, o as Files, p as Project, r as CANONICAL_GITHUB_URL, s as Git, t as cliLogger, u as NodeResolver, v as StagedFiles, w as formatErrorChain, x as discoverReactSubprojects, y as buildJsonReport } from "./cli-logger-BliQX9s8.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { execFileSync, execSync } from "node:child_process";
|
|
5
5
|
import path, { join } from "node:path";
|
|
@@ -6323,6 +6323,12 @@ const colorizeByScore = (text, score) => {
|
|
|
6323
6323
|
//#region src/cli/utils/render-score-header.ts
|
|
6324
6324
|
const SCORE_BAR_ANIMATION_FRAME_COUNT = 40;
|
|
6325
6325
|
const SCORE_BAR_ANIMATION_FRAME_DELAY_MS = 50;
|
|
6326
|
+
const PERFECT_SCORE_RAINBOW_FRAME_COUNT = 16;
|
|
6327
|
+
const PERFECT_SCORE_RAINBOW_FRAME_DELAY_MS = 50;
|
|
6328
|
+
const RAINBOW_HUE_SHIFT_PER_FRAME = 9;
|
|
6329
|
+
const RAINBOW_GRADIENT_WIDTH = 80;
|
|
6330
|
+
const RAINBOW_OKLCH_LIGHTNESS = .638;
|
|
6331
|
+
const RAINBOW_OKLCH_CHROMA = .129;
|
|
6326
6332
|
const easeOutCubic = (progress) => 1 - (1 - progress) ** 3;
|
|
6327
6333
|
const sleep = (milliseconds) => Effect.promise(() => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
6328
6334
|
const buildScoreBarSegments = (filledCount) => {
|
|
@@ -6333,6 +6339,36 @@ const buildScoreBarSegments = (filledCount) => {
|
|
|
6333
6339
|
};
|
|
6334
6340
|
};
|
|
6335
6341
|
const getFilledCount = (score) => Math.round(score / 100 * 50);
|
|
6342
|
+
const joinScoreHeaderFrame = (lines) => `${lines[0]}\n\r${lines[1]}\n\r${lines[2]}\n\r${lines[3]}\n`;
|
|
6343
|
+
const buildRawScoreBar = (displayScore) => {
|
|
6344
|
+
const { filledSegment, emptySegment } = buildScoreBarSegments(getFilledCount(displayScore));
|
|
6345
|
+
return filledSegment + emptySegment;
|
|
6346
|
+
};
|
|
6347
|
+
const buildScoreHeaderLine = (faceLine, rightColumnContent) => {
|
|
6348
|
+
return ` ${faceLine}${rightColumnContent.length > 0 ? " " : ""}${rightColumnContent}`;
|
|
6349
|
+
};
|
|
6350
|
+
const getRightColumnOffset = (faceLine) => ` ${faceLine} `.length;
|
|
6351
|
+
const clampColorChannel = (value) => Math.max(0, Math.min(255, Math.round(value)));
|
|
6352
|
+
const encodeSrgb = (value) => value <= .0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - .055;
|
|
6353
|
+
const oklchToRgb = (lightness, chroma, hue) => {
|
|
6354
|
+
const hueRadians = hue * Math.PI / 180;
|
|
6355
|
+
const labA = chroma * Math.cos(hueRadians);
|
|
6356
|
+
const labB = chroma * Math.sin(hueRadians);
|
|
6357
|
+
const longCone = (lightness + .3963377774 * labA + .2158037573 * labB) ** 3;
|
|
6358
|
+
const mediumCone = (lightness - .1055613458 * labA - .0638541728 * labB) ** 3;
|
|
6359
|
+
const shortCone = (lightness - .0894841775 * labA - 1.291485548 * labB) ** 3;
|
|
6360
|
+
return {
|
|
6361
|
+
red: clampColorChannel(encodeSrgb(4.0767416621 * longCone - 3.3077115913 * mediumCone + .2309699292 * shortCone) * 255),
|
|
6362
|
+
green: clampColorChannel(encodeSrgb(-1.2684380046 * longCone + 2.6097574011 * mediumCone - .3413193965 * shortCone) * 255),
|
|
6363
|
+
blue: clampColorChannel(encodeSrgb(-.0041960863 * longCone - .7034186147 * mediumCone + 1.707614701 * shortCone) * 255)
|
|
6364
|
+
};
|
|
6365
|
+
};
|
|
6366
|
+
const colorizeTrueColor = (text, { red, green, blue }) => `\x1b[38;2;${red};${green};${blue}m${text}\x1b[39m`;
|
|
6367
|
+
const colorizeRainbowText = (text, frame, offset = 0) => [...text].map((character, index) => {
|
|
6368
|
+
if (character === " ") return character;
|
|
6369
|
+
return colorizeTrueColor(character, oklchToRgb(RAINBOW_OKLCH_LIGHTNESS, RAINBOW_OKLCH_CHROMA, ((index + offset) / RAINBOW_GRADIENT_WIDTH * 360 + frame * RAINBOW_HUE_SHIFT_PER_FRAME) % 360));
|
|
6370
|
+
}).join("");
|
|
6371
|
+
const buildRainbowHeaderLine = (faceLine, rightColumnContent, frame) => colorizeRainbowText(buildScoreHeaderLine(faceLine, rightColumnContent), frame);
|
|
6336
6372
|
const buildScoreBar = (displayScore, colorScore = displayScore) => {
|
|
6337
6373
|
const { filledSegment, emptySegment } = buildScoreBarSegments(getFilledCount(displayScore));
|
|
6338
6374
|
return colorizeByScore(filledSegment, colorScore) + highlighter.dim(emptySegment);
|
|
@@ -6343,18 +6379,19 @@ const getDoctorFace = (score) => {
|
|
|
6343
6379
|
return ["x x", " ▽ "];
|
|
6344
6380
|
};
|
|
6345
6381
|
const BRANDING_LINE = `React Doctor ${highlighter.dim("(https://react.doctor)")}`;
|
|
6346
|
-
const
|
|
6382
|
+
const RAW_BRANDING_LINE = "React Doctor (https://react.doctor)";
|
|
6383
|
+
const buildRawFaceLines = (score) => {
|
|
6347
6384
|
const [eyes, mouth] = getDoctorFace(score);
|
|
6348
|
-
const colorize = (text) => colorizeByScore(text, score);
|
|
6349
6385
|
return [
|
|
6350
6386
|
"┌─────┐",
|
|
6351
6387
|
`│ ${eyes} │`,
|
|
6352
6388
|
`│ ${mouth} │`,
|
|
6353
6389
|
"└─────┘"
|
|
6354
|
-
]
|
|
6390
|
+
];
|
|
6355
6391
|
};
|
|
6356
|
-
const
|
|
6357
|
-
|
|
6392
|
+
const buildFaceRenderedLines = (score) => {
|
|
6393
|
+
const colorize = (text) => colorizeByScore(text, score);
|
|
6394
|
+
return buildRawFaceLines(score).map(colorize);
|
|
6358
6395
|
};
|
|
6359
6396
|
const writeScoreHeaderLine = (line) => Effect.sync(() => {
|
|
6360
6397
|
process.stdout.write(line);
|
|
@@ -6365,29 +6402,97 @@ const buildScoreLine = (displayScore, finalScore, label, projectName) => {
|
|
|
6365
6402
|
const projectSuffix = projectName ? ` ${highlighter.dim("·")} ${highlighter.dim(projectName)}` : "";
|
|
6366
6403
|
return `${scoreNumber} ${highlighter.dim(`/ 100`)} ${scoreLabel}${projectSuffix}`;
|
|
6367
6404
|
};
|
|
6405
|
+
const buildRawScoreLine = (displayScore, label, projectName) => {
|
|
6406
|
+
return `${displayScore} / 100 ${label}${projectName ? ` · ${projectName}` : ""}`;
|
|
6407
|
+
};
|
|
6408
|
+
const buildRainbowScoreHeaderFrame = ({ score, displayScore, label, frame, projectName }) => {
|
|
6409
|
+
const rawFaceLines = buildRawFaceLines(score);
|
|
6410
|
+
return joinScoreHeaderFrame([
|
|
6411
|
+
buildRainbowHeaderLine(rawFaceLines[0] ?? "", buildRawScoreLine(displayScore, label, projectName), frame),
|
|
6412
|
+
buildRainbowHeaderLine(rawFaceLines[1] ?? "", buildRawScoreBar(displayScore), frame),
|
|
6413
|
+
buildRainbowHeaderLine(rawFaceLines[2] ?? "", RAW_BRANDING_LINE, frame),
|
|
6414
|
+
buildRainbowHeaderLine(rawFaceLines[3] ?? "", "", frame)
|
|
6415
|
+
]);
|
|
6416
|
+
};
|
|
6417
|
+
const buildFinalPerfectScoreHeaderFrame = (score, label, frame, projectName) => {
|
|
6418
|
+
const rawFaceLines = buildRawFaceLines(score);
|
|
6419
|
+
const renderedFaceLines = buildFaceRenderedLines(score);
|
|
6420
|
+
const rainbowBarLine = colorizeRainbowText(buildRawScoreBar(score), frame, getRightColumnOffset(rawFaceLines[1] ?? ""));
|
|
6421
|
+
return joinScoreHeaderFrame([
|
|
6422
|
+
buildScoreHeaderLine(renderedFaceLines[0] ?? "", buildScoreLine(score, score, label, projectName)),
|
|
6423
|
+
buildScoreHeaderLine(renderedFaceLines[1] ?? "", rainbowBarLine),
|
|
6424
|
+
buildScoreHeaderLine(renderedFaceLines[2] ?? "", BRANDING_LINE),
|
|
6425
|
+
buildScoreHeaderLine(renderedFaceLines[3] ?? "", "")
|
|
6426
|
+
]);
|
|
6427
|
+
};
|
|
6428
|
+
const buildInitialScoreHeaderLine = ({ isPerfectScore, shouldAnimate, lineIndex, renderedFaceLine, rawFaceLine, rightColumnContent, rawRightColumnContent, score }) => {
|
|
6429
|
+
if (!isPerfectScore) return buildScoreHeaderLine(renderedFaceLine, rightColumnContent);
|
|
6430
|
+
if (shouldAnimate) return buildRainbowHeaderLine(rawFaceLine, rawRightColumnContent, 0);
|
|
6431
|
+
if (lineIndex !== 1) return buildScoreHeaderLine(renderedFaceLine, rightColumnContent);
|
|
6432
|
+
return buildScoreHeaderLine(renderedFaceLine, colorizeRainbowText(buildRawScoreBar(score), 0, getRightColumnOffset(rawFaceLine)));
|
|
6433
|
+
};
|
|
6368
6434
|
const printAnimatedScore = (scoreFaceLine, barFaceLine, score, label, projectName) => Effect.gen(function* () {
|
|
6435
|
+
const isPerfectScore = score === 100;
|
|
6369
6436
|
for (let frame = 0; frame <= SCORE_BAR_ANIMATION_FRAME_COUNT; frame += 1) {
|
|
6370
6437
|
const progress = easeOutCubic(frame / SCORE_BAR_ANIMATION_FRAME_COUNT);
|
|
6371
6438
|
const animatedScore = Math.round(score * progress);
|
|
6439
|
+
if (isPerfectScore) {
|
|
6440
|
+
yield* writeScoreHeaderLine(`${frame === 0 ? "" : "\x1B[4A"}\r${buildRainbowScoreHeaderFrame({
|
|
6441
|
+
score,
|
|
6442
|
+
displayScore: animatedScore,
|
|
6443
|
+
label,
|
|
6444
|
+
frame,
|
|
6445
|
+
projectName
|
|
6446
|
+
})}`);
|
|
6447
|
+
if (frame < SCORE_BAR_ANIMATION_FRAME_COUNT) yield* sleep(SCORE_BAR_ANIMATION_FRAME_DELAY_MS);
|
|
6448
|
+
continue;
|
|
6449
|
+
}
|
|
6372
6450
|
const animatedScoreLine = buildScoreLine(animatedScore, score, label, projectName);
|
|
6373
6451
|
const animatedBarLine = buildScoreBar(animatedScore, score);
|
|
6374
6452
|
yield* writeScoreHeaderLine(`${frame === 0 ? "" : "\x1B[2A"}\r${buildScoreHeaderLine(scoreFaceLine, animatedScoreLine)}\n\r${buildScoreHeaderLine(barFaceLine, animatedBarLine)}\n`);
|
|
6375
6453
|
if (frame < SCORE_BAR_ANIMATION_FRAME_COUNT) yield* sleep(SCORE_BAR_ANIMATION_FRAME_DELAY_MS);
|
|
6376
6454
|
}
|
|
6455
|
+
if (!isPerfectScore) return;
|
|
6456
|
+
for (let frame = 0; frame < PERFECT_SCORE_RAINBOW_FRAME_COUNT; frame += 1) {
|
|
6457
|
+
yield* writeScoreHeaderLine(`\x1b[4A\r${buildRainbowScoreHeaderFrame({
|
|
6458
|
+
score,
|
|
6459
|
+
displayScore: score,
|
|
6460
|
+
label,
|
|
6461
|
+
frame,
|
|
6462
|
+
projectName
|
|
6463
|
+
})}`);
|
|
6464
|
+
yield* sleep(PERFECT_SCORE_RAINBOW_FRAME_DELAY_MS);
|
|
6465
|
+
}
|
|
6466
|
+
yield* writeScoreHeaderLine(`\x1b[4A\r${buildFinalPerfectScoreHeaderFrame(score, label, PERFECT_SCORE_RAINBOW_FRAME_COUNT, projectName)}\x1b[2A`);
|
|
6377
6467
|
});
|
|
6378
6468
|
const printScoreHeader = (scoreResult, projectName) => Effect.gen(function* () {
|
|
6469
|
+
const isPerfectScore = scoreResult.score === 100;
|
|
6379
6470
|
const renderedFaceLines = buildFaceRenderedLines(scoreResult.score);
|
|
6471
|
+
const rawFaceLines = buildRawFaceLines(scoreResult.score);
|
|
6380
6472
|
const shouldAnimate = !isSpinnerSilent() && isSpinnerInteractive(process.stdout);
|
|
6473
|
+
const displayScore = shouldAnimate ? 0 : scoreResult.score;
|
|
6381
6474
|
const rightColumnLines = [
|
|
6382
|
-
buildScoreLine(
|
|
6475
|
+
buildScoreLine(displayScore, scoreResult.score, scoreResult.label, projectName),
|
|
6383
6476
|
shouldAnimate ? buildScoreBar(0, scoreResult.score) : buildScoreBar(scoreResult.score),
|
|
6384
6477
|
BRANDING_LINE,
|
|
6385
6478
|
""
|
|
6386
6479
|
];
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6480
|
+
const rawRightColumnLines = [
|
|
6481
|
+
buildRawScoreLine(displayScore, scoreResult.label, projectName),
|
|
6482
|
+
buildRawScoreBar(displayScore),
|
|
6483
|
+
RAW_BRANDING_LINE,
|
|
6484
|
+
""
|
|
6485
|
+
];
|
|
6486
|
+
for (let lineIndex = 0; lineIndex < renderedFaceLines.length; lineIndex += 1) yield* Console.log(buildInitialScoreHeaderLine({
|
|
6487
|
+
isPerfectScore,
|
|
6488
|
+
shouldAnimate,
|
|
6489
|
+
lineIndex,
|
|
6490
|
+
renderedFaceLine: renderedFaceLines[lineIndex] ?? "",
|
|
6491
|
+
rawFaceLine: rawFaceLines[lineIndex] ?? "",
|
|
6492
|
+
rightColumnContent: rightColumnLines[lineIndex] ?? "",
|
|
6493
|
+
rawRightColumnContent: rawRightColumnLines[lineIndex] ?? "",
|
|
6494
|
+
score: scoreResult.score
|
|
6495
|
+
}));
|
|
6391
6496
|
yield* Console.log("");
|
|
6392
6497
|
if (shouldAnimate) {
|
|
6393
6498
|
yield* writeScoreHeaderLine("\x1B[5A");
|
|
@@ -6561,7 +6666,7 @@ const resolveOxlintNodeEffect = (isLintEnabled, isQuiet) => Effect.gen(function*
|
|
|
6561
6666
|
const resolveOxlintNode = (isLintEnabled, isQuiet) => Effect.runPromise(resolveOxlintNodeEffect(isLintEnabled, isQuiet).pipe(Effect.provide(NodeResolver.layerNode)));
|
|
6562
6667
|
//#endregion
|
|
6563
6668
|
//#region src/cli/utils/version.ts
|
|
6564
|
-
const VERSION = "0.2.
|
|
6669
|
+
const VERSION = "0.2.9";
|
|
6565
6670
|
//#endregion
|
|
6566
6671
|
//#region src/inspect.ts
|
|
6567
6672
|
const silentConsole = makeNoopConsole();
|
|
@@ -7362,7 +7467,7 @@ const warnSetupPromptFailure = async (options, error) => {
|
|
|
7362
7467
|
return;
|
|
7363
7468
|
}
|
|
7364
7469
|
try {
|
|
7365
|
-
const { cliLogger } = await import("./cli-logger-
|
|
7470
|
+
const { cliLogger } = await import("./cli-logger-BliQX9s8.js").then((n) => n.n);
|
|
7366
7471
|
cliLogger.warn(message);
|
|
7367
7472
|
} catch {}
|
|
7368
7473
|
};
|
|
@@ -8646,7 +8751,8 @@ const defaultInstallDependencyRunner = (input) => {
|
|
|
8646
8751
|
env: {
|
|
8647
8752
|
...process.env,
|
|
8648
8753
|
REACT_DOCTOR_INSTALL: "1"
|
|
8649
|
-
}
|
|
8754
|
+
},
|
|
8755
|
+
shell: process.platform === "win32"
|
|
8650
8756
|
});
|
|
8651
8757
|
};
|
|
8652
8758
|
const installReactDoctorDependency = async (options) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ interface SurfaceControls {
|
|
|
79
79
|
excludeRules?: string[];
|
|
80
80
|
}
|
|
81
81
|
interface ReactDoctorConfig {
|
|
82
|
+
$schema?: string;
|
|
82
83
|
ignore?: ReactDoctorIgnoreConfig;
|
|
83
84
|
lint?: boolean;
|
|
84
85
|
/**
|
|
@@ -354,8 +355,14 @@ interface DiagnoseResult {
|
|
|
354
355
|
skippedCheckReasons?: Record<string, string>;
|
|
355
356
|
project: ProjectInfo;
|
|
356
357
|
elapsedMilliseconds: number;
|
|
357
|
-
}
|
|
358
|
-
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* A single project to scan as part of a `diagnoseProjects()` batch.
|
|
361
|
+
* Scan options (`deadCode`, `lint`, etc.) are flat on the entry and
|
|
362
|
+
* layer on top of the global defaults — omitted fields fall through.
|
|
363
|
+
* `config` is a full `ReactDoctorConfig` override that replaces the
|
|
364
|
+
* on-disk `react-doctor.config.json` for this project's scan.
|
|
365
|
+
*/
|
|
359
366
|
//#endregion
|
|
360
367
|
//#region src/types/inspect.d.ts
|
|
361
368
|
interface InspectResult {
|
|
@@ -491,13 +498,18 @@ declare class PackageJsonNotFoundError extends Error {
|
|
|
491
498
|
readonly directory: string;
|
|
492
499
|
constructor(directory: string, options?: ErrorOptions);
|
|
493
500
|
}
|
|
501
|
+
declare class NotADirectoryError extends Error {
|
|
502
|
+
readonly name = "NotADirectoryError";
|
|
503
|
+
readonly resolvedPath: string;
|
|
504
|
+
constructor(resolvedPath: string, options?: ErrorOptions);
|
|
505
|
+
}
|
|
494
506
|
declare class AmbiguousProjectError extends Error {
|
|
495
507
|
readonly name = "AmbiguousProjectError";
|
|
496
508
|
readonly directory: string;
|
|
497
509
|
readonly candidates: readonly string[];
|
|
498
510
|
constructor(directory: string, candidates: readonly string[], options?: ErrorOptions);
|
|
499
511
|
}
|
|
500
|
-
declare const isProjectDiscoveryError: (value: unknown) => value is ProjectNotFoundError | NoReactDependencyError | PackageJsonNotFoundError | AmbiguousProjectError; //#endregion
|
|
512
|
+
declare const isProjectDiscoveryError: (value: unknown) => value is ProjectNotFoundError | NoReactDependencyError | PackageJsonNotFoundError | NotADirectoryError | AmbiguousProjectError; //#endregion
|
|
501
513
|
//#region src/project-info/utils/is-directory.d.ts
|
|
502
514
|
//#endregion
|
|
503
515
|
//#region src/errors.d.ts
|
|
@@ -650,7 +662,41 @@ declare const summarizeDiagnostics: (diagnostics: Diagnostic[], worstScore?: num
|
|
|
650
662
|
//#endregion
|
|
651
663
|
//#region ../api/dist/index.d.ts
|
|
652
664
|
//#region src/diagnose.d.ts
|
|
653
|
-
declare const diagnose: (directory: string, options?: DiagnoseOptions) => Promise<DiagnoseResult>;
|
|
665
|
+
declare const diagnose: (directory: string, options?: DiagnoseOptions) => Promise<DiagnoseResult>;
|
|
666
|
+
/**
|
|
667
|
+
* Scan multiple projects in parallel and return per-project scores,
|
|
668
|
+
* diagnostics, and an aggregate score (worst-of across all projects).
|
|
669
|
+
*
|
|
670
|
+
* Each project runs its own independent `runInspect` pipeline — the
|
|
671
|
+
* same pipeline `diagnose()` uses — so per-project config overrides,
|
|
672
|
+
* dead-code analysis, and scoring all work identically to a single
|
|
673
|
+
* `diagnose()` call.
|
|
674
|
+
*
|
|
675
|
+
* Projects that fail (e.g. missing `package.json`, no React dependency)
|
|
676
|
+
* are included in the result with `ok: false` rather than aborting the
|
|
677
|
+
* entire batch, so callers always receive partial results.
|
|
678
|
+
*
|
|
679
|
+
* ```ts
|
|
680
|
+
* const result = await diagnoseProjects({
|
|
681
|
+
* projects: [
|
|
682
|
+
* { directory: "packages/app" },
|
|
683
|
+
* { directory: "packages/shared", deadCode: false },
|
|
684
|
+
* { directory: "packages/admin", config: {
|
|
685
|
+
* rules: { "react-doctor/no-array-index-as-key": "off" },
|
|
686
|
+
* }},
|
|
687
|
+
* ],
|
|
688
|
+
* concurrency: 4,
|
|
689
|
+
* });
|
|
690
|
+
*
|
|
691
|
+
* for (const project of result.projects) {
|
|
692
|
+
* if (project.ok) {
|
|
693
|
+
* console.log(project.directory, project.score);
|
|
694
|
+
* } else {
|
|
695
|
+
* console.error(project.directory, project.error);
|
|
696
|
+
* }
|
|
697
|
+
* }
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
654
700
|
//#endregion
|
|
655
701
|
//#region src/index.d.ts
|
|
656
702
|
declare const clearCaches: () => void;
|
|
@@ -661,5 +707,5 @@ interface ToJsonReportOptions {
|
|
|
661
707
|
}
|
|
662
708
|
declare const toJsonReport: (result: DiagnoseResult, options: ToJsonReportOptions) => JsonReport;
|
|
663
709
|
//#endregion
|
|
664
|
-
export { AmbiguousProjectError, type DiagnoseOptions, type DiagnoseResult, type Diagnostic, type DiffInfo, type JsonReport, type JsonReportDiffInfo, type JsonReportError, type JsonReportMode, type JsonReportProjectEntry, type JsonReportSummary, NoReactDependencyError, PackageJsonNotFoundError, type ProjectInfo, ProjectNotFoundError, type ReactDoctorConfig, ReactDoctorError, type ScoreResult, buildJsonReport, buildJsonReportError, clearCaches, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
710
|
+
export { AmbiguousProjectError, type DiagnoseOptions, type DiagnoseResult, type Diagnostic, type DiffInfo, type JsonReport, type JsonReportDiffInfo, type JsonReportError, type JsonReportMode, type JsonReportProjectEntry, type JsonReportSummary, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, type ProjectInfo, ProjectNotFoundError, type ReactDoctorConfig, ReactDoctorError, type ScoreResult, buildJsonReport, buildJsonReportError, clearCaches, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
665
711
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -2067,6 +2067,14 @@ var PackageJsonNotFoundError = class extends Error {
|
|
|
2067
2067
|
this.directory = directory;
|
|
2068
2068
|
}
|
|
2069
2069
|
};
|
|
2070
|
+
var NotADirectoryError = class extends Error {
|
|
2071
|
+
name = "NotADirectoryError";
|
|
2072
|
+
resolvedPath;
|
|
2073
|
+
constructor(resolvedPath, options) {
|
|
2074
|
+
super(`Resolved scan target "${resolvedPath}" is not a directory. Ensure the path exists and points to a project directory, not a file.`, options);
|
|
2075
|
+
this.resolvedPath = resolvedPath;
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2070
2078
|
var AmbiguousProjectError = class extends Error {
|
|
2071
2079
|
name = "AmbiguousProjectError";
|
|
2072
2080
|
directory;
|
|
@@ -2077,7 +2085,7 @@ var AmbiguousProjectError = class extends Error {
|
|
|
2077
2085
|
this.candidates = candidates;
|
|
2078
2086
|
}
|
|
2079
2087
|
};
|
|
2080
|
-
const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof AmbiguousProjectError;
|
|
2088
|
+
const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
|
|
2081
2089
|
const isFile = (filePath) => {
|
|
2082
2090
|
try {
|
|
2083
2091
|
return fs.statSync(filePath).isFile();
|
|
@@ -4160,8 +4168,10 @@ const resolveScanTarget = (requestedDirectory) => {
|
|
|
4160
4168
|
const configSourceDirectory = loadedConfig?.sourceDirectory ?? null;
|
|
4161
4169
|
const redirectedDirectory = resolveConfigRootDir(userConfig, configSourceDirectory);
|
|
4162
4170
|
const directoryAfterRedirect = redirectedDirectory ?? absoluteRequested;
|
|
4171
|
+
const resolvedDirectory = resolveDiagnoseTarget(directoryAfterRedirect) ?? directoryAfterRedirect;
|
|
4172
|
+
if (!isDirectory(resolvedDirectory)) throw existsSync(resolvedDirectory) ? new NotADirectoryError(resolvedDirectory) : new ProjectNotFoundError(resolvedDirectory);
|
|
4163
4173
|
return {
|
|
4164
|
-
resolvedDirectory
|
|
4174
|
+
resolvedDirectory,
|
|
4165
4175
|
requestedDirectory: absoluteRequested,
|
|
4166
4176
|
userConfig,
|
|
4167
4177
|
configSourceDirectory,
|
|
@@ -4645,7 +4655,7 @@ var Files = class Files extends Context.Service()("react-doctor/Files") {
|
|
|
4645
4655
|
* pattern in react-doctor-evals' test layers.
|
|
4646
4656
|
*/
|
|
4647
4657
|
static layerInMemory = (tree) => {
|
|
4648
|
-
const resolveAbsolute = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath :
|
|
4658
|
+
const resolveAbsolute = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath : `${rootDirectory}/${filePath}`;
|
|
4649
4659
|
return Layer.succeed(Files, Files.of({
|
|
4650
4660
|
readLines: (input) => Effect.sync(() => {
|
|
4651
4661
|
const absolute = resolveAbsolute(input.filePath, input.rootDirectory);
|
|
@@ -4653,17 +4663,17 @@ var Files = class Files extends Context.Service()("react-doctor/Files") {
|
|
|
4653
4663
|
return content === void 0 ? null : content.split("\n");
|
|
4654
4664
|
}),
|
|
4655
4665
|
listSourceFiles: (rootDirectory) => Effect.sync(() => {
|
|
4656
|
-
const prefix = rootDirectory.endsWith(
|
|
4666
|
+
const prefix = rootDirectory.endsWith("/") ? rootDirectory : `${rootDirectory}/`;
|
|
4657
4667
|
const files = [];
|
|
4658
4668
|
for (const absolute of tree.keys()) {
|
|
4659
4669
|
if (!absolute.startsWith(prefix)) continue;
|
|
4660
|
-
files.push(absolute.slice(prefix.length)
|
|
4670
|
+
files.push(absolute.slice(prefix.length));
|
|
4661
4671
|
}
|
|
4662
4672
|
return files;
|
|
4663
4673
|
}),
|
|
4664
4674
|
isFile: (filePath) => Effect.sync(() => tree.has(filePath)),
|
|
4665
4675
|
isDirectory: (filePath) => Effect.sync(() => {
|
|
4666
|
-
const prefix = filePath.endsWith(
|
|
4676
|
+
const prefix = filePath.endsWith("/") ? filePath : `${filePath}/`;
|
|
4667
4677
|
for (const absolute of tree.keys()) if (absolute.startsWith(prefix)) return true;
|
|
4668
4678
|
return false;
|
|
4669
4679
|
})
|
|
@@ -5800,7 +5810,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
5800
5810
|
const primaryLabel = diagnostic.labels[0];
|
|
5801
5811
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
5802
5812
|
return {
|
|
5803
|
-
filePath: diagnostic.filename,
|
|
5813
|
+
filePath: diagnostic.filename.replaceAll("\\", "/"),
|
|
5804
5814
|
plugin,
|
|
5805
5815
|
rule,
|
|
5806
5816
|
severity: diagnostic.severity,
|
|
@@ -6602,22 +6612,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6602
6612
|
"inspect.isCi": input.isCi,
|
|
6603
6613
|
"inspect.scoreSurface": input.scoreSurface ?? "score"
|
|
6604
6614
|
} }));
|
|
6605
|
-
|
|
6606
|
-
* Default layer stack for the production CLI / programmatic API:
|
|
6607
|
-
* real Node-side services for Project / Config / Files / Git / Linter /
|
|
6608
|
-
* DeadCode; HTTP for Score; noop Progress (the CLI overrides with
|
|
6609
|
-
* `Progress.layerOra(...)` for terminal feedback); the silent Reporter
|
|
6610
|
-
* (the orchestrator already returns the diagnostic array via
|
|
6611
|
-
* `Stream.runCollect`).
|
|
6612
|
-
*
|
|
6613
|
-
* Callers tweak by replacing individual layers: `--no-score` swaps
|
|
6614
|
-
* `Score.layerHttp` for `Score.layerOf(null)`; `--no-lint` swaps
|
|
6615
|
-
* `Linter.layerOxlint` for `Linter.layerOf([])`; `--no-dead-code`
|
|
6616
|
-
* swaps `DeadCode.layerNode` for `DeadCode.layerOf([])`; a caller
|
|
6617
|
-
* with a pre-loaded config swaps `Config.layerNode` for
|
|
6618
|
-
* `Config.layerOf(resolved)`.
|
|
6619
|
-
*/
|
|
6620
|
-
const layerInspectLive = Layer.mergeAll(Project.layerNode, Config.layerNode, DeadCode.layerNode, Files.layerNode, Git.layerNode, Linter.layerOxlint, LintPartialFailures.layerLive, Progress.layerNoop, Reporter.layerNoop, Score.layerHttp);
|
|
6615
|
+
Layer.mergeAll(Project.layerNode, Config.layerNode, DeadCode.layerNode, Files.layerNode, Git.layerNode, Linter.layerOxlint, LintPartialFailures.layerLive, Progress.layerNoop, Reporter.layerNoop, Score.layerHttp);
|
|
6621
6616
|
const parseNodeVersion = (versionString) => {
|
|
6622
6617
|
const [major = 0, minor = 0, patch = 0] = versionString.replace(/^v/, "").trim().split(".").map(Number);
|
|
6623
6618
|
return {
|
|
@@ -7021,22 +7016,23 @@ import_picocolors.default.red, import_picocolors.default.yellow, import_picocolo
|
|
|
7021
7016
|
const clearAutoSuppressionCaches = () => {};
|
|
7022
7017
|
//#endregion
|
|
7023
7018
|
//#region ../api/dist/index.js
|
|
7024
|
-
const
|
|
7025
|
-
|
|
7026
|
-
const
|
|
7019
|
+
const DEFAULT_LAYER = Layer.mergeAll(Project.layerNode, Config.layerNode, DeadCode.layerNode, Files.layerNode, Git.layerNode, Linter.layerOxlint, LintPartialFailures.layerLive, Progress.layerNoop, Reporter.layerNoop, Score.layerHttp);
|
|
7020
|
+
const buildInspectProgram = (scanTarget, options, configOverride) => {
|
|
7021
|
+
const effectiveConfig = configOverride ?? scanTarget.userConfig;
|
|
7027
7022
|
const includePaths = options.includePaths ?? [];
|
|
7028
|
-
|
|
7023
|
+
return runInspect({
|
|
7029
7024
|
directory: scanTarget.resolvedDirectory,
|
|
7030
7025
|
includePaths,
|
|
7031
|
-
customRulesOnly:
|
|
7032
|
-
respectInlineDisables: options.respectInlineDisables ??
|
|
7033
|
-
adoptExistingLintConfig:
|
|
7034
|
-
ignoredTags: new Set(
|
|
7035
|
-
runDeadCode: options.deadCode ??
|
|
7026
|
+
customRulesOnly: effectiveConfig?.customRulesOnly ?? false,
|
|
7027
|
+
respectInlineDisables: options.respectInlineDisables ?? effectiveConfig?.respectInlineDisables ?? true,
|
|
7028
|
+
adoptExistingLintConfig: effectiveConfig?.adoptExistingLintConfig ?? true,
|
|
7029
|
+
ignoredTags: new Set(effectiveConfig?.ignore?.tags ?? []),
|
|
7030
|
+
runDeadCode: options.deadCode ?? effectiveConfig?.deadCode ?? true,
|
|
7036
7031
|
isCi: false,
|
|
7037
7032
|
resolveLocalGithubViewerPermission: true
|
|
7038
7033
|
});
|
|
7039
|
-
|
|
7034
|
+
};
|
|
7035
|
+
const outputToDiagnoseResult = (output, elapsedMilliseconds) => {
|
|
7040
7036
|
if (output.didLintFail && output.lintFailureReason !== null) console.error("Lint failed:", output.lintFailureReason);
|
|
7041
7037
|
const skippedChecks = [];
|
|
7042
7038
|
const skippedCheckReasons = {};
|
|
@@ -7050,9 +7046,14 @@ const diagnose = async (directory, options = {}) => {
|
|
|
7050
7046
|
skippedChecks,
|
|
7051
7047
|
...Object.keys(skippedCheckReasons).length > 0 ? { skippedCheckReasons } : {},
|
|
7052
7048
|
project: output.project,
|
|
7053
|
-
elapsedMilliseconds
|
|
7049
|
+
elapsedMilliseconds
|
|
7054
7050
|
};
|
|
7055
7051
|
};
|
|
7052
|
+
const diagnose = async (directory, options = {}) => {
|
|
7053
|
+
const startTime = globalThis.performance.now();
|
|
7054
|
+
const program = buildInspectProgram(resolveScanTarget(directory), options);
|
|
7055
|
+
return outputToDiagnoseResult(await Effect.runPromise(restoreLegacyThrow(program.pipe(Effect.provide(DEFAULT_LAYER), Effect.provide(layerOtlp)))), globalThis.performance.now() - startTime);
|
|
7056
|
+
};
|
|
7056
7057
|
//#endregion
|
|
7057
7058
|
//#region src/index.ts
|
|
7058
7059
|
const clearCaches = () => {
|
|
@@ -7081,6 +7082,6 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
7081
7082
|
totalElapsedMilliseconds: result.elapsedMilliseconds
|
|
7082
7083
|
});
|
|
7083
7084
|
//#endregion
|
|
7084
|
-
export { AmbiguousProjectError, NoReactDependencyError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
7085
|
+
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
7085
7086
|
|
|
7086
7087
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -58,21 +58,21 @@
|
|
|
58
58
|
"oxlint": "^1.66.0",
|
|
59
59
|
"prompts": "^2.4.2",
|
|
60
60
|
"typescript": ">=5.0.4 <7",
|
|
61
|
-
"oxlint-plugin-react-doctor": "0.2.
|
|
61
|
+
"oxlint-plugin-react-doctor": "0.2.9"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/prompts": "^2.4.9",
|
|
65
65
|
"commander": "^14.0.3",
|
|
66
66
|
"ora": "^9.4.0",
|
|
67
|
-
"@react-doctor/
|
|
68
|
-
"@react-doctor/
|
|
67
|
+
"@react-doctor/api": "0.2.9",
|
|
68
|
+
"@react-doctor/core": "0.2.9"
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|
|
71
71
|
"node": "^20.19.0 || >=22.12.0"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"dev": "vp pack --watch",
|
|
75
|
-
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && NODE_ENV=production vp pack",
|
|
75
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && cross-env NODE_ENV=production vp pack",
|
|
76
76
|
"typecheck": "tsc --noEmit",
|
|
77
77
|
"test": "vp test run"
|
|
78
78
|
}
|