react-doctor 0.1.6 → 0.2.0-beta.0
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 +167 -130
- package/dist/cli.js +19865 -4136
- package/dist/compat-CM6aj69a.js +1851 -0
- package/dist/compat.d.ts +53 -0
- package/dist/compat.js +3 -0
- package/dist/errors-ZdckckLr.d.ts +87 -0
- package/dist/eslint-plugin-BIjw2MeW.d.ts +105 -0
- package/dist/eslint-plugin.d.ts +2 -57
- package/dist/eslint-plugin.js +32 -6946
- package/dist/index-CFzh1cBi.d.ts +1798 -0
- package/dist/metadata-se470mRG.js +604 -0
- package/dist/oxlint-plugin.d.ts +2 -0
- package/dist/oxlint-plugin.js +7 -0
- package/dist/rules-BfZ4Ujfv.js +16701 -0
- package/dist/rules-ebKa330H.d.ts +28 -0
- package/dist/score-CzbtoFAu.js +69 -0
- package/dist/score.d.ts +35 -0
- package/dist/score.js +2 -0
- package/dist/sdk.d.ts +90 -0
- package/dist/sdk.js +17 -0
- package/package.json +22 -27
- package/dist/index.d.ts +0 -272
- package/dist/index.js +0 -3376
- package/dist/react-doctor-plugin.d.ts +0 -31
- package/dist/react-doctor-plugin.js +0 -6720
package/README.md
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
|
|
10
10
|
Your agent writes bad React, this catches it.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
React Doctor scans React projects with native codebase analysis, a curated oxlint rule set, and actionable diagnostics.
|
|
13
13
|
|
|
14
|
-
Works with Next.js,
|
|
14
|
+
Works with React, Next.js, React Native, Expo, TanStack Start, and common React ecosystem libraries.
|
|
15
15
|
|
|
16
|
-
### [See it in action
|
|
16
|
+
### [See it in action](https://react.doctor)
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Run
|
|
19
19
|
|
|
20
20
|
Run this at your project root:
|
|
21
21
|
|
|
@@ -23,104 +23,119 @@ Run this at your project root:
|
|
|
23
23
|
npx -y react-doctor@latest .
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
By default React Doctor runs:
|
|
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.
|
|
27
33
|
|
|
28
34
|
https://github.com/user-attachments/assets/07cc88d9-9589-44c3-aa73-5d603cb1c570
|
|
29
35
|
|
|
30
|
-
##
|
|
36
|
+
## React Doctor Skill
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
React Doctor also ships as an agent Skill. The CLI catches problems after code is written; the Skill teaches your coding agent the same React, framework, and performance guidance before it writes the next patch.
|
|
33
39
|
|
|
34
40
|
```bash
|
|
35
41
|
npx -y react-doctor@latest install
|
|
36
42
|
```
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Works with Claude Code, Cursor, Codex, OpenCode, and 50+ other agents.
|
|
44
|
+
Use the Skill when you want agents to:
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
- avoid common state and effect mistakes
|
|
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
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
The installer detects supported coding agents and prompts you to choose where to install the Skill. Pass `--yes` to accept the default detected targets.
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
name: React Doctor
|
|
53
|
+
## CLI
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
branches: [main]
|
|
55
|
+
```bash
|
|
56
|
+
react-doctor [directory]
|
|
57
|
+
```
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
contents: read
|
|
56
|
-
pull-requests: write # required to post PR comments
|
|
59
|
+
Useful flags:
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
```bash
|
|
62
|
+
react-doctor apps/web --json
|
|
63
|
+
react-doctor apps/web --json --json-compact
|
|
64
|
+
react-doctor apps/web --no-lint
|
|
65
|
+
react-doctor apps/web --no-dead-code
|
|
66
|
+
react-doctor apps/web --custom-rules-only
|
|
67
|
+
react-doctor apps/web --staged
|
|
68
|
+
react-doctor apps/web --unstaged
|
|
69
|
+
react-doctor apps/web --changed
|
|
70
|
+
react-doctor apps/web --diff main
|
|
71
|
+
react-doctor apps/web --offline
|
|
72
|
+
react-doctor apps/web --fail-on error
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
|
|
75
|
+
Changed-file modes only inspect matching source files:
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
- `--staged` scans the git index for pre-commit flows.
|
|
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`.
|
|
74
81
|
|
|
75
|
-
|
|
82
|
+
If no changed source files are found, source checks are skipped instead of falling back to a full scan.
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
- run: npx -y react-doctor@latest --fail-on warning
|
|
79
|
-
```
|
|
84
|
+
`--fail-on` accepts `error`, `warning`, or `none`.
|
|
80
85
|
|
|
81
86
|
## Configuration
|
|
82
87
|
|
|
83
|
-
|
|
88
|
+
React Doctor looks for configuration in:
|
|
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.
|
|
84
94
|
|
|
85
95
|
```json
|
|
86
96
|
{
|
|
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"],
|
|
87
109
|
"ignore": {
|
|
88
|
-
"rules": ["react/no-
|
|
110
|
+
"rules": ["react-doctor/no-gradient-text"],
|
|
89
111
|
"files": ["src/generated/**"],
|
|
90
112
|
"overrides": [
|
|
91
113
|
{
|
|
92
|
-
"files": ["
|
|
93
|
-
"rules": ["react-doctor/no-
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"files": ["components/search/HighlightedSnippet.tsx"],
|
|
97
|
-
"rules": ["react/no-danger"]
|
|
114
|
+
"files": ["src/legacy/**"],
|
|
115
|
+
"rules": ["react-doctor/no-default-props"]
|
|
98
116
|
}
|
|
99
117
|
]
|
|
100
118
|
}
|
|
101
119
|
}
|
|
102
120
|
```
|
|
103
121
|
|
|
104
|
-
|
|
122
|
+
Pick the narrowest ignore that fits:
|
|
105
123
|
|
|
106
|
-
- **`ignore.rules`** silences a rule across the
|
|
107
|
-
- **`ignore.files`** silences
|
|
124
|
+
- **`ignore.rules`** silences a rule across the codebase.
|
|
125
|
+
- **`ignore.files`** silences every rule on matched files.
|
|
108
126
|
- **`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.
|
|
109
127
|
|
|
110
|
-
|
|
128
|
+
React Doctor scans only its curated rule set by default. Set `adoptExistingLintConfig` to `true` to adopt the first JSON `.oxlintrc.json` or `.eslintrc.json` found while walking ancestors.
|
|
111
129
|
|
|
112
|
-
|
|
130
|
+
`ignoredTags` lets you trim noisy categories without turning the whole scanner off. For example, `["design"]` keeps structural React checks while skipping subjective visual style suggestions.
|
|
113
131
|
|
|
114
|
-
|
|
132
|
+
For React Native, `textComponents` marks custom components that behave like `<Text>`, while `rawTextWrapperComponents` marks components that safely wrap string-only children in text internally.
|
|
115
133
|
|
|
116
|
-
|
|
134
|
+
## Scoring
|
|
117
135
|
|
|
118
|
-
|
|
136
|
+
Scores are a simple health signal, not a moral judgment. React Doctor starts at 100, subtracts more for error-level rule families than warning-level families, and counts a repeated rule once so one noisy pattern does not dominate the whole project.
|
|
119
137
|
|
|
120
|
-
|
|
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/*` |
|
|
138
|
+
The score can move between releases as rules become more precise, new framework rules are added, or noisy checks are demoted. Treat the detailed diagnostics as the source of truth and use the score for trend tracking across repeated runs.
|
|
124
139
|
|
|
125
140
|
### Inline suppressions
|
|
126
141
|
|
|
@@ -139,7 +154,7 @@ When two rules fire on the same line, you have two equivalent options. Comma-sep
|
|
|
139
154
|
const [localSearch, setLocalSearch] = useState(searchQuery);
|
|
140
155
|
```
|
|
141
156
|
|
|
142
|
-
Or stack one comment per rule directly above the diagnostic
|
|
157
|
+
Or stack one comment per rule directly above the diagnostic:
|
|
143
158
|
|
|
144
159
|
```tsx
|
|
145
160
|
// react-doctor-disable-next-line react-doctor/rerender-state-only-in-handlers
|
|
@@ -147,122 +162,142 @@ Or stack one comment per rule directly above the diagnostic. Stacked comments ar
|
|
|
147
162
|
const [localSearch, setLocalSearch] = useState(searchQuery);
|
|
148
163
|
```
|
|
149
164
|
|
|
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
|
-
|
|
152
165
|
Block comments work inside JSX:
|
|
153
166
|
|
|
154
167
|
<!-- prettier-ignore -->
|
|
155
168
|
```tsx
|
|
156
|
-
{/* react-doctor-disable-next-line
|
|
169
|
+
{/* react-doctor-disable-next-line no-danger */}
|
|
157
170
|
<div dangerouslySetInnerHTML={{ __html }} />
|
|
158
171
|
```
|
|
159
172
|
|
|
160
173
|
For multi-line JSX, putting the comment immediately above the opening tag covers the entire attribute list (matching ESLint convention).
|
|
161
174
|
|
|
162
|
-
## Lint
|
|
175
|
+
## Lint Integrations
|
|
163
176
|
|
|
164
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.
|
|
165
178
|
|
|
166
|
-
|
|
179
|
+
Oxlint (`.oxlintrc.json`):
|
|
167
180
|
|
|
168
181
|
```jsonc
|
|
169
182
|
{
|
|
170
183
|
"jsPlugins": [{ "name": "react-doctor", "specifier": "react-doctor/oxlint-plugin" }],
|
|
171
184
|
"rules": {
|
|
172
185
|
"react-doctor/no-fetch-in-effect": "warn",
|
|
173
|
-
"react-doctor/no-derived-state-effect": "warn",
|
|
174
186
|
},
|
|
175
187
|
}
|
|
176
188
|
```
|
|
177
189
|
|
|
178
|
-
|
|
190
|
+
ESLint:
|
|
179
191
|
|
|
180
192
|
```js
|
|
181
193
|
import reactDoctor from "react-doctor/eslint-plugin";
|
|
182
194
|
|
|
183
195
|
export default [
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
{
|
|
197
|
+
plugins: {
|
|
198
|
+
"react-doctor": reactDoctor,
|
|
199
|
+
},
|
|
200
|
+
rules: {
|
|
201
|
+
"react-doctor/no-fetch-in-effect": "warn",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
189
204
|
];
|
|
190
205
|
```
|
|
191
206
|
|
|
192
|
-
The
|
|
207
|
+
The ESLint wrapper reuses the same rule implementations and metadata as the oxlint plugin.
|
|
208
|
+
|
|
209
|
+
## SDK
|
|
193
210
|
|
|
194
|
-
|
|
211
|
+
```ts
|
|
212
|
+
import { createReactDoctor, inspectReactProject } from "react-doctor";
|
|
195
213
|
|
|
214
|
+
const result = await inspectReactProject({
|
|
215
|
+
rootDirectory: "apps/web",
|
|
216
|
+
lint: true,
|
|
217
|
+
deadCode: true,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const reactDoctor = createReactDoctor({ rootDirectory: "apps/web" });
|
|
221
|
+
const nextResult = await reactDoctor.inspect();
|
|
196
222
|
```
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
--staged scan only staged files (for pre-commit hooks)
|
|
211
|
-
--offline skip telemetry
|
|
212
|
-
--fail-on <level> exit with error on diagnostics: error, warning, none
|
|
213
|
-
--annotations output diagnostics as GitHub Actions annotations
|
|
214
|
-
--explain <file:line> diagnose why a rule fired or why a suppression didn't apply
|
|
215
|
-
--why <file:line> alias for --explain
|
|
216
|
-
-h, --help display help
|
|
223
|
+
|
|
224
|
+
The result includes project metadata, check results, normalized issues, score, and timing.
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { buildReactDoctorJsonReport } from "react-doctor";
|
|
228
|
+
|
|
229
|
+
const report = buildReactDoctorJsonReport(result);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Typed runtime errors are exported from the main SDK:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { ReactDoctorInvalidConfigError, isReactDoctorError } from "react-doctor";
|
|
217
236
|
```
|
|
218
237
|
|
|
219
|
-
|
|
238
|
+
## Compatibility API
|
|
220
239
|
|
|
221
|
-
|
|
240
|
+
Deprecated compatibility APIs live under `react-doctor/api` and are intentionally isolated from the main runtime.
|
|
222
241
|
|
|
223
|
-
|
|
242
|
+
```ts
|
|
243
|
+
import { diagnose, clearCaches } from "react-doctor/api";
|
|
224
244
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
| `ignore.overrides` | `{ files, rules? }[]` | `[]` |
|
|
230
|
-
| `lint` | `boolean` | `true` |
|
|
231
|
-
| `deadCode` | `boolean` | `true` |
|
|
232
|
-
| `verbose` | `boolean` | `false` |
|
|
233
|
-
| `diff` | `boolean \| string` | |
|
|
234
|
-
| `failOn` | `"error" \| "warning" \| "none"` | `"none"` |
|
|
235
|
-
| `customRulesOnly` | `boolean` | `false` |
|
|
236
|
-
| `share` | `boolean` | `true` |
|
|
237
|
-
| `textComponents` | `string[]` | `[]` |
|
|
238
|
-
| `rawTextWrapperComponents` | `string[]` | `[]` |
|
|
239
|
-
| `respectInlineDisables` | `boolean` | `true` |
|
|
240
|
-
| `adoptExistingLintConfig` | `boolean` | `true` |
|
|
245
|
+
const result = await diagnose("apps/web", {
|
|
246
|
+
lint: true,
|
|
247
|
+
deadCode: true,
|
|
248
|
+
});
|
|
241
249
|
|
|
242
|
-
|
|
250
|
+
clearCaches();
|
|
251
|
+
```
|
|
243
252
|
|
|
244
|
-
`
|
|
253
|
+
Prefer `createReactDoctor()` or `inspectReactProject()` for new integrations.
|
|
245
254
|
|
|
246
|
-
##
|
|
255
|
+
## Development
|
|
247
256
|
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
Run package checks from the package directory:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
nr typecheck
|
|
261
|
+
nr test
|
|
262
|
+
nr build
|
|
263
|
+
```
|
|
250
264
|
|
|
251
|
-
|
|
265
|
+
Run workspace formatting and linting from the repository root:
|
|
252
266
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
267
|
+
```bash
|
|
268
|
+
nr format:check packages/react-doctor/src packages/react-doctor/tests
|
|
269
|
+
nr lint packages/react-doctor/src packages/react-doctor/tests
|
|
256
270
|
```
|
|
257
271
|
|
|
258
|
-
|
|
272
|
+
## Regression testing
|
|
259
273
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
274
|
+
Drive the sandbox test suite in the sibling [`react-review`](https://github.com/millionco/react-review) repo against a fleet of real React projects, using the local working copy of `react-doctor` (packed into a tarball).
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pnpm --filter react-doctor test:regression
|
|
263
278
|
```
|
|
264
279
|
|
|
265
|
-
|
|
280
|
+
The script builds the package, packs it into `packages/react-doctor/.regression/react-doctor-<version>.tgz`, then shells out to `~/Developer/react-review/apps/api` and runs `pnpm test` with:
|
|
281
|
+
|
|
282
|
+
- `GITHUB_TOKEN` from `gh auth token` (required so GitHub does not 403 the tarball downloads).
|
|
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.
|
|
285
|
+
|
|
286
|
+
Preconditions the script checks for and surfaces clear errors when missing:
|
|
287
|
+
|
|
288
|
+
- `gh auth token` succeeds (run `gh auth login` first if not).
|
|
289
|
+
- `~/Developer/react-review/apps/api` exists.
|
|
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
|
+
|
|
292
|
+
Overrides:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
REACT_DOCTOR_REGRESSION_SAMPLE=25 pnpm --filter react-doctor test:regression
|
|
296
|
+
REACT_DOCTOR_REGRESSION_SAMPLE=all pnpm --filter react-doctor test:regression
|
|
297
|
+
REACT_DOCTOR_TEST_REPOS="vercel/ai-chatbot,shadcn-ui/ui" pnpm --filter react-doctor test:regression
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Each repo runs sequentially inside a Vercel Sandbox; expect a few minutes per repo. The default 10-repo sample is the sane batch size for a single run.
|
|
266
301
|
|
|
267
302
|
## Leaderboard
|
|
268
303
|
|
|
@@ -272,7 +307,7 @@ Top React codebases scanned by React Doctor, ranked by score. Updated automatica
|
|
|
272
307
|
<!-- prettier-ignore -->
|
|
273
308
|
| # | Repo | Score |
|
|
274
309
|
| -- | ---- | ----: |
|
|
275
|
-
| 1 | [executor](https://github.com/RhysSullivan/executor) |
|
|
310
|
+
| 1 | [executor](https://github.com/RhysSullivan/executor) | 94 |
|
|
276
311
|
| 2 | [nodejs.org](https://github.com/nodejs/nodejs.org) | 86 |
|
|
277
312
|
| 3 | [tldraw](https://github.com/tldraw/tldraw) | 70 |
|
|
278
313
|
| 4 | [t3code](https://github.com/pingdotgg/t3code) | 68 |
|
|
@@ -296,13 +331,15 @@ Looking to contribute back? Clone the repo, install, build, and submit a PR.
|
|
|
296
331
|
```bash
|
|
297
332
|
git clone https://github.com/millionco/react-doctor
|
|
298
333
|
cd react-doctor
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
node packages/react-doctor/bin/react-doctor.js /
|
|
334
|
+
ni
|
|
335
|
+
nr build
|
|
336
|
+
node packages/react-doctor/bin/react-doctor.js apps/web
|
|
302
337
|
```
|
|
303
338
|
|
|
304
339
|
Find a bug? Head to the [issue tracker](https://github.com/millionco/react-doctor/issues).
|
|
305
340
|
|
|
341
|
+
Release notes are published on [GitHub Releases](https://github.com/millionco/react-doctor/releases).
|
|
342
|
+
|
|
306
343
|
### License
|
|
307
344
|
|
|
308
345
|
React Doctor is MIT-licensed open-source software.
|