@taole/deploy-helper 0.2.10 → 0.3.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.
Files changed (28) hide show
  1. package/README.md +7 -1
  2. package/index.mjs +31 -6
  3. package/lib/pipelineApi.mjs +183 -0
  4. package/modules/alibabacloud-devops-mcp-server/dist/common/errors.js +69 -0
  5. package/modules/alibabacloud-devops-mcp-server/dist/common/modularTemplates.js +483 -0
  6. package/modules/alibabacloud-devops-mcp-server/dist/common/pipelineTemplates.js +19 -0
  7. package/modules/alibabacloud-devops-mcp-server/dist/common/types.js +1119 -0
  8. package/modules/alibabacloud-devops-mcp-server/dist/common/utils.js +353 -0
  9. package/modules/alibabacloud-devops-mcp-server/dist/common/version.js +1 -0
  10. package/modules/alibabacloud-devops-mcp-server/dist/index.js +1067 -0
  11. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/branches.js +144 -0
  12. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/changeRequestComments.js +89 -0
  13. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/changeRequests.js +203 -0
  14. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/compare.js +26 -0
  15. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/files.js +233 -0
  16. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/repositories.js +64 -0
  17. package/modules/alibabacloud-devops-mcp-server/dist/operations/flow/hostGroup.js +48 -0
  18. package/modules/alibabacloud-devops-mcp-server/dist/operations/flow/pipeline.js +507 -0
  19. package/modules/alibabacloud-devops-mcp-server/dist/operations/flow/pipelineJob.js +113 -0
  20. package/modules/alibabacloud-devops-mcp-server/dist/operations/flow/serviceConnection.js +23 -0
  21. package/modules/alibabacloud-devops-mcp-server/dist/operations/organization/members.js +94 -0
  22. package/modules/alibabacloud-devops-mcp-server/dist/operations/organization/organization.js +73 -0
  23. package/modules/alibabacloud-devops-mcp-server/dist/operations/packages/artifacts.js +64 -0
  24. package/modules/alibabacloud-devops-mcp-server/dist/operations/packages/repositories.js +35 -0
  25. package/modules/alibabacloud-devops-mcp-server/dist/operations/projex/project.js +206 -0
  26. package/modules/alibabacloud-devops-mcp-server/dist/operations/projex/sprint.js +30 -0
  27. package/modules/alibabacloud-devops-mcp-server/dist/operations/projex/workitem.js +264 -0
  28. package/package.json +6 -3
package/README.md CHANGED
@@ -1,2 +1,8 @@
1
1
  # deploy-helper
2
- 脚本部署用
2
+ 脚本部署用
3
+
4
+ # 关于modules/alibabacloud-devops-mcp-server
5
+
6
+ 使用里边封装的api, 但是太吵了(debug信息有点多,还没法关闭, 所以没有直接使用它的npm包)
7
+ npm包地址 https://www.npmjs.com/package/alibabacloud-devops-mcp-server
8
+
package/index.mjs CHANGED
@@ -5,6 +5,7 @@ import fs from 'fs';
5
5
  import { join, basename, dirname } from "path";
6
6
  import { simpleGit } from 'simple-git';
7
7
  import { homedir } from 'os'
8
+ import { runPipeline, checkYunxiaoToken } from './lib/pipelineApi.mjs';
8
9
 
9
10
  const TEST_SERVER_HOST = "192.168.0.35";
