From b9264a0b5fd9364f01d116ab9b2eb38d71a1b5cc Mon Sep 17 00:00:00 2001 From: Taiki92777 Date: Thu, 19 Feb 2026 21:55:53 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20CLI=E8=A8=AD=E5=AE=9A=E3=81=A8=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=BC=E3=83=AB=E3=83=90=E3=83=83=E3=82=AF=E6=8C=99?= =?UTF-8?q?=E5=8B=95=E3=82=92=E6=94=B9=E5=96=84=20-=20=E8=A8=BA=E6=96=AD?= =?UTF-8?q?=E7=B5=90=E6=9E=9C=E3=81=AE=E4=BF=A1=E9=A0=BC=E6=80=A7=E3=82=92?= =?UTF-8?q?=E5=90=91=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react-doctor/README.md | 3 ++ packages/react-doctor/src/cli.ts | 19 ++++++++-- packages/react-doctor/src/scan.ts | 3 +- packages/react-doctor/src/types.ts | 1 + .../src/utils/discover-project.ts | 38 ++++++++++++++++++- .../react-doctor/src/utils/load-config.ts | 5 +-- 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/packages/react-doctor/README.md b/packages/react-doctor/README.md index 9751db5..290a8ce 100644 --- a/packages/react-doctor/README.md +++ b/packages/react-doctor/README.md @@ -55,13 +55,16 @@ Usage: react-doctor [directory] [options] Options: -v, --version display the version number + --lint run linting --no-lint skip linting + --dead-code run dead code detection --no-dead-code skip dead code detection --verbose show file details per rule --score output only the score -y, --yes skip prompts, scan all workspace projects --project select workspace project (comma-separated for multiple) --diff [base] scan only files changed vs base branch + --offline skip telemetry (anonymous, not stored, only used to calculate score) --no-ami skip Ami-related prompts --fix open Ami to auto-fix all issues -h, --help display help for command diff --git a/packages/react-doctor/src/cli.ts b/packages/react-doctor/src/cli.ts index 1dbee47..d0fa30e 100644 --- a/packages/react-doctor/src/cli.ts +++ b/packages/react-doctor/src/cli.ts @@ -84,7 +84,9 @@ const program = new Command() .version(VERSION, "-v, --version", "display the version number") .argument("[directory]", "project directory to scan", ".") .option("--no-lint", "skip linting") + .option("--lint", "run linting") .option("--no-dead-code", "skip dead code detection") + .option("--dead-code", "run 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") @@ -179,7 +181,12 @@ const program = new Command() logger.dim(`Scanning ${projectDirectory}...`); logger.break(); } - const scanResult = await scan(projectDirectory, { ...scanOptions, includePaths }); + const projectUserConfig = projectDirectory === resolvedDirectory ? userConfig : undefined; + const scanResult = await scan(projectDirectory, { + ...scanOptions, + includePaths, + userConfig: projectUserConfig, + }); allDiagnostics.push(...scanResult.diagnostics); if (!isScoreOnly) { logger.break(); @@ -286,14 +293,20 @@ const buildWebDeeplink = (directory: string): string => `${OPEN_BASE_URL}?${buildDeeplinkParams(directory).toString()}`; const openAmiToFix = (directory: string): void => { - const isInstalled = isAmiInstalled(); + let isInstalled = isAmiInstalled(); const deeplink = buildDeeplink(directory); const webDeeplink = buildWebDeeplink(directory); if (!isInstalled) { if (process.platform === "darwin") { installAmi(); - logger.success("Ami installed successfully."); + isInstalled = isAmiInstalled(); + if (isInstalled) { + logger.success("Ami installed successfully."); + } else { + logger.error("Ami installation completed, but Ami was not detected."); + logger.dim(`Download at ${highlighter.info(AMI_RELEASES_URL)}`); + } } else { logger.error("Ami is not installed."); logger.dim(`Download at ${highlighter.info(AMI_RELEASES_URL)}`); diff --git a/packages/react-doctor/src/scan.ts b/packages/react-doctor/src/scan.ts index 565ff8f..59608bb 100644 --- a/packages/react-doctor/src/scan.ts +++ b/packages/react-doctor/src/scan.ts @@ -320,7 +320,8 @@ export const scan = async ( ): Promise => { const startTime = performance.now(); const projectInfo = discoverProject(directory); - const userConfig = loadConfig(directory); + const userConfig = + inputOptions.userConfig === undefined ? loadConfig(directory) : inputOptions.userConfig; const options = { lint: inputOptions.lint ?? userConfig?.lint ?? true, diff --git a/packages/react-doctor/src/types.ts b/packages/react-doctor/src/types.ts index a7a3f48..8f1b8ef 100644 --- a/packages/react-doctor/src/types.ts +++ b/packages/react-doctor/src/types.ts @@ -102,6 +102,7 @@ export interface ScanOptions { scoreOnly?: boolean; offline?: boolean; includePaths?: string[]; + userConfig?: ReactDoctorConfig | null; } export interface DiffInfo { diff --git a/packages/react-doctor/src/utils/discover-project.ts b/packages/react-doctor/src/utils/discover-project.ts index 402d289..a22ca60 100644 --- a/packages/react-doctor/src/utils/discover-project.ts +++ b/packages/react-doctor/src/utils/discover-project.ts @@ -64,6 +64,42 @@ const FRAMEWORK_DISPLAY_NAMES: Record = { export const formatFrameworkName = (framework: Framework): string => FRAMEWORK_DISPLAY_NAMES[framework]; +const SOURCE_FILE_FALLBACK_IGNORED_DIRECTORIES = new Set([ + ".git", + ".next", + ".turbo", + "build", + "dist", + "node_modules", +]); + +const countSourceFilesByDirectoryWalk = (rootDirectory: string): number => { + const stack = [rootDirectory]; + let fileCount = 0; + + while (stack.length > 0) { + const currentDirectory = stack.pop(); + if (!currentDirectory) continue; + + const entries = fs.readdirSync(currentDirectory, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + if (SOURCE_FILE_FALLBACK_IGNORED_DIRECTORIES.has(entry.name)) { + continue; + } + stack.push(path.join(currentDirectory, entry.name)); + continue; + } + + if (entry.isFile() && SOURCE_FILE_PATTERN.test(entry.name)) { + fileCount += 1; + } + } + } + + return fileCount; +}; + const countSourceFiles = (rootDirectory: string): number => { const result = spawnSync("git", ["ls-files", "--cached", "--others", "--exclude-standard"], { cwd: rootDirectory, @@ -72,7 +108,7 @@ const countSourceFiles = (rootDirectory: string): number => { }); if (result.error || result.status !== 0) { - return 0; + return countSourceFilesByDirectoryWalk(rootDirectory); } return result.stdout diff --git a/packages/react-doctor/src/utils/load-config.ts b/packages/react-doctor/src/utils/load-config.ts index 70b5713..b21250b 100644 --- a/packages/react-doctor/src/utils/load-config.ts +++ b/packages/react-doctor/src/utils/load-config.ts @@ -17,14 +17,13 @@ export const loadConfig = (rootDirectory: string): ReactDoctorConfig | null => { const parsed: unknown = JSON.parse(fileContent); if (!isPlainObject(parsed)) { console.warn(`Warning: ${CONFIG_FILENAME} must be a JSON object, ignoring.`); - return null; + } else { + return parsed as ReactDoctorConfig; } - return parsed as ReactDoctorConfig; } catch (error) { console.warn( `Warning: Failed to parse ${CONFIG_FILENAME}: ${error instanceof Error ? error.message : String(error)}`, ); - return null; } }