1 | import * as fs from 'fs-extra'
|
2 | import * as path from 'path'
|
3 | import { exec } from 'child_process'
|
4 | import * as ora from 'ora'
|
5 | import { shouldUseYarn, shouldUseCnpm, chalk } from '@tarojs/helper'
|
6 |
|
7 | import { getAllFilesInFloder, getPkgVersion } from '../util'
|
8 | import { IProjectConf } from './project'
|
9 | import { IPageConf } from './page'
|
10 | import Creator from './creator'
|
11 |
|
12 | const CONFIG_DIR_NAME = 'config'
|
13 | const TEMPLATE_CREATOR = 'template_creator.js'
|
14 | const PACKAGE_JSON_ALIAS_REG = /\/pkg$/
|
15 |
|
16 | const styleExtMap = {
|
17 | sass: 'scss',
|
18 | less: 'less',
|
19 | stylus: 'styl',
|
20 | none: 'css'
|
21 | }
|
22 |
|
23 | const doNotCopyFiles = [
|
24 | '.DS_Store',
|
25 | '.npmrc',
|
26 | TEMPLATE_CREATOR
|
27 | ]
|
28 |
|
29 | function 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 |
|
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 |
|
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 |
|
92 | if (config.setPageName) {
|
93 | destRePath = config.setPageName
|
94 | }
|
95 | destRePath = destRePath.replace(/^\//, '')
|
96 |
|
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 |
|
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 |
|
123 | export 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 |
|
161 | export 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 |