diff --git a/packages/nuxi/src/commands/init.ts b/packages/nuxi/src/commands/init.ts index 92ee93b09..767a75848 100644 --- a/packages/nuxi/src/commands/init.ts +++ b/packages/nuxi/src/commands/init.ts @@ -1,11 +1,10 @@ -import type { SelectPromptOptions } from 'consola' import type { DownloadTemplateResult } from 'giget' import type { PackageManagerName } from 'nypm' import { existsSync } from 'node:fs' import process from 'node:process' -import * as clack from '@clack/prompts' +import { box, cancel, confirm, isCancel, multiselect, outro, select, text } from '@clack/prompts' import { defineCommand } from 'citty' import { colors } from 'consola/utils' import { downloadTemplate, startShell } from 'giget' @@ -171,12 +170,18 @@ export default defineCommand({ logger.info(colors.bold(`Welcome to Nuxt!`.split('').map(m => `${themeColor}${m}`).join(''))) if (ctx.args.dir === '') { - ctx.args.dir = await logger.prompt('Where would you like to create your project?', { + const result = await text({ + message: 'Where would you like to create your project?', placeholder: './nuxt-app', - type: 'text', - default: 'nuxt-app', - cancel: 'reject', - }).catch(() => process.exit(1)) + defaultValue: 'nuxt-app', + }) + + if (isCancel(result)) { + cancel('Operation cancelled.') + process.exit(1) + } + + ctx.args.dir = result } const cwd = resolve(ctx.args.cwd) @@ -197,28 +202,41 @@ export default defineCommand({ // when no `--force` flag is provided const shouldVerify = !shouldForce && existsSync(templateDownloadPath) if (shouldVerify) { - const selectedAction = await logger.prompt( - `The directory ${colors.cyan(templateDownloadPath)} already exists. What would you like to do?`, - { - type: 'select', - options: ['Override its contents', 'Select different directory', 'Abort'], - }, - ) + const selectedAction = await select({ + message: `The directory ${colors.cyan(templateDownloadPath)} already exists. What would you like to do?`, + options: [ + { value: 'override', label: 'Override its contents' }, + { value: 'different', label: 'Select different directory' }, + { value: 'abort', label: 'Abort' }, + ], + }) + + if (isCancel(selectedAction)) { + cancel('Operation cancelled.') + process.exit(1) + } switch (selectedAction) { - case 'Override its contents': + case 'override': shouldForce = true break - case 'Select different directory': { - templateDownloadPath = resolve(cwd, await logger.prompt('Please specify a different directory:', { - type: 'text', - cancel: 'reject', - }).catch(() => process.exit(1))) + case 'different': { + const result = await text({ + message: 'Please specify a different directory:', + }) + + if (isCancel(result)) { + cancel('Operation cancelled.') + process.exit(1) + } + + templateDownloadPath = resolve(cwd, result) break } - // 'Abort' or Ctrl+C + // 'Abort' + case 'abort': default: process.exit(1) } @@ -318,15 +336,26 @@ export default defineCommand({ label: pm, value: pm, hint: currentPackageManager === pm ? 'current' : undefined, - } satisfies SelectPromptOptions['options'][number])) - const selectedPackageManager = packageManagerOptions.includes(packageManagerArg) - ? packageManagerArg - : await logger.prompt('Which package manager would you like to use?', { - type: 'select', - options: packageManagerSelectOptions, - initial: currentPackageManager, - cancel: 'reject', - }).catch(() => process.exit(1)) + })) + + let selectedPackageManager: PackageManagerName + if (packageManagerOptions.includes(packageManagerArg)) { + selectedPackageManager = packageManagerArg + } + else { + const result = await select({ + message: 'Which package manager would you like to use?', + options: packageManagerSelectOptions, + initialValue: currentPackageManager, + }) + + if (isCancel(result)) { + cancel('Operation cancelled.') + process.exit(1) + } + + selectedPackageManager = result + } // Install project dependencies // or skip installation based on the '--no-install' flag @@ -357,10 +386,16 @@ export default defineCommand({ } if (ctx.args.gitInit === undefined) { - ctx.args.gitInit = await logger.prompt('Initialize git repository?', { - type: 'confirm', - cancel: 'reject', - }).catch(() => process.exit(1)) + const result = await confirm({ + message: 'Initialize git repository?', + }) + + if (isCancel(result)) { + cancel('Operation cancelled.') + process.exit(1) + } + + ctx.args.gitInit = result } if (ctx.args.gitInit) { logger.info('Initializing git repository...\n') @@ -396,14 +431,15 @@ export default defineCommand({ }[] }>('https://api.nuxt.com/modules') - const wantsUserModules = await logger.prompt( - `Would you like to install any of the official modules?`, - { - initial: false, - type: 'confirm', - cancel: 'reject', - }, - ).catch(() => process.exit(1)) + const wantsUserModules = await confirm({ + message: `Would you like to install any of the official modules?`, + initialValue: false, + }) + + if (isCancel(wantsUserModules)) { + cancel('Operation cancelled.') + process.exit(1) + } if (wantsUserModules) { const [response, templateDeps] = await Promise.all([ @@ -419,19 +455,16 @@ export default defineCommand({ logger.info('All official modules are already included in this template.') } else { - const selectedOfficialModules = await logger.prompt( - 'Pick the modules to install:', - { - type: 'multiselect', - options: officialModules.map(module => ({ - label: `${colors.bold(colors.greenBright(module.npm))} – ${module.description.replace(/\.$/, '')}`, - value: module.npm, - })), - required: false, - }, - ) - - if (selectedOfficialModules === undefined) { + const selectedOfficialModules = await multiselect({ + message: 'Pick the modules to install:', + options: officialModules.map(module => ({ + label: `${colors.bold(colors.greenBright(module.npm))} – ${module.description.replace(/\.$/, '')}`, + value: module.npm, + })), + required: false, + }) + + if (isCancel(selectedOfficialModules)) { process.exit(1) } @@ -467,7 +500,7 @@ export default defineCommand({ await runCommand(addModuleCommand, args) } - logger.log(`\n✨ Nuxt project has been created with the \`${template.name}\` template.\n`) + outro(`✨ Nuxt project has been created with the \`${template.name}\` template.`) // Display next steps const relativeTemplateDir = relative(process.cwd(), template.dir) || '.' @@ -479,7 +512,7 @@ export default defineCommand({ colors.cyan(`${selectedPackageManager} ${runCmd} dev`), ].filter(Boolean) - clack.box(`\n${nextSteps.map(step => ` › ${step}`).join('\n')}\n`, ` 👉 Next steps `, { + box(`\n${nextSteps.map(step => ` › ${step}`).join('\n')}\n`, ` 👉 Next steps `, { contentAlign: 'left', titleAlign: 'left', width: 'auto', diff --git a/packages/nuxi/src/commands/module/add.ts b/packages/nuxi/src/commands/module/add.ts index 3dabe609a..a42a607a0 100644 --- a/packages/nuxi/src/commands/module/add.ts +++ b/packages/nuxi/src/commands/module/add.ts @@ -8,6 +8,7 @@ import { homedir } from 'node:os' import { join } from 'node:path' import process from 'node:process' +import { confirm, isCancel, select } from '@clack/prompts' import { updateConfig } from 'c12/update' import { defineCommand } from 'citty' import { colors } from 'consola/utils' @@ -72,16 +73,12 @@ export default defineCommand({ if (!projectPkg.dependencies?.nuxt && !projectPkg.devDependencies?.nuxt) { logger.warn(`No \`nuxt\` dependency detected in \`${cwd}\`.`) - const shouldContinue = await logger.prompt( - `Do you want to continue anyway?`, - { - type: 'confirm', - initial: false, - cancel: 'default', - }, - ) + const shouldContinue = await confirm({ + message: `Do you want to continue anyway?`, + initialValue: false, + }) - if (shouldContinue !== true) { + if (isCancel(shouldContinue) || shouldContinue !== true) { process.exit(1) } } @@ -146,16 +143,21 @@ async function addModules(modules: ResolvedModule[], { skipInstall, skipConfig, packageManager, workspace: packageManager?.name === 'pnpm' && existsSync(resolve(cwd, 'pnpm-workspace.yaml')), }).then(() => true).catch( - (error) => { + async (error) => { logger.error(error) const failedModulesList = notInstalledModules.map(module => colors.cyan(module.pkg)).join('\`, \`') const s = notInstalledModules.length > 1 ? 's' : '' - return logger.prompt(`Install failed for \`${failedModulesList}\`. Do you want to continue adding the module${s} to ${colors.cyan('nuxt.config')}?`, { - type: 'confirm', - initial: false, - cancel: 'default', + const result = await confirm({ + message: `Install failed for \`${failedModulesList}\`. Do you want to continue adding the module${s} to ${colors.cyan('nuxt.config')}?`, + initialValue: false, }) + + if (isCancel(result)) { + return false + } + + return result }, ) @@ -255,15 +257,11 @@ async function resolveModule(moduleName: string, cwd: string): Promise { - const nuxtVersion: NuxtVersionTag = await logger.prompt( - 'Which nightly Nuxt release channel do you want to install? (3.x or 4.x)', - { - type: 'select', - options: ['3.x', '4.x'] as const, - default: '4.x', - cancel: 'reject', - }, - ).catch(() => process.exit(1)) + const result = await select({ + message: 'Which nightly Nuxt release channel do you want to install?', + options: [ + { value: '3.x' as const, label: '3.x' }, + { value: '4.x' as const, label: '4.x' }, + ], + initialValue: '4.x' as const, + }) + + if (isCancel(result)) { + cancel('Operation cancelled.') + process.exit(1) + } + + const nuxtVersion = result as NuxtVersionTag const npmPackages = packageNames.map(p => getNightlyDependency(p, nuxtVersion)) @@ -141,12 +148,9 @@ export default defineCommand({ let method: 'force' | 'dedupe' | 'skip' | undefined = ctx.args.force ? 'force' : ctx.args.dedupe ? 'dedupe' : undefined - method ||= await logger.prompt( - `Would you like to dedupe your lockfile (recommended) or recreate ${forceRemovals}? This can fix problems with hoisted dependency versions and ensure you have the most up-to-date dependencies.`, - { - type: 'select', - initial: 'dedupe', - cancel: 'reject', + if (!method) { + const result = await select({ + message: `Would you like to dedupe your lockfile (recommended) or recreate ${forceRemovals}? This can fix problems with hoisted dependency versions and ensure you have the most up-to-date dependencies.`, options: [ { label: 'dedupe lockfile', @@ -162,8 +166,16 @@ export default defineCommand({ value: 'skip' as const, }, ], - }, - ).catch(() => process.exit(1)) + initialValue: 'dedupe' as const, + }) + + if (isCancel(result)) { + cancel('Operation cancelled.') + process.exit(1) + } + + method = result + } const versionType = ctx.args.channel === 'nightly' ? 'nightly' : `latest ${ctx.args.channel}` logger.info(`Installing ${versionType} Nuxt ${nuxtVersion} release...`)