@taole/deploy-helper 1.0.0 → 1.0.2

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.
@@ -1,365 +1,365 @@
1
- import { homedir } from 'node:os';
2
- import { join } from 'node:path';
3
- import fs from 'node:fs';
4
- import simpleGit from 'simple-git';
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";
9
-
10
- import { createPipelineRunFunc, listPipelinesFunc, getPipelineFunc, getPipelineRunFunc }
11
- from '../modules/alibabacloud-devops-mcp-server/dist/operations/flow/pipeline.js'
12
-
13
- export const organizationId = "5ec8bb7bd1d1abe63b55cd33";
14
-
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 === "INIT" || 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 === "INIT" || 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
-
68
-
69
- let isFirstFoundToken = true;
70
-
71
-
72
- function getPipelineConfig(config, mode) {
73
- const pipelineCfgName = mode === "test" ? "testPipeline" : "prodPipeline";
74
- let pipelineConfig = config[pipelineCfgName];
75
- if (!pipelineConfig) {
76
- return null;
77
- }
78
- if (!Array.isArray(pipelineConfig.pipelines) || pipelineConfig.pipelines.length === 0) {
79
- throw new Error(`${pipelineCfgName}配置中请至少配置一个流水线`);
80
- }
81
- for (const [index, pipeline] of pipelineConfig.pipelines.entries()) {
82
- if (!pipeline.id && !pipeline.name) {
83
- throw new Error(`${pipelineCfgName}[${index}]配置中请至少配置id或name中的一个`);
84
- }
85
- }
86
- return pipelineConfig;
87
- }
88
-
89
- let devToken = "";
90
-
91
- export function getYunxiaoToken(pipelineConfig) {
92
- if (devToken) {
93
- if (isFirstFoundToken) {
94
- isFirstFoundToken = false;
95
- log(`将使用devToken`);
96
- }
97
- return devToken;
98
- }
99
- let token = "";
100
- if (pipelineConfig.useEnvToken !== false) {
101
- token = process.env.YUNXIAO_ACCESS_TOKEN || ""
102
- if (token) {
103
- if (isFirstFoundToken) {
104
- isFirstFoundToken = false;
105
- log(`将使用来自环境变量YUNXIAO_ACCESS_TOKEN的云效token`);
106
- }
107
- return token;
108
- }
109
- }
110
-
111
- const userDeployHelperDir = join(homedir(), "deploy-helper.config.json");
112
- if (fs.existsSync(userDeployHelperDir)) {
113
- try {
114
- const userDeployHelperConfig = JSON.parse(fs.readFileSync(userDeployHelperDir, "utf-8"));
115
- token = userDeployHelperConfig.token || "";
116
- } catch (error) {
117
- console.error(`读取${userDeployHelperDir}配置失败: ${error}`);
118
- }
119
- if (token) {
120
- if (isFirstFoundToken) {
121
- isFirstFoundToken = false;
122
- log(`将使用来自${userDeployHelperDir}的云效token`);
123
- }
124
- return token;
125
- }
126
- }
127
-
128
- return token;
129
- }
130
-
131
- export function setDevToken(token) {
132
- devToken = token;
133
- }
134
-
135
- async function getPipelineInfoByName(name) {
136
- let pipeline = null;
137
- try {
138
- const pipelines = await listPipelinesFunc(organizationId, { pipelineName: name, perPage: 50, page: 1 });
139
- if (pipelines && Array.isArray(pipelines.items) && pipelines.items.length > 0) {
140
- pipeline = pipelines.items.find(item => item.name === name);
141
- }
142
- } catch (error) {
143
- throw new Error(`获取流水线信息失败, 请确认流水线名称:【${name}】是否正确或检查云效token的权限是否足够: ${error}`);
144
- }
145
- if (!pipeline) {
146
- throw new Error(`未找到对应的流水线, 请确认流水线名称:【${name}】是否正确或检查云效token的权限是否足够`);
147
- }
148
- return pipeline;
149
- }
150
-
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 = 5000) {
159
- const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineID}/builds/${runId}`;
160
- log(`开始轮询至流水线执行完成`);
161
- let runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
162
- let passFailedCount = 0;
163
- let passFailedMaxCount = 3;
164
- while (runDetail.status === "RUNNING" || runDetail.status === "WAITING" || runDetail.status === "INIT") {
165
- const { stage, job } = getCurrentJob(runDetail);
166
- log(`流水线执行状态: ${runDetail.status},阶段: ${stage.name},任务: ${job.name},${interval / 1000}秒后再次查询`);
167
- if (runDetail.status === "WAITING") {
168
- if (stage && job) {
169
- const PassPipelineValidate = (job.actions || []).find(action => action.type === "PassPipelineValidate");
170
- if (PassPipelineValidate) {
171
- log(`尝试自动通过人工卡点`);
172
- try {
173
- const isPass = await passPipelineJob(pipelineID, runId, job.id);
174
- if (isPass) {
175
- log(`人工卡点通过`);
176
- } else {
177
- log(`人工卡点通过失败`);
178
- passFailedCount++;
179
- }
180
- } catch (error) {
181
- passFailedCount++;
182
- log(`人工卡点通过失败`, error);
183
- }
184
- }
185
- }
186
- if (passFailedCount >= passFailedMaxCount) {
187
- log(`人工卡点通过失败次数超过最大值, 放弃`);
188
- break;
189
- }
190
- }
191
- await new Promise(resolve => setTimeout(resolve, interval));
192
- runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
193
- }
194
- if (runDetail.status === "SUCCESS") {
195
- log(`流水线执行完成,当前状态: ${runDetail.status}`);
196
- } else {
197
- log(`流水线执行失败,当前状态: ${runDetail.status}, 请查看详情${pipelineRunDetailUrl}`);
198
- }
199
- return runDetail;
200
- }
201
-
202
- /**
203
- * 获取流水线信息和源码仓库信息, 不指定分支则不返回源码仓库信息
204
- * @param {*} pipelineName 流水线名称或id
205
- * @param {*} targetBranch 指定分支, 如果为空,则不指定分支
206
- * @returns 流水线信息和源码仓库信息, 触发流水线参数
207
- */
208
- async function getPipelineInfoDetail(pipelineName, targetBranch = "") {
209
- let pipelineID = "";
210
- let pipelineInfo = null;
211
- let repoInfo = null;
212
- let hasDetail = false;
213
- if (/^\d+$/.test(pipelineName)) {
214
- pipelineID = pipelineName;
215
- pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
216
- pipelineInfo.id = pipelineID;
217
- hasDetail = true;
218
- } else {
219
- const info = await getPipelineInfoByName(pipelineName);
220
- pipelineInfo = info;
221
- pipelineID = info.id;
222
- hasDetail = false;
223
- }
224
- // 如果需要指定分支,则需要获取源码仓库信息
225
- if (targetBranch) {
226
- if (!hasDetail) {
227
- pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
228
- pipelineInfo.id = pipelineID;
229
- }
230
- if (pipelineInfo && pipelineInfo.pipelineConfig && Array.isArray(pipelineInfo.pipelineConfig.sources)) {
231
- repoInfo = pipelineInfo.pipelineConfig.sources[0];
232
- }
233
- }
234
-
235
- if (targetBranch && !repoInfo) {
236
- throw new Error(`未找到源码仓库信息,请检查流水线配置是否正确`);
237
- }
238
- const params = {
239
- "envs": {
240
- "Change_Log": "deploy-helper触发", // 此处不可以带空格,否则会报错
241
- },
242
- "needCreateBranch": false,
243
- "comment": "triggered by deploy-helper"
244
- }
245
- if (targetBranch && repoInfo) {
246
- params.runningBranchs = {
247
- [`${repoInfo.data.repo}`]: targetBranch
248
- }
249
- log(`指定分支: ${targetBranch}, 源码仓库: ${repoInfo.data.repo}`);
250
- }
251
- const runParams = {
252
- params: JSON.stringify(params)
253
- };
254
- return { pipelineInfo, repoInfo, runParams };
255
- }
256
-
257
- /**
258
- * 触发流水线
259
- * @param {*} pipelineName 流水线名称或id
260
- * @param {*} waitResult 是否等待流水线执行完成
261
- */
262
- export async function triggerPipeline(pipelineName, opts = {}) {
263
- const { waitResult = false } = opts;
264
- const yunxiaoToken = getYunxiaoToken({});
265
- if (!yunxiaoToken) {
266
- throw new Error(`未设置云效token, 请检查云效token是否正确`);
267
- }
268
- const { pipelineInfo, runParams } = await getPipelineInfoDetail(pipelineName, opts.branch);
269
- const runId = await createPipelineRunFunc(organizationId, pipelineInfo.id, runParams);
270
- const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineInfo.id}/builds/${runId}`;
271
- log(`流水线${pipelineInfo.name}[${pipelineInfo.id}]触发成功,流水线执行id: ${runId}, 流水线执行详情: ${pipelineRunDetailUrl}`);
272
- if (waitResult) {
273
- await waitPipelineRunFinish(pipelineInfo.id, runId);
274
- }
275
- }
276
-
277
-
278
- /**
279
- * 从配置中确认是否需要使用云效流水线,如果需要,则检查是否配置了云效流水线token
280
- * @param {Object} config
281
- */
282
- export function checkYunxiaoToken(config, mode) {
283
- let token = "";
284
- let pipelineConfig = null;
285
- try {
286
- pipelineConfig = getPipelineConfig(config, mode);
287
- } catch (error) {
288
- // pass
289
- }
290
- if (!pipelineConfig) {
291
- // 无相关配置, 检查通过
292
- return true;
293
- }
294
- try {
295
- if (pipelineConfig) {
296
- token = getYunxiaoToken(pipelineConfig);
297
- if (token) {
298
- // 有相关配置,且token存在, 检查通过
299
- return true;
300
- }
301
- }
302
- } catch (error) {
303
- console.error(`检查云效token失败: ${error}`);
304
- }
305
- console.warn(`未设置云效token, 将不会自动触发云效流水线`);
306
- return false;
307
- }
308
-
309
- async function runSinglePipeline(pipeline, index, total) {
310
- log(`开始处理流水线: ${pipeline.name}, 当前是第${index + 1}个, 总共${total}个`);
311
- const { pipelineInfo, runParams } = await getPipelineInfoDetail(pipeline.id || pipeline.name, pipeline.branch);
312
-
313
- // 处理分支强推的问题
314
- // 将要废弃force2Branch这个配置
315
- const force2Branch = pipeline.force2Branch;
316
- if (force2Branch && force2Branch.src && force2Branch.dest) {
317
- let repo = pipeline.repo || "./";
318
- repo = join(process.cwd(), repo);
319
- if (!fs.existsSync(repo)) {
320
- log(`仓库${repo}有未提交的修改, 跳过流水线处理`);
321
- return;
322
- }
323
- // 强推到指定分支
324
- const git = simpleGit(repo);
325
- const gitStatus = await git.status();
326
- if (!gitStatus.isClean()) {
327
- log(`仓库${repo}有未提交的修改, 跳过流水线处理`);
328
- return;
329
- }
330
- if (gitStatus.current !== force2Branch.src) {
331
- log(`仓库${repo}当前分支不是${force2Branch.src}, 切换到${force2Branch.src}`);
332
- await git.checkout(force2Branch.src);
333
- log(`仓库${repo}切换到${force2Branch.src}完成`);
334
- }
335
- await git.pull();
336
- await git.push();
337
- // git push origin master-test-0513:test --force
338
- await git.raw("push", "origin", `${force2Branch.src}:${force2Branch.dest}`, "--force");
339
- log(`仓库${repo}强推${force2Branch.src}到${force2Branch.dest}完成`);
340
- }
341
- const runId = await createPipelineRunFunc(organizationId, pipelineInfo.id, runParams);
342
- const piplineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineInfo.id}/builds/${runId}`;
343
- log(`流水线${pipelineInfo.name}触发成功,流水线执行id: ${runId}, 流水线执行详情: ${piplineRunDetailUrl}`);
344
- if (pipeline.waitResult) {
345
- await waitPipelineRunFinish(pipelineInfo.id, runId);
346
- }
347
- }
348
-
349
- export async function runPipeline(config, mode) {
350
- const pipelineConfig = getPipelineConfig(config, mode);
351
- if (!pipelineConfig) {
352
- // 无相关配置, 不执行
353
- return;
354
- }
355
- const token = getYunxiaoToken(pipelineConfig);
356
- if (!token) {
357
- log(`未设置云效token, 此次流水线未自动执行: ${JSON.stringify(pipelineConfig)}`);
358
- }
359
- process.env.YUNXIAO_ACCESS_TOKEN = token;
360
-
361
- for (const [index, pipeline] of pipelineConfig.pipelines.entries()) {
362
- await runSinglePipeline(pipeline, index, pipelineConfig.pipelines.length);
363
- }
364
-
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import fs from 'node:fs';
4
+ import simpleGit from 'simple-git';
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";
9
+
10
+ import { createPipelineRunFunc, listPipelinesFunc, getPipelineFunc, getPipelineRunFunc }
11
+ from '../modules/alibabacloud-devops-mcp-server/dist/operations/flow/pipeline.js'
12
+
13
+ export const organizationId = "5ec8bb7bd1d1abe63b55cd33";
14
+
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 === "INIT" || 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 === "INIT" || 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
+
68
+
69
+ let isFirstFoundToken = true;
70
+
71
+
72
+ function getPipelineConfig(config, mode) {
73
+ const pipelineCfgName = mode === "test" ? "testPipeline" : "prodPipeline";
74
+ let pipelineConfig = config[pipelineCfgName];
75
+ if (!pipelineConfig) {
76
+ return null;
77
+ }
78
+ if (!Array.isArray(pipelineConfig.pipelines) || pipelineConfig.pipelines.length === 0) {
79
+ throw new Error(`${pipelineCfgName}配置中请至少配置一个流水线`);
80
+ }
81
+ for (const [index, pipeline] of pipelineConfig.pipelines.entries()) {
82
+ if (!pipeline.id && !pipeline.name) {
83
+ throw new Error(`${pipelineCfgName}[${index}]配置中请至少配置id或name中的一个`);
84
+ }
85
+ }
86
+ return pipelineConfig;
87
+ }
88
+
89
+ let devToken = "";
90
+
91
+ export function getYunxiaoToken(pipelineConfig) {
92
+ if (devToken) {
93
+ if (isFirstFoundToken) {
94
+ isFirstFoundToken = false;
95
+ log(`将使用devToken`);
96
+ }
97
+ return devToken;
98
+ }
99
+ let token = "";
100
+ if (pipelineConfig.useEnvToken !== false) {
101
+ token = process.env.YUNXIAO_ACCESS_TOKEN || ""
102
+ if (token) {
103
+ if (isFirstFoundToken) {
104
+ isFirstFoundToken = false;
105
+ log(`将使用来自环境变量YUNXIAO_ACCESS_TOKEN的云效token`);
106
+ }
107
+ return token;
108
+ }
109
+ }
110
+
111
+ const userDeployHelperDir = join(homedir(), "deploy-helper.config.json");
112
+ if (fs.existsSync(userDeployHelperDir)) {
113
+ try {
114
+ const userDeployHelperConfig = JSON.parse(fs.readFileSync(userDeployHelperDir, "utf-8"));
115
+ token = userDeployHelperConfig.token || "";
116
+ } catch (error) {
117
+ console.error(`读取${userDeployHelperDir}配置失败: ${error}`);
118
+ }
119
+ if (token) {
120
+ if (isFirstFoundToken) {
121
+ isFirstFoundToken = false;
122
+ log(`将使用来自${userDeployHelperDir}的云效token`);
123
+ }
124
+ return token;
125
+ }
126
+ }
127
+
128
+ return token;
129
+ }
130
+
131
+ export function setDevToken(token) {
132
+ devToken = token;
133
+ }
134
+
135
+ async function getPipelineInfoByName(name) {
136
+ let pipeline = null;
137
+ try {
138
+ const pipelines = await listPipelinesFunc(organizationId, { pipelineName: name, perPage: 50, page: 1 });
139
+ if (pipelines && Array.isArray(pipelines.items) && pipelines.items.length > 0) {
140
+ pipeline = pipelines.items.find(item => item.name === name);
141
+ }
142
+ } catch (error) {
143
+ throw new Error(`获取流水线信息失败, 请确认流水线名称:【${name}】是否正确或检查云效token的权限是否足够: ${error}`);
144
+ }
145
+ if (!pipeline) {
146
+ throw new Error(`未找到对应的流水线, 请确认流水线名称:【${name}】是否正确或检查云效token的权限是否足够`);
147
+ }
148
+ return pipeline;
149
+ }
150
+
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 = 5000) {
159
+ const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineID}/builds/${runId}`;
160
+ log(`开始轮询至流水线执行完成`);
161
+ let runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
162
+ let passFailedCount = 0;
163
+ let passFailedMaxCount = 3;
164
+ while (runDetail.status === "RUNNING" || runDetail.status === "WAITING" || runDetail.status === "INIT") {
165
+ const { stage, job } = getCurrentJob(runDetail);
166
+ log(`流水线执行状态: ${runDetail.status},阶段: ${stage.name},任务: ${job.name},${interval / 1000}秒后再次查询`);
167
+ if (runDetail.status === "WAITING") {
168
+ if (stage && job) {
169
+ const PassPipelineValidate = (job.actions || []).find(action => action.type === "PassPipelineValidate");
170
+ if (PassPipelineValidate) {
171
+ log(`尝试自动通过人工卡点`);
172
+ try {
173
+ const isPass = await passPipelineJob(pipelineID, runId, job.id);
174
+ if (isPass) {
175
+ log(`人工卡点通过`);
176
+ } else {
177
+ log(`人工卡点通过失败`);
178
+ passFailedCount++;
179
+ }
180
+ } catch (error) {
181
+ passFailedCount++;
182
+ log(`人工卡点通过失败`, error);
183
+ }
184
+ }
185
+ }
186
+ if (passFailedCount >= passFailedMaxCount) {
187
+ log(`人工卡点通过失败次数超过最大值, 放弃`);
188
+ break;
189
+ }
190
+ }
191
+ await new Promise(resolve => setTimeout(resolve, interval));
192
+ runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
193
+ }
194
+ if (runDetail.status === "SUCCESS") {
195
+ log(`流水线执行完成,当前状态: ${runDetail.status}`);
196
+ } else {
197
+ log(`流水线执行失败,当前状态: ${runDetail.status}, 请查看详情${pipelineRunDetailUrl}`);
198
+ }
199
+ return runDetail;
200
+ }
201
+
202
+ /**
203
+ * 获取流水线信息和源码仓库信息, 不指定分支则不返回源码仓库信息
204
+ * @param {*} pipelineName 流水线名称或id
205
+ * @param {*} targetBranch 指定分支, 如果为空,则不指定分支
206
+ * @returns 流水线信息和源码仓库信息, 触发流水线参数
207
+ */
208
+ async function getPipelineInfoDetail(pipelineName, targetBranch = "") {
209
+ let pipelineID = "";
210
+ let pipelineInfo = null;
211
+ let repoInfo = null;
212
+ let hasDetail = false;
213
+ if (/^\d+$/.test(pipelineName)) {
214
+ pipelineID = pipelineName;
215
+ pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
216
+ pipelineInfo.id = pipelineID;
217
+ hasDetail = true;
218
+ } else {
219
+ const info = await getPipelineInfoByName(pipelineName);
220
+ pipelineInfo = info;
221
+ pipelineID = info.id;
222
+ hasDetail = false;
223
+ }
224
+ // 如果需要指定分支,则需要获取源码仓库信息
225
+ if (targetBranch) {
226
+ if (!hasDetail) {
227
+ pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
228
+ pipelineInfo.id = pipelineID;
229
+ }
230
+ if (pipelineInfo && pipelineInfo.pipelineConfig && Array.isArray(pipelineInfo.pipelineConfig.sources)) {
231
+ repoInfo = pipelineInfo.pipelineConfig.sources[0];
232
+ }
233
+ }
234
+
235
+ if (targetBranch && !repoInfo) {
236
+ throw new Error(`未找到源码仓库信息,请检查流水线配置是否正确`);
237
+ }
238
+ const params = {
239
+ "envs": {
240
+ "Change_Log": "deploy-helper触发", // 此处不可以带空格,否则会报错
241
+ },
242
+ "needCreateBranch": false,
243
+ "comment": "triggered by deploy-helper"
244
+ }
245
+ if (targetBranch && repoInfo) {
246
+ params.runningBranchs = {
247
+ [`${repoInfo.data.repo}`]: targetBranch
248
+ }
249
+ log(`指定分支: ${targetBranch}, 源码仓库: ${repoInfo.data.repo}`);
250
+ }
251
+ const runParams = {
252
+ params: JSON.stringify(params)
253
+ };
254
+ return { pipelineInfo, repoInfo, runParams };
255
+ }
256
+
257
+ /**
258
+ * 触发流水线
259
+ * @param {*} pipelineName 流水线名称或id
260
+ * @param {*} waitResult 是否等待流水线执行完成
261
+ */
262
+ export async function triggerPipeline(pipelineName, opts = {}) {
263
+ const { waitResult = false } = opts;
264
+ const yunxiaoToken = getYunxiaoToken({});
265
+ if (!yunxiaoToken) {
266
+ throw new Error(`未设置云效token, 请检查云效token是否正确`);
267
+ }
268
+ const { pipelineInfo, runParams } = await getPipelineInfoDetail(pipelineName, opts.branch);
269
+ const runId = await createPipelineRunFunc(organizationId, pipelineInfo.id, runParams);
270
+ const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineInfo.id}/builds/${runId}`;
271
+ log(`流水线${pipelineInfo.name}[${pipelineInfo.id}]触发成功,流水线执行id: ${runId}, 流水线执行详情: ${pipelineRunDetailUrl}`);
272
+ if (waitResult) {
273
+ await waitPipelineRunFinish(pipelineInfo.id, runId);
274
+ }
275
+ }
276
+
277
+
278
+ /**
279
+ * 从配置中确认是否需要使用云效流水线,如果需要,则检查是否配置了云效流水线token
280
+ * @param {Object} config
281
+ */
282
+ export function checkYunxiaoToken(config, mode) {
283
+ let token = "";
284
+ let pipelineConfig = null;
285
+ try {
286
+ pipelineConfig = getPipelineConfig(config, mode);
287
+ } catch (error) {
288
+ // pass
289
+ }
290
+ if (!pipelineConfig) {
291
+ // 无相关配置, 检查通过
292
+ return true;
293
+ }
294
+ try {
295
+ if (pipelineConfig) {
296
+ token = getYunxiaoToken(pipelineConfig);
297
+ if (token) {
298
+ // 有相关配置,且token存在, 检查通过
299
+ return true;
300
+ }
301
+ }
302
+ } catch (error) {
303
+ console.error(`检查云效token失败: ${error}`);
304
+ }
305
+ console.warn(`未设置云效token, 将不会自动触发云效流水线`);
306
+ return false;
307
+ }
308
+
309
+ async function runSinglePipeline(pipeline, index, total) {
310
+ log(`开始处理流水线: ${pipeline.name}, 当前是第${index + 1}个, 总共${total}个`);
311
+ const { pipelineInfo, runParams } = await getPipelineInfoDetail(pipeline.id || pipeline.name, pipeline.branch);
312
+
313
+ // 处理分支强推的问题
314
+ // 将要废弃force2Branch这个配置
315
+ const force2Branch = pipeline.force2Branch;
316
+ if (force2Branch && force2Branch.src && force2Branch.dest) {
317
+ let repo = pipeline.repo || "./";
318
+ repo = join(process.cwd(), repo);
319
+ if (!fs.existsSync(repo)) {
320
+ log(`仓库${repo}有未提交的修改, 跳过流水线处理`);
321
+ return;
322
+ }
323
+ // 强推到指定分支
324
+ const git = simpleGit(repo);
325
+ const gitStatus = await git.status();
326
+ if (!gitStatus.isClean()) {
327
+ log(`仓库${repo}有未提交的修改, 跳过流水线处理`);
328
+ return;
329
+ }
330
+ if (gitStatus.current !== force2Branch.src) {
331
+ log(`仓库${repo}当前分支不是${force2Branch.src}, 切换到${force2Branch.src}`);
332
+ await git.checkout(force2Branch.src);
333
+ log(`仓库${repo}切换到${force2Branch.src}完成`);
334
+ }
335
+ await git.pull();
336
+ await git.push();
337
+ // git push origin master-test-0513:test --force
338
+ await git.raw("push", "origin", `${force2Branch.src}:${force2Branch.dest}`, "--force");
339
+ log(`仓库${repo}强推${force2Branch.src}到${force2Branch.dest}完成`);
340
+ }
341
+ const runId = await createPipelineRunFunc(organizationId, pipelineInfo.id, runParams);
342
+ const piplineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineInfo.id}/builds/${runId}`;
343
+ log(`流水线${pipelineInfo.name}触发成功,流水线执行id: ${runId}, 流水线执行详情: ${piplineRunDetailUrl}`);
344
+ if (pipeline.waitResult) {
345
+ await waitPipelineRunFinish(pipelineInfo.id, runId);
346
+ }
347
+ }
348
+
349
+ export async function runPipeline(config, mode) {
350
+ const pipelineConfig = getPipelineConfig(config, mode);
351
+ if (!pipelineConfig) {
352
+ // 无相关配置, 不执行
353
+ return;
354
+ }
355
+ const token = getYunxiaoToken(pipelineConfig);
356
+ if (!token) {
357
+ log(`未设置云效token, 此次流水线未自动执行: ${JSON.stringify(pipelineConfig)}`);
358
+ }
359
+ process.env.YUNXIAO_ACCESS_TOKEN = token;
360
+
361
+ for (const [index, pipeline] of pipelineConfig.pipelines.entries()) {
362
+ await runSinglePipeline(pipeline, index, pipelineConfig.pipelines.length);
363
+ }
364
+
365
365
  }