sparrow-ci 1.0.5
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/README.md +81 -0
- package/bin/ci.js +355 -0
- package/bin/command/index.js +136 -0
- package/bin/main.js +70 -0
- package/bin/utils/index.js +203 -0
- package/bin/utils/node_utils.js +276 -0
- package/ci.config.js +41 -0
- package/git.sh +15 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* @Author: wangqi
|
|
3
|
+
* @Date: 2022-08-12 15:36:41
|
|
4
|
+
* @LastEditors: wangqi
|
|
5
|
+
* @LastEditTime: 2022-09-01 17:17:29
|
|
6
|
+
* @FilePath: /sparrow-ci/README.md
|
|
7
|
+
* @Description: README
|
|
8
|
+
-->
|
|
9
|
+
# sparrow-ci
|
|
10
|
+
|
|
11
|
+
#### 介绍
|
|
12
|
+
基于miniprogram-ci开发的小程序命令行工具
|
|
13
|
+
|
|
14
|
+
# install
|
|
15
|
+
|
|
16
|
+
(1)全局安装:
|
|
17
|
+
```shell
|
|
18
|
+
npm install sparrow-ci -g
|
|
19
|
+
```
|
|
20
|
+
(2)也可以只在项目中安装:
|
|
21
|
+
```shell
|
|
22
|
+
npm install sparrow-ci --save-dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
在`package.json`的`scripts`属性下加上:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
"scripts": {
|
|
29
|
+
...
|
|
30
|
+
"ci": "ci "
|
|
31
|
+
},
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
在使用的时候不能像全局安装那样直接使用`ci`了,需要在指令前面加上`npx`。比如,全局安装时上传代码用的是`ci -u`,只在项目中安装时需要使用`npx ci -u`。
|
|
35
|
+
|
|
36
|
+
# usage
|
|
37
|
+
|
|
38
|
+
1.使用命令行工具的前提条件是,在微信开发者工具中把服务端口打开。
|
|
39
|
+
|
|
40
|
+
设置 -> 安全设置 -> 服务端口
|
|
41
|
+
|
|
42
|
+
2.执行`cli`指令初始化之后,会生成一个`cli.js`文件,文件内容是这样的:
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
module.exports = {
|
|
46
|
+
"environment": "DEV",
|
|
47
|
+
"version": "版本号",
|
|
48
|
+
"description": "描述文本",
|
|
49
|
+
"projectRoot": "电脑里面项目的路径",
|
|
50
|
+
"needCheckNpmPackages": ["需要检查的包的名字"]
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
在项目代码中引入这个文件的数据,主要是前三个属性,来进行一些处理。
|
|
55
|
+
|
|
56
|
+
3.指令使用方式:`ci <command>`
|
|
57
|
+
|
|
58
|
+
` <command>` 是以下选项中的一个:
|
|
59
|
+
|
|
60
|
+
| 选项 | 简写 | 作用 |
|
|
61
|
+
| --------------- | ---- | --------------------------- |
|
|
62
|
+
| --publish | -pub | 上传正式代码 |
|
|
63
|
+
| --upload | -u | 上传测试代码 |
|
|
64
|
+
| --preview | -p | 预览 |
|
|
65
|
+
| --switch | -s | 切换环境 |
|
|
66
|
+
| --build-npm | -b | npm构建 |
|
|
67
|
+
| --help | -h | 描述如何使用 |
|
|
68
|
+
| --edit-live | -e | 编辑live直播插件json |
|
|
69
|
+
| --git-pull | -g | 拉取远端master代码 |
|
|
70
|
+
| --clone | -c | 克隆文件夹 |
|
|
71
|
+
|
|
72
|
+
比如,上传测试代码:
|
|
73
|
+
|
|
74
|
+
```shell
|
|
75
|
+
ci --upload
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
4.注意:
|
|
79
|
+
|
|
80
|
+
+ node版本需大于等于v14.9.0。
|
|
81
|
+
|
package/bin/ci.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: wangqi
|
|
3
|
+
* @Date: 2022-08-12 15:56:29
|
|
4
|
+
* @LastEditors: wangqi
|
|
5
|
+
* @LastEditTime: 2022-09-01 17:15:42
|
|
6
|
+
* @FilePath: /sparrow-ci/bin/ci.js
|
|
7
|
+
* @Description:
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const inquirer = require('inquirer');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const { getProInfo, commonFilePath, configOutPut, createVersion, handleList, printSuccess, printFail, printWarnning, handleVersion } = require('./utils/index');
|
|
15
|
+
const { isDependentExisted, writeToFile, installAndBuild, installPackages, handleLivePlugin, handleExecFile, handleFolder } = require('./utils/node_utils.js');
|
|
16
|
+
const { environment, helpDescription } = require('../ci.config');
|
|
17
|
+
const { uploadCi, packNpmCi, previewCi } = require('./command');
|
|
18
|
+
|
|
19
|
+
const currentWorkingDirectory = process.cwd(); // 命令执行位置的路径
|
|
20
|
+
|
|
21
|
+
class Ci {
|
|
22
|
+
/*
|
|
23
|
+
@constructor
|
|
24
|
+
environment 项目环境 默认是开发环境
|
|
25
|
+
projectRoot 项目路径
|
|
26
|
+
version 项目版本号
|
|
27
|
+
description 项目描述
|
|
28
|
+
miniprogramRoot 代码根路径
|
|
29
|
+
appJson app.json文件
|
|
30
|
+
subPackages 所有的分包
|
|
31
|
+
independentSubPackages 过滤得到独立分包
|
|
32
|
+
mainCliFilePath cli.js 文件的路径
|
|
33
|
+
packageJSONPath 项目中package.json文件的位置
|
|
34
|
+
cliFileExisted cli.js文件是否已经创建了,默认为false
|
|
35
|
+
needCheckNpmPackages 需要检查更新的包
|
|
36
|
+
appid
|
|
37
|
+
*/
|
|
38
|
+
version = '';
|
|
39
|
+
description = '';
|
|
40
|
+
miniprogramRoot = '';
|
|
41
|
+
appJson = '';
|
|
42
|
+
subPackages = '';
|
|
43
|
+
mainCliFilePath = '';
|
|
44
|
+
packageJSONPath = '';
|
|
45
|
+
cliFileExisted = false;
|
|
46
|
+
needCheckNpmPackages = '';
|
|
47
|
+
appid = '';
|
|
48
|
+
environment = environment.DEV;
|
|
49
|
+
constructor() {
|
|
50
|
+
this.projectRoot = currentWorkingDirectory;
|
|
51
|
+
const { version, description } = getProInfo(currentWorkingDirectory);
|
|
52
|
+
this.version = version;
|
|
53
|
+
this.description = description;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async init() {
|
|
57
|
+
// 代码根路径,比如src目录
|
|
58
|
+
const configJson = commonFilePath(this.projectRoot, 'project.config.json');
|
|
59
|
+
this.appid = configJson.appid;
|
|
60
|
+
this.miniprogramRoot = configJson.miniprogramRoot ? path.join(this.projectRoot, configJson.miniprogramRoot) : this.projectRoot;
|
|
61
|
+
this.appJson = commonFilePath(this.miniprogramRoot, 'app.json');
|
|
62
|
+
this.subPackages = this.appJson.subPackages;
|
|
63
|
+
this.independentSubPackages = this.subPackages.filter(inPackage => inPackage.independent);
|
|
64
|
+
this.mainCliFilePath = path.join(this.miniprogramRoot, 'cli.js');
|
|
65
|
+
this.packageJSONPath = path.join(this.projectRoot, 'package.json');
|
|
66
|
+
// 独立分包是否存在
|
|
67
|
+
isDependentExisted(this.miniprogramRoot, this.independentSubPackages);
|
|
68
|
+
// cli.js是否存在
|
|
69
|
+
this.cliFileExisted = await this.checkCliFileCreated();
|
|
70
|
+
if (!this.cliFileExisted) { // 没有创建cli.js文件,就需要创建
|
|
71
|
+
this.version = createVersion(); // 初始化版本号
|
|
72
|
+
const createCliFileSuccess = await this.createCliFile();
|
|
73
|
+
if (!createCliFileSuccess) return; // cli.js文件没有创建成功直接返回
|
|
74
|
+
|
|
75
|
+
const installSuccess = await installPackages(this.packageJSONPath); // 安装依赖
|
|
76
|
+
if (!installSuccess) return; // 依赖没有安装成功直接返回
|
|
77
|
+
|
|
78
|
+
const buildSuccess = await this.buildNpm(); // 构建npm包
|
|
79
|
+
if (!buildSuccess) return; // 没有构建成功直接返回
|
|
80
|
+
|
|
81
|
+
printSuccess('初始化完成');
|
|
82
|
+
} else { // 已经初始化过,直接读取cli.js文件中的数据
|
|
83
|
+
this.readCliFile(this.mainCliFilePath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 需要的配置参数
|
|
88
|
+
handleConfigParams() {
|
|
89
|
+
return {
|
|
90
|
+
environment: this.environment,
|
|
91
|
+
version: this.version,
|
|
92
|
+
description: this.description,
|
|
93
|
+
projectRoot: this.projectRoot,
|
|
94
|
+
needCheckNpmPackages: this.needCheckNpmPackages,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 设置实例的配置信息
|
|
99
|
+
setConfigInfo(data = {}) {
|
|
100
|
+
const {
|
|
101
|
+
environment,
|
|
102
|
+
version,
|
|
103
|
+
description,
|
|
104
|
+
projectRoot,
|
|
105
|
+
needCheckNpmPackages,
|
|
106
|
+
} = data;
|
|
107
|
+
// 给相应属性赋值
|
|
108
|
+
this.environment = environment;
|
|
109
|
+
this.version = version;
|
|
110
|
+
this.description = description;
|
|
111
|
+
this.projectRoot = projectRoot;
|
|
112
|
+
this.needCheckNpmPackages = needCheckNpmPackages;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 检查配置文件是否已经创建过了;TODO 如果主包存在,就以主包为准;如果主包不存在,就提示创建;然后复制到所有独立分包中
|
|
116
|
+
async checkCliFileCreated() {
|
|
117
|
+
const { mainCliFilePath, independentSubPackages, miniprogramRoot } = this;
|
|
118
|
+
return new Promise(async (resolve) => {
|
|
119
|
+
const cliFileExisted = fs.existsSync(mainCliFilePath);
|
|
120
|
+
if (!cliFileExisted) {
|
|
121
|
+
resolve(false); // 独立分包不存在
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// 主包cli文件存在
|
|
125
|
+
// 复制到分包中
|
|
126
|
+
const cliFileData = this.formatData(mainCliFilePath);
|
|
127
|
+
const result = await Promise.all(independentSubPackages.map(inPackage => {
|
|
128
|
+
const inPackageCliPath = path.join(miniprogramRoot, inPackage.root, 'cli.js');
|
|
129
|
+
return writeToFile(inPackageCliPath, cliFileData, false);
|
|
130
|
+
}));
|
|
131
|
+
if (!result) {
|
|
132
|
+
fs.rmSync(mainCliFilePath); // 删主包cli文件
|
|
133
|
+
resolve(false);
|
|
134
|
+
} else {
|
|
135
|
+
resolve(true);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 创建cli.js文件
|
|
141
|
+
async createCliFile() {
|
|
142
|
+
let configInfo = this.handleConfigParams();
|
|
143
|
+
configInfo.environment = environment.PRO; // 默认是PRO
|
|
144
|
+
return await this.writeCliFile(configInfo);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 读取cli.js文件的信息
|
|
148
|
+
readCliFile(filePath) {
|
|
149
|
+
const cliFileData = require(filePath);
|
|
150
|
+
const { miniapp_version, version, version_description } = require(this.packageJSONPath);
|
|
151
|
+
// 把package里的版本和版本描述同步到cli中,当使用cli -u时写入到cli
|
|
152
|
+
// 22.1.8新加miniapp_version
|
|
153
|
+
cliFileData.version = miniapp_version || version;
|
|
154
|
+
cliFileData.description = version_description || ''
|
|
155
|
+
this.setConfigInfo(cliFileData);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 写cli.json文件
|
|
159
|
+
async writeCliFile(data = {}) {
|
|
160
|
+
return new Promise(async (resolve) => {
|
|
161
|
+
// 默认值是初始化实例后对应的属性的值
|
|
162
|
+
const {
|
|
163
|
+
environment = this.environment,
|
|
164
|
+
version = this.version,
|
|
165
|
+
description = this.description,
|
|
166
|
+
projectRoot = this.projectRoot,
|
|
167
|
+
needCheckNpmPackages = this.needCheckNpmPackages.join(','),
|
|
168
|
+
} = data;
|
|
169
|
+
|
|
170
|
+
// 这个对象是为了用来初始化cli实例的
|
|
171
|
+
const configData = {
|
|
172
|
+
environment,
|
|
173
|
+
version,
|
|
174
|
+
description,
|
|
175
|
+
projectRoot,
|
|
176
|
+
needCheckNpmPackages,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const configDataString = this.formatData('', false, configData);
|
|
180
|
+
|
|
181
|
+
const r1 = writeToFile(this.mainCliFilePath, configDataString);
|
|
182
|
+
const r2 = Promise.all(this.independentSubPackages.map(inPackage => {
|
|
183
|
+
const inPackageCliPath = path.join(this.miniprogramRoot, inPackage.root, 'cli.js');
|
|
184
|
+
return writeToFile(inPackageCliPath, configDataString);
|
|
185
|
+
}))
|
|
186
|
+
let result1 = await r1;
|
|
187
|
+
let result2 = await r2;
|
|
188
|
+
|
|
189
|
+
if (result1 && result2) {
|
|
190
|
+
this.setConfigInfo(configData);
|
|
191
|
+
}
|
|
192
|
+
resolve(result1 && result2);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 展示简单的配置信息
|
|
197
|
+
async showSimpleCliConfig(type) {
|
|
198
|
+
let promptArr = handleList(['environment', 'version', 'description'], { environmentType: this.environment, version: this.version, description: this.description });
|
|
199
|
+
if (type === 'publish') {
|
|
200
|
+
// 如果是publish, 移除选择环境,默认PRO
|
|
201
|
+
promptArr.shift();
|
|
202
|
+
this.environment = environment.PRO;
|
|
203
|
+
}
|
|
204
|
+
return new Promise((resolve) => {
|
|
205
|
+
inquirer
|
|
206
|
+
.prompt(promptArr)
|
|
207
|
+
.then(async (answers) => {
|
|
208
|
+
const { version } = answers;
|
|
209
|
+
if (!version) {
|
|
210
|
+
printWarnning('项目版本号为必填');
|
|
211
|
+
resolve(false);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
await this.writeCliFile(answers);
|
|
215
|
+
resolve(answers);
|
|
216
|
+
})
|
|
217
|
+
.catch(error => {
|
|
218
|
+
console.log(`提问简单配置信息的时候出错,error:`, error);
|
|
219
|
+
resolve(false);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 组合文件要写的内容
|
|
225
|
+
formatData(filePath, isTrue = true, configParams) {
|
|
226
|
+
if (isTrue) this.readCliFile(filePath);
|
|
227
|
+
const needCheckNpmPackages = isTrue ? this.needCheckNpmPackages.join(',') : configParams.needCheckNpmPackages;
|
|
228
|
+
let needCheckNpmPackagesArr = [];
|
|
229
|
+
if (needCheckNpmPackages) {
|
|
230
|
+
needCheckNpmPackagesArr = needCheckNpmPackages.split(',');
|
|
231
|
+
}
|
|
232
|
+
const needCheckNpmPackagesString = JSON.stringify(needCheckNpmPackagesArr);
|
|
233
|
+
if (!isTrue) {
|
|
234
|
+
const { environment, version, description, projectRoot } = configParams;
|
|
235
|
+
return configOutPut(environment, version, description, projectRoot, needCheckNpmPackagesString)
|
|
236
|
+
};
|
|
237
|
+
return configOutPut(this.environment, this.version, this.description, this.projectRoot, needCheckNpmPackagesString);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async online() {
|
|
241
|
+
await this.upload('online');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async publish() {
|
|
245
|
+
// 拉取master
|
|
246
|
+
await this.pull();
|
|
247
|
+
// 处理直播插件 默认添加
|
|
248
|
+
const writeFileSuccess = await handleLivePlugin(this.miniprogramRoot);
|
|
249
|
+
if (writeFileSuccess) {
|
|
250
|
+
printSuccess(`直播插件${writeFileSuccess}`);
|
|
251
|
+
}
|
|
252
|
+
await this.upload('publish');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 上传代码
|
|
256
|
+
// @isPublish 是否自动上传
|
|
257
|
+
async upload(type = 'upload') {
|
|
258
|
+
const installAndBuildSuccess = await installAndBuild(this.needCheckNpmPackages, this.packageJSONPath, this.miniprogramRoot); // 检查安装的版本和package.json文件中的版本是否一致,不一致的话要重新安装和构建
|
|
259
|
+
if (!installAndBuildSuccess) return;
|
|
260
|
+
// 获取用户填写的版本和描述,写入cli
|
|
261
|
+
if (type !== 'online') {
|
|
262
|
+
const answers = await this.showSimpleCliConfig(type);
|
|
263
|
+
if (!answers) return;
|
|
264
|
+
}
|
|
265
|
+
await uploadCi({
|
|
266
|
+
projectPath: this.projectRoot,
|
|
267
|
+
version: this.version,
|
|
268
|
+
description: this.description,
|
|
269
|
+
keyPath: this.miniprogramRoot,
|
|
270
|
+
appid: this.appid,
|
|
271
|
+
});
|
|
272
|
+
if (type === 'online') return;
|
|
273
|
+
const packageObj = require(this.packageJSONPath);
|
|
274
|
+
packageObj.miniapp_version = this.version;
|
|
275
|
+
packageObj.version = handleVersion(this.version);
|
|
276
|
+
packageObj.version_description = this.description;
|
|
277
|
+
const packageStr = JSON.stringify(packageObj, null, 2);
|
|
278
|
+
writeToFile(this.packageJSONPath, packageStr);
|
|
279
|
+
console.log(`上传成功后可至微信小程序管理后台 ${chalk.blue('https://mp.weixin.qq.com/wxamp/wacodepage/getcodepage')} 将上传的版本选为体验版`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 构建
|
|
283
|
+
async buildNpm() {
|
|
284
|
+
const data = await packNpmCi({
|
|
285
|
+
packageJSONPath: this.packageJSONPath,
|
|
286
|
+
miniprogramRoot: this.miniprogramRoot,
|
|
287
|
+
});
|
|
288
|
+
return data;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 预览
|
|
292
|
+
preview() {
|
|
293
|
+
previewCi({
|
|
294
|
+
version: this.version,
|
|
295
|
+
description: this.description,
|
|
296
|
+
projectPath: this.projectRoot,
|
|
297
|
+
keyPath: __dirname,
|
|
298
|
+
projectRoot: this.projectRoot
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 添加/删除直播插件
|
|
303
|
+
subscribe() {
|
|
304
|
+
let promptArr = handleList(['status'], { status: 'add' });
|
|
305
|
+
inquirer
|
|
306
|
+
.prompt(promptArr)
|
|
307
|
+
.then(async (answers) => {
|
|
308
|
+
const writeFileSuccess = await handleLivePlugin(this.miniprogramRoot, answers.status);
|
|
309
|
+
if (writeFileSuccess) {
|
|
310
|
+
printSuccess(`直播插件${writeFileSuccess}`);
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
.catch(error => {
|
|
314
|
+
printFail(`${error}`);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 拉取远端代码
|
|
319
|
+
async pull() {
|
|
320
|
+
//获取文件路径
|
|
321
|
+
const filepath = path.join(__dirname, '../git.sh');
|
|
322
|
+
// 执行拉取
|
|
323
|
+
await handleExecFile(filepath);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 克隆文件夹(克隆到独立分包)
|
|
327
|
+
clone() {
|
|
328
|
+
handleFolder(this.independentSubPackages, this.miniprogramRoot);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 切换项目环境
|
|
332
|
+
switch() {
|
|
333
|
+
const promptArr = handleList(['environment'], { environmentType: this.environment });
|
|
334
|
+
inquirer
|
|
335
|
+
.prompt(promptArr)
|
|
336
|
+
.then(async (answers) => {
|
|
337
|
+
const { environment } = answers;
|
|
338
|
+
const writeFileSuccess = await this.writeCliFile(answers);
|
|
339
|
+
if (writeFileSuccess) {
|
|
340
|
+
printSuccess(`环境已成功切换为${environment}`);
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
.catch(error => {
|
|
344
|
+
printFail(`${error}`);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 帮助
|
|
349
|
+
// 查看帮助
|
|
350
|
+
help() {
|
|
351
|
+
console.log(helpDescription);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
module.exports = Ci;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: wangqi
|
|
3
|
+
* @Date: 2022-08-12 10:20:19
|
|
4
|
+
* @LastEditors: wangqi
|
|
5
|
+
* @LastEditTime: 2022-09-01 17:17:37
|
|
6
|
+
* @FilePath: /sparrow-ci/bin/command/index.js
|
|
7
|
+
* @Description: 上传代码
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const ci = require("miniprogram-ci");
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const ora = require("ora");
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const { handleCodeSize, getKeyName } = require('../utils/index');
|
|
15
|
+
|
|
16
|
+
function project(params) {
|
|
17
|
+
const { projectPath, keyPath, appid } = params;
|
|
18
|
+
const project = new ci.Project({
|
|
19
|
+
appid,
|
|
20
|
+
type: "miniProgram",
|
|
21
|
+
projectPath,
|
|
22
|
+
privateKeyPath: getKeyName(path.join(keyPath, '/key/')),
|
|
23
|
+
ignores: ["node_modules/**/*"],
|
|
24
|
+
});
|
|
25
|
+
return project;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function uploadCi(params) {
|
|
29
|
+
const spinner = ora(`${chalk.blue('准备上传')}\n`).start();
|
|
30
|
+
spinner.color = 'blue';
|
|
31
|
+
const { version, description } = params;
|
|
32
|
+
try {
|
|
33
|
+
const { subPackageInfo } = await ci.upload({
|
|
34
|
+
project: project(params),
|
|
35
|
+
version,
|
|
36
|
+
desc: description,
|
|
37
|
+
setting: {
|
|
38
|
+
es6: true, // 对应小程序开发者工具的 "es6 转 es5"
|
|
39
|
+
minify: true, // 压缩所有代码,对应小程序开发者工具的 "压缩代码"
|
|
40
|
+
autoPrefixWXSS: true, // 对应小程序开发者工具的 "样式自动补全"
|
|
41
|
+
minifyWXML: true, // 压缩 WXML 代码
|
|
42
|
+
minifyWXSS: true, // 压缩 WXSS 代码
|
|
43
|
+
minifyJS: true, // 压缩 JS 代码
|
|
44
|
+
},
|
|
45
|
+
robot: 1,
|
|
46
|
+
onProgressUpdate: (res) => {
|
|
47
|
+
const { _status } = res;
|
|
48
|
+
if (_status === 'doing') {
|
|
49
|
+
spinner.text = `${chalk.blue('上传代码中')}\n`;
|
|
50
|
+
spinner.color = 'blue';
|
|
51
|
+
}
|
|
52
|
+
if (_status === 'done') {
|
|
53
|
+
spinner.text = `${chalk.green('上传完成')}\n`;
|
|
54
|
+
spinner.color = 'green';
|
|
55
|
+
spinner.succeed();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
console.table(handleCodeSize(subPackageInfo));
|
|
60
|
+
} catch(err) {
|
|
61
|
+
spinner.text = `${chalk.red('上传失败')}\n${chalk.yellow(err)}`;
|
|
62
|
+
spinner.fail();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 注意当前构建存在问题
|
|
67
|
+
function packNpmCi(params) {
|
|
68
|
+
return new Promise(async (resolve) => {
|
|
69
|
+
const spinner = ora(`${chalk.blue('构建中')}\n`).start();
|
|
70
|
+
spinner.color = 'blue';
|
|
71
|
+
const { packageJSONPath, miniprogramRoot } = params;
|
|
72
|
+
const { miniProgramPackNum, otherNpmPackNum, warnList} = await ci.packNpmManually({
|
|
73
|
+
packageJsonPath: packageJSONPath,
|
|
74
|
+
miniprogramNpmDistDir: miniprogramRoot,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if ((miniProgramPackNum > 0 || otherNpmPackNum > 0) && warnList.length === 0) {
|
|
78
|
+
spinner.text = `${chalk.green('构建成功')}\n${chalk.green(`miniProgramPackNum: ${miniProgramPackNum}个,otherNpmPackNum:${otherNpmPackNum}个`)}`;
|
|
79
|
+
spinner.color = 'green';
|
|
80
|
+
spinner.succeed();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (warnList.length > 0) {
|
|
84
|
+
warnList.forEach(item => {
|
|
85
|
+
spinner.text = `${chalk.yellow('提示')}\n${chalk.yellow(`${item.jsPath},${item.msg}`)}`;
|
|
86
|
+
spinner.warn();
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
resolve(true);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function previewCi(params) {
|
|
94
|
+
const { version, description, pagePath, searchQuery, scene, projectRoot, keyPath } = params;
|
|
95
|
+
const previewResult = await ci.preview({
|
|
96
|
+
project: project(params),
|
|
97
|
+
desc: description,
|
|
98
|
+
version,
|
|
99
|
+
setting: {
|
|
100
|
+
es6: true, // 对应小程序开发者工具的 "es6 转 es5"
|
|
101
|
+
minify: true, // 压缩所有代码,对应小程序开发者工具的 "压缩代码"
|
|
102
|
+
autoPrefixWXSS: true, // 对应小程序开发者工具的 "样式自动补全"
|
|
103
|
+
minifyWXML: true, // 压缩 WXML 代码
|
|
104
|
+
minifyWXSS: true, // 压缩 WXSS 代码
|
|
105
|
+
minifyJS: true, // 压缩 JS 代码
|
|
106
|
+
},
|
|
107
|
+
qrcodeFormat: 'base64',
|
|
108
|
+
qrcodeOutputDest: '/Users/wq/Desktop/qrcode/destination.jpg',
|
|
109
|
+
onProgressUpdate: (res) => {
|
|
110
|
+
const { _status, subPackageInfo } = res;
|
|
111
|
+
if (_status === 'doing') {
|
|
112
|
+
console.log('==============');
|
|
113
|
+
console.log('_status', _status);
|
|
114
|
+
console.log('==============');
|
|
115
|
+
}
|
|
116
|
+
if (_status === 'done') {
|
|
117
|
+
console.log('==============');
|
|
118
|
+
console.log('_status', _status, subPackageInfo);
|
|
119
|
+
console.log('==============');
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
pagePath, // 预览页面 pages/index/index
|
|
123
|
+
searchQuery, // 预览参数 [注意!]这里的`&`字符在命令行中应写成转义字符`\&` a=1&b=2
|
|
124
|
+
scene, // 场景值 1011
|
|
125
|
+
})
|
|
126
|
+
const { subPackageInfo } = previewResult;
|
|
127
|
+
console.log('==============');
|
|
128
|
+
console.log('subPackageInfo', subPackageInfo);
|
|
129
|
+
console.log('==============');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
uploadCi,
|
|
134
|
+
packNpmCi,
|
|
135
|
+
previewCi
|
|
136
|
+
}
|
package/bin/main.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* @Author: wangqi
|
|
5
|
+
* @Date: 2020-11-06 13:33:56
|
|
6
|
+
* @LastEditTime: 2022-08-31 17:23:47
|
|
7
|
+
* @LastEditor: wangqi
|
|
8
|
+
* @Description: 脚手架的类
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { program } = require('commander');
|
|
12
|
+
const Ci = require('./ci');
|
|
13
|
+
|
|
14
|
+
// 设置命令行的选项
|
|
15
|
+
program
|
|
16
|
+
.option('-o, --online', '自动发布代码')
|
|
17
|
+
.option('-pub, --publish', '发布正式环境代码')
|
|
18
|
+
.option('-u, --upload', '上传代码')
|
|
19
|
+
.option('-p, --preview', '预览')
|
|
20
|
+
.option('-s, --switch', '切换环境')
|
|
21
|
+
.option('-b, --build-npm', 'npm构建')
|
|
22
|
+
.option('-e, --edit-live', '编辑live直播插件json')
|
|
23
|
+
.option('-g, --git-pull', '拉取远端master代码')
|
|
24
|
+
.option('-c, --clone', '克隆文件夹')
|
|
25
|
+
.option('-h, --help', '描述如何使用')
|
|
26
|
+
|
|
27
|
+
program.parse(process.argv); // 处理传入的参数
|
|
28
|
+
|
|
29
|
+
async function main () {
|
|
30
|
+
const ci = new Ci();
|
|
31
|
+
// 等待实例初始化完成
|
|
32
|
+
await ci.init();
|
|
33
|
+
switch (true) {
|
|
34
|
+
case Boolean(program.online): // 自动发布代码
|
|
35
|
+
ci.online();
|
|
36
|
+
break;
|
|
37
|
+
case Boolean(program.publish): // 发布正式环境代码
|
|
38
|
+
ci.publish();
|
|
39
|
+
break;
|
|
40
|
+
case Boolean(program.upload): // 上传
|
|
41
|
+
ci.upload();
|
|
42
|
+
break;
|
|
43
|
+
case Boolean(program.preview): // 预览
|
|
44
|
+
ci.preview();
|
|
45
|
+
break;
|
|
46
|
+
case Boolean(program.switch): // 切换项目环境
|
|
47
|
+
ci.switch();
|
|
48
|
+
return;
|
|
49
|
+
case Boolean(program.buildNpm): // npm构建
|
|
50
|
+
ci.buildNpm();
|
|
51
|
+
break;
|
|
52
|
+
case Boolean(program.editLive): // 编辑直播插件
|
|
53
|
+
ci.subscribe();
|
|
54
|
+
break;
|
|
55
|
+
case Boolean(program.gitPull): // 编辑直播插件
|
|
56
|
+
ci.pull();
|
|
57
|
+
break;
|
|
58
|
+
case Boolean(program.clone): // 编辑直播插件
|
|
59
|
+
ci.clone();
|
|
60
|
+
break;
|
|
61
|
+
case Boolean(program.help): // 查看帮助
|
|
62
|
+
ci.help();
|
|
63
|
+
break;
|
|
64
|
+
default: // 默认是查看帮助
|
|
65
|
+
ci.help();
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
main();
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: wangqi
|
|
3
|
+
* @Date: 2022-08-12 17:10:25
|
|
4
|
+
* @LastEditors: wangqi
|
|
5
|
+
* @LastEditTime: 2022-09-01 18:13:11
|
|
6
|
+
* @FilePath: /sparrow-ci/bin/utils/index.js
|
|
7
|
+
* @Description: 公用方法
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const logSymbols = require('log-symbols');
|
|
14
|
+
const { environment, folderSrc } = require('../../ci.config');
|
|
15
|
+
|
|
16
|
+
// 数字补0
|
|
17
|
+
function formatNumber(number) {
|
|
18
|
+
if (number > 0 && number < 10) {
|
|
19
|
+
return `0${number}`;
|
|
20
|
+
}
|
|
21
|
+
return number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 处理版本号为三位,因为写入小程序package.json里的version如果不是三位,那么构建npm的时候虽然显示构建成功,但实际没有构建
|
|
26
|
+
*
|
|
27
|
+
*/
|
|
28
|
+
function handleVersion(miniappVersion) {
|
|
29
|
+
let version = '';
|
|
30
|
+
const arr = miniappVersion.split('.');
|
|
31
|
+
arr.length = 3;
|
|
32
|
+
version = arr.join('.');
|
|
33
|
+
return version;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 获取项目版本和描述
|
|
37
|
+
function getProInfo(projectRoot) {
|
|
38
|
+
const packageJson = commonFilePath(projectRoot, 'package.json'); // 项目package.json的路径
|
|
39
|
+
const { miniapp_version, version_description } = packageJson;
|
|
40
|
+
return {
|
|
41
|
+
version: miniapp_version,
|
|
42
|
+
description: version_description,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function commonFilePath(pathUrl, file) {
|
|
47
|
+
return require(path.join(pathUrl, file));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 输出需要的配置
|
|
51
|
+
function configOutPut(environment, version, description, projectRoot, needCheckNpmPackagesString) {
|
|
52
|
+
const configDataString =
|
|
53
|
+
`module.exports = {
|
|
54
|
+
"environment": "${environment}",
|
|
55
|
+
"version": "${version}",
|
|
56
|
+
"description": "${description}",
|
|
57
|
+
"projectRoot": "${projectRoot}",
|
|
58
|
+
"needCheckNpmPackages": ${needCheckNpmPackagesString}
|
|
59
|
+
};
|
|
60
|
+
`;
|
|
61
|
+
return configDataString;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 创建版本号,目前的版本号是这样的:21.7.261
|
|
65
|
+
function createVersion(lastNumber) {
|
|
66
|
+
const date = new Date();
|
|
67
|
+
const currentYear = date.getFullYear();
|
|
68
|
+
const currentMonth = date.getMonth() + 1;
|
|
69
|
+
const currentDate = date.getDate();
|
|
70
|
+
const lastVersionNumber = lastNumber || 1;
|
|
71
|
+
const versionNumber = `${currentYear.toString().slice(2)}.${currentMonth}.${currentDate}${lastVersionNumber}`;
|
|
72
|
+
return versionNumber;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 打印警告的信息
|
|
76
|
+
function printWarnning(message) {
|
|
77
|
+
console.log(logSymbols.warning, chalk.yellow(message));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 打印成功的信息
|
|
81
|
+
function printSuccess(message) {
|
|
82
|
+
console.log(logSymbols.success, chalk.green(message));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 打印失败的信息
|
|
86
|
+
function printFail(message) {
|
|
87
|
+
console.log(logSymbols.error, chalk.red(message));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 处理列表
|
|
91
|
+
function handleList(listName, configObj, independentSubPackages) {
|
|
92
|
+
let { environmentType, version, description, projectRoot, typeName, defaultDestinationRelativePath, status, folderName, positionName } = configObj;
|
|
93
|
+
let folderDest = [];
|
|
94
|
+
if (independentSubPackages && independentSubPackages.length > 0) {
|
|
95
|
+
independentSubPackages.forEach((item) => {
|
|
96
|
+
folderDest.push(item.root);
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
const listChioce = [
|
|
100
|
+
{
|
|
101
|
+
name: 'environment',
|
|
102
|
+
type: 'list',
|
|
103
|
+
default: environmentType,
|
|
104
|
+
message: '项目环境:',
|
|
105
|
+
choices: [environment.DEV, environment.PRO],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'version',
|
|
109
|
+
type: 'input',
|
|
110
|
+
default: version,
|
|
111
|
+
message: '项目版本:',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'description',
|
|
115
|
+
type: 'input',
|
|
116
|
+
default: description,
|
|
117
|
+
message: '项目描述:',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'projectRoot',
|
|
121
|
+
type: 'input',
|
|
122
|
+
default: projectRoot,
|
|
123
|
+
message: '项目根路径:'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'needCheckNpmPackages',
|
|
127
|
+
type: 'input',
|
|
128
|
+
default: '',
|
|
129
|
+
message: '需要检查更新的组件(以,分隔):',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'name',
|
|
133
|
+
type: 'input',
|
|
134
|
+
default: '',
|
|
135
|
+
message: `${typeName}名称:`,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'location',
|
|
139
|
+
type: 'input',
|
|
140
|
+
default: defaultDestinationRelativePath,
|
|
141
|
+
message: `${typeName}位置:`,
|
|
142
|
+
suffix: '(相对项目根目录,且路径以/结尾)',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'status',
|
|
146
|
+
type: 'list',
|
|
147
|
+
default: status,
|
|
148
|
+
message: '直播插件:',
|
|
149
|
+
choices: ['add', 'delete'],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'folderName',
|
|
153
|
+
type: 'list',
|
|
154
|
+
default: folderName,
|
|
155
|
+
message: '源目录:',
|
|
156
|
+
choices: folderSrc,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'positionName',
|
|
160
|
+
type: 'list',
|
|
161
|
+
default: positionName,
|
|
162
|
+
message: '目标目录:',
|
|
163
|
+
choices: folderDest,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
let arr = [];
|
|
167
|
+
listName.forEach(type => {
|
|
168
|
+
listChioce.forEach(item => {
|
|
169
|
+
if (type === item.name) {
|
|
170
|
+
arr.push(item);
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
return arr;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function handleCodeSize(arr) {
|
|
178
|
+
let value = JSON.parse(JSON.stringify(arr).replace(/size/g,'sizeByte'));
|
|
179
|
+
|
|
180
|
+
value.forEach(item => {
|
|
181
|
+
item.size = (item.sizeByte / 1024).toFixed(2) + 'kb';
|
|
182
|
+
});
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getKeyName(pathUrl) {
|
|
187
|
+
const file = fs.readdirSync(pathUrl);
|
|
188
|
+
return path.join(pathUrl, file[0]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
printWarnning,
|
|
193
|
+
printSuccess,
|
|
194
|
+
printFail,
|
|
195
|
+
getProInfo,
|
|
196
|
+
commonFilePath,
|
|
197
|
+
configOutPut,
|
|
198
|
+
createVersion,
|
|
199
|
+
handleList,
|
|
200
|
+
handleCodeSize,
|
|
201
|
+
handleVersion,
|
|
202
|
+
getKeyName,
|
|
203
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: wangqi
|
|
3
|
+
* @Date: 2022-08-26 10:39:50
|
|
4
|
+
* @LastEditors: wangqi
|
|
5
|
+
* @LastEditTime: 2022-09-01 17:16:49
|
|
6
|
+
* @FilePath: /sparrow-ci/bin/utils/node_utils.js
|
|
7
|
+
* @Description: 工具方法
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const ora = require('ora');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const logSymbols = require('log-symbols');
|
|
15
|
+
const inquirer = require('inquirer');
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
17
|
+
const { mkdirSync, existsSync, writeFile, readFile, execFile } = require('fs');
|
|
18
|
+
const { handleList, printSuccess, printWarnning, printFail } = require('./index');
|
|
19
|
+
const { packNpmCi } = require('../command');
|
|
20
|
+
const { liveJsonArr, liveUrl, folderSrc } = require('../../ci.config');
|
|
21
|
+
|
|
22
|
+
// 判断独立分包文件是否存在
|
|
23
|
+
function isDependentExisted(miniprogramRoot, independentSubPackages) {
|
|
24
|
+
for (let i = 0; i < independentSubPackages.length; i++) {
|
|
25
|
+
const inPackagePath = path.join(miniprogramRoot, independentSubPackages[i].root);
|
|
26
|
+
if (!existsSync(inPackagePath)) {
|
|
27
|
+
mkdirSync(inPackagePath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 写入文件
|
|
33
|
+
function writeToFile(filePath, configDataString, isLog = true) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
writeFile(filePath, configDataString, (err) => {
|
|
36
|
+
if (err) {
|
|
37
|
+
console.log(chalk.red(`写入${filePath}文件失败,error:${err}`, chalk.green(`请输入cli重新执行`)));
|
|
38
|
+
resolve(false);
|
|
39
|
+
}
|
|
40
|
+
if (isLog) printSuccess(`写入${filePath}文件成功`)
|
|
41
|
+
resolve(true);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 提问用户初始化相关的问题
|
|
47
|
+
async function askUsersInitQuestions(params) {
|
|
48
|
+
const { environment, version, description, projectRoot, needCheckNpmPackages } = params;
|
|
49
|
+
const promptArr = handleList(['environment', 'version', 'description', 'projectRoot', 'needCheckNpmPackages'], { environment, version, description, projectRoot, needCheckNpmPackages });
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
inquirer
|
|
52
|
+
.prompt(promptArr)
|
|
53
|
+
.then(answers => {
|
|
54
|
+
const { projectRoot } = answers;
|
|
55
|
+
if (!projectRoot) {
|
|
56
|
+
console.log(chalk.red('初始化失败'), `${chalk.yellow('微信开发者工具的命令行工具的安装路径')}和${chalk.yellow('项目根路径')}是必填项!`);
|
|
57
|
+
resolve(false);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log('==============');
|
|
61
|
+
console.log('answers', answers);
|
|
62
|
+
console.log('==============');
|
|
63
|
+
resolve(answers);
|
|
64
|
+
})
|
|
65
|
+
.catch(error => {
|
|
66
|
+
console.log(`回答初始化相关问题中出现的错误,error:`, error);
|
|
67
|
+
resolve(false);
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 检查npm包的版本是否一直
|
|
73
|
+
function checkNpmVersionConsistent(needCheckNpmPackages, packageJSONPath) {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const checkedPackagesCount = needCheckNpmPackages.length;
|
|
76
|
+
|
|
77
|
+
const packageJSON = require(packageJSONPath);
|
|
78
|
+
|
|
79
|
+
let inconsistentPackages = []; // 版本不一致的包
|
|
80
|
+
let count = 0;
|
|
81
|
+
|
|
82
|
+
if (checkedPackagesCount < 1) {
|
|
83
|
+
resolve(inconsistentPackages);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < checkedPackagesCount; i++) {
|
|
88
|
+
const packageName = needCheckNpmPackages[i];
|
|
89
|
+
if (packageName) {
|
|
90
|
+
let installedPackageVersion = spawnSync('npm', ['ls', packageName]); // 检查这个包本地安装的版本
|
|
91
|
+
installedPackageVersion = installedPackageVersion.stdout.toString().trim();
|
|
92
|
+
const packageNameIndex = installedPackageVersion.indexOf(packageName);
|
|
93
|
+
installedPackageVersion = packageNameIndex === -1 ? '' : installedPackageVersion.slice(packageNameIndex + packageName.length + 1);
|
|
94
|
+
|
|
95
|
+
const packageVersionInPackageJSON = packageJSON.dependencies[packageName]; // 本地package.json文件中包的版本
|
|
96
|
+
let packageLocalVersion = packageVersionInPackageJSON;
|
|
97
|
+
if (packageVersionInPackageJSON.indexOf('^') > -1) { // 如果版本号包含^,需要把^去掉
|
|
98
|
+
packageLocalVersion = packageVersionInPackageJSON.slice(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (installedPackageVersion !== packageLocalVersion) {
|
|
102
|
+
inconsistentPackages.push(packageName + `@${packageLocalVersion}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
count++;
|
|
106
|
+
if (count === checkedPackagesCount) {
|
|
107
|
+
resolve(inconsistentPackages);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 安装和进行npm构建
|
|
114
|
+
async function installAndBuild(needCheckNpmPackages, packageJSONPath, miniprogramRoot) {
|
|
115
|
+
const checkedPackagesCount = needCheckNpmPackages.length;
|
|
116
|
+
if (checkedPackagesCount === 0) return true; // 没有设置要检查的包
|
|
117
|
+
const checkConsistentSpinner = ora('检查package.json文件中包的版本与实际安装的版本是否一致...').start();
|
|
118
|
+
const inconsistentPackages = await checkNpmVersionConsistent(); // 检查npm包的版本是否一致
|
|
119
|
+
checkConsistentSpinner.stop();
|
|
120
|
+
if (inconsistentPackages.length < 1) {
|
|
121
|
+
console.log('package.json文件中包的版本与实际安装的版本一致', chalk.magenta('不必重新构建'));
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const inconsistentPackagesStr = inconsistentPackages.join(',');
|
|
125
|
+
// package.json中包的版本和实际安装的版本不一致时,重新进行npm install 和 npm 构建
|
|
126
|
+
console.log(`package.json文件中`, chalk.magenta(`${inconsistentPackagesStr}`), `包的版本与实际安装的版本不同,`, chalk.magenta(`重新进行npm安装和npm构建中,请稍候...`));
|
|
127
|
+
const installSuccess = await installPackages(packageJSONPath, inconsistentPackages);
|
|
128
|
+
if (!installSuccess) return; // 依赖没有安装成功直接返回
|
|
129
|
+
return await packNpmCi({packageJSONPath, miniprogramRoot});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 安装依赖
|
|
133
|
+
async function installPackages(packageJSONPath, packageNames = []) {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
const installPackagesOra = ora('npm install 安装依赖中...').start();
|
|
136
|
+
// 如果package.json文件存在就安装依赖
|
|
137
|
+
if (existsSync(packageJSONPath)) {
|
|
138
|
+
const npmInstall = spawnSync(
|
|
139
|
+
'npm',
|
|
140
|
+
['install', ...packageNames],
|
|
141
|
+
{ stdio: 'inherit' }
|
|
142
|
+
);
|
|
143
|
+
const { status } = npmInstall;
|
|
144
|
+
const installSuccess = status === 0; // 根据实验,status为0的时候安装成功,status为1的时候安装失败
|
|
145
|
+
if (installSuccess) {
|
|
146
|
+
installPackagesOra.succeed();
|
|
147
|
+
printSuccess('依赖安装成功');
|
|
148
|
+
} else {
|
|
149
|
+
installPackagesOra.fail('依赖安装失败')
|
|
150
|
+
}
|
|
151
|
+
resolve(installSuccess);
|
|
152
|
+
} else {
|
|
153
|
+
installPackagesOra.fail('执行失败');
|
|
154
|
+
console.log(logSymbols.warning, chalk.red('此项目没有package.json文件'));
|
|
155
|
+
resolve(false);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 处理直播插件
|
|
161
|
+
function handleLivePlugin(pathFile, status = 'add') {
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
liveJsonArr.forEach(item => {
|
|
164
|
+
readFile(path.join(pathFile, item), 'utf8', (err, data) => {
|
|
165
|
+
if (err) {
|
|
166
|
+
reject(err);
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const configComponent = JSON.parse(data);
|
|
171
|
+
let { usingComponents } = configComponent;
|
|
172
|
+
|
|
173
|
+
if (!usingComponents) reject(err);
|
|
174
|
+
|
|
175
|
+
if (status === 'add') { // 添加
|
|
176
|
+
if (usingComponents.subscribe) {
|
|
177
|
+
resolve('已添加!');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
usingComponents.subscribe = liveUrl;
|
|
181
|
+
} else { // 删除
|
|
182
|
+
if (!usingComponents.subscribe) {
|
|
183
|
+
resolve('已删除!');
|
|
184
|
+
return;
|
|
185
|
+
};
|
|
186
|
+
delete usingComponents.subscribe;
|
|
187
|
+
}
|
|
188
|
+
writeFile(path.join(pathFile, item), JSON.stringify(configComponent, null, "\t"), "utf8", (err1) => {
|
|
189
|
+
if (err1) {
|
|
190
|
+
reject(err1);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
resolve(status === 'add' ? '添加成功' : '删除成功');
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function handleExecFile(filePath) {
|
|
201
|
+
const { stdout, stderr } = await execFile(filePath, { cwd: '.' });
|
|
202
|
+
console.log('===================================');
|
|
203
|
+
console.log(stdout);
|
|
204
|
+
console.log(stderr);
|
|
205
|
+
console.log('===================================');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 复制文件夹到目标文件夹
|
|
210
|
+
* @param {string} src 源目录
|
|
211
|
+
* @param {string} dest 目标目录
|
|
212
|
+
* @param {function} callback 回调
|
|
213
|
+
*/
|
|
214
|
+
function handleFolder(independentArr, rootPath) {
|
|
215
|
+
const copyDir = (src, dest, callback) => {
|
|
216
|
+
const copy = (copySrc, copyDest) => {
|
|
217
|
+
fs.readdir(copySrc, (err, list) => {
|
|
218
|
+
if (err) {
|
|
219
|
+
callback(err);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
list.forEach((item) => {
|
|
223
|
+
const filePath = path.resolve(copySrc, item);
|
|
224
|
+
fs.stat(filePath, (err, stat) => {
|
|
225
|
+
if (err) {
|
|
226
|
+
callback(err);
|
|
227
|
+
} else {
|
|
228
|
+
const curSrc = path.resolve(copySrc, item);
|
|
229
|
+
const curDest = path.resolve(copyDest, item);
|
|
230
|
+
|
|
231
|
+
if (stat.isFile()) {
|
|
232
|
+
// 文件,直接复制
|
|
233
|
+
fs.createReadStream(curSrc).pipe(fs.createWriteStream(curDest));
|
|
234
|
+
} else if (stat.isDirectory()) {
|
|
235
|
+
// 目录,进行递归
|
|
236
|
+
fs.mkdirSync(curDest, { recursive: true });
|
|
237
|
+
copy(curSrc, curDest);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
fs.access(dest, (err) => {
|
|
246
|
+
if (err) {
|
|
247
|
+
// 若目标目录不存在,则创建
|
|
248
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
249
|
+
}
|
|
250
|
+
copy(src, dest);
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
for (let i = 0; i < folderSrc.length; i++) {
|
|
254
|
+
for (let j = 0; j < independentArr.length; j++) {
|
|
255
|
+
copyDir(rootPath + folderSrc[i], rootPath + independentArr[j].root + '/' + folderSrc[i], (err) => {
|
|
256
|
+
console.log('==============');
|
|
257
|
+
console.log(chalk.red(err));
|
|
258
|
+
console.log('==============');
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
console.log('==============');
|
|
263
|
+
console.log(chalk.yellow('复制成功!'));
|
|
264
|
+
console.log('==============');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = {
|
|
268
|
+
isDependentExisted,
|
|
269
|
+
writeToFile,
|
|
270
|
+
askUsersInitQuestions,
|
|
271
|
+
installAndBuild,
|
|
272
|
+
installPackages,
|
|
273
|
+
handleLivePlugin,
|
|
274
|
+
handleExecFile,
|
|
275
|
+
handleFolder,
|
|
276
|
+
}
|
package/ci.config.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author: wangqi
|
|
3
|
+
* @Date: 2022-04-29 17:18:33
|
|
4
|
+
* @LastEditors: wangqi
|
|
5
|
+
* @LastEditTime: 2022-08-31 17:35:45
|
|
6
|
+
* @FilePath: /sparrow-ci/ci.config.js
|
|
7
|
+
* @Description: 全局配置文件
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
environment: {
|
|
12
|
+
DEV: 'DEV', // 开发环境
|
|
13
|
+
PRO: 'PRO', // 生产环境
|
|
14
|
+
},
|
|
15
|
+
miniShopPath: '/Users/wq/Desktop/company/miniapp_shop',
|
|
16
|
+
// 直播插件地址
|
|
17
|
+
liveUrl: 'plugin-private://wx2b03c6e691cd7370/components/subscribe/subscribe',
|
|
18
|
+
// 直播插件数组
|
|
19
|
+
liveJsonArr: ['subpackages/shop/components/live/index.json', 'independent_shop/shop/components/live/index.json'],
|
|
20
|
+
// 源目录
|
|
21
|
+
folderSrc: ['sparrow_utils', 'sparrow_ui'],
|
|
22
|
+
// 帮助配置
|
|
23
|
+
helpDescription: `
|
|
24
|
+
使用方式:cli <command>
|
|
25
|
+
|
|
26
|
+
<command> 是以下选项中的一个:
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
| 选项 | 简写 | 作用 |
|
|
30
|
+
| --------------- | ---- | --------------------------- |
|
|
31
|
+
| --publish | -pub | 上传正式代码 |
|
|
32
|
+
| --upload | -u | 上传测试代码 |
|
|
33
|
+
| --preview | -p | 预览 |
|
|
34
|
+
| --switch | -s | 切换环境 |
|
|
35
|
+
| --build-npm | -b | npm构建 |
|
|
36
|
+
| --help | -h | 描述如何使用 |
|
|
37
|
+
| --edit-live | -e | 编辑live直播插件json |
|
|
38
|
+
| --git-pull | -g | 拉取远端master代码 |
|
|
39
|
+
| --clone | -c | 克隆文件夹 |
|
|
40
|
+
`,
|
|
41
|
+
};
|
package/git.sh
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
###
|
|
2
|
+
# @Author: wangqi
|
|
3
|
+
# @Date: 2022-05-06 10:24:20
|
|
4
|
+
# @LastEditors: wangqi
|
|
5
|
+
# @LastEditTime: 2022-08-12 15:54:14
|
|
6
|
+
# @FilePath: /sparrow-ci/git.sh
|
|
7
|
+
# @Description: git拉取脚本命令
|
|
8
|
+
###
|
|
9
|
+
|
|
10
|
+
# 开始拉取远端upstream master
|
|
11
|
+
echo '正在拉取upstream master代码...'
|
|
12
|
+
|
|
13
|
+
git pull upstream master
|
|
14
|
+
|
|
15
|
+
echo '执行拉取upstream master命令成功!'
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sparrow-ci",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "基于miniprogram-ci开发的小程序命令行工具",
|
|
5
|
+
"main": "",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ci": "bin/main.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://gitee.com/dsba/sparrow-ci.git"
|
|
15
|
+
},
|
|
16
|
+
"author": "wangqi",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"chalk": "^4.1.0",
|
|
20
|
+
"commander": "^6.1.0",
|
|
21
|
+
"inquirer": "^7.3.3",
|
|
22
|
+
"log-symbols": "^4.0.0",
|
|
23
|
+
"miniprogram-ci": "^1.8.35",
|
|
24
|
+
"ora": "^5.1.0"
|
|
25
|
+
}
|
|
26
|
+
}
|