Skip to content

Commit 370ea4c

Browse files
committed
refactor: enhance CLI options handling and improve automated environment detection
1 parent 85842ea commit 370ea4c

File tree

10 files changed

+280
-210
lines changed

10 files changed

+280
-210
lines changed

packages/react-doctor/src/cli.ts

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@ import { existsSync } from "node:fs";
33
import os from "node:os";
44
import path from "node:path";
55
import { Command } from "commander";
6-
import { OPEN_BASE_URL, SCORE_GOOD_THRESHOLD, SCORE_OK_THRESHOLD } from "./constants.js";
6+
import { AMI_INSTALL_URL, AMI_RELEASES_URL, AMI_WEBSITE_URL, OPEN_BASE_URL } from "./constants.js";
77
import { scan } from "./scan.js";
8-
import type { Diagnostic, DiffInfo, EstimatedScoreResult, ScanOptions } from "./types.js";
8+
import type {
9+
Diagnostic,
10+
DiffInfo,
11+
EstimatedScoreResult,
12+
ReactDoctorConfig,
13+
ScanOptions,
14+
} from "./types.js";
915
import { fetchEstimatedScore } from "./utils/calculate-score.js";
16+
import { colorizeByScore } from "./utils/colorize-by-score.js";
1017
import { createFramedLine, renderFramedBoxString } from "./utils/framed-box.js";
1118
import { filterSourceFiles, getDiffInfo } from "./utils/get-diff-files.js";
1219
import { handleError } from "./utils/handle-error.js";
@@ -43,6 +50,36 @@ const exitWithFixHint = () => {
4350
process.on("SIGINT", exitWithFixHint);
4451
process.on("SIGTERM", exitWithFixHint);
4552

53+
const AUTOMATED_ENVIRONMENT_VARIABLES = [
54+
"CI",
55+
"CLAUDECODE",
56+
"CURSOR_AGENT",
57+
"CODEX_CI",
58+
"OPENCODE",
59+
"AMP_HOME",
60+
"AMI",
61+
];
62+
63+
const isAutomatedEnvironment = (): boolean =>
64+
AUTOMATED_ENVIRONMENT_VARIABLES.some((envVariable) => Boolean(process.env[envVariable]));
65+
66+
const resolveCliScanOptions = (
67+
flags: CliFlags,
68+
userConfig: ReactDoctorConfig | null,
69+
programInstance: Command,
70+
): ScanOptions => {
71+
const isCliOverride = (optionName: string) =>
72+
programInstance.getOptionValueSource(optionName) === "cli";
73+
74+
return {
75+
lint: isCliOverride("lint") ? flags.lint : (userConfig?.lint ?? flags.lint),
76+
deadCode: isCliOverride("deadCode") ? flags.deadCode : (userConfig?.deadCode ?? flags.deadCode),
77+
verbose: isCliOverride("verbose") ? Boolean(flags.verbose) : (userConfig?.verbose ?? false),
78+
scoreOnly: flags.score,
79+
offline: flags.offline,
80+
};
81+
};
82+
4683
const resolveDiffMode = async (
4784
diffInfo: DiffInfo | null,
4885
effectiveDiff: boolean | string | undefined,
@@ -105,37 +142,17 @@ const program = new Command()
105142
logger.break();
106143
}
107144

108-
const isCliOverride = (optionName: string) =>
109-
program.getOptionValueSource(optionName) === "cli";
110-
111-
const scanOptions: ScanOptions = {
112-
lint: isCliOverride("lint") ? flags.lint : (userConfig?.lint ?? flags.lint),
113-
deadCode: isCliOverride("deadCode")
114-
? flags.deadCode
115-
: (userConfig?.deadCode ?? flags.deadCode),
116-
verbose: isCliOverride("verbose") ? Boolean(flags.verbose) : (userConfig?.verbose ?? false),
117-
scoreOnly: isScoreOnly,
118-
offline: flags.offline,
119-
};
120-
121-
const isAutomatedEnvironment = [
122-
process.env.CI,
123-
process.env.CLAUDECODE,
124-
process.env.CURSOR_AGENT,
125-
process.env.CODEX_CI,
126-
process.env.OPENCODE,
127-
process.env.AMP_HOME,
128-
process.env.AMI,
129-
].some(Boolean);
130-
const shouldSkipPrompts = flags.yes || isAutomatedEnvironment || !process.stdin.isTTY;
145+
const scanOptions = resolveCliScanOptions(flags, userConfig, program);
146+
const shouldSkipPrompts = flags.yes || isAutomatedEnvironment() || !process.stdin.isTTY;
131147
const shouldSkipAmiPrompts = shouldSkipPrompts || !flags.ami;
132148
const projectDirectories = await selectProjects(
133149
resolvedDirectory,
134150
flags.project,
135151
shouldSkipPrompts,
136152
);
137153

138-
const effectiveDiff = isCliOverride("diff") ? flags.diff : userConfig?.diff;
154+
const isDiffCliOverride = program.getOptionValueSource("diff") === "cli";
155+
const effectiveDiff = isDiffCliOverride ? flags.diff : userConfig?.diff;
139156
const explicitBaseBranch = typeof effectiveDiff === "string" ? effectiveDiff : undefined;
140157
const diffInfo = getDiffInfo(resolvedDirectory, explicitBaseBranch);
141158
const isDiffMode = await resolveDiffMode(
@@ -209,16 +226,6 @@ ${highlighter.dim("Learn more:")}
209226
`,
210227
);
211228

212-
const AMI_WEBSITE_URL = "https://ami.dev";
213-
const AMI_INSTALL_URL = `${AMI_WEBSITE_URL}/install.sh`;
214-
const AMI_RELEASES_URL = "https://github.com/millionco/ami-releases/releases";
215-
216-
const colorizeByScore = (text: string, score: number): string => {
217-
if (score >= SCORE_GOOD_THRESHOLD) return highlighter.success(text);
218-
if (score >= SCORE_OK_THRESHOLD) return highlighter.warn(text);
219-
return highlighter.error(text);
220-
};
221-
222229
const DEEPLINK_FIX_PROMPT = "/{slash-command:ami:react-doctor}";
223230

224231
const isAmiInstalled = (): boolean => {

packages/react-doctor/src/constants.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,19 @@ export const OFFLINE_MESSAGE =
4040
export const OFFLINE_FLAG_MESSAGE = "Score not calculated. Remove --offline to calculate score.";
4141

4242
export const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
43+
44+
export const ERROR_RULE_PENALTY = 1.5;
45+
46+
export const WARNING_RULE_PENALTY = 0.75;
47+
48+
export const ERROR_ESTIMATED_FIX_RATE = 0.85;
49+
50+
export const WARNING_ESTIMATED_FIX_RATE = 0.8;
51+
52+
export const MAX_KNIP_RETRIES = 5;
53+
54+
export const AMI_WEBSITE_URL = "https://ami.dev";
55+
56+
export const AMI_INSTALL_URL = `${AMI_WEBSITE_URL}/install.sh`;
57+
58+
export const AMI_RELEASES_URL = "https://github.com/millionco/ami-releases/releases";

packages/react-doctor/src/index.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import path from "node:path";
22
import { performance } from "node:perf_hooks";
3-
import { JSX_FILE_PATTERN } from "./constants.js";
43
import type { Diagnostic, DiffInfo, ProjectInfo, ReactDoctorConfig, ScoreResult } from "./types.js";
54
import { calculateScore } from "./utils/calculate-score.js";
6-
import { checkReducedMotion } from "./utils/check-reduced-motion.js";
5+
import { combineDiagnostics, computeJsxIncludePaths } from "./utils/combine-diagnostics.js";
76
import { discoverProject } from "./utils/discover-project.js";
8-
import { filterIgnoredDiagnostics } from "./utils/filter-diagnostics.js";
97
import { loadConfig } from "./utils/load-config.js";
108
import { runKnip } from "./utils/run-knip.js";
119
import { runOxlint } from "./utils/run-oxlint.js";
@@ -45,9 +43,9 @@ export const diagnose = async (
4543
throw new Error("No React dependency found in package.json");
4644
}
4745

48-
const jsxIncludePaths = isDiffMode
49-
? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath))
50-
: undefined;
46+
const jsxIncludePaths = computeJsxIncludePaths(includePaths);
47+
48+
const emptyDiagnostics: Diagnostic[] = [];
5149

5250
const lintPromise = effectiveLint
5351
? runOxlint(
@@ -58,27 +56,26 @@ export const diagnose = async (
5856
jsxIncludePaths,
5957
).catch((error: unknown) => {
6058
console.error("Lint failed:", error);
61-
return [] as Diagnostic[];
59+
return emptyDiagnostics;
6260
})
63-
: Promise.resolve([] as Diagnostic[]);
61+
: Promise.resolve(emptyDiagnostics);
6462

6563
const deadCodePromise =
6664
effectiveDeadCode && !isDiffMode
6765
? runKnip(resolvedDirectory).catch((error: unknown) => {
6866
console.error("Dead code analysis failed:", error);
69-
return [] as Diagnostic[];
67+
return emptyDiagnostics;
7068
})
71-
: Promise.resolve([] as Diagnostic[]);
69+
: Promise.resolve(emptyDiagnostics);
7270

7371
const [lintDiagnostics, deadCodeDiagnostics] = await Promise.all([lintPromise, deadCodePromise]);
74-
const allDiagnostics = [
75-
...lintDiagnostics,
76-
...deadCodeDiagnostics,
77-
...(isDiffMode ? [] : checkReducedMotion(resolvedDirectory)),
78-
];
79-
const diagnostics = userConfig
80-
? filterIgnoredDiagnostics(allDiagnostics, userConfig)
81-
: allDiagnostics;
72+
const diagnostics = combineDiagnostics(
73+
lintDiagnostics,
74+
deadCodeDiagnostics,
75+
resolvedDirectory,
76+
isDiffMode,
77+
userConfig,
78+
);
8279

8380
const elapsedMilliseconds = performance.now() - startTime;
8481
const score = await calculateScore(diagnostics);

0 commit comments

Comments
 (0)