@taole/deploy-helper 0.3.7 → 0.4.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/index.mjs +10 -5
- package/lib/pipelineApi.mjs +110 -5
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -88,7 +88,6 @@ async function main() {
|
|
|
88
88
|
// 默认开启debug debug目前只是打印日志的时候额外打印时间
|
|
89
89
|
setDebug(true);
|
|
90
90
|
|
|
91
|
-
log("deploy-helper start");
|
|
92
91
|
|
|
93
92
|
// await devTest();
|
|
94
93
|
// log(`process.argv: ${process.argv[2]} ${process.argv[3]} ${process.argv[4]}`);
|
|
@@ -110,9 +109,13 @@ async function main() {
|
|
|
110
109
|
console.log(`command: scp {file} {dest} 复制文件{file}到OfficialSite测试服务器{dest}`);
|
|
111
110
|
console.log(`command: scpevt {file} 复制文件{file}到events测试服务器{file}`);
|
|
112
111
|
console.log(`command: scpevt {file} {dest} 复制文件{file}到events测试服务器{dest}`);
|
|
113
|
-
log(`command: pipeline {pipelineName|pipelineId} 触发流水线{pipelineName|pipelineId}`);
|
|
112
|
+
console.log(`command: pipeline {pipelineName|pipelineId} 触发流水线{pipelineName|pipelineId}`);
|
|
114
113
|
process.exit(0);
|
|
115
|
-
}
|
|
114
|
+
}
|
|
115
|
+
log("deploy-helper start");
|
|
116
|
+
|
|
117
|
+
// 其他命令
|
|
118
|
+
if (command === "init") {
|
|
116
119
|
await initConfigJson();
|
|
117
120
|
process.exit(0);
|
|
118
121
|
} else if (["scp", "scpevt"].includes(command)) {
|
|
@@ -160,7 +163,7 @@ async function main() {
|
|
|
160
163
|
process.exit(1);
|
|
161
164
|
}
|
|
162
165
|
try {
|
|
163
|
-
await triggerPipeline(pipelineName);
|
|
166
|
+
await triggerPipeline(pipelineName, true);
|
|
164
167
|
process.exit(0);
|
|
165
168
|
} catch (error) {
|
|
166
169
|
log(`触发流水线失败: ${error}`);
|
|
@@ -196,7 +199,7 @@ async function main() {
|
|
|
196
199
|
const entryTestBranch = (config.entry && config.entry.testBranch) || "test";
|
|
197
200
|
const entryProdBranch = (config.entry && config.entry.prodBranch) || "master";
|
|
198
201
|
const entryTargetBranch = mode === 'prod' ? entryProdBranch : entryTestBranch;
|
|
199
|
-
const currentProdBranch = config.prodBranch || "master";
|
|
202
|
+
// const currentProdBranch = config.prodBranch || "master";
|
|
200
203
|
|
|
201
204
|
|
|
202
205
|
// 检查流水线配置
|
|
@@ -344,6 +347,7 @@ async function main() {
|
|
|
344
347
|
|
|
345
348
|
// 7. 推送
|
|
346
349
|
if (canPushAssets) {
|
|
350
|
+
log(`assets push start`);
|
|
347
351
|
try {
|
|
348
352
|
await assetsGit.push();
|
|
349
353
|
} catch (error) {
|
|
@@ -352,6 +356,7 @@ async function main() {
|
|
|
352
356
|
log(`assets push done.`);
|
|
353
357
|
}
|
|
354
358
|
if (canPushEntry) {
|
|
359
|
+
log(`entry push start`);
|
|
355
360
|
try {
|
|
356
361
|
await entryGit.push();
|
|
357
362
|
} catch (error) {
|
package/lib/pipelineApi.mjs
CHANGED
|
@@ -3,12 +3,68 @@ import { join } from 'node:path';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import simpleGit from 'simple-git';
|
|
5
5
|
import { log, isDebug } from './util.mjs';
|
|
6
|
+
import { PipelineRunSchema } from "../modules/alibabacloud-devops-mcp-server/dist/common/types.js";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import * as utils from "../modules/alibabacloud-devops-mcp-server/dist/common/utils.js";
|
|
6
9
|
|
|
7
|
-
import { createPipelineRunFunc, listPipelinesFunc, getPipelineFunc }
|
|
10
|
+
import { createPipelineRunFunc, listPipelinesFunc, getPipelineFunc, getPipelineRunFunc }
|
|
11
|
+
from '../modules/alibabacloud-devops-mcp-server/dist/operations/flow/pipeline.js'
|
|
8
12
|
|
|
9
13
|
export const organizationId = "5ec8bb7bd1d1abe63b55cd33";
|
|
10
14
|
|
|
11
15
|
|
|
16
|
+
// docs: https://help.aliyun.com/zh/yunxiao/developer-reference/passpipelinevalidate
|
|
17
|
+
// POST https://{domain}/oapi/v1/flow/organizations/{organizationId}/pipelines/{pipelineId}/pipelineRuns/{pipelineRunId}/jobs/{jobId}/pass
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 获取当前阶段和任务
|
|
21
|
+
* @param {z.infer<typeof PipelineRunSchema>} runDetail 流水线执行详情
|
|
22
|
+
*/
|
|
23
|
+
function getCurrentJob(runDetail) {
|
|
24
|
+
let currentStage = null;
|
|
25
|
+
let currentJob = null;
|
|
26
|
+
const stages = runDetail.stages;
|
|
27
|
+
|
|
28
|
+
// 找到当前阶段
|
|
29
|
+
for(let i = 0; i < stages.length; i++) {
|
|
30
|
+
const stage = stages[i];
|
|
31
|
+
currentStage = stage;
|
|
32
|
+
const stageStatus = stage.stageInfo.status;
|
|
33
|
+
if(stageStatus === "RUNNING" || stageStatus === "WAITING" || stageStatus === "FAIL") {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if(currentStage) {
|
|
39
|
+
const jobs = currentStage.stageInfo.jobs;
|
|
40
|
+
for(let i = 0; i < jobs.length; i++) {
|
|
41
|
+
const job = jobs[i];
|
|
42
|
+
currentJob = job;
|
|
43
|
+
const jobStatus = job.status;
|
|
44
|
+
if(jobStatus === "RUNNING" || jobStatus === "WAITING" || jobStatus === "FAIL") {
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {stage: currentStage, job: currentJob};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 通过人工卡点
|
|
54
|
+
* @param {*} pipelineID 流水线id
|
|
55
|
+
* @param {*} runId 流水线执行id
|
|
56
|
+
* @param {*} jobId 流水线执行任务id
|
|
57
|
+
* @returns 是否通过
|
|
58
|
+
*/
|
|
59
|
+
async function passPipelineJob(pipelineID, runId, jobId) {
|
|
60
|
+
const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineID}/pipelineRuns/${runId}/jobs/${jobId}/pass`;
|
|
61
|
+
const response = await utils.yunxiaoRequest(url, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
});
|
|
64
|
+
return Boolean(response);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
12
68
|
|
|
13
69
|
let isFirstFoundToken = true;
|
|
14
70
|
|
|
@@ -92,14 +148,57 @@ async function getPipelineInfoByName(name) {
|
|
|
92
148
|
return pipeline;
|
|
93
149
|
}
|
|
94
150
|
|
|
95
|
-
|
|
151
|
+
/**
|
|
152
|
+
* 等待流水线执行完成(尝试自行通过人工卡点)
|
|
153
|
+
* @param {string|number} pipelineID 流水线id
|
|
154
|
+
* @param {string|number} runId 流水线执行id
|
|
155
|
+
* @param {number} interval 轮询间隔时间
|
|
156
|
+
* @returns 流水线执行详情
|
|
157
|
+
*/
|
|
158
|
+
async function waitPipelineRunFinish(pipelineID, runId, interval = 3000) {
|
|
159
|
+
const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineID}/builds/${runId}`;
|
|
160
|
+
log(`开始轮询至流水线执行完成`);
|
|
161
|
+
let runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
|
|
162
|
+
while (runDetail.status === "RUNNING" || runDetail.status === "WAITING") {
|
|
163
|
+
const { stage, job } = getCurrentJob(runDetail);
|
|
164
|
+
log(`流水线执行状态: ${runDetail.status},阶段: ${stage.name},任务: ${job.name},等${interval / 1000}秒后重新轮询`);
|
|
165
|
+
if (runDetail.status === "WAITING") {
|
|
166
|
+
if (stage && job) {
|
|
167
|
+
log(`尝试自动通过人工卡点`);
|
|
168
|
+
const isPass = await passPipelineJob(pipelineID, runId, job.id);
|
|
169
|
+
if (isPass) {
|
|
170
|
+
log(`人工卡点通过`);
|
|
171
|
+
} else {
|
|
172
|
+
log(`人工卡点通过失败`);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
178
|
+
runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
|
|
179
|
+
}
|
|
180
|
+
if (runDetail.status === "SUCCESS") {
|
|
181
|
+
log(`流水线执行完成,当前状态: ${runDetail.status}`);
|
|
182
|
+
} else {
|
|
183
|
+
log(`流水线执行失败,当前状态: ${runDetail.status}, 请查看详情${pipelineRunDetailUrl}`);
|
|
184
|
+
}
|
|
185
|
+
return runDetail;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 触发流水线
|
|
191
|
+
* @param {*} pipelineName 流水线名称或id
|
|
192
|
+
* @param {*} waitResult 是否等待流水线执行完成
|
|
193
|
+
*/
|
|
194
|
+
export async function triggerPipeline(pipelineName, waitResult = false) {
|
|
96
195
|
const yunxiaoToken = getYunxiaoToken({});
|
|
97
|
-
if(!yunxiaoToken) {
|
|
196
|
+
if (!yunxiaoToken) {
|
|
98
197
|
throw new Error(`未设置云效token, 请检查云效token是否正确`);
|
|
99
198
|
}
|
|
100
199
|
let pipelineID = "";
|
|
101
200
|
let name = pipelineName;
|
|
102
|
-
if(/^\d+$/.test(pipelineName)) {
|
|
201
|
+
if (/^\d+$/.test(pipelineName)) {
|
|
103
202
|
pipelineID = pipelineName;
|
|
104
203
|
const pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
|
|
105
204
|
name = pipelineInfo.name;
|
|
@@ -111,6 +210,9 @@ export async function triggerPipeline(pipelineName) {
|
|
|
111
210
|
const runId = await createPipelineRunFunc(organizationId, pipelineID, {});
|
|
112
211
|
const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineID}/builds/${runId}`;
|
|
113
212
|
log(`流水线${name}[${pipelineID}]触发成功,流水线执行id: ${runId}, 流水线执行详情: ${pipelineRunDetailUrl}`);
|
|
213
|
+
if (waitResult) {
|
|
214
|
+
await waitPipelineRunFinish(pipelineID, runId);
|
|
215
|
+
}
|
|
114
216
|
}
|
|
115
217
|
|
|
116
218
|
|
|
@@ -163,7 +265,7 @@ async function runSinglePipeline(pipeline, index, total) {
|
|
|
163
265
|
if (!fs.existsSync(repo)) {
|
|
164
266
|
log(`仓库${repo}有未提交的修改, 跳过流水线处理`);
|
|
165
267
|
return;
|
|
166
|
-
}
|
|
268
|
+
}
|
|
167
269
|
// 强推到指定分支
|
|
168
270
|
const git = simpleGit(repo);
|
|
169
271
|
const gitStatus = await git.status();
|
|
@@ -187,6 +289,9 @@ async function runSinglePipeline(pipeline, index, total) {
|
|
|
187
289
|
const runId = await createPipelineRunFunc(organizationId, pipelineId, {});
|
|
188
290
|
const piplineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineId}/builds/${runId}`;
|
|
189
291
|
log(`流水线${pipeline.name}触发成功,流水线执行id: ${runId}, 流水线执行详情: ${piplineRunDetailUrl}`);
|
|
292
|
+
if(pipeline.waitResult) {
|
|
293
|
+
await waitPipelineRunFinish(pipelineId, runId);
|
|
294
|
+
}
|
|
190
295
|
}
|
|
191
296
|
|
|
192
297
|
export async function runPipeline(config, mode) {
|