Skip to content

feat(cli): npx vitest-native init | doctor | migrate#75

Open
danfry1 wants to merge 3 commits into
mainfrom
feat/migration-cli
Open

feat(cli): npx vitest-native init | doctor | migrate#75
danfry1 wants to merge 3 commits into
mainfrom
feat/migration-cli

Conversation

@danfry1

@danfry1 danfry1 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Problem

The project's own migration-friction audit found ~70% of Jest-migration friction to be mechanically mappable configuration — but the docs asked the user to be the codemod: the jest-compat config block was copy-pasted across four doc pages, and environment problems (missing Babel preset, RNTL 14 on Node < 22.13, absent peers) surfaced as raw runtime failures with no diagnosis.

Change

A package bin (dist/cli.mjs, vitest-free at module load) with three commands:

  • init — writes a ready-to-run config (.mts for TS projects, .mjs otherwise; refuses to overwrite without --force). --jest-compat emits the migration guide's canonical block, making the CLI the single source of that shape.
  • doctor — read-only diagnosis: Node floor, the RNTL 14 ⇄ Node 22.13 interaction, required peers against the supported ranges (table shared with the plugin's startup validation via src/peer-requirements.ts, so they cannot drift), which engine auto resolves to and why, every auto-detected preset, Expo presence with known-limits pointer, and config presence. Exits non-zero on blocking problems so it can gate CI setup.
  • migrate — analyzes jest.config.{js,cjs,json} or package.json#jest (Jest's own precedence) and reports key-by-key: mapped automatically (setup files, path aliases emitted as absolute paths, transformIgnorePatterns allowlist extraction → transform: [...], timeouts, coverage include, mock-hygiene flags), covered by presets — delete (manual __mocks__ and RN setup lines), needs attention (regex mappers, capturing-group allowlist entries, fake timers, unknown keys — surfaced rather than guessed at; the raw transformIgnorePatterns is always echoed), and dropped. Ends with a complete suggested config; --write saves it. Test files are never edited — jestMockTransform() handles top-level jest.mock at runtime.

Review

Independently adversarially reviewed pre-PR. Both blockers it found are fixed: a Windows-fatal URL.pathname path derivation in a test, and — the important one — the alias emission produced relative Vite aliases, which Vite resolves per-importer; aliases now emit absolute fileURLToPath(new URL(...)) expressions. Also incorporated: Jest-matching config precedence, honest handling of capturing-group allowlist entries (previously stripped into fabricated package names), --root-before-command dispatch, --help exit 0, coverage emission, and the shared peer table. The reviewer verified the bin shims dispatch correctly on POSIX and Windows and that the consumer smoke passes.

Tests

15 unit tests (dispatch, init variants and guards, doctor including the RNTL/Node conflict and a live self-check, migrate extraction/report/write, capturing-group and no-config cases) plus the packed-tarball consumer suite now running npx vitest-native doctor and migrate end-to-end. Website gets a CLI page; changeset is minor (new feature).

Full gate: mock suite 1307, consumers green, lint/format/typecheck clean.

danfry1 added 2 commits July 5, 2026 21:17
The docs previously asked migrating users to be the codemod: the jest-compat
config block was copy-pasted across four doc pages, environment problems
(missing babel preset, RNTL 14 on Node < 22.13, absent peers) surfaced as raw
runtime failures, and the project's own migration-friction audit found ~70%
of Jest-migration friction to be mechanically mappable configuration.

- init: writes a ready-to-run config; --jest-compat emits the migration
  guide's canonical block (single source of shape); refuses to overwrite
  without --force; .mts for TS projects, .mjs otherwise.
- doctor: read-only diagnosis — Node floor, RNTL 14 ⇄ Node 22.13, required
  peers against supported ranges (per-major Vite floors included), the
  engine 'auto' resolution and its reason, every auto-detected preset, Expo
  presence with known-limits pointer, config presence. Non-zero exit on
  blocking problems so it can gate CI setup.
- migrate: analyzes package.json#jest / jest.config.{js,cjs,json} and
  reports key-by-key: mapped automatically (setup files, path aliases,
  transformIgnorePatterns allowlist extraction → transform: [...],
  timeouts, clear/reset/restoreMocks), covered-by-presets (deletable
  __mocks__ and setup lines), needs-attention (regex mappers, fake timers,
  unknown keys), and dropped keys — ending with a complete suggested
  config. Dry-run by default; --write saves it. ts/mjs/function configs
  are reported as unloadable rather than guessed at.

Implementation reuses the plugin's own building blocks (detectEngine,
validatePeerDependency, AUTO_DETECT_PRESETS) and is vitest-free at module
load (runs via npx). Ships as dist/cli.mjs via the package bin; the
packed-tarball consumer suite runs doctor and migrate end-to-end.
…d extraction fixes

Pre-PR review findings, two of them blockers:

- migrate's alias emission produced relative Vite aliases ('@' → './src'),
  which Vite resolves relative to the IMPORTER — broken for any test not at
  the project root. Aliases now emit absolute paths via
  fileURLToPath(new URL(..., import.meta.url)) with the import added to the
  suggested config; mappers that still carry regex syntax after the strip go
  to needs-attention instead of emitting something half-right.
- A test derived a filesystem path from URL.pathname, which yields /C:/…
  on Windows and would have failed the Windows CI leg; fileURLToPath now.
- transformIgnorePatterns extraction: entries with capturing groups
  ((jest-)?react-native) previously stripped into fabricated package names;
  they are now surfaced as needs-attention, the raw pattern is always
  echoed in the report, and the lookahead-body regex no longer closes early
  on a leading nested group.
- Jest config precedence now matches Jest (config file wins over
  package.json#jest); collectCoverageFrom is actually emitted as
  test.coverage.include; --root before the command dispatches correctly;
  explicit --help exits 0; the dead setupFilesAfterEach branch is gone.
- The peer-requirements table is extracted to src/peer-requirements.ts and
  shared by plugin startup validation and doctor, so they cannot drift.
Comment thread packages/vitest-native/src/cli/migrate.ts Fixed
CodeQL (js/redos): the lookahead-body regex used [^()]+ inside a star — the
classic nested-quantifier shape with exponential backtracking on unclosed
input like '(?!aaaa…'. Single-character alternatives keep the repetition
unambiguous and linear while preserving the no-early-close property for
leading nested groups. Verified: 50k-char unclosed input resolves in
milliseconds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants