react-doctor 0.2.0-beta.4 → 0.2.0-beta.6
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 +140 -8
- package/dist/cli.js +2102 -152
- package/dist/index.d.ts +149 -0
- package/dist/index.js +2035 -133
- package/dist/skills/react-doctor/SKILL.md +4 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -20,10 +20,12 @@ Works with Next.js, Vite, and React Native.
|
|
|
20
20
|
Run this at your project root:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
npx
|
|
23
|
+
npx react-doctor@latest
|
|
24
24
|
```
|
|
25
25
|
|
|
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,
|
|
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, and accessibility. Rules toggle automatically based on your framework and React version.
|
|
27
|
+
|
|
28
|
+
> **Migration note:** React Doctor used to bundle [knip](https://knip.dev/) for dead-code detection. That integration was removed in v0.2 — if you want dead-code analysis, run `npx knip` directly as part of your own pre-commit or CI pipeline.
|
|
27
29
|
|
|
28
30
|
https://github.com/user-attachments/assets/07cc88d9-9589-44c3-aa73-5d603cb1c570
|
|
29
31
|
|
|
@@ -32,7 +34,7 @@ https://github.com/user-attachments/assets/07cc88d9-9589-44c3-aa73-5d603cb1c570
|
|
|
32
34
|
Teach your coding agent React best practices so it stops writing the bad code in the first place.
|
|
33
35
|
|
|
34
36
|
```bash
|
|
35
|
-
npx
|
|
37
|
+
npx react-doctor@latest install
|
|
36
38
|
```
|
|
37
39
|
|
|
38
40
|
You'll be prompted to pick which detected agents to install for. Pass `--yes` to skip prompts.
|
|
@@ -68,16 +70,87 @@ jobs:
|
|
|
68
70
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
69
71
|
```
|
|
70
72
|
|
|
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.
|
|
73
|
+
When `github-token` is set on `pull_request` events, findings are posted (and updated) as a sticky PR comment. The action also exposes a `score` output (0–100) you can use in subsequent steps.
|
|
74
|
+
|
|
75
|
+
**Inputs:** `directory`, `verbose`, `project`, `diff`, `github-token`, `fail-on` (`error` / `warning` / `none`), `offline`, `annotations`, `node-version`. See [`action.yml`](https://github.com/millionco/react-doctor/blob/main/action.yml) for full descriptions.
|
|
76
|
+
|
|
77
|
+
#### PR feedback modes
|
|
78
|
+
|
|
79
|
+
Pick one or both; they're independent.
|
|
72
80
|
|
|
73
|
-
**
|
|
81
|
+
- **Comments only** (default): set `github-token`.
|
|
82
|
+
- **Annotations only**: set `annotations: true`.
|
|
83
|
+
- **Both**: set `github-token` and `annotations: true`. Annotation lines are stripped from the comment body.
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
- uses: millionco/react-doctor@main
|
|
87
|
+
with:
|
|
88
|
+
diff: main
|
|
89
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
90
|
+
annotations: true
|
|
91
|
+
```
|
|
74
92
|
|
|
75
93
|
Prefer not to add a marketplace action? The bare `npx` form works too:
|
|
76
94
|
|
|
77
95
|
```yaml
|
|
78
|
-
- run: npx
|
|
96
|
+
- run: npx react-doctor@latest --fail-on warning
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## PR blocking and exit codes
|
|
100
|
+
|
|
101
|
+
Two independent gates can block a PR — pick one or both:
|
|
102
|
+
|
|
103
|
+
- **`--fail-on <level>`** exits non-zero on diagnostics: `error` (default, any error-severity rule fires), `warning` (any diagnostic fires), or `none` (never). Runs against the `ciFailure` surface, so the default `design`-tag exclusion still applies.
|
|
104
|
+
- **Score floor** — a follow-up step that reads the action's `score` output and `exit 1`s when it's below your threshold.
|
|
105
|
+
|
|
106
|
+
Combine `--fail-on` with `--diff <base>` to scope the gate to the PR's changed files only — that's the built-in way to fail on **new** regressions without dragging in baseline backlog. There is no separate `--fail-on-new` flag.
|
|
107
|
+
|
|
108
|
+
`--annotations` (bare `npx` only) and `github-token` (sticky PR comment) are visualization layers and never change the exit code.
|
|
109
|
+
|
|
110
|
+
### Examples
|
|
111
|
+
|
|
112
|
+
**Advisory mode** — never blocks, always comments:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
- uses: millionco/react-doctor@main
|
|
116
|
+
with:
|
|
117
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
118
|
+
fail-on: none
|
|
79
119
|
```
|
|
80
120
|
|
|
121
|
+
**Regression-only mode** — fail only on new diagnostics introduced by the PR:
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
- uses: actions/checkout@v5
|
|
125
|
+
with:
|
|
126
|
+
fetch-depth: 0 # required for `diff`
|
|
127
|
+
- uses: millionco/react-doctor@main
|
|
128
|
+
with:
|
|
129
|
+
diff: main
|
|
130
|
+
fail-on: warning
|
|
131
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Strict threshold mode** — fail when the baseline score drops below a floor:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
- id: doctor
|
|
138
|
+
uses: millionco/react-doctor@main
|
|
139
|
+
with:
|
|
140
|
+
fail-on: error
|
|
141
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
142
|
+
- env:
|
|
143
|
+
SCORE: ${{ steps.doctor.outputs.score }}
|
|
144
|
+
FLOOR: "80"
|
|
145
|
+
run: |
|
|
146
|
+
if [ -n "$SCORE" ] && [ "$SCORE" -lt "$FLOOR" ]; then
|
|
147
|
+
echo "::error::React Doctor score $SCORE is below floor $FLOOR"
|
|
148
|
+
exit 1
|
|
149
|
+
fi
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Pin a specific `react-doctor` version when using a score floor — new rule releases can lower the score even when your code hasn't changed (see [Scoring](#scoring)).
|
|
153
|
+
|
|
81
154
|
## Configuration
|
|
82
155
|
|
|
83
156
|
Create a `react-doctor.config.json` in your project root:
|
|
@@ -113,6 +186,38 @@ React Doctor respects `.gitignore`, `.eslintignore`, `.oxlintignore`, `.prettier
|
|
|
113
186
|
|
|
114
187
|
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.
|
|
115
188
|
|
|
189
|
+
#### Surface controls (CLI, PR comments, score, CI failure)
|
|
190
|
+
|
|
191
|
+
Diagnostics flow through four independent surfaces — `cli`, `prComment`, `score`, and `ciFailure` — and each one can be tuned per tag, category, or rule id. By default the `design` tag (Tailwind shorthand cleanup like `w-5 h-5 → size-5`, pure-black backgrounds, gradient text, …) stays visible on the local CLI but is excluded from the PR comment, the score, and the `--fail-on` gate so style cleanup can't dilute meaningful React findings:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"surfaces": {
|
|
196
|
+
"prComment": {
|
|
197
|
+
"includeTags": ["design"],
|
|
198
|
+
"excludeCategories": ["Performance"]
|
|
199
|
+
},
|
|
200
|
+
"score": { "includeRules": ["react-doctor/design-no-redundant-size-axes"] },
|
|
201
|
+
"ciFailure": { "excludeTags": ["test-noise"] }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Each surface accepts `includeTags`, `excludeTags`, `includeCategories`, `excludeCategories`, `includeRules`, and `excludeRules`. Include wins over exclude when both match. Run the CLI with `--pr-comment` (the GitHub Action passes it automatically when `github-token` is set) to apply the `prComment` surface to the printed output destined for sticky PR comments.
|
|
207
|
+
|
|
208
|
+
#### Rule severity (`rules`, `categories`)
|
|
209
|
+
|
|
210
|
+
Same shape as ESLint / oxlint. `rules` is ESLint's exact field; `categories` mirrors oxlint's, keyed by React Doctor display categories (`"React Native"`, `"Server"`, `"Architecture"`, …).
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"rules": { "react-doctor/no-array-index-as-key": "error" },
|
|
215
|
+
"categories": { "React Native": "warn" }
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Per-rule wins over per-category. `"off"` short-circuits before the rule runs; `"warn"` / `"error"` re-stamps the diagnostic so every channel — CLI, PR comment, score, `--fail-on` — sees the chosen severity, including for external-plugin rules. Use `surfaces` instead when you only want to hide a rule from one channel; use `ignore.tags` to silence a whole tag-defined family (`"design"`, `"test-noise"`, `"migration-hint"`) that doesn't align with a single category.
|
|
220
|
+
|
|
116
221
|
#### Optional companion plugins
|
|
117
222
|
|
|
118
223
|
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.
|
|
@@ -210,6 +315,8 @@ Options:
|
|
|
210
315
|
--offline skip the score API and share URL (no score shown)
|
|
211
316
|
--fail-on <level> exit with error on diagnostics: error, warning, none
|
|
212
317
|
--annotations output diagnostics as GitHub Actions annotations
|
|
318
|
+
--pr-comment tune CLI output for sticky PR comments (drops design
|
|
319
|
+
cleanup from the printed list and fail-on gate)
|
|
213
320
|
--explain <file:line> diagnose why a rule fired or why a suppression didn't apply
|
|
214
321
|
--why <file:line> alias for --explain
|
|
215
322
|
-h, --help display help
|
|
@@ -235,6 +342,7 @@ When a suppression isn't working, `--explain <file:line>` (or its alias `--why <
|
|
|
235
342
|
| `offline` | `boolean` | `false` |
|
|
236
343
|
| `textComponents` | `string[]` | `[]` |
|
|
237
344
|
| `rawTextWrapperComponents` | `string[]` | `[]` |
|
|
345
|
+
| `serverAuthFunctionNames` | `string[]` | `[]` |
|
|
238
346
|
| `respectInlineDisables` | `boolean` | `true` |
|
|
239
347
|
| `adoptExistingLintConfig` | `boolean` | `true` |
|
|
240
348
|
| `ignore.tags` | `string[]` | `[]` |
|
|
@@ -243,10 +351,34 @@ When a suppression isn't working, `--explain <file:line>` (or its alias `--why <
|
|
|
243
351
|
|
|
244
352
|
`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.
|
|
245
353
|
|
|
354
|
+
`serverAuthFunctionNames` teaches `server-auth-actions` about custom auth guards your codebase wraps around its auth library (e.g. `requireWorkspaceMember`, `ensureSignedIn`). Listed names are accepted as a valid top-of-action auth check whether called bare (`requireWorkspaceMember()`) or as a member (`guards.requireWorkspaceMember()`), and — unlike the built-in default list — are treated as distinctive so the receiver is not re-validated.
|
|
355
|
+
|
|
246
356
|
`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"`.
|
|
247
357
|
|
|
248
358
|
`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.
|
|
249
359
|
|
|
360
|
+
### React Native rules in mixed monorepos
|
|
361
|
+
|
|
362
|
+
`rn-*` rules respect per-package boundaries automatically. In a mixed React Native + web monorepo (`apps/mobile` alongside `apps/web` / `apps/vite-app` / `packages/storybook` / `apps/docs`), every `rn-*` rule walks up to the file's nearest `package.json` before running:
|
|
363
|
+
|
|
364
|
+
- Packages that declare `react-native`, `react-native-tvos`, `expo`, `expo-router`, `@expo/*`, `react-native-windows`, `react-native-macos`, anything under the `@react-native/` or `@react-native-` namespaces (`@react-native-firebase/app`, `@react-native-async-storage/async-storage`, …), or Metro's top-level `"react-native"` resolution field → rules ON.
|
|
365
|
+
- Packages that declare a web-only framework (`next`, `vite`, `react-scripts`, `gatsby`, `@remix-run/*`, `@docusaurus/*`, `@storybook/*`, or plain `react-dom` without an RN sibling) → rules OFF.
|
|
366
|
+
- Packages with no clear local signal → fall back to the project-level framework detection.
|
|
367
|
+
|
|
368
|
+
File extensions override the package classification when they're unambiguous: `*.web.tsx` / `*.web.jsx` are always skipped (Metro resolves these only against `react-native-web`); `*.ios.tsx` / `*.android.tsx` / `*.native.tsx` are always scanned (mobile-only).
|
|
369
|
+
|
|
370
|
+
The detection is bidirectional: a web-rooted monorepo (root `package.json` declares `next` or `vite`) still loads the `rn-*` rules when any workspace targets React Native — the file-level boundary then keeps them silent on the web workspaces and active on the mobile ones.
|
|
371
|
+
|
|
372
|
+
`rn-no-raw-text` additionally short-circuits raw text inside platform-fork branches:
|
|
373
|
+
|
|
374
|
+
- `if (Platform.OS === "web") { … }` consequent — and the `else` branch of `if (Platform.OS !== "web")`.
|
|
375
|
+
- `Platform.OS === "web" ? <X /> : …` ternaries, `Platform.OS === "web" && <X />` short-circuits, and the reversed-operand form `"web" === Platform.OS`.
|
|
376
|
+
- `switch (Platform.OS) { case "web": … }` case bodies (other cases still report).
|
|
377
|
+
- `Platform.select({ web: <X />, default: <Y /> })` — only the `web` arm is exempt.
|
|
378
|
+
- `Platform?.OS === "web"` (optional chain) and `Platform.OS! === "web"` (TS non-null assertion) parse the same way as the bare form.
|
|
379
|
+
|
|
380
|
+
The walker stops at function and `Program` boundaries — JSX defined inside a callback hoisted out of a `Platform.OS` branch does not inherit the parent guard. Negative platform checks like `Platform.OS === "ios"` are deliberately NOT treated as web exemptions; only the explicit web branch is.
|
|
381
|
+
|
|
250
382
|
## Scoring
|
|
251
383
|
|
|
252
384
|
The health score formula: `100 - (unique_error_rules x 1.5) - (unique_warning_rules x 0.75)`.
|
|
@@ -280,8 +412,8 @@ React Doctor detects 50+ coding agents (Claude Code, Cursor, Codex, OpenCode, Wi
|
|
|
280
412
|
- **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
413
|
- **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
414
|
- **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.
|
|
415
|
+
- **GitHub Actions annotations**: `--annotations` emits `::error` / `::warning` format for inline PR annotations. Annotations don't change the exit code.
|
|
416
|
+
- **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. See [PR blocking and exit codes](#pr-blocking-and-exit-codes) for the full model — including how to fail only on new regressions vs. fail on the baseline score.
|
|
285
417
|
- **Programmatic API**: `import { diagnose } from "react-doctor/api"` for direct integration in scripts and automation.
|
|
286
418
|
|
|
287
419
|
In CI environments, prompts are automatically skipped and `--offline` is implied (no network round-trip; score is omitted from the output).
|