diff --git a/.changeset/dull-toys-share.md b/.changeset/dull-toys-share.md new file mode 100644 index 00000000..ab14d441 --- /dev/null +++ b/.changeset/dull-toys-share.md @@ -0,0 +1,5 @@ +--- +'svelte-migrate': patch +--- + +chore: align dependencies with `sv` diff --git a/.changeset/happy-singers-roll.md b/.changeset/happy-singers-roll.md new file mode 100644 index 00000000..f76778a7 --- /dev/null +++ b/.changeset/happy-singers-roll.md @@ -0,0 +1,5 @@ +--- +'svelte-migrate': minor +--- + +feat: add ability to select migration to run diff --git a/documentation/docs/20-commands/40-sv-migrate.md b/documentation/docs/20-commands/40-sv-migrate.md index 4283a5e0..87e69568 100644 --- a/documentation/docs/20-commands/40-sv-migrate.md +++ b/documentation/docs/20-commands/40-sv-migrate.md @@ -8,6 +8,11 @@ Some migrations may annotate your codebase with tasks for completion that you ca ## Usage +```bash +npx sv migrate +``` + +You can also specify a migration directly via the CLI: ```bash npx sv migrate [migration] ``` diff --git a/packages/migrate/README.md b/packages/migrate/README.md index 07ae73a9..ed7413bd 100644 --- a/packages/migrate/README.md +++ b/packages/migrate/README.md @@ -4,13 +4,13 @@ A CLI for migrating Svelte(Kit) codebases. Run it directly using: -``` -npx svelte-migrate [migration] +```bash +npx sv migrate ``` -Or via the unified Svlete CLI with: +You can also specify a migration directly via the CLI: -``` +```bash npx sv migrate [migration] ``` diff --git a/packages/migrate/bin.js b/packages/migrate/bin.js index df8d7de7..18f1ad8b 100755 --- a/packages/migrate/bin.js +++ b/packages/migrate/bin.js @@ -2,7 +2,8 @@ import fs from 'node:fs'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; -import colors from 'kleur'; +import pc from 'picocolors'; +import * as p from '@clack/prompts'; const migration = process.argv[2]; const dir = fileURLToPath(new URL('.', import.meta.url)); @@ -11,17 +12,29 @@ const migrations = fs .readdirSync(`${dir}/migrations`) .filter((migration) => fs.existsSync(`${dir}/migrations/${migration}/index.js`)); +const pkg = JSON.parse(fs.readFileSync(`${dir}/package.json`, 'utf8')); + +p.intro(`Welcome to the svelte-migrate CLI! ${pc.gray(`(v${pkg.version})`)}`); + if (migrations.includes(migration)) { - const { migrate } = await import(`./migrations/${migration}/index.js`); - migrate(); + await run_migration(migration); } else { - console.error( - colors - .bold() - .red( - `You must specify one of the following migrations: ${migrations.join(', ')}\n` + - 'If you expected this to work, try re-running the command with the latest svelte-migrate version:\n' + - ` npx svelte-migrate@latest ${migration}` - ) - ); + if (migration) p.log.warning(pc.yellow(`Invalid migration "${migration}" provided.`)); + + const selectedMigration = await p.select({ + message: 'Which migration would you like to run?', + options: migrations.map((x) => ({ value: x, label: x })) + }); + + if (!p.isCancel(selectedMigration)) await run_migration(selectedMigration); +} + +p.outro("You're all set!"); + +/** + * @param {string} migration + */ +async function run_migration(migration) { + const { migrate } = await import(`./migrations/${migration}/index.js`); + await migrate(); } diff --git a/packages/migrate/migrations/app-state/index.js b/packages/migrate/migrations/app-state/index.js index 8ba9a326..b3775e20 100644 --- a/packages/migrate/migrations/app-state/index.js +++ b/packages/migrate/migrations/app-state/index.js @@ -1,10 +1,10 @@ -import colors from 'kleur'; +import pc from 'picocolors'; import fs from 'node:fs'; import process from 'node:process'; -import prompts from 'prompts'; +import * as p from '@clack/prompts'; import semver from 'semver'; import glob from 'tiny-glob/sync.js'; -import { bail, check_git, update_svelte_file } from '../../utils.js'; +import { bail, check_git, migration_succeeded, update_svelte_file } from '../../utils.js'; import { transform_svelte_code, update_pkg_json } from './migrate.js'; export async function migrate() { @@ -16,51 +16,46 @@ export async function migrate() { const svelte_dep = pkg.devDependencies?.svelte ?? pkg.dependencies?.svelte; if (svelte_dep && semver.validRange(svelte_dep) && semver.gtr('5.0.0', svelte_dep)) { - console.log( - colors - .bold() - .red('\nYou need to upgrade to Svelte version 5 first (`npx sv migrate svelte-5`).\n') + p.log.error( + pc.bold(pc.red('You need to upgrade to Svelte version 5 first (`npx sv migrate svelte-5`).')) ); process.exit(1); } 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)) { - console.log( - colors - .bold() - .red('\nYou need to upgrade to SvelteKit version 2 first (`npx sv migrate sveltekit-2`).\n') + p.log.error( + pc.bold( + pc.red('You need to upgrade to SvelteKit version 2 first (`npx sv migrate sveltekit-2`).') + ) ); process.exit(1); } - console.log( - colors - .bold() - .yellow( - '\nThis will update files in the current directory\n' + - "If you're inside a monorepo, don't run this in the root directory, rather run it in all projects independently.\n" + 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 prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Continue?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } - const folders = await prompts({ - type: 'multiselect', - name: 'value', + const folders = await p.multiselect({ message: 'Which folders should be migrated?', - choices: fs + options: fs .readdirSync('.') .filter( (dir) => fs.statSync(dir).isDirectory() && dir !== 'node_modules' && !dir.startsWith('.') @@ -68,14 +63,14 @@ export async function migrate() { .map((dir) => ({ title: dir, value: dir, selected: true })) }); - if (!folders.value?.length) { + if (p.isCancel(folders) || !folders?.length) { process.exit(1); } update_pkg_json(); // For some reason {folders.value.join(',')} as part of the glob doesn't work and returns less files - const files = folders.value.flatMap( + const files = folders.flatMap( /** @param {string} folder */ (folder) => glob(`${folder}/**`, { filesOnly: true, dot: true }) .map((file) => file.replace(/\\/g, '/')) @@ -96,24 +91,15 @@ export async function migrate() { ); } - console.log(colors.bold().green('✔ Your project has been migrated')); - - console.log('\nRecommended next steps:\n'); - - const cyan = colors.bold().cyan; - - const tasks = [ - "install the updated dependencies ('npm i' / 'pnpm i' / etc) " + use_git && - cyan('git commit -m "migration to $app/state"') - ].filter(Boolean); - - tasks.forEach((task, i) => { - console.log(` ${i + 1}: ${task}`); - }); - - console.log(''); + /** @type {(s: string) => string} */ + const cyan = (s) => pc.bold(pc.cyan(s)); + // TODO: use package-manager-detector here + const tasks = ["install the updated dependencies ('npm i' / 'pnpm i' / etc)"]; if (use_git) { - console.log(`Run ${cyan('git diff')} to review changes.\n`); + tasks.push(cyan('git commit -m "migration to $app/state"')); + tasks.push(`Run ${cyan('git diff')} to review changes.`); } + + migration_succeeded(tasks); } diff --git a/packages/migrate/migrations/package/index.js b/packages/migrate/migrations/package/index.js index 0e365476..05565eed 100644 --- a/packages/migrate/migrations/package/index.js +++ b/packages/migrate/migrations/package/index.js @@ -1,10 +1,10 @@ import fs from 'node:fs'; -import colors from 'kleur'; +import pc from 'picocolors'; import path from 'node:path'; import process from 'node:process'; -import prompts from 'prompts'; +import * as p from '@clack/prompts'; import { pathToFileURL } from 'node:url'; -import { bail, check_git } from '../../utils.js'; +import { bail, check_git, migration_succeeded } from '../../utils.js'; import { migrate_config } from './migrate_config.js'; import { migrate_pkg } from './migrate_pkg.js'; @@ -16,24 +16,20 @@ export async function migrate() { bail('Please re-run this script in a directory with a package.json'); } - console.log( - colors - .bold() - .yellow( - '\nThis will update your svelte.config.js and package.json in the current directory\n' - ) + p.log.warning( + pc.bold( + pc.yellow('This will update your svelte.config.js and package.json in the current directory') + ) ); const use_git = check_git(); - const response = await prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Continue?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } @@ -57,25 +53,16 @@ export async function migrate() { migrate_config(); } - console.log(colors.bold().green('✔ Your project has been migrated')); + /** @type {(s: string) => string} */ + const cyan = (s) => pc.bold(pc.cyan(s)); - console.log('\nRecommended next steps:\n'); + /** @type {string[]} */ + const tasks = []; - const cyan = colors.bold().cyan; + if (use_git) tasks.push(cyan('git commit -m "migration to @sveltejs/package v2"')); - const tasks = [ - use_git && cyan('git commit -m "migration to @sveltejs/package v2"'), - 'Review the migration guide at https://github.com/sveltejs/kit/pull/8922', - 'Read the updated docs at https://svelte.dev/docs/kit/packaging' - ].filter(Boolean); + tasks.push('Review the migration guide at https://github.com/sveltejs/kit/pull/8922'); + tasks.push('Read the updated docs at https://svelte.dev/docs/kit/packaging'); - tasks.forEach((task, i) => { - console.log(` ${i + 1}: ${task}`); - }); - - console.log(''); - - if (use_git) { - console.log(`Run ${cyan('git diff')} to review changes.\n`); - } + migration_succeeded; } diff --git a/packages/migrate/migrations/package/migrate_config.js b/packages/migrate/migrations/package/migrate_config.js index b401626d..5d42e227 100644 --- a/packages/migrate/migrations/package/migrate_config.js +++ b/packages/migrate/migrations/package/migrate_config.js @@ -1,5 +1,6 @@ +import * as p from '@clack/prompts'; import fs from 'node:fs'; -import colors from 'kleur'; +import pc from 'picocolors'; import MagicString from 'magic-string'; import ts from 'typescript'; @@ -8,10 +9,12 @@ export function migrate_config() { const content = fs.readFileSync('svelte.config.js', 'utf8'); fs.writeFileSync('svelte.config.js', remove_package_from_config(content)); } catch { - console.log( - colors - .bold() - .yellow('Could not remove package config from svelte.config.js, please remove it manually') + p.log.warning( + pc.bold( + pc.yellow( + 'Could not remove package config from svelte.config.js, please remove it manually' + ) + ) ); } } diff --git a/packages/migrate/migrations/package/migrate_pkg.js b/packages/migrate/migrations/package/migrate_pkg.js index 3e5f5b6a..f79a2294 100644 --- a/packages/migrate/migrations/package/migrate_pkg.js +++ b/packages/migrate/migrations/package/migrate_pkg.js @@ -1,6 +1,7 @@ +import * as p from '@clack/prompts'; import fs from 'node:fs'; import path from 'node:path'; -import colors from 'kleur'; +import pc from 'picocolors'; import { guess_indent, posixify, walk } from '../../utils.js'; /** @@ -58,8 +59,8 @@ export function update_pkg_json(config, pkg, files) { // See: https://pnpm.io/package_json#publishconfigdirectory if (pkg.publishConfig?.directory || pkg.linkDirectory?.directory) { - console.log( - colors.yellow( + p.log.warning( + pc.yellow( 'Detected "publishConfig.directory" or "linkDirectory.directory" fields in your package.json. ' + 'This migration removes them, which may or may not be what you want. Please review closely.' ) @@ -101,8 +102,8 @@ export function update_pkg_json(config, pkg, files) { const key = `./${file.dest}`.replace(/\/index\.js$|(\/[^/]+)\.js$/, '$1'); if (clashes[key]) { - console.log( - colors.yellow( + p.log.warning( + pc.yellow( `Duplicate "${key}" export. Closely review your "exports" field in package.json after the migration.` ) ); @@ -169,15 +170,15 @@ export function update_pkg_json(config, pkg, files) { if (svelte_export) { pkg.svelte = svelte_export; } else { - console.log( - colors.yellow( + p.log.warning( + pc.yellow( 'Cannot generate a "svelte" entry point because the "." entry in "exports" is not a string. Please specify a "svelte" entry point yourself\n' ) ); } } else { - console.log( - colors.yellow( + p.log.warning( + pc.yellow( 'Cannot generate a "svelte" entry point because the "." entry in "exports" is missing. Please specify a "svelte" entry point yourself\n' ) ); diff --git a/packages/migrate/migrations/routes/index.js b/packages/migrate/migrations/routes/index.js index 9f043327..fa95d446 100644 --- a/packages/migrate/migrations/routes/index.js +++ b/packages/migrate/migrations/routes/index.js @@ -1,8 +1,8 @@ import fs from 'node:fs'; -import colors from 'kleur'; +import pc from 'picocolors'; import path from 'node:path'; import process from 'node:process'; -import prompts from 'prompts'; +import * as p from '@clack/prompts'; import glob from 'tiny-glob/sync.js'; import { pathToFileURL } from 'node:url'; import { migrate_scripts } from './migrate_scripts/index.js'; @@ -10,7 +10,7 @@ import { migrate_page } from './migrate_page_js/index.js'; import { migrate_page_server } from './migrate_page_server/index.js'; import { migrate_server } from './migrate_server/index.js'; import { adjust_imports, task } from './utils.js'; -import { bail, relative, move_file, check_git } from '../../utils.js'; +import { bail, relative, move_file, check_git, migration_succeeded } from '../../utils.js'; export async function migrate() { if (!fs.existsSync('svelte.config.js')) { @@ -56,18 +56,16 @@ export async function migrate() { } } - console.log(colors.bold().yellow('\nThis will overwrite files in the current directory!\n')); + p.log.warning(pc.bold(pc.yellow('This will overwrite files in the current directory!'))); const use_git = check_git(); - const response = await prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Continue?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } @@ -185,27 +183,17 @@ export async function migrate() { } } - console.log(colors.bold().green('✔ Your project has been migrated')); - - console.log('\nRecommended next steps:\n'); - - const cyan = colors.bold().cyan; + /** @type {(s: string) => string} */ + const cyan = (s) => pc.bold(pc.cyan(s)); const tasks = [ use_git && cyan('git commit -m "svelte-migrate: renamed files"'), 'Review the migration guide at https://github.com/sveltejs/kit/discussions/5774', `Search codebase for ${cyan('"@migration"')} and manually complete migration tasks`, use_git && cyan('git add -A'), - use_git && cyan('git commit -m "svelte-migrate: updated files"') + use_git && cyan('git commit -m "svelte-migrate: updated files"'), + use_git && `Run ${cyan('git diff')} to review changes.` ].filter(Boolean); - tasks.forEach((task, i) => { - console.log(` ${i + 1}: ${task}`); - }); - - console.log(''); - - if (use_git) { - console.log(`Run ${cyan('git diff')} to review changes.\n`); - } + migration_succeeded(tasks); } diff --git a/packages/migrate/migrations/self-closing-tags/index.js b/packages/migrate/migrations/self-closing-tags/index.js index ced6ca30..d724f2f3 100644 --- a/packages/migrate/migrations/self-closing-tags/index.js +++ b/packages/migrate/migrations/self-closing-tags/index.js @@ -1,33 +1,32 @@ -import colors from 'kleur'; +import pc from 'picocolors'; import fs from 'node:fs'; import process from 'node:process'; -import prompts from 'prompts'; +import * as p from '@clack/prompts'; import glob from 'tiny-glob/sync.js'; import { remove_self_closing_tags } from './migrate.js'; import { pathToFileURL } from 'node:url'; import { resolve } from 'import-meta-resolve'; +import { migration_succeeded } from '../../utils.js'; export async function migrate() { let compiler; try { compiler = await import_from_cwd('svelte/compiler'); } catch { - console.log(colors.bold().red('❌ Could not find a local Svelte installation.')); + p.log.error(pc.bold(pc.red('❌ Could not find a local Svelte installation.'))); return; } - console.log( - colors.bold().yellow('\nThis will update .svelte files inside the current directory\n') + p.log.warning( + pc.bold(pc.yellow('\nThis will update .svelte files inside the current directory\n')) ); - const response = await prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Continue?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } @@ -44,8 +43,9 @@ export async function migrate() { } } - console.log(colors.bold().green('✔ Your project has been updated')); - console.log(' If using Prettier, please upgrade to the latest prettier-plugin-svelte version'); + const tasks = ['If using Prettier, please upgrade to the latest prettier-plugin-svelte version']; + + migration_succeeded(tasks); } /** @param {string} name */ diff --git a/packages/migrate/migrations/svelte-4/index.js b/packages/migrate/migrations/svelte-4/index.js index 4fea5173..4bab4316 100644 --- a/packages/migrate/migrations/svelte-4/index.js +++ b/packages/migrate/migrations/svelte-4/index.js @@ -1,9 +1,15 @@ -import colors from 'kleur'; +import pc from 'picocolors'; import fs from 'node:fs'; import process from 'node:process'; -import prompts from 'prompts'; +import * as p from '@clack/prompts'; import glob from 'tiny-glob/sync.js'; -import { bail, check_git, update_js_file, update_svelte_file } from '../../utils.js'; +import { + bail, + check_git, + migration_succeeded, + update_js_file, + update_svelte_file +} from '../../utils.js'; import { transform_code, transform_svelte_code, update_pkg_json } from './migrate.js'; export async function migrate() { @@ -11,33 +17,30 @@ export async function migrate() { bail('Please re-run this script in a directory with a package.json'); } - console.log( - colors - .bold() - .yellow( - '\nThis will update files in the current directory\n' + - "If you're inside a monorepo, don't run this in the root directory, rather run it in all projects independently.\n" + 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 prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Continue?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } - const folders = await prompts({ - type: 'multiselect', - name: 'value', + const folders = await p.multiselect({ message: 'Which folders should be migrated?', - choices: fs + options: fs .readdirSync('.') .filter( (dir) => fs.statSync(dir).isDirectory() && dir !== 'node_modules' && !dir.startsWith('.') @@ -45,18 +48,20 @@ export async function migrate() { .map((dir) => ({ title: dir, value: dir, selected: true })) }); - if (!folders.value?.length) { + if (p.isCancel(folders) || !folders?.length) { process.exit(1); } - const migrate_transition = await prompts({ - type: 'confirm', - name: 'value', + const migrate_transition = await p.confirm({ message: 'Add the `|global` modifier to currently global transitions for backwards compatibility? More info at https://svelte.dev/docs/svelte/v4-migration-guide#transitions-are-local-by-default', - initial: true + initialValue: true }); + if (p.isCancel(migrate_transition)) { + process.exit(1); + } + update_pkg_json(); // const { default: config } = fs.existsSync('svelte.config.js') @@ -68,8 +73,8 @@ export async function migrate() { '.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.value.flatMap( + // For some reason {folders.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, '/')) @@ -80,7 +85,7 @@ export async function migrate() { if (extensions.some((ext) => file.endsWith(ext))) { if (svelte_extensions.some((ext) => file.endsWith(ext))) { update_svelte_file(file, transform_code, (code) => - transform_svelte_code(code, migrate_transition.value) + transform_svelte_code(code, migrate_transition) ); } else { update_js_file(file, transform_code); @@ -88,25 +93,15 @@ export async function migrate() { } } - console.log(colors.bold().green('✔ Your project has been migrated')); - - console.log('\nRecommended next steps:\n'); - - const cyan = colors.bold().cyan; + /** @type {(s: string) => string} */ + const cyan = (s) => pc.bold(pc.cyan(s)); const tasks = [ use_git && cyan('git commit -m "migration to Svelte 4"'), 'Review the migration guide at https://svelte.dev/docs/svelte/v4-migration-guide', - 'Read the updated docs at https://svelte.dev/docs/svelte' + 'Read the updated docs at https://svelte.dev/docs/svelte', + use_git && `Run ${cyan('git diff')} to review changes.` ].filter(Boolean); - tasks.forEach((task, i) => { - console.log(` ${i + 1}: ${task}`); - }); - - console.log(''); - - if (use_git) { - console.log(`Run ${cyan('git diff')} to review changes.\n`); - } + migration_succeeded(tasks); } diff --git a/packages/migrate/migrations/svelte-5/index.js b/packages/migrate/migrations/svelte-5/index.js index 7ba620f1..92a1dfa9 100644 --- a/packages/migrate/migrations/svelte-5/index.js +++ b/packages/migrate/migrations/svelte-5/index.js @@ -1,14 +1,20 @@ import { resolve } from 'import-meta-resolve'; -import colors from 'kleur'; +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 prompts from 'prompts'; +import * as p from '@clack/prompts'; import semver from 'semver'; import glob from 'tiny-glob/sync.js'; -import { bail, check_git, update_js_file, update_svelte_file } from '../../utils.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_module_code, transform_svelte_code, update_pkg_json } from './migrate.js'; @@ -22,29 +28,27 @@ export async function migrate() { const svelte_dep = pkg.devDependencies?.svelte ?? pkg.dependencies?.svelte; if (svelte_dep && semver.validRange(svelte_dep) && semver.gtr('4.0.0', svelte_dep)) { - console.log( - colors - .bold() - .yellow( - '\nDetected Svelte 3. You need to upgrade to Svelte version 4 first (`npx sv migrate svelte-4`).\n' + 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 prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Run svelte-4 migration now?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } else { await migrate_svelte_4(); - console.log( - colors - .bold() - .green( - 'svelte-4 migration complete. Check that everything is ok, then run `npx sv migrate svelte-5` again to continue the Svelte 5 migration.\n' + 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); } @@ -52,29 +56,27 @@ export async function migrate() { 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)) { - console.log( - colors - .bold() - .yellow( - '\nDetected SvelteKit 1. You need to upgrade to SvelteKit version 2 first (`npx sv migrate sveltekit-2`).\n' + 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 prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Run sveltekit-2 migration now?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } else { await migrate_sveltekit_2(); - console.log( - colors - .bold() - .green( - 'sveltekit-2 migration complete. Check that everything is ok, then run `npx sv migrate svelte-5` again to continue the Svelte 5 migration.\n' + 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); } @@ -95,83 +97,80 @@ export async function migrate() { } } catch (e) { console.log(e); - console.log( - colors - .bold() - .red( + 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; } - console.log( - colors - .bold() - .yellow( - '\nThis will update files in the current directory\n' + - "If you're inside a monorepo, don't run this in the root directory, rather run it in all projects independently.\n" + 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 prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Continue?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } - const folders = await prompts({ - type: 'multiselect', - name: 'value', + 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?', - choices: fs - .readdirSync('.') - .filter( - (dir) => fs.statSync(dir).isDirectory() && dir !== 'node_modules' && !dir.startsWith('.') - ) - .map((dir) => ({ title: dir, value: dir, selected: true })) + options: dirs + .map((dir) => ({ label: dir, value: dir })) .concat([ { - title: 'custom (overrides selection, allows to specify sub folders)', - value: ',', // a value that definitely isn't a valid folder name so it cannot clash - selected: false + 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 (!folders.value?.length) { + if (p.isCancel(folders) || !folders?.length) { process.exit(1); } - if (folders.value.includes(',')) { - const custom = await prompts({ - type: 'list', - name: 'value', + if (folders.includes(',')) { + const custom = await p.text({ message: 'Specify folder paths (comma separated)' }); - if (!custom.value) { + if (p.isCancel(custom) || !custom) { process.exit(1); } - folders.value = custom.value.map((/** @type {string} */ folder) => (folder = folder.trim())); + folders = custom.split(',').map((/** @type {string} */ folder) => (folder = folder.trim())); } - const do_migration = await prompts({ - type: 'confirm', - name: 'value', + 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)', - initial: true + initialValue: true }); + if (p.isCancel(do_migration)) process.exit(1); + update_pkg_json(); const use_ts = fs.existsSync('tsconfig.json'); @@ -186,7 +185,7 @@ export async function migrate() { ]; 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.value.flatMap( + const files = folders.flatMap( /** @param {string} folder */ (folder) => glob(`${folder}/**`, { filesOnly: true, dot: true }) .map((file) => file.replace(/\\/g, '/')) @@ -196,7 +195,7 @@ export async function migrate() { for (const file of files) { if (extensions.some((ext) => file.endsWith(ext))) { if (svelte_extensions.some((ext) => file.endsWith(ext))) { - if (do_migration.value) { + if (do_migration) { update_svelte_file(file, transform_module_code, (code) => transform_svelte_code(code, migrate, { filename: file, use_ts }) ); @@ -207,28 +206,18 @@ export async function migrate() { } } - console.log(colors.bold().green('✔ Your project has been migrated')); - - console.log('\nRecommended next steps:\n'); - - const cyan = colors.bold().cyan; + /** @type {(s: string) => string} */ + const cyan = (s) => pc.bold(pc.cyan(s)); const tasks = [ "install the updated dependencies ('npm i' / 'pnpm i' / etc) " + '(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' + 'Review the migration guide at https://svelte.dev/docs/svelte/v5-migration-guide', + `Run ${cyan('git diff')} to review changes.` ].filter(Boolean); - tasks.forEach((task, i) => { - console.log(` ${i + 1}: ${task}`); - }); - - console.log(''); - - if (use_git) { - console.log(`Run ${cyan('git diff')} to review changes.\n`); - } + migration_succeeded(tasks); } /** @param {string} name */ diff --git a/packages/migrate/migrations/sveltekit-2/index.js b/packages/migrate/migrations/sveltekit-2/index.js index c62442ab..466233d3 100644 --- a/packages/migrate/migrations/sveltekit-2/index.js +++ b/packages/migrate/migrations/sveltekit-2/index.js @@ -1,12 +1,13 @@ -import colors from 'kleur'; +import pc from 'picocolors'; import fs from 'node:fs'; import process from 'node:process'; -import prompts from 'prompts'; +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, update_tsconfig @@ -28,25 +29,24 @@ export async function migrate() { bail('Please re-run this script in a directory with a svelte.config.js'); } - console.log( - colors - .bold() - .yellow( - '\nThis will update files in the current directory\n' + - "If you're inside a monorepo, run this in individual project directories rather than the workspace root.\n" + 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 prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Continue?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } @@ -57,45 +57,37 @@ export async function migrate() { } if (semver.validRange(svelte_dep) && semver.gtr('4.0.0', svelte_dep)) { - console.log( - colors - .bold() - .yellow( - '\nSvelteKit 2 requires Svelte 4 or newer. We recommend running the `svelte-4` migration first (`npx sv migrate svelte-4`).\n' + p.log.warning( + pc.bold( + pc.yellow( + 'SvelteKit 2 requires Svelte 4 or newer. We recommend running the `svelte-4` migration first (`npx sv migrate svelte-4`).' ) + ) ); - const response = await prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Run `svelte-4` migration now?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } else { await migrate_svelte_4(); - console.log( - colors - .bold() - .green('`svelte-4` migration complete. Continue with `sveltekit-2` migration?\n') + p.log.success( + pc.bold(pc.green('`svelte-4` migration complete. Continue with `sveltekit-2` migration?\n')) ); - const response = await prompts({ - type: 'confirm', - name: 'value', + const response = await p.confirm({ message: 'Continue?', - initial: false + initialValue: false }); - if (!response.value) { + if (p.isCancel(response) || !response) { process.exit(1); } } } - const folders = await prompts({ - type: 'multiselect', - name: 'value', + const folders = await p.multiselect({ message: 'Which folders should be migrated?', - choices: fs + options: fs .readdirSync('.') .filter( (dir) => @@ -107,7 +99,7 @@ export async function migrate() { .map((dir) => ({ title: dir, value: dir, selected: dir === 'src' })) }); - if (!folders.value?.length) { + if (p.isCancel(folders) || !folders?.length) { process.exit(1); } @@ -124,8 +116,8 @@ export async function migrate() { '.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.value.flatMap( + // For some reason {folders.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, '/')) @@ -142,26 +134,16 @@ export async function migrate() { } } - console.log(colors.bold().green('✔ Your project has been migrated')); - - console.log('\nRecommended next steps:\n'); - - const cyan = colors.bold().cyan; + /** @type {(s: string) => string} */ + const cyan = (s) => pc.bold(pc.cyan(s)); const tasks = [ 'Run npm install (or the corresponding installation command of your package manager)', use_git && cyan('git commit -m "migration to SvelteKit 2"'), 'Review the migration guide at https://svelte.dev/docs/kit/migrating-to-sveltekit-2', - 'Read the updated docs at https://svelte.dev/docs/kit' + 'Read the updated docs at https://svelte.dev/docs/kit', + use_git && `Run ${cyan('git diff')} to review changes.` ].filter(Boolean); - tasks.forEach((task, i) => { - console.log(` ${i + 1}: ${task}`); - }); - - console.log(''); - - if (use_git) { - console.log(`Run ${cyan('git diff')} to review changes.\n`); - } + migration_succeeded(tasks); } diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 92111c99..4a1b65ad 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -27,10 +27,10 @@ "svelte-migrate": "./bin.js" }, "dependencies": { + "@clack/prompts": "^0.9.0", "import-meta-resolve": "^4.1.0", - "kleur": "^4.1.5", "magic-string": "^0.30.15", - "prompts": "^2.4.2", + "picocolors": "^1.1.1", "semver": "^7.6.3", "tiny-glob": "^0.2.9", "ts-morph": "^24.0.0", diff --git a/packages/migrate/utils.js b/packages/migrate/utils.js index 944461f0..4fd00665 100644 --- a/packages/migrate/utils.js +++ b/packages/migrate/utils.js @@ -1,4 +1,5 @@ -import colors from 'kleur'; +import * as p from '@clack/prompts'; +import pc from 'picocolors'; import MagicString from 'magic-string'; import { execFileSync, execSync } from 'node:child_process'; import fs from 'node:fs'; @@ -9,7 +10,7 @@ import ts from 'typescript'; /** @param {string} message */ export function bail(message) { - console.error(colors.bold().red(message)); + p.log.error(pc.bold(pc.red(message))); process.exit(1); } @@ -152,15 +153,15 @@ export function check_git() { if (status) { const message = - 'Your git working directory is dirty — we recommend committing your changes before running this migration.\n'; - console.log(colors.bold().red(message)); + 'Your git working directory is dirty — we recommend committing your changes before running this migration.'; + p.log.warning(pc.bold(pc.red(message))); } } catch { // would be weird to have a .git folder if git is not installed, // but always expect the unexpected const message = - 'Could not detect a git installation. If this is unexpected, please raise an issue: https://github.com/sveltejs/kit.\n'; - console.log(colors.bold().red(message)); + 'Could not detect a git installation. If this is unexpected, please raise an issue: https://github.com/sveltejs/cli.\n'; + p.log.warning(pc.bold(pc.red(message))); use_git = false; } } @@ -419,3 +420,25 @@ export function add_named_import(source, _import, method) { }); } } + +/** + * @param {(string | false)[]} next_steps + */ +export function migration_succeeded(next_steps) { + p.log.success(pc.bold(pc.green('✔ Your project has been migrated'))); + + if (!next_steps || next_steps.length === 0) { + return; + } + + /** @type {string[]} */ + const messages = []; + + next_steps.forEach((step, i) => { + if (!step) return; + + messages.push(`${i + 1}: ${step}`); + }); + + p.note(messages.join('\n'), 'Recommended next steps:'); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff7592a3..fa68d69c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -256,18 +256,18 @@ importers: packages/migrate: dependencies: + '@clack/prompts': + specifier: ^0.9.0 + version: 0.9.0 import-meta-resolve: specifier: ^4.1.0 version: 4.1.0 - kleur: - specifier: ^4.1.5 - version: 4.1.5 magic-string: specifier: ^0.30.15 version: 0.30.15 - prompts: - specifier: ^2.4.2 - version: 2.4.2 + picocolors: + specifier: ^1.1.1 + version: 1.1.1 semver: specifier: ^7.6.3 version: 7.6.3 @@ -382,6 +382,12 @@ packages: '@changesets/write@0.3.2': resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} + '@clack/core@0.4.0': + resolution: {integrity: sha512-YJCYBsyJfNDaTbvDUVSJ3SgSuPrcujarRgkJ5NLjexDZKvaOiVVJvAQYx8lIgG0qRT8ff0fPgqyBCVivanIZ+A==} + + '@clack/prompts@0.9.0': + resolution: {integrity: sha512-nGsytiExgUr4FL0pR/LeqxA28nz3E0cW7eLTSh3Iod9TGrbBt8Y7BHbV3mmkNC4G0evdYyQ3ZsbiBkk7ektArA==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -1752,10 +1758,6 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - known-css-properties@0.35.0: resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==} @@ -2038,10 +2040,6 @@ packages: engines: {node: '>=14'} hasBin: true - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - ps-tree@1.2.0: resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} engines: {node: '>= 0.10'} @@ -2668,6 +2666,17 @@ snapshots: human-id: 1.0.2 prettier: 2.8.8 + '@clack/core@0.4.0': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@0.9.0': + dependencies: + '@clack/core': 0.4.0 + picocolors: 1.1.1 + sisteransi: 1.0.5 + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -3921,8 +3930,6 @@ snapshots: kleur@3.0.3: {} - kleur@4.1.5: {} - known-css-properties@0.35.0: {} levn@0.4.1: @@ -4156,11 +4163,6 @@ snapshots: prettier@3.4.2: {} - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - ps-tree@1.2.0: dependencies: event-stream: 3.3.4