Vitest Skill
sickn33/agentic-awesome-skills
Generates Vitest tests in JavaScript/TypeScript with Vite-native speed.
Migrate an Nx repo package's unit tests from Jest to Vitest, reusing the shared setup that packages/workspace established.
$ npx skills add nrwl/nx --skill vitest-migration -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install nrwl/nx vitest-migration --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/nrwl/nx.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/vitest-migration .claude/skills/vitest-migration && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "vitest-migration" agent skill from https://github.com/nrwl/nx/tree/master/.claude/skills/vitest-migration into .claude/skills/vitest-migration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "vitest-migration", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/nrwl/nx/tree/master/.claude/skills/vitest-migrationType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add nrwl/nx --skill vitest-migration -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install nrwl/nx vitest-migration --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nrwl/nx.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/vitest-migration .agents/skills/vitest-migration && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "vitest-migration" agent skill from https://github.com/nrwl/nx/tree/master/.claude/skills/vitest-migration into .agents/skills/vitest-migration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "vitest-migration", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add nrwl/nx --skill vitest-migration -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install nrwl/nx vitest-migration --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nrwl/nx.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/vitest-migration .cursor/skills/vitest-migration && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "vitest-migration" agent skill from https://github.com/nrwl/nx/tree/master/.claude/skills/vitest-migration into .cursor/skills/vitest-migration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "vitest-migration", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/nrwl/nx.git --path .claude/skills/vitest-migration--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add nrwl/nx --skill vitest-migration -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install nrwl/nx vitest-migration --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nrwl/nx.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/vitest-migration .gemini/skills/vitest-migration && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "vitest-migration" agent skill from https://github.com/nrwl/nx/tree/master/.claude/skills/vitest-migration into .gemini/skills/vitest-migration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "vitest-migration", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install nrwl/nx vitest-migrationInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add nrwl/nx --skill vitest-migration -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/nrwl/nx.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/vitest-migration .github/skills/vitest-migration && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "vitest-migration" agent skill from https://github.com/nrwl/nx/tree/master/.claude/skills/vitest-migration into .github/skills/vitest-migration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "vitest-migration", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add nrwl/nx --skill vitest-migration -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install nrwl/nx vitest-migration --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nrwl/nx.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/vitest-migration .opencode/skills/vitest-migration && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "vitest-migration" agent skill from https://github.com/nrwl/nx/tree/master/.claude/skills/vitest-migration into .opencode/skills/vitest-migration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "vitest-migration", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
vitest-migrationMigrate an Nx repo package's unit tests from Jest to Vitest, reusing the shared setup that packages/workspace established.
Vitest Migration is an agent skill from nrwl/nx. Migrate an Nx repo package's unit tests from Jest to Vitest, reusing the shared setup that packages/workspace established. Use when asked to "move <pkg to vitest", "migrate <pkg tests off jest", or "run <pkg unit tests with vitest".
Its SKILL.md is about 7.3k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Testing & QA, covering Unit testing. It works with Vitest, Jest and TypeScript. The repository describes itself as: The Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time. The licence is MIT.
10 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit db71d69. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
ReadGlobGrepAgentEdit(*)Write(*)Bash(pnpm nx *)Bash(npx nx *)Bash(nx *)Bash(git *)…and 17 more on the same allowed-tools line.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
pnpmnpxnxgitjqFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use pnpm, npx and git, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Vitest Migration loads about 7.3k tokens when it runs. Until then it costs about 63 tokens; SKILL.md has 2,980 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from nrwl/nx at commit db71d69, republished under its MIT licence (© nrwl). 2,980 words, ~7,335 tokens.
.claude/skills/vitest-migration/SKILL.md (or your agent's skills folder).Move packages/<name>'s unit tests from Jest to Vitest 4, inferred through the
@nx/vitest plugin.
Start from packages/workspace, not packages/nx. The shared machinery a
sibling package needs already exists — read these first and reuse them as-is:
tools/vitest/setup.mts — the port of scripts/unit-test-setup.js; every
migrated package loads it as its setupFilestools/vitest/nx-source-resolver.mts — resolves nx / @nx/* to this
repo's source, for both vite and nodetools/vitest/tsconfig.json — a leaf tsconfig whose only job is to stop
vite's tsconfig lookup. Do not move these files to the workspace root.
With no tsconfig beside them, the nearest one is the root solution file, and
vite walks its references — reading all ~114 project tsconfigs on every
run, which lands as a sandbox violation. tools/vitest is deliberately a
plain directory, not an Nx project: adding project.json makes
@nx/js:typescript-sync demand a project reference to a test-only tool from
each consuming package's published tsconfig.lib.json (composite: false
does not suppress it)packages/workspace/vitest.config.mts — the config those two plug intopackages/workspace/project.json — test.inputs naming the shared scriptspackages/nx (PR #36754, commit 32dd3fb533) is the original migration but a
poor template: it is the one package that imports almost no siblings, so it
needs neither the source resolver nor the CJS-channel mocks. Consult it only for
vitest-write-guard.cjs and src/internal-testing-utils/cjs-mock.ts.
packages/angular-rspack/vitest.config.mts is the simple end of the spectrum
(no nx source at all).
The package name (e.g. js, devkit, workspace). The package lives at
packages/<name>/.
Jest and Vitest disagree on module semantics, not just API names. The
mechanical jest.* → vi.* rename is maybe 80% of the diff and 20% of the
work. The rest is: which channel a mock reaches (ESM graph vs CJS
require()), whether a namespace is frozen, and what resetAllMocks does to a
spy. Budget for hand-fixing specs after the codemod.
Run these and write the answers into tmp/notes/vitest-migration-<name>.md
before touching anything:
ls packages/<name>/jest.config.cts packages/<name>/jest*.js 2>/dev/null
cat packages/<name>/jest.config.cts
cat packages/<name>/tsconfig.spec.json
grep -rl "\.spec\.ts" -c packages/<name>/src | wc -l # rough spec count
pnpm nx show project <name> --json | head -40Capture:
pnpm nx test <name> --skip-nx-cache
once and record the reported test count and wall time. That number is the
parity target in Step 6 — you cannot verify the migration without it.displayName/preset/
moduleFileExtensions is behavior you must reproduce:setupFiles (e.g. packages/devkit/jest-setup-nx-workspace-data-dir.js)moduleNameMapper (path shims; also identity-obj-proxy for CSS)testEnvironment: 'jsdom' → needs environment: 'jsdom' and the
jsdom depmodulePathIgnorePatterns / testPathIgnorePatterns → excluderesolver → resolve.conditions (see Step 2)jest.preset.js) that Vitest does not get
for free:setupFiles: ['../../scripts/unit-test-setup.js'] — the workspace-wide
project-graph / workspace-context / native guards. This must be ported
(Step 3).resolver: '../../scripts/patched-jest-resolver.js' — maps @nx/* and
nx/* onto packages/* source, and sets
NX_WORKSPACE_ROOT_PATH=<repo>/tmp/unit as a side effect. Both are
reproduced by the shared scripts (Steps 2 and 3).moduleNameMapper ESM shims (@clack/prompts, ora, chalk,
yargs-parser, prettier, magic-string, oxfmt). Most are pure ESM
interop Vitest does not need — but check each for behavior before
dropping it. @clack/prompts is load-bearing: the stub answers undefined
where the real library drives a synchronous prompt, and a generator
that asks a question blocks the worker forever with no test timeout.
tools/vitest/setup.mts already keeps that one. prettier's stub also
pins resolveConfig: () => null, which matters if the package snapshots
formatted output.maxWorkers: 1 — Vitest runs files in parallel. Any spec relying on
cross-file ordering or a shared mutable temp dir will now fail. This is
the main source of "it passed under jest" flakes.nx/src/native or a .node
file? If yes you need pool: 'forks' and the native shim plugin from
packages/nx/vitest.config.mts.require() of TS source — grep -rn "require(" packages/<name>/src --include=*.ts | grep -v "^.*spec". Every bare require() of a local .ts
file needs @swc-node/register (Step 2) and can only be mocked through
mockCjsModule (Step 4).@nx/vitest is already registered in nx.json for packages/**/*, so a
vitest.config.mts at the package root is enough to infer <name>:test.
Verify the plugin block still reads:
{
"plugin": "@nx/vitest",
"options": { "testTargetName": "test" },
"include": ["packages/**/*"],
"exclude": ["**/out-tsc/**"]
}@nx/jest infers test from jest.config.* presence. Both plugins would
claim test, so jest.config.cts must be deleted in the same change, not
left behind "just in case". Also delete any jest-resolver.js and drop
project.json target overrides that reference jest inputs (see the
packages/nx diff — a "test": { "inputs": [..., "patched-jest-resolver.js"] }
block was removed).
packages/<name>/vitest.config.mtsStart from packages/nx/vitest.config.mts and keep only what the survey
justified. The load-bearing pieces and why:
export default defineConfig({
root: import.meta.dirname,
cacheDir: '../../node_modules/.vite/<name>/unit',
test: {
watch: false,
globals: true, // specs use bare describe/it/expect/vi
environment: 'node', // or 'jsdom' if the jest config said so
include: ['**/*.spec.ts'],
exclude: ['**/node_modules/**'],
setupFiles: ['./vitest.setup.mts'],
testTimeout: 35000, // matches jest.preset.js
pool: 'forks', // ONLY if native .node bindings are loaded;
// they are not thread-safe across workers
teardownTimeout: 60_000, // specs holding native contexts exit slowly;
// the jest setup hid this behind --forceExit
execArgv: ['--conditions=@nx/nx-source'],
server: { deps: { external: [/\.node$/] } },
},
resolve: {
conditions: ['@nx/nx-source'],
},
plugins: [nxSourceResolver()], // tools/vitest/nx-source-resolver.mts
});Rules for resolution — the part that most looks solved and isn't:
conditions: ['@nx/nx-source'] does NOT replace the jest resolver.
node_modules/nx and node_modules/@nx/* are the published tarballs (dist
only, no source), and their exports maps advertise @nx/nx-source entries
pointing at ./src/index.ts files the tarball does not ship — so the
condition resolves to a file that isn't there. Use nxSourceResolver() from
tools/vitest/nx-source-resolver.mts, which maps nx / @nx/* through
the local packages/<pkg>/package.json, with a file fallback for deep
imports no exports entry covers (@nx/workspace/src/...).execArgv: ['--conditions=@nx/nx-source'] on its own actively breaks node
resolution, for the same reason: a lazy require('@nx/js') dies with
Cannot find module '.../node_modules/@nx/js/src/index.ts'. Keep the flag,
but tools/vitest/setup.mts must also patch Module._resolveFilename
with the same mapping so both channels agree.'@nx/devkit' would rewrite @nx/devkit/internal too. Use
{ find: /^@nx\/devkit$/, replacement: ... }.packages/nx predates the shared resolver and hard-codes nx/src/* and
nx/bin/* aliases instead. Don't copy that — the resolver covers it.yargs with CJS-namespace style
(yargs.terminalWidth()), alias it to node_modules/yargs/index.cjs.nx/src/native, copy the nx-native-shim plugin
verbatim — src/native/index.js requires TS files and cannot run outside a
transform, so it must be routed to the generated native-bindings.js and
externalized.Point the config at the shared file; do not write a per-package copy:
setupFiles: ['../../tools/vitest/setup.mts'],tools/vitest/setup.mts is the port of scripts/unit-test-setup.js (which
is jest-only — jest.doMock — so it can never be imported from vitest). Read
it before assuming anything is missing; it already does all of the following,
and each line is there because its absence broke packages/workspace:
NX_DAEMON=false, npm_config_user_agent deleted, FORCE_COLOR deleted and
NO_COLOR=1 (snapshots are recorded colorless).NX_WORKSPACE_ROOT_PATH under tmp/unit/<pid> — per worker process,
unlike jest. The jest resolver set a single tmp/unit as a side effect;
with vitest's parallel workers one shared root makes every worker queue on
the same lock ("Waiting for graph construction in another process to
complete", 35s timeouts).NX_ISOLATE_PLUGINS=false. Otherwise plugin isolation spawns a worker
subprocess per plugin that is never torn down, and the spec file stalls to
its timeout. Two packages/nx specs already carry this same note.@swc-node/register, with Error.prepareStackTrace restored immediately
after: the hook installs source-map-support, which mis-maps
vite-transformed frames and breaks error locations and inline-snapshot
updates.Module._resolveFilename patched with the source mapping (Step 2), plus
@clack/prompts → scripts/jest-mocks/clack-prompts.js.vi.doMock graph/workspace-context/native guards, keyed by absolute
physical path — mocking the nx/src/... specifier routes through the pnpm
symlink and keys as a different module, so the mock silently never applies.Module._load
patch. This is the one most easily missed and the most expensive to debug:
generators reach graph builders through lazy require(), which vi.mock
cannot see, and the unmocked createProjectGraphAsync takes
project-graph.lock and deadlocks the worker — no output, and no test
timeout fires, because the main thread is blocked in a futex.vi.fn(), so a suite's
vi.resetAllMocks() cannot wipe them into () => undefined.Add to the shared file (not a package-local one) if the package needs a guard nothing else does, and say so in the PR — every migrated package loads it.
Two more rules:
jest global in the setup. A stray jest.mock would not
be hoisted by vitest's transform and would silently fail to intercept. Let
it throw.packages/nx/vitest-write-guard.cjs and load it through
execArgv: ['--require', ...]. It must be execArgv, not setupFiles:
node snapshots a module's ESM named exports on first import, so a patch
applied from a setup file is invisible to import { writeFile } from 'fs'.
(The packages/nx migration found a spec that had been overwriting the
repo's real nx.json.)Finally, name the shared files in the package's project.json so the cache
sees them — they live outside {projectRoot}, so nothing else invalidates on
an edit:
"test": {
"inputs": [
"...",
"{workspaceRoot}/tools/vitest/**/*",
"{workspaceRoot}/scripts/jest-mocks/clack-prompts.js"
]
}These are not optional bookkeeping. @nx/vitest infers setup.mts and its
tsconfig (nx#36920), but nothing infers the resolver the config imports or the
clack mock the setup loads by path — default is project-scoped — so leaving
them off does not fail loudly; it serves a stale cache hit the next time
someone edits them. Keep the whole tools/vitest/**/* glob rather than naming
the resolver alone, so a helper added there later is covered too.
tsconfig.spec.json{
"compilerOptions": {
"types": ["vitest/globals", "node"], // was ["jest", "node"]
},
"include": [
// ...
"vitest.config.mts", // replaces "jest.config.ts"
"vitest.setup.mts",
],
}Drop @types/jest from the package's devDependencies only if no other
project in the repo still needs it there.
Apply mechanically, then hand-fix. Prefer one script over 200 manual edits, and commit the codemod pass separately from the hand fixes so review can follow.
Two rules before you run anything:
jest.* in
strings, not calls — a spec for a codemod that rewrites jest.mock(...)
text, generator specs asserting on jest.config.cts contents, or
'@nx/jest:jest' executor names. Build the file list from a grep for real
API usage (grep -l 'jest\.[a-z]' | grep -v the string-only ones) and
eyeball it.jest\n .fn() and jest\n .spyOn(...) are
common in this repo and a line-based s/jest\.fn(/vi.fn(/ silently misses
them, leaving ReferenceError: jest is not defined at collection. Use
perl -0p (or equivalent) and re-grep for a bare \bjest\b afterwards.| Jest | Vitest | Note |
|---|---|---|
jest.fn / jest.spyOn / jest.mock / jest.doMock / jest.unmock / jest.clearAllMocks / jest.restoreAllMocks / jest.resetModules / jest.mocked | same with vi. | pure rename |
jest.requireActual<T>(x) | await vi.importActual<T>(x) | factory must become async |
jest.requireMock(x) | await vi.importMock(x) | factory must become async |
jest.isolateModules(() => { require(x) }) | vi.resetModules() + await import(x) | for CJS-loaded modules use delete cjsRequire.cache[cjsRequire.resolve(x)]; cjsRequire(x) |
jest.isolateModulesAsync(async () => …) | vi.resetModules() then fresh await import()s | |
jest.Mock | import type { Mock } from 'vitest' | type-only import |
jest.SpyInstance | import type { MockInstance } from 'vitest' | type-only import |
jest.MockedFunction | import type { MockedFunction } from 'vitest' | type-only import |
it('x', (done) => …) | return a promise | Vitest has no done callback |
xdescribe / xit | describe.skip / it.skip | not defined in Vitest |
import { jest } from '@jest/globals' | delete | vi is global with globals: true |
Hoisting is real in Vitest: vi.mock calls are lifted to the top of the file,
above imports and above any const the factory closes over. Anything a
factory needs must go through vi.hoisted(() => …).
This is where the time goes. The catalogue, from the packages/nx migration:
Frozen ESM namespaces. vi.spyOn(semverNamespace, 'gt') throws on a
node builtin or an external ESM package — the namespace object is frozen.
Mock at the module level in spy mode, which keeps the real implementations
until a test overrides one:
vi.mock('semver', { spy: true });
vi.mock('child_process', { spy: true });Modules the source loads with bare require(). vi.mock never sees that
channel. Use the helper (add it if the package does not have one — it lives in
packages/nx/src/internal-testing-utils/cjs-mock.ts and patches
Module._load):
import { mockCjsModule } from '<path>/internal-testing-utils/cjs-mock';
mockCjsModule(import.meta.url, './run', { runCommand: vi.fn() });Registrations are per-file (Vitest forks per file), but a swap made for a
single test must be undone with unmockCjsModule / resetCjsMocks or it leaks
into later tests in the same file.
Class mocks must be constructible. vi.fn() returning an object is not
new-able the way jest's auto-mock was. Return a real function with a
prototype, as GuardedWorkspaceContext does in vitest.setup.mts.
vi.resetAllMocks() restores a spy's real implementation rather than
leaving () => undefined like jest. Specs that relied on the jest behavior
(expecting undefined after a reset) need explicit mockReturnValue(undefined).
Setup-file mocks must not use vi.fn() for pass-through helpers. A spec
calling vi.resetAllMocks() would wipe the implementation and surface as
"is not iterable" downstream. Use plain functions, as the workspace-context
mock does.
Hooks that must not return a mock. beforeEach(() => vi.fn()) — Vitest
treats a returned function as a teardown callback. Make the body a block.
Parallelism. With maxWorkers: 1 gone, two spec files sharing a temp dir,
a process.chdir, or a module-level singleton will now collide. Fix by giving
each file its own TempFs root; reach for test.sequential/isolate: false
only after proving the collision is not the spec's own bug.
A spec that hangs with no output and no timeout. The test timeout cannot fire, because the worker's main thread is blocked in a futex — so the usual "which test is slow" reflexes give you nothing. Diagnose it from the outside:
p=$(pgrep -f "workers/forks" | head -1)
cat /proc/$p/wchan # futex_do_wait == blocked, not busy
ps -o pcpu= -p $p # ~0% confirms it is not just slow
ls -l /proc/$p/fd | grep -v socket # the lock file it is stuck on
pgrep -aP $p # a spawned worker/install it waits forIn packages/workspace this was project-graph.lock: real graph construction
running on the CJS channel. The three causes seen so far are all handled by
tools/vitest/setup.mts — CJS graph mocks, NX_ISOLATE_PLUGINS=false, and
the @clack/prompts stub — so first check the setup is actually loaded before
hunting further.
Watch for latent test bugs. Both spec bugs the packages/nx migration
uncovered were assertions that passed while the mock never applied. If a spec
starts failing after the mock finally lands, the test was wrong — fix the
expectation, do not paper over it by restoring the broken mock.
Vitest joins describe and test names with > where jest used a space, so
every existing key reads as new: a plain run appends a full second copy of
the file and leaves the jest entries orphaned. Regenerate from a pristine tree
so -u also drops the old keys:
git checkout -- 'packages/<name>/**/__snapshots__/*.snap'
cp -r <snapshots> tmp/snapshot-baseline/ # keep the jest originals
pnpm nx test <name> --skip-nx-cache -- -uThen prove the values did not move: parse both sides into {key: value},
normalize the separator (' > ' → ' '), and diff. Key counts and every value
must match — that is the real parity check, not the pass/fail.
toThrowErrorMatchingInlineSnapshot is the known exception: vitest records
[Error: msg] where jest recorded "msg". Same message, different serializer.
Vitest's serializer differs from Jest's elsewhere too. Regenerate, then read the diff:
pnpm nx test <name> --skip-nx-cache -- -u
git diff --stat -- 'packages/<name>/**/__snapshots__/*'Snapshot churn should be formatting only (quoting, indentation, Object { →
{). Any change in content is a real behavior difference — investigate it
before accepting. Colorless output is guaranteed by the NO_COLOR pin in the
setup file; if you see ANSI codes land in a snapshot, that pin is missing.
# same test count as Step 0, and it should be dramatically faster
pnpm nx test <name> --skip-nx-cache
# parallel-safety: repeat runs must be stable, not just green once
pnpm nx test <name> --skip-nx-cache
pnpm nx test <name> --skip-nx-cache
# a single file still works (paths relative to the package root)
pnpm nx run <name>:test -- src/utils/some-file.spec.ts
# nothing else broke - EVERY target the project has, not a set you picked
pnpm nx show project <name> --json | jq '.targets | keys'
pnpm nx run-many -t test,build,lint,oxlint -p <name> --skip-nx-cache
pnpm nx sync:checkoxlint is a separate target from lint. Running test,build,lint and
calling it green is how a restricted-import error reaches CI: this repo bans
nx/src/... imports in favour of @nx/devkit/internal*, and only oxlint
catches it. Read the target list rather than assuming the usual three.
Parity is test count, not just a green run. A dropped include pattern or
a silently-skipped directory shows up as a lower count, and a green suite hides
it. If the count differs, find every missing file before proceeding.
Note the caching caveat: after a mechanical sweep, nx affected can replay a
stale cached pass. Always validate with --skip-nx-cache.
Do not run a full nx affected: a new or moved file under a workspace-root
directory marks all ~90 projects affected, which is hours of jest for changes
that touch nothing jest reads. Run one still-on-jest package as a canary
instead — devkit is the most entangled.
The migration is not done when CI is green. Nx Cloud reports the task's file reads against its declared inputs, and a vitest suite reads things the jest one did not. Fetch them once the PR has run:
npx nx-cloud get sandbox-reports --branch <PR-number> --since 1d
npx nx-cloud validate sandbox-violations \
.nx/workspace-data/sandbox-reports/<PR-number>/index.json --jsonnx reset deletes .nx/workspace-data, and the downloaded reports with it —
re-download after one.
The per-task JSON carries processTree plus a pid on every read, which is
how you attribute a violation instead of guessing. Map them:
tree = {p['pid']: p for p in report['processTree']}
for r in report['unexpectedReads']:
print(r['path'], tree.get(r['pid'], {}).get('cmd'))The two this migration produced, both worth checking for:
Every project's tsconfig.json, read by the vitest main process. The
timestamps show the root solution tsconfig read milliseconds after a
workspace-root source file. Cause and fix are in the Step 3 note about where
the shared setup lives. Confirm with the resolver vite itself uses rather
than a filesystem tracer — tsconfck's parse() reports what it consulted,
and a tracer on fs misses it because the ESM node:fs/promises bindings
are snapshotted before a --require preload can patch them:
const { parse } =
await import('<repo>/node_modules/.pnpm/tsconfck@*/node_modules/tsconfck/src/index.js');
console.log((await parse('tools/vitest/setup.mts')).referenced?.length); // 0 == leaf, 114 == solution rootThe repo's .editorconfig, read by a worker. formatFiles resolves
prettier config from disk at tree.root, and a spec that points
tree.root into the repo (e.g. process.cwd()) picks it up; the jest
prettier shim pinned resolveConfig: () => null and hid it. Give that spec a
TempFs root instead of declaring the file as an input.
Prefer declaring an input over excluding a path: an over-broad input costs cache misses, an over-broad exclusion buys wrong cache hits. But a violation that only exists because a file sits in the wrong place is a layout bug — fix the layout. Declaring 114 tsconfigs as inputs would have been "correct" and would have quietly wrecked the cache for every vitest suite in the repo.
packages/<name>/jest.config.cts and any package-local jest resolver
or setup file whose behavior you ported.scripts/jest-mocks/* shim or a
branch of scripts/unit-test-setup.js, delete it. If not, leave it and say
so in the PR body — the packages/nx PR explicitly deferred the dead
scripts/unit-test-setup.js branches to a follow-up rather than mixing them
in.CONTRIBUTING.md — it documents npx jest <path> for targeting a
single test. Add the package to the vitest note next to packages/nx.npx oxfmt <changed files> (check the branch's own check target
first — a feature branch may still run pretty-quick).nx affected: a new file under scripts/ marks all 90+
projects affected, which is hours of jest. Nothing jest reads has changed
(jest.preset.js, scripts/unit-test-setup.js,
scripts/patched-jest-resolver.js are untouched), so run one still-on-jest
package as a canary instead — devkit is the most entangled.tmp/notes/vitest-migration-<name>.md: before/after test count and
wall time, the list of hand-fixed specs and why, and anything deferred.Follow the reference PR — small, reviewable, conventional-commit slices:
chore(<scope>): add vitest config and setup for <name> unit testschore(<scope>): codemod jest.* to vi.* in <name> specschore(<scope>): regen <name> snapshots for vitestchore(<scope>): remove <name> jest configPR body: fill the template, state the before/after test count and wall time, list what the vitest config reproduces from the jest setup, and call out any latent test bug the migration exposed.
© nrwl, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/vitest-migration of nrwl/nx.
Open the folder on GitHubat commit db71d69
Vitest Migration next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Vitest Migration this skillnrwl/nx | 29k | — | ~7.3k | Automated safety check: Pass | MIT | |
| Vitest Skillsickn33/agentic-awesome-skills | 47k | 1 repos | ~1.2k | Automated safety check: Pass | MIT | |
| Code Testing Agentmicrosoft/testfx | 1k | — | ~2.7k | Automated safety check: Pass | MIT | |
| Test Taggingmicrosoft/testfx | 1k | — | ~4.3k | Automated safety check: Pass | MIT | |
| Vitestjezweb/claude-skills | 1.1k | — | ~3k | Automated safety check: Pass | MIT | |
| Test CommanderEliasOulkadi/shokunin | 114 | — | ~3k | Automated safety check: Notes | MIT |
sickn33/agentic-awesome-skills
Generates Vitest tests in JavaScript/TypeScript with Vite-native speed.
microsoft/testfx
Generates and writes new unit tests for any programming language — scaffolds .NET test projects, pytest suites, Vitest/Jest suites, Go test files, and JUnit suites, and configures coverage tooling…
microsoft/testfx
Analyzes test suites in any language and tags each test with a standardized set of traits (positive, negative, critical-path, boundary, smoke, regression, integration, performance, security).
jezweb/claude-skills
Set up Vitest testing in any project — detects type (Cloudflare Workers, React, Node, library), generates vitest.config.ts, test setup, utilities, and a sample test.
EliasOulkadi/shokunin
Generate unit, integration, E2E, and visual regression tests following the Testing Trophy methodology (80% integration).
yonatangross/orchestkit
Unit testing patterns for isolated business logic tests — AAA pattern, parametrized tests (test.each, @pytest.mark.parametrize), fixture scoping (function/module/session), mocking with MSW/VCR at…
nrwl/nx
Monitor Nx Cloud CI pipeline and handle self-healing fixes. An agent skill from nrwl/nx.
nrwl/nx
Import, merge, or combine repositories into an Nx workspace using nx import.
nrwl/nx
Run Nx generators with prioritization for workspace-plugin generators.
nrwl/nx
Author or scope a first-party Nx migration. An agent skill from nrwl/nx.
nrwl/nx
Generate code using nx generators. An agent skill from nrwl/nx.
nrwl/nx
Check modified Nx documentation pages against the astro-docs style guide.
Works with
Categories
Migrate an Nx repo package's unit tests from Jest to Vitest, reusing the shared setup that packages/workspace established. Vitest Migration is an agent skill from nrwl/nx. Migrate an Nx repo package's unit tests from Jest to Vitest, reusing the shared setup that packages/workspace established.
Vitest Migration fits situations like: asked to move <pkg to vitest; migrate <pkg tests off jest; run <pkg unit tests with vitest.
Run `npx skills add nrwl/nx --skill vitest-migration -a claude-code`. Or copy the skill folder (.claude/skills/vitest-migration in nrwl/nx) into .claude/skills/vitest-migration in your project. Claude Code loads it when a task matches its description.
Run `npx skills add nrwl/nx --skill vitest-migration -a codex`. Or copy the skill folder (.claude/skills/vitest-migration in nrwl/nx) into .agents/skills/vitest-migration in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add nrwl/nx --skill vitest-migration -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/vitest-migration, .gemini/skills/vitest-migration, .github/skills/vitest-migration and .opencode/skills/vitest-migration in your project.
Going by SKILL.md and its folder, Vitest Migration needs the command-line tools its instructions call (pnpm, npx, nx, git and jq). Our summary lists: Node.js. Its frontmatter pre-approves these tools: Read, Glob, Grep, Agent, Edit(*), Write(*), Bash(pnpm nx *), Bash(npx nx *), Bash(nx *), Bash(git *), Bash(ls *), Bash(cat *), Bash(head *), Bash(tail *), Bash(sed *), Bash(grep *), Bash(rg *), Bash(find *), Bash(wc *), Bash(echo *), Bash(mkdir *), Bash(rm *), Bash(mv *), Bash(node *), Bash(npx oxfmt *), Bash(gh pr view *), Bash(gh pr diff *).
SKILL.md contains no URLs. Its commands use npx and git, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Vitest Migration is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.3k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Vitest Migration: Vitest Skill (sickn33/agentic-awesome-skills, 47k stars), Code Testing Agent (microsoft/testfx, 1k stars), Test Tagging (microsoft/testfx, 1k stars) and Vitest (jezweb/claude-skills, 1.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
nrwl (a GitHub organization) maintains it in nrwl/nx, which has 29,399 GitHub stars. The repository holds 21 skills in this directory. The repository was last updated on October 9, 2026.
Source: nrwl/nx on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.