From b95474072daefac496450438859e850921142643 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Wed, 5 Aug 2026 20:26:31 +1000 Subject: [PATCH 1/5] chore: [nx migration] checkpoint before running migrations --- migrations.json | 260 ++++ package.json | 30 +- .../eslint/23.1.1/convert-to-flat-config.md | 291 ++++ .../eslint/23.1.1/migrate-ban-types-rule.md | 29 + .../@nx/jest/23.1.1/verify-typecheck.md | 14 + .../vite/23.1.1/ai-instructions-for-vite-8.md | 254 +++ .../23.1.1/ai-instructions-for-vitest-3.md | 604 ++++++++ .../23.1.1/ai-instructions-for-vitest-4.md | 838 ++++++++++ yarn.lock | 1356 ++++++++--------- 9 files changed, 2975 insertions(+), 701 deletions(-) create mode 100644 migrations.json create mode 100644 tools/ai-migrations/@nx/eslint/23.1.1/convert-to-flat-config.md create mode 100644 tools/ai-migrations/@nx/eslint/23.1.1/migrate-ban-types-rule.md create mode 100644 tools/ai-migrations/@nx/jest/23.1.1/verify-typecheck.md create mode 100644 tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vite-8.md create mode 100644 tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-3.md create mode 100644 tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-4.md diff --git a/migrations.json b/migrations.json new file mode 100644 index 00000000..115420ca --- /dev/null +++ b/migrations.json @@ -0,0 +1,260 @@ +{ + "migrations": [ + { + "version": "23.0.0-beta.16", + "description": "Consolidates any remaining legacy releaseTag* flat properties into the nested releaseTag object. The flat properties were removed in Nx 23.", + "implementation": "./dist/src/migrations/update-23-0-0/consolidate-release-tag-config", + "package": "nx", + "name": "23-0-0-consolidate-release-tag-config" + }, + { + "version": "23.0.0-beta.18", + "description": "Adds .nx/migrate-runs to .gitignore", + "implementation": "./dist/src/migrations/update-23-0-0/add-migrate-runs-to-git-ignore", + "package": "nx", + "name": "23-0-0-add-migrate-runs-to-git-ignore" + }, + { + "version": "23.0.0-beta.24", + "description": "Rename imports of `createNodesV2` from `@nx/vitest` to the canonical `createNodes` export.", + "implementation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes", + "documentation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.md", + "package": "@nx/vitest", + "name": "update-23-0-0-migrate-create-nodes-v2-import" + }, + { + "version": "23.0.0-beta.25", + "description": "Rewrites `@nx/node/src/*` subpath imports now that the `./src/*` subpath is no longer exposed by `@nx/node`'s exports map. Named imports/exports of public symbols are routed to `@nx/node` and the rest to the new `@nx/node/internal` entry; `require`, dynamic `import` and `jest.mock` calls reference the whole module and are routed to `@nx/node/internal`.", + "implementation": "./dist/src/migrations/update-23-0-0/rewrite-internal-subpath-imports", + "package": "@nx/node", + "name": "rewrite-node-internal-subpath-imports" + }, + { + "version": "23.0.0-beta.10", + "requires": { + "jest": ">=30.0.0" + }, + "description": "Update the Jest snapshot guide link in `.snap` files from the legacy `https://goo.gl/fbAQLP` URL to `https://jestjs.io/docs/snapshot-testing`, which Jest v30 now requires.", + "implementation": "./dist/src/migrations/update-23-0-0/update-snapshot-guide-link", + "documentation": "./dist/src/migrations/update-23-0-0/update-snapshot-guide-link.md", + "package": "@nx/jest", + "name": "update-snapshot-guide-link" + }, + { + "version": "23.0.0-beta.16", + "description": "Rewrites `@nx/jest/src/*` subpath imports now that the `./src/*` subpath is no longer exposed by `@nx/jest`'s exports map. Named imports/exports of public symbols are routed to `@nx/jest` and the rest to the new `@nx/jest/internal` entry; `require`, dynamic `import` and `jest.mock` calls reference the whole module and are routed to `@nx/jest/internal`.", + "implementation": "./dist/src/migrations/update-23-0-0/rewrite-internal-subpath-imports", + "package": "@nx/jest", + "name": "rewrite-jest-internal-subpath-imports" + }, + { + "version": "23.0.0-beta.16", + "description": "Replaces the removed `jestProjectGenerator` export from `@nx/jest` with its replacement `configurationGenerator`.", + "implementation": "./dist/src/migrations/update-23-0-0/rewrite-jest-project-generator", + "package": "@nx/jest", + "name": "rewrite-jest-project-generator" + }, + { + "version": "23.0.0-beta.22", + "description": "Migrate the deprecated `setupFile` option of the `@nx/jest:jest` executor: push the file path into `setupFilesAfterEnv` in the project's Jest config and remove the option from `project.json` and `nx.json` target defaults.", + "implementation": "./dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file", + "documentation": "./dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file.md", + "package": "@nx/jest", + "name": "migrate-jest-executor-setup-file" + }, + { + "version": "23.0.0-beta.22", + "description": "Migrate the deprecated `skipSetupFile` option of the `@nx/jest:configuration` generator stored as a default in `nx.json` or per-project `project.json` to `setupFile: 'none'` (when `true`) or remove it (when `false`).", + "implementation": "./dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file", + "documentation": "./dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file.md", + "package": "@nx/jest", + "name": "migrate-jest-configuration-skip-setup-file" + }, + { + "version": "23.0.0-beta.24", + "description": "Rename imports of `createNodesV2` from `@nx/jest/plugin` to the canonical `createNodes` export.", + "implementation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes", + "documentation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.md", + "package": "@nx/jest", + "name": "update-23-0-0-migrate-create-nodes-v2-import" + }, + { + "version": "23.1.0-beta.4", + "requires": { + "ts-jest": ">=29.2.0" + }, + "description": "Set `isolatedModules: true` in `tsconfig.spec.json` for ts-jest projects on TypeScript < 6 (ts-jest 29.2+ forces `moduleResolution: node10` on the CommonJS path, which ignores package `exports` maps and breaks resolution of exports-only workspace libraries), then verify the workspace still typechecks and remedy any failures the change surfaced.", + "implementation": "./dist/src/migrations/update-23-1-0/set-ts-jest-isolated-modules", + "prompt": "tools/ai-migrations/@nx/jest/23.1.1/verify-typecheck.md", + "documentation": "./dist/src/migrations/update-23-1-0/set-ts-jest-isolated-modules.md", + "package": "@nx/jest", + "name": "set-ts-jest-isolated-modules" + }, + { + "version": "23.0.0-beta.17", + "description": "Rewrites `@nx/eslint/src/*` subpath imports now that the `./src/*` subpath is no longer exposed by `@nx/eslint`'s exports map. Named imports/exports of public symbols are routed to `@nx/eslint` and the rest to the new `@nx/eslint/internal` entry; `require`, dynamic `import` and `jest.mock` calls reference the whole module and are routed to `@nx/eslint/internal`.", + "implementation": "./dist/src/migrations/update-23-0-0/rewrite-internal-subpath-imports", + "package": "@nx/eslint", + "name": "rewrite-eslint-internal-subpath-imports" + }, + { + "version": "23.0.0-beta.24", + "description": "Rename imports of `createNodesV2` from `@nx/eslint/plugin` to the canonical `createNodes` export.", + "implementation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes", + "documentation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.md", + "package": "@nx/eslint", + "name": "update-23-0-0-migrate-create-nodes-v2-import" + }, + { + "version": "23.1.0-beta.0", + "requires": { + "eslint": ">=9.0.0" + }, + "description": "Convert remaining ESLint configs to flat config for ESLint v9 and keep the workspace lint-passing, disabling rules whose preset defaults changed.", + "implementation": "./dist/src/migrations/update-23-1-0/convert-to-flat-config", + "prompt": "tools/ai-migrations/@nx/eslint/23.1.1/convert-to-flat-config.md", + "package": "@nx/eslint", + "name": "update-23-1-0-convert-to-flat-config" + }, + { + "version": "23.1.0-beta.5", + "description": "Handle typescript-eslint rules removed in v8 in ESLint flat configs, since referencing a removed rule stops the config from loading. Deletes the removed formatting/extension rules (e.g. @typescript-eslint/no-extra-semi) and rewrites the safe 1:1 renames (no-throw-literal -> only-throw-error, no-useless-template-literals -> no-unnecessary-template-expression) to preserve enforcement.", + "implementation": "./dist/src/migrations/update-23-1-0/remove-removed-typescript-eslint-extension-rules", + "package": "@nx/eslint", + "name": "update-23-1-0-remove-removed-typescript-eslint-extension-rules" + }, + { + "version": "23.1.0-beta.5", + "description": "Migrate the removed @typescript-eslint/ban-types rule to its v8 successors (no-empty-object-type, no-unsafe-function-type, no-wrapper-object-types), whose options do not map 1:1 - so it is driven by an AI prompt rather than a deterministic codemod.", + "prompt": "tools/ai-migrations/@nx/eslint/23.1.1/migrate-ban-types-rule.md", + "package": "@nx/eslint", + "name": "update-23-1-0-migrate-ban-types-rule" + }, + { + "version": "23.0.0-beta.14", + "description": "Rewrites `@nx/js/src/*` subpath imports to the new `@nx/js/internal` entry. The `./src/*` wildcard has been removed from `@nx/js`'s exports map; `@nx/js/src/release/version-actions` is preserved as a non-wildcard entry for back-compat with existing nx.json release configs. If a rewritten import resolves to a symbol that lives on the public `@nx/js` entry (e.g. `libraryGenerator`, `extractTsConfigBase`, `resolvePathsBaseUrl`), change the specifier to `@nx/js`.", + "factory": "./dist/src/migrations/update-23-0-0/rewrite-internal-subpath-imports", + "package": "@nx/js", + "name": "23-0-0-rewrite-internal-subpath-imports" + }, + { + "version": "23.0.0-beta.24", + "description": "Rename imports of `createNodesV2` from `@nx/js/typescript` to the canonical `createNodes` export.", + "implementation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes", + "documentation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.md", + "package": "@nx/js", + "name": "update-23-0-0-migrate-create-nodes-v2-import" + }, + { + "version": "23.1.0-beta.8", + "description": "Adds `\"ignoreDeprecations\": \"6.0\"` to tsconfig files whose compilerOptions (or ts-node.compilerOptions) carry a TypeScript 6 deprecated option value, set directly or inherited through `extends` including from a base this migration does not edit (e.g. moduleResolution node/node10/classic, baseUrl, target es5, esModuleInterop false, outFile, module amd/umd/system/none, alwaysStrict false, allowSyntheticDefaultImports false, downlevelIteration set). Also pins `\"strict\": false`, `\"noUncheckedSideEffectImports\": false`, `\"types\": [\"*\"]`, and `\"esModuleInterop\": false` in chain-root tsconfigs (no \"extends\") that lack each key, preserving pre-TS6 behavior where these defaults changed; `esModuleInterop` false is itself deprecated (removed in TS7) so it also receives `ignoreDeprecations`, deferring the interop change to the TS7 migration. Also adds `ignoreDeprecations` to every `tsconfig.json` (the exact name jest/ts-node auto-resolve when compiling a config file; ts-node injects a deprecated default `target: es5` there, so the load can hit a TS6 error even on a clean config), even one with no deprecated value of its own; a stale local `ignoreDeprecations` (e.g. `5.0`) that would otherwise override the inherited flag is upgraded.", + "requires": { + "typescript": ">=6.0.0" + }, + "factory": "./dist/src/migrations/update-23-1-0/add-ignore-deprecations-for-ts6", + "documentation": "./dist/src/migrations/update-23-1-0/add-ignore-deprecations-for-ts6.md", + "package": "@nx/js", + "name": "23-1-0-add-ignore-deprecations-for-ts6" + }, + { + "version": "23.1.0-rc.2", + "description": "Sets an explicit `rootDir` on project `tsconfig*.json` files that lack one, pinned to the source directory TypeScript 5 inferred implicitly, so programs keep compiling and emitting the same layout under TypeScript 6, which otherwise hard-fails with TS5011 or TS6059 (for example a spec tsconfig importing another project's source through a `paths` alias). The pin is written even when the inferred directory already equals the tsconfig directory, because tools like ts-jest with `isolatedModules` compile a program per file and re-infer a deeper directory from that subset. The value is computed by the compiler, so emit layout is unchanged. Each config is written on its own; the migration never writes to a shared `extends` base, so a config never inherits a value computed for a sibling. Composite projects are pinned to their own directory: `rootDir` already defaults there under `tsc` so it is a no-op for a real composite build, but ts-jest strips `composite` for its per-file transpile and the explicit value is needed to avoid TS5011 there.", + "requires": { + "typescript": ">=6.0.0" + }, + "factory": "./dist/src/migrations/update-23-1-0/set-tsconfig-root-dir-for-ts6", + "documentation": "./dist/src/migrations/update-23-1-0/set-tsconfig-root-dir-for-ts6.md", + "package": "@nx/js", + "name": "23-1-0-set-tsconfig-root-dir-for-ts6" + }, + { + "version": "23.0.0-beta.6", + "description": "Rewrite imports from `@nx/devkit/src/...` to `@nx/devkit` (for public symbols) or `@nx/devkit/internal` (for the rest), since deep imports are no longer reachable through the package's `exports` map.", + "implementation": "./dist/src/migrations/update-23-0-0/update-deep-imports", + "documentation": "./dist/src/migrations/update-23-0-0/update-deep-imports.md", + "package": "@nx/devkit", + "name": "update-devkit-deep-imports" + }, + { + "version": "23.0.0-beta.24", + "description": "Rename deprecated CreateNodes `V2` type imports (CreateNodesV2, CreateNodesContextV2, CreateNodesResultV2, CreateNodesFunctionV2, NxPluginV2) from `@nx/devkit` to their canonical names.", + "implementation": "./dist/src/migrations/update-23-0-0/rename-create-nodes-v2-types", + "documentation": "./dist/src/migrations/update-23-0-0/rename-create-nodes-v2-types.md", + "package": "@nx/devkit", + "name": "update-23-0-0-rename-create-nodes-v2-types" + }, + { + "version": "23.0.0-beta.10", + "description": "Rewrites imports of `@nx/workspace/src/utilities/typescript/compilation` to `@nx/js/internal`, where the module now lives.", + "implementation": "./dist/src/migrations/update-23-0-0/move-typescript-compilation-import", + "package": "@nx/workspace", + "name": "23-0-0-move-typescript-compilation-import" + }, + { + "version": "23.0.0-beta.10", + "description": "Safety net: ensure any remaining @nx/vite:test executor usages are swapped to @nx/vitest:test and @nx/vitest is installed.", + "implementation": "./dist/src/migrations/update-23-0-0/ensure-vitest-package-migration", + "documentation": "./dist/src/migrations/update-23-0-0/ensure-vitest-package-migration.md", + "package": "@nx/vite", + "name": "ensure-vitest-package-migration-23" + }, + { + "version": "23.0.0-beta.10", + "requires": { + "vite": ">=8.0.0" + }, + "description": "Rename `rollupOptions` to `rolldownOptions` in vite config files (top-level and inside `environments`). Vite 8 replaced Rollup with Rolldown.", + "implementation": "./dist/src/migrations/update-23-0-0/rename-rollup-options-to-rolldown-options", + "documentation": "./dist/src/migrations/update-23-0-0/rename-rollup-options-to-rolldown-options.md", + "package": "@nx/vite", + "name": "rename-rollup-options-to-rolldown-options" + }, + { + "version": "23.0.0-beta.10", + "requires": { + "vite": ">=8.0.0" + }, + "description": "Create AI Instructions to help migrate users workspaces past breaking changes for Vite 8.", + "prompt": "tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vite-8.md", + "package": "@nx/vite", + "name": "create-ai-instructions-for-vite-8" + }, + { + "version": "23.0.0-beta.22", + "requires": { + "vitest": ">=3.0.0" + }, + "description": "Migrate workspaces past breaking changes for Vitest 3.", + "implementation": "./dist/src/migrations/update-23-0-0/migrate-to-vitest-3", + "prompt": "tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-3.md", + "package": "@nx/vite", + "name": "migrate-to-vitest-3" + }, + { + "version": "23.0.0-beta.22", + "requires": { + "vitest": ">=4.0.0" + }, + "description": "Migrate workspaces past breaking changes for Vitest 4.", + "implementation": "./dist/src/migrations/update-23-0-0/migrate-to-vitest-4", + "prompt": "tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-4.md", + "package": "@nx/vite", + "name": "migrate-to-vitest-4" + }, + { + "version": "23.0.0-beta.24", + "description": "Rename imports of `createNodesV2` from `@nx/vite/plugin` to the canonical `createNodes` export.", + "implementation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes", + "documentation": "./dist/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.md", + "package": "@nx/vite", + "name": "update-23-0-0-migrate-create-nodes-v2-import" + }, + { + "version": "23.0.0-beta.25", + "description": "Rewrites `@nx/web/src/*` subpath imports now that the `./src/*` subpath is no longer exposed by `@nx/web`'s exports map. Named imports/exports of public symbols are routed to `@nx/web` and the rest to the new `@nx/web/internal` entry; `require`, dynamic `import` and `jest.mock` calls reference the whole module and are routed to `@nx/web/internal`.", + "implementation": "./dist/src/migrations/update-23-0-0/rewrite-internal-subpath-imports", + "package": "@nx/web", + "name": "rewrite-web-internal-subpath-imports" + } + ] +} diff --git a/package.json b/package.json index 109ff8a3..e49e4e7d 100644 --- a/package.json +++ b/package.json @@ -23,17 +23,17 @@ "@graphql-codegen/typescript-react-apollo": "4.4.2", "@graphql-codegen/typescript-resolvers": "6.1.0", "@graphql-tools/merge": "9.2.0", - "@nx/devkit": "22.7.8", - "@nx/eslint": "22.7.8", - "@nx/eslint-plugin": "22.7.8", - "@nx/jest": "22.7.8", - "@nx/js": "22.7.8", - "@nx/node": "22.7.8", - "@nx/plugin": "22.7.8", - "@nx/vite": "22.7.8", - "@nx/vitest": "22.7.8", - "@nx/web": "22.7.8", - "@nx/workspace": "22.7.8", + "@nx/devkit": "23.1.1", + "@nx/eslint": "23.1.1", + "@nx/eslint-plugin": "23.1.1", + "@nx/jest": "23.1.1", + "@nx/js": "23.1.1", + "@nx/node": "23.1.1", + "@nx/plugin": "23.1.1", + "@nx/vite": "23.1.1", + "@nx/vitest": "23.1.1", + "@nx/web": "23.1.1", + "@nx/workspace": "23.1.1", "@swc-node/register": "1.12.1", "@swc/cli": "~0.8.0", "@swc/core": "1.15.8", @@ -48,23 +48,23 @@ "eslint-config-prettier": "10.1.8", "graphql": "17.0.2", "graphql-scalars": "1.25.0", - "jest": "30.0.4", + "jest": "30.3.0", "jest-environment-jsdom": "30.4.1", "jest-environment-node": "30.0.4", "jest-util": "30.0.5", "jiti": "2.7.0", "jsonc-eslint-parser": "^2.1.0", "micromatch": "4.0.5", - "nx": "22.7.8", + "nx": "23.1.1", "prettier": "^2.6.2", "rimraf": "6.1.3", "ts-jest": "29.4.9", "ts-morph": "22.0.0", "ts-node": "10.9.1", "tsx": "4.23.1", - "typescript": "5.9.3", + "typescript": "6.0.3", "typescript-eslint": "8.65.0", - "vite": "7.3.6", + "vite": "8.2.0", "vitest": "4.1.9" }, "workspaces": [ diff --git a/tools/ai-migrations/@nx/eslint/23.1.1/convert-to-flat-config.md b/tools/ai-migrations/@nx/eslint/23.1.1/convert-to-flat-config.md new file mode 100644 index 00000000..e5ffc10b --- /dev/null +++ b/tools/ai-migrations/@nx/eslint/23.1.1/convert-to-flat-config.md @@ -0,0 +1,291 @@ +# ESLint v9 Flat Config Migration Instructions for LLM + +## Overview + +These instructions guide you through finishing the migration of an Nx workspace to ESLint v9. + +ESLint v9 makes flat config (`eslint.config.{mjs,cjs,js}`) the default config format. The legacy eslintrc format (`.eslintrc.*`) still works at runtime, but only when `ESLINT_USE_FLAT_CONFIG=false` is set, so Nx converts workspaces to flat config instead of relying on that escape hatch. + +The migration runs in two halves: + +1. A deterministic pre-pass (the `@nx/eslint:convert-to-flat-config` generator) that already converted the JSON and YAML eslintrc configs. +2. This prompt: finish the parts that need judgment and leave the workspace lint-passing. + +Work systematically through each section below. + + + +The pre-pass handled, mechanically: + +- Converted the root and per-project JSON/YAML eslintrc files to `eslint.config.mjs`: + - `eslint:recommended` to `js.configs.recommended` + - `@nx/*` presets to their flat-config equivalents + - `env` to `languageOptions.globals` + - `parser` / `parserOptions` to `languageOptions` + - `plugins` to the flat `plugins` object + - `ignorePatterns` and `.eslintignore` to `ignores` + - stale `.eslintrc`/`.eslintignore` references in `nx.json` and `project.json` inputs +- Added `@eslint/js` and `@eslint/eslintrc` to `package.json` when the converted config needs them. + +The pre-pass does NOT: + +- Convert JavaScript-based eslintrc files (`.eslintrc.js`, `.eslintrc.cjs`). It cannot evaluate them safely. +- Change the output formatter a lint target uses. +- Decide whether a generated `FlatCompat` shim should become flat-native config. +- Make the workspace pass lint after ESLint v9 changed which rules its preset defaults enable. + +Everything the pre-pass could not finish is forwarded to you in ``. + +How to read the wrapper sections above this file: + +- `` lists files the pre-pass wrote. Verify the new shape is in place; do not re-apply the same edit. It is absent when the pre-pass made no changes (for example a workspace that was already on flat config). +- `` lists detections the pre-pass forwarded because it could not safely complete them. Every entry is pending work. Address each one in the relevant section below. + + + + +In your handoff `summary` (1 to 3 sentences per the system prompt), name the sections you applied and explicitly call out any you skipped because they did not apply (for example "no JavaScript-based configs and no removed formatters in this workspace"). + + +## Pre-Migration Checklist + +1. **Confirm the ESLint version is v9**: + + ```bash + npx eslint --version + ``` + +2. **Locate all ESLint config files**: + - Flat configs: `eslint.config.{mjs,cjs,js}` at the root and in each project. + - Any remaining eslintrc files: `.eslintrc`, `.eslintrc.json`, `.eslintrc.yaml`, `.eslintrc.yml`, `.eslintrc.js`, `.eslintrc.cjs`. + - Ignore files: `.eslintignore`. + +3. **Identify all lint targets**: + + ```bash + nx show projects --with-target lint + ``` + + Check `project.json` files for the `@nx/eslint:lint` executor or `eslint` run-commands. Workspaces using the inferred plugin (`@nx/eslint/plugin`) get lint targets from the presence of `eslint.config.*`; inspect them with `nx show project --json`. + +4. **Identify local ESLint rules or plugins** authored inside the workspace. These use the rule API that changed in v9 (see section 6). + +--- + +## Nx-Specific Notes (read first) + +- **Flat config is the default in v9**. eslintrc only resolves when `ESLINT_USE_FLAT_CONFIG=false` is set. Nx converts the workspace to flat config so that no environment variable is required. +- **Shared base config pattern**: many Nx workspaces have a root `eslint.config.mjs` that each project imports, for example `import baseConfig from '../../eslint.config.mjs'`. Convert and verify the base config first, then the per-project configs. +- **Inferred plugin targets**: `@nx/eslint/plugin` infers the lint target from the presence of `eslint.config.*`. Renaming or moving the config invalidates inference. After config edits, run `nx reset && nx show project ` on a sample project to confirm the target is still present. +- **FlatCompat shim**: when the pre-pass could not translate a third-party `extends` or a complex override natively, it emitted a `FlatCompat` shim (from the `@eslint/eslintrc` package). That config works as-is, but section 3 covers replacing it with flat-native config where low-risk. + +--- + +## 1. Already on flat config? Verify only + +If the workspace already uses `eslint.config.*` at the root and in every project, with no remaining `.eslintrc.*` files, do NOT restructure it. The only required work is the passing-state check in section 4: a workspace on ESLint v9 can newly fail because v9 and typescript-eslint v8 changed which rules their recommended sets enable, even when the config was already flat. + +## 2. Convert JavaScript-based ESLint configs the pre-pass skipped + +**Search pattern**: `.eslintrc.js` and `.eslintrc.cjs` files (forwarded in ``). + +**What changed**: the pre-pass only converts JSON and YAML eslintrc files. JavaScript-based configs run arbitrary code, so they need manual conversion. + +```js +// BEFORE (.eslintrc.js) +module.exports = { + extends: ['../../.eslintrc.json'], + overrides: [ + { + files: ['*.ts'], + rules: { '@typescript-eslint/no-explicit-any': 'error' }, + }, + ], +}; +``` + +```js +// AFTER (eslint.config.mjs) +import baseConfig from '../../eslint.config.mjs'; + +export default [ + ...baseConfig, + { + files: ['**/*.ts'], + rules: { '@typescript-eslint/no-explicit-any': 'error' }, + }, +]; +``` + +**Action items**: + +- [ ] Convert each JavaScript-based config to `eslint.config.mjs`, mirroring the structure the pre-pass produced for the JSON/YAML configs. +- [ ] Preserve the existing rules, plugins, parser options, and overrides. +- [ ] Delete the original `.eslintrc.js` / `.eslintrc.cjs` once the flat config replaces it. +- [ ] Update any `project.json` / `nx.json` inputs that referenced the old file name. + +## 3. Convert FlatCompat shims to flat-native config where low-risk + +**Search pattern**: `FlatCompat`, `@eslint/eslintrc`, `compat.extends(`, `compat.config(` in the generated `eslint.config.*` files (listed in ``). + +**What changed**: `FlatCompat` is a runtime shim that adapts eslintrc-style `extends` into flat config. Many plugins now ship native flat presets, which are clearer and avoid the shim. + +**Decision rule**: convert a `FlatCompat` usage to flat-native config when it is low-risk, otherwise keep the shim. + +- Low-risk (prefer flat-native): typescript-eslint configs, and plugins that document a flat preset (for example `eslint-plugin-react`, `eslint-plugin-import`). +- Keep the shim: third-party shared configs that do not document a flat-config entry point. + +```js +// BEFORE (FlatCompat shim, eslint.config.mjs) +import js from '@eslint/js'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import { FlatCompat } from '@eslint/eslintrc'; + +const compat = new FlatCompat({ + baseDirectory: dirname(fileURLToPath(import.meta.url)), + recommendedConfig: js.configs.recommended, +}); + +export default [...compat.extends('plugin:@typescript-eslint/recommended')]; +``` + +```js +// AFTER (flat-native, eslint.config.mjs) +import tseslint from 'typescript-eslint'; + +export default [...tseslint.configs.recommended]; +``` + +**Action items**: + +- [ ] For each `FlatCompat` usage, decide flat-native vs keep-the-shim using the rule above. +- [ ] When converting, drop the now-unused `@eslint/eslintrc` import if no shim remains in that file. +- [ ] Re-run lint after each change to confirm the rule set did not silently shift. + +## 4. Restore the passing baseline (required) + +This is the core requirement of the migration: the workspace must lint cleanly when you are done. + +ESLint v9 and typescript-eslint v8 changed which rules their recommended sets enable. A rule the user never configured may now report errors. Disable those rules; do not edit source files to satisfy them. + +The set of rules the user explicitly configured before the migration is in `` (the entry that starts with "Passing-state requirement"). + +**Procedure**: + +1. Run lint across the workspace: + + ```bash + nx run-many -t lint + ``` + +2. Tell a rule violation apart from a plugin crash. If a project fails with a thrown error instead of rule findings - a `TypeError` such as `context.getAncestors is not a function`, a `Could not find "" in plugin ""` / `couldn't find the config "" to extend from`, or a plugin that fails to load - the plugin predates ESLint v9; this is not a changed preset default. Do NOT disable the rule (that silently drops its coverage); update the plugin instead (section 6), then re-run lint and continue. + +3. For each rule that now reports errors (findings, not a thrown error): + - If the rule ID is NOT in the user's explicit list, it came from a changed preset default. Disable it in the relevant flat config with a short comment explaining why. + - If the rule ID IS in the user's explicit list, the user chose it. Leave it as-is and report it in your handoff summary. + +```js +// Disable a rule that a changed preset default newly enabled (eslint.config.mjs). +export default [ + ...baseConfig, + { + files: ['**/*.ts'], + rules: { + // Newly enabled by the ESLint v9 recommended set; was not enforced before the upgrade. + 'no-unused-expressions': 'off', + }, + }, +]; +``` + +**Action items**: + +- [ ] Run lint and collect every newly reported rule. +- [ ] Treat a plugin crash (a thrown error, not rule findings) as a version incompatibility: update the plugin (section 6), never disable its rules to silence it. +- [ ] Disable preset-originated rules that the user did not configure. +- [ ] Never disable or weaken a rule the user explicitly configured. +- [ ] Never edit source files to satisfy a newly enabled rule. + + +You cannot make lint pass without either editing source files or disabling a rule the user explicitly configured. Write status: failed and explain which rule and project in your summary. Do not guess. + + +## 5. Fix removed output formatters + +**Search pattern**: the `format` option on lint targets (forwarded in ``). + +**What changed**: ESLint v9 removed several built-in output formatters. The built-ins that remain are `stylish`, `html`, `json`, and `json-with-metadata`. Removed: `compact`, `codeframe`, `unix`, `visualstudio`, `table`, `checkstyle`, `jslint-xml`, `junit`, `tap`. + +**Fix**: switch the target to a built-in that remains, or install the matching community package and reference it by its package name. + +```bash +# Example: keep junit output by installing the community formatter package. +npm install --save-dev eslint-formatter-junit +``` + +```jsonc +// project.json (reference the community formatter by package name) +"lint": { + "executor": "@nx/eslint:lint", + "options": { "format": "eslint-formatter-junit" } +} +``` + +**Action items**: + +- [ ] For each flagged target, switch to a remaining built-in formatter or a community package. +- [ ] When using a community package, add it to `devDependencies`. + +## 6. Other ESLint v9 runtime breaking changes + +**Search pattern**: lint executor options, run-commands invoking `eslint`, and local rule/plugin source. + +- **Removed CLI flags and executor options**: `--rulesdir`, `--ext`, and `--resolve-plugins-relative-to` were removed. The matching `@nx/eslint:lint` options (`rulesdir`, `resolvePluginsRelativeTo`, `ignorePath`) are not supported for flat config. Move file targeting into the config via the `files` and `ignores` keys. +- **No eslintrc auto-merge**: flat config does not merge `.eslintrc.*` files found up the tree. Every setting must live in `eslint.config.*`. +- **Third-party plugins that predate ESLint v9**: an installed ESLint plugin that was not updated for v9 breaks at lint time - its rules call the removed `context` APIs below, or it only ships an eslintrc config that no longer loads. This surfaces as a thrown error (see section 4), not a new rule violation. List the installed plugins from `package.json` (`dependencies`/`devDependencies` matching `eslint-plugin-*` or `@/eslint-plugin-*`), and for each confirm its version supports ESLint v9 (its changelog, or that `peerDependencies.eslint` allows `>=9`). Update any that do not, and prefer the plugin's flat entry point where it ships one (for example `eslint-plugin-cypress/flat`). Update the plugin rather than disabling its rules. +- **Local rule API moved to `SourceCode`** (only relevant if the workspace authors its own rules): + - `context.getScope()` to `sourceCode.getScope(node)` + - `context.getAncestors()` to `sourceCode.getAncestors(node)` + - `context.getDeclaredVariables()` to `sourceCode.getDeclaredVariables(node)` + - `context.markVariableAsUsed(name)` to `sourceCode.markVariableAsUsed(name, node)` + - `context.getSource()` to `sourceCode.getText()` + - `context.parserServices` to `sourceCode.parserServices` +- **Stricter rule schema**: a custom rule that accepts options must declare `meta.schema` in v9. + +**Action items**: + +- [ ] Remove unsupported CLI flags and executor options; move targeting into `files` / `ignores`. +- [ ] Update any installed third-party ESLint plugin that does not yet support ESLint v9, preferring its flat entry point; do not disable its rules to work around a load error. +- [ ] Update local rules to the `SourceCode` API and add `meta.schema` where required. + +--- + +## Post-Migration Verification + +1. Clear the inference cache so renamed configs are re-detected: + + ```bash + nx reset + ``` + +2. Confirm lint passes across the workspace: + + ```bash + nx run-many -t lint + ``` + +3. Spot-check that a converted project resolves its config: + + ```bash + npx eslint --print-config + ``` + +4. Confirm no `.eslintrc.*` files remain unless one was intentionally kept. + +## References + +- ESLint configuration files (flat config): https://eslint.org/docs/latest/use/configure/configuration-files +- Migrate to ESLint v9.0.0: https://eslint.org/docs/latest/use/migrate-to-9.0.0 +- typescript-eslint configs: https://typescript-eslint.io/users/configs +- Nx ESLint plugin: https://nx.dev/nx-api/eslint diff --git a/tools/ai-migrations/@nx/eslint/23.1.1/migrate-ban-types-rule.md b/tools/ai-migrations/@nx/eslint/23.1.1/migrate-ban-types-rule.md new file mode 100644 index 00000000..407c98c6 --- /dev/null +++ b/tools/ai-migrations/@nx/eslint/23.1.1/migrate-ban-types-rule.md @@ -0,0 +1,29 @@ +# Migrate the removed `@typescript-eslint/ban-types` rule + +typescript-eslint v8 removed `@typescript-eslint/ban-types`, so an ESLint flat +config that still references it fails to load. It was split into three rules: + +- `@typescript-eslint/no-empty-object-type` - the `{}` type +- `@typescript-eslint/no-unsafe-function-type` - the `Function` type +- `@typescript-eslint/no-wrapper-object-types` - wrapper types (`String`, `Number`, `Boolean`, `Object`, ...) + +## First, check whether there is anything to do + +Confirm both conditions before changing anything. If either fails, make no +changes and stop: + +1. Some ESLint flat config (`eslint.config.{mjs,cjs,js,cts,ts,mts}`) references + `@typescript-eslint/ban-types`. Search the workspace for that string; if no + config uses the rule, there is nothing to migrate. +2. The workspace is on typescript-eslint v8 or later (check `typescript-eslint` + or `@typescript-eslint/eslint-plugin` in `package.json`). On v7 the rule + still exists, so leave it untouched. + +## Migrate + +In every ESLint flat config (`eslint.config.{mjs,cjs,js,cts,ts,mts}`) that sets +`@typescript-eslint/ban-types`, replace that single entry with the three rules +above. The options do not map 1:1: if the old entry was just `'error'`/`'warn'`, +set all three to that level; if it customized `types`/`extendDefaults`, translate +the intent to whichever successor rule covers each banned type and drop anything +with no equivalent. Then run `nx run-many -t lint` and confirm the configs load. diff --git a/tools/ai-migrations/@nx/jest/23.1.1/verify-typecheck.md b/tools/ai-migrations/@nx/jest/23.1.1/verify-typecheck.md new file mode 100644 index 00000000..66c88f3b --- /dev/null +++ b/tools/ai-migrations/@nx/jest/23.1.1/verify-typecheck.md @@ -0,0 +1,14 @@ +# Verify typecheck after the Nx 23.1 migration + +This migration enables `isolatedModules` on ts-jest spec configs. Run +`nx run-many -t typecheck` and fix any project it broke. + +`isolatedModules` can fail typecheck (TS1205 - a re-exported type needs `export type`; +TS2748 - const enum access) or break a project's tests at runtime. To fix a broken +project, remove `isolatedModules` from its `tsconfig.spec.json` and re-run. If a +`TS2307: Cannot find module` error then appears for a workspace library, that project +needs `isolatedModules` - keep it and fix the source instead (avoid mixing +`module.exports` with an ESM `export`, and avoid cross-file `const enum`). + +Re-run until the projects this migration touched typecheck. See the migration +documentation for details. diff --git a/tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vite-8.md b/tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vite-8.md new file mode 100644 index 00000000..f921251f --- /dev/null +++ b/tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vite-8.md @@ -0,0 +1,254 @@ +# Vite 8 Migration Instructions for LLM + +## Overview + +These instructions guide you through migrating an Nx workspace from Vite 7 to Vite 8. Vite 8 swaps Rollup for Rolldown as its bundler and updates a number of plugin APIs. Work through each section in order and run tests after each change. + +## Pre-Migration Checklist + +1. **Identify all Vite-using projects**: + + ```bash + nx show projects --with-target build + nx show projects --with-target serve + ``` + +2. **Locate all Vite configuration files**: + - Search for `vite.config.{ts,js,mts,mjs,cts,cjs}` + - Check `project.json` files for inline Vite-related options + +3. **Cypress Component Testing**: Cypress >= 15.14.0 supports Vite 8. The `nx migrate` step bumps Cypress automatically. If you have explicitly pinned Cypress below 15.14.0, upgrade it before bumping Vite. + +## Migration Steps by Category + +### 1. Rename `rollupOptions` to `rolldownOptions` + +The `nx migrate` codemod handles this automatically for `vite.config.{ts,js,mts,mjs,cts,cjs}` files. If you have `rollupOptions` declared elsewhere (e.g., in helper modules imported by your config), rename them by hand. + +**Search Pattern**: `rollupOptions` in any TypeScript/JavaScript file + +```typescript +// ❌ BEFORE (Vite 7) +export default defineConfig({ + build: { + rollupOptions: { + external: ['react'], + output: { manualChunks: { vendor: ['react', 'react-dom'] } }, + }, + }, +}); + +// ✅ AFTER (Vite 8) +export default defineConfig({ + build: { + rolldownOptions: { + external: ['react'], + output: { manualChunks: { vendor: ['react', 'react-dom'] } }, + }, + }, +}); +``` + +**Action Items**: + +- [ ] Verify the codemod picked up every config file (`rg "rollupOptions"` should return zero hits inside vite configs) +- [ ] Rename any `rollupOptions` in helper modules or shared config builders +- [ ] Update CI scripts that parse `build.rollupOptions` (e.g., custom bundle-size assertions) + +### 2. `@vitejs/plugin-react` v6 (Oxc Replaces Babel) + +`@vitejs/plugin-react@^6` is required for Vite 8 and uses [Oxc](https://oxc.rs/) instead of Babel for JSX transformation. The plugin's `babel` option is gone. + +```typescript +// ❌ BEFORE (Vite 7, plugin-react v4) +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [ + react({ + babel: { + plugins: ['babel-plugin-styled-components'], + }, + }), + ], +}); + +// ✅ AFTER (Vite 8, plugin-react v6) +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], +}); +``` + +**Action Items**: + +- [ ] Remove `babel` options from `react()` plugin invocations +- [ ] If you depended on a Babel plugin (e.g., styled-components, emotion, relay), find an Oxc-compatible replacement or switch to `@vitejs/plugin-react-swc` (still Babel-free). There is no drop-in for arbitrary Babel plugins. +- [ ] If you cannot drop your Babel plugin, stay on Vite 7 + plugin-react v4 for now (see "Project-Level Vite 7 Pinning") +- [ ] Run `pnpm install` (or your package manager equivalent) so the new plugin-react version resolves + +### 3. Angular + Vitest (vitest-analog path): Add `@oxc-project/runtime` + +Angular projects whose `test` target uses `@nx/vitest:test` (the vitest-analog setup wired by `@analogjs/vite-plugin-angular`) need `@oxc-project/runtime` declared in the workspace `devDependencies`. + +`@analogjs/vite-plugin-angular` registers an `angularVitestPlugin` (active in test mode) whose `transform` hook matches `@angular/*` `fesm2022` modules that contain `async ` (plus any `@angular/cdk` file) and calls `vite.transformWithOxc(code, id, { target: 'es2016', … })`. The downlevel is deliberate: Zone.js relies on monkey-patching promise scheduling for `fakeAsync` and friends, which it cannot do on native `async`/`await`, so the plugin lowers them to a form Zone.js can intercept. With `target: 'es2016'`, oxc emits the helpers as external `@oxc-project/runtime/helpers/*` imports (oxc's default `HelperMode = 'Runtime'`). Nothing in the upstream chain (`analogjs`, `@angular/core`, `vite`, `rolldown`) declares `@oxc-project/runtime` in a way that's resolvable from the consumer's workspace, so `vite:import-analysis` fails to resolve those imports unless the dep is added explicitly. + +Angular projects whose `test` target uses `@angular/build:unit-test` or `@nx/angular:unit-test` (the vitest-angular path) do **not** need this dependency — that path doesn't load `@analogjs/vite-plugin-angular`, sets `optimizeDeps.noDiscovery: true`, and uses an in-memory test provider, so the downlevel transform that emits the helper imports never runs. + +**Search Pattern**: Projects with `test.executor` set to `@nx/vitest:test` that also have `@analogjs/vite-plugin-angular` in their `vite.config.*`. + +```bash +rg '"@nx/vitest:test"' --type json +rg '@analogjs/vite-plugin-angular' --type ts --type js +``` + +**Action Items**: + +- [ ] For each affected workspace, add `@oxc-project/runtime` to root `devDependencies` (the Nx Angular generators do this automatically on the vitest-analog path; check legacy workspaces that pre-date that) +- [ ] Run `pnpm install` (or equivalent) +- [ ] Run the project's tests to confirm the helper resolves + +### 4. Type Resolution Under `moduleResolution: "node"` + +Vite 8 ships its types only via conditional `exports` (it dropped the top-level `types` field that Vite 7 carried), which TypeScript cannot resolve under `moduleResolution: "node"`. Symptoms include type errors on `defineConfig`, `UserConfig`, or plugin return types. + +**Action Items**: + +- [ ] Update affected `tsconfig*.json` files: `"moduleResolution": "bundler"` (recommended) or `"node16"`/`"nodenext"` +- [ ] If you cannot change `moduleResolution`, narrow the impact with explicit `as any` casts at vite imports. The Nx-generated configs already do this in a handful of places. +- [ ] Run `tsc --noEmit` after the change to confirm types resolve cleanly + +### 5. Bundle Validation Scripts + +Rolldown produces different chunk and module counts than Rollup for the same input. Custom build validation (e.g., "bundle has exactly N chunks") will need to be re-baselined. + +**Action Items**: + +- [ ] Identify scripts that assert chunk/module counts or names +- [ ] Re-run the build and update expected values +- [ ] Prefer asserting on size budgets over exact counts going forward + +### 6. Project-Level Vite 7 Pinning (Custom Babel Plugins) + +If a project depends on a Babel plugin that has no Oxc equivalent, pin that project to Vite 7. + +```jsonc +// package.json (workspace root) +{ + "devDependencies": { + "vite": "^7.1.0", + "@vitejs/plugin-react": "^4.3.0", + }, +} +``` + +If only some projects need to stay on 7 while the rest move to 8, use your package manager's overrides feature: + +- pnpm: `pnpm.overrides` in root `package.json` +- npm/yarn: `overrides`/`resolutions` + +**Action Items**: + +- [ ] Document which projects are pinned to Vite 7 and why +- [ ] Track Oxc plugin equivalents so you can unpin later + +## Post-Migration Validation + +### 1. Run Tests Per Project + +```bash +nx run-many -t test -p PROJECT_NAME +``` + +### 2. Build All Affected Projects + +```bash +nx affected -t build +``` + +### 3. Validate Dev Server + +```bash +nx serve PROJECT_NAME +``` + +Open the app and verify HMR still works for changes in source files. + +### 4. Validate CI Pipeline + +```bash +nx prepush +``` + +### 5. Review Migration Checklist + +- [ ] All `rollupOptions` references renamed to `rolldownOptions` +- [ ] `@vitejs/plugin-react` upgraded to v6 (or pinned to v4 with a documented reason) +- [ ] No `babel: { ... }` options remain in `react()` calls (or those projects are pinned to Vite 7) +- [ ] Angular + vitest-analog projects have `@oxc-project/runtime` in root `devDependencies` +- [ ] Cypress upgraded by `nx migrate` (>= 15.14.0 for Vite 8 support) +- [ ] `tsc --noEmit` passes on all affected projects +- [ ] Build, test, and dev-server commands all succeed + +## Common Issues and Solutions + +### Issue: `Cannot find name 'rollupOptions'` or build options ignored + +**Solution**: Rename to `rolldownOptions`. Vite 8 still accepts `rollupOptions` as a deprecated alias (it copies the value to `rolldownOptions` and logs a deprecation warning), but mixing both at the same level may cause precedence surprises (`rolldownOptions` wins). + +### Issue: Babel plugin no longer applied (e.g., styled-components classNames missing) + +**Solution**: `@vitejs/plugin-react@6` removed Babel. Find an Oxc-compatible alternative, switch to `@vitejs/plugin-react-swc`, or pin to Vite 7 + plugin-react v4. + +### Issue: Angular + Vitest fails with `Failed to resolve import "@oxc-project/runtime/helpers/..."` + +**Solution**: Add `@oxc-project/runtime` to root `devDependencies` and reinstall. This only affects projects whose `test` target uses `@nx/vitest:test` (the vitest-analog setup); projects using `@angular/build:unit-test` / `@nx/angular:unit-test` are not affected. + +### Issue: Type errors on `defineConfig`, `UserConfig`, or `Plugin` imports from vite + +**Solution**: Set `moduleResolution: "bundler"` in your tsconfig (or `nodenext` if you need Node-style resolution). + +### Issue: Cypress CT fails to start under Vite 8 + +**Solution**: Confirm `cypress >= 15.14.0` is installed (Vite 8 support landed in that release). `nx migrate` bumps Cypress automatically; if you pinned it lower in `package.json`, remove the pin and reinstall. + +### Issue: Bundle-size or chunk-count assertions fail after upgrade + +**Solution**: Rolldown chunks differently than Rollup. Re-baseline expected values. + +## Files to Review + +```bash +# Vite config files +find . -name "vite.config.*" -not -path "*/node_modules/*" + +# Cypress component testing setup +rg "@nx/(angular|react|next|remix)/plugins/component-testing" + +# Babel plugin usage in plugin-react +rg "@vitejs/plugin-react.*babel|babel:\s*\{" --type ts --type js + +# Angular projects with Vitest +rg "@angular/build" -l package.json +``` + +## Guard Rails + +DO NOT + +- Force tests to pass by removing assertions or replacing them with `expect(true).toBe(true)` +- Strip `react()` plugin options without finding an equivalent for what they did +- Roll Cypress back below 15.14.0 after the migrate. Older Cypress fails to start under Vite 8. + +--- + +## Notes for LLM Execution + +When executing this migration: + +1. **Work systematically**: Complete one category before moving to the next +2. **Test after each change**: Build and test affected projects after each step +3. **Keep user informed**: Report which categories applied and which were skipped +4. **Use TodoWrite tool**: Track migration progress for visibility +5. **Stop and ask** if a project depends on a Babel plugin with no Oxc equivalent. Pinning to Vite 7 is a workspace decision. diff --git a/tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-3.md b/tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-3.md new file mode 100644 index 00000000..52638e66 --- /dev/null +++ b/tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-3.md @@ -0,0 +1,604 @@ +# Vitest 3.0 Migration Instructions for LLM + +## Overview + +These instructions guide you through migrating an Nx workspace containing multiple Vitest projects to Vitest 3.0. The workspace may currently be on Vitest 1.x or Vitest 2.x. This guide covers breaking changes for both upgrade paths: + +- **From Vitest 1.x**: apply BOTH the "Vitest 1.x → 2.0" and "Vitest 2.x → 3.0" sections below. +- **From Vitest 2.x**: apply only the "Vitest 2.x → 3.0" section. + +Work systematically through each breaking change category. + + + +The pre-pass handled, mechanically: + +- `--segfault-retry` removal from `package.json` scripts AND `project.json` `options.{args,command,commands}` +- `@vitest/coverage-c8` → `@vitest/coverage-v8` package rename (preserving the user's pin) +- `vitest typecheck` → `vitest --typecheck` in `package.json` scripts AND `project.json` `options.{args,command,commands}` +- `SnapshotEnvironment` import path `'vitest'` → `'vitest/snapshot'` (only when it is the sole named binding) +- `browser.provider: 'none'` → `'preview'` (only when the value is a direct string literal under `test.browser.provider`) +- `browser.indexScripts` → `orchestratorScripts` (only as a direct property name) + +The pre-pass **does not** edit CI provider configs (`.github/workflows/*.yml`, `.gitlab-ci.yml`, `azure-pipelines.yml`, `.circleci/config.yml`, `bitbucket-pipelines.yml`) — YAML structure varies too much. Any matches it finds there are forwarded to you in ``. + +**The vast majority of action items below are NOT covered by the pre-pass** and still require your attention — every section other than the six items above. + +How to read the wrapper sections above this file: + +- `` lists files the pre-pass already wrote to. Verify the new shape is in place; do not re-apply the same edit. +- `` lists detections the pre-pass forwarded because it could not safely complete them. **Every entry is pending work** — address each one in the relevant section below, not as a separate task. + + + + +In your handoff `summary` (1–3 sentences per the system prompt), name the breaking-change categories you applied; explicitly call out any you skipped because they didn't apply (e.g., "no browser-mode configs in this workspace"). + + +## Pre-Migration Checklist + +1. **Identify the current Vitest version**: + + ```bash + npx vitest --version + ``` + +2. **Identify all Vitest projects**: + + ```bash + nx show projects --with-target test + ``` + +3. **Locate all Vitest configuration files**: + - Search for `vitest.config.{ts,js,mjs}` + - Search for `vitest.workspace.{ts,js,mjs}` (deprecated in Vitest 3.2 — see "v3.2 Workspace File Deprecation" below) + - Check `project.json` files for `@nx/vitest:test` / `@nx/vite:test` executor options + - For workspaces relying on the inferred plugin (`@nx/vitest/plugin`), targets come from inference — inspect them with `nx show project --json | jq .targets` + +4. **Identify affected code**: + - Test files: `**/*.{spec,test}.{ts,js,tsx,jsx}` + - Mock usage: files using `vi.fn()`, `vi.spyOn()`, `vi.mock()`, `vi.useFakeTimers()` + - Coverage configuration references + - Browser mode configurations + - Custom reporters, sequencers, and snapshot-related imports + +--- + +## Nx-Specific Notes (read first) + +- **Inferred plugin targets**: modern Nx workspaces use `@nx/vitest/plugin` (or `@nx/vite/plugin` historically) to _infer_ the test target from the presence of a `vitest.config.*` file. `project.json` may have no `test` target at all. Renaming or moving `vitest.config.*` invalidates inference. After config edits, run `nx reset && nx show project ` on a sample project to confirm the target is still present. +- **Shared base config pattern**: many Nx workspaces extend a workspace-root `vitest.config.base.ts` via `mergeConfig`. Apply transforms to the BASE config first, then per-project overrides. Otherwise a migrated base may conflict with un-migrated children. +- **Angular projects using AnalogJS** (`@analogjs/vitest-angular`, `@analogjs/vite-plugin-angular`): the Analog packages are bumped automatically by Nx's `packageJsonUpdates`. Review the Analog-specific setup file (typically `src/test-setup.ts`) and any plugin invocations in the per-project `vitest.config.ts` for changes between Analog `~1.x` and `~2.x` lines. +- **CI configuration**: when `@nx/vitest/plugin` is configured with `ciTargetName`, per-test-file targets are inferred — your `.github/workflows/*.yml` doesn't need direct reporter changes. CI-side reporter config matters only if you bypass the plugin. + +--- + +## Vitest 1.x → 2.0 Breaking Changes + +Skip this section if your workspace is already on Vitest 2.x. + +### 1.1 Default Pool Changed from `threads` to `forks` + +**Search Pattern**: `poolOptions` in all `vitest.config.*` files + +**What Changed**: The default `pool` switched from `threads` to `forks` for improved stability. Existing `poolOptions.threads` configurations now apply to the non-default pool unless `pool` is explicitly set. + +```typescript +// ❌ BEFORE (Vitest 1.x — relying on implicit `threads` default) +export default defineConfig({ + test: { + poolOptions: { + threads: { singleThread: true }, + }, + }, +}); + +// ✅ AFTER (Vitest 2.0 — either explicitly set the pool or move options to `forks`) +export default defineConfig({ + test: { + poolOptions: { + forks: { singleFork: true }, + }, + }, +}); +``` + +**Action Items**: + +- [ ] If a project relied on the implicit pool, decide whether to keep `threads` (set `pool: 'threads'` explicitly) or move options to `forks`. +- [ ] Migrate `poolOptions.threads.singleThread` → `poolOptions.forks.singleFork` if switching. +- [ ] Migrate `poolOptions.threads.maxThreads/minThreads` → `poolOptions.forks.maxForks/minForks` if switching. + + +You cannot determine whether the workspace intended the v1.x implicit-`threads` behavior (e.g., the codebase uses worker-only APIs like `SharedArrayBuffer`) or expected the v2.x `forks` default. Do not guess. + + +### 1.2 Hook Execution Order: Now Serial (and Reverse for `after*`) + +**Search Pattern**: `beforeAll`, `beforeEach`, `afterAll`, `afterEach` usages relying on parallel execution + +**What Changed**: Hooks moved from parallel to serial execution. `afterAll`/`afterEach` now run in reverse declaration order. Tests relying on parallel hook side effects or specific teardown ordering may break. + +```typescript +// To revert to parallel behavior: +export default defineConfig({ + test: { + sequence: { + hooks: 'parallel', + }, + }, +}); +``` + +**Action Items**: + +- [ ] Audit hooks that mutate shared state — they now execute sequentially in declaration order. +- [ ] Audit `afterAll`/`afterEach` hooks that depend on registration order; their order is now reversed. +- [ ] Add `sequence.hooks: 'parallel'` only if you cannot otherwise resolve hook-order dependencies. + +### 1.3 Concurrent Suites Now Run All Tests Concurrently + +**Search Pattern**: `describe.concurrent`, `suite.concurrent` + +**What Changed**: Declaring `concurrent` on a suite now runs all its tests concurrently (Jest-aligned) instead of grouping by suite. Bound by `maxConcurrency`. + +**Action Items**: + +- [ ] Review concurrent suites for shared mutable state that previously serialized through suite grouping. +- [ ] Adjust `maxConcurrency` if needed. + +### 1.4 V8 Coverage: `ignoreEmptyLines` On by Default + +**Search Pattern**: `coverage` configuration in `vitest.config.*` (V8 provider) + +**What Changed**: `coverage.ignoreEmptyLines` defaults to `true`. Coverage thresholds may shift. + +```typescript +// To restore the previous behavior: +export default defineConfig({ + test: { + coverage: { + ignoreEmptyLines: false, + }, + }, +}); +``` + +**Action Items**: + +- [ ] Re-baseline V8 coverage thresholds if they exist. +- [ ] Set `coverage.ignoreEmptyLines: false` only if you need the prior numbers exactly. + +### 1.5 `watchExclude` Option Removed + +**Search Pattern**: `watchExclude` in `vitest.config.*` + +```typescript +// ❌ BEFORE (Vitest 1.x) +export default defineConfig({ + test: { + watchExclude: ['node_modules', 'custom/path/**'], + }, +}); + +// ✅ AFTER (Vitest 2.0) +export default defineConfig({ + server: { + watch: { + ignored: ['**/node_modules/**', 'custom/path/**'], + }, + }, +}); +``` + +**Pattern Semantics Note**: `server.watch.ignored` uses **chokidar** patterns, while Vitest 1.x's `watchExclude` accepted simpler relative-path matchers. A literal find-and-replace may over- or under-ignore. Treat each entry as manual review: + +- A bare directory name like `'node_modules'` typically needs `'**/node_modules/**'` to match nested occurrences. +- Glob patterns ending in `/**` are usually portable as-is. +- After the rewrite, verify watch mode picks up the right files with `nx test --watch`. + +**Action Items**: + +- [ ] Move every `test.watchExclude` entry to `server.watch.ignored`, adjusting pattern syntax to chokidar conventions. +- [ ] Remove `watchExclude` from `vitest.config.*`. + + +An existing `watchExclude` entry uses a pattern whose chokidar equivalent is ambiguous (e.g., a relative path without `**` wrapping, an entry that may need both `**/foo/**` and `foo/**`). Write status: failed with the specific patterns you're unsure about; the user should decide. + + +### 1.6 `--segfault-retry` CLI Flag Removed + +**What Changed**: The CLI flag was removed; the underlying issue is fixed by switching to the `forks` pool (now the default). + +**Action Items**: + +- [ ] Remove `--segfault-retry` from scripts, `project.json` test target options, and CI configuration. + +### 1.7 Task API: `.suite` Now Optional, `.testPath` Required + +**Search Pattern**: Custom reporters and tooling that traverse the task tree + +**What Changed**: Top-level task `.suite` is now optional. Use `.file` (available on all tasks) instead. `expect.getState().testPath` is now always populated; `expect.getState().currentTestName` no longer includes the file name. + +**Action Items**: + +- [ ] In custom reporters / utilities, replace `.suite` chains with `.file` where the top-level task could be a file root. +- [ ] If you parsed file names out of `currentTestName`, switch to `testPath`. + +### 1.8 JSON Reporter Now Includes `task.meta` + +**What Changed**: Output shape gained `task.meta` per assertion result. + +**Action Items**: + +- [ ] If you ingest the JSON reporter output, accept the new `task.meta` field (additive — most consumers will be unaffected). + +### 1.9 Mock Generic Types Simplified + +**Search Pattern**: `vi.fn<...>(...)`, `Mock<...>` + +```typescript +// ❌ BEFORE (Vitest 1.x) +import { type Mock, vi } from 'vitest'; + +const add = (x: number, y: number): number => x + y; + +const mockAdd = vi.fn, ReturnType>(); +const mockAdd2: Mock, ReturnType> = vi.fn(); + +// ✅ AFTER (Vitest 2.0) +const mockAdd = vi.fn(); +const mockAdd2: Mock = vi.fn(); +``` + +**Action Items**: + +- [ ] Replace two-generic `vi.fn()` with single-generic `vi.fn()` everywhere. +- [ ] Replace two-generic `Mock` with single-generic `Mock` everywhere. + +### 1.10 `mock.results` No Longer Auto-Resolves Promises + +**Search Pattern**: `.mock.results` accesses on async mocks + +**What Changed**: For mocks returning Promises, `mock.results` now contains the Promise itself. Use the new `mock.settledResults` for resolved values. + +```typescript +// ❌ BEFORE (Vitest 1.x) +const fn = vi.fn().mockResolvedValueOnce('result'); +await fn(); +const result = fn.mock.results[0]; // 'result' (auto-resolved) + +// ✅ AFTER (Vitest 2.0) +const result = fn.mock.results[0]; // a Promise +const settled = fn.mock.settledResults[0]; // 'result' +``` + +**Action Items**: + +- [ ] Replace `.mock.results[i]` reads with `.mock.settledResults[i]` for promise-returning mocks. +- [ ] Consider switching assertions to the new `toHaveResolved*` matchers where appropriate. + +### 1.11 Browser Mode Renames + +**Search Pattern**: `browser.provider`, `browser.indexScripts` in `vitest.config.*` + +**What Changed**: The `none` provider was renamed to `preview` and is now the default. `indexScripts` was renamed to `orchestratorScripts`. + +**Action Items**: + +- [ ] Rename `browser.provider: 'none'` → `browser.provider: 'preview'`. +- [ ] Rename `browser.indexScripts` → `browser.orchestratorScripts`. + +### 1.12 Deprecated APIs Fully Removed in 2.0 + +**Action Items**: + +- [ ] Replace `vitest typecheck` command usages with `vitest --typecheck`. +- [ ] Remove `VITEST_JUNIT_CLASSNAME` and `VITEST_JUNIT_SUITE_NAME` env vars; move equivalent values into JUnit reporter options. +- [ ] If you import `SnapshotEnvironment`, change the import path from `vitest` to `vitest/snapshot`. +- [ ] Replace any `SpyInstance` type imports with `MockInstance`. +- [ ] If you still configure `c8` as a coverage provider, switch to `v8` (`@vitest/coverage-v8`). + +--- + +## Vitest 2.x → 3.0 Breaking Changes + +### 2.1 Test Options Argument Position + +**Search Pattern**: `test('name', () => {...}, { ... })`, `describe('name', () => {...}, { ... })` + +**What Changed**: Test/describe options objects must now be passed as the **second** argument, not the third. + +```typescript +// ❌ BEFORE (Vitest 2.x) +test( + 'validation works', + () => { + /* ... */ + }, + { retry: 3 } +); + +// ✅ AFTER (Vitest 3.0) +test('validation works', { retry: 3 }, () => { + /* ... */ +}); +``` + +A numeric timeout value as the third argument is still accepted (`test('name', () => {}, 1000)`). + +**Action Items**: + +- [ ] Move every options-object argument from third to second position in `test`, `it`, `describe`, and their variants (`.skip`, `.only`, `.each`, etc.). + +### 2.2 Browser Configuration: `browser.instances` + +**Search Pattern**: `browser.name`, `browser.providerOptions` in `vitest.config.*` + +**What Changed**: `browser.name` and `browser.providerOptions` are **deprecated in v3** (still work, emit warnings) and **removed in v4**. Use `browser.instances` instead. Migrating now silences the v3 warnings and is required before reaching v4. + +```typescript +// ❌ BEFORE (Vitest 2.x) +export default defineConfig({ + test: { + browser: { + name: 'chromium', + providerOptions: { + launch: { devtools: true }, + }, + }, + }, +}); + +// ✅ AFTER (Vitest 3.0) +export default defineConfig({ + test: { + browser: { + instances: [ + { + browser: 'chromium', + launch: { devtools: true }, + }, + ], + }, + }, +}); +``` + +**Action Items**: + +- [ ] Collapse `browser.name` + `browser.providerOptions` into a single entry in `browser.instances`. +- [ ] Remove `browser.name` and `browser.providerOptions`. + +### 2.3 `mockReset()` Restores Original Implementation + +**Search Pattern**: `.mockReset()`, `mockReset: true` in `vitest.config.*` + +**What Changed**: `spy.mockReset()` now restores the original implementation rather than replacing it with a noop returning `undefined`. + +```typescript +// Behavior change illustration: +const foo = { bar: () => 'Hello, world!' }; +vi.spyOn(foo, 'bar').mockImplementation(() => 'Hello, mock!'); +foo.bar(); // 'Hello, mock!' + +foo.bar.mockReset(); +foo.bar(); // BEFORE: undefined → AFTER: 'Hello, world!' +``` + +**Action Items**: + +- [ ] Audit tests that rely on the post-reset behavior returning `undefined`; explicitly mock to a noop if needed (`vi.spyOn(foo, 'bar').mockReturnValue(undefined)`). +- [ ] If you set `mockReset: true` globally, expect spied methods to return their original implementation between tests. + +### 2.4 `vi.spyOn()` Reuses Existing Mocks + +**Search Pattern**: Repeated `vi.spyOn(obj, 'method')` on the same target + +**What Changed**: Calling `vi.spyOn()` on an already-mocked method now returns the existing mock rather than creating a new one. After `vi.restoreAllMocks()`, the method is no longer a mock. + +```typescript +vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar'); +vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar'); +vi.restoreAllMocks(); +vi.isMockFunction(fooService.foo); +// BEFORE: true (the second spy survived restore) +// AFTER: false (both calls referenced the same spy) +``` + +**Action Items**: + +- [ ] Audit tests that double-spied on the same method expecting two independent mocks. +- [ ] Audit tests that asserted a method remained mocked after `restoreAllMocks` — they will now see the original. + +### 2.5 Fake Timers Mock Everything by Default + +**Search Pattern**: `vi.useFakeTimers()` usages and `fakeTimers.toFake` config + +**What Changed**: Vitest now mocks **all** timer-related APIs by default (the previously-restricted built-in subset is gone). This includes `performance.now()`. Only `nextTick` is left unmocked. To restore the prior, narrower subset, configure `fakeTimers.toFake` explicitly. + +```typescript +// To restore the prior subset: +export default defineConfig({ + test: { + fakeTimers: { + toFake: [ + 'setTimeout', + 'clearTimeout', + 'setInterval', + 'clearInterval', + 'setImmediate', + 'clearImmediate', + 'Date', + ], + }, + }, +}); +``` + +**Action Items**: + +- [ ] Audit tests using `vi.useFakeTimers()` that observe `performance.now()` or other newly-faked APIs. +- [ ] Restrict `fakeTimers.toFake` if a test should leave specific timer APIs real. + +### 2.6 Stricter Error Equality + +**Search Pattern**: `toEqual(new Error(...))`, `toThrowError(new Error(...))` + +**What Changed**: Error comparisons via `toEqual()` and `toThrowError()` now check `name`, `message`, `cause`, `AggregateError.errors`, and prototype. + +```typescript +// Cause is checked asymmetrically: +expect(new Error('hi', { cause: 'x' })).toEqual(new Error('hi')); // ✅ passes +expect(new Error('hi')).toEqual(new Error('hi', { cause: 'x' })); // ❌ fails + +// Prototype is checked: +expect(() => { + throw new TypeError('type error'); +}) + .toThrowError(new Error('type error')) // ❌ fails (Error vs TypeError) + .toThrowError(new TypeError('type error')); // ✅ passes +``` + +**Action Items**: + +- [ ] Update assertions that pass a base `Error` to match a subclassed throw — use the matching subclass. +- [ ] Update assertions whose expected error has a `cause`/`name` that the actual error lacks. + +### 2.7 Vite 6 + Vitest 3: `module` Condition Excluded from `resolve.conditions` + +**Applies when**: Workspace is on Vite 6 (and Vitest 3 with Vite 6). + +**What Changed**: `module` is excluded from `resolve.conditions` by default to align with the upstream Vite 6 migration. + +**Action Items**: + +- [ ] If a dependency requires the `module` condition for tests, add it explicitly to `resolve.conditions` in your Vitest config. + +### 2.8 Hook Signature: `onTestFinished` / `onTestFailed` Receive Context + +**Search Pattern**: `onTestFinished`, `onTestFailed` usages in custom reporters or test utilities + +**What Changed**: These hooks now receive a test context as the first argument, matching `beforeEach`/`afterEach`. + +**Action Items**: + +- [ ] Update `onTestFinished`/`onTestFailed` callbacks to take a context argument and access the previous "result" through it. + +### 2.9 `Custom` Type Deprecated; `WorkspaceSpec` Removed + +**Search Pattern**: `import { Custom, WorkspaceSpec } from 'vitest'` + +**What Changed**: `Custom` is now an alias for `Test`; prefer `RunnerCustomCase` and `RunnerTestCase`. `WorkspaceSpec` is removed — use `TestSpecification` instead. Tasks created via `getCurrentSuite().custom()` now have `type: 'test'`. + +**Action Items**: + +- [ ] Replace `import { Custom } from 'vitest'`: most usages should become `RunnerTestCase` (the regular test case type — `Custom` was an alias for `Test`). Only use `RunnerCustomCase` if the workspace explicitly creates tasks via `getCurrentSuite().custom()`. +- [ ] Replace `WorkspaceSpec` references with `TestSpecification`. + +### 2.10 `resolveConfig()` API Shape Changed + +**Search Pattern**: `import { resolveConfig } from 'vitest/node'` (or similar) used by custom tooling + +**What Changed**: `resolveConfig()` now takes user configuration and returns a resolved configuration, rather than taking an already-resolved Vite config. + +**Action Items**: + +- [ ] If you call `resolveConfig()`, pass user config (not already-resolved Vite config) and consume the resolved return value. + +### 2.11 `vitest/reporters` Export Slimmed Down + +**Search Pattern**: Type imports from `vitest/reporters` + +**What Changed**: `vitest/reporters` now exports only reporter implementations and their option types. Task types (`TestCase`, `TestSuite`, etc.) moved. + +**Action Items**: + +- [ ] Move task-type imports (`TestCase`, `TestSuite`, related) from `vitest/reporters` to `vitest/node`. + +### 2.12 Test Files Always Excluded from Coverage + +**Search Pattern**: `coverage.excludes` in `vitest.config.*` attempting to include test files + +**What Changed**: Test files are always excluded from coverage, even if `coverage.excludes` is customized. + +**Action Items**: + +- [ ] Remove any `coverage.excludes` patterns that were trying to surface test files in coverage — they no longer apply. + +### 2.13 Snapshot Internal API Restructured + +**Applies when**: A workspace consumes `@vitest/snapshot` directly (custom snapshot tooling). + +**What Changed**: The public Snapshot API in `@vitest/snapshot` was restructured to support multiple states within a single run. The `.toMatchSnapshot()` matcher API is unchanged. + +**Action Items**: + +- [ ] Most workspaces have nothing to do here. If you maintain custom snapshot tooling against `@vitest/snapshot`, review your usage against the v3 API. + +### 2.14 Workspace → Projects (v3.2 option rename, v4 file removal) + +**Applies when**: Workspace has `vitest.workspace.{ts,js,mjs}` files, uses `defineWorkspace`, or has `test.workspace` set in `vitest.config.*`. + +**What Changed**: + +- **Vitest 3.2**: introduced `test.projects` as the config option, with `test.workspace` deprecated in favor of it (the option emits a deprecation warning). +- **Vitest 4**: the external file form (`vitest.workspace.*` + `defineWorkspace`) is **removed entirely**. Projects must be inlined in the root `vitest.config.*`. + +Migrating now (while still on v3) clears the v3.2 deprecation warning and is required before reaching v4. + +```typescript +// ❌ DEPRECATED (Vitest 3.2+) +// vitest.workspace.ts +import { defineWorkspace } from 'vitest/config'; +export default defineWorkspace(['apps/*', 'libs/*']); + +// ✅ AFTER (inline in root vitest.config.ts) +import { defineConfig } from 'vitest/config'; +export default defineConfig({ + test: { + projects: ['apps/*', 'libs/*'], + }, +}); +``` + +**Action Items**: + +- [ ] Move project list from `vitest.workspace.*` into `test.projects` in the root `vitest.config.*`. +- [ ] Delete the `vitest.workspace.*` file once migration is complete. +- [ ] If you import `defineWorkspace` anywhere, replace with `defineConfig` and the inlined shape above. +- [ ] If you continue to Vitest 4, this step is required, not optional. + +--- + +## Post-Migration Verification + +1. Reinstall: `pnpm install` (or `npm install` / `yarn install`) +2. Run affected tests: `nx affected -t test` +3. Re-baseline coverage on key projects: `nx run :test --coverage` +4. Typecheck custom reporters / sequencers / snapshot tooling: `nx affected -t typecheck` +5. Watch mode (if applicable): verify `server.watch.ignored` covers what `watchExclude` previously did. + +Confirm: + +- All configuration files updated +- All test files pass (or are flagged in your handoff `summary` if they remain failing — see `` below) +- Coverage reports generate correctly +- No deprecated API warnings in console + + + +- Do NOT force tests to pass by replacing test logic with `expect(true).toBe(true)`. +- Do NOT remove assertions to silence a failure. +- Do NOT add mocks that exist solely to make a failing test pass. + +If a test cannot be made to pass within the scope of this migration, leave it failing and report it in your handoff `summary`. + + + +## References + +- Vitest 2.0 migration guide: https://v2.vitest.dev/guide/migration +- Vitest 3.0 migration guide: https://v3.vitest.dev/guide/migration +- Vitest 4.0 migration guide (for cascading bumps): https://vitest.dev/guide/migration +- Vitest releases: https://github.com/vitest-dev/vitest/releases diff --git a/tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-4.md b/tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-4.md new file mode 100644 index 00000000..086f0bec --- /dev/null +++ b/tools/ai-migrations/@nx/vite/23.1.1/ai-instructions-for-vitest-4.md @@ -0,0 +1,838 @@ +# Vitest 4.0 Migration Instructions for LLM + +## Overview + +These instructions guide you through migrating an Nx workspace containing multiple Vitest projects from Vitest 3.x to Vitest 4.0. Work systematically through each breaking change category. + + + +The pre-pass handled, mechanically: + +- dead `coverage.{all,extensions,ignoreEmptyLines,experimentalAstAwareRemapping}` removal +- `test.workspace` → `test.projects` rename (the property form) +- inlining of static `vitest.workspace.*` files (a plain array of glob strings) into the root `vitest.config.*` under `test.projects`, deleting the workspace file. Workspace files the pre-pass could not safely inline are forwarded to you: dynamic content (imports, spreads, object entries), an empty project list, or a sibling config it could not merge into (an existing `test.projects`/`test.workspace`, a non-object-literal config, or a directory that only has a `vite.config.*`) +- when the inlined globs matched both a `vite.config.*` and a `vitest.config.*` in the same directory resolving to the same project name, a negative glob excluding the `vite.config.*` was appended (avoids vitest's duplicate-project-name startup error). When either config's project name could not be statically determined, an advisory was forwarded to you instead +- minimal local `vitest.config.*` files for packages that run vitest via a package.json script but had no local config. Without one, Vitest 4 climbs from the package directory to the root `test.projects` config, resolves the globs relative to the package directory, and fails with "No projects were found" +- `@vitest/browser/context` import-path rewrite to `vitest/browser` +- `deps.optimizer.web` → `deps.optimizer.client` rename +- `poolOptions.threads.useAtomics` and top-level `test.minWorkers` removal +- `'verbose'` → `'tree'` and `'basic'` → `['default', { summary: false }]` inside `test.reporters` +- `VITEST_MAX_{THREADS,FORKS}` → `VITEST_MAX_WORKERS` and `VITE_NODE_DEPS_MODULE_DIRECTORIES` → `VITEST_MODULE_DIRECTORIES` renames in: `package.json` scripts, `.env` / `.env.*` files, `project.json` `options.env` keys, and inline `VAR=value` prefixes inside `project.json` `options.{args,command,commands}` + +The pre-pass **skips the rename when both `VITEST_MAX_THREADS` and `VITEST_MAX_FORKS` appear in the same file/scope** (they collapse to a single `VITEST_MAX_WORKERS` whose value depends on which pool the project uses — a decision the pre-pass can't make safely). It also **does not** edit CI provider configs (`.github/workflows/*.yml`, `.gitlab-ci.yml`, `azure-pipelines.yml`, `.circleci/config.yml`, `bitbucket-pipelines.yml`) — YAML structure varies too much. Any conflicts and any CI matches are forwarded to you in ``. + +**The cross-cutting changes below still require your attention** — pool option flattening (`singleThread`/`singleFork`, `maxThreads`/`maxForks`, `poolOptions..{execArgv,isolate}`, `poolOptions.vmThreads.memoryLimit`), `test.deps.{external,inline,fallbackCJS}` → `test.server.deps.*` move, `test.{poolMatchGlobs,environmentMatchGlobs}` projects rewrite, browser provider function-form rewrite, `browser.testerScripts` → `testerHtmlPath`, inlining of `vitest.workspace.*` files the pre-pass could not inline automatically (+ `defineWorkspace` removal), custom reporter callback API updates, and `@vitest/browser` package replacement with per-provider packages. + +How to read the wrapper sections above this file: + +- `` lists files the pre-pass already wrote to. Verify the new shape is in place; do not re-apply the same edit. +- `` lists detections the pre-pass forwarded because it could not safely complete them. **Every entry is pending work** — address each one in the relevant section below, not as a separate task. + +A workspace-wide reminder is also emitted as a post-run "next step" about env vars set in CI provider dashboards — those can't be detected from the workspace tree. + + + + +In your handoff `summary` (1–3 sentences per the system prompt), name the breaking-change categories you applied; explicitly call out any you skipped because they didn't apply (e.g., "no custom reporters in this workspace", "no browser-mode configs"). + + + + +Vitest 4 has hard runtime requirements: + +- **Vite ≥ 6.0.0** (Vite 5 is unsupported). Check with `npx vite --version`. If on Vite 5, apply the Vite 6 / 7 / 8 migration guides first. +- **Node.js ≥ 20.0.0** (Node 18 support dropped). Check with `node --version`. Update CI `actions/setup-node` versions, `.nvmrc`, `engines` in `package.json`, and Docker base images. + +If either prerequisite is unmet, write status: failed with the unmet requirement in `summary` — config-level migration on an unsupported runtime will produce confusing errors. + + + +## Nx-Specific Notes (read first) + +- **Inferred plugin targets**: modern Nx workspaces use `@nx/vitest/plugin` (or historically `@nx/vite/plugin`) to _infer_ test targets from `vitest.config.*` presence. `project.json` may have no `test` target. Inspect inferred targets with `nx show project --json | jq .targets`. Renaming/moving `vitest.config.*` invalidates inference; run `nx reset` after structural moves. +- **`@nx/vitest:test` / `@nx/vite:test` executor options**: when present in `project.json`, the relevant options are `configFile`, `reportsDirectory`, `mode`, `testFiles`, `watch`. Most option-level breaking changes in v4 are inside `vitest.config.*`, not these. The exception: any `--segfault-retry` in the executor `args` is now invalid and must be removed. +- **Shared base config**: apply transforms to a workspace-root `vitest.config.base.ts` (extended via `mergeConfig`) BEFORE per-project overrides. Otherwise inherited options may shadow the migrated ones. +- **Angular projects using AnalogJS** (`@analogjs/vitest-angular` / `@analogjs/vite-plugin-angular`): the Analog packages are bumped automatically by Nx's `packageJsonUpdates` (to the `~2.2.x` line). Review `src/test-setup.ts` and Analog plugin invocations in per-project `vitest.config.ts`. + +## Pre-Migration Checklist + +1. **Identify all Vitest projects**: + + ```bash + nx show projects --with-target test + ``` + +2. **Locate all Vitest configuration files**: + - Search for `vitest.config.{ts,js,mjs}` + - Search for `vitest.workspace.{ts,js,mjs}` (removed in Vitest 4 — migrate to inline `test.projects`; see section 1.3 below) + - Check `project.json` files for `@nx/vitest:test` / `@nx/vite:test` executor options + - For workspaces relying on the inferred plugin (`@nx/vitest/plugin`), targets come from inference — inspect them with `nx show project --json | jq .targets` + +3. **Identify affected code**: + - Test files: `**/*.{spec,test}.{ts,js,tsx,jsx}` + - Mock usage: Files using `vi.fn()`, `vi.spyOn()`, `vi.mock()` + - Coverage configuration references + +## Migration Steps by Category + +### 1. Configuration File Updates + +#### 1.1 Coverage Configuration + +**Search Pattern**: `coverage` in all `vitest.config.*` files and `project.json` test target options + +**Changes Required**: + +```typescript +// ❌ BEFORE (Vitest 3.x) +export default defineConfig({ + test: { + coverage: { + all: true, + extensions: ['.ts', '.tsx'], + ignoreEmptyLines: false, + experimentalAstAwareRemapping: true, + }, + }, +}); + +// ✅ AFTER (Vitest 4.0) +export default defineConfig({ + test: { + coverage: { + // Explicitly define files to include in coverage + include: ['src/**/*.{ts,tsx}'], + // Remove: all, extensions, ignoreEmptyLines, experimentalAstAwareRemapping + }, + }, +}); +``` + +**Action Items**: + +- [ ] Remove `coverage.all` option +- [ ] Remove `coverage.extensions` option +- [ ] Remove `coverage.ignoreEmptyLines` option +- [ ] Remove `coverage.experimentalAstAwareRemapping` option +- [ ] Add explicit `coverage.include` patterns based on project structure +- [ ] Update any documentation referencing these options + +#### 1.2 Pool Options Restructuring + +**Search Pattern**: `poolOptions`, `maxThreads`, `maxForks`, `singleThread`, `singleFork` in all Vitest config files + +**Changes Required**: + +```typescript +// ❌ BEFORE (Vitest 3.x — pool serialized via singleThread/singleFork) +export default defineConfig({ + test: { + maxThreads: 4, + maxForks: 2, + singleFork: true, + poolOptions: { + forks: { + execArgv: ['--expose-gc'], + isolate: false, + }, + threads: { + useAtomics: true, + }, + vmThreads: { + memoryLimit: '512MB', + }, + }, + }, +}); + +// ✅ AFTER (Vitest 4.0 — top-level pool config) +export default defineConfig({ + test: { + maxWorkers: 1, // singleFork: true => maxWorkers: 1, isolate: false + isolate: false, + execArgv: ['--expose-gc'], // moved from poolOptions.forks + vmMemoryLimit: '512MB', // moved from poolOptions.vmThreads.memoryLimit + // Remove: poolOptions, threads.useAtomics, minWorkers (also removed in v4) + }, +}); +``` + +**Action Items**: + +- [ ] Replace `maxThreads` and `maxForks` with a single `maxWorkers` option. If both values were set with different numbers (one for threads pool, one for forks pool), pick the value matching the pool the project actually uses — `maxWorkers` is pool-agnostic in v4. +- [ ] Replace `singleThread: true` or `singleFork: true` with `maxWorkers: 1, isolate: false`. +- [ ] If `singleThread: false` or `singleFork: false` was set explicitly, just delete — it's the default. +- [ ] Move `poolOptions.{forks,threads}.execArgv` → top-level `execArgv`. +- [ ] Move `poolOptions.{forks,threads}.isolate` → top-level `isolate`. +- [ ] Move `poolOptions.vmThreads.memoryLimit` → top-level `vmMemoryLimit`. +- [ ] Remove `poolOptions.threads.useAtomics` (option removed). +- [ ] Remove `minWorkers` if present (option removed; behaves as if set to 0 in non-watch mode). +- [ ] Update CI environment variables: `VITEST_MAX_THREADS` and `VITEST_MAX_FORKS` → `VITEST_MAX_WORKERS`. + + +Both `maxThreads` and `maxForks` are set with different numbers AND the project has no explicit `test.pool` you can use to decide which value `maxWorkers` should take. Do not guess. Write status: failed and ask the user which pool the project uses. + + +#### 1.3 Workspace to Projects Rename + +**Search Pattern**: `workspace` property in Vitest config files + +**Changes Required**: + +```typescript +// ❌ BEFORE (Vitest 3.x) +export default defineConfig({ + test: { + workspace: ['apps/*', 'libs/*'], + }, +}); + +// ✅ AFTER (Vitest 4.0) +export default defineConfig({ + test: { + projects: ['apps/*', 'libs/*'], + }, +}); +``` + +**Action Items**: + +- [ ] Rename `workspace` property to `projects` in all config files. +- [ ] Inline any external `vitest.workspace.*` content into `test.projects` and delete the workspace file (external file references are no longer supported). The pre-pass already inlined workspace files containing a plain static array of glob strings; the ones you see in `` were skipped because of dynamic content or because the sibling config could not be merged into mechanically (existing `test.projects`/`test.workspace`, non-object-literal config, or a directory with only a `vite.config.*`). For an existing `test.projects`, merge the workspace entries into it; for a `vite.config.*`-only directory, decide whether its `test` block (if any) and the project list belong in that file or a new `vitest.config.*`. +- [ ] If projects need different pool/environment options, set them inside each project entry rather than via the (now-removed) `poolMatchGlobs` / `environmentMatchGlobs` — see section 1.5. + +When you inline a workspace file yourself, also apply these checks (the pre-pass applies them for the files it inlines): + +- [ ] **Config-less packages**: a root config with `test.projects` breaks `vitest` invocations from package directories that have no `vite.config.*`/`vitest.config.*` of their own (common for packages with a package.json `"test": "vitest run"` script). Vitest 4 finds the root config by walking up from the package directory, but resolves the `test.projects` globs relative to the package directory, so it errors with "No projects were found". For each such package, create a minimal local config so vitest stops at it and `@nx/vitest` infers the test target: + + ```typescript + import { defineConfig } from 'vitest/config'; + + export default defineConfig({ + test: {}, + }); + ``` + + Do NOT "fix" this by anchoring the root globs to the workspace root instead: that makes the package-directory invocation run every project in the workspace while still skipping the config-less package's own tests. Skip packages whose vitest script already passes an explicit `--config`/`-c`/`--root` (space- or `=`-joined); those don't rely on config discovery. + +- [ ] **Duplicate project names**: when the inlined globs match both a `vite.config.*` and a `vitest.config.*` in the same directory and both resolve to the same project name (the default name is the directory's package.json name, so two name-less configs always collide), vitest errors at startup with "Project name ... is not unique". Append a negative glob excluding the `vite.config.*` file (e.g. `'!packages/foo/vite.config.ts'`), matching vitest's own vitest-over-vite preference. Leave directories whose configs have distinct explicit `test.name` values alone; those are intentionally separate projects. + +- [ ] **Root config self-match**: when the inlined globs match the root config file itself (e.g. `**/vitest.config.*` matches the `vitest.config.*` you inlined into), vitest resolves that root config as an extra project. A pure aggregator has no `include` of its own, so this extra project re-runs every test via vitest's default glob without each project's `environment`/`setupFiles`, duplicating runs and failing tests that depend on their project config. Append a negative glob excluding the root config file (e.g. `'!vitest.config.ts'`, relative to its own directory) so vitest treats it only as the projects orchestrator. Skip this when the root config is itself a real project with its own `test.include`: it runs its own tests and needs no exclusion. + + +The `vitest.workspace.*` file imports modules / calls functions / uses spreads that you cannot evaluate at edit time (dynamic project arrays, conditional imports). Write status: failed listing the file path and the dynamic shape that needs human resolution. + + +#### 1.4 Browser Configuration + +**Search Pattern**: `browser.provider`, `browser.testerScripts`, imports from `@vitest/browser`, `@vitest/browser/context`, `@vitest/browser/utils` + +**Changes Required**: `browser.provider` is no longer a string. It's now the **return value of a provider function** imported from a per-provider package. Official packages: + +- `@vitest/browser-playwright` — exports `playwright(options?)` +- `@vitest/browser-webdriverio` — exports `webdriverio(options?)` +- `@vitest/browser-preview` — exports `preview(options?)` (dev-only) + +```typescript +// ❌ BEFORE (Vitest 3.x) +export default defineConfig({ + test: { + browser: { + enabled: true, + provider: 'playwright', // string + testerScripts: ['./setup.js'], // array of scripts + }, + }, +}); +import { page } from '@vitest/browser/context'; +import { getElementError } from '@vitest/browser/utils'; + +// ✅ AFTER (Vitest 4.0) +import { defineConfig } from 'vitest/config'; +import { playwright } from '@vitest/browser-playwright'; + +export default defineConfig({ + test: { + browser: { + enabled: true, + provider: playwright({ + launchOptions: { slowMo: 100 }, + }), + instances: [{ browser: 'chromium' }], + testerHtmlPath: './test-setup.html', // single HTML path replaces script array + }, + }, +}); +import { page, utils } from 'vitest/browser'; +const { getElementError } = utils; +``` + +**Action Items**: + +- [ ] Install the appropriate provider package: `@vitest/browser-playwright`, `@vitest/browser-webdriverio`, or `@vitest/browser-preview`. Match whatever your `browser.provider` string was previously. +- [ ] Remove `@vitest/browser` from `dependencies`/`devDependencies` — its public surface moved into the main `vitest` package and the per-provider packages. +- [ ] Replace string `browser.provider: 'name'` with the function-call form: `provider: ()`. +- [ ] Replace `browser.testerScripts: [...]` with `browser.testerHtmlPath: '.html'`. Note: this is a **semantic** change (array of scripts → one HTML file). Move the script contents into a `