import * as path from 'path' import * as fs from 'fs-extra' import * as inquirer from 'inquirer' import * as semver from 'semver' import * as request from 'request' import * as ora from 'ora' import { TARO_CONFIG_FLODER, TARO_BASE_CONFIG, getUserHomeDir, chalk, SOURCE_DIR } from '@tarojs/helper' import { isArray } from '@tarojs/shared' import { createApp } from './init' import fetchTemplate from './fetchTemplate' import Creator from './creator' import type { ITemplates } from './fetchTemplate' const DEFAULT_TEMPLATE_SRC = "github:agree-amap/agree-templates" const DEFAULT_TEMPLATE_SRC_GITEE = "direct:https://gitee.com/agree-amap/amap-project-templates.git" export interface IProjectConf { projectName: string; projectDir: string; templateSource: string; clone?: boolean; template: string; description?: string; typescript?: boolean; css: 'none' | 'sass' | 'stylus' | 'less'; date?: string; src?: string; sourceRoot?: string; env?: string; autoInstall?: boolean, framework: 'nerv' | 'react' | 'vue' | 'vue3' } interface AskMethods { (conf: IProjectConf, prompts: Record[], choices?: ITemplates[]): void; } export default class Project extends Creator { public rootPath: string public conf: IProjectConf constructor (options: IProjectConf) { super(options.sourceRoot) const unSupportedVer = semver.lt(process.version, 'v7.6.0') if (unSupportedVer) { throw new Error('Node.js 版本过低,推荐升级 Node.js 至 v8.0.0+') } this.rootPath = this._rootPath this.conf = Object.assign( { projectName: '', projectDir: '', template: '', description: '' }, options ) } init () { console.log(chalk.green('AgreeJS 即将创建一个新项目!')) } async create () { try { const answers = await this.ask() const date = new Date() this.conf = Object.assign(this.conf, answers) this.conf.date = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}` this.write() } catch (error) { console.log(chalk.red('创建项目失败: ', error)) } } async ask () { let prompts: Record[] = [] const conf = this.conf this.askProjectName(conf, prompts) this.askDescription(conf, prompts) await this.askTemplateSource(conf, prompts) const answers = await inquirer.prompt(prompts) answers.framework = 'react' answers.typescript = true answers.css = 'sass' prompts = [] const templates = await this.fetchTemplates(answers) await this.askTemplate(conf, prompts, templates) const templateChoiceAnswer = await inquirer.prompt(prompts) return { src: 'src', ...answers, ...templateChoiceAnswer } } askProjectName: AskMethods = function (conf, prompts) { if ((typeof conf.projectName as string | undefined) !== 'string') { prompts.push({ type: 'input', name: 'projectName', message: '请输入项目名称!', validate (input) { if (!input) { return '项目名不能为空!' } if (fs.existsSync(input)) { return '当前目录已经存在同名项目,请换一个项目名!' } return true } }) } else if (fs.existsSync(conf.projectName)) { prompts.push({ type: 'input', name: 'projectName', message: '当前目录已经存在同名项目,请换一个项目名!', validate (input) { if (!input) { return '项目名不能为空!' } if (fs.existsSync(input)) { return '项目名依然重复!' } return true } }) } } askDescription: AskMethods = function (conf, prompts) { if (typeof conf.description !== 'string') { prompts.push({ type: 'input', name: 'description', message: '请输入项目介绍!' }) } } askTemplateSource: AskMethods = async function (conf, prompts) { if (conf.template === 'default' || conf.templateSource) return const homedir = getUserHomeDir() const taroConfigPath = path.join(homedir, TARO_CONFIG_FLODER) const taroConfig = path.join(taroConfigPath, TARO_BASE_CONFIG) let localTemplateSource: string // 检查本地配置 if (fs.existsSync(taroConfig)) { // 存在则把模板源读出来 const config = await fs.readJSON(taroConfig) localTemplateSource = config?.templateSource } else { // 不存在则创建配置 await fs.createFile(taroConfig) await fs.writeJSON(taroConfig, { templateSource: DEFAULT_TEMPLATE_SRC }) localTemplateSource = DEFAULT_TEMPLATE_SRC } const choices = [ { name: 'Gitee(最快)', value: DEFAULT_TEMPLATE_SRC_GITEE }, { name: 'Github(最新)', value: DEFAULT_TEMPLATE_SRC } ] prompts.push({ type: 'list', name: 'templateSource', message: '请选择模板源', choices }) } async fetchTemplates (answers): Promise { const { templateSource, framework } = answers this.conf.templateSource = this.conf.templateSource || templateSource // 从模板源下载模板 const isClone = /gitee/.test(this.conf.templateSource) || this.conf.clone const templateChoices = await fetchTemplate(this.conf.templateSource, this.templatePath(''), isClone) // 根据用户选择的框架筛选模板 const newTemplateChoices: ITemplates[] = templateChoices .filter(templateChoice => { const { platforms } = templateChoice if (typeof platforms === 'string' && platforms) { return framework === templateChoice.platforms } else if (isArray(platforms)) { return templateChoice.platforms?.includes(framework) } else { return true } }) return newTemplateChoices } askTemplate: AskMethods = function (conf, prompts, list = []) { const choices = [ { name: '默认模板', value: 'default' }, ...list.map(item => ({ name: item.desc ? `${item.name}(${item.desc})` : item.name, value: item.name })) ] if ((typeof conf.template as 'string' | undefined) !== 'string') { prompts.push({ type: 'list', name: 'template', message: '请选择模板', choices }) } } write (cb?: () => void) { this.conf.src = SOURCE_DIR createApp(this, this.conf, cb).catch(err => console.log(err)) } }