react-doctor 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +66 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.js +45 -18
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -242,23 +242,34 @@ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
|
242
242
|
if (!fs.existsSync(baseDirectory) || !fs.statSync(baseDirectory).isDirectory()) return [];
|
|
243
243
|
return fs.readdirSync(baseDirectory).map((entry) => path.join(baseDirectory, entry)).filter((entryPath) => fs.statSync(entryPath).isDirectory() && fs.existsSync(path.join(entryPath, "package.json")));
|
|
244
244
|
};
|
|
245
|
-
const
|
|
245
|
+
const isMonorepoRoot = (directory) => {
|
|
246
|
+
if (fs.existsSync(path.join(directory, "pnpm-workspace.yaml"))) return true;
|
|
247
|
+
const packageJsonPath = path.join(directory, "package.json");
|
|
248
|
+
if (!fs.existsSync(packageJsonPath)) return false;
|
|
249
|
+
const packageJson = readPackageJson(packageJsonPath);
|
|
250
|
+
return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
|
|
251
|
+
};
|
|
252
|
+
const findMonorepoRoot$1 = (startDirectory) => {
|
|
246
253
|
let currentDirectory = path.dirname(startDirectory);
|
|
247
|
-
const result = {
|
|
248
|
-
reactVersion: null,
|
|
249
|
-
framework: "unknown"
|
|
250
|
-
};
|
|
251
254
|
while (currentDirectory !== path.dirname(currentDirectory)) {
|
|
252
|
-
|
|
253
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
254
|
-
const info = extractDependencyInfo(readPackageJson(packageJsonPath));
|
|
255
|
-
if (!result.reactVersion && info.reactVersion) result.reactVersion = info.reactVersion;
|
|
256
|
-
if (result.framework === "unknown" && info.framework !== "unknown") result.framework = info.framework;
|
|
257
|
-
if (result.reactVersion && result.framework !== "unknown") return result;
|
|
258
|
-
}
|
|
255
|
+
if (isMonorepoRoot(currentDirectory)) return currentDirectory;
|
|
259
256
|
currentDirectory = path.dirname(currentDirectory);
|
|
260
257
|
}
|
|
261
|
-
return
|
|
258
|
+
return null;
|
|
259
|
+
};
|
|
260
|
+
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
261
|
+
const monorepoRoot = findMonorepoRoot$1(directory);
|
|
262
|
+
if (!monorepoRoot) return {
|
|
263
|
+
reactVersion: null,
|
|
264
|
+
framework: "unknown"
|
|
265
|
+
};
|
|
266
|
+
const rootPackageJson = readPackageJson(path.join(monorepoRoot, "package.json"));
|
|
267
|
+
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
268
|
+
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
269
|
+
return {
|
|
270
|
+
reactVersion: rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
271
|
+
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
272
|
+
};
|
|
262
273
|
};
|
|
263
274
|
const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
264
275
|
const patterns = getWorkspacePatterns(rootDirectory, packageJson);
|
|
@@ -355,10 +366,10 @@ const discoverProject = (directory) => {
|
|
|
355
366
|
if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
|
|
356
367
|
if (framework === "unknown" && workspaceInfo.framework !== "unknown") framework = workspaceInfo.framework;
|
|
357
368
|
}
|
|
358
|
-
if (!reactVersion || framework === "unknown") {
|
|
359
|
-
const
|
|
360
|
-
if (!reactVersion) reactVersion =
|
|
361
|
-
if (framework === "unknown") framework =
|
|
369
|
+
if ((!reactVersion || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
370
|
+
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
371
|
+
if (!reactVersion) reactVersion = monorepoInfo.reactVersion;
|
|
372
|
+
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
362
373
|
}
|
|
363
374
|
const projectName = packageJson.name ?? path.basename(directory);
|
|
364
375
|
const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
|
|
@@ -474,15 +485,18 @@ const silenced = async (fn) => {
|
|
|
474
485
|
const originalLog = console.log;
|
|
475
486
|
const originalInfo = console.info;
|
|
476
487
|
const originalWarn = console.warn;
|
|
488
|
+
const originalError = console.error;
|
|
477
489
|
console.log = () => {};
|
|
478
490
|
console.info = () => {};
|
|
479
491
|
console.warn = () => {};
|
|
492
|
+
console.error = () => {};
|
|
480
493
|
try {
|
|
481
494
|
return await fn();
|
|
482
495
|
} finally {
|
|
483
496
|
console.log = originalLog;
|
|
484
497
|
console.info = originalInfo;
|
|
485
498
|
console.warn = originalWarn;
|
|
499
|
+
console.error = originalError;
|
|
486
500
|
}
|
|
487
501
|
};
|
|
488
502
|
const findMonorepoRoot = (directory) => {
|
|
@@ -498,13 +512,26 @@ const findMonorepoRoot = (directory) => {
|
|
|
498
512
|
}
|
|
499
513
|
return null;
|
|
500
514
|
};
|
|
515
|
+
const CONFIG_LOADING_ERROR_PATTERN = /Error loading .*\/([a-z-]+)\.config\./;
|
|
516
|
+
const extractFailedPluginName = (error) => {
|
|
517
|
+
return String(error).match(CONFIG_LOADING_ERROR_PATTERN)?.[1] ?? null;
|
|
518
|
+
};
|
|
519
|
+
const MAX_KNIP_RETRIES = 5;
|
|
501
520
|
const runKnipWithOptions = async (knipCwd, workspaceName) => {
|
|
502
521
|
const options = await silenced(() => createOptions({
|
|
503
522
|
cwd: knipCwd,
|
|
504
523
|
isShowProgress: false,
|
|
505
524
|
...workspaceName ? { workspace: workspaceName } : {}
|
|
506
525
|
}));
|
|
507
|
-
|
|
526
|
+
const parsedConfig = options.parsedConfig;
|
|
527
|
+
for (let attempt = 0; attempt <= MAX_KNIP_RETRIES; attempt++) try {
|
|
528
|
+
return await silenced(() => main(options));
|
|
529
|
+
} catch (error) {
|
|
530
|
+
const failedPlugin = extractFailedPluginName(error);
|
|
531
|
+
if (!failedPlugin || attempt === MAX_KNIP_RETRIES) throw error;
|
|
532
|
+
parsedConfig[failedPlugin] = false;
|
|
533
|
+
}
|
|
534
|
+
throw new Error("Unreachable");
|
|
508
535
|
};
|
|
509
536
|
const hasNodeModules = (directory) => {
|
|
510
537
|
const nodeModulesPath = path.join(directory, "node_modules");
|
|
@@ -1205,6 +1232,14 @@ const scan = async (directory, options) => {
|
|
|
1205
1232
|
printSummary(diagnostics, elapsedMilliseconds, scoreResult, projectInfo.projectName, projectInfo.sourceFileCount);
|
|
1206
1233
|
};
|
|
1207
1234
|
|
|
1235
|
+
//#endregion
|
|
1236
|
+
//#region src/utils/should-auto-select-current-choice.ts
|
|
1237
|
+
const shouldAutoSelectCurrentChoice = (choiceStates, cursor) => {
|
|
1238
|
+
if (choiceStates.some((choiceState) => choiceState.selected)) return false;
|
|
1239
|
+
const currentChoice = choiceStates[cursor];
|
|
1240
|
+
return Boolean(currentChoice) && !currentChoice.disabled;
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1208
1243
|
//#endregion
|
|
1209
1244
|
//#region src/utils/should-select-all-choices.ts
|
|
1210
1245
|
const shouldSelectAllChoices = (choiceStates) => {
|
|
@@ -1216,6 +1251,7 @@ const shouldSelectAllChoices = (choiceStates) => {
|
|
|
1216
1251
|
const require = createRequire(import.meta.url);
|
|
1217
1252
|
const PROMPTS_MULTISELECT_MODULE_PATH = "prompts/lib/elements/multiselect";
|
|
1218
1253
|
let didPatchMultiselectToggleAll = false;
|
|
1254
|
+
let didPatchMultiselectSubmit = false;
|
|
1219
1255
|
const onCancel = () => {
|
|
1220
1256
|
logger.break();
|
|
1221
1257
|
logger.log("Cancelled.");
|
|
@@ -1240,8 +1276,19 @@ const patchMultiselectToggleAll = () => {
|
|
|
1240
1276
|
this.render();
|
|
1241
1277
|
};
|
|
1242
1278
|
};
|
|
1279
|
+
const patchMultiselectSubmit = () => {
|
|
1280
|
+
if (didPatchMultiselectSubmit) return;
|
|
1281
|
+
didPatchMultiselectSubmit = true;
|
|
1282
|
+
const multiselectPromptConstructor = require(PROMPTS_MULTISELECT_MODULE_PATH);
|
|
1283
|
+
const originalSubmit = multiselectPromptConstructor.prototype.submit;
|
|
1284
|
+
multiselectPromptConstructor.prototype.submit = function() {
|
|
1285
|
+
if (shouldAutoSelectCurrentChoice(this.value, this.cursor)) this.value[this.cursor].selected = true;
|
|
1286
|
+
originalSubmit.call(this);
|
|
1287
|
+
};
|
|
1288
|
+
};
|
|
1243
1289
|
const prompts = (questions) => {
|
|
1244
1290
|
patchMultiselectToggleAll();
|
|
1291
|
+
patchMultiselectSubmit();
|
|
1245
1292
|
return basePrompts(questions, { onCancel });
|
|
1246
1293
|
};
|
|
1247
1294
|
|
|
@@ -1416,7 +1463,7 @@ const copyToClipboard = (text) => {
|
|
|
1416
1463
|
|
|
1417
1464
|
//#endregion
|
|
1418
1465
|
//#region src/cli.ts
|
|
1419
|
-
const VERSION = "0.0.
|
|
1466
|
+
const VERSION = "0.0.18";
|
|
1420
1467
|
process.on("SIGINT", () => process.exit(0));
|
|
1421
1468
|
process.on("SIGTERM", () => process.exit(0));
|
|
1422
1469
|
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--no-lint", "skip linting").option("--no-dead-code", "skip dead code detection").option("--verbose", "show file details per rule").option("--score", "output only the score").option("-y, --yes", "skip prompts, scan all workspace projects").option("--project <name>", "select workspace project (comma-separated for multiple)").option("--fix", "open Ami to auto-fix all issues").option("--prompt", "copy latest scan output to clipboard").action(async (directory, flags) => {
|