Skip to content

Commit 3240c57

Browse files
committed
fix(migrate): detect actual Vitest source usage
1 parent a75c01b commit 3240c57

12 files changed

Lines changed: 319 additions & 3 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "example",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"test": "vitest run"
7+
},
8+
"devDependencies": {
9+
"vitest": "^4.1.11"
10+
}
11+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { expect, test } from 'vitest';
2+
3+
test.sequential('works', () => {
4+
expect(() => { throw new Error(''); }).toThrow('');
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default { test: { globals: true } };
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "plugin-workspace",
3+
"private": true,
4+
"type": "module",
5+
"packageManager": "pnpm@11.24.0",
6+
"devDependencies": {
7+
"vite": "^8.0.0"
8+
}
9+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
packages:
2+
- examples/*
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[[case]]
2+
name = "migrate_only_the_vitest_member"
3+
vp = "global"
4+
comment = "A Vitest package-name string in the root plugin must not block migration of its Vitest v4 example. Only the example receives versioned migration state."
5+
steps = [
6+
["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"],
7+
["vpt", "print-file", "src/index.ts", "examples/vite-8/unit.test.js", ".vite-plus/migrations.json"],
8+
{ argv = ["vp", "test", "run"], cwd = "examples/vite-8" },
9+
["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"],
10+
["vpt", "print-file", ".vite-plus/migrations.json"],
11+
]
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# migrate_only_the_vitest_member
2+
3+
A Vitest package-name string in the root plugin must not block migration of its Vitest v4 example. Only the example receives versioned migration state.
4+
5+
## `vp migrate --no-interactive --no-hooks --no-agent --no-editor`
6+
7+
```
8+
VITE+ - The Unified Toolchain for the Web
9+
10+
◇ Migrated . to Vite+ <version>
11+
• Node <version> pnpm <version>
12+
• 1 config update applied, 1 file had imports rewritten
13+
```
14+
15+
## `vpt print-file src/index.ts examples/vite-8/unit.test.js .vite-plus/migrations.json`
16+
17+
```
18+
// Tool names are not imports or evidence that this package runs Vitest.
19+
export const NON_RUNTIME_PKGS = ['vite', 'vitest'];
20+
import { expect, test } from 'vite-plus/test';
21+
22+
test('works', { concurrent: false }, () => {
23+
expect(() => { throw new Error(''); }).toThrow(/^$/);
24+
});
25+
{
26+
"version": 1,
27+
"vitest5": {
28+
"examples/vite-8": {
29+
"sourceVersion": "4.1.11",
30+
"configless": false
31+
}
32+
}
33+
}
34+
```
35+
36+
## `cd examples/vite-8 && vp test run`
37+
38+
```
39+
VITE+ - The Unified Toolchain for the Web
40+
41+
note: You are running `vp test` as a Vite+ built-in command. If you meant to run the test npm script, use `vpr test` instead.
42+
43+
RUN <version> <workspace>/examples/vite-8
44+
45+
✓ unit.test.js (1 test) <duration>
46+
✓ works <duration>
47+
48+
Test Files 1 passed (1)
49+
Tests 1 passed (1)
50+
Start at <time>
51+
Duration <duration> (<timing>)
52+
```
53+
54+
## `vp migrate --no-interactive --no-hooks --no-agent --no-editor`
55+
56+
```
57+
VITE+ - The Unified Toolchain for the Web
58+
59+
This project is already using Vite+! Happy coding!
60+
```
61+
62+
## `vpt print-file .vite-plus/migrations.json`
63+
64+
```
65+
{
66+
"version": 1,
67+
"vitest5": {
68+
"examples/vite-8": {
69+
"sourceVersion": "4.1.11",
70+
"configless": false
71+
}
72+
}
73+
}
74+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Tool names are not imports or evidence that this package runs Vitest.
2+
export const NON_RUNTIME_PKGS = ['vite', 'vitest'];

packages/cli/src/migration/__tests__/vitest-v5.spec.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,6 +1680,108 @@ export default defineConfig(CONFIG);`,
16801680
expect(planProject(root).projects[0].active).toBe(false);
16811681
});
16821682

1683+
it.each([
1684+
"export const tools = ['vite', 'vitest', '@vitest/runner', 'vite-plus/test'];",
1685+
"export const tool = { name: 'vitest' };",
1686+
"// import { test } from 'vitest';\nexport const value = 1;",
1687+
"/* export * from 'vitest'; vitest run */\nexport const value = 1;",
1688+
'export const example = "import { test } from \'vitest\';";',
1689+
'export const help = `Run vitest run or vp test`;',
1690+
"console.log('vitest run');",
1691+
"const execSync = (command) => command; execSync('vitest run');",
1692+
"const { execSync } = require('./logger'); execSync('vitest run');",
1693+
'const docs = `\n/// <reference types="vitest/globals" />\n`;',
1694+
'/*\n/// <reference types="vitest/globals" />\n*/',
1695+
"function require(name) { return name; } require('vitest');",
1696+
"const loader = { require(name) { return name; } }; loader.require('vitest');",
1697+
"import { expect } from 'vitest-like';",
1698+
"export * from 'vite-plus/testing';",
1699+
])('does not require a runner version for incidental source text: %s', (input) => {
1700+
const root = project({ 'package.json': '{}', 'src/plugin.ts': input });
1701+
const plan = planProject(root);
1702+
expect(plan.projects[0].active).toBe(false);
1703+
expect(plan.findings).toEqual([]);
1704+
expect(plan.changes).toEqual([]);
1705+
});
1706+
1707+
it.each([
1708+
"import { test } from 'vitest';",
1709+
"import 'vitest';",
1710+
"import type { TestAPI } from 'vitest';",
1711+
"export { test } from 'vite-plus/test';",
1712+
"export * from '@vitest/runner';",
1713+
"const runner = await import('vitest/node');",
1714+
"const runner = require('vitest');",
1715+
"import runner = require('vitest');",
1716+
"type Runner = import('vitest/node').Vitest;",
1717+
"declare module 'vitest' { interface ProvidedContext { port: number } }",
1718+
'/// <reference types="vitest/globals" />',
1719+
"import { execSync as run } from 'node:child_process'; run('pnpm exec vitest run');",
1720+
"import * as cp from 'child_process'; cp.spawn('vitest', ['run']);",
1721+
"import cp from 'node:child_process'; cp.execSync('vp test');",
1722+
"import { spawnSync } from 'node:child_process'; spawnSync('pnpm', ['exec', 'vitest', 'run']);",
1723+
"import { execFileSync } from 'child_process'; execFileSync('vp', ['test']);",
1724+
"const { execSync: run } = require('node:child_process'); run('vitest run');",
1725+
"const cp = require('child_process'); cp.spawn('vitest', ['run']);",
1726+
"require('node:child_process').execSync('vp test');",
1727+
])('still requires a runner version for actual source usage: %s', (input) => {
1728+
const root = project({ 'package.json': '{}', 'runner.ts': input });
1729+
const plan = planProject(root);
1730+
expect(plan.projects[0].active).toBe(true);
1731+
expect(plan.findings).toContainEqual(
1732+
expect.objectContaining({ code: 'source-version', severity: 'block' }),
1733+
);
1734+
expect(plan.changes).toEqual([]);
1735+
});
1736+
1737+
it('keeps the version check when source usage cannot be parsed', () => {
1738+
const root = project({
1739+
'package.json': '{}',
1740+
'runner.ts': "import { test } from 'vitest'; const broken = ;",
1741+
});
1742+
const plan = planProject(root);
1743+
expect(plan.projects[0].active).toBe(true);
1744+
expect(plan.findings).toContainEqual(
1745+
expect.objectContaining({ code: 'source-version', severity: 'block' }),
1746+
);
1747+
});
1748+
1749+
it('migrates Vitest workspace members without activating their plugin package', () => {
1750+
const plugin = "export const NON_RUNTIME_PKGS = ['vite', 'vitest'];";
1751+
const root = project({
1752+
'package.json': JSON.stringify({
1753+
name: 'plugin',
1754+
scripts: { test: 'node test-examples.js' },
1755+
}),
1756+
'src/index.ts': plugin,
1757+
'examples/vite-8/package.json': JSON.stringify({ devDependencies: { vitest: '^4.1.11' } }),
1758+
'examples/vite-8/vitest.config.ts': 'export default { test: { globals: true } };',
1759+
'examples/vite-8/unit.test.js': "test.sequential('works', () => {});",
1760+
});
1761+
const workspace = {
1762+
rootDir: root,
1763+
packageManager: PackageManager.pnpm,
1764+
packages: [{ name: 'example', path: 'examples/vite-8' }],
1765+
};
1766+
const plan = planVitestV5Migration(workspace);
1767+
expect(plan.projects.map(({ active, sourceVersion }) => ({ active, sourceVersion }))).toEqual([
1768+
{ active: false, sourceVersion: undefined },
1769+
{ active: true, sourceVersion: '4.1.11' },
1770+
]);
1771+
expect(plan.findings).toEqual([]);
1772+
applyVitestV5Migration(plan);
1773+
expect(finishVitestV5Migration(plan)).toEqual([]);
1774+
expect(fs.readFileSync(path.join(root, 'src/index.ts'), 'utf8')).toBe(plugin);
1775+
expect(fs.readFileSync(path.join(root, 'examples/vite-8/unit.test.js'), 'utf8')).toContain(
1776+
"test('works', { concurrent: false }",
1777+
);
1778+
const repeated = planVitestV5Migration(workspace);
1779+
expect(repeated.projects[0].active).toBe(false);
1780+
expect(repeated.findings).toEqual([]);
1781+
expect(repeated.changes).toEqual([]);
1782+
expect(Object.keys(repeated.state.vitest5!)).toEqual(['examples/vite-8']);
1783+
});
1784+
16831785
it.each([
16841786
['vitest', '4.1.11', '4.1.11', {}],
16851787
[

packages/cli/src/migration/migrator/vitest-v5.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
resolveVitestV5TestModes,
2323
type VitestV5TestMode,
2424
} from '../vitest-v5/scopes.ts';
25-
import { migrateVitestV5Source } from '../vitest-v5/source.ts';
25+
import { hasVitestV5SourceUsage, migrateVitestV5Source } from '../vitest-v5/source.ts';
2626

2727
const STATE_PATH = '.vite-plus/migrations.json';
2828
const SKIP_DIRS = new Set([
@@ -774,7 +774,7 @@ export function planVitestV5Migration(
774774
) ||
775775
[...sources].some(
776776
([file, source]) =>
777-
(CODE_FILE.test(file) && VITEST_SIGNAL.test(source)) ||
777+
(CODE_FILE.test(file) && hasVitestV5SourceUsage(file, source)) ||
778778
(configFiles.has(file) && /\btest\s*:/.test(source)),
779779
));
780780
const options: SourceOptions = {

0 commit comments

Comments
 (0)