UNPKG

@tanstack/cli

Version:
284 lines (283 loc) 9.14 kB
import { cancel, confirm, isCancel, multiselect, note, password, select, text, } from '@clack/prompts'; import { DEFAULT_PACKAGE_MANAGER, SUPPORTED_PACKAGE_MANAGERS, getAllAddOns, } from '@tanstack/create'; import { validateProjectName } from './utils.js'; export async function getProjectName() { const value = await text({ message: 'What would you like to name your project?', defaultValue: 'my-app', validate(value) { if (!value) { return 'Please enter a name'; } const { valid, error } = validateProjectName(value); if (!valid) { return error; } }, }); if (isCancel(value)) { cancel('Operation cancelled.'); process.exit(0); } return value; } export async function selectPackageManager() { const packageManager = await select({ message: 'Select package manager:', options: SUPPORTED_PACKAGE_MANAGERS.map((pm) => ({ value: pm, label: pm, })), initialValue: DEFAULT_PACKAGE_MANAGER, }); if (isCancel(packageManager)) { cancel('Operation cancelled.'); process.exit(0); } return packageManager; } export async function selectTemplate(templates) { if (templates.length === 0) { return undefined; } const selected = await select({ message: 'Would you like to start from a template?', options: [ { value: undefined, label: 'None (base starter)', hint: 'Two-page baseline (Home + About)', }, ...templates.map((template) => ({ value: template.id, label: template.name, hint: template.description, })), ], initialValue: undefined, }); if (isCancel(selected)) { cancel('Operation cancelled.'); process.exit(0); } return selected; } // Track if we've shown the multiselect help text let hasShownMultiselectHelp = false; export async function selectAddOns(framework, mode, type, message, forcedAddOns = [], allowMultiple = true) { const allAddOns = await getAllAddOns(framework, mode); const addOns = allAddOns.filter((addOn) => addOn.type === type); if (addOns.length === 0) { return []; } // Show help text only once if (!hasShownMultiselectHelp) { note('Use ↑/↓ to navigate • Space to select/deselect • Enter to confirm', 'Keyboard Shortcuts'); hasShownMultiselectHelp = true; } if (allowMultiple) { const selectableAddOns = addOns.filter((addOn) => !forcedAddOns.includes(addOn.id)); if (selectableAddOns.length === 0) { return []; } const value = await multiselect({ message, options: selectableAddOns.map((addOn) => ({ value: addOn.id, label: addOn.name, hint: addOn.description, })), maxItems: selectableAddOns.length, required: false, }); if (isCancel(value)) { cancel('Operation cancelled.'); process.exit(0); } return value; } else { const value = await select({ message, options: [ { value: 'none', label: 'None', }, ...addOns .filter((addOn) => !forcedAddOns.includes(addOn.id)) .map((addOn) => ({ value: addOn.id, label: addOn.name, hint: addOn.description, })), ], initialValue: 'none', }); if (isCancel(value)) { cancel('Operation cancelled.'); process.exit(0); } return value === 'none' ? [] : [value]; } } export async function selectGit() { const git = await confirm({ message: 'Would you like to initialize a new git repository?', initialValue: true, }); if (isCancel(git)) { cancel('Operation cancelled.'); process.exit(0); } return git; } export async function selectExamples() { const includeExamples = await confirm({ message: 'Would you like to include demo/example pages?', initialValue: true, }); if (isCancel(includeExamples)) { cancel('Operation cancelled.'); process.exit(0); } return includeExamples; } export async function selectToolchain(framework, toolchain) { if (toolchain === false) { return undefined; } const toolchains = new Set(); for (const addOn of framework.getAddOns()) { if (addOn.type === 'toolchain') { toolchains.add(addOn); if (toolchain && addOn.id === toolchain) { return toolchain; } } } const tc = await select({ message: 'Select toolchain', options: [ { value: undefined, label: 'None', }, ...Array.from(toolchains).map((tc) => ({ value: tc.id, label: tc.name, })), ], initialValue: undefined, }); if (isCancel(tc)) { cancel('Operation cancelled.'); process.exit(0); } return tc; } export async function promptForAddOnOptions(addOnIds, framework) { const addOnOptions = {}; for (const addOnId of addOnIds) { const addOn = framework.getAddOns().find((a) => a.id === addOnId); if (!addOn || !addOn.options) continue; addOnOptions[addOnId] = {}; for (const [optionName, option] of Object.entries(addOn.options)) { if (option && typeof option === 'object' && 'type' in option) { if (option.type === 'select') { const selectOption = option; const value = await select({ message: `${addOn.name}: ${selectOption.label}`, options: selectOption.options.map((opt) => ({ value: opt.value, label: opt.label, })), initialValue: selectOption.default, }); if (isCancel(value)) { cancel('Operation cancelled.'); process.exit(0); } addOnOptions[addOnId][optionName] = value; } // Future option types can be added here } } } return addOnOptions; } export async function promptForEnvVars(addOns) { const envVars = new Map(); for (const addOn of addOns) { for (const envVar of addOn.envVars || []) { if (!envVars.has(envVar.name)) { envVars.set(envVar.name, envVar); } } } const result = {}; for (const envVar of envVars.values()) { const label = envVar.description ? `${envVar.name} (${envVar.description})` : envVar.name; const value = envVar.secret ? await password({ message: `Enter ${label}`, validate: envVar.required ? (v) => v && v.trim().length > 0 ? undefined : `${envVar.name} is required` : undefined, }) : await text({ message: `Enter ${label}`, defaultValue: envVar.default, validate: envVar.required ? (v) => v && v.trim().length > 0 ? undefined : `${envVar.name} is required` : undefined, }); if (isCancel(value)) { cancel('Operation cancelled.'); process.exit(0); } if (value && value.trim()) { result[envVar.name] = value.trim(); } } return result; } export async function selectDeployment(framework, deployment) { const deployments = new Set(); let initialValue = undefined; for (const addOn of framework .getAddOns() .sort((a, b) => a.name.localeCompare(b.name))) { if (addOn.type === 'deployment') { deployments.add(addOn); if (deployment && addOn.id === deployment) { return deployment; } if (addOn.default) { initialValue = addOn.id; } } } const dp = await select({ message: 'Select deployment adapter', options: [ ...Array.from(deployments).map((d) => ({ value: d.id, label: d.name, })), ], initialValue: initialValue, }); if (isCancel(dp)) { cancel('Operation cancelled.'); process.exit(0); } return dp; }