Skip to content

Commit b58805b

Browse files
committed
fix(migrate): guard unsupported tsup configs
1 parent cc11ad6 commit b58805b

7 files changed

Lines changed: 320 additions & 145 deletions

File tree

crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_from_tsup/snapshots.toml

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,26 @@ steps = [
3535
]
3636

3737
[[case]]
38-
name = "migration_from_tsup_nested_runner"
38+
name = "migration_from_tsup_inline_config_collision"
39+
vp = "local"
40+
steps = [
41+
{ argv = ["vpt", "rm", "tsup.config.ts"], snapshot = false },
42+
{ argv = ["vpt", "json-edit", "package.json", "scripts.build", "tsup"], snapshot = false },
43+
{ argv = ["vpt", "json-edit", "package.json", "tsup", '{"entry":["src/index.ts"]}'], snapshot = false },
44+
{ argv = ["vpt", "json-edit", "package.json", "tsdown", '{"entry":["src/existing.ts"]}'], snapshot = false },
45+
{ argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "an existing inline tsdown config should stop automatic migration", continue-on-failure = true },
46+
{ argv = ["vpt", "print-file", "package.json"], comment = "both inline configs are unchanged" },
47+
]
48+
49+
[[case]]
50+
name = "migration_from_tsup_custom_config"
3951
vp = "local"
40-
env = { VP_CLI_BIN = "./tsdown-migrate-stub.mjs" }
4152
steps = [
42-
{ argv = ["vpt", "chmod", "+x", "tsdown-migrate-stub.mjs"], snapshot = false },
43-
{ argv = ["vpt", "json-edit", "package.json", "scripts.build", 'concurrently "tsup --watch --config=tsup.config.ts" "tsc --watch"'], snapshot = false },
44-
{ argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "a nested tsdown command should migrate to vp pack" },
45-
{ argv = ["vpt", "print-file", "package.json"], comment = "the quoted runner command uses vp pack" },
53+
{ argv = ["vpt", "mkdir", "-p", "configs"], snapshot = false },
54+
{ argv = ["vpt", "write-file", "configs/legacy.ts", "export default {};\n"], snapshot = false },
55+
{ argv = ["vpt", "json-edit", "package.json", "scripts.build", "tsup --config configs/legacy.ts"], snapshot = false },
56+
{ argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "a custom config should stop automatic migration before any files change", continue-on-failure = true },
57+
{ argv = ["vpt", "print-file", "tsup.config.ts"], comment = "the standard config is unchanged" },
58+
{ argv = ["vpt", "print-file", "configs/legacy.ts"], comment = "the custom config is unchanged" },
59+
{ argv = ["vpt", "print-file", "package.json"], comment = "the custom-config script and tsup dependency are unchanged" },
4660
]

crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_from_tsup/snapshots/migration_from_tsup_config_collision.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ VITE+ - The Unified Toolchain for the Web
1515
1616
tsup configuration detected. Auto-migrating to tsdown...
1717
18-
Automatic tsup migration was skipped because these tsdown config files already exist:
18+
Automatic tsup migration was skipped because these tsdown configs already exist:
1919
tsdown.config.ts
2020
2121
Choose one of these manual migration methods:
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# migration_from_tsup_custom_config
2+
3+
## `vpt mkdir -p configs`
4+
5+
6+
## `vpt write-file configs/legacy.ts 'export default {};
7+
'`
8+
9+
10+
## `vpt json-edit package.json scripts.build 'tsup --config configs/legacy.ts'`
11+
12+
13+
## `vp migrate --no-interactive --no-hooks --no-agent --no-editor`
14+
15+
a custom config should stop automatic migration before any files change
16+
17+
**Exit code:** 1
18+
19+
```
20+
VITE+ - The Unified Toolchain for the Web
21+
22+
tsup configuration detected. Auto-migrating to tsdown...
23+
24+
Automatic tsup migration was skipped because these scripts use configs that cannot be migrated automatically:
25+
package.json#build -> configs/legacy.ts
26+
27+
Choose one of these manual migration methods:
28+
1. Run `vp dlx tsdown-migrate` in the project root.
29+
2. Use the tsdown migration skill:
30+
https://github.com/rolldown/tsdown/blob/main/skills/tsdown-migrate/SKILL.md
31+
Complete the tsup migration manually, then re-run `vp migrate`.
32+
```
33+
34+
## `vpt print-file tsup.config.ts`
35+
36+
the standard config is unchanged
37+
38+
```
39+
import { defineConfig } from 'tsup';
40+
41+
export default defineConfig({
42+
entry: ['src/index.ts'],
43+
dts: true,
44+
format: ['esm', 'cjs'],
45+
splitting: false,
46+
});
47+
```
48+
49+
## `vpt print-file configs/legacy.ts`
50+
51+
the custom config is unchanged
52+
53+
```
54+
export default {};
55+
```
56+
57+
## `vpt print-file package.json`
58+
59+
the custom-config script and tsup dependency are unchanged
60+
61+
```
62+
{
63+
"devDependencies": {
64+
"tsup": "^8.5.0",
65+
"vite": "^7.0.0"
66+
},
67+
"name": "migration-from-tsup",
68+
"scripts": {
69+
"build": "tsup --config configs/legacy.ts"
70+
}
71+
}
72+
```
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# migration_from_tsup_inline_config_collision
2+
3+
## `vpt rm tsup.config.ts`
4+
5+
6+
## `vpt json-edit package.json scripts.build tsup`
7+
8+
9+
## `vpt json-edit package.json tsup '{"entry":["src/index.ts"]}'`
10+
11+
12+
## `vpt json-edit package.json tsdown '{"entry":["src/existing.ts"]}'`
13+
14+
15+
## `vp migrate --no-interactive --no-hooks --no-agent --no-editor`
16+
17+
an existing inline tsdown config should stop automatic migration
18+
19+
**Exit code:** 1
20+
21+
```
22+
VITE+ - The Unified Toolchain for the Web
23+
24+
tsup configuration detected. Auto-migrating to tsdown...
25+
26+
Automatic tsup migration was skipped because these tsdown configs already exist:
27+
package.json#tsdown
28+
29+
Choose one of these manual migration methods:
30+
1. Run `vp dlx tsdown-migrate` in the project root.
31+
2. Use the tsdown migration skill:
32+
https://github.com/rolldown/tsdown/blob/main/skills/tsdown-migrate/SKILL.md
33+
Complete the tsup migration manually, then re-run `vp migrate`.
34+
```
35+
36+
## `vpt print-file package.json`
37+
38+
both inline configs are unchanged
39+
40+
```
41+
{
42+
"devDependencies": {
43+
"tsup": "^8.5.0",
44+
"vite": "^7.0.0"
45+
},
46+
"name": "migration-from-tsup",
47+
"scripts": {
48+
"build": "tsup"
49+
},
50+
"tsdown": {
51+
"entry": [
52+
"src/existing.ts"
53+
]
54+
},
55+
"tsup": {
56+
"entry": [
57+
"src/index.ts"
58+
]
59+
}
60+
}
61+
```

crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_from_tsup/snapshots/migration_from_tsup_nested_runner.md

Lines changed: 0 additions & 49 deletions
This file was deleted.

packages/cli/src/migration/__tests__/tsup.spec.ts

Lines changed: 82 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,86 @@ describe('tsup migration', () => {
112112
expect(fs.readFileSync(tsupConfigPath, 'utf8')).toBe(originalTsupConfig);
113113
expect(fs.readFileSync(tsdownConfigPath, 'utf8')).toBe(originalTsdownConfig);
114114
expect(mockWarn).toHaveBeenCalledWith(
115-
'Automatic tsup migration was skipped because these tsdown config files already exist:\n tsdown.config.ts',
115+
'Automatic tsup migration was skipped because these tsdown configs already exist:\n tsdown.config.ts',
116+
);
117+
expect(mockInfo).toHaveBeenCalledWith(manualMigrationOptions());
118+
});
119+
120+
it('refuses to overwrite an inline tsdown config', async () => {
121+
fs.unlinkSync(path.join(projectPath, 'tsup.config.ts'));
122+
const packageJsonPath = path.join(projectPath, 'package.json');
123+
const originalPackageJson = {
124+
name: 'fixture',
125+
scripts: { build: 'tsup' },
126+
devDependencies: { tsup: '^8.5.0' },
127+
tsup: { entry: ['src/index.ts'] },
128+
tsdown: { entry: ['src/existing.ts'] },
129+
};
130+
fs.writeFileSync(packageJsonPath, `${JSON.stringify(originalPackageJson, null, 2)}\n`);
131+
132+
await expect(
133+
migrateTsupToTsdown(projectPath, false, PackageManager.npm, 'package.json#tsup', undefined, {
134+
silent: true,
135+
}),
136+
).resolves.toBe(false);
137+
138+
expect(mockRunCommandSilently).not.toHaveBeenCalled();
139+
expect(readJsonFile(packageJsonPath)).toEqual(originalPackageJson);
140+
expect(mockWarn).toHaveBeenCalledWith(
141+
'Automatic tsup migration was skipped because these tsdown configs already exist:\n package.json#tsdown',
142+
);
143+
expect(mockInfo).toHaveBeenCalledWith(manualMigrationOptions());
144+
});
145+
146+
it('refuses to migrate a script that uses a custom tsup config', async () => {
147+
const packageJsonPath = path.join(projectPath, 'package.json');
148+
const originalPackageJson = {
149+
name: 'fixture',
150+
scripts: { build: 'tsup --config configs/legacy.ts' },
151+
devDependencies: { tsup: '^8.5.0' },
152+
};
153+
fs.mkdirSync(path.join(projectPath, 'configs'));
154+
fs.writeFileSync(path.join(projectPath, 'configs/legacy.ts'), 'export default {};\n');
155+
fs.writeFileSync(packageJsonPath, `${JSON.stringify(originalPackageJson, null, 2)}\n`);
156+
157+
await expect(
158+
migrateTsupToTsdown(projectPath, false, PackageManager.npm, 'tsup.config.ts', undefined, {
159+
silent: true,
160+
}),
161+
).resolves.toBe(false);
162+
163+
expect(mockRunCommandSilently).not.toHaveBeenCalled();
164+
expect(readJsonFile(packageJsonPath)).toEqual(originalPackageJson);
165+
expect(fs.existsSync(path.join(projectPath, 'tsup.config.ts'))).toBe(true);
166+
expect(mockWarn).toHaveBeenCalledWith(
167+
'Automatic tsup migration was skipped because these scripts use configs that cannot be migrated automatically:\n package.json#build -> configs/legacy.ts',
168+
);
169+
expect(mockInfo).toHaveBeenCalledWith(manualMigrationOptions());
170+
});
171+
172+
it('refuses to remove selectors for multiple standard tsup configs', async () => {
173+
fs.writeFileSync(path.join(projectPath, 'tsup.config.js'), 'export default {};\n');
174+
const packageJsonPath = path.join(projectPath, 'package.json');
175+
const originalPackageJson = {
176+
name: 'fixture',
177+
scripts: {
178+
buildTs: 'tsup --config tsup.config.ts',
179+
buildJs: 'tsup --config tsup.config.js',
180+
},
181+
devDependencies: { tsup: '^8.5.0' },
182+
};
183+
fs.writeFileSync(packageJsonPath, `${JSON.stringify(originalPackageJson, null, 2)}\n`);
184+
185+
await expect(
186+
migrateTsupToTsdown(projectPath, false, PackageManager.npm, 'tsup.config.ts', undefined, {
187+
silent: true,
188+
}),
189+
).resolves.toBe(false);
190+
191+
expect(mockRunCommandSilently).not.toHaveBeenCalled();
192+
expect(readJsonFile(packageJsonPath)).toEqual(originalPackageJson);
193+
expect(mockWarn).toHaveBeenCalledWith(
194+
'Automatic tsup migration was skipped because these scripts use configs that cannot be migrated automatically:\n package.json#buildJs -> tsup.config.js',
116195
);
117196
expect(mockInfo).toHaveBeenCalledWith(manualMigrationOptions());
118197
});
@@ -446,6 +525,7 @@ describe('tsup migration', () => {
446525
build: 'tsup --config ./tsup.config.ts',
447526
watch: 'tsup --watch --config=tsup.config.ts',
448527
wrapped: 'cross-env NODE_ENV=test tsup -c "tsup.config.ts" --watch',
528+
quotedData: "echo 'tsdown'",
449529
},
450530
devDependencies: { tsup: '^8.5.0' },
451531
},
@@ -478,53 +558,7 @@ describe('tsup migration', () => {
478558
build: 'tsdown',
479559
watch: 'tsdown --watch',
480560
wrapped: 'cross-env NODE_ENV=test tsdown --watch',
481-
});
482-
});
483-
484-
it('rewrites tsdown commands nested in quoted runner arguments', async () => {
485-
fs.writeFileSync(
486-
path.join(projectPath, 'package.json'),
487-
`${JSON.stringify(
488-
{
489-
name: 'fixture',
490-
scripts: {
491-
build: 'concurrently "tsup --watch --config=tsup.config.ts" "tsc --watch"',
492-
wrapped: 'concurrently "pnpm exec tsup --watch" "tsc --watch"',
493-
singleQuoted: "concurrently 'tsup' 'tsc'",
494-
},
495-
devDependencies: { tsup: '^8.5.0' },
496-
},
497-
null,
498-
2,
499-
)}\n`,
500-
);
501-
mockRunCommandSilently.mockImplementation(async () => {
502-
const packageJsonPath = path.join(projectPath, 'package.json');
503-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
504-
for (const scriptName of Object.keys(packageJson.scripts)) {
505-
packageJson.scripts[scriptName] = packageJson.scripts[scriptName].replaceAll(
506-
'tsup',
507-
'tsdown',
508-
);
509-
}
510-
packageJson.devDependencies.tsdown = '0.22.14';
511-
delete packageJson.devDependencies.tsup;
512-
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
513-
fs.writeFileSync(path.join(projectPath, 'tsdown.config.ts'), 'export default {};\n');
514-
fs.unlinkSync(path.join(projectPath, 'tsup.config.ts'));
515-
return { exitCode: 0, stdout: Buffer.alloc(0), stderr: Buffer.alloc(0) };
516-
});
517-
518-
await expect(
519-
migrateTsupToTsdown(projectPath, false, PackageManager.pnpm, 'tsup.config.ts', undefined, {
520-
silent: true,
521-
}),
522-
).resolves.toBe(true);
523-
524-
expect(readJsonFile(path.join(projectPath, 'package.json')).scripts).toEqual({
525-
build: 'concurrently "vp pack --watch" "tsc --watch"',
526-
wrapped: 'concurrently "pnpm exec vp pack --watch" "tsc --watch"',
527-
singleQuoted: "concurrently 'vp pack' 'tsc'",
561+
quotedData: "echo 'tsdown'",
528562
});
529563
});
530564

0 commit comments

Comments
 (0)