diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_from_tsup_monorepo/snapshots/migration_from_tsup_monorepo_shared_root_config.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_from_tsup_monorepo/snapshots/migration_from_tsup_monorepo_shared_root_config.md index 739daf78ca..c8c4aec996 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_from_tsup_monorepo/snapshots/migration_from_tsup_monorepo_shared_root_config.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_from_tsup_monorepo/snapshots/migration_from_tsup_monorepo_shared_root_config.md @@ -43,8 +43,6 @@ Please manually merge tsdown.config.ts into vite.config.ts, see https://viteplus • tsup config migrated to tsdown (`vp pack`) ! Warnings: - tsup.config.ts is shared by packages/a, packages/b. It was preserved and must be migrated manually. -→ Manual follow-up: - - Please manually merge tsdown.config.ts into vite.config.ts, see https://viteplus.dev/guide/migrate#tsdown ``` ## `vpt stat-file tsup.config.ts --assert file` diff --git a/packages/cli/src/migration/__tests__/migrator.spec.ts b/packages/cli/src/migration/__tests__/migrator.spec.ts index d3e92bdc6c..c395b594db 100644 --- a/packages/cli/src/migration/__tests__/migrator.spec.ts +++ b/packages/cli/src/migration/__tests__/migrator.spec.ts @@ -8796,6 +8796,7 @@ describe('existing Vite+ core migration finalization', () => { scripts: true, tsconfigTypes: true, imports: true, + tsdownConfig: false, }); const pkg = readJson(path.join(tmpDir, 'package.json')) as { @@ -8850,6 +8851,85 @@ describe('existing Vite+ core migration finalization', () => { }; expect(appPkg.scripts.dev).toBe('vp dev'); }); + + it('makes a leftover tsdown config discoverable in an existing Vite+ project', () => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ name: 'test', devDependencies: { 'vite-plus': 'latest' } }, null, 2), + ); + fs.writeFileSync( + path.join(tmpDir, 'tsdown.config.ts'), + `import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: { index: 'src/index.ts', utils: 'src/utils.ts' }, +}); +`, + ); + + const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.pnpm); + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true)).toEqual({ + scripts: false, + tsconfigTypes: false, + imports: true, + tsdownConfig: true, + }); + expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toContain( + "import tsdownConfig from './tsdown.config.js';", + ); + expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toContain( + 'pack: tsdownConfig', + ); + expect(fs.readFileSync(path.join(tmpDir, 'tsdown.config.ts'), 'utf8')).toContain( + "from 'vite-plus/pack'", + ); + + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true)).toEqual({ + scripts: false, + tsconfigTypes: false, + imports: false, + tsdownConfig: false, + }); + }); + + it('preserves a tsdown config already wired to pack under a different import name', () => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ name: 'test', devDependencies: { 'vite-plus': 'latest' } }, null, 2), + ); + fs.writeFileSync( + path.join(tmpDir, 'vite.config.ts'), + `import packConfig from './tsdown.config.js'; + +export default { pack: packConfig({}) }; +`, + ); + fs.writeFileSync( + path.join(tmpDir, 'tsdown.config.ts'), + `import { defineConfig } from 'tsdown'; + +export default defineConfig({ entry: 'src/index.ts' }); +`, + ); + + const originalViteConfig = fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8'); + const report = createMigrationReport(); + const result = finalizeCoreMigrationForExistingVitePlus( + makeWorkspaceInfo(tmpDir, PackageManager.pnpm), + true, + report, + ); + + expect(result).toEqual({ + scripts: false, + tsconfigTypes: false, + imports: true, + tsdownConfig: false, + }); + expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toBe(originalViteConfig); + expect(report.tsdownImportCount).toBe(0); + expect(report.manualSteps).toEqual([]); + }); }); // Regression: templates such as `create-fate` ship a populated vite.config.ts diff --git a/packages/cli/src/migration/bin.ts b/packages/cli/src/migration/bin.ts index b43647875e..ceaaa61ce8 100644 --- a/packages/cli/src/migration/bin.ts +++ b/packages/cli/src/migration/bin.ts @@ -1114,7 +1114,8 @@ async function main() { if ( coreMigrationResult.scripts || coreMigrationResult.tsconfigTypes || - coreMigrationResult.imports + coreMigrationResult.imports || + coreMigrationResult.tsdownConfig ) { didMigrate = true; } diff --git a/packages/cli/src/migration/migrator/core-finalization.ts b/packages/cli/src/migration/migrator/core-finalization.ts index 29af68a91a..46b0d51d76 100644 --- a/packages/cli/src/migration/migrator/core-finalization.ts +++ b/packages/cli/src/migration/migrator/core-finalization.ts @@ -5,7 +5,12 @@ import { rewriteScripts } from '../../../binding/index.js'; import { type WorkspacePackage } from '../../types/index.ts'; import { editJsonFile, readJsonFile } from '../../utils/json.ts'; import { rulesDir } from '../../utils/path.ts'; -import { hasTsconfigTypesToRewrite, rewriteAllImports, rewriteTsconfigTypes } from '../migrator.ts'; +import { + hasTsconfigTypesToRewrite, + mergeTsdownConfigFile, + rewriteAllImports, + rewriteTsconfigTypes, +} from '../migrator.ts'; import { type MigrationReport } from '../report.ts'; const RULES_YAML_PATH = path.join(rulesDir, 'vite-tools.yml'); @@ -76,6 +81,7 @@ export type CoreMigrationFinalizationResult = { scripts: boolean; tsconfigTypes: boolean; imports: boolean; + tsdownConfig: boolean; }; function getCoreMigrationProjectPaths(workspaceInfo: CoreMigrationWorkspace): string[] { @@ -140,6 +146,7 @@ export function finalizeCoreMigrationForExistingVitePlus( scripts: false, tsconfigTypes: false, imports: false, + tsdownConfig: false, }; if (pending.scripts) { @@ -157,5 +164,12 @@ export function finalizeCoreMigrationForExistingVitePlus( result.imports = rewriteAllImports(workspaceInfo.rootDir, silent, report, true); + // Partial migrations can already have a Vite+ dependency while leaving + // tsdown.config.* undiscoverable by vp pack. Finalize those configs on the + // existing-Vite+ path just as the fresh migration path does. + for (const projectPath of projectPaths) { + result.tsdownConfig = mergeTsdownConfigFile(projectPath, silent, report) || result.tsdownConfig; + } + return result; } diff --git a/packages/cli/src/migration/migrator/vite-config.ts b/packages/cli/src/migration/migrator/vite-config.ts index 2310ba9e08..63253688cb 100644 --- a/packages/cli/src/migration/migrator/vite-config.ts +++ b/packages/cli/src/migration/migrator/vite-config.ts @@ -174,11 +174,12 @@ export function mergeTsdownConfigFile( projectPath: string, silent = false, report?: MigrationReport, -): void { +): boolean { const configs = detectConfigs(projectPath); if (!configs.tsdownConfig) { - return; + return false; } + const createdViteConfig = !configs.viteConfig; const viteConfig = ensureViteConfig(projectPath, configs, silent, report); const fullViteConfigPath = path.join(projectPath, viteConfig); @@ -187,11 +188,34 @@ export function mergeTsdownConfigFile( // For JSON files, merge content directly and delete the file if (configs.tsdownConfig.endsWith('.json')) { mergeAndRemoveJsonConfig(projectPath, viteConfig, configs.tsdownConfig, 'pack', silent, report); - return; + return createdViteConfig || !fs.existsSync(fullTsdownConfigPath); } // For TS/JS files, import the config file const tsdownRelativePath = `./${configs.tsdownConfig}`; + // Do not prepend a second `pack` key when the config is already wired under + // a different local import name. If a pack config exists but does not import + // this tsdown config, leave both files untouched and keep the manual follow-up. + if (hasConfigKey(fullViteConfigPath, 'pack')) { + const viteConfigContent = fs.readFileSync(fullViteConfigPath, 'utf8'); + const runtimeImportPath = tsdownRelativePath + .replace(/\.mts$/, '.mjs') + .replace(/\.cts$/, '.cjs') + .replace(/\.ts$/, '.js'); + const importsTsdownConfig = [tsdownRelativePath, runtimeImportPath].some( + (importPath) => + viteConfigContent.includes(`from '${importPath}'`) || + viteConfigContent.includes(`from "${importPath}"`), + ); + if (!importsTsdownConfig) { + infoMigration( + `Please manually merge ${displayRelative(fullTsdownConfigPath)} into ${displayRelative(fullViteConfigPath)}, see https://viteplus.dev/guide/migrate#tsdown`, + report, + ); + } + return false; + } + const result = mergeTsdownConfig(fullViteConfigPath, tsdownRelativePath); if (result.updated) { fs.writeFileSync(fullViteConfigPath, result.content); @@ -209,6 +233,7 @@ export function mergeTsdownConfigFile( `Please manually merge ${displayRelative(fullTsdownConfigPath)} into ${displayRelative(fullViteConfigPath)}, see https://viteplus.dev/guide/migrate#tsdown`, report, ); + return createdViteConfig || result.updated; } /**