@yuw-cli-dev/init 1.0.37 → 1.0.39

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.
@@ -0,0 +1,8 @@
1
+ const request = require('@yuw-cli-dev/request');
2
+
3
+ module.exports = async function () {
4
+ return request({
5
+ url: '/project/template',
6
+ method: 'GET',
7
+ });
8
+ };
package/lib/index.js CHANGED
@@ -1,8 +1,379 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
- module.exports = init;
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const inquire = require('inquirer');
6
+ const fse = require('fs-extra');
7
+ const semver = require('semver');
8
+ const userHome = require('user-home');
9
+ const isValidFilename = require('valid-filename');
10
+ const glob = require('glob');
11
+ const ejs = require('ejs');
12
+ const Command = require('@yuw-cli-dev/command');
13
+ const log = require('@yuw-cli-dev/log');
14
+ const Package = require('@yuw-cli-dev/package');
15
+ const { spinnerStart, execSync } = require('@yuw-cli-dev/utils');
16
+ const getProjectTemplate = require('./getProjectTemplate');
17
+
18
+ const TYPE_PROJECT = 'project';
19
+ const TYPE_COMPONENT = 'component';
20
+ const TEMPLATE_TYPE_NORMAL = 'normal';
21
+ const TEMPLATE_TYPE_CUSTOM = 'custom';
22
+ const WHITE_COMMAND = ['npm', 'cnpm', 'yarn', 'pnpm'];
23
+
24
+ class InitCommand extends Command {
25
+ init() {
26
+ this.projectName = this._argv[0] || '';
27
+ this.force = !!this._argv[1].force;
28
+ log.verbose('projectName', this.projectName);
29
+ log.verbose('force', this.force);
30
+ }
31
+ async exec() {
32
+ try {
33
+ const projectInfo = await this.prepare();
34
+ log.verbose('projectInfo', projectInfo);
35
+ if (projectInfo) {
36
+ this.projectInfo = projectInfo;
37
+ await this.downloadTemplate();
38
+ await this.installTemplate();
39
+ }
40
+ } catch (e) {
41
+ log.error(e.message);
42
+ }
43
+ }
44
+ checkCommand(cmd) {
45
+ if (WHITE_COMMAND.includes(cmd)) {
46
+ return cmd;
47
+ }
48
+ return null;
49
+ }
50
+
51
+ async execCmd(command, errMsg) {
52
+ const installCmdArr = command.split(' ');
53
+ const installCmd = installCmdArr[0];
54
+ const checkedCmd = this.checkCommand(installCmd);
55
+ if (!checkedCmd) {
56
+ throw new Error('命令不存在,请检查:', installCmd);
57
+ }
58
+ const installArgs = installCmdArr.slice(1);
59
+ const ret = await execSync(checkedCmd, installArgs, {
60
+ cwd: process.cwd(),
61
+ stdio: 'inherit',
62
+ });
63
+ if (ret !== 0) {
64
+ throw new Error(errMsg);
65
+ }
66
+ }
67
+
68
+ async ejsRender(options) {
69
+ const dir = process.cwd();
70
+ const projectInfo = this.projectInfo;
71
+ return new Promise((resolve, reject) => {
72
+ glob(
73
+ '**',
74
+ {
75
+ cwd: dir,
76
+ nodir: true,
77
+ ignore: options && options.ignore,
78
+ },
79
+ (err, files) => {
80
+ if (err) {
81
+ reject(err);
82
+ return;
83
+ }
84
+ Promise.all(
85
+ files.map(file => {
86
+ const filePath = path.join(dir, file);
87
+ return new Promise((res, rej) => {
88
+ ejs.renderFile(
89
+ filePath,
90
+ {
91
+ className: projectInfo.projectName,
92
+ version: projectInfo.projectVersion,
93
+ },
94
+ {},
95
+ (e, result) => {
96
+ if (e) {
97
+ rej(e);
98
+ return;
99
+ }
100
+ fse.writeFileSync(filePath, result);
101
+ res();
102
+ },
103
+ );
104
+ });
105
+ }),
106
+ )
107
+ .then(() => {
108
+ resolve();
109
+ })
110
+ .catch(e => {
111
+ reject(e);
112
+ });
113
+ },
114
+ );
115
+ });
116
+ }
117
+
118
+ async installTemplate() {
119
+ if (this.templateInfo) {
120
+ if (!this.templateInfo.type) {
121
+ this.templateInfo.type = TEMPLATE_TYPE_NORMAL;
122
+ }
123
+ if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
124
+ await this.installNormalTemplate();
125
+ } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
126
+ await this.installCustomTemplate();
127
+ } else {
128
+ throw new Error('无法识别项目模板类型');
129
+ }
130
+ } else {
131
+ throw new Error('项目模板信息不存在');
132
+ }
133
+
134
+ // 进入指定目录
135
+ process.chdir(path.resolve(process.cwd(), this.projectInfo.projectName));
136
+
137
+ const ignore = ['node_modules/**', 'public/**'];
138
+ await this.ejsRender({ ignore });
139
+ // 安装依赖并启动项目
140
+ const { installCommand, startCommand } = this.templateInfo;
141
+ if (installCommand) {
142
+ await this.execCmd(installCommand, '依赖安装过程中失败');
143
+ }
144
+ if (startCommand) {
145
+ await this.execCmd(startCommand, '启动项目过程中失败');
146
+ }
147
+ }
148
+
149
+ async installNormalTemplate() {
150
+ let spinner;
151
+ try {
152
+ const templatePath = path.resolve(
153
+ this.templateNpm.cacheFilePath,
154
+ 'template',
155
+ );
156
+ const targetPath = path.resolve(
157
+ process.cwd(),
158
+ this.projectInfo.projectName,
159
+ );
160
+ log.verbose('templatePath', templatePath);
161
+ log.verbose('targetPath', targetPath);
4
162
 
5
- function init(projectName, cmdObj) {
6
- // TODO
7
- console.log("init", projectName, cmdObj.force, process.env.CLI_TARGET_PATH);
163
+ spinner = spinnerStart('正在安装模板...');
164
+ fse.ensureDirSync(templatePath);
165
+ fse.ensureDirSync(targetPath);
166
+ fse.copySync(templatePath, targetPath);
167
+ } catch (e) {
168
+ throw e;
169
+ } finally {
170
+ spinner.stop(true);
171
+ log.success('模板安装完成');
172
+ }
173
+ }
174
+ async installCustomTemplate() {
175
+ log.info('正在安装自定义模板...');
176
+ }
177
+ async downloadTemplate() {
178
+ const { projectTemplate } = this.projectInfo;
179
+ const templateInfo = this.template.find(
180
+ item => item.npmName === projectTemplate,
181
+ );
182
+ log.verbose('templateInfo', templateInfo);
183
+ this.templateInfo = templateInfo;
184
+
185
+ const targetPath = path.resolve(
186
+ userHome,
187
+ process.env.CLI_HOME_PATH,
188
+ 'template',
189
+ );
190
+ const storeDir = path.resolve(
191
+ userHome,
192
+ process.env.CLI_HOME_PATH,
193
+ 'template',
194
+ 'node_modules',
195
+ );
196
+ log.verbose('targetPath', targetPath);
197
+ log.verbose('storeDir', storeDir);
198
+
199
+ const templateNpm = new Package({
200
+ targetPath,
201
+ storeDir,
202
+ packageName: templateInfo.npmName,
203
+ packageVersion: templateInfo.version,
204
+ });
205
+ if (!(await templateNpm.exists())) {
206
+ const spinner = spinnerStart('正在下载模板...');
207
+ try {
208
+ await templateNpm.install();
209
+ } catch (e) {
210
+ throw e;
211
+ } finally {
212
+ spinner.stop(true);
213
+ if (await templateNpm.exists()) {
214
+ this.templateNpm = templateNpm;
215
+ log.success('模板下载完成');
216
+ }
217
+ }
218
+ } else {
219
+ const spinner = spinnerStart('正在更新模板...');
220
+ try {
221
+ await templateNpm.update();
222
+ } catch (error) {
223
+ throw error;
224
+ } finally {
225
+ spinner.stop(true);
226
+ if (await templateNpm.exists()) {
227
+ this.templateNpm = templateNpm;
228
+ log.verbose('templateNpm-----------------', this.templateNpm);
229
+ log.success('模板更新完成');
230
+ }
231
+ }
232
+ }
233
+ }
234
+ async prepare() {
235
+ const template = await getProjectTemplate();
236
+ if (!template || template.length === 0) {
237
+ throw new Error('项目模板不存在');
238
+ }
239
+ this.template = template;
240
+ log.verbose('template', template);
241
+
242
+ const localPath = process.cwd();
243
+ let isContinue = false;
244
+ if (!this.isDirEmpty(localPath)) {
245
+ if (!this.force) {
246
+ const rs = await inquire.prompt({
247
+ type: 'confirm',
248
+ name: 'isContinue',
249
+ message: '当前目录不为空,是否继续创建项目?(会清空当前目录)',
250
+ default: false,
251
+ });
252
+ isContinue = rs.isContinue;
253
+ if (!isContinue) {
254
+ return;
255
+ }
256
+ }
257
+ if (isContinue || this.force) {
258
+ const { ifContinue } = await inquire.prompt({
259
+ type: 'confirm',
260
+ name: 'ifContinue',
261
+ message: '是否确认清空当前目录下的文件?',
262
+ default: false,
263
+ });
264
+ let spinner;
265
+ if (ifContinue) {
266
+ try {
267
+ spinner = spinnerStart('正在清空目录...');
268
+ fse.emptyDirSync(localPath);
269
+ } catch (error) {
270
+ throw error;
271
+ } finally {
272
+ spinner.stop(true);
273
+ }
274
+ }
275
+ }
276
+ }
277
+ return this.getProjectInfo();
278
+ }
279
+ async getProjectInfo() {
280
+ let projectInfo = {};
281
+ let isProjectNameValid = false;
282
+ if (isValidFilename(this.projectName)) {
283
+ isProjectNameValid = true;
284
+ projectInfo.projectName = this.projectName;
285
+ }
286
+ const { type } = await inquire.prompt({
287
+ type: 'list',
288
+ name: 'type',
289
+ message: '请选择初始化的类型',
290
+ default: TYPE_PROJECT,
291
+ choices: [
292
+ { name: '项目', value: TYPE_PROJECT },
293
+ { name: '组件', value: TYPE_COMPONENT },
294
+ ],
295
+ });
296
+ log.verbose('type', type);
297
+ const projectNamePrompt = {
298
+ type: 'input',
299
+ name: 'projectName',
300
+ message: '请输入项目名称',
301
+ default: '',
302
+ validate(v) {
303
+ const done = this.async();
304
+ setTimeout(() => {
305
+ if (!isValidFilename(v)) {
306
+ done('项目名称不合法');
307
+ return;
308
+ }
309
+ done(null, true);
310
+ }, 0);
311
+ },
312
+ filter(v) {
313
+ return v;
314
+ },
315
+ };
316
+ if (type === TYPE_PROJECT) {
317
+ const promptArr = [];
318
+ if (!isProjectNameValid) {
319
+ promptArr.push(projectNamePrompt);
320
+ }
321
+ promptArr.push({
322
+ type: 'input',
323
+ name: 'projectVersion',
324
+ message: '请输入项目版本号',
325
+ default: '1.0.0',
326
+ validate(v) {
327
+ const done = this.async();
328
+ setTimeout(() => {
329
+ if (!!semver.valid(v) === false) {
330
+ done('项目版本号不合法');
331
+ return;
332
+ }
333
+ done(null, true);
334
+ }, 0);
335
+ },
336
+ filter(v) {
337
+ if (!!semver.valid(v)) {
338
+ return semver.valid(v);
339
+ }
340
+ return v;
341
+ },
342
+ });
343
+ promptArr.push({
344
+ type: 'list',
345
+ name: 'projectTemplate',
346
+ message: '请选择项目模板',
347
+ choices: this.createTemplateChoices(),
348
+ });
349
+ const project = await inquire.prompt(promptArr);
350
+ projectInfo = { ...projectInfo, ...project, type };
351
+ } else if (type === TYPE_COMPONENT) {
352
+ }
353
+ return projectInfo;
354
+ }
355
+
356
+ createTemplateChoices() {
357
+ return this.template.map(item => ({
358
+ name: item.name,
359
+ value: item.npmName,
360
+ }));
361
+ }
362
+
363
+ isDirEmpty(localPath) {
364
+ let fileList = fs.readdirSync(localPath);
365
+ // 文件过滤逻辑
366
+ fileList = fileList.filter(
367
+ file => !file.startsWith('.') && !['node_modules'].includes(file),
368
+ );
369
+ return !fileList || fileList.length <= 0;
370
+ }
8
371
  }
