@taole/deploy-helper 0.7.4-beta.6 → 0.7.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/index.mjs +38 -62
- package/lib/offlinePkg.mjs +0 -8
- package/lib/pipelineApi.mjs +0 -1
- package/lib/util.mjs +39 -64
- package/modules/alibabacloud-devops-mcp-server/dist/operations/flow/pipeline.js +8 -1
- package/package.json +2 -3
- package/lib/tagRelease.mjs +0 -254
package/index.mjs
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { Client } from 'node-scp'
|
|
3
4
|
import fs from 'fs';
|
|
4
5
|
import { join, basename, dirname } from "path";
|
|
5
6
|
import { simpleGit } from 'simple-git';
|
|
7
|
+
import { homedir } from 'os'
|
|
6
8
|
import { runPipeline, checkYunxiaoToken, triggerPipeline } from './lib/pipelineApi.mjs';
|
|
7
|
-
import { setDebug, log, getUserDeployHelperConfig
|
|
9
|
+
import { setDebug, log, getUserDeployHelperConfig } from './lib/util.mjs';
|
|
8
10
|
import path from 'path';
|
|
9
11
|
import { fileURLToPath } from 'url';
|
|
10
12
|
import { checkOfflinePkg, syncApi as syncOfflinePkgApi } from './lib/offlinePkg.mjs';
|
|
11
|
-
import tagRelease from './lib/tagRelease.mjs';
|
|
12
|
-
|
|
13
|
-
|
|
14
13
|
|
|
15
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
15
|
const __dirname = path.dirname(__filename);
|
|
17
16
|
|
|
18
17
|
|
|
18
|
+
const TEST_SERVER_HOST = "192.168.0.35";
|
|
19
19
|
|
|
20
20
|
function fmtAssetsCommit(msg) {
|
|
21
21
|
if (msg.startsWith("-#")) {
|
|
@@ -87,6 +87,25 @@ function hasChangeMe(content) {
|
|
|
87
87
|
return content.includes("CHANGE_ME");
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
async function getScpClient() {
|
|
91
|
+
let scpClient = null;
|
|
92
|
+
// 不能放密码。。残念
|
|
93
|
+
const scpClientConfig = {
|
|
94
|
+
host: TEST_SERVER_HOST,
|
|
95
|
+
username: 'root',
|
|
96
|
+
tryKeyboard: false,
|
|
97
|
+
password: 'tuwan123!@#',
|
|
98
|
+
}
|
|
99
|
+
// const sshKeyPath = join(homedir(), ".ssh/id_rsa");
|
|
100
|
+
// if (fs.existsSync(sshKeyPath)) {
|
|
101
|
+
// scpClientConfig.privateKey = fs.readFileSync(sshKeyPath, "utf-8");
|
|
102
|
+
// } else {
|
|
103
|
+
// log(`${sshKeyPath}不存在, 建议配置本机ssh免密登录, 参考地址:https://foochane.cn/article/2019061601.html`);
|
|
104
|
+
// }
|
|
105
|
+
scpClient = await Client(scpClientConfig);
|
|
106
|
+
return scpClient;
|
|
107
|
+
}
|
|
108
|
+
|
|
90
109
|
async function getVersion() {
|
|
91
110
|
const packageJsonPath = join(__dirname, "package.json");
|
|
92
111
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
@@ -108,7 +127,7 @@ async function main() {
|
|
|
108
127
|
// return;
|
|
109
128
|
// other commands
|
|
110
129
|
let command = process.argv[2];
|
|
111
|
-
if (!["init", "prod", "test", "scp", "scpevt", "pipeline", "offlinepkgrm"
|
|
130
|
+
if (!["init", "prod", "test", "scp", "scpevt", "pipeline", "offlinepkgrm"].includes(command)) {
|
|
112
131
|
command = "help";
|
|
113
132
|
}
|
|
114
133
|
|
|
@@ -118,7 +137,6 @@ async function main() {
|
|
|
118
137
|
console.log(`command: init [-c {configFileName}] 初始化配置`);
|
|
119
138
|
console.log(`command: prod [-c {configFileName}] 生产环境部署`);
|
|
120
139
|
console.log(`command: test 测试环境部署`);
|
|
121
|
-
console.log(`command: rollback {prod|test} {tag} 回滚到指定tag, 注意不处理离线包`);
|
|
122
140
|
console.log(`command: scp {file} 复制文件{file}到OfficialSite测试服务器{file}`);
|
|
123
141
|
console.log(`command: scp {file} {dest} 复制文件{file}到OfficialSite测试服务器{dest}`);
|
|
124
142
|
console.log(`command: scpevt {file} 复制文件{file}到events测试服务器{file}`);
|
|
@@ -129,12 +147,6 @@ async function main() {
|
|
|
129
147
|
}
|
|
130
148
|
log(`deploy-helper v${version} start`);
|
|
131
149
|
|
|
132
|
-
const workDir = process.cwd(); // 当前项目根目录
|
|
133
|
-
let mode = '';
|
|
134
|
-
let configFileName = '';
|
|
135
|
-
let isDoRollback = false;
|
|
136
|
-
let rollbackCleanFunc = null;
|
|
137
|
-
|
|
138
150
|
// 其他命令
|
|
139
151
|
if (command === "init") {
|
|
140
152
|
await initConfigJson();
|
|
@@ -145,6 +157,7 @@ async function main() {
|
|
|
145
157
|
log(`file参数不能为空`);
|
|
146
158
|
process.exit(1);
|
|
147
159
|
}
|
|
160
|
+
const workDir = process.cwd(); // 当前项目根目录
|
|
148
161
|
|
|
149
162
|
const srcFilePath = join(workDir, file);
|
|
150
163
|
if (!fs.existsSync(srcFilePath)) {
|
|
@@ -214,29 +227,13 @@ async function main() {
|
|
|
214
227
|
}
|
|
215
228
|
await syncOfflinePkgApi(userDeployHelperConfig, mode, { name, remove: true, platform }, null);
|
|
216
229
|
process.exit(0);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
log(`mode参数只能是prod或test`);
|
|
222
|
-
process.exit(1);
|
|
223
|
-
}
|
|
224
|
-
let tag = process.argv[4];
|
|
225
|
-
if(!tag){
|
|
226
|
-
tag = await tagRelease.getTag(workDir, mode);
|
|
227
|
-
if(!tag){
|
|
228
|
-
log(`未找到tag`);
|
|
229
|
-
process.exit(1);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
// 做好回滚操作
|
|
233
|
-
rollbackCleanFunc = await tagRelease.rollback(workDir, tag);
|
|
234
|
-
isDoRollback = true;
|
|
235
|
-
} else {
|
|
236
|
-
// 部署
|
|
237
|
-
mode = process.argv[2] === 'prod' ? 'prod' : 'test'; // 部署环境
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const workDir = process.cwd(); // 当前项目根目录
|
|
233
|
+
const mode = process.argv[2] === 'prod' ? 'prod' : 'test'; // 部署环境
|
|
238
234
|
log(`deploy mode: ${mode}`);
|
|
239
235
|
log(`workDir: ${workDir}`);
|
|
236
|
+
let configFileName = '';
|
|
240
237
|
if(process.argv[3] === '-c'){
|
|
241
238
|
// 尝试从-c中获取配置文件名
|
|
242
239
|
configFileName = process.argv[4];
|
|
@@ -247,8 +244,6 @@ async function main() {
|
|
|
247
244
|
log(`将使用配置文件: ${configFileName}`);
|
|
248
245
|
}
|
|
249
246
|
}
|
|
250
|
-
}
|
|
251
|
-
try {
|
|
252
247
|
// 读取配置
|
|
253
248
|
const config = await loadConfig(configFileName);
|
|
254
249
|
if(!config){
|
|
@@ -262,15 +257,8 @@ async function main() {
|
|
|
262
257
|
process.exit(1);
|
|
263
258
|
}
|
|
264
259
|
|
|
265
|
-
if(!isDoRollback){ // 正常部署的时候,才会处理打tag操作
|
|
266
|
-
const canDoTag = await tagRelease.check(workDir, config, mode);
|
|
267
|
-
if(canDoTag){
|
|
268
|
-
await tagRelease.createRelease(workDir,config, mode);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
260
|
|
|
272
|
-
|
|
273
|
-
const needHandleAssets = !isDoRollback && config.assets && config.assets.dest;
|
|
261
|
+
const needHandleAssets = config.assets && config.assets.dest;
|
|
274
262
|
|
|
275
263
|
// 需要处理entry
|
|
276
264
|
const needHandleEntry = config.entry && config.entry.dest && (mode === 'prod' || (mode === 'test' && !config.entry.onlyProd));
|
|
@@ -285,7 +273,6 @@ async function main() {
|
|
|
285
273
|
const offlinePkgResult = checkOfflinePkg({
|
|
286
274
|
workDir: workDir,
|
|
287
275
|
mode,
|
|
288
|
-
isDoRollback,
|
|
289
276
|
});
|
|
290
277
|
if (!offlinePkgResult.canBuild && offlinePkgResult.errorMsg) {
|
|
291
278
|
log(`${offlinePkgResult.errorMsg}`);
|
|
@@ -493,29 +480,18 @@ async function main() {
|
|
|
493
480
|
}
|
|
494
481
|
} catch (error) {
|
|
495
482
|
log(`scp error: ${error}`);
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
483
|
+
log(`复制文件到测试环境服务器失败, 建议配置本机ssh免密登录, 参考地址:https://foochane.cn/article/2019061601.html`);
|
|
484
|
+
if (assetsFilesCount > 0) {
|
|
485
|
+
log(`不过请放心,构建产物的assets已经处理完成,只要手动处理index.html文件即可。`);
|
|
486
|
+
}
|
|
500
487
|
process.exit(1);
|
|
501
488
|
}
|
|
502
489
|
|
|
503
|
-
if(isDoRollback && rollbackCleanFunc){
|
|
504
|
-
await rollbackCleanFunc();
|
|
505
|
-
}
|
|
506
490
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
await runPipeline(config, mode);
|
|
510
|
-
} catch (error) {
|
|
511
|
-
log(`触发流水线失败: `, error);
|
|
512
|
-
}
|
|
491
|
+
// 处理触发流水线的任务
|
|
492
|
+
await runPipeline(config, mode);
|
|
513
493
|
|
|
514
|
-
|
|
515
|
-
log(`deploy-helper rollback done.`);
|
|
516
|
-
} else {
|
|
517
|
-
log(`deploy-helper deploy done.`);
|
|
518
|
-
}
|
|
494
|
+
log(`deploy-helper deploy done.`);
|
|
519
495
|
process.exit(0);
|
|
520
496
|
|
|
521
497
|
} catch (error) {
|
package/lib/offlinePkg.mjs
CHANGED
|
@@ -154,17 +154,9 @@ if(item.attrs.id=='vite-legacy-polyfill')childNode.onload=function(){System.impo
|
|
|
154
154
|
* @param {Object} config 配置
|
|
155
155
|
* @param {string} config.workDir 工作目录
|
|
156
156
|
* @param {"prod"|"test"} config.mode 部署模式
|
|
157
|
-
* @param {boolean} config.isDoRollback 是否是回滚操作
|
|
158
157
|
* @returns {CheckOfflinePkgResult} 结果
|
|
159
158
|
*/
|
|
160
159
|
export function checkOfflinePkg(config) {
|
|
161
|
-
if(config.isDoRollback){
|
|
162
|
-
log(`【注意】回滚操作,不处理离线包`);
|
|
163
|
-
return {
|
|
164
|
-
canBuild: false,
|
|
165
|
-
errorMsg: "",
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
160
|
let canBuildOfflinePkg = true;
|
|
169
161
|
let errorMsg = "";
|
|
170
162
|
const offlineConfig = getJsonConfig(config.workDir, "offline.config.json");
|
package/lib/pipelineApi.mjs
CHANGED
|
@@ -355,7 +355,6 @@ export async function runPipeline(config, mode) {
|
|
|
355
355
|
const token = getYunxiaoToken(pipelineConfig);
|
|
356
356
|
if (!token) {
|
|
357
357
|
log(`未设置云效token, 此次流水线未自动执行: ${JSON.stringify(pipelineConfig)}`);
|
|
358
|
-
return;
|
|
359
358
|
}
|
|
360
359
|
process.env.YUNXIAO_ACCESS_TOKEN = token;
|
|
361
360
|
|
package/lib/util.mjs
CHANGED
|
@@ -1,49 +1,46 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import { join } from "node:path"
|
|
3
|
-
import { homedir } from "node:os"
|
|
4
|
-
import { Client } from "node-scp";
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
import { homedir } from "node:os"
|
|
5
4
|
|
|
6
5
|
let _isDebug = false;
|
|
7
6
|
export function setDebug(debug) {
|
|
8
|
-
|
|
7
|
+
_isDebug = debug;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
export function isDebug() {
|
|
12
|
-
|
|
11
|
+
return _isDebug;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
export function log(...args) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
if (_isDebug) {
|
|
16
|
+
// 打印时间
|
|
17
|
+
console.log(`[${new Date().toLocaleTimeString()}]`, ...args);
|
|
18
|
+
} else {
|
|
19
|
+
console.log(...args);
|
|
20
|
+
}
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
export function getOssToken() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (token) {
|
|
28
|
-
log(`将使用来自环境变量TW_DH_OSS_TOKEN的token`);
|
|
29
|
-
return token;
|
|
30
|
-
}
|
|
31
|
-
const userDeployHelperDir = join(homedir(), "deploy-helper.config.json");
|
|
32
|
-
if (fs.existsSync(userDeployHelperDir)) {
|
|
33
|
-
try {
|
|
34
|
-
const userDeployHelperConfig = JSON.parse(
|
|
35
|
-
fs.readFileSync(userDeployHelperDir, "utf-8")
|
|
36
|
-
);
|
|
37
|
-
token = userDeployHelperConfig.ossToken || "";
|
|
38
|
-
} catch (error) {
|
|
39
|
-
console.error(`读取${userDeployHelperDir}配置失败: ${error}`);
|
|
40
|
-
}
|
|
24
|
+
let token = "";
|
|
25
|
+
token = process.env.TW_DH_OSS_TOKEN || ""
|
|
41
26
|
if (token) {
|
|
42
|
-
|
|
43
|
-
|
|
27
|
+
log(`将使用来自环境变量TW_DH_OSS_TOKEN的token`);
|
|
28
|
+
return token;
|
|
29
|
+
}
|
|
30
|
+
const userDeployHelperDir = join(homedir(), "deploy-helper.config.json");
|
|
31
|
+
if (fs.existsSync(userDeployHelperDir)) {
|
|
32
|
+
try {
|
|
33
|
+
const userDeployHelperConfig = JSON.parse(fs.readFileSync(userDeployHelperDir, "utf-8"));
|
|
34
|
+
token = userDeployHelperConfig.ossToken || "";
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`读取${userDeployHelperDir}配置失败: ${error}`);
|
|
37
|
+
}
|
|
38
|
+
if (token) {
|
|
39
|
+
log(`将使用来自${userDeployHelperDir}的ossToken`);
|
|
40
|
+
return token;
|
|
41
|
+
}
|
|
44
42
|
}
|
|
45
|
-
|
|
46
|
-
return token;
|
|
43
|
+
return token;
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
/**
|
|
@@ -51,36 +48,14 @@ export function getOssToken() {
|
|
|
51
48
|
* @returns {Object|null} 用户配置
|
|
52
49
|
*/
|
|
53
50
|
export function getUserDeployHelperConfig() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
console.error(`读取${userDeployHelperDir}配置失败: ${error}`);
|
|
51
|
+
const userDeployHelperDir = join(homedir(), "deploy-helper.config.json");
|
|
52
|
+
if (fs.existsSync(userDeployHelperDir)) {
|
|
53
|
+
try {
|
|
54
|
+
const userDeployHelperConfig = JSON.parse(fs.readFileSync(userDeployHelperDir, "utf-8"));
|
|
55
|
+
return userDeployHelperConfig;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`读取${userDeployHelperDir}配置失败: ${error}`);
|
|
58
|
+
}
|
|
63
59
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export const TEST_SERVER_HOST = "192.168.0.35";
|
|
69
|
-
export async function getScpClient() {
|
|
70
|
-
let scpClient = null;
|
|
71
|
-
// 不能放密码。。残念
|
|
72
|
-
const scpClientConfig = {
|
|
73
|
-
host: TEST_SERVER_HOST,
|
|
74
|
-
username: "root",
|
|
75
|
-
tryKeyboard: false,
|
|
76
|
-
password: "tuwan123!@#",
|
|
77
|
-
};
|
|
78
|
-
// const sshKeyPath = join(homedir(), ".ssh/id_rsa");
|
|
79
|
-
// if (fs.existsSync(sshKeyPath)) {
|
|
80
|
-
// scpClientConfig.privateKey = fs.readFileSync(sshKeyPath, "utf-8");
|
|
81
|
-
// } else {
|
|
82
|
-
// log(`${sshKeyPath}不存在, 建议配置本机ssh免密登录, 参考地址:https://foochane.cn/article/2019061601.html`);
|
|
83
|
-
// }
|
|
84
|
-
scpClient = await Client(scpClientConfig);
|
|
85
|
-
return scpClient;
|
|
86
|
-
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
@@ -52,7 +52,7 @@ export async function listPipelinesFunc(organizationId, options) {
|
|
|
52
52
|
queryParams.page = options.page;
|
|
53
53
|
}
|
|
54
54
|
const url = utils.buildUrl(baseUrl, queryParams);
|
|
55
|
-
|
|
55
|
+
let response = await utils.yunxiaoRequest(url, {
|
|
56
56
|
method: "GET",
|
|
57
57
|
});
|
|
58
58
|
const pagination = {
|
|
@@ -65,6 +65,13 @@ export async function listPipelinesFunc(organizationId, options) {
|
|
|
65
65
|
};
|
|
66
66
|
let items = [];
|
|
67
67
|
if (Array.isArray(response)) {
|
|
68
|
+
response = response.map((item) => ({
|
|
69
|
+
...item,
|
|
70
|
+
id: item.id || item.pipelineId,
|
|
71
|
+
name: item.name || item.pipelineName,
|
|
72
|
+
creatorAccountId: item.creatorAccountId || item.createAccountId,
|
|
73
|
+
createTime: item.createTime || item.createTime,
|
|
74
|
+
}))
|
|
68
75
|
items = response.map(item => PipelineListItemSchema.parse(item));
|
|
69
76
|
}
|
|
70
77
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taole/deploy-helper",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "脚本部署工具,用于将项目部署到测试环境或生产环境",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -24,9 +24,8 @@
|
|
|
24
24
|
"author": "",
|
|
25
25
|
"license": "ISC",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@inquirer/rawlist": "^5.2.0",
|
|
28
|
-
"ali-oss": "^6.23.0",
|
|
29
27
|
"alibabacloud-devops-mcp-server": "*",
|
|
28
|
+
"ali-oss": "^6.23.0",
|
|
30
29
|
"archiver": "^7.0.1",
|
|
31
30
|
"md5-file": "^5.0.0",
|
|
32
31
|
"node-scp": "^0.0.25",
|
package/lib/tagRelease.mjs
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
// 处理项目发布时的构建产物问题
|
|
2
|
-
|
|
3
|
-
import { log } from "./util.mjs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import simpleGit from "simple-git";
|
|
7
|
-
import rawlist from "@inquirer/rawlist";
|
|
8
|
-
|
|
9
|
-
async function getTag(workDir, mode) {
|
|
10
|
-
const packageJson = JSON.parse(
|
|
11
|
-
fs.readFileSync(path.join(workDir, "package.json"), "utf-8")
|
|
12
|
-
);
|
|
13
|
-
const prefix = getTagNamePrefix(packageJson, mode);
|
|
14
|
-
const git = simpleGit(workDir);
|
|
15
|
-
let tags = await git.tags(["-n", "--sort=-creatordate"]); // 按照创建时间排序,最新的在前
|
|
16
|
-
// console.log(`tags: ${JSON.stringify(tags)}`);
|
|
17
|
-
tags = tags.all
|
|
18
|
-
.filter((tag) => tag.startsWith(prefix))
|
|
19
|
-
.slice(0, 15) // 最多显示15个tag
|
|
20
|
-
.map((tag) => {
|
|
21
|
-
// tag是一个字符串, 从左到右找到第一个空格, 这个空格以前的内容就是tag的名称, 以后的如有, 取出来当作commit信息,并考虑tag无空格的情况
|
|
22
|
-
const spaceIndex = tag.indexOf(" ");
|
|
23
|
-
let commitInfo = "";
|
|
24
|
-
let tagName = tag;
|
|
25
|
-
if (spaceIndex !== -1) {
|
|
26
|
-
commitInfo = (tag.slice(spaceIndex + 1) || "").trim();
|
|
27
|
-
tagName = tag.slice(0, spaceIndex);
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
name: tagName,
|
|
31
|
-
commitInfo: commitInfo,
|
|
32
|
-
rawTag: tag,
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
// console.log(`formated tags: ${JSON.stringify(tags)}`);
|
|
36
|
-
let tag = null;
|
|
37
|
-
if (tags.length > 0) {
|
|
38
|
-
const propmtConfig = {
|
|
39
|
-
message: "请选择要回滚的tag版本: ",
|
|
40
|
-
choices: tags.map((item) => ({
|
|
41
|
-
name: item.rawTag,
|
|
42
|
-
value: item.name,
|
|
43
|
-
})),
|
|
44
|
-
};
|
|
45
|
-
try {
|
|
46
|
-
tag = await rawlist(propmtConfig);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
log(`选择获取tag失败`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (tag) {
|
|
52
|
-
log(`已选择tag: ${tag}`);
|
|
53
|
-
}
|
|
54
|
-
return tag;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 前置条件,项目当前是干净的,没有未提交的修改
|
|
58
|
-
|
|
59
|
-
function getTagNamePrefix(packageJson, mode) {
|
|
60
|
-
return `release_${mode}_${packageJson.name}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function getTagName(packageJson, commitId, mode) {
|
|
64
|
-
const prefix = getTagNamePrefix(packageJson, mode);
|
|
65
|
-
let tagName = prefix;
|
|
66
|
-
const date = new Date();
|
|
67
|
-
const timeStr =
|
|
68
|
-
date.getFullYear() +
|
|
69
|
-
"-" +
|
|
70
|
-
date.getMonth() +
|
|
71
|
-
1 +
|
|
72
|
-
"-" +
|
|
73
|
-
date.getDate() +
|
|
74
|
-
"-" +
|
|
75
|
-
date.getHours() +
|
|
76
|
-
"-" +
|
|
77
|
-
date.getMinutes() +
|
|
78
|
-
"-" +
|
|
79
|
-
date.getSeconds();
|
|
80
|
-
// tagName += `@${packageJson.version}`;
|
|
81
|
-
tagName += `_${commitId.slice(0, 8)}`;
|
|
82
|
-
tagName += `_${timeStr}`;
|
|
83
|
-
return tagName;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 检查是否需要打tag
|
|
88
|
-
* @param {*} config 配置对象
|
|
89
|
-
* @returns 是否需要打tag
|
|
90
|
-
*/
|
|
91
|
-
async function check(workDir, config, mode) {
|
|
92
|
-
const tagConfig = config.tag;
|
|
93
|
-
if (!tagConfig) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
let needTag = false;
|
|
97
|
-
if (tagConfig.test && mode === "test") {
|
|
98
|
-
needTag = true;
|
|
99
|
-
}
|
|
100
|
-
if (tagConfig.prod && mode === "prod") {
|
|
101
|
-
needTag = true;
|
|
102
|
-
}
|
|
103
|
-
const skipCleanCheck = false;
|
|
104
|
-
if (needTag && !skipCleanCheck) {
|
|
105
|
-
// 检查当前仓库是否干净
|
|
106
|
-
const git = simpleGit(workDir);
|
|
107
|
-
const status = await git.status();
|
|
108
|
-
if (!status.isClean()) {
|
|
109
|
-
log(`【tag】当前仓库有未提交的修改,请先提交`);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return needTag;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* 回滚到指定tag
|
|
117
|
-
* @param {string} workDir 工作目录
|
|
118
|
-
* @param {string} mode 部署模式
|
|
119
|
-
* @param {string} tag 要回滚的tag
|
|
120
|
-
* @returns Promise<function> 回滚后需要执行的清理函数
|
|
121
|
-
*/
|
|
122
|
-
async function rollback(workDir, tag) {
|
|
123
|
-
const git = simpleGit(workDir);
|
|
124
|
-
const commitId = await git.revparse("HEAD");
|
|
125
|
-
const branchInfo = await git.branch();
|
|
126
|
-
const status = await git.status();
|
|
127
|
-
if (!status.isClean()) {
|
|
128
|
-
log(`【rollback】回滚前请确保当前仓库是干净的`);
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
// log("回滚前状态:");
|
|
132
|
-
log(`branch: ${branchInfo.current}`);
|
|
133
|
-
log(`commitId: ${commitId}`);
|
|
134
|
-
const cleanfunc = async () => {
|
|
135
|
-
await git.reset(["--hard", commitId]);
|
|
136
|
-
await git.checkout(branchInfo.current);
|
|
137
|
-
};
|
|
138
|
-
try {
|
|
139
|
-
await git.checkout(tag);
|
|
140
|
-
const releaseDir = path.join(process.cwd(), ".taole_release");
|
|
141
|
-
if (!fs.existsSync(releaseDir)) {
|
|
142
|
-
log(`【rollback】.taole_release目录不存在,无法回滚`);
|
|
143
|
-
await cleanfunc();
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
const config = JSON.parse(
|
|
147
|
-
fs.readFileSync(path.join(releaseDir, "deploy.config.json"), "utf-8")
|
|
148
|
-
);
|
|
149
|
-
const entryFiles = config.entry.files;
|
|
150
|
-
Object.keys(entryFiles).forEach((key) => {
|
|
151
|
-
const src = `_${key}`;
|
|
152
|
-
const dest = key;
|
|
153
|
-
log(`rollback file: ${dest}`);
|
|
154
|
-
fs.cpSync(path.join(releaseDir, src), path.join(workDir, dest), {
|
|
155
|
-
recursive: true,
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
fs.cpSync(
|
|
159
|
-
path.join(releaseDir, "deploy.config.json"),
|
|
160
|
-
path.join(workDir, "deploy.config.json"),
|
|
161
|
-
{
|
|
162
|
-
recursive: true,
|
|
163
|
-
force: true,
|
|
164
|
-
}
|
|
165
|
-
);
|
|
166
|
-
log(`rollback file: deploy.config.json`);
|
|
167
|
-
log(`ready to re-deploy`);
|
|
168
|
-
} catch (error) {
|
|
169
|
-
await cleanfunc();
|
|
170
|
-
log(`【rollback】回滚失败: ${error}`);
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
return cleanfunc;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* 创建tag记录,用于后续的回退
|
|
178
|
-
* @param {*} config
|
|
179
|
-
*/
|
|
180
|
-
async function createRelease(workDir, config, mode) {
|
|
181
|
-
// 操作步骤:
|
|
182
|
-
// 0. 记录当前git的commit id, 记为commitId
|
|
183
|
-
// 1. 在项目目录检查是否有.taole_release目录,有则删除然后新建
|
|
184
|
-
// 2. 将配置中entry.files定义的后缀为html的文件复制到.taole_release目录下
|
|
185
|
-
// 3. 提交.taole_release目录到git,commmit 信息为 -#DH-2 整理构建产物
|
|
186
|
-
// 4. 打tag,tag名称为 v{package.json的version}.{commitId}.{yyyy_mm_dd_HH_MM}, 并推到远程仓库
|
|
187
|
-
// 5. 回退当前git的commit id到commitId
|
|
188
|
-
|
|
189
|
-
const git = simpleGit(workDir);
|
|
190
|
-
const commitId = await git.revparse("HEAD");
|
|
191
|
-
const commitMessage = (
|
|
192
|
-
await git.raw(["log", "-1", "--format=%s", commitId])
|
|
193
|
-
).trim();
|
|
194
|
-
|
|
195
|
-
log(`commitId: ${commitId}`);
|
|
196
|
-
|
|
197
|
-
const cleanfunc = async () => {
|
|
198
|
-
await git.reset(["--hard", commitId]);
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
// 1. 在项目目录检查是否有.taole_release目录,有则删除然后新建
|
|
203
|
-
const releaseDir = path.join(process.cwd(), ".taole_release");
|
|
204
|
-
if (fs.existsSync(releaseDir)) {
|
|
205
|
-
fs.rmSync(releaseDir, { recursive: true });
|
|
206
|
-
}
|
|
207
|
-
fs.mkdirSync(releaseDir);
|
|
208
|
-
|
|
209
|
-
// 2. 将配置中entry.files定义的后缀为html的文件涉及的所有html文件复制到.taole_release目录下
|
|
210
|
-
const entryFiles = config.entry.files;
|
|
211
|
-
Object.keys(entryFiles).forEach((key) => {
|
|
212
|
-
const file = key;
|
|
213
|
-
const htmlFilePath = path.join(workDir, file);
|
|
214
|
-
// log(`htmlFilePath: ${htmlFilePath}`);
|
|
215
|
-
if (fs.existsSync(htmlFilePath)) {
|
|
216
|
-
const destFile = `_${file}`;
|
|
217
|
-
fs.cpSync(htmlFilePath, path.join(releaseDir, destFile), {
|
|
218
|
-
recursive: true,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
// 2.1 将当前的config也写入.taole_release目录下
|
|
223
|
-
fs.writeFileSync(
|
|
224
|
-
path.join(releaseDir, "deploy.config.json"),
|
|
225
|
-
JSON.stringify(config, null, 2)
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
await git.add(releaseDir);
|
|
229
|
-
// await git.commit("-#DH-2 --> ${commitMessage}");
|
|
230
|
-
await git.commit(commitMessage || `-#DH-2 ${commitId.slice(0, 8)}`);
|
|
231
|
-
// log(`commitResult: ${JSON.stringify(commitResult)}`);
|
|
232
|
-
|
|
233
|
-
const packageJson = JSON.parse(
|
|
234
|
-
fs.readFileSync(path.join(workDir, "package.json"), "utf-8")
|
|
235
|
-
);
|
|
236
|
-
const tagName = getTagName(packageJson, commitId, mode);
|
|
237
|
-
// log(`tagName: ${tagName}`);
|
|
238
|
-
await git.tag([tagName]);
|
|
239
|
-
await git.push("origin", tagName);
|
|
240
|
-
log(`打tag成功: ${tagName}, 可用于后续回滚`);
|
|
241
|
-
await cleanfunc();
|
|
242
|
-
} catch (error) {
|
|
243
|
-
log(`创建tag记录失败: ${error}`);
|
|
244
|
-
await cleanfunc();
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export default {
|
|
250
|
-
getTag,
|
|
251
|
-
check,
|
|
252
|
-
createRelease,
|
|
253
|
-
rollback,
|
|
254
|
-
};
|