UNPKG

vibe-cli-tool

Version:

VibeCLI v1.9 - 模板商店生态,智能部署生成,多语言支持,交互式学习系统,企业级Web全栈应用CLI工具

214 lines 9.09 kB
import inquirer from 'inquirer'; import chalk from 'chalk'; import ora from 'ora'; import fs from 'fs-extra'; import path from 'path'; import { execSync } from 'child_process'; import { VibeCLICore } from '../core/vibecli-core.js'; export async function createApp(projectName, options) { // 项目名称校验 if (!/^[a-zA-Z0-9-_]+$/.test(projectName)) { console.error('项目名称无效,名称仅可包含字母、数字、-、_'); process.exit(1); } console.log(chalk.blue.bold('\n🚀 创建Web全栈应用\n')); const projectPath = path.join(process.cwd(), projectName); // spinner 在整个函数作用域中保持可见,便于全局错误处理 let spinner = null; const core = new VibeCLICore(); try { // 提前初始化 spinner 以便任何阶段的错误都能触发 fail spinner = ora(); const projectExists = fs.existsSync(projectPath); // 情景一:目录已存在 -> 先单独询问是否覆盖 let overwrite = false; if (projectExists) { const { overwrite: ow } = await inquirer.prompt([ { type: 'confirm', name: 'overwrite', message: `目录 ${projectName} 已存在,是否覆盖?`, default: false } ]); overwrite = ow === undefined ? true : ow; if (overwrite === false) { console.log(chalk.yellow('❌ 操作已取消')); process.exit(0); return; } // 用户确认覆盖,先删除旧目录 fs.removeSync(projectPath); } // 情景二:目录不存在 -> 在配置提示里包含 overwrite 问题 const configAnswers = await promptForConfig(projectName, options, !projectExists); // 当 includeOverwriteQuestion=true 时,answers 可能包含 overwrite 字段 if (!projectExists) { const ansOverwrite = configAnswers.overwrite; // 如果用户明确选择不覆盖(罕见但测试需要)则退出 if (ansOverwrite === false) { console.log(chalk.yellow('❌ 操作已取消')); process.exit(0); return; } // 目录不存在时,默认不覆盖(overwrite=false),让 createProject 正常生成文件 overwrite = ansOverwrite === undefined ? false : ansOverwrite; } const config = { ...configAnswers, overwrite }; // 如果选择覆盖且目录仍然存在(可能在 projectExists=false 情况),再做一次删除保证干净 if (overwrite && fs.existsSync(projectPath)) { fs.removeSync(projectPath); } try { spinner.start('正在创建项目...'); const result = await core.createProject(config); if (!result.success) { spinner.fail('项目创建失败'); console.error(chalk.red('❌ ' + result.message)); if (result.error) { console.error(chalk.red('详细错误: ' + result.error)); } process.exit(1); return; } spinner.succeed('项目创建成功!'); // 依赖安装与数据库初始化 try { spinner.start('正在安装依赖...'); execSync('npm install', { stdio: 'pipe' }); spinner.succeed('依赖安装完成!'); if (config.database !== 'sqlite') { spinner.start('正在初始化数据库...'); execSync('npx prisma generate', { stdio: 'pipe' }); spinner.succeed('数据库初始化完成!'); } } catch (err) { if (spinner) spinner.fail('项目创建失败'); console.error(err); process.exit(1); return; } // 显示成功信息 console.log(chalk.green.bold('\n✅ 项目创建成功!\n')); console.log(chalk.blue('生成的文件:')); result.generatedFiles.slice(0, 5).forEach(file => { console.log(` ✓ ${file}`); }); if (result.generatedFiles.length > 5) { console.log(` ... 和其他 ${result.generatedFiles.length - 5} 个文件`); } console.log(chalk.blue('\n下一步操作:')); result.nextSteps.forEach(step => { console.log(` ${step}`); }); console.log(chalk.blue('\n其他命令:')); console.log(' npm run build # 构建生产版本'); console.log(' npm run db:studio # 打开数据库管理界面'); console.log(' vibecli add auth # 添加认证功能'); console.log(''); } catch (err) { if (spinner) spinner.fail('项目创建失败'); console.error(err); process.exit(1); } } catch (error) { if (spinner) { spinner.fail('项目创建失败'); } console.error(chalk.red('❌ 创建项目时发生错误:')); console.error(chalk.red(error instanceof Error ? error.message : String(error))); process.exit(1); } } async function promptForConfig(projectName, options, includeOverwriteQuestion = true) { const questions = []; // 项目模板选择 if (!options.template) { questions.push({ type: 'list', name: 'template', message: '选择项目模板:', choices: [ { name: '默认模板 (基础功能)', value: 'default' }, { name: '电商模板 (商品管理)', value: 'ecommerce' }, { name: '博客模板 (内容管理)', value: 'blog' }, { name: '仪表板模板 (数据展示)', value: 'dashboard' }, { name: 'SaaS模板 (多租户)', value: 'saas' } ], default: 'default' }); } // 数据库选择 if (!options.database) { questions.push({ type: 'list', name: 'database', message: '选择数据库:', choices: [ { name: 'SQLite (零配置,推荐)', value: 'sqlite' }, { name: 'PostgreSQL', value: 'postgresql' }, { name: 'MySQL', value: 'mysql' } ], default: 'sqlite' }); } // 功能选择(根据模板调整默认选择) const defaultAuth = options.template === 'auth-system' || (options.template !== 'default' && options.auth !== false); const defaultAdmin = options.template === 'auth-system' || (options.template !== 'default' && options.admin !== false); questions.push({ type: 'checkbox', name: 'features', message: '选择功能模块:', choices: [ { name: '用户认证系统', value: 'auth', checked: defaultAuth }, { name: '管理员面板', value: 'admin', checked: defaultAdmin }, { name: '文件上传', value: 'upload', checked: options.template === 'auth-system' }, { name: '邮件发送', value: 'email', checked: false }, { name: '支付集成', value: 'payment', checked: false }, { name: '实时通信', value: 'realtime', checked: false } ] }); // UI框架选择 questions.push({ type: 'list', name: 'uiFramework', message: '选择UI框架:', choices: [ { name: 'Tailwind CSS + Radix UI (推荐)', value: 'tailwind-radix' }, { name: 'Ant Design', value: 'antd' }, { name: 'Material-UI', value: 'mui' }, { name: 'Chakra UI', value: 'chakra' } ], default: 'tailwind-radix' }); if (includeOverwriteQuestion) { questions.unshift({ type: 'confirm', name: 'overwrite', message: '是否覆盖现有目录?', default: true }); } const answers = await inquirer.prompt(questions); return { name: projectName, template: (options.template || answers.template), database: (options.database || answers.database), uiFramework: (answers.uiFramework || 'tailwind-radix'), features: { auth: options.auth !== false && (answers.features || []).includes('auth'), admin: options.admin !== false && (answers.features || []).includes('admin'), upload: (answers.features || []).includes('upload'), email: (answers.features || []).includes('email'), payment: (answers.features || []).includes('payment'), realtime: (answers.features || []).includes('realtime') }, overwrite: answers.overwrite ?? false }; } //# sourceMappingURL=create.js.map