UNPKG

7.65 kBPlain TextView Raw
1import * as path from 'path'
2import * as fs from 'fs-extra'
3import * as inquirer from 'inquirer'
4import * as semver from 'semver'
5import {
6 DEFAULT_TEMPLATE_SRC,
7 TARO_CONFIG_FLODER,
8 TARO_BASE_CONFIG,
9 getUserHomeDir,
10 chalk,
11 SOURCE_DIR
12} from '@tarojs/helper'
13import { isArray } from '@tarojs/shared'
14
15import { createApp } from './init'
16import fetchTemplate from './fetchTemplate'
17import Creator from './creator'
18
19import type { ITemplates } from './fetchTemplate'
20
21export interface IProjectConf {
22 projectName: string;
23 projectDir: string;
24 templateSource: string;
25 clone?: boolean;
26 template: string;
27 description?: string;
28 typescript?: boolean;
29 css: 'none' | 'sass' | 'stylus' | 'less';
30 date?: string;
31 src?: string;
32 sourceRoot?: string;
33 env?: string;
34 autoInstall?: boolean,
35 framework: 'nerv' | 'react' | 'vue' | 'vue3'
36}
37
38interface AskMethods {
39 (conf: IProjectConf, prompts: object[], choices?: ITemplates[]): void;
40}
41
42export default class Project extends Creator {
43 public rootPath: string
44 public conf: IProjectConf
45
46 constructor (options: IProjectConf) {
47 super(options.sourceRoot)
48 const unSupportedVer = semver.lt(process.version, 'v7.6.0')
49 if (unSupportedVer) {
50 throw new Error('Node.js 版本过低,推荐升级 Node.js 至 v8.0.0+')
51 }
52 this.rootPath = this._rootPath
53
54 this.conf = Object.assign(
55 {
56 projectName: '',
57 projectDir: '',
58 template: '',
59 description: ''
60 },
61 options
62 )
63 }
64
65 init () {
66 console.log(chalk.green('Taro即将创建一个新项目!'))
67 console.log('Need help? Go and open issue: https://github.com/NervJS/taro/issues/new')
68 console.log()
69 }
70
71 create () {
72 this.fetchTemplates()
73 .then((templateChoices: ITemplates[]) => {
74 return this.ask(templateChoices)
75 })
76 .then(answers => {
77 const date = new Date()
78 this.conf = Object.assign(this.conf, answers)
79 this.conf.date = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
80 this.write()
81 })
82 .catch(err => console.log(chalk.red('创建项目失败: ', err)))
83 }
84
85 async fetchTemplates (): Promise<ITemplates[]> {
86 const conf = this.conf
87 // 使用默认模版
88 if (conf.template && conf.template === 'default') {
89 return Promise.resolve([])
90 }
91
92 // 处理模版源取值
93 if (!conf.templateSource) {
94 const homedir = getUserHomeDir()
95 if (!homedir) {
96 chalk.yellow('找不到用户根目录,使用默认模版源!')
97 conf.templateSource = DEFAULT_TEMPLATE_SRC
98 }
99
100 const taroConfigPath = path.join(homedir, TARO_CONFIG_FLODER)
101 const taroConfig = path.join(taroConfigPath, TARO_BASE_CONFIG)
102
103 if (fs.existsSync(taroConfig)) {
104 const config = await fs.readJSON(taroConfig)
105 conf.templateSource = config && config.templateSource ? config.templateSource : DEFAULT_TEMPLATE_SRC
106 } else {
107 await fs.createFile(taroConfig)
108 await fs.writeJSON(taroConfig, { templateSource: DEFAULT_TEMPLATE_SRC })
109 conf.templateSource = DEFAULT_TEMPLATE_SRC
110 }
111 }
112
113 // 从模板源下载模板
114 return fetchTemplate(this.conf.templateSource, this.templatePath(''), this.conf.clone)
115 }
116
117 ask (templateChoices: ITemplates[]) {
118 const prompts: object[] = []
119 const templateChoicesPrompts: object[] = []
120 const conf = this.conf
121
122 this.askProjectName(conf, prompts)
123 this.askDescription(conf, prompts)
124 this.askFramework(conf, prompts)
125 this.askTypescript(conf, prompts)
126 this.askCSS(conf, prompts)
127
128 return inquirer.prompt(prompts).then(answers => {
129 const newTemplateChoices: ITemplates[] = templateChoices
130 .filter(templateChoice => {
131 const { platforms } = templateChoice
132 if (typeof platforms === 'string') {
133 return answers.framework === templateChoice.platforms
134 } else if (isArray(platforms)) {
135 return templateChoice.platforms?.includes(answers.framework)
136 } else {
137 return true
138 }
139 })
140 this.askTemplate(conf, templateChoicesPrompts, newTemplateChoices)
141 return inquirer.prompt(templateChoicesPrompts)
142 .then(templateChoiceAnswer => {
143 return {
144 ...answers,
145 ...templateChoiceAnswer
146 }
147 })
148 })
149 }
150
151 askProjectName: AskMethods = function (conf, prompts) {
152 if ((typeof conf.projectName as string | undefined) !== 'string') {
153 prompts.push({
154 type: 'input',
155 name: 'projectName',
156 message: '请输入项目名称!',
157 validate (input) {
158 if (!input) {
159 return '项目名不能为空!'
160 }
161 if (fs.existsSync(input)) {
162 return '当前目录已经存在同名项目,请换一个项目名!'
163 }
164 return true
165 }
166 })
167 } else if (fs.existsSync(conf.projectName)) {
168 prompts.push({
169 type: 'input',
170 name: 'projectName',
171 message: '当前目录已经存在同名项目,请换一个项目名!',
172 validate (input) {
173 if (!input) {
174 return '项目名不能为空!'
175 }
176 if (fs.existsSync(input)) {
177 return '项目名依然重复!'
178 }
179 return true
180 }
181 })
182 }
183 }
184
185 askDescription: AskMethods = function (conf, prompts) {
186 if (typeof conf.description !== 'string') {
187 prompts.push({
188 type: 'input',
189 name: 'description',
190 message: '请输入项目介绍!'
191 })
192 }
193 }
194
195 askTypescript: AskMethods = function (conf, prompts) {
196 if (typeof conf.typescript !== 'boolean') {
197 prompts.push({
198 type: 'confirm',
199 name: 'typescript',
200 message: '是否需要使用 TypeScript ?'
201 })
202 }
203 }
204
205 askCSS: AskMethods = function (conf, prompts) {
206 const cssChoices = [
207 {
208 name: 'Sass',
209 value: 'sass'
210 },
211 {
212 name: 'Less',
213 value: 'less'
214 },
215 {
216 name: 'Stylus',
217 value: 'stylus'
218 },
219 {
220 name: '无',
221 value: 'none'
222 }
223 ]
224
225 if ((typeof conf.css as string | undefined) !== 'string') {
226 prompts.push({
227 type: 'list',
228 name: 'css',
229 message: '请选择 CSS 预处理器(Sass/Less/Stylus)',
230 choices: cssChoices
231 })
232 }
233 }
234
235 askFramework: AskMethods = function (conf, prompts) {
236 const frameworks = [
237 {
238 name: 'React',
239 value: 'react'
240 },
241 {
242 name: 'Nerv',
243 value: 'nerv'
244 },
245 {
246 name: 'Vue',
247 value: 'vue'
248 },
249 {
250 name: 'Vue3',
251 value: 'vue3'
252 }
253 ]
254
255 if ((typeof conf.framework as string | undefined) !== 'string') {
256 prompts.push({
257 type: 'list',
258 name: 'framework',
259 message: '请选择框架',
260 choices: frameworks
261 })
262 }
263 }
264
265 askTemplate: AskMethods = function (conf, prompts, list = []) {
266 const choices = [
267 {
268 name: '默认模板',
269 value: 'default'
270 },
271 ...list.map(item => ({
272 name: item.desc ? `${item.name}${item.desc})` : item.name,
273 value: item.name
274 }))
275 ]
276
277 if ((typeof conf.template as 'string' | undefined) !== 'string') {
278 prompts.push({
279 type: 'list',
280 name: 'template',
281 message: '请选择模板',
282 choices
283 })
284 }
285 }
286
287 write (cb?: () => void) {
288 this.conf.src = SOURCE_DIR
289 createApp(this, this.conf, cb).catch(err => console.log(err))
290 }
291}