10
11
  /**
@@ -78,8 +79,13 @@ async function getScpClient() {
78
79
  }
79
80
 
80
81
 
82
+
83
+
81
84
  async function main() {
85
+
86
+ // await devTest();
82
87
  // console.log(`process.argv: ${process.argv[2]} ${process.argv[3]} ${process.argv[4]}`);
88
+
83
89
  // return;
84
90
  // other commands
85
91
  let command = process.argv[2];
@@ -108,7 +114,7 @@ async function main() {
108
114
  process.exit(1);
109
115
  }
110
116
  const workDir = process.cwd(); // 当前项目根目录
111
-
117
+
112
118
  const srcFilePath = join(workDir, file);
113
119
  if (!fs.existsSync(srcFilePath)) {
114
120
  console.log(`${srcFilePath}不存在`);
@@ -122,17 +128,17 @@ async function main() {
122
128
  const fileDir = dirname(srcFilePath);
123
129
  const fileName = basename(srcFilePath);
124
130
  const dest = process.argv[4] || fileName;
125
- if(dest.includes("/") || dest.includes("\\")){
131
+ if (dest.includes("/") || dest.includes("\\")) {
126
132
  console.log(`dest不能包含/或\\`);
127
133
  process.exit(1);
128
134
  }
129
135
 
130
- if(workDir !== fileDir){
136
+ if (workDir !== fileDir) {
131
137
  console.log(`仅支持scp当前目录下的文件(不带子目录)`);
132
138
  process.exit(1);
133
139
  }
134
140
 
135
- const destPath = "/home/web/website/tuwan_www/templets/static/play/" + (command === 'scp' ? '' : 'events/') + dest;
141
+ const destPath = "/home/web/website/tuwan_www/templets/static/play/" + (command === 'scp' ? '' : 'events/') + dest;
136
142
 
137
143
  const scpClient = await getScpClient();
138
144
  console.log(`scp: ${srcFilePath} -> ${TEST_SERVER_HOST}:${destPath}`);
@@ -162,6 +168,7 @@ async function main() {
162
168
  process.exit(1);
163
169
  }
164
170
 
171
+
165
172
  // 需要处理entry
166
173
  const needHandleEntry = mode === 'prod' || (mode === 'test' && !config.entry.onlyProd);
167
174
  const entryTestBranch = (config.entry && config.entry.testBranch) || "test";
@@ -169,6 +176,11 @@ async function main() {
169
176
  const entryTargetBranch = mode === 'prod' ? entryProdBranch : entryTestBranch;
170
177
  const currentProdBranch = config.prodBranch || "master";
171
178
 
179
+
180
+ // 检查流水线配置
181
+ checkYunxiaoToken(config, mode);
182
+
183
+
172
184
  // 检查项目
173
185
  // 1. 检查项目是否存在
174
186
  const assetsDest = join(workDir, config.assets.dest);
@@ -306,11 +318,19 @@ async function main() {
306
318
 
307
319
  // 7. 推送
308
320
  if (canPushAssets) {
309
- await assetsGit.push();
321
+ try {
322
+ await assetsGit.push();
323
+ } catch (error) {
324
+ await assetsGit.push();
325
+ }
310
326
  console.log(`assets push done.`);
311
327
  }
312
328
  if (canPushEntry) {
313
- await entryGit.push();
329
+ try {
330
+ await entryGit.push();
331
+ } catch (error) {
332
+ await entryGit.push();
333
+ }
314
334
  console.log(`entry push done.`);
315
335
  }
316
336
 
@@ -334,6 +354,11 @@ async function main() {
334
354
  }
335
355
  process.exit(1);
336
356
  }
357
+
358
+
359
+ // 处理触发流水线的任务
360
+ await runPipeline(config, mode);
361
+
337
362
  console.log(`deploy-helper deploy done.`);
338
363
  process.exit(0);
339
364
 
@@ -0,0 +1,183 @@
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
+
6
+ import { createPipelineRunFunc, getLatestPipelineRunFunc, listPipelinesFunc } from '../modules/alibabacloud-devops-mcp-server/dist/operations/flow/pipeline.js'
7
+
8
+ export const organizationId = "5ec8bb7bd1d1abe63b55cd33";
9
+
10
+
11
+
12
+ let isFirstFoundToken = true;
13
+
14
+
15
+ function getPipelineConfig(config, mode) {
16
+ const pipelineCfgName = mode === "test" ? "testPipeline" : "prodPipeline";
17
+ let pipelineConfig = config[pipelineCfgName];
18
+ if (!pipelineConfig) {
19
+ return null;
20
+ }
21
+ if (!Array.isArray(pipelineConfig.pipelines) || pipelineConfig.pipelines.length === 0) {
22
+ throw new Error(`${pipelineCfgName}配置中请至少配置一个流水线`);
23
+ }
24
+ for (const [index, pipeline] of pipelineConfig.pipelines.entries()) {
25
+ if (!pipeline.id && !pipeline.name) {
26
+ throw new Error(`${pipelineCfgName}[${index}]配置中请至少配置id或name中的一个`);
27
+ }
28
+ }
29
+ return pipelineConfig;
30
+ }
31
+
32
+ let devToken = "";
33
+
34
+ export function getYunxiaoToken(pipelineConfig) {
35
+ if (devToken) {
36
+ if (isFirstFoundToken) {
37
+ isFirstFoundToken = false;
38
+ console.log(`将使用devToken`);
39
+ }
40
+ return devToken;
41
+ }
42
+ let token = "";
43
+ if (pipelineConfig.useEnvToken !== false) {
44
+ token = process.env.YUNXIAO_ACCESS_TOKEN || ""
45
+ if (token) {
46
+ if (isFirstFoundToken) {
47
+ isFirstFoundToken = false;
48
+ console.log(`将使用来自环境变量YUNXIAO_ACCESS_TOKEN的云效token`);
49
+ }
50
+ return token;
51
+ }
52
+ }
53
+
54
+ const userDeployHelperDir = join(homedir(), "deploy-helper.config.json");
55
+ if (fs.existsSync(userDeployHelperDir)) {
56
+ try {
57
+ const userDeployHelperConfig = JSON.parse(fs.readFileSync(userDeployHelperDir, "utf-8"));
58
+ token = userDeployHelperConfig.token || "";
59
+ } catch (error) {
60
+ console.error(`读取${userDeployHelperDir}配置失败: ${error}`);
61
+ }
62
+ if (token) {
63
+ if (isFirstFoundToken) {
64
+ isFirstFoundToken = false;
65
+ console.log(`将使用来自${userDeployHelperDir}的云效token`);
66
+ }
67
+ return token;
68
+ }
69
+ }
70
+
71
+ return token;
72
+ }
73
+
74
+ export function setDevToken(token) {
75
+ devToken = token;
76
+ }
77
+
78
+ async function getPipelineInfoByName(name) {
79
+ let pipeline = null;
80
+ try {
81
+ const pipelines = await listPipelinesFunc(organizationId, { pipelineName: name, perPage: 1, page: 1 });
82
+ if (pipelines && Array.isArray(pipelines.items) && pipelines.items.length > 0) {
83
+ pipeline = pipelines.items[0];
84
+ }
85
+ } catch (error) {
86
+ throw new Error(`获取流水线信息失败, 请确认流水线名称: ${name} 是否正确或检查云效token的权限是否足够: ${error}`);
87
+ }
88
+ if (!pipeline) {
89
+ throw new Error(`未找到对应的流水线, 请确认流水线名称: ${name} 是否正确或检查云效token的权限是否足够`);
90
+ }
91
+ return pipeline;
92
+ }
93
+
94
+
95
+
96
+ /**
97
+ * 从配置中确认是否需要使用云效流水线,如果需要,则检查是否配置了云效流水线token
98
+ * @param {Object} config
99
+ */
100
+ export function checkYunxiaoToken(config, mode) {
101
+ let token = "";
102
+ let pipelineConfig = null;
103
+ try {
104
+ pipelineConfig = getPipelineConfig(config, mode);
105
+ } catch (error) {
106
+ // pass
107
+ }
108
+ if (!pipelineConfig) {
109
+ // 无相关配置, 检查通过
110
+ return true;
111
+ }
112
+ try {
113
+ if (pipelineConfig) {
114
+ token = getYunxiaoToken(pipelineConfig);
115
+ if (token) {
116
+ // 有相关配置,且token存在, 检查通过
117
+ return true;
118
+ }
119
+ }
120
+ } catch (error) {
121
+ console.error(`检查云效token失败: ${error}`);
122
+ }
123
+ console.warn(`未设置云效token, 将不会自动触发云效流水线`);
124
+ return false;
125
+ }
126
+
127
+ async function runSinglePipeline(pipeline, index, total) {
128
+ console.log(`开始处理流水线: ${pipeline.name}, 当前是第${index + 1}个, 总共${total}个`);
129
+ // 获取流水线信息
130
+ const pipelineInfo = await getPipelineInfoByName(pipeline.name);
131
+ console.log(`流水线${pipeline.name}的id: ${pipelineInfo.id}`);
132
+
133
+ // 处理分支强推的问题
134
+ const force2Branch = pipeline.force2Branch;
135
+ if (force2Branch && force2Branch.src && force2Branch.dest) {
136
+ let repo = pipeline.repo || "./";
137
+ repo = join(process.cwd(), repo);
138
+ if (!fs.existsSync(repo)) {
139
+ console.log(`仓库${repo}有未提交的修改, 跳过流水线处理`);
140
+ return;
141
+ }
142
+ // 强推到指定分支
143
+ const git = simpleGit(repo);
144
+ const gitStatus = await git.status();
145
+ if (!gitStatus.isClean()) {
146
+ console.log(`仓库${repo}有未提交的修改, 跳过流水线处理`);
147
+ return;
148
+ }
149
+ if (gitStatus.current !== force2Branch.src) {
150
+ console.log(`仓库${repo}当前分支不是${force2Branch.src}, 切换到${force2Branch.src}`);
151
+ await git.checkout(force2Branch.src);
152
+ console.log(`仓库${repo}切换到${force2Branch.src}完成`);
153
+ }
154
+ await git.pull();
155
+ await git.push();
156
+ // git push origin master-test-0513:test --force
157
+ await git.raw("push", "origin", `${force2Branch.src}:${force2Branch.dest}`, "--force");
158
+ console.log(`仓库${repo}强推${force2Branch.src}到${force2Branch.dest}完成`);
159
+ }
160
+ // TODO: PERF: 获取流水线信息后, 可以缓存下来, 避免每次都重新获取
161
+ // TODO: 触发流水线时, 可以传入参数, 比如分支, 比如环境, 但是不知道为什么覆盖不了。这里先不传了
162
+ const runId = await createPipelineRunFunc(organizationId, pipelineInfo.id, {});
163
+ const piplineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineInfo.id}/builds/${runId}`;
164
+ console.log(`流水线${pipelineInfo.name}触发成功,流水线执行id: ${runId}, 流水线执行详情: ${piplineRunDetailUrl}`);
165
+ }
166
+
167
+ export async function runPipeline(config, mode) {
168
+ const pipelineConfig = getPipelineConfig(config, mode);
169
+ if (!pipelineConfig) {
170
+ // 无相关配置, 不执行
171
+ return;
172
+ }
173
+ const token = getYunxiaoToken(pipelineConfig);
174
+ if (!token) {
175
+ console.log(`未设置云效token, 此次流水线未自动执行: ${JSON.stringify(pipelineConfig)}`);
176
+ }
177
+ process.env.YUNXIAO_ACCESS_TOKEN = token;
178
+
179
+ for (const [index, pipeline] of pipelineConfig.pipelines.entries()) {
180
+ await runSinglePipeline(pipeline, index, pipelineConfig.pipelines.length);
181
+ }
182
+
183
+ }
@@ -0,0 +1,69 @@
1
+ export class YunxiaoError extends Error {
2
+ status;
3
+ response;
4
+ constructor(message, status, response) {
5
+ super(message);
6
+ this.status = status;
7
+ this.response = response;
8
+ this.name = "YunxiaoError";
9
+ }
10
+ }
11
+ export class YunxiaoValidationError extends YunxiaoError {
12
+ constructor(message, status, response) {
13
+ super(message, status, response);
14
+ this.name = "YunxiaoValidationError";
15
+ }
16
+ }
17
+ export class YunxiaoResourceNotFoundError extends YunxiaoError {
18
+ constructor(resource) {
19
+ super(`Resource not found: ${resource}`, 404, { message: `${resource} not found` });
20
+ this.name = "YunxiaoResourceNotFoundError";
21
+ }
22
+ }
23
+ export class YunxiaoAuthenticationError extends YunxiaoError {
24
+ constructor(message = "Authentication failed") {
25
+ super(message, 401, { message });
26
+ this.name = "YunxiaoAuthenticationError";
27
+ }
28
+ }
29
+ export class YunxiaoPermissionError extends YunxiaoError {
30
+ constructor(message = "Insufficient permissions") {
31
+ super(message, 403, { message });
32
+ this.name = "YunxiaoPermissionError";
33
+ }
34
+ }
35
+ export class YunxiaoRateLimitError extends YunxiaoError {
36
+ resetAt;
37
+ constructor(message = "Rate limit exceeded", resetAt) {
38
+ super(message, 429, { message, reset_at: resetAt.toISOString() });
39
+ this.resetAt = resetAt;
40
+ this.name = "YunxiaoRateLimitError";
41
+ }
42
+ }
43
+ export class YunxiaoConflictError extends YunxiaoError {
44
+ constructor(message) {
45
+ super(message, 409, { message });
46
+ this.name = "YunxiaoConflictError";
47
+ }
48
+ }
49
+ export function isYunxiaoError(error) {
50
+ return error instanceof YunxiaoError;
51
+ }
52
+ export function createYunxiaoError(status, response) {
53
+ switch (status) {
54
+ case 401:
55
+ return new YunxiaoAuthenticationError(response?.message);
56
+ case 403:
57
+ return new YunxiaoPermissionError(response?.message);
58
+ case 404:
59
+ return new YunxiaoResourceNotFoundError(response?.message || "Resource");
60
+ case 409:
61
+ return new YunxiaoConflictError(response?.message || "Conflict occurred");
62
+ case 422:
63
+ return new YunxiaoValidationError(response?.message || "Validation failed", status, response);
64
+ case 429:
65
+ return new YunxiaoRateLimitError(response?.message, new Date(response?.reset_at || Date.now() + 60000));
66
+ default:
67
+ return new YunxiaoError(response?.message || "Yunxiao API error", status, response);
68
+ }
69
+ }