mirror of
https://github.com/millionco/react-doctor.git
synced 2026-09-14 20:00:24 +08:00
aa61efae28
* refactor: collapse @react-doctor/types and @react-doctor/project-info into core Eliminates two private workspace packages whose only role was to namespace shared TS types and project-discovery helpers — neither was a real consumer boundary, and the indirection cost a `workspace:*` edge per leaf package without saving any code. **Layout** - `packages/types/` → `packages/core/src/types/` (10 files, pure TS types) - `packages/project-info/` → `packages/core/src/project-info/` (27 files, discoverProject + framework detection + monorepo helpers) - `packages/types/src/react-native-dependency-names.ts` → `packages/oxlint-plugin-react-doctor/src/react-native-dependency-names.ts` (the canonical RN-aware-package list lives next to its heaviest consumer — the rule gate — and is re-exported from core to avoid the rule-plugin ↔ core dependency cycle the previous types package was specifically designed to break). The two packages are deleted from the workspace; every consumer (`react-doctor`, `api`, `oxlint-plugin-react-doctor`, `actions/review`, +130 source/test files) now imports from `@react-doctor/core` directly. **Errors cleanup** Dropped the legacy plain-class `ReactDoctorError` and `isReactDoctorError` from `project-info`. The four narrow errors (`ProjectNotFoundError`, `NoReactDependencyError`, `PackageJsonNotFoundError`, `AmbiguousProjectError`) now extend `Error` directly — the only valid hierarchy is the tagged `Schema.TaggedErrorClass` `ReactDoctorError` from `core/errors.ts`, which has been the canonical one since the Effect v4 refactor. Added `isProjectDiscoveryError` as a narrow type guard for the four leaves. The discovery errors are thrown synchronously BEFORE the Effect runtime takes over (at `diagnose()` / CLI entry points); inside the runtime, the Project service's `translateProjectInfoError` converts each one to its tagged-reason equivalent. **Schema vs type name collisions** `schemas.ts` exports `Diagnostic` / `JsonReport` / `JsonReportMode` / … as Schema.Class values, while `types/` exports the same names as TS types. The core barrel re-exports the TS-type versions (broad-strokes API) and exposes the Schema classes via a new `@react-doctor/core/schemas` subpath export for the two callers that need them (`Reporter` service, smoke validator, schema tests). **Validation** - `pnpm typecheck` ✓ - `pnpm lint` ✓ - `pnpm format` ✓ - `pnpm test` ✓ (1449 passing, 3 skipped — no regressions) - `pnpm smoke:json-report` ✓ Co-authored-by: Cursor <cursoragent@cursor.com> * fix(core): keep RN-dependency constants out of types barrel to avoid pulling oxlint plugin into discovery Bugbot caught (medium severity): re-exporting `isReactNativeDependencyName` from `oxlint-plugin-react-doctor` via the core types barrel meant every consumer of `@react-doctor/core` (including `discoverProject` / `discoverReactSubprojects`) would load the entire 286-rule plugin at module-init. Adds a tiny standalone leaf at `core/src/project-info/internal-rn-dependency-names.ts` (zero imports, just the constants the workspace-discovery gate needs) and switches `is-package-json-react-native-aware.ts` to use it. The types barrel no longer re-exports the plugin-side constants — rule consumers import them directly from `oxlint-plugin-react-doctor`. The duplication is intentional and documented at both files; the two lists are short enough that a future regression test in the plugin can observe the union. Validated: typecheck + test (1449 pass) + lint + format all green. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
56 lines
2.3 KiB
TypeScript
56 lines
2.3 KiB
TypeScript
import { describe, expect, it } from "vite-plus/test";
|
|
import { peerRangeMinMajor } from "@react-doctor/core";
|
|
|
|
describe("peerRangeMinMajor", () => {
|
|
it("returns the lowest concrete major from OR ranges", () => {
|
|
expect(peerRangeMinMajor("^17.0.0 || ^18.0.0 || ^19.0.0")).toBe(17);
|
|
expect(peerRangeMinMajor("^18.0.0 || ^19.0.0")).toBe(18);
|
|
expect(peerRangeMinMajor("^19.0.0")).toBe(19);
|
|
expect(peerRangeMinMajor(">=17")).toBe(17);
|
|
expect(peerRangeMinMajor(">=18 <20")).toBe(18);
|
|
expect(peerRangeMinMajor("18 || 19")).toBe(18);
|
|
});
|
|
|
|
it("returns null for upper-bound-only ranges", () => {
|
|
expect(peerRangeMinMajor("<19")).toBeNull();
|
|
expect(peerRangeMinMajor("<=18")).toBeNull();
|
|
expect(peerRangeMinMajor("< 19")).toBeNull();
|
|
});
|
|
|
|
it("returns null when any OR branch has only an upper bound", () => {
|
|
expect(peerRangeMinMajor("<19 || ^19.0.0")).toBeNull();
|
|
expect(peerRangeMinMajor("<=18 || ^19.0.0")).toBeNull();
|
|
expect(peerRangeMinMajor("^19.0.0 || <19")).toBeNull();
|
|
expect(peerRangeMinMajor("<20 || >=17")).toBeNull();
|
|
});
|
|
|
|
it("returns null for wildcards, tags, protocols, and missing input", () => {
|
|
expect(peerRangeMinMajor("*")).toBeNull();
|
|
expect(peerRangeMinMajor("latest")).toBeNull();
|
|
expect(peerRangeMinMajor("workspace:*")).toBeNull();
|
|
expect(peerRangeMinMajor("github:facebook/react#1234567890")).toBeNull();
|
|
expect(peerRangeMinMajor("file:../react-19-local.tgz")).toBeNull();
|
|
expect(peerRangeMinMajor("npm:@scope/react19-fork@latest")).toBeNull();
|
|
expect(peerRangeMinMajor(null)).toBeNull();
|
|
expect(peerRangeMinMajor(undefined)).toBeNull();
|
|
expect(peerRangeMinMajor("")).toBeNull();
|
|
});
|
|
|
|
it("ignores 0.x experimental versions", () => {
|
|
expect(peerRangeMinMajor("0.0.0-experimental")).toBeNull();
|
|
expect(peerRangeMinMajor("0.0.0-canary-1a2b3c4d")).toBeNull();
|
|
});
|
|
|
|
it("handles single-version specs", () => {
|
|
expect(peerRangeMinMajor("19")).toBe(19);
|
|
expect(peerRangeMinMajor("~19.0.0")).toBe(19);
|
|
expect(peerRangeMinMajor("^19.0.0")).toBe(19);
|
|
});
|
|
|
|
it("returns null for non-lower-bound comparators", () => {
|
|
expect(peerRangeMinMajor(">18")).toBeNull();
|
|
expect(peerRangeMinMajor(">18 <20")).toBeNull();
|
|
expect(peerRangeMinMajor("!=19")).toBeNull();
|
|
});
|
|
});
|