react-doctor 0.2.0-beta.0 → 0.2.0-beta.2
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/README.md +163 -157
- package/dist/cli.js +1130 -20196
- package/dist/index.d.ts +16 -0
- package/dist/index.js +77 -0
- package/package.json +26 -28
- package/dist/compat-CM6aj69a.js +0 -1851
- package/dist/compat.d.ts +0 -53
- package/dist/compat.js +0 -3
- package/dist/errors-ZdckckLr.d.ts +0 -87
- package/dist/eslint-plugin-BIjw2MeW.d.ts +0 -105
- package/dist/eslint-plugin.d.ts +0 -2
- package/dist/eslint-plugin.js +0 -51
- package/dist/index-CFzh1cBi.d.ts +0 -1798
- package/dist/metadata-se470mRG.js +0 -604
- package/dist/oxlint-plugin.d.ts +0 -2
- package/dist/oxlint-plugin.js +0 -7
- package/dist/rules-BfZ4Ujfv.js +0 -16701
- package/dist/rules-ebKa330H.d.ts +0 -28
- package/dist/score-CzbtoFAu.js +0 -69
- package/dist/score.d.ts +0 -35
- package/dist/score.js +0 -2
- package/dist/sdk.d.ts +0 -90
- package/dist/sdk.js +0 -17
package/README.md
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
|
|
10
10
|
Your agent writes bad React, this catches it.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
One command scans your codebase and outputs a **0 to 100 health score** with actionable diagnostics.
|
|
13
13
|
|
|
14
|
-
Works with
|
|
14
|
+
Works with Next.js, Vite, and React Native.
|
|
15
15
|
|
|
16
|
-
### [See it in action](https://react.doctor)
|
|
16
|
+
### [See it in action →](https://react.doctor)
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Install
|
|
19
19
|
|
|
20
20
|
Run this at your project root:
|
|
21
21
|
|
|
@@ -23,119 +23,104 @@ Run this at your project root:
|
|
|
23
23
|
npx -y react-doctor@latest .
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- native project structure and codebase graph checks
|
|
29
|
-
- oxlint with the React Doctor custom plugin
|
|
30
|
-
- scoring and grouped human output
|
|
31
|
-
|
|
32
|
-
You get a 0 to 100 score and a list of issues across state and effects, performance, architecture, security, accessibility, framework usage, dependencies, and dead code. Rules toggle automatically based on your framework, React version, and detected libraries.
|
|
26
|
+
You'll get a score (75+ Great, 50 to 74 Needs work, under 50 Critical) and a list of issues across state & effects, performance, architecture, security, accessibility, and dead code. Rules toggle automatically based on your framework and React version.
|
|
33
27
|
|
|
34
28
|
https://github.com/user-attachments/assets/07cc88d9-9589-44c3-aa73-5d603cb1c570
|
|
35
29
|
|
|
36
|
-
##
|
|
30
|
+
## Install for your coding agent
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
Teach your coding agent React best practices so it stops writing the bad code in the first place.
|
|
39
33
|
|
|
40
34
|
```bash
|
|
41
35
|
npx -y react-doctor@latest install
|
|
42
36
|
```
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
You'll be prompted to pick which detected agents to install for. Pass `--yes` to skip prompts.
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
- choose framework-native APIs for Next.js, React Native, Expo, and TanStack Start
|
|
48
|
-
- keep rendering, animation, data fetching, and accessibility choices high-signal
|
|
49
|
-
- understand React Doctor diagnostics and fix the underlying issue instead of hiding it
|
|
40
|
+
Works with Claude Code, Cursor, Codex, OpenCode, and 50+ other agents.
|
|
50
41
|
|
|
51
|
-
|
|
42
|
+
## GitHub Actions
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
A composite action ships with this repository. Drop it into `.github/workflows/react-doctor.yml`:
|
|
54
45
|
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
```
|
|
46
|
+
```yaml
|
|
47
|
+
name: React Doctor
|
|
58
48
|
|
|
59
|
-
|
|
49
|
+
on:
|
|
50
|
+
pull_request:
|
|
51
|
+
push:
|
|
52
|
+
branches: [main]
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
react-doctor
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
54
|
+
permissions:
|
|
55
|
+
contents: read
|
|
56
|
+
pull-requests: write # required to post PR comments
|
|
57
|
+
|
|
58
|
+
jobs:
|
|
59
|
+
react-doctor:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v5
|
|
63
|
+
with:
|
|
64
|
+
fetch-depth: 0 # required for `diff`
|
|
65
|
+
- uses: millionco/react-doctor@main
|
|
66
|
+
with:
|
|
67
|
+
diff: main
|
|
68
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
73
69
|
```
|
|
74
70
|
|
|
75
|
-
|
|
71
|
+
When `github-token` is set on `pull_request` events, findings are posted (and updated) as a PR comment. The action also exposes a `score` output (0–100) you can use in subsequent steps.
|
|
76
72
|
|
|
77
|
-
-
|
|
78
|
-
- `--unstaged` scans unstaged and untracked source files.
|
|
79
|
-
- `--changed` scans staged, unstaged, and untracked source files since `HEAD`.
|
|
80
|
-
- `--diff [base]` scans files changed against a base branch, defaulting to `main`.
|
|
73
|
+
**Inputs:** `directory`, `verbose`, `project`, `diff`, `github-token`, `fail-on` (`error` / `warning` / `none`), `offline`, `node-version`. See [`action.yml`](https://github.com/millionco/react-doctor/blob/main/action.yml) for full descriptions.
|
|
81
74
|
|
|
82
|
-
|
|
75
|
+
Prefer not to add a marketplace action? The bare `npx` form works too:
|
|
83
76
|
|
|
84
|
-
|
|
77
|
+
```yaml
|
|
78
|
+
- run: npx -y react-doctor@latest --fail-on warning
|
|
79
|
+
```
|
|
85
80
|
|
|
86
81
|
## Configuration
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- `react-doctor.config.json`
|
|
91
|
-
- `package.json#reactDoctor`
|
|
92
|
-
|
|
93
|
-
Config lookup starts at the requested directory and walks ancestors until a project boundary. `rootDir` is resolved relative to the config source, not the current working directory.
|
|
83
|
+
Create a `react-doctor.config.json` in your project root:
|
|
94
84
|
|
|
95
85
|
```json
|
|
96
86
|
{
|
|
97
|
-
"rootDir": "apps/web",
|
|
98
|
-
"lint": true,
|
|
99
|
-
"deadCode": true,
|
|
100
|
-
"customRulesOnly": false,
|
|
101
|
-
"offline": true,
|
|
102
|
-
"failOn": "error",
|
|
103
|
-
"respectInlineDisables": true,
|
|
104
|
-
"adoptExistingLintConfig": false,
|
|
105
|
-
"includeEcosystemRules": true,
|
|
106
|
-
"ignoredTags": ["design"],
|
|
107
|
-
"textComponents": ["Trans"],
|
|
108
|
-
"rawTextWrapperComponents": ["Button"],
|
|
109
87
|
"ignore": {
|
|
110
|
-
"rules": ["react-
|
|
88
|
+
"rules": ["react/no-danger", "jsx-a11y/no-autofocus"],
|
|
111
89
|
"files": ["src/generated/**"],
|
|
112
90
|
"overrides": [
|
|
113
91
|
{
|
|
114
|
-
"files": ["
|
|
115
|
-
"rules": ["react-doctor/no-
|
|
92
|
+
"files": ["components/modules/diff/**"],
|
|
93
|
+
"rules": ["react-doctor/no-array-index-as-key", "react-doctor/no-render-in-render"]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"files": ["components/search/HighlightedSnippet.tsx"],
|
|
97
|
+
"rules": ["react/no-danger"]
|
|
116
98
|
}
|
|
117
99
|
]
|
|
118
100
|
}
|
|
119
101
|
}
|
|
120
102
|
```
|
|
121
103
|
|
|
122
|
-
|
|
104
|
+
Three nested keys, three layers of granularity — pick the narrowest one that fits:
|
|
123
105
|
|
|
124
|
-
- **`ignore.rules`** silences a rule across the codebase.
|
|
125
|
-
- **`ignore.files`** silences every rule on matched files.
|
|
106
|
+
- **`ignore.rules`** silences a rule across the whole codebase.
|
|
107
|
+
- **`ignore.files`** silences **every** rule on the matched files (use sparingly — it loses coverage for unrelated rules).
|
|
126
108
|
- **`ignore.overrides`** silences only the listed rules on the matched files, leaving every other rule active. This is what you want when a single file (or glob) legitimately needs an exemption from one or two rules but should still be scanned for everything else.
|
|
127
109
|
|
|
128
|
-
|
|
110
|
+
You can also use the `"reactDoctor"` key in `package.json`. CLI flags always override config values.
|
|
129
111
|
|
|
130
|
-
|
|
112
|
+
React Doctor respects `.gitignore`, `.eslintignore`, `.oxlintignore`, `.prettierignore`, and `linguist-vendored` / `linguist-generated` annotations in `.gitattributes`. Inline `// eslint-disable*` and `// oxlint-disable*` comments are honored too.
|
|
131
113
|
|
|
132
|
-
|
|
114
|
+
If you have a JSON oxlint or eslint config (`.oxlintrc.json` or `.eslintrc.json`), its rules get merged into the scan automatically and count toward the score. Set `adoptExistingLintConfig: false` to opt out.
|
|
133
115
|
|
|
134
|
-
|
|
116
|
+
#### Optional companion plugins
|
|
135
117
|
|
|
136
|
-
|
|
118
|
+
When the following ESLint plugins are installed in the scanned project (or hoisted in your monorepo), React Doctor folds their rules into the same scan. Both are listed as **optional peer dependencies** — install only what you want.
|
|
137
119
|
|
|
138
|
-
|
|
120
|
+
| Plugin | Adds | Namespace |
|
|
121
|
+
| ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
|
122
|
+
| [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks) (v6 or v7) | The React Compiler frontend's correctness rules — fired when a React Compiler is detected in the project. | `react-hooks-js/*` |
|
|
123
|
+
| [`eslint-plugin-react-you-might-not-need-an-effect`](https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect) (v0.10+) | Complementary effects-as-anti-pattern rules (`no-derived-state`, `no-chain-state-updates`, `no-event-handler`, `no-pass-data-to-parent`, …) that run alongside React Doctor's native State & Effects rules. | `effect/*` |
|
|
139
124
|
|
|
140
125
|
### Inline suppressions
|
|
141
126
|
|
|
@@ -154,7 +139,7 @@ When two rules fire on the same line, you have two equivalent options. Comma-sep
|
|
|
154
139
|
const [localSearch, setLocalSearch] = useState(searchQuery);
|
|
155
140
|
```
|
|
156
141
|
|
|
157
|
-
Or stack one comment per rule directly above the diagnostic:
|
|
142
|
+
Or stack one comment per rule directly above the diagnostic. Stacked comments are honored as long as nothing but other `react-doctor-disable-next-line` comments sits between them and the target line:
|
|
158
143
|
|
|
159
144
|
```tsx
|
|
160
145
|
// react-doctor-disable-next-line react-doctor/rerender-state-only-in-handlers
|
|
@@ -162,142 +147,165 @@ Or stack one comment per rule directly above the diagnostic:
|
|
|
162
147
|
const [localSearch, setLocalSearch] = useState(searchQuery);
|
|
163
148
|
```
|
|
164
149
|
|
|
150
|
+
A code line between stacked comments breaks the chain: only the comment immediately above the diagnostic (and any contiguous `react-doctor-disable-next-line` comments stacked on top of it) is honored. If a comment looks adjacent but the rule still fires, run `react-doctor --explain <file:line>` — it reports whether a nearby suppression was found, what rules it covers, and why it didn't apply.
|
|
151
|
+
|
|
165
152
|
Block comments work inside JSX:
|
|
166
153
|
|
|
167
154
|
<!-- prettier-ignore -->
|
|
168
155
|
```tsx
|
|
169
|
-
{/* react-doctor-disable-next-line no-danger */}
|
|
156
|
+
{/* react-doctor-disable-next-line react/no-danger */}
|
|
170
157
|
<div dangerouslySetInnerHTML={{ __html }} />
|
|
171
158
|
```
|
|
172
159
|
|
|
173
160
|
For multi-line JSX, putting the comment immediately above the opening tag covers the entire attribute list (matching ESLint convention).
|
|
174
161
|
|
|
175
|
-
## Lint
|
|
162
|
+
## Lint plugin (standalone)
|
|
176
163
|
|
|
177
|
-
The same rule set ships as both an oxlint plugin and an ESLint plugin, so you can wire it into whichever lint engine your project already runs.
|
|
164
|
+
The same rule set ships as both an oxlint plugin and an ESLint plugin, so you can wire it into whichever lint engine your project already runs. These are published as separate packages, so you can install just the lint integration without pulling in the full CLI.
|
|
178
165
|
|
|
179
|
-
|
|
166
|
+
**oxlint** in `.oxlintrc.json` (install [`oxlint-plugin-react-doctor`](https://npmjs.com/package/oxlint-plugin-react-doctor)):
|
|
180
167
|
|
|
181
168
|
```jsonc
|
|
182
169
|
{
|
|
183
|
-
"jsPlugins": [{ "name": "react-doctor", "specifier": "react-doctor
|
|
170
|
+
"jsPlugins": [{ "name": "react-doctor", "specifier": "oxlint-plugin-react-doctor" }],
|
|
184
171
|
"rules": {
|
|
185
172
|
"react-doctor/no-fetch-in-effect": "warn",
|
|
173
|
+
"react-doctor/no-derived-state-effect": "warn",
|
|
186
174
|
},
|
|
187
175
|
}
|
|
188
176
|
```
|
|
189
177
|
|
|
190
|
-
ESLint:
|
|
178
|
+
**ESLint** flat config (install [`eslint-plugin-react-doctor`](https://npmjs.com/package/eslint-plugin-react-doctor)):
|
|
191
179
|
|
|
192
180
|
```js
|
|
193
|
-
import reactDoctor from "react-doctor
|
|
181
|
+
import reactDoctor from "eslint-plugin-react-doctor";
|
|
194
182
|
|
|
195
183
|
export default [
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
"react-doctor/no-fetch-in-effect": "warn",
|
|
202
|
-
},
|
|
203
|
-
},
|
|
184
|
+
reactDoctor.configs.recommended,
|
|
185
|
+
reactDoctor.configs.next,
|
|
186
|
+
reactDoctor.configs["react-native"],
|
|
187
|
+
reactDoctor.configs["tanstack-start"],
|
|
188
|
+
reactDoctor.configs["tanstack-query"],
|
|
204
189
|
];
|
|
205
190
|
```
|
|
206
191
|
|
|
207
|
-
The
|
|
192
|
+
The full rule list lives in [`packages/oxlint-plugin-react-doctor/src/plugin/rules`](https://github.com/millionco/react-doctor/tree/main/packages/oxlint-plugin-react-doctor/src/plugin/rules).
|
|
208
193
|
|
|
209
|
-
##
|
|
194
|
+
## CLI reference
|
|
210
195
|
|
|
211
|
-
```
|
|
212
|
-
|
|
196
|
+
```
|
|
197
|
+
Usage: react-doctor [directory] [options]
|
|
198
|
+
|
|
199
|
+
Options:
|
|
200
|
+
-v, --version display the version number
|
|
201
|
+
--no-lint skip linting
|
|
202
|
+
--verbose show every rule and per-file details (default shows top 3 rules)
|
|
203
|
+
--score output only the score
|
|
204
|
+
--json output a single structured JSON report
|
|
205
|
+
-y, --yes skip prompts, scan all workspace projects
|
|
206
|
+
--full skip prompts, always run a full scan
|
|
207
|
+
--project <name> select workspace project (comma-separated for multiple)
|
|
208
|
+
--diff [base] scan only files changed vs base branch
|
|
209
|
+
--staged scan only staged files (for pre-commit hooks)
|
|
210
|
+
--offline skip the score API and share URL (no score shown)
|
|
211
|
+
--fail-on <level> exit with error on diagnostics: error, warning, none
|
|
212
|
+
--annotations output diagnostics as GitHub Actions annotations
|
|
213
|
+
--explain <file:line> diagnose why a rule fired or why a suppression didn't apply
|
|
214
|
+
--why <file:line> alias for --explain
|
|
215
|
+
-h, --help display help
|
|
216
|
+
```
|
|
213
217
|
|
|
214
|
-
|
|
215
|
-
rootDirectory: "apps/web",
|
|
216
|
-
lint: true,
|
|
217
|
-
deadCode: true,
|
|
218
|
-
});
|
|
218
|
+
When a suppression isn't working, `--explain <file:line>` (or its alias `--why <file:line>`) reports what the scanner sees at that location, including why a nearby `react-doctor-disable-next-line` didn't apply. The diagnosis distinguishes the common failure modes — adjacent comment for a different rule (use the comma form), a code line between the comment and the diagnostic (the chain is broken), or no nearby suppression at all. The same hint surfaces inline with `--verbose` for every flagged site, and in `--json` output as `diagnostic.suppressionHint`, so a single scan doubles as a suppression audit without a separate flag.
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
const nextResult = await reactDoctor.inspect();
|
|
222
|
-
```
|
|
220
|
+
`--json` produces a parsable object on stdout with all human-readable output suppressed. Errors still produce a JSON object with `ok: false`, so stdout is always a valid document.
|
|
223
221
|
|
|
224
|
-
|
|
222
|
+
### Config keys
|
|
225
223
|
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
| Key | Type | Default |
|
|
225
|
+
| -------------------------- | -------------------------------- | -------- |
|
|
226
|
+
| `ignore.rules` | `string[]` | `[]` |
|
|
227
|
+
| `ignore.files` | `string[]` | `[]` |
|
|
228
|
+
| `ignore.overrides` | `{ files, rules? }[]` | `[]` |
|
|
229
|
+
| `lint` | `boolean` | `true` |
|
|
230
|
+
| `verbose` | `boolean` | `false` |
|
|
231
|
+
| `diff` | `boolean \| string` | |
|
|
232
|
+
| `failOn` | `"error" \| "warning" \| "none"` | `"none"` |
|
|
233
|
+
| `customRulesOnly` | `boolean` | `false` |
|
|
234
|
+
| `share` | `boolean` | `true` |
|
|
235
|
+
| `offline` | `boolean` | `false` |
|
|
236
|
+
| `textComponents` | `string[]` | `[]` |
|
|
237
|
+
| `rawTextWrapperComponents` | `string[]` | `[]` |
|
|
238
|
+
| `respectInlineDisables` | `boolean` | `true` |
|
|
239
|
+
| `adoptExistingLintConfig` | `boolean` | `true` |
|
|
240
|
+
| `ignore.tags` | `string[]` | `[]` |
|
|
228
241
|
|
|
229
|
-
|
|
230
|
-
```
|
|
242
|
+
`textComponents` is the broad escape hatch for `rn-no-raw-text` — list components that themselves behave like React Native's `<Text>` (custom `Typography`, `NativeTabs.Trigger.Label`, etc.) and the rule will treat them as text containers regardless of what their children look like.
|
|
231
243
|
|
|
232
|
-
|
|
244
|
+
`rawTextWrapperComponents` is the narrower option for components that are not text elements but safely route string-only children through an internal `<Text>` (e.g. `heroui-native`'s `Button`, which stringifies its children and renders them through a `ButtonLabel`). Listed wrappers suppress `rn-no-raw-text` only when their children are entirely stringifiable. A wrapper with mixed children — e.g. `<Button>Save<Icon /></Button>` — still reports because the wrapper can't safely route raw text alongside a sibling JSX element.
|
|
233
245
|
|
|
234
|
-
|
|
235
|
-
import { ReactDoctorInvalidConfigError, isReactDoctorError } from "react-doctor";
|
|
236
|
-
```
|
|
246
|
+
`ignore.tags` suppresses entire categories of rules by tag. For example, `"tags": ["design"]` disables all opinionated design rules (gradient text, pure black backgrounds, side tab borders, default Tailwind palettes). Available tags: `"design"`.
|
|
237
247
|
|
|
238
|
-
|
|
248
|
+
`offline` skips the score API entirely — no score is shown and no share URL is generated. Automatically enabled in CI environments (GitHub Actions, GitLab CI, CircleCI) so CI runs don't depend on the network.
|
|
239
249
|
|
|
240
|
-
|
|
250
|
+
## Scoring
|
|
241
251
|
|
|
242
|
-
|
|
243
|
-
import { diagnose, clearCaches } from "react-doctor/api";
|
|
252
|
+
The health score formula: `100 - (unique_error_rules x 1.5) - (unique_warning_rules x 0.75)`.
|
|
244
253
|
|
|
245
|
-
|
|
246
|
-
lint: true,
|
|
247
|
-
deadCode: true,
|
|
248
|
-
});
|
|
254
|
+
Scoring runs on react.doctor's API and is **network-dependent**: without a successful API round-trip (or under `--offline`) the score is omitted and the rest of the report still renders normally. Key details:
|
|
249
255
|
|
|
250
|
-
|
|
251
|
-
|
|
256
|
+
- The score counts **unique rules triggered**, not total instances. Fixing 49 of 50 `no-barrel-import` violations does not change the score; fixing all 50 removes the 0.75 penalty for that rule.
|
|
257
|
+
- Error-severity rules cost 1.5 points each. Warning-severity rules cost 0.75 points each.
|
|
258
|
+
- Category breakdowns shown in the output are for display only and do not weight the score.
|
|
252
259
|
|
|
253
|
-
|
|
260
|
+
Score labels: 75+ is **Great**, 50 to 74 is **Needs work**, under 50 is **Critical**.
|
|
254
261
|
|
|
255
|
-
|
|
262
|
+
Scores may decrease across releases as new rules are added. Each new rule that fires in your codebase introduces an additional penalty. This is expected — it means the tool is catching more issues, not that your code got worse. Pin to a specific react-doctor version in CI if you need stable scores across upgrades.
|
|
256
263
|
|
|
257
|
-
|
|
264
|
+
## Diff and staged modes
|
|
258
265
|
|
|
259
|
-
|
|
260
|
-
nr typecheck
|
|
261
|
-
nr test
|
|
262
|
-
nr build
|
|
263
|
-
```
|
|
266
|
+
React Doctor can scan only changed files instead of the full project:
|
|
264
267
|
|
|
265
|
-
|
|
268
|
+
- **`--diff [base]`** scans files changed vs a base branch. Auto-detects `main`/`master`, or pass an explicit branch: `--diff develop`. Also available as a config key: `"diff": true` or `"diff": "develop"`.
|
|
269
|
+
- **`--staged`** scans only files in the git staging area (index). Designed for pre-commit hooks — materializes staged file contents into a temp directory so the scan reflects exactly what will be committed.
|
|
270
|
+
- **`--full`** forces a full scan, overriding any `diff` value in config or CLI.
|
|
266
271
|
|
|
267
|
-
|
|
268
|
-
nr format:check packages/react-doctor/src packages/react-doctor/tests
|
|
269
|
-
nr lint packages/react-doctor/src packages/react-doctor/tests
|
|
270
|
-
```
|
|
272
|
+
When on a feature branch without explicit flags, you'll be prompted: "Only scan changed files?" This prompt is suppressed in CI, `--json` mode, and non-interactive environments.
|
|
271
273
|
|
|
272
|
-
|
|
274
|
+
`--staged` and `--diff` cannot be combined.
|
|
273
275
|
|
|
274
|
-
|
|
276
|
+
## Agent and CI integration
|
|
275
277
|
|
|
276
|
-
|
|
277
|
-
pnpm --filter react-doctor test:regression
|
|
278
|
-
```
|
|
278
|
+
React Doctor detects 50+ coding agents (Claude Code, Cursor, Codex, OpenCode, Windsurf, and more) and adapts its behavior automatically:
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
- **Install for agents**: `npx react-doctor@latest install` writes agent-specific rule files (SKILL.md, AGENTS.md, .cursorrules) into your project so agents learn React best practices.
|
|
281
|
+
- **JSON output**: `--json` produces a structured `JsonReport` on stdout. Errors still produce a valid JSON document with `ok: false`. Use `--json-compact` for minimal whitespace.
|
|
282
|
+
- **Score-only output**: `--score` outputs just the numeric score (0-100), useful for threshold checks in agent loops.
|
|
283
|
+
- **GitHub Actions annotations**: `--annotations` emits `::error` / `::warning` format for inline PR annotations.
|
|
284
|
+
- **Exit codes**: `--fail-on error` (default) exits non-zero when error-severity diagnostics are found. Use `--fail-on warning` or `--fail-on none` to tune CI gating.
|
|
285
|
+
- **Programmatic API**: `import { diagnose } from "react-doctor/api"` for direct integration in scripts and automation.
|
|
281
286
|
|
|
282
|
-
|
|
283
|
-
- `REACT_DOCTOR_SPECIFIERS` pointing at the local tarball — `react-review`'s sandbox test detects the `.tgz`, uploads it into the Vercel Sandbox, and installs via `file:`.
|
|
284
|
-
- `REACT_DOCTOR_TEST_REPOS` defaulting to the first 10 repos from a curated fleet, comma-separated `owner/repo` entries.
|
|
287
|
+
In CI environments, prompts are automatically skipped and `--offline` is implied (no network round-trip; score is omitted from the output).
|
|
285
288
|
|
|
286
|
-
|
|
289
|
+
## Node.js API
|
|
287
290
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
- `~/Developer/react-review/apps/api/.env.local` exists (run `vercel env pull` inside `~/Developer/react-review/apps/api` to populate `@vercel/sandbox` credentials).
|
|
291
|
+
```js
|
|
292
|
+
import { diagnose, toJsonReport, summarizeDiagnostics } from "react-doctor/api";
|
|
291
293
|
|
|
292
|
-
|
|
294
|
+
const result = await diagnose("./path/to/your/react-project");
|
|
293
295
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
296
|
+
console.log(result.score); // { score: 82, label: "Great" } or null
|
|
297
|
+
console.log(result.diagnostics); // Diagnostic[]
|
|
298
|
+
console.log(result.project); // detected framework, React version, etc.
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`diagnose` accepts a second argument: `{ lint?: boolean }`.
|
|
302
|
+
|
|
303
|
+
```js
|
|
304
|
+
const report = toJsonReport(result, { version: "1.0.0" });
|
|
305
|
+
const counts = summarizeDiagnostics(result.diagnostics);
|
|
298
306
|
```
|
|
299
307
|
|
|
300
|
-
|
|
308
|
+
`react-doctor/api` re-exports `JsonReport`, `JsonReportSummary`, `JsonReportProjectEntry`, `JsonReportMode`, plus the lower-level `buildJsonReport` and `buildJsonReportError` builders. See [`packages/react-doctor/src/api.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/api.ts) for the full types.
|
|
301
309
|
|
|
302
310
|
## Leaderboard
|
|
303
311
|
|
|
@@ -331,15 +339,13 @@ Looking to contribute back? Clone the repo, install, build, and submit a PR.
|
|
|
331
339
|
```bash
|
|
332
340
|
git clone https://github.com/millionco/react-doctor
|
|
333
341
|
cd react-doctor
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
node packages/react-doctor/bin/react-doctor.js
|
|
342
|
+
pnpm install
|
|
343
|
+
pnpm build
|
|
344
|
+
node packages/react-doctor/bin/react-doctor.js /path/to/your/react-project
|
|
337
345
|
```
|
|
338
346
|
|
|
339
347
|
Find a bug? Head to the [issue tracker](https://github.com/millionco/react-doctor/issues).
|
|
340
348
|
|
|
341
|
-
Release notes are published on [GitHub Releases](https://github.com/millionco/react-doctor/releases).
|
|
342
|
-
|
|
343
349
|
### License
|
|
344
350
|
|
|
345
351
|
React Doctor is MIT-licensed open-source software.
|