UNPKG

svelte-migrate

Version:

A CLI for migrating Svelte(Kit) codebases

244 lines (216 loc) 7.21 kB
import { resolve } from 'import-meta-resolve'; import pc from 'picocolors'; import { execSync } from 'node:child_process'; import process from 'node:process'; import fs from 'node:fs'; import { dirname } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import * as p from '@clack/prompts'; import semver from 'semver'; import glob from 'tiny-glob/sync.js'; import { bail, check_git, migration_succeeded, update_js_file, update_svelte_file } from '../../utils.js'; import { migrate as migrate_svelte_4 } from '../svelte-4/index.js'; import { migrate as migrate_sveltekit_2 } from '../sveltekit-2/index.js'; import { transform_svelte_code as transform_app_state_code } from '../app-state/migrate.js'; import { transform_module_code, transform_svelte_code, update_pkg_json } from './migrate.js'; import { detect, resolveCommand } from 'package-manager-detector'; export async function migrate() { if (!fs.existsSync('package.json')) { bail('Please re-run this script in a directory with a package.json'); } const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); const svelte_dep = pkg.devDependencies?.svelte ?? pkg.dependencies?.svelte; if (svelte_dep && semver.validRange(svelte_dep) && semver.gtr('4.0.0', svelte_dep)) { p.log.warning( pc.bold( pc.yellow( 'Detected Svelte 3. You need to upgrade to Svelte version 4 first (`npx sv migrate svelte-4`).' ) ) ); const response = await p.confirm({ message: 'Run svelte-4 migration now?', initialValue: false }); if (p.isCancel(response) || !response) { process.exit(1); } else { await migrate_svelte_4(); p.log.success( pc.bold( pc.green( 'svelte-4 migration complete. Check that everything is ok, then run `npx sv migrate svelte-5` again to continue the Svelte 5 migration.' ) ) ); process.exit(0); } } const kit_dep = pkg.devDependencies?.['@sveltejs/kit'] ?? pkg.dependencies?.['@sveltejs/kit']; if (kit_dep && semver.validRange(kit_dep) && semver.gtr('2.0.0', kit_dep)) { p.log.warning( pc.bold( pc.yellow( 'Detected SvelteKit 1. You need to upgrade to SvelteKit version 2 first (`npx sv migrate sveltekit-2`).' ) ) ); const response = await p.confirm({ message: 'Run sveltekit-2 migration now?', initialValue: false }); if (p.isCancel(response) || !response) { process.exit(1); } else { await migrate_sveltekit_2(); p.log.success( pc.bold( pc.green( 'sveltekit-2 migration complete. Check that everything is ok, then run `npx sv migrate svelte-5` again to continue the Svelte 5 migration.' ) ) ); process.exit(0); } } let migrate; try { try { ({ migrate } = await import_from_cwd('svelte/compiler')); if (!migrate) throw new Error('found Svelte 4'); } catch { execSync('npm install svelte@^5.0.0 --no-save', { stdio: 'inherit', cwd: dirname(fileURLToPath(import.meta.url)) }); const url = resolve('svelte/compiler', import.meta.url); ({ migrate } = await import(url)); } } catch (e) { console.log(e); p.log.error( pc.bold( pc.red( '❌ Could not install Svelte. Manually bump the dependency to version 5 in your package.json, install it, then try again.' ) ) ); return; } p.log.warning( pc.bold(pc.yellow('This will update files in the current directory.')) + '\n' + pc.bold( pc.yellow( "If you're inside a monorepo, don't run this in the root directory, rather run it in all projects independently." ) ) ); const use_git = check_git(); const response = await p.confirm({ message: 'Continue?', initialValue: false }); if (p.isCancel(response) || !response) { process.exit(1); } const dirs = fs .readdirSync('.') .filter( (dir) => fs.statSync(dir).isDirectory() && dir !== 'node_modules' && !dir.startsWith('.') ); let folders = await p.multiselect({ message: 'Which folders should be migrated?', options: dirs .map((dir) => ({ label: dir, value: dir })) .concat([ { label: 'custom (overrides selection, allows to specify sub folders)', value: ',' // a value that definitely isn't a valid folder name so it cannot clash } ]), initialValues: dirs }); if (p.isCancel(folders) || !folders?.length) { process.exit(1); } if (folders.includes(',')) { const custom = await p.text({ message: 'Specify folder paths (comma separated)' }); if (p.isCancel(custom) || !custom) { process.exit(1); } folders = custom.split(',').map((/** @type {string} */ folder) => (folder = folder.trim())); } const do_migration = await p.confirm({ message: 'Do you want to use the migration tool to convert your Svelte components to the new syntax? (You can also do this per component or sub path later)', initialValue: true }); if (p.isCancel(do_migration)) process.exit(1); update_pkg_json(); const use_ts = fs.existsSync('tsconfig.json'); // const { default: config } = fs.existsSync('svelte.config.js') // ? await import(pathToFileURL(path.resolve('svelte.config.js')).href) // : { default: {} }; /** @type {string[]} */ const svelte_extensions = /* config.extensions ?? - disabled because it would break .svx */ [ '.svelte' ]; const extensions = [...svelte_extensions, '.ts', '.js']; // For some reason {folders.value.join(',')} as part of the glob doesn't work and returns less files const files = folders.flatMap( /** @param {string} folder */ (folder) => glob(`${folder}/**`, { filesOnly: true, dot: true }) .map((file) => file.replace(/\\/g, '/')) .filter((file) => !file.includes('/node_modules/')) ); for (const file of files) { if (extensions.some((ext) => file.endsWith(ext))) { if (svelte_extensions.some((ext) => file.endsWith(ext))) { if (do_migration) { if (kit_dep) { update_svelte_file( file, (code) => code, (code) => transform_app_state_code(code) ); } update_svelte_file(file, transform_module_code, (code) => transform_svelte_code(code, migrate, { filename: file, use_ts }) ); } } else { update_js_file(file, transform_module_code); } } } /** @type {(s: string) => string} */ const cyan = (s) => pc.bold(pc.cyan(s)); const detected = await detect({ cwd: process.cwd() }); const pm = detected?.name ?? 'npm'; const cmd = /** @type {import('package-manager-detector').ResolvedCommand} */ ( resolveCommand(pm, 'install', []) ); const tasks = [ `Install the updated dependencies by running ${cyan(`${cmd.command} ${cmd.args.join(' ')}`)} ` + '(note that there may be peer dependency issues when not all your libraries officially support Svelte 5 yet. In this case try installing with the --force option)', use_git && cyan('git commit -m "migration to Svelte 5"'), 'Review the migration guide at https://svelte.dev/docs/svelte/v5-migration-guide', `Run ${cyan('git diff')} to review changes.` ].filter(Boolean); migration_succeeded(tasks); } /** @param {string} name */ function import_from_cwd(name) { const cwd = pathToFileURL(process.cwd()).href; const url = resolve(name, cwd + '/x.js'); return import(url); }