UNPKG

8.22 kBPlain TextView Raw
1import * as fs from 'fs-extra'
2import * as path from 'path'
3import { exec } from 'child_process'
4import * as ora from 'ora'
5import { shouldUseYarn, shouldUseCnpm, chalk } from '@tarojs/helper'
6
7import { getAllFilesInFloder, getPkgVersion } from '../util'
8import { IProjectConf } from './project'
9import { IPageConf } from './page'
10import Creator from './creator'
11
12const CONFIG_DIR_NAME = 'config'
13const TEMPLATE_CREATOR = 'template_creator.js'
14const PACKAGE_JSON_ALIAS_REG = /\/pkg$/
15
16const styleExtMap = {
17 sass: 'scss',
18 less: 'less',
19 stylus: 'styl',
20 none: 'css'
21}
22
23const doNotCopyFiles = [
24 '.DS_Store',
25 '.npmrc',
26 TEMPLATE_CREATOR
27]
28
29function createFiles (
30 creater: Creator,
31 files: string[],
32 handler,
33 options: (IProjectConf | IPageConf) & {
34 templatePath: string,
35 projectPath: string,
36 pageName: string,
37 period: string,
38 version?: string
39 }
40): string[] {
41 const {
42 description,
43 projectName,
44 version,
45 css,
46 date,
47 typescript,
48 template,
49 templatePath,
50 projectPath,
51 pageName
52 } = options
53 const logs: string[] = []
54 // 模板库模板,直接创建,不需要改后缀
55 const globalChangeExt = Boolean(handler)
56 const currentStyleExt = styleExtMap[css] || 'css'
57
58 files.forEach(file => {
59 // fileRePath startsWith '/'
60 const fileRePath = file.replace(templatePath, '').replace(new RegExp(`\\${path.sep}`, 'g'), '/')
61 let externalConfig: any = null
62
63 // 跑自定义逻辑,确定是否创建此文件
64 if (handler && typeof handler[fileRePath] === 'function') {
65 externalConfig = handler[fileRePath](options)
66 if (!externalConfig) return
67 }
68
69 let changeExt = globalChangeExt
70 if (externalConfig && typeof externalConfig === 'object') {
71 if (externalConfig.changeExt === false) {
72 changeExt = false
73 }
74 }
75
76 // 合并自定义 config
77 const config = Object.assign({}, {
78 description,
79 projectName,
80 version,
81 css,
82 cssExt: currentStyleExt,
83 date,
84 typescript,
85 template,
86 pageName
87 }, externalConfig)
88
89 let destRePath = fileRePath
90
91 // createPage 创建页面模式
92 if (config.setPageName) {
93 destRePath = config.setPageName
94 }
95 destRePath = destRePath.replace(/^\//, '')
96 // 处理 .js 和 .css 的后缀
97 if (
98 typescript &&
99 changeExt &&
100 !destRePath.startsWith(`${CONFIG_DIR_NAME}`) &&
101 (path.extname(destRePath) === '.js' || path.extname(destRePath) === '.jsx')
102 ) {
103 destRePath = destRePath.replace('.js', '.ts')
104 }
105 if (changeExt && path.extname(destRePath).includes('.css')) {
106 destRePath = destRePath.replace('.css', `.${currentStyleExt}`)
107 }
108
109 let dest = path.join(projectPath, destRePath)
110
111 // 兼容 Nodejs 13+ 调用 require 时 package.json 格式不能非法
112 if (PACKAGE_JSON_ALIAS_REG.test(fileRePath)) {
113 dest = path.join(projectPath, fileRePath.replace(PACKAGE_JSON_ALIAS_REG, '/package.json'))
114 }
115
116 // 创建
117 creater.template(template, fileRePath, dest, config)
118 logs.push(`${chalk.green('✔ ')}${chalk.grey(`创建文件: ${path.join(projectName, destRePath)}`)}`)
119 })
120 return logs
121}
122
123export async function createPage (
124 creater: Creator,
125 params: IPageConf,
126 cb
127) {
128 const {
129 projectDir,
130 template,
131 pageName
132 } = params
133 // path
134 const templatePath = creater.templatePath(template)
135
136 if (!fs.existsSync(templatePath)) return console.log(chalk.red(`创建页面错误:找不到模板${templatePath}`))
137
138 // 引入模板编写者的自定义逻辑
139 const handlerPath = path.join(templatePath, TEMPLATE_CREATOR)
140 const basePageFiles = fs.existsSync(handlerPath) ? require(handlerPath).basePageFiles : []
141 const files = Array.isArray(basePageFiles) ? basePageFiles : []
142 const handler = fs.existsSync(handlerPath) ? require(handlerPath).handler : null
143
144 const logs = createFiles(creater, files, handler, {
145 ...params,
146 templatePath,
147 projectPath: projectDir,
148 pageName,
149 period: 'createPage'
150 })
151
152 creater.fs.commit(() => {
153 // logs
154 console.log()
155 logs.forEach(log => console.log(log))
156 console.log()
157 typeof cb === 'function' && cb()
158 })
159}
160
161export async function createApp (
162 creater: Creator,
163 params: IProjectConf,
164 cb
165) {
166 const {
167 projectName,
168 projectDir,
169 template,
170 env,
171 autoInstall = true
172 } = params
173 const logs: string[] = []
174 // path
175 const templatePath = creater.templatePath(template)
176 const projectPath = path.join(projectDir, projectName)
177
178 // default 模板发布 npm 会滤掉 '.' 开头的文件,因此改为 '_' 开头,这里先改回来。
179 if (env !== 'test' && template === 'default') {
180 const files = await fs.readdir(templatePath)
181 const renames = files
182 .map(file => {
183 const filePath = path.join(templatePath, file)
184 if (fs.statSync(filePath).isFile() && file.startsWith('_')) {
185 return fs.rename(filePath, path.join(templatePath, file.replace(/^_/, '.')))
186 }
187 return Promise.resolve()
188 })
189
190 await Promise.all(renames)
191 }
192
193 // npm & yarn
194 const version = getPkgVersion()
195 const isShouldUseYarn = shouldUseYarn()
196 const useNpmrc = !isShouldUseYarn
197 const yarnLockfilePath = path.join('yarn-lockfiles', `${version}-yarn.lock`)
198 const useYarnLock = isShouldUseYarn && fs.existsSync(creater.templatePath(template, yarnLockfilePath))
199
200 if (useNpmrc) {
201 creater.template(template, '.npmrc', path.join(projectPath, '.npmrc'))
202 logs.push(`${chalk.green('✔ ')}${chalk.grey(`创建文件: ${projectName}${path.sep}.npmrc`)}`)
203 }
204 if (useYarnLock) {
205 creater.template(template, yarnLockfilePath, path.join(projectPath, 'yarn.lock'))
206 logs.push(`${chalk.green('✔ ')}${chalk.grey(`创建文件: ${projectName}${path.sep}yarn.lock`)}`)
207 }
208
209 // 遍历出模板中所有文件
210 const files = await getAllFilesInFloder(templatePath, doNotCopyFiles)
211
212 // 引入模板编写者的自定义逻辑
213 const handlerPath = path.join(templatePath, TEMPLATE_CREATOR)
214 const handler = fs.existsSync(handlerPath) ? require(handlerPath).handler : null
215
216 // 为所有文件进行创建
217 logs.push(
218 ...createFiles(creater, files, handler, {
219 ...params,
220 version,
221 templatePath,
222 projectPath,
223 pageName: 'index',
224 period: 'createApp'
225 })
226 )
227
228 // fs commit
229 creater.fs.commit(() => {
230 // logs
231 console.log()
232 console.log(`${chalk.green('✔ ')}${chalk.grey(`创建项目: ${chalk.grey.bold(projectName)}`)}`)
233 logs.forEach(log => console.log(log))
234 console.log()
235
236 // git init
237 const gitInitSpinner = ora(`cd ${chalk.cyan.bold(projectName)}, 执行 ${chalk.cyan.bold('git init')}`).start()
238 process.chdir(projectPath)
239 const gitInit = exec('git init')
240 gitInit.on('close', code => {
241 if (code === 0) {
242 gitInitSpinner.color = 'green'
243 gitInitSpinner.succeed(gitInit.stdout.read())
244 } else {
245 gitInitSpinner.color = 'red'
246 gitInitSpinner.fail(gitInit.stderr.read())
247 }
248 })
249
250 const callSuccess = () => {
251 console.log(chalk.green(`创建项目 ${chalk.green.bold(projectName)} 成功!`))
252 console.log(chalk.green(`请进入项目目录 ${chalk.green.bold(projectName)} 开始工作吧!😝`))
253 if (typeof cb === 'function') {
254 cb()
255 }
256 }
257
258 if (autoInstall) {
259 // packages install
260 let command: string
261 if (isShouldUseYarn) {
262 command = 'yarn install'
263 } else if (shouldUseCnpm()) {
264 command = 'cnpm install'
265 } else {
266 command = 'npm install'
267 }
268 const installSpinner = ora(`执行安装项目依赖 ${chalk.cyan.bold(command)}, 需要一会儿...`).start()
269 exec(command, (error, stdout, stderr) => {
270 if (error) {
271 installSpinner.color = 'red'
272 installSpinner.fail(chalk.red('安装项目依赖失败,请自行重新安装!'))
273 console.log(error)
274 } else {
275 installSpinner.color = 'green'
276 installSpinner.succeed('安装成功')
277 console.log(`${stderr}${stdout}`)
278 }
279 callSuccess()
280 })
281 } else {
282 callSuccess()
283 }
284 })
285}
286
\No newline at end of file