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 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 findDependencyInfoFromAncestors = (startDirectory) => {
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
- const packageJsonPath = path.join(currentDirectory, "package.json");
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 result;
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 ancestorInfo = findDependencyInfoFromAncestors(directory);
360
- if (!reactVersion) reactVersion = ancestorInfo.reactVersion;
361
- if (framework === "unknown") framework = ancestorInfo.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
- return await silenced(() => main(options));
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.17";
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) => {