@john-cli/init 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +0 -0
- package/README.md +11 -0
- package/lib/getProjectTemplate.js +9 -0
- package/lib/index.js +362 -0
- package/package.json +38 -0
package/LICENSE.md
ADDED
|
File without changes
|
package/README.md
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const inquirer = require('inquirer')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const fse = require('fs-extra')
|
|
7
|
+
const semver = require('semver')
|
|
8
|
+
const userHome = require('user-home')
|
|
9
|
+
const Command = require('@john-cli/command')
|
|
10
|
+
const log = require('@john-cli/log')
|
|
11
|
+
const Package = require('@john-cli/package')
|
|
12
|
+
const { spinnerStart, sleep, execAsync } = require('@john-cli/utils')
|
|
13
|
+
|
|
14
|
+
const getProjectTemplate = require('./getProjectTemplate')
|
|
15
|
+
|
|
16
|
+
const TYPE_PROJECT = 'project'
|
|
17
|
+
const TYPE_COMPONENT = 'component'
|
|
18
|
+
|
|
19
|
+
const TEMPLATE_TYPE_NORMAL = 'normal' // 标准模板
|
|
20
|
+
const TEMPLATE_TYPE_CUSTOM = 'custom' // 定义模板
|
|
21
|
+
|
|
22
|
+
const WHITE_CMD = ['npm', 'cnpm']
|
|
23
|
+
|
|
24
|
+
class InitCommand extends Command {
|
|
25
|
+
constructor(argv) {
|
|
26
|
+
super(argv)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
init () {
|
|
30
|
+
this.projectName = this._argv[0] || '';
|
|
31
|
+
const opts = this._cmd._optionValues
|
|
32
|
+
// console.log('opts', opts)
|
|
33
|
+
this.force = !!opts.force
|
|
34
|
+
// console.log('argv:', this._cmd._optionValues)
|
|
35
|
+
log.verbose(this.projectName, this.force)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async exec () {
|
|
39
|
+
/**
|
|
40
|
+
* 1.准备阶段
|
|
41
|
+
* 2.下载模板
|
|
42
|
+
* 3.安装模板
|
|
43
|
+
*/
|
|
44
|
+
try {
|
|
45
|
+
const projectInfo = await this.prepare()
|
|
46
|
+
log.verbose('projectInfo', projectInfo)
|
|
47
|
+
this.projectInfo = projectInfo
|
|
48
|
+
if (projectInfo.type === TYPE_PROJECT) {
|
|
49
|
+
// 1.下载模板
|
|
50
|
+
await this.downloadTemplate()
|
|
51
|
+
// 2.安装模板
|
|
52
|
+
await this.installTemplate()
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
log.error(e.message)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async installTemplate () {
|
|
60
|
+
// 判断当前模板是否包含template信息
|
|
61
|
+
if (this.templateInfo) {
|
|
62
|
+
if (!this.templateInfo.type) {
|
|
63
|
+
this.templateInfo.type = TEMPLATE_TYPE_NORMAL
|
|
64
|
+
}
|
|
65
|
+
if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
|
|
66
|
+
// 标准安装
|
|
67
|
+
await this.installNormalTemplate()
|
|
68
|
+
} else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
|
|
69
|
+
// 自定义安装
|
|
70
|
+
await this.installCustomTemplate()
|
|
71
|
+
} else {
|
|
72
|
+
// 未知类型
|
|
73
|
+
throw new Error('项目模板无法识别!')
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
throw new Error('项目模板信息不存在!')
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
checkCommand (cmd) {
|
|
81
|
+
if (WHITE_CMD.includes(cmd)) {
|
|
82
|
+
return cmd
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async execCommand (command, errMsg) {
|
|
88
|
+
let ret
|
|
89
|
+
if (command) {
|
|
90
|
+
const cmdArr = command.split(' ') // npm install or npm run serve
|
|
91
|
+
const cmd = this.checkCommand(cmdArr[0])
|
|
92
|
+
if (!cmd) {
|
|
93
|
+
throw new Error(errMsg)
|
|
94
|
+
}
|
|
95
|
+
const args = cmdArr.slice(1)
|
|
96
|
+
ret = await execAsync(cmd,args, {
|
|
97
|
+
stdio: 'inherit',
|
|
98
|
+
cwd: process.cwd()
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
if (ret !== 0) {
|
|
102
|
+
throw new Error('安装依赖过程失败!')
|
|
103
|
+
}
|
|
104
|
+
return ret
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async installNormalTemplate() {
|
|
108
|
+
console.log('templateNpm22:', this.templateNpm)
|
|
109
|
+
// 拷贝模板代码至当前目录
|
|
110
|
+
let spinner = spinnerStart('正在安装模板...')
|
|
111
|
+
await sleep()
|
|
112
|
+
try {
|
|
113
|
+
const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template')
|
|
114
|
+
const targetPath = process.cwd()
|
|
115
|
+
// fse.ensureDirSync 确保目录存在,不存在则会创建
|
|
116
|
+
fse.ensureDirSync(templatePath)
|
|
117
|
+
fse.ensureDirSync(targetPath)
|
|
118
|
+
fse.copySync(templatePath, targetPath)
|
|
119
|
+
} catch (e) {
|
|
120
|
+
throw e
|
|
121
|
+
} finally {
|
|
122
|
+
spinner.stop(true)
|
|
123
|
+
log.success('模板安装成功')
|
|
124
|
+
}
|
|
125
|
+
const { installCommand, startCommand } = this.templateInfo
|
|
126
|
+
// 依赖安装 npm install
|
|
127
|
+
await this.execCommand(installCommand, '安装依赖过程失败!')
|
|
128
|
+
// 启动命令 npm run serve
|
|
129
|
+
await this.execCommand(startCommand, '启动过程失败!')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async installCustomTemplate() {
|
|
133
|
+
console.log('安装自定义模板', this.templateNpm)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async downloadTemplate () {
|
|
137
|
+
// 1.通过项目模板API获取项目模板信息
|
|
138
|
+
// 1.1.通过egg.js搭建一套后端系统
|
|
139
|
+
// 1.2.通过npm存储项目模板
|
|
140
|
+
// 1.3.将项目模板信息存储到mongodb数据库
|
|
141
|
+
// 1.4.通过egg.js获取mongodb中的数据并且通过API返回
|
|
142
|
+
// console.log(this.projectInfo, this.template)
|
|
143
|
+
const { projectTemplate } = this.projectInfo
|
|
144
|
+
const templateInfo = this.template.find((item) => {
|
|
145
|
+
return item.npmName === projectTemplate
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const targetPath = path.resolve(userHome, '.john-cli', 'template') // 缓存项目模板
|
|
149
|
+
const storeDir = path.resolve(userHome, '.john-cli', 'template', 'node_modules') // 实际存储的目录
|
|
150
|
+
const { npmName, version } = templateInfo
|
|
151
|
+
this.templateInfo = templateInfo
|
|
152
|
+
const templateNpm = new Package({
|
|
153
|
+
targetPath,
|
|
154
|
+
storeDir,
|
|
155
|
+
packageName: npmName,
|
|
156
|
+
packageVersion: version
|
|
157
|
+
})
|
|
158
|
+
// console.log(targetPath, storeDir, npmName, version, 'templateNpm::',templateNpm)
|
|
159
|
+
if (!await templateNpm.exists()) {
|
|
160
|
+
const spinner = spinnerStart('正在下载模板')
|
|
161
|
+
await sleep()
|
|
162
|
+
try {
|
|
163
|
+
await templateNpm.install()
|
|
164
|
+
} catch (e) {
|
|
165
|
+
throw e
|
|
166
|
+
} finally {
|
|
167
|
+
spinner.stop(true)
|
|
168
|
+
if (await templateNpm.exists()) {
|
|
169
|
+
log.success('模板下载成功')
|
|
170
|
+
this.templateNpm = templateNpm
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
const spinner = spinnerStart('正在更新模板')
|
|
175
|
+
await sleep()
|
|
176
|
+
try {
|
|
177
|
+
await templateNpm.update()
|
|
178
|
+
} catch (e) {
|
|
179
|
+
throw e
|
|
180
|
+
} finally {
|
|
181
|
+
spinner.stop(true)
|
|
182
|
+
if (await templateNpm.exists()) {
|
|
183
|
+
log.success('模板更新成功')
|
|
184
|
+
this.templateNpm = templateNpm
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async prepare () {
|
|
191
|
+
// 0.判断项目模板是否存在
|
|
192
|
+
const template = await getProjectTemplate()
|
|
193
|
+
// console.log('template',template)
|
|
194
|
+
|
|
195
|
+
if (!template || template.length === 0) {
|
|
196
|
+
throw new Error('项目模板不存在')
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.template = template
|
|
200
|
+
const localPath = process.cwd() // 当前命令执行的目录
|
|
201
|
+
// 1.当前目录是否为空
|
|
202
|
+
// console.log(__dirname) // 当前代码文件所胡的目录地址
|
|
203
|
+
// console.log(path.resolve('.'))
|
|
204
|
+
// const ret = this.cwdIsEmpty()
|
|
205
|
+
// console.log('ret:',ret)
|
|
206
|
+
|
|
207
|
+
// 当前目录是否为空
|
|
208
|
+
if (!this.isDirEmpty(localPath)) {
|
|
209
|
+
let ifContinue = false
|
|
210
|
+
// 2.询问是否继续创建
|
|
211
|
+
if (!this.force) {
|
|
212
|
+
ifContinue = (await inquirer.prompt({
|
|
213
|
+
type: 'confirm',
|
|
214
|
+
name: 'ifContinue',
|
|
215
|
+
message: '当前文件夹不为空,是否继续创建项目?',
|
|
216
|
+
default: false
|
|
217
|
+
})).ifContinue;
|
|
218
|
+
// 不继续创建,终止流程
|
|
219
|
+
if (!ifContinue) {
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// console.log('ifContinue:',ifContinue)
|
|
224
|
+
// 3.是否强制更新
|
|
225
|
+
if (ifContinue || this.force) {
|
|
226
|
+
// 给用户做二次确认
|
|
227
|
+
const { confirmDelete } = await inquirer.prompt({
|
|
228
|
+
type: 'confirm',
|
|
229
|
+
name: 'confirmDelete',
|
|
230
|
+
message: '是否确认清空当前目录下的文件?',
|
|
231
|
+
default: false
|
|
232
|
+
})
|
|
233
|
+
if (confirmDelete) {
|
|
234
|
+
// 清空当前目录
|
|
235
|
+
fse.emptyDirSync(localPath)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// 4.强制?清空当前目录:''
|
|
240
|
+
// throw new Error('出错了')
|
|
241
|
+
return this.getProjectInfo()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async getProjectInfo () {
|
|
245
|
+
let projectInfo = {}
|
|
246
|
+
// 1.获取项目类型(项目/组件)
|
|
247
|
+
const { type } = await inquirer.prompt({
|
|
248
|
+
type: 'list',
|
|
249
|
+
name: 'type',
|
|
250
|
+
message: '请选择初始化类型',
|
|
251
|
+
default: TYPE_PROJECT,
|
|
252
|
+
choices: [{
|
|
253
|
+
name: '项目',
|
|
254
|
+
value: TYPE_PROJECT
|
|
255
|
+
},{
|
|
256
|
+
name: '组件',
|
|
257
|
+
value: TYPE_COMPONENT
|
|
258
|
+
}]
|
|
259
|
+
})
|
|
260
|
+
log.verbose('type', type)
|
|
261
|
+
// 2.获取项目基本信息(名称、版本号、描述)
|
|
262
|
+
if (type === TYPE_PROJECT) {
|
|
263
|
+
// 获取项目的基本信息
|
|
264
|
+
const project = await inquirer.prompt([{
|
|
265
|
+
type: 'input',
|
|
266
|
+
name: 'projectName',
|
|
267
|
+
message: '请输入项目的名称',
|
|
268
|
+
default: '',
|
|
269
|
+
validate: function (v) {
|
|
270
|
+
// 限制输入规则
|
|
271
|
+
// 1.输入的首字符必须为英文字符
|
|
272
|
+
// 2.尾字符必须为英文或数字,不能为字符
|
|
273
|
+
// 3.字符仅允许"-_"
|
|
274
|
+
// a-b a_b aaabbb 不能出现 a_ a-
|
|
275
|
+
// 合法:a,a-b,a_b,a-b-c,a_b_c,a-b1-c1, a_b1_c1
|
|
276
|
+
// 不合法:1, a_, a-, a_1, a-1
|
|
277
|
+
// return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v)
|
|
278
|
+
const done = this.async()
|
|
279
|
+
setTimeout(function() {
|
|
280
|
+
if (!/^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v)) {
|
|
281
|
+
done('请输入合法的项目名称(a-zA-Z0-9 or a_zA_Z0_9)')
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
done(null, true)
|
|
285
|
+
}, 0);
|
|
286
|
+
},
|
|
287
|
+
filter: function (v) {
|
|
288
|
+
return v
|
|
289
|
+
}
|
|
290
|
+
},{
|
|
291
|
+
type: 'input',
|
|
292
|
+
name: 'projectVersion',
|
|
293
|
+
message: '请输入项目版本号',
|
|
294
|
+
default: '1.0.0',
|
|
295
|
+
validate: function (v) {
|
|
296
|
+
const done = this.async()
|
|
297
|
+
setTimeout(function() {
|
|
298
|
+
if (!(!!semver.valid(v))) {
|
|
299
|
+
done('请输入合法的版本号(如:v1.0.0 or 1.0.0)')
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
done(null, true)
|
|
303
|
+
}, 0);
|
|
304
|
+
},
|
|
305
|
+
filter: function (v) {
|
|
306
|
+
if (!!semver.valid(v)) {
|
|
307
|
+
return semver.valid(v)
|
|
308
|
+
} else {
|
|
309
|
+
return v
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
},{
|
|
313
|
+
type: 'list',
|
|
314
|
+
name: 'projectTemplate',
|
|
315
|
+
message: '请选择项目模板',
|
|
316
|
+
choices: this.createTemplateChoice()
|
|
317
|
+
}
|
|
318
|
+
])
|
|
319
|
+
projectInfo = {
|
|
320
|
+
type,
|
|
321
|
+
...project
|
|
322
|
+
}
|
|
323
|
+
} else if (type === TYPE_COMPONENT) {
|
|
324
|
+
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 生成className
|
|
328
|
+
if (projectInfo.projectName) {
|
|
329
|
+
projectInfo.className = require('kebab-case')(projectInfo.projectName).replace(/^-/,'')
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// return 项目的基本信息(Object)
|
|
333
|
+
return projectInfo
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
createTemplateChoice () {
|
|
337
|
+
return this.template.map((item) => {
|
|
338
|
+
return {
|
|
339
|
+
value: item.npmName,
|
|
340
|
+
name: item.name
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
isDirEmpty (localPath) {
|
|
346
|
+
let fileList = fs.readdirSync(localPath) // 当前目录下的所有文件
|
|
347
|
+
// 文件过滤逻辑
|
|
348
|
+
fileList = fileList.filter((file) => {
|
|
349
|
+
return !file.startsWith('.') && !['node_modules'].includes(file)
|
|
350
|
+
})
|
|
351
|
+
return !fileList || fileList.length <= 0
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function init(argv) {
|
|
357
|
+
// console.log('init', projectName, cmdObj.force, process.env.CLI_TARGET_PATH)
|
|
358
|
+
return new InitCommand(argv);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
module.exports = init;
|
|
362
|
+
module.exports.InitCommand = InitCommand;
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@john-cli/init",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "john-cli init",
|
|
5
|
+
"author": "tanhongjian <350089447@qq.com>",
|
|
6
|
+
"homepage": "",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"main": "lib/index.js",
|
|
9
|
+
"directories": {
|
|
10
|
+
"lib": "lib",
|
|
11
|
+
"test": "__tests__"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"lib"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://gitee.com/boshilang/john-cli.git"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@john-cli/command": "file:../../mocels/command",
|
|
28
|
+
"@john-cli/log": "file:../../log/log",
|
|
29
|
+
"@john-cli/package": "^1.0.7",
|
|
30
|
+
"@john-cli/request": "^1.0.7",
|
|
31
|
+
"@john-cli/utils": "^1.0.7",
|
|
32
|
+
"fs-extra": "^10.1.0",
|
|
33
|
+
"inquirer": "^8.0.0",
|
|
34
|
+
"kebab-case": "^1.0.1",
|
|
35
|
+
"semver": "^7.3.7"
|
|
36
|
+
},
|
|
37
|
+
"gitHead": "dca53a0950924ea93b55b1354100343ed7c902cb"
|
|
38
|
+
}
|