react-doctor 0.0.41 → 0.0.42
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/{diagnose-browser-B17IqMa3.d.ts → browser-DFbjNpPb.d.ts} +15 -15
- package/dist/browser-DFbjNpPb.d.ts.map +1 -0
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +2 -3
- package/dist/cli.js +95 -107
- package/dist/cli.js.map +1 -1
- package/dist/index.js +71 -65
- package/dist/index.js.map +1 -1
- package/dist/{process-browser-diagnostics-DpaZeYLI.js → process-browser-diagnostics-BHiLPUJT.js} +5 -25
- package/dist/process-browser-diagnostics-BHiLPUJT.js.map +1 -0
- package/dist/react-doctor-plugin.js +25 -63
- package/dist/react-doctor-plugin.js.map +1 -1
- package/dist/worker.d.ts +1 -1
- package/dist/worker.js +2 -3
- package/package.json +1 -1
- package/dist/diagnose-browser-B17IqMa3.d.ts.map +0 -1
- package/dist/process-browser-diagnostics-DpaZeYLI.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,6 @@ import { performance } from "node:perf_hooks";
|
|
|
13
13
|
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
14
14
|
import { main } from "knip";
|
|
15
15
|
import { createOptions } from "knip/session";
|
|
16
|
-
|
|
17
16
|
//#region src/utils/detect-agents.ts
|
|
18
17
|
const AGENTS_SKILL_DIR = ".agents/skills";
|
|
19
18
|
const SUPPORTED_AGENTS = {
|
|
@@ -75,7 +74,6 @@ const isCommandAvailable = (command) => {
|
|
|
75
74
|
const detectAvailableAgents = () => ALL_SUPPORTED_AGENTS.filter((agent) => SUPPORTED_AGENTS[agent].binaries.some(isCommandAvailable));
|
|
76
75
|
const toDisplayName = (agent) => SUPPORTED_AGENTS[agent].displayName;
|
|
77
76
|
const toSkillDir = (agent) => SUPPORTED_AGENTS[agent].skillDir;
|
|
78
|
-
|
|
79
77
|
//#endregion
|
|
80
78
|
//#region src/utils/highlighter.ts
|
|
81
79
|
const highlighter = {
|
|
@@ -85,7 +83,6 @@ const highlighter = {
|
|
|
85
83
|
success: pc.green,
|
|
86
84
|
dim: pc.dim
|
|
87
85
|
};
|
|
88
|
-
|
|
89
86
|
//#endregion
|
|
90
87
|
//#region src/utils/install-skill-for-agent.ts
|
|
91
88
|
const installSkillForAgent = (projectRoot, agent, skillSourceDirectory, skillName, alreadyInstalledDirectories) => {
|
|
@@ -99,7 +96,6 @@ const installSkillForAgent = (projectRoot, agent, skillSourceDirectory, skillNam
|
|
|
99
96
|
cpSync(skillSourceDirectory, installedSkillDirectory, { recursive: true });
|
|
100
97
|
return installedSkillDirectory;
|
|
101
98
|
};
|
|
102
|
-
|
|
103
99
|
//#endregion
|
|
104
100
|
//#region src/utils/logger.ts
|
|
105
101
|
const logger = {
|
|
@@ -125,7 +121,6 @@ const logger = {
|
|
|
125
121
|
console.log("");
|
|
126
122
|
}
|
|
127
123
|
};
|
|
128
|
-
|
|
129
124
|
//#endregion
|
|
130
125
|
//#region src/utils/should-auto-select-current-choice.ts
|
|
131
126
|
const shouldAutoSelectCurrentChoice = (choiceStates, cursor) => {
|
|
@@ -133,13 +128,11 @@ const shouldAutoSelectCurrentChoice = (choiceStates, cursor) => {
|
|
|
133
128
|
const currentChoice = choiceStates[cursor];
|
|
134
129
|
return Boolean(currentChoice) && !currentChoice.disabled;
|
|
135
130
|
};
|
|
136
|
-
|
|
137
131
|
//#endregion
|
|
138
132
|
//#region src/utils/should-select-all-choices.ts
|
|
139
133
|
const shouldSelectAllChoices = (choiceStates) => {
|
|
140
134
|
return choiceStates.filter((choiceState) => !choiceState.disabled).some((choiceState) => choiceState.selected !== true);
|
|
141
135
|
};
|
|
142
|
-
|
|
143
136
|
//#endregion
|
|
144
137
|
//#region src/utils/prompts.ts
|
|
145
138
|
const require = createRequire(import.meta.url);
|
|
@@ -205,7 +198,6 @@ const prompts = (questions) => {
|
|
|
205
198
|
patchSelectBanner();
|
|
206
199
|
return basePrompts(questions, { onCancel });
|
|
207
200
|
};
|
|
208
|
-
|
|
209
201
|
//#endregion
|
|
210
202
|
//#region src/utils/spinner.ts
|
|
211
203
|
let sharedInstance = null;
|
|
@@ -236,7 +228,6 @@ const spinner = (text) => ({ start() {
|
|
|
236
228
|
fail: (displayText) => finalize("fail", text, displayText)
|
|
237
229
|
};
|
|
238
230
|
} });
|
|
239
|
-
|
|
240
231
|
//#endregion
|
|
241
232
|
//#region src/install-skill.ts
|
|
242
233
|
const SKILL_NAME = "react-doctor";
|
|
@@ -280,32 +271,30 @@ const runInstallSkill = async (options = {}) => {
|
|
|
280
271
|
}
|
|
281
272
|
installSpinner.succeed(`${SKILL_NAME} skill installed for ${selectedAgents.map(toDisplayName).join(", ")}.`);
|
|
282
273
|
};
|
|
283
|
-
|
|
284
274
|
//#endregion
|
|
285
275
|
//#region src/constants.ts
|
|
286
276
|
const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?)$/;
|
|
287
277
|
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
288
278
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
289
|
-
const ERROR_PREVIEW_LENGTH_CHARS = 200;
|
|
290
|
-
const PERFECT_SCORE = 100;
|
|
291
|
-
const SCORE_GOOD_THRESHOLD = 75;
|
|
292
|
-
const SCORE_OK_THRESHOLD = 50;
|
|
293
|
-
const SCORE_BAR_WIDTH_CHARS = 50;
|
|
294
|
-
const SUMMARY_BOX_HORIZONTAL_PADDING_CHARS = 1;
|
|
295
|
-
const SUMMARY_BOX_OUTER_INDENT_CHARS = 2;
|
|
296
279
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
297
280
|
const SHARE_BASE_URL = "https://www.react.doctor/share";
|
|
298
281
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
299
282
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
300
|
-
const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
|
|
301
|
-
const OXLINT_MAX_FILES_PER_BATCH = 500;
|
|
302
283
|
const OFFLINE_MESSAGE = "Score calculated locally (offline mode).";
|
|
303
284
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
304
285
|
const ERROR_RULE_PENALTY = 1.5;
|
|
305
286
|
const WARNING_RULE_PENALTY = .75;
|
|
306
|
-
const
|
|
287
|
+
const KNIP_CONFIG_LOCATIONS = [
|
|
288
|
+
"knip.json",
|
|
289
|
+
"knip.jsonc",
|
|
290
|
+
".knip.json",
|
|
291
|
+
".knip.jsonc",
|
|
292
|
+
"knip.ts",
|
|
293
|
+
"knip.js",
|
|
294
|
+
"knip.config.ts",
|
|
295
|
+
"knip.config.js"
|
|
296
|
+
];
|
|
307
297
|
const OXLINT_NODE_REQUIREMENT = "^20.19.0 || >=22.12.0";
|
|
308
|
-
const OXLINT_RECOMMENDED_NODE_MAJOR = 24;
|
|
309
298
|
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
310
299
|
const IGNORED_DIRECTORIES = new Set([
|
|
311
300
|
"node_modules",
|
|
@@ -313,12 +302,11 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
313
302
|
"build",
|
|
314
303
|
"coverage"
|
|
315
304
|
]);
|
|
316
|
-
|
|
317
305
|
//#endregion
|
|
318
306
|
//#region src/core/calculate-score-locally.ts
|
|
319
307
|
const getScoreLabel = (score) => {
|
|
320
|
-
if (score >=
|
|
321
|
-
if (score >=
|
|
308
|
+
if (score >= 75) return "Great";
|
|
309
|
+
if (score >= 50) return "Needs work";
|
|
322
310
|
return "Critical";
|
|
323
311
|
};
|
|
324
312
|
const countUniqueRules = (diagnostics) => {
|
|
@@ -336,7 +324,7 @@ const countUniqueRules = (diagnostics) => {
|
|
|
336
324
|
};
|
|
337
325
|
const scoreFromRuleCounts = (errorRuleCount, warningRuleCount) => {
|
|
338
326
|
const penalty = errorRuleCount * ERROR_RULE_PENALTY + warningRuleCount * WARNING_RULE_PENALTY;
|
|
339
|
-
return Math.max(0, Math.round(
|
|
327
|
+
return Math.max(0, Math.round(100 - penalty));
|
|
340
328
|
};
|
|
341
329
|
const calculateScoreLocally = (diagnostics) => {
|
|
342
330
|
const { errorRuleCount, warningRuleCount } = countUniqueRules(diagnostics);
|
|
@@ -346,7 +334,6 @@ const calculateScoreLocally = (diagnostics) => {
|
|
|
346
334
|
label: getScoreLabel(score)
|
|
347
335
|
};
|
|
348
336
|
};
|
|
349
|
-
|
|
350
337
|
//#endregion
|
|
351
338
|
//#region src/core/try-score-from-api.ts
|
|
352
339
|
const parseScoreResult = (value) => {
|
|
@@ -378,7 +365,6 @@ const tryScoreFromApi = async (diagnostics, fetchImplementation) => {
|
|
|
378
365
|
clearTimeout(timeoutId);
|
|
379
366
|
}
|
|
380
367
|
};
|
|
381
|
-
|
|
382
368
|
//#endregion
|
|
383
369
|
//#region src/utils/proxy-fetch.ts
|
|
384
370
|
const getGlobalProcess = () => {
|
|
@@ -421,7 +407,6 @@ const proxyFetch = async (url, init) => {
|
|
|
421
407
|
clearTimeout(timeoutId);
|
|
422
408
|
}
|
|
423
409
|
};
|
|
424
|
-
|
|
425
410
|
//#endregion
|
|
426
411
|
//#region src/utils/calculate-score-node.ts
|
|
427
412
|
const calculateScore = async (diagnostics) => {
|
|
@@ -429,19 +414,16 @@ const calculateScore = async (diagnostics) => {
|
|
|
429
414
|
if (apiScore) return apiScore;
|
|
430
415
|
return calculateScoreLocally(diagnostics);
|
|
431
416
|
};
|
|
432
|
-
|
|
433
417
|
//#endregion
|
|
434
418
|
//#region src/utils/colorize-by-score.ts
|
|
435
419
|
const colorizeByScore = (text, score) => {
|
|
436
|
-
if (score >=
|
|
437
|
-
if (score >=
|
|
420
|
+
if (score >= 75) return highlighter.success(text);
|
|
421
|
+
if (score >= 50) return highlighter.warn(text);
|
|
438
422
|
return highlighter.error(text);
|
|
439
423
|
};
|
|
440
|
-
|
|
441
424
|
//#endregion
|
|
442
425
|
//#region src/plugin/constants.ts
|
|
443
426
|
const MOTION_LIBRARY_PACKAGES = new Set(["framer-motion", "motion"]);
|
|
444
|
-
|
|
445
427
|
//#endregion
|
|
446
428
|
//#region src/utils/is-file.ts
|
|
447
429
|
const isFile = (filePath) => {
|
|
@@ -451,7 +433,6 @@ const isFile = (filePath) => {
|
|
|
451
433
|
return false;
|
|
452
434
|
}
|
|
453
435
|
};
|
|
454
|
-
|
|
455
436
|
//#endregion
|
|
456
437
|
//#region src/utils/read-package-json.ts
|
|
457
438
|
const readPackageJson = (packageJsonPath) => {
|
|
@@ -466,7 +447,6 @@ const readPackageJson = (packageJsonPath) => {
|
|
|
466
447
|
throw error;
|
|
467
448
|
}
|
|
468
449
|
};
|
|
469
|
-
|
|
470
450
|
//#endregion
|
|
471
451
|
//#region src/utils/check-reduced-motion.ts
|
|
472
452
|
const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
|
|
@@ -508,7 +488,6 @@ const checkReducedMotion = (rootDirectory) => {
|
|
|
508
488
|
return [MISSING_REDUCED_MOTION_DIAGNOSTIC];
|
|
509
489
|
}
|
|
510
490
|
};
|
|
511
|
-
|
|
512
491
|
//#endregion
|
|
513
492
|
//#region src/utils/read-file-lines-node.ts
|
|
514
493
|
const createNodeReadFileLinesSync = (rootDirectory) => {
|
|
@@ -521,7 +500,6 @@ const createNodeReadFileLinesSync = (rootDirectory) => {
|
|
|
521
500
|
}
|
|
522
501
|
};
|
|
523
502
|
};
|
|
524
|
-
|
|
525
503
|
//#endregion
|
|
526
504
|
//#region src/utils/match-glob-pattern.ts
|
|
527
505
|
const REGEX_SPECIAL_CHARACTERS = /[.+^${}()|[\]\\]/g;
|
|
@@ -549,7 +527,6 @@ const compileGlobPattern = (pattern) => {
|
|
|
549
527
|
regexSource += "$";
|
|
550
528
|
return new RegExp(regexSource);
|
|
551
529
|
};
|
|
552
|
-
|
|
553
530
|
//#endregion
|
|
554
531
|
//#region src/utils/is-ignored-file.ts
|
|
555
532
|
const toRelativePath = (filePath, rootDirectory) => {
|
|
@@ -564,7 +541,6 @@ const isFileIgnoredByPatterns = (filePath, rootDirectory, patterns) => {
|
|
|
564
541
|
const relativePath = toRelativePath(filePath, rootDirectory);
|
|
565
542
|
return patterns.some((pattern) => pattern.test(relativePath));
|
|
566
543
|
};
|
|
567
|
-
|
|
568
544
|
//#endregion
|
|
569
545
|
//#region src/utils/filter-diagnostics.ts
|
|
570
546
|
const resolveCandidateReadPath = (rootDirectory, filePath) => {
|
|
@@ -638,17 +614,14 @@ const filterInlineSuppressions = (diagnostics, rootDirectory, readFileLinesSync)
|
|
|
638
614
|
return true;
|
|
639
615
|
});
|
|
640
616
|
};
|
|
641
|
-
|
|
642
617
|
//#endregion
|
|
643
618
|
//#region src/utils/merge-and-filter-diagnostics.ts
|
|
644
619
|
const mergeAndFilterDiagnostics = (mergedDiagnostics, directory, userConfig, readFileLinesSync) => {
|
|
645
620
|
return filterInlineSuppressions(userConfig ? filterIgnoredDiagnostics(mergedDiagnostics, userConfig, directory, readFileLinesSync) : mergedDiagnostics, directory, readFileLinesSync);
|
|
646
621
|
};
|
|
647
|
-
|
|
648
622
|
//#endregion
|
|
649
623
|
//#region src/utils/jsx-include-paths.ts
|
|
650
624
|
const computeJsxIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath)) : void 0;
|
|
651
|
-
|
|
652
625
|
//#endregion
|
|
653
626
|
//#region src/utils/combine-diagnostics.ts
|
|
654
627
|
const combineDiagnostics = (lintDiagnostics, deadCodeDiagnostics, directory, isDiffMode, userConfig, readFileLinesSync = createNodeReadFileLinesSync(directory), includeEnvironmentChecks = true) => {
|
|
@@ -659,7 +632,6 @@ const combineDiagnostics = (lintDiagnostics, deadCodeDiagnostics, directory, isD
|
|
|
659
632
|
...extraDiagnostics
|
|
660
633
|
], directory, userConfig, readFileLinesSync);
|
|
661
634
|
};
|
|
662
|
-
|
|
663
635
|
//#endregion
|
|
664
636
|
//#region src/utils/find-monorepo-root.ts
|
|
665
637
|
const isMonorepoRoot = (directory) => {
|
|
@@ -678,11 +650,9 @@ const findMonorepoRoot = (startDirectory) => {
|
|
|
678
650
|
}
|
|
679
651
|
return null;
|
|
680
652
|
};
|
|
681
|
-
|
|
682
653
|
//#endregion
|
|
683
654
|
//#region src/utils/is-plain-object.ts
|
|
684
655
|
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
685
|
-
|
|
686
656
|
//#endregion
|
|
687
657
|
//#region src/utils/discover-project.ts
|
|
688
658
|
const REACT_COMPILER_PACKAGES = new Set([
|
|
@@ -1103,7 +1073,22 @@ const discoverProject = (directory) => {
|
|
|
1103
1073
|
sourceFileCount
|
|
1104
1074
|
};
|
|
1105
1075
|
};
|
|
1106
|
-
|
|
1076
|
+
//#endregion
|
|
1077
|
+
//#region src/utils/format-error-chain.ts
|
|
1078
|
+
const collectErrorChain = (rootError) => {
|
|
1079
|
+
const errorChain = [];
|
|
1080
|
+
const visitedErrors = /* @__PURE__ */ new Set();
|
|
1081
|
+
let currentError = rootError;
|
|
1082
|
+
while (currentError !== void 0 && !visitedErrors.has(currentError)) {
|
|
1083
|
+
visitedErrors.add(currentError);
|
|
1084
|
+
errorChain.push(currentError);
|
|
1085
|
+
currentError = currentError instanceof Error ? currentError.cause : void 0;
|
|
1086
|
+
}
|
|
1087
|
+
return errorChain;
|
|
1088
|
+
};
|
|
1089
|
+
const formatErrorMessage = (error) => error instanceof Error ? error.message || error.name : String(error);
|
|
1090
|
+
const formatErrorChain = (rootError) => collectErrorChain(rootError).map(formatErrorMessage).join(" → ");
|
|
1091
|
+
const getErrorChainMessages = (rootError) => collectErrorChain(rootError).map(formatErrorMessage);
|
|
1107
1092
|
//#endregion
|
|
1108
1093
|
//#region src/utils/framed-box.ts
|
|
1109
1094
|
const createFramedLine = (plainText, renderedText = plainText) => ({
|
|
@@ -1113,10 +1098,10 @@ const createFramedLine = (plainText, renderedText = plainText) => ({
|
|
|
1113
1098
|
const renderFramedBoxString = (framedLines) => {
|
|
1114
1099
|
if (framedLines.length === 0) return "";
|
|
1115
1100
|
const borderColorizer = highlighter.dim;
|
|
1116
|
-
const outerIndent = " ".repeat(
|
|
1117
|
-
const horizontalPadding = " ".repeat(
|
|
1101
|
+
const outerIndent = " ".repeat(2);
|
|
1102
|
+
const horizontalPadding = " ".repeat(1);
|
|
1118
1103
|
const maximumLineLength = Math.max(...framedLines.map((framedLine) => framedLine.plainText.length));
|
|
1119
|
-
const borderLine = "─".repeat(maximumLineLength +
|
|
1104
|
+
const borderLine = "─".repeat(maximumLineLength + 2);
|
|
1120
1105
|
const lines = [];
|
|
1121
1106
|
lines.push(`${outerIndent}${borderColorizer(`┌${borderLine}┐`)}`);
|
|
1122
1107
|
for (const framedLine of framedLines) {
|
|
@@ -1130,7 +1115,6 @@ const printFramedBox = (framedLines) => {
|
|
|
1130
1115
|
const rendered = renderFramedBoxString(framedLines);
|
|
1131
1116
|
if (rendered) logger.log(rendered);
|
|
1132
1117
|
};
|
|
1133
|
-
|
|
1134
1118
|
//#endregion
|
|
1135
1119
|
//#region src/utils/group-by.ts
|
|
1136
1120
|
const groupBy = (items, keyFn) => {
|
|
@@ -1143,11 +1127,9 @@ const groupBy = (items, keyFn) => {
|
|
|
1143
1127
|
}
|
|
1144
1128
|
return groups;
|
|
1145
1129
|
};
|
|
1146
|
-
|
|
1147
1130
|
//#endregion
|
|
1148
1131
|
//#region src/utils/indent-multiline-text.ts
|
|
1149
1132
|
const indentMultilineText = (text, linePrefix) => text.split("\n").map((lineText) => `${linePrefix}${lineText}`).join("\n");
|
|
1150
|
-
|
|
1151
1133
|
//#endregion
|
|
1152
1134
|
//#region src/utils/load-config.ts
|
|
1153
1135
|
const CONFIG_FILENAME = "react-doctor.config.json";
|
|
@@ -1183,7 +1165,6 @@ const loadConfig = (rootDirectory) => {
|
|
|
1183
1165
|
}
|
|
1184
1166
|
return null;
|
|
1185
1167
|
};
|
|
1186
|
-
|
|
1187
1168
|
//#endregion
|
|
1188
1169
|
//#region src/utils/resolve-compatible-node.ts
|
|
1189
1170
|
const parseNodeVersion = (versionString) => {
|
|
@@ -1236,7 +1217,7 @@ const installNodeViaNvm = () => {
|
|
|
1236
1217
|
const nvmScript = path.join(nvmDirectory, "nvm.sh");
|
|
1237
1218
|
if (!existsSync(nvmScript)) return false;
|
|
1238
1219
|
try {
|
|
1239
|
-
execSync(`bash -c ". '${nvmScript}' && nvm install
|
|
1220
|
+
execSync(`bash -c ". '${nvmScript}' && nvm install 24"`, { stdio: "inherit" });
|
|
1240
1221
|
return findCompatibleNvmBinary() !== null;
|
|
1241
1222
|
} catch {
|
|
1242
1223
|
return false;
|
|
@@ -1258,7 +1239,6 @@ const resolveNodeForOxlint = () => {
|
|
|
1258
1239
|
version
|
|
1259
1240
|
};
|
|
1260
1241
|
};
|
|
1261
|
-
|
|
1262
1242
|
//#endregion
|
|
1263
1243
|
//#region src/utils/resolve-lint-include-paths.ts
|
|
1264
1244
|
const listSourceFilesViaGit = (rootDirectory) => {
|
|
@@ -1301,7 +1281,6 @@ const resolveLintIncludePaths = (rootDirectory, userConfig) => {
|
|
|
1301
1281
|
return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
|
|
1302
1282
|
});
|
|
1303
1283
|
};
|
|
1304
|
-
|
|
1305
1284
|
//#endregion
|
|
1306
1285
|
//#region src/utils/collect-unused-file-paths.ts
|
|
1307
1286
|
const collectUnusedFilePaths = (filesIssues) => {
|
|
@@ -1315,7 +1294,19 @@ const collectUnusedFilePaths = (filesIssues) => {
|
|
|
1315
1294
|
}
|
|
1316
1295
|
return unusedFilePaths;
|
|
1317
1296
|
};
|
|
1318
|
-
|
|
1297
|
+
//#endregion
|
|
1298
|
+
//#region src/utils/extract-failed-plugin-name.ts
|
|
1299
|
+
const PLUGIN_CONFIG_PATTERN = /(?:^|[/\\\s])([a-z][a-z0-9-]*)\.config\./i;
|
|
1300
|
+
const extractFailedPluginName = (error) => {
|
|
1301
|
+
for (const errorMessage of getErrorChainMessages(error)) {
|
|
1302
|
+
const pluginNameMatch = errorMessage.match(PLUGIN_CONFIG_PATTERN);
|
|
1303
|
+
if (pluginNameMatch?.[1]) return pluginNameMatch[1].toLowerCase();
|
|
1304
|
+
}
|
|
1305
|
+
return null;
|
|
1306
|
+
};
|
|
1307
|
+
//#endregion
|
|
1308
|
+
//#region src/utils/has-knip-config.ts
|
|
1309
|
+
const hasKnipConfig = (directory) => KNIP_CONFIG_LOCATIONS.some((configFilename) => isFile(path.join(directory, configFilename)));
|
|
1319
1310
|
//#endregion
|
|
1320
1311
|
//#region src/utils/run-knip.ts
|
|
1321
1312
|
const KNIP_CATEGORY_MAP = {
|
|
@@ -1370,12 +1361,15 @@ const silenced = async (fn) => {
|
|
|
1370
1361
|
console.error = originalError;
|
|
1371
1362
|
}
|
|
1372
1363
|
};
|
|
1373
|
-
const CONFIG_LOADING_ERROR_PATTERN = /Error loading .*\/([a-z-]+)\.config\./;
|
|
1374
|
-
const extractFailedPluginName = (error) => {
|
|
1375
|
-
return String(error).match(CONFIG_LOADING_ERROR_PATTERN)?.[1] ?? null;
|
|
1376
|
-
};
|
|
1377
1364
|
const TSCONFIG_FILENAMES = ["tsconfig.base.json", "tsconfig.json"];
|
|
1378
1365
|
const resolveTsConfigFile = (directory) => TSCONFIG_FILENAMES.find((filename) => fs.existsSync(path.join(directory, filename)));
|
|
1366
|
+
const tryDisableFailedPlugin = (error, parsedConfig, disabledPlugins) => {
|
|
1367
|
+
const failedPlugin = extractFailedPluginName(error);
|
|
1368
|
+
if (!failedPlugin || !(failedPlugin in parsedConfig) || disabledPlugins.has(failedPlugin)) return false;
|
|
1369
|
+
disabledPlugins.add(failedPlugin);
|
|
1370
|
+
parsedConfig[failedPlugin] = false;
|
|
1371
|
+
return true;
|
|
1372
|
+
};
|
|
1379
1373
|
const runKnipWithOptions = async (knipCwd, workspaceName) => {
|
|
1380
1374
|
const tsConfigFile = resolveTsConfigFile(knipCwd);
|
|
1381
1375
|
const options = await silenced(() => createOptions({
|
|
@@ -1385,33 +1379,36 @@ const runKnipWithOptions = async (knipCwd, workspaceName) => {
|
|
|
1385
1379
|
...tsConfigFile ? { tsConfigFile } : {}
|
|
1386
1380
|
}));
|
|
1387
1381
|
const parsedConfig = options.parsedConfig;
|
|
1388
|
-
|
|
1382
|
+
const disabledPlugins = /* @__PURE__ */ new Set();
|
|
1383
|
+
let lastKnipError;
|
|
1384
|
+
for (let attempt = 0; attempt <= 5; attempt++) try {
|
|
1389
1385
|
return await silenced(() => main(options));
|
|
1390
1386
|
} catch (error) {
|
|
1391
|
-
|
|
1392
|
-
if (!
|
|
1393
|
-
parsedConfig[failedPlugin] = false;
|
|
1387
|
+
lastKnipError = error;
|
|
1388
|
+
if (!tryDisableFailedPlugin(error, parsedConfig, disabledPlugins)) throw error;
|
|
1394
1389
|
}
|
|
1395
|
-
throw
|
|
1390
|
+
throw lastKnipError;
|
|
1396
1391
|
};
|
|
1397
1392
|
const hasNodeModules = (directory) => {
|
|
1398
1393
|
const nodeModulesPath = path.join(directory, "node_modules");
|
|
1399
1394
|
return fs.existsSync(nodeModulesPath) && fs.statSync(nodeModulesPath).isDirectory();
|
|
1400
1395
|
};
|
|
1396
|
+
const resolveWorkspaceName = (rootDirectory) => {
|
|
1397
|
+
const packageJsonPath = path.join(rootDirectory, "package.json");
|
|
1398
|
+
return (isFile(packageJsonPath) ? readPackageJson(packageJsonPath) : {}).name ?? path.basename(rootDirectory);
|
|
1399
|
+
};
|
|
1400
|
+
const runKnipForProject = async (rootDirectory, monorepoRoot) => {
|
|
1401
|
+
if (!monorepoRoot || hasKnipConfig(rootDirectory)) return runKnipWithOptions(rootDirectory);
|
|
1402
|
+
try {
|
|
1403
|
+
return await runKnipWithOptions(monorepoRoot, resolveWorkspaceName(rootDirectory));
|
|
1404
|
+
} catch {
|
|
1405
|
+
return runKnipWithOptions(rootDirectory);
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1401
1408
|
const runKnip = async (rootDirectory) => {
|
|
1402
1409
|
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
1403
1410
|
if (!(hasNodeModules(rootDirectory) || monorepoRoot !== null && hasNodeModules(monorepoRoot))) return [];
|
|
1404
|
-
|
|
1405
|
-
if (monorepoRoot) {
|
|
1406
|
-
const packageJsonPath = path.join(rootDirectory, "package.json");
|
|
1407
|
-
const workspaceName = (isFile(packageJsonPath) ? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) : {}).name ?? path.basename(rootDirectory);
|
|
1408
|
-
try {
|
|
1409
|
-
knipResult = await runKnipWithOptions(monorepoRoot, workspaceName);
|
|
1410
|
-
} catch {
|
|
1411
|
-
knipResult = await runKnipWithOptions(rootDirectory);
|
|
1412
|
-
}
|
|
1413
|
-
} else knipResult = await runKnipWithOptions(rootDirectory);
|
|
1414
|
-
const { issues } = knipResult;
|
|
1411
|
+
const { issues } = await runKnipForProject(rootDirectory, monorepoRoot);
|
|
1415
1412
|
const diagnostics = [];
|
|
1416
1413
|
for (const unusedFilePath of collectUnusedFilePaths(issues.files)) diagnostics.push({
|
|
1417
1414
|
filePath: path.relative(rootDirectory, unusedFilePath),
|
|
@@ -1432,7 +1429,6 @@ const runKnip = async (rootDirectory) => {
|
|
|
1432
1429
|
]) diagnostics.push(...collectIssueRecords(issues[issueType], issueType, rootDirectory));
|
|
1433
1430
|
return diagnostics;
|
|
1434
1431
|
};
|
|
1435
|
-
|
|
1436
1432
|
//#endregion
|
|
1437
1433
|
//#region src/oxlint-config.ts
|
|
1438
1434
|
const esmRequire$1 = createRequire(import.meta.url);
|
|
@@ -1616,7 +1612,6 @@ const createOxlintConfig = ({ pluginPath, framework, hasReactCompiler, customRul
|
|
|
1616
1612
|
...framework === "tanstack-start" ? TANSTACK_START_RULES : {}
|
|
1617
1613
|
}
|
|
1618
1614
|
});
|
|
1619
|
-
|
|
1620
1615
|
//#endregion
|
|
1621
1616
|
//#region src/utils/neutralize-disable-directives.ts
|
|
1622
1617
|
const findFilesWithDisableDirectives = (rootDirectory, includePaths) => {
|
|
@@ -1659,7 +1654,6 @@ const neutralizeDisableDirectives = (rootDirectory, includePaths) => {
|
|
|
1659
1654
|
for (const [absolutePath, originalContent] of originalContents) fs.writeFileSync(absolutePath, originalContent);
|
|
1660
1655
|
};
|
|
1661
1656
|
};
|
|
1662
|
-
|
|
1663
1657
|
//#endregion
|
|
1664
1658
|
//#region src/utils/run-oxlint.ts
|
|
1665
1659
|
const esmRequire = createRequire(import.meta.url);
|
|
@@ -1919,8 +1913,8 @@ const batchIncludePaths = (baseArgs, includePaths) => {
|
|
|
1919
1913
|
let currentBatchLength = baseArgsLength;
|
|
1920
1914
|
for (const filePath of includePaths) {
|
|
1921
1915
|
const entryLength = filePath.length + 1;
|
|
1922
|
-
const exceedsArgLength = currentBatch.length > 0 && currentBatchLength + entryLength >
|
|
1923
|
-
const exceedsFileCount = currentBatch.length >=
|
|
1916
|
+
const exceedsArgLength = currentBatch.length > 0 && currentBatchLength + entryLength > 24e3;
|
|
1917
|
+
const exceedsFileCount = currentBatch.length >= 500;
|
|
1924
1918
|
if (exceedsArgLength || exceedsFileCount) {
|
|
1925
1919
|
batches.push(currentBatch);
|
|
1926
1920
|
currentBatch = [];
|
|
@@ -1964,7 +1958,7 @@ const parseOxlintOutput = (stdout) => {
|
|
|
1964
1958
|
try {
|
|
1965
1959
|
output = JSON.parse(stdout);
|
|
1966
1960
|
} catch {
|
|
1967
|
-
throw new Error(`Failed to parse oxlint output: ${stdout.slice(0,
|
|
1961
|
+
throw new Error(`Failed to parse oxlint output: ${stdout.slice(0, 200)}`);
|
|
1968
1962
|
}
|
|
1969
1963
|
return output.diagnostics.filter((diagnostic) => diagnostic.code && JSX_FILE_PATTERN.test(diagnostic.filename)).map((diagnostic) => {
|
|
1970
1964
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
@@ -2015,7 +2009,6 @@ const runOxlint = async (rootDirectory, hasTypeScript, framework, hasReactCompil
|
|
|
2015
2009
|
if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
|
|
2016
2010
|
}
|
|
2017
2011
|
};
|
|
2018
|
-
|
|
2019
2012
|
//#endregion
|
|
2020
2013
|
//#region src/scan.ts
|
|
2021
2014
|
const SEVERITY_ORDER = {
|
|
@@ -2054,7 +2047,7 @@ const printDiagnostics = (diagnostics, isVerbose) => {
|
|
|
2054
2047
|
}
|
|
2055
2048
|
};
|
|
2056
2049
|
const formatElapsedTime = (elapsedMilliseconds) => {
|
|
2057
|
-
if (elapsedMilliseconds <
|
|
2050
|
+
if (elapsedMilliseconds < 1e3) return `${Math.round(elapsedMilliseconds)}ms`;
|
|
2058
2051
|
return `${(elapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1)}s`;
|
|
2059
2052
|
};
|
|
2060
2053
|
const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
|
|
@@ -2083,8 +2076,8 @@ const writeDiagnosticsDirectory = (diagnostics) => {
|
|
|
2083
2076
|
return outputDirectory;
|
|
2084
2077
|
};
|
|
2085
2078
|
const buildScoreBarSegments = (score) => {
|
|
2086
|
-
const filledCount = Math.round(score /
|
|
2087
|
-
const emptyCount =
|
|
2079
|
+
const filledCount = Math.round(score / 100 * 50);
|
|
2080
|
+
const emptyCount = 50 - filledCount;
|
|
2088
2081
|
return {
|
|
2089
2082
|
filledSegment: "█".repeat(filledCount),
|
|
2090
2083
|
emptySegment: "░".repeat(emptyCount)
|
|
@@ -2101,14 +2094,14 @@ const buildScoreBar = (score) => {
|
|
|
2101
2094
|
const printScoreGauge = (score, label) => {
|
|
2102
2095
|
const scoreDisplay = colorizeByScore(`${score}`, score);
|
|
2103
2096
|
const labelDisplay = colorizeByScore(label, score);
|
|
2104
|
-
logger.log(` ${scoreDisplay} /
|
|
2097
|
+
logger.log(` ${scoreDisplay} / 100 ${labelDisplay}`);
|
|
2105
2098
|
logger.break();
|
|
2106
2099
|
logger.log(` ${buildScoreBar(score)}`);
|
|
2107
2100
|
logger.break();
|
|
2108
2101
|
};
|
|
2109
2102
|
const getDoctorFace = (score) => {
|
|
2110
|
-
if (score >=
|
|
2111
|
-
if (score >=
|
|
2103
|
+
if (score >= 75) return ["◠ ◠", " ▽ "];
|
|
2104
|
+
if (score >= 50) return ["• •", " ─ "];
|
|
2112
2105
|
return ["x x", " ▽ "];
|
|
2113
2106
|
};
|
|
2114
2107
|
const printBranding = (score) => {
|
|
@@ -2146,8 +2139,8 @@ const buildBrandingLines = (scoreResult, noScoreMessage) => {
|
|
|
2146
2139
|
lines.push(createFramedLine("└─────┘", scoreColorizer("└─────┘")));
|
|
2147
2140
|
lines.push(createFramedLine("React Doctor (www.react.doctor)", `React Doctor ${highlighter.dim("(www.react.doctor)")}`));
|
|
2148
2141
|
lines.push(createFramedLine(""));
|
|
2149
|
-
const scoreLinePlainText = `${scoreResult.score} /
|
|
2150
|
-
const scoreLineRenderedText = `${colorizeByScore(String(scoreResult.score), scoreResult.score)} /
|
|
2142
|
+
const scoreLinePlainText = `${scoreResult.score} / 100 ${scoreResult.label}`;
|
|
2143
|
+
const scoreLineRenderedText = `${colorizeByScore(String(scoreResult.score), scoreResult.score)} / 100 ${colorizeByScore(scoreResult.label, scoreResult.score)}`;
|
|
2151
2144
|
lines.push(createFramedLine(scoreLinePlainText, scoreLineRenderedText));
|
|
2152
2145
|
lines.push(createFramedLine(""));
|
|
2153
2146
|
lines.push(createFramedLine(buildPlainScoreBar(scoreResult.score), buildScoreBar(scoreResult.score)));
|
|
@@ -2214,7 +2207,7 @@ const resolveOxlintNode = async (isLintEnabled, isScoreOnly) => {
|
|
|
2214
2207
|
const { shouldInstallNode } = await prompts({
|
|
2215
2208
|
type: "confirm",
|
|
2216
2209
|
name: "shouldInstallNode",
|
|
2217
|
-
message: `Install Node
|
|
2210
|
+
message: `Install Node 24 via nvm to enable lint checks?`,
|
|
2218
2211
|
initial: true
|
|
2219
2212
|
});
|
|
2220
2213
|
if (shouldInstallNode) {
|
|
@@ -2231,8 +2224,8 @@ const resolveOxlintNode = async (isLintEnabled, isScoreOnly) => {
|
|
|
2231
2224
|
logger.break();
|
|
2232
2225
|
return null;
|
|
2233
2226
|
}
|
|
2234
|
-
} else if (isNvmInstalled()) logger.dim(` Run: nvm install
|
|
2235
|
-
else logger.dim(` Install nvm (https://github.com/nvm-sh/nvm) and run: nvm install
|
|
2227
|
+
} else if (isNvmInstalled()) logger.dim(` Run: nvm install 24`);
|
|
2228
|
+
else logger.dim(` Install nvm (https://github.com/nvm-sh/nvm) and run: nvm install 24`);
|
|
2236
2229
|
logger.break();
|
|
2237
2230
|
return null;
|
|
2238
2231
|
};
|
|
@@ -2285,13 +2278,13 @@ const scan = async (directory, inputOptions = {}) => {
|
|
|
2285
2278
|
} catch (error) {
|
|
2286
2279
|
didLintFail = true;
|
|
2287
2280
|
if (!options.scoreOnly) {
|
|
2288
|
-
const
|
|
2289
|
-
if (
|
|
2281
|
+
const lintErrorChain = formatErrorChain(error);
|
|
2282
|
+
if (lintErrorChain.includes("native binding")) {
|
|
2290
2283
|
lintSpinner?.fail(`Lint checks failed — oxlint native binding not found (Node ${process.version}).`);
|
|
2291
2284
|
logger.dim(` Upgrade to Node ${OXLINT_NODE_REQUIREMENT} or run: npx -p oxlint@latest react-doctor@latest`);
|
|
2292
2285
|
} else {
|
|
2293
2286
|
lintSpinner?.fail("Lint checks failed (non-fatal, skipping).");
|
|
2294
|
-
logger.error(
|
|
2287
|
+
logger.error(lintErrorChain);
|
|
2295
2288
|
}
|
|
2296
2289
|
}
|
|
2297
2290
|
return [];
|
|
@@ -2307,7 +2300,7 @@ const scan = async (directory, inputOptions = {}) => {
|
|
|
2307
2300
|
didDeadCodeFail = true;
|
|
2308
2301
|
if (!options.scoreOnly) {
|
|
2309
2302
|
deadCodeSpinner?.fail("Dead code detection failed (non-fatal, skipping).");
|
|
2310
|
-
logger.error(
|
|
2303
|
+
logger.error(formatErrorChain(error));
|
|
2311
2304
|
}
|
|
2312
2305
|
return [];
|
|
2313
2306
|
}
|
|
@@ -2364,7 +2357,6 @@ const scan = async (directory, inputOptions = {}) => {
|
|
|
2364
2357
|
skippedChecks
|
|
2365
2358
|
};
|
|
2366
2359
|
};
|
|
2367
|
-
|
|
2368
2360
|
//#endregion
|
|
2369
2361
|
//#region src/utils/get-diff-files.ts
|
|
2370
2362
|
const getCurrentBranch = (directory) => {
|
|
@@ -2444,7 +2436,6 @@ const getDiffInfo = (directory, explicitBaseBranch) => {
|
|
|
2444
2436
|
};
|
|
2445
2437
|
};
|
|
2446
2438
|
const filterSourceFiles = (filePaths) => filePaths.filter((filePath) => SOURCE_FILE_PATTERN.test(filePath));
|
|
2447
|
-
|
|
2448
2439
|
//#endregion
|
|
2449
2440
|
//#region src/utils/get-staged-files.ts
|
|
2450
2441
|
const getStagedFilePaths = (directory) => {
|
|
@@ -2506,7 +2497,6 @@ const materializeStagedFiles = (directory, stagedFiles, tempDirectory) => {
|
|
|
2506
2497
|
}
|
|
2507
2498
|
};
|
|
2508
2499
|
};
|
|
2509
|
-
|
|
2510
2500
|
//#endregion
|
|
2511
2501
|
//#region src/utils/handle-error.ts
|
|
2512
2502
|
const DEFAULT_HANDLE_ERROR_OPTIONS = { shouldExit: true };
|
|
@@ -2520,7 +2510,6 @@ const handleError = (error, options = DEFAULT_HANDLE_ERROR_OPTIONS) => {
|
|
|
2520
2510
|
if (options.shouldExit) process.exit(1);
|
|
2521
2511
|
process.exitCode = 1;
|
|
2522
2512
|
};
|
|
2523
|
-
|
|
2524
2513
|
//#endregion
|
|
2525
2514
|
//#region src/utils/select-projects.ts
|
|
2526
2515
|
const selectProjects = async (rootDirectory, projectFlag, skipPrompts) => {
|
|
@@ -2568,10 +2557,9 @@ const promptProjectSelection = async (workspacePackages, rootDirectory) => {
|
|
|
2568
2557
|
});
|
|
2569
2558
|
return selectedDirectories;
|
|
2570
2559
|
};
|
|
2571
|
-
|
|
2572
2560
|
//#endregion
|
|
2573
2561
|
//#region src/cli.ts
|
|
2574
|
-
const VERSION = "0.0.
|
|
2562
|
+
const VERSION = "0.0.42";
|
|
2575
2563
|
const VALID_FAIL_ON_LEVELS = new Set([
|
|
2576
2564
|
"error",
|
|
2577
2565
|
"warning",
|
|
@@ -2740,7 +2728,7 @@ const main$1 = async () => {
|
|
|
2740
2728
|
await program.parseAsync();
|
|
2741
2729
|
};
|
|
2742
2730
|
main$1();
|
|
2743
|
-
|
|
2744
2731
|
//#endregion
|
|
2745
|
-
export {
|
|
2732
|
+
export {};
|
|
2733
|
+
|
|
2746
2734
|
//# sourceMappingURL=cli.js.map
|