@opentapd/tplugin-cli 0.19.0
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.cloud.md +13 -0
- package/Readme.ex.md +13 -0
- package/Readme.oa.md +23 -0
- package/cli.js +109 -0
- package/config.json +12 -0
- package/index.js +5 -0
- package/lib/create.js +314 -0
- package/lib/deploy.js +278 -0
- package/lib/encrypt.js +59 -0
- package/lib/init.js +132 -0
- package/lib/install.js +59 -0
- package/lib/lint.js +55 -0
- package/lib/login.js +194 -0
- package/lib/logout.js +21 -0
- package/lib/repo.js +52 -0
- package/lib/resources.js +256 -0
- package/lib/serve.js +455 -0
- package/lib/workspace.js +57 -0
- package/package.json +79 -0
- package/scf-bin/node.js +50 -0
- package/scf-bin/python.py +59 -0
- package/util/axios.js +52 -0
- package/util/checkNodeVersion.js +21 -0
- package/util/checkPluginYaml.js +19 -0
- package/util/createDevWorkspace.js +62 -0
- package/util/enhanceErrorMessages.js +23 -0
- package/util/errorHandler.js +131 -0
- package/util/getPluginCode.js +38 -0
- package/util/selectWorkspace.js +70 -0
- package/util/session.js +119 -0
- package/util/spinner.js +14 -0
- package/util/tapd.js +14 -0
- package/util/tools.js +46 -0
- package/util/trimObjectValue.js +10 -0
package/lib/deploy.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/* eslint-disable no-restricted-syntax */
|
|
2
|
+
/**
|
|
3
|
+
* Copyright 2021 Tencent Inc. All rights reserved.
|
|
4
|
+
* Author: terrysxu@tencent.com
|
|
5
|
+
* Author: raferzeng@tencent.com
|
|
6
|
+
*
|
|
7
|
+
* The implementation of CLI: deploy
|
|
8
|
+
*/
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const yaml = require('js-yaml');
|
|
12
|
+
const spinner = require('../util/spinner');
|
|
13
|
+
const cliLint = require('./lint');
|
|
14
|
+
const inquirer = require('inquirer');
|
|
15
|
+
const tapdsdk = require('../util/tapd');
|
|
16
|
+
const tpluginConf = require('../config');
|
|
17
|
+
const SimpleGit = require('simple-git');
|
|
18
|
+
const archiver = require('archiver');
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const { get } = require('lodash');
|
|
22
|
+
|
|
23
|
+
// CLI部署
|
|
24
|
+
module.exports = async () => {
|
|
25
|
+
// 先lint检查插件格式是否正常
|
|
26
|
+
if (!await cliLint()) {
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const yamlPath = `${process.cwd()}/plugin.yaml`;
|
|
31
|
+
// 读取plugin.yaml 插件配置
|
|
32
|
+
const docObj = yaml.load(fs.readFileSync(yamlPath, 'utf8'));
|
|
33
|
+
|
|
34
|
+
if (!docObj.app || !docObj.app.code) {
|
|
35
|
+
console.log(chalk.red('找不到 app[code]标签, 请检查 plugin.yaml 格式。'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (tpluginConf.tapd.pluginEnv === 'oa') {
|
|
40
|
+
await oaEnvDeploy(docObj);
|
|
41
|
+
} else {
|
|
42
|
+
otherEnvDeploy(docObj);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// 使用timeout做sleep
|
|
47
|
+
function sleep(time) {
|
|
48
|
+
return new Promise(resolve => setTimeout(resolve, time));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// OA环境部署
|
|
52
|
+
async function oaEnvDeploy(docObj) {
|
|
53
|
+
const git = new SimpleGit();
|
|
54
|
+
|
|
55
|
+
// 获取代码提交状态
|
|
56
|
+
const status = await git.status();
|
|
57
|
+
|
|
58
|
+
// 有本地修改时,提醒手动 commit后再部署
|
|
59
|
+
if (!!status.modified.length) {
|
|
60
|
+
const statusHandle = await inquirer.prompt([{
|
|
61
|
+
type: 'confirm',
|
|
62
|
+
name: 'isBreak',
|
|
63
|
+
message: '本地存在未提交变更,是否手动提交后再进行部署',
|
|
64
|
+
default: true,
|
|
65
|
+
}]);
|
|
66
|
+
|
|
67
|
+
// 选择本地提交时,直接退出
|
|
68
|
+
if (statusHandle.isBreak) {
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 提醒通过平台进行部署
|
|
74
|
+
const tips = `通过平台部署代码: ${tpluginConf.tapd.openHost}/admin/${docObj.app.code}/codedeploy`;
|
|
75
|
+
|
|
76
|
+
// 获取本地分支列表
|
|
77
|
+
const localBranches = await git.branchLocal();
|
|
78
|
+
for (const branch in localBranches.branches) {
|
|
79
|
+
const item = localBranches.branches[branch];
|
|
80
|
+
|
|
81
|
+
if (!item.current) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 通过关键字,匹配 behind 和 ahead
|
|
86
|
+
const behind = item.label.search(/behind \d+/) >= 0;
|
|
87
|
+
const ahead = item.label.search(/ahead \d+/) >= 0;
|
|
88
|
+
|
|
89
|
+
// 本地代码不是最新时,提醒跟新代码
|
|
90
|
+
if (behind) {
|
|
91
|
+
console.log(chalk.red(`请更新到最新远端代码,再进行部署操作.\n${tips}`));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 有commit,未push的commit时,提醒是否推送并部署
|
|
96
|
+
if (ahead) {
|
|
97
|
+
const isContinue = await inquirer.prompt([{
|
|
98
|
+
type: 'confirm',
|
|
99
|
+
name: 'template',
|
|
100
|
+
message: `有未推送到远程仓库的提交,是否推送并发布[发布节点: ${item.commit} ${item.label}]`,
|
|
101
|
+
default: true,
|
|
102
|
+
}]);
|
|
103
|
+
if (!isContinue) {
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// push推送本地代码到 origin
|
|
108
|
+
await git.push('origin', item.name);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 部署 docObj.app.code 仓库 item.name 分支的 item.commit 提交点的代码
|
|
112
|
+
await deploy(item.name, item.commit, docObj.app.code);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 其他环境部署
|
|
117
|
+
function otherEnvDeploy(docObj) {
|
|
118
|
+
// 压缩本地代码
|
|
119
|
+
zipCurrentWorkspace(docObj.app.code, async (zipFilePath) => {
|
|
120
|
+
// 开始传附件到tapd
|
|
121
|
+
await uploadZipFile(docObj.app.code, zipFilePath);
|
|
122
|
+
// 删除文件
|
|
123
|
+
removeZipFile(zipFilePath);
|
|
124
|
+
// 开始触发部署流水线
|
|
125
|
+
deploy('', '', docObj.app.code);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function removeZipFile(filePath) {
|
|
130
|
+
spinner.start('开始删除本地临时压缩文件');
|
|
131
|
+
try {
|
|
132
|
+
fs.unlinkSync(filePath); // 删除文件
|
|
133
|
+
spinner.succeed('临时压缩文件删除成功!');
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error(err);
|
|
136
|
+
spinner.fail(`临时压缩文件删除失败!${err}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function zipCurrentWorkspace(code, callback) {
|
|
141
|
+
const zipPath = path.join(process.cwd(), '..');
|
|
142
|
+
spinner.start('开始压缩当前项目');
|
|
143
|
+
// 创建可写流来写数据
|
|
144
|
+
const fileName = `__${crypto.randomUUID({ disableEntropyCache: true })}.zip`;
|
|
145
|
+
const output = fs.createWriteStream(`${zipPath}/${fileName}`);
|
|
146
|
+
const archive = archiver('zip', {
|
|
147
|
+
zlib: { level: 9 },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
output.on('close', () => {
|
|
151
|
+
spinner.succeed(`压缩完成: ${archive.pointer()} total bytes`);
|
|
152
|
+
callback(`${zipPath}/${fileName}`);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
archive.on('warning', (err) => {
|
|
156
|
+
if (err.code === 'ENOENT') {
|
|
157
|
+
spinner.warn(`ENOENT:${err}`);
|
|
158
|
+
} else {
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
archive.on('error', (err) => {
|
|
164
|
+
throw err;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
archive.pipe(output);
|
|
168
|
+
|
|
169
|
+
archive.glob(
|
|
170
|
+
'**/*',
|
|
171
|
+
{
|
|
172
|
+
cwd: `${process.cwd()}/`,
|
|
173
|
+
dot: true,
|
|
174
|
+
ignore: ['node_modules/**', '.git/**'],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
prefix: `${code}/`,
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
archive.finalize();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function uploadZipFile(code, filePath) {
|
|
185
|
+
spinner.start('开始上传压缩包');
|
|
186
|
+
return uploadToCos({
|
|
187
|
+
client_id: code,
|
|
188
|
+
file: fs.createReadStream(filePath),
|
|
189
|
+
})
|
|
190
|
+
.then(() => {
|
|
191
|
+
spinner.succeed('压缩包上传成功!');
|
|
192
|
+
})
|
|
193
|
+
.catch((e) => {
|
|
194
|
+
spinner.fail('压缩包上传失败!');
|
|
195
|
+
throw e;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function uploadToCos(...args) {
|
|
200
|
+
return tapdsdk.oAuthRequest.apply(tapdsdk, [
|
|
201
|
+
'/open_user_app/upload_zip_to_cos',
|
|
202
|
+
'POST',
|
|
203
|
+
...args,
|
|
204
|
+
]);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @description 部署插件
|
|
209
|
+
* @param branch 部署分支
|
|
210
|
+
* @param commit 部署的commit点
|
|
211
|
+
* @param code 插件code
|
|
212
|
+
*/
|
|
213
|
+
async function deploy(branch, commit, code) {
|
|
214
|
+
let buildId = '';
|
|
215
|
+
try {
|
|
216
|
+
spinner.start('启动部署任务');
|
|
217
|
+
|
|
218
|
+
// 触发部署流水线
|
|
219
|
+
const { data } = await tapdsdk.request({ url: 'open_user_app/bk_pipeline_start', params: {
|
|
220
|
+
branch,
|
|
221
|
+
commit_id: commit,
|
|
222
|
+
client_id: code,
|
|
223
|
+
plugin_env: tpluginConf.tapd.pluginEnv,
|
|
224
|
+
}, method: 'POST' });
|
|
225
|
+
|
|
226
|
+
// 流水线启动失败
|
|
227
|
+
if (!data.status || !data.data || !data.data.bk_build_id) {
|
|
228
|
+
const tips = `启动部署任务失败,Err:${data.info ? data.info : ''}'`;
|
|
229
|
+
spinner.fail(tips);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 获取触发成功后的 buildId
|
|
234
|
+
buildId = data.data.bk_build_id;
|
|
235
|
+
} catch (e) {
|
|
236
|
+
spinner.fail('启动部署任务失败');
|
|
237
|
+
throw e;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
spinner.succeed('部署任务启动成功');
|
|
241
|
+
spinner.start('部署中... ...');
|
|
242
|
+
let i = 0;
|
|
243
|
+
// 轮训等待部署任务结果
|
|
244
|
+
while (i++ < 60) {
|
|
245
|
+
let buildStatus = '';
|
|
246
|
+
try {
|
|
247
|
+
const { data } = await tapdsdk.request({ url: 'open_user_app/get_build_status', params: {
|
|
248
|
+
build_id: buildId,
|
|
249
|
+
client_id: code,
|
|
250
|
+
}, method: 'GET' });
|
|
251
|
+
// 判断部署流水线状态
|
|
252
|
+
buildStatus = get(data, 'data.build_status', '');
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.log(err);
|
|
255
|
+
await sleep(5000);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 部署后,提示安装测试项目
|
|
260
|
+
if (buildStatus === 'SUCCEED') {
|
|
261
|
+
if (tpluginConf.tapd.pluginEnv === 'oa') {
|
|
262
|
+
spinner.succeed(`部署成功 => 分支[${branch}], 部署节点[${commit}]`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const tips = `前往TAPD开放平台,添加项目进行测试。${tpluginConf.tapd.openHost}/admin/${code}/testdeploy`;
|
|
266
|
+
console.log(chalk.green(tips));
|
|
267
|
+
|
|
268
|
+
return undefined;
|
|
269
|
+
} if (buildStatus === 'FAILED') {
|
|
270
|
+
spinner.fail('部署失败!');
|
|
271
|
+
return new Error('部署失败!');
|
|
272
|
+
}
|
|
273
|
+
await sleep(5000);
|
|
274
|
+
}
|
|
275
|
+
// 超时时,提醒前往平台关注部署进度
|
|
276
|
+
const deployTips = `前往TAPD开放平台,查看部署进度。${tpluginConf.tapd.openHost}/admin/${code}/codedeploy`;
|
|
277
|
+
spinner.fail(deployTips);
|
|
278
|
+
}
|
package/lib/encrypt.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2021 Tencent Inc. All rights reserved.
|
|
3
|
+
* Author: terrysxu@tencent.com
|
|
4
|
+
* Author: raferzeng@tencent.com
|
|
5
|
+
*
|
|
6
|
+
* The implementation of CLI: encrypt <value>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const yaml = require('js-yaml');
|
|
12
|
+
const spinner = require('../util/spinner');
|
|
13
|
+
const tapdsdk = require('../util/tapd');
|
|
14
|
+
const checkPluginYaml = require('../util/checkPluginYaml');
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* CLI 加密指令
|
|
19
|
+
* @param value 需要加密的值
|
|
20
|
+
* @description 根据秘钥加密 value值,并输出到控制台中
|
|
21
|
+
*/
|
|
22
|
+
module.exports = async (value) => {
|
|
23
|
+
// 校验 plugin.yaml 文件合法性
|
|
24
|
+
checkPluginYaml();
|
|
25
|
+
|
|
26
|
+
spinner.start('开始校验plugin.yaml...');
|
|
27
|
+
const yamlPath = `${process.cwd()}/plugin.yaml`;
|
|
28
|
+
|
|
29
|
+
// 加载插件配置
|
|
30
|
+
const docObj = yaml.load(fs.readFileSync(yamlPath, 'utf8'));
|
|
31
|
+
|
|
32
|
+
if (!docObj.app.code) {
|
|
33
|
+
spinner.fail('plugin.yaml 格式错误app code not found!');
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
spinner.start('开始加密数据... ...');
|
|
38
|
+
// 发起加密请求
|
|
39
|
+
const data = await encrypt(docObj.app.code, value);
|
|
40
|
+
|
|
41
|
+
if (data.data) {
|
|
42
|
+
spinner.succeed('加密完成');
|
|
43
|
+
// 输出加密结果
|
|
44
|
+
console.log(`密文: ${chalk.yellow(data.data)}`);
|
|
45
|
+
} else {
|
|
46
|
+
spinner.fail('加密失败');
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param code 插件code
|
|
52
|
+
* @param value 加密前值
|
|
53
|
+
* @return object 加密请求的返回值
|
|
54
|
+
*/
|
|
55
|
+
async function encrypt(code, value) {
|
|
56
|
+
// 发起加密请求
|
|
57
|
+
const { data } = await tapdsdk.request({ url: 'open_user_app/encrypt', params: { data: value, code }, method: 'POST' });
|
|
58
|
+
return data;
|
|
59
|
+
}
|
package/lib/init.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2021 Tencent Inc. All rights reserved.
|
|
3
|
+
* Author: terrysxu@tencent.com
|
|
4
|
+
* Author: raferzeng@tencent.com
|
|
5
|
+
*
|
|
6
|
+
* The implementation of CLI: init
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const inquirer = require('inquirer');
|
|
11
|
+
const fs = require('fs-extra');
|
|
12
|
+
const yaml = require('js-yaml');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { exec } = require('child_process');
|
|
15
|
+
const spinner = require('../util/spinner');
|
|
16
|
+
const getPluginCode = require('../util/getPluginCode');
|
|
17
|
+
const tapdsdk = require('../util/tapd');
|
|
18
|
+
const tpluginConf = require('../config');
|
|
19
|
+
|
|
20
|
+
module.exports = async (defaultReload = false) => {
|
|
21
|
+
const yamlPath = `${process.cwd()}/plugin.yaml`;
|
|
22
|
+
|
|
23
|
+
// 判断plugin.yaml 是否正常存在
|
|
24
|
+
if (!fs.pathExistsSync(yamlPath)) {
|
|
25
|
+
console.log(chalk.red('当前目录未找到plugin.yaml文件'));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 获取插件Code
|
|
30
|
+
const appCode = getPluginCode();
|
|
31
|
+
|
|
32
|
+
const envPath = `${process.cwd()}/.env.yaml`;
|
|
33
|
+
|
|
34
|
+
// 安装依赖
|
|
35
|
+
const modulesPath = path.join(process.cwd(), 'node_modules');
|
|
36
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
37
|
+
if (fs.pathExistsSync(packageJsonPath) && !fs.pathExistsSync(modulesPath)) {
|
|
38
|
+
try {
|
|
39
|
+
spinner.start('稍等片刻,正在安装依赖...');
|
|
40
|
+
// 设置npm源到 mirror.tencent.com
|
|
41
|
+
// 安装依赖
|
|
42
|
+
if (tpluginConf.tapd.pluginEnv === 'oa') {
|
|
43
|
+
await execSync(`npm config set registry ${tpluginConf.npm.registry} && npm install`);
|
|
44
|
+
} else {
|
|
45
|
+
await execSync('npm install');
|
|
46
|
+
}
|
|
47
|
+
spinner.succeed('依赖安装成功');
|
|
48
|
+
} catch (e) {
|
|
49
|
+
e.title = '依赖安装失败,请手动npm install';
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const requirementPath = path.join(process.cwd(), 'requirements.txt');
|
|
55
|
+
if (fs.pathExistsSync(requirementPath)) {
|
|
56
|
+
try {
|
|
57
|
+
spinner.start('稍等片刻,正在安装依赖...');
|
|
58
|
+
if (tpluginConf.tapd.pluginEnv === 'oa') {
|
|
59
|
+
await execSync(`pip3 install -i ${tpluginConf.pip.baseURL} --extra-index-url ${tpluginConf.pip.extralURL} -r requirements.txt -t .`);
|
|
60
|
+
} else {
|
|
61
|
+
await execSync('pip3 install -r requirements.txt -t .');
|
|
62
|
+
}
|
|
63
|
+
spinner.succeed('依赖安装成功');
|
|
64
|
+
} catch (e) {
|
|
65
|
+
spinner.fail('依赖安装失败,请手动安装requirement');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// env.yaml 配置覆盖
|
|
70
|
+
if (!defaultReload) {
|
|
71
|
+
try {
|
|
72
|
+
fs.accessSync(envPath, fs.constants.F_OK);
|
|
73
|
+
// 是否覆盖原文件
|
|
74
|
+
const continueTmpl = await inquirer.prompt([{
|
|
75
|
+
type: 'list',
|
|
76
|
+
name: 'template',
|
|
77
|
+
message: '.env.yaml文件已经存在, 继续执行将覆盖文件?',
|
|
78
|
+
choices: ['continue', 'cancel'],
|
|
79
|
+
}]);
|
|
80
|
+
|
|
81
|
+
if (continueTmpl.template !== 'continue') {
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
spinner.start('获取初始化配置... ...');
|
|
88
|
+
let data = {};
|
|
89
|
+
try {
|
|
90
|
+
// 获取初始化信息
|
|
91
|
+
data = await init(appCode);
|
|
92
|
+
spinner.succeed('初始化配置获取成功');
|
|
93
|
+
} catch (err) {
|
|
94
|
+
err.title = '获取初始化数据失败';
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
spinner.start('开始写入配置信息 .env.yaml');
|
|
99
|
+
try {
|
|
100
|
+
// 同步写数据到 .env.yaml 中
|
|
101
|
+
fs.writeFileSync(envPath, yaml.dump(data));
|
|
102
|
+
spinner.succeed('配置信息写入成功 .env.yaml');
|
|
103
|
+
} catch (err) {
|
|
104
|
+
spinner.fail(`${envPath} 写入失败. Err : ${err}`);
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* init(code): 拉取基本的插件信息
|
|
111
|
+
* @param {string} code 插件code
|
|
112
|
+
* @return {Object} 获取到的初始化信息
|
|
113
|
+
*/
|
|
114
|
+
async function init(code) {
|
|
115
|
+
// sdk调起获取请求
|
|
116
|
+
const { data } = await tapdsdk.request({ url: 'open_user_app/app_info', params: { code }, method: 'GET' });
|
|
117
|
+
if (data.status === 1 && data.data) {
|
|
118
|
+
return data.data;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`API error. Err:${data.info}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* execSync(command) 异步执行命令 command
|
|
125
|
+
*/
|
|
126
|
+
function execSync(command) {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
exec(command, (err) => {
|
|
129
|
+
err ? reject(err) : resolve();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2021 Tencent Inc. All rights reserved.
|
|
3
|
+
* Author: terrysxu@tencent.com
|
|
4
|
+
* Author: raferzeng@tencent.com
|
|
5
|
+
*
|
|
6
|
+
* The implementation of CLI: install
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const inquirer = require('inquirer');
|
|
10
|
+
const tapdsdk = require('../util/tapd');
|
|
11
|
+
const checkPluginYaml = require('../util/checkPluginYaml');
|
|
12
|
+
const selectWorkspace = require('../util/selectWorkspace');
|
|
13
|
+
const getPluginCode = require('../util/getPluginCode');
|
|
14
|
+
const spinner = require('../util/spinner');
|
|
15
|
+
const tpluginConf = require('../config');
|
|
16
|
+
|
|
17
|
+
module.exports = async () => {
|
|
18
|
+
checkPluginYaml();
|
|
19
|
+
|
|
20
|
+
const appCode = getPluginCode();
|
|
21
|
+
|
|
22
|
+
// 选择安装项目
|
|
23
|
+
const workspace = await selectWorkspace();
|
|
24
|
+
|
|
25
|
+
// 已经安装项目,重新选择时,更新到 最新版本
|
|
26
|
+
if (workspace.installed_type) {
|
|
27
|
+
const tips = `项目 [${workspace.id} : ${workspace.name}] 已经安装 [${workspace.app_version}] 版本应用. 继续执行将更新至最新版本,是否继续?`;
|
|
28
|
+
const continueTmpl = await inquirer.prompt([{
|
|
29
|
+
type: 'confirm',
|
|
30
|
+
name: 'template',
|
|
31
|
+
message: tips,
|
|
32
|
+
}]);
|
|
33
|
+
if (continueTmpl.template !== true) {
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
spinner.start(`开始安装应用到项目 [${workspace.id} : ${workspace.name}]`);
|
|
38
|
+
try {
|
|
39
|
+
// 安装最新版本插件到workspace中
|
|
40
|
+
const data = await install(appCode, workspace.id);
|
|
41
|
+
if (data.status) {
|
|
42
|
+
spinner.succeed(`最新版本应用已经安装到项目 [${workspace.id} : ${workspace.name}]. 前往测试 ${tpluginConf.tapd.tapdHost}/${workspace.pretty_name}`);
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
e.title = '安装测试项目失败';
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* install workspace to app[code]
|
|
52
|
+
* @param {string} code 插件Code
|
|
53
|
+
* @param {string} workspaceId 项目ID
|
|
54
|
+
* @param {string} [version='$LATEST'] 安装的插件版本
|
|
55
|
+
*/
|
|
56
|
+
async function install(code, workspaceId, version = '$LATEST') {
|
|
57
|
+
const { data } = await tapdsdk.request({ url: 'open_user_app/open_app_authorize', params: { client_id: code, workspace_id: workspaceId, version }, method: 'POST' });
|
|
58
|
+
return data;
|
|
59
|
+
}
|
package/lib/lint.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2021 Tencent Inc. All rights reserved.
|
|
3
|
+
* Author: terrysxu@tencent.com
|
|
4
|
+
* Author: raferzeng@tencent.com
|
|
5
|
+
*
|
|
6
|
+
* The implementation of CLI: lint
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const yaml = require('js-yaml');
|
|
11
|
+
const spinner = require('../util/spinner');
|
|
12
|
+
const tapdsdk = require('../util/tapd');
|
|
13
|
+
const checkPluginYaml = require('../util/checkPluginYaml');
|
|
14
|
+
|
|
15
|
+
// 检查插件格式
|
|
16
|
+
module.exports = async () => {
|
|
17
|
+
// 校验plugin.yaml文件
|
|
18
|
+
checkPluginYaml();
|
|
19
|
+
|
|
20
|
+
spinner.start('开始校验plugin.yaml...');
|
|
21
|
+
const yamlPath = `${process.cwd()}/plugin.yaml`;
|
|
22
|
+
|
|
23
|
+
const docObj = yaml.load(fs.readFileSync(yamlPath, 'utf8'));
|
|
24
|
+
const doc = yaml.dump(docObj);
|
|
25
|
+
|
|
26
|
+
// 发起lint,远程校验文件配置内容
|
|
27
|
+
const res = await lint(doc);
|
|
28
|
+
|
|
29
|
+
// 异常提醒
|
|
30
|
+
if (!res || !res.status) {
|
|
31
|
+
spinner.fail('plugin.yaml校验异常,请稍后重试');
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 校验通过
|
|
36
|
+
if (res && res.status) {
|
|
37
|
+
spinner.succeed('plugin.yaml校验通过!');
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* lint() 发起校验请求,验证配置具体内容
|
|
44
|
+
* @param {string} doc - plugin.yaml配置内容
|
|
45
|
+
* @returns {Object} data - 校验结果
|
|
46
|
+
*/
|
|
47
|
+
async function lint(doc) {
|
|
48
|
+
try {
|
|
49
|
+
const { data } = await tapdsdk.request({ url: 'open_user_app/lint', params: { data: doc }, method: 'POST' });
|
|
50
|
+
return data;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
e.title = 'plugin.yaml校验不通过';
|
|
53
|
+
throw e;
|
|
54
|
+
}
|
|
55
|
+
}
|