From 4ef22453d8b5af6af486666ceba9b746b8394a2e Mon Sep 17 00:00:00 2001 From: MK Date: Sat, 28 Mar 2026 13:20:10 +0800 Subject: [PATCH 1/4] fix(test): rewrite vitest self-references in globals.d.ts for type-aware linting The bundled globals.d.ts declares types like `typeof import('vitest')['test']`, but `vitest` is not resolvable from the @voidzero-dev/vite-plus-test package context in pnpm's strict node_modules layout. TypeScript silently treats the unresolved import as `any`, but oxlint's type-aware linting treats it as an `error` type, causing `no-unsafe-call` errors. Fix by rewriting `import('vitest')` to `import('@voidzero-dev/vite-plus-test')` during the bundleVitest() copy step, making it a self-reference that resolves correctly via Node.js package self-referencing. Also adds the vite-plus-vitest-global-type-minimal-repro project to ecosystem-ci. --- .github/workflows/e2e-test.yml | 5 +++++ ecosystem-ci/repo.json | 6 ++++++ packages/test/BUNDLING.md | 3 +++ packages/test/build.ts | 6 +++++- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 28b1fdbe6d..e80ef8b0ee 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -294,6 +294,11 @@ jobs: command: | vp fmt vp run validate + - name: vite-plus-vitest-global-type-minimal-repro + node-version: 24 + command: | + vp test + vp check --fix exclude: # frm-stack uses Docker (testcontainers) which doesn't work the same way on Windows - os: windows-latest diff --git a/ecosystem-ci/repo.json b/ecosystem-ci/repo.json index ba94fcd98d..63fb7b3f26 100644 --- a/ecosystem-ci/repo.json +++ b/ecosystem-ci/repo.json @@ -102,5 +102,11 @@ "repository": "https://github.com/why-reproductions-are-required/bun-vite-template.git", "branch": "master", "hash": "d84099b4a153c7e0f35e510725b2ceb24c6e09ad" + }, + "vite-plus-vitest-global-type-minimal-repro": { + "repository": "https://github.com/why-reproductions-are-required/vite-plus-vitest-global-type-minimal-repro.git", + "branch": "main", + "hash": "419653665e4f0688ad3cac68a34673fdd0632b55", + "forceFreshMigration": true } } diff --git a/packages/test/BUNDLING.md b/packages/test/BUNDLING.md index 2ab49f9541..64b9b67ae1 100644 --- a/packages/test/BUNDLING.md +++ b/packages/test/BUNDLING.md @@ -113,9 +113,12 @@ For maintainers developing the vitest/vite migration feature, here are the trans | `from "@vitest/browser-preview"` | `from "@voidzero-dev/vite-plus-test/browser-preview"` | | `from "vite"` | `from "@voidzero-dev/vite-plus-core"` | | `from "vite/module-runner"` | `from "@voidzero-dev/vite-plus-core/module-runner"` | +| `import('vitest')` | `import('@voidzero-dev/vite-plus-test')` | **Note**: `@voidzero-dev/vite-plus-core` is the bundled version of upstream vite (Vite v8 beta). See [Core Package Bundling](../core/BUNDLING.md) for details on what it contains. +**Note**: The `import('vitest')` → `import('@voidzero-dev/vite-plus-test')` rewrite is critical for `globals.d.ts`, which declares global types like `typeof import('vitest')['test']`. Without this rewrite, `vitest` is not resolvable from the `@voidzero-dev/vite-plus-test` package context in pnpm's strict `node_modules` layout. TypeScript silently treats unresolved dynamic type imports as `any`, but oxlint's type-aware linting treats them as `error` types, causing `no-unsafe-call` errors. The rewrite turns this into a self-reference that resolves correctly via Node.js package self-referencing. + **Note:** When using pnpm overrides, you have three options for browser provider imports: - `vitest/browser-playwright` (or `vitest/browser-webdriverio`, `vitest/browser-preview`) - works when `vitest` is overridden to our package (Recommended) diff --git a/packages/test/build.ts b/packages/test/build.ts index f96cd14f33..b78d5a63a1 100644 --- a/packages/test/build.ts +++ b/packages/test/build.ts @@ -65,6 +65,7 @@ const distDir = resolve(projectDir, 'dist'); const vendorDir = resolve(distDir, 'vendor'); const CORE_PACKAGE_NAME = '@voidzero-dev/vite-plus-core'; +const TEST_PACKAGE_NAME = '@voidzero-dev/vite-plus-test'; // @vitest/* packages to copy (not bundle) to preserve browser/Node.js separation // These are copied from node_modules to dist/@vitest/ to avoid shared chunks @@ -480,7 +481,10 @@ async function bundleVitest() { .replaceAll(/require\("vite"\)/g, `require("${CORE_PACKAGE_NAME}")`) .replaceAll(`import 'vite';`, `import '${CORE_PACKAGE_NAME}';`) .replaceAll(`'vite/module-runner'`, `'${CORE_PACKAGE_NAME}/module-runner'`) - .replaceAll(`declare module "vite"`, `declare module "${CORE_PACKAGE_NAME}"`); + .replaceAll(`declare module "vite"`, `declare module "${CORE_PACKAGE_NAME}"`) + // Rewrite vitest self-references in .d.ts files (e.g., globals.d.ts uses + // `typeof import('vitest')['test']` which must resolve via self-reference) + .replaceAll(/import\(['"]vitest['"]\)/g, `import('${TEST_PACKAGE_NAME}')`); console.log(`Replaced vite imports in ${destPath}`); await writeFile(destPath, content, 'utf-8'); } else { From 717f2a0810c1b55bf40452587a04649792548f7d Mon Sep 17 00:00:00 2001 From: MK Date: Sat, 28 Mar 2026 14:37:51 +0800 Subject: [PATCH 2/4] fix(migrate): override vite-plus version for standalone projects in force-migrate mode In `rewriteStandaloneProject`, when `VITE_PLUS_FORCE_MIGRATE=1`: - The pnpm overrides didn't include `vite-plus` itself (only vite, vitest, etc.) - The devDependency wasn't updated if `vite-plus` already existed This caused ecosystem-ci to install the published `vite-plus@0.1.14` instead of the local `vite-plus@0.0.0` tgz, failing the `verify-install.ts` check. Closes #1177 --- packages/cli/src/migration/migrator.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/migration/migrator.ts b/packages/cli/src/migration/migrator.ts index 4f27411f22..2057aa5843 100644 --- a/packages/cli/src/migration/migrator.ts +++ b/packages/cli/src/migration/migrator.ts @@ -720,6 +720,7 @@ export function rewriteStandaloneProject( overrides: { ...pkg.pnpm?.overrides, ...VITE_PLUS_OVERRIDE_PACKAGES, + ...(isForceOverrideMode() ? { [VITE_PLUS_NAME]: VITE_PLUS_VERSION } : {}), }, }; // remove packages from `resolutions` field if they exist @@ -734,7 +735,7 @@ export function rewriteStandaloneProject( extractedStagedConfig = rewritePackageJson(pkg, packageManager, false, skipStagedMigration); // ensure vite-plus is in devDependencies - if (!pkg.devDependencies?.[VITE_PLUS_NAME]) { + if (!pkg.devDependencies?.[VITE_PLUS_NAME] || isForceOverrideMode()) { pkg.devDependencies = { ...pkg.devDependencies, [VITE_PLUS_NAME]: VITE_PLUS_VERSION, From 4fc04dc37a49d4ccc3c9923815ede21ad1ba31bd Mon Sep 17 00:00:00 2001 From: MK Date: Sat, 28 Mar 2026 14:39:13 +0800 Subject: [PATCH 3/4] test: add snap test for vitest globals with type-aware linting Ensures `vp check` passes when using vitest globals (`test.globals: true`) with type-aware linting and `typescript/no-unsafe-call` rule enabled. --- .../check-vitest-globals-typecheck/package.json | 5 +++++ .../check-vitest-globals-typecheck/snap.txt | 3 +++ .../src/index.test.ts | 5 +++++ .../check-vitest-globals-typecheck/src/index.ts | 3 +++ .../check-vitest-globals-typecheck/steps.json | 6 ++++++ .../check-vitest-globals-typecheck/tsconfig.json | 13 +++++++++++++ .../check-vitest-globals-typecheck/vite.config.ts | 14 ++++++++++++++ 7 files changed, 49 insertions(+) create mode 100644 packages/cli/snap-tests/check-vitest-globals-typecheck/package.json create mode 100644 packages/cli/snap-tests/check-vitest-globals-typecheck/snap.txt create mode 100644 packages/cli/snap-tests/check-vitest-globals-typecheck/src/index.test.ts create mode 100644 packages/cli/snap-tests/check-vitest-globals-typecheck/src/index.ts create mode 100644 packages/cli/snap-tests/check-vitest-globals-typecheck/steps.json create mode 100644 packages/cli/snap-tests/check-vitest-globals-typecheck/tsconfig.json create mode 100644 packages/cli/snap-tests/check-vitest-globals-typecheck/vite.config.ts diff --git a/packages/cli/snap-tests/check-vitest-globals-typecheck/package.json b/packages/cli/snap-tests/check-vitest-globals-typecheck/package.json new file mode 100644 index 0000000000..c992d4d0a5 --- /dev/null +++ b/packages/cli/snap-tests/check-vitest-globals-typecheck/package.json @@ -0,0 +1,5 @@ +{ + "name": "check-vitest-globals-typecheck", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/check-vitest-globals-typecheck/snap.txt b/packages/cli/snap-tests/check-vitest-globals-typecheck/snap.txt new file mode 100644 index 0000000000..e17c51b30c --- /dev/null +++ b/packages/cli/snap-tests/check-vitest-globals-typecheck/snap.txt @@ -0,0 +1,3 @@ +> vp check +pass: All 6 files are correctly formatted (ms, threads) +pass: Found no warnings, lint errors, or type errors in 3 files (ms, threads) diff --git a/packages/cli/snap-tests/check-vitest-globals-typecheck/src/index.test.ts b/packages/cli/snap-tests/check-vitest-globals-typecheck/src/index.test.ts new file mode 100644 index 0000000000..81faad4669 --- /dev/null +++ b/packages/cli/snap-tests/check-vitest-globals-typecheck/src/index.test.ts @@ -0,0 +1,5 @@ +import { fn } from "./index.ts"; + +test("fn", () => { + expect(fn()).toBe("Hello, world!"); +}); diff --git a/packages/cli/snap-tests/check-vitest-globals-typecheck/src/index.ts b/packages/cli/snap-tests/check-vitest-globals-typecheck/src/index.ts new file mode 100644 index 0000000000..b6b6588250 --- /dev/null +++ b/packages/cli/snap-tests/check-vitest-globals-typecheck/src/index.ts @@ -0,0 +1,3 @@ +export function fn() { + return "Hello, world!"; +} diff --git a/packages/cli/snap-tests/check-vitest-globals-typecheck/steps.json b/packages/cli/snap-tests/check-vitest-globals-typecheck/steps.json new file mode 100644 index 0000000000..d9c26d5a29 --- /dev/null +++ b/packages/cli/snap-tests/check-vitest-globals-typecheck/steps.json @@ -0,0 +1,6 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": ["vp check"] +} diff --git a/packages/cli/snap-tests/check-vitest-globals-typecheck/tsconfig.json b/packages/cli/snap-tests/check-vitest-globals-typecheck/tsconfig.json new file mode 100644 index 0000000000..c11910b88e --- /dev/null +++ b/packages/cli/snap-tests/check-vitest-globals-typecheck/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "types": ["vite-plus/test/globals"], + "strict": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/packages/cli/snap-tests/check-vitest-globals-typecheck/vite.config.ts b/packages/cli/snap-tests/check-vitest-globals-typecheck/vite.config.ts new file mode 100644 index 0000000000..227508e08e --- /dev/null +++ b/packages/cli/snap-tests/check-vitest-globals-typecheck/vite.config.ts @@ -0,0 +1,14 @@ +export default { + test: { + globals: true, + }, + lint: { + options: { + typeAware: true, + typeCheck: true, + }, + rules: { + "typescript/no-unsafe-call": "error", + }, + }, +}; From fd1b75e2ac170472017e3e601189fdfb3ed5f5d8 Mon Sep 17 00:00:00 2001 From: MK Date: Sat, 28 Mar 2026 14:44:00 +0800 Subject: [PATCH 4/4] chore: remove redundant comment in build.ts The vitest self-reference rewrite is already documented in BUNDLING.md. --- packages/test/build.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/test/build.ts b/packages/test/build.ts index b78d5a63a1..0f16824d38 100644 --- a/packages/test/build.ts +++ b/packages/test/build.ts @@ -482,8 +482,6 @@ async function bundleVitest() { .replaceAll(`import 'vite';`, `import '${CORE_PACKAGE_NAME}';`) .replaceAll(`'vite/module-runner'`, `'${CORE_PACKAGE_NAME}/module-runner'`) .replaceAll(`declare module "vite"`, `declare module "${CORE_PACKAGE_NAME}"`) - // Rewrite vitest self-references in .d.ts files (e.g., globals.d.ts uses - // `typeof import('vitest')['test']` which must resolve via self-reference) .replaceAll(/import\(['"]vitest['"]\)/g, `import('${TEST_PACKAGE_NAME}')`); console.log(`Replaced vite imports in ${destPath}`); await writeFile(destPath, content, 'utf-8');