372
+
373
+ function init(argv) {
374
+ log.verbose('argv', argv);
375
+ return new InitCommand(argv);
376
+ }
377
+
378
+ module.exports = init;
379
+ module.exports.InitCommand = InitCommand;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuw-cli-dev/init",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "description": "yuw-cli-dev init",
5
5
  "author": "yuwei <yuwei@huya.com>",
6
6
  "homepage": "https://github.com/yousanjin/yuw-cli#readme",
@@ -26,5 +26,19 @@
26
26
  "bugs": {
27
27
  "url": "https://github.com/yousanjin/yuw-cli/issues"
28
28
  },
29
- "gitHead": "ab03556d97ed9d4caf7af33d1720f4e45e34749a"
29
+ "dependencies": {
30
+ "@yuw-cli-dev/command": "file:../../models/command",
31
+ "@yuw-cli-dev/log": "^1.0.39",
32
+ "@yuw-cli-dev/package": "file:../../models/package",
33
+ "@yuw-cli-dev/request": "^1.0.39",
34
+ "@yuw-cli-dev/utils": "^1.0.39",
35
+ "ejs": "^4.0.1",
36
+ "fs-extra": "^9.0.1",
37
+ "glob": "^7.1.6",
38
+ "inquirer": "^7.3.3",
39
+ "semver": "^7.7.3",
40
+ "user-home": "^3.0.0",
41
+ "valid-filename": "^3.1.0"
42
+ },
43
+ "gitHead": "7bcace542ccccc47f518b0537cbb0cce93229adf"
30
44
  }