diff --git a/packages/core/package.json b/packages/core/package.json index f9fba71f3..d89fa9d70 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,11 +28,18 @@ "effect": "4.0.0-beta.70", "eslint-plugin-react-hooks": "^7.1.1", "jiti": "^2.7.0", - "oxlint": "^1.66.0", "oxlint-plugin-react-doctor": "workspace:*", "picomatch": "^4.0.4", "typescript": "^6.0.3" }, + "peerDependencies": { + "oxlint": ">=1.0.0" + }, + "peerDependenciesMeta": { + "oxlint": { + "optional": true + } + }, "devDependencies": { "@effect/vitest": "4.0.0-beta.70", "@types/node": "^25.6.0", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7ced5adbb..985e6d258 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -89,3 +89,4 @@ export * from "./utils/warn-config-issue.js"; export * from "./runners/oxlint/capabilities.js"; export * from "./runners/oxlint/config.js"; export * from "./runners/oxlint/plugin-resolution.js"; +export { OxlintNotInstalledError } from "./runners/oxlint/resolve-paths.js"; diff --git a/packages/core/src/runners/oxlint/resolve-paths.ts b/packages/core/src/runners/oxlint/resolve-paths.ts index c3fdc9de8..b9feb983f 100644 --- a/packages/core/src/runners/oxlint/resolve-paths.ts +++ b/packages/core/src/runners/oxlint/resolve-paths.ts @@ -4,8 +4,27 @@ import * as path from "node:path"; const esmRequire = createRequire(import.meta.url); +export class OxlintNotInstalledError extends Error { + constructor() { + super( + `oxlint is not installed. Install it as a dependency:\n\n` + + ` npm install -D oxlint\n` + + ` # or: pnpm add -D oxlint\n` + + ` # or: yarn add -D oxlint\n\n` + + `In pnpm monorepos using vite-plus/vitest, install oxlint at the workspace root ` + + `to avoid duplicate module instances. See: https://github.com/millionco/react-doctor/issues`, + ); + this.name = "OxlintNotInstalledError"; + } +} + export const resolveOxlintBinary = (): string => { - const oxlintMainPath = esmRequire.resolve("oxlint"); + let oxlintMainPath: string; + try { + oxlintMainPath = esmRequire.resolve("oxlint"); + } catch { + throw new OxlintNotInstalledError(); + } const oxlintPackageDirectory = path.resolve(path.dirname(oxlintMainPath), ".."); return path.join(oxlintPackageDirectory, "bin", "oxlint"); }; diff --git a/packages/react-doctor/README.md b/packages/react-doctor/README.md index 9908c1c55..a166bcfd6 100644 --- a/packages/react-doctor/README.md +++ b/packages/react-doctor/README.md @@ -116,6 +116,54 @@ Prefer JSON? Use `doctor.config.json`: } ``` +## Troubleshooting + +### pnpm monorepos with vite-plus/vitest + +Installing `react-doctor` as a workspace devDependency in pnpm monorepos using vite-plus (or custom vitest aliases) can cause Vitest to fail with: + +``` +Error: Vitest failed to find the current suite. +``` + +This happens because `react-doctor` depends on `oxlint`, which can introduce a second physical install of vitest with a different peer-dependency fingerprint. pnpm creates multiple module instances when peer contexts differ, causing Vitest's hook registry to split. + +**Workarounds:** + +1. **Use `npx`/`pnpm dlx` instead of installing** (recommended): + + ```bash + pnpm dlx react-doctor@latest + ``` + + This avoids polluting the dependency graph entirely. + +2. **Install `oxlint` at the workspace root separately**: + + ```bash + pnpm add -Dw oxlint + ``` + + Then add `react-doctor` to a specific package instead of the workspace root. + +3. **Use pnpm overrides** to force a single vitest instance: + + ```yaml + # pnpm-workspace.yaml or package.json + pnpm: + overrides: + vitest: "catalog:viteplus" + peerDependencyRules: + allowedVersions: + vitest: "*" + ``` + +For programmatic use without the dependency graph, you can import types only: + +```ts +import type { ReactDoctorConfig } from "react-doctor/api"; +``` + ## Telemetry The CLI reports crashes, basic run traces, and anonymous usage counters to [Sentry](https://sentry.io/) to help us fix bugs and prioritize work. diff --git a/packages/react-doctor/package.json b/packages/react-doctor/package.json index a7afd7687..62cdbc1b9 100644 --- a/packages/react-doctor/package.json +++ b/packages/react-doctor/package.json @@ -66,7 +66,6 @@ "eslint-plugin-react-hooks": "^7.1.1", "jiti": "^2.7.0", "magicast": "^0.5.3", - "oxlint": "^1.66.0", "oxlint-plugin-react-doctor": "workspace:*", "prompts": "^2.4.2", "typescript": ">=5.0.4 <7", @@ -74,6 +73,14 @@ "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.1.0" }, + "peerDependencies": { + "oxlint": ">=1.0.0" + }, + "peerDependenciesMeta": { + "oxlint": { + "optional": true + } + }, "devDependencies": { "@react-doctor/api": "workspace:*", "@react-doctor/core": "workspace:*",