@taole/deploy-helper 0.2.9 → 0.3.1

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 +35 -9
  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 +5 -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,7 +5,9 @@ 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
 
10
+ const TEST_SERVER_HOST = "192.168.0.35";
9
11
  /**
10
12
  * 加载配置
11
13
  * @returns 配置对象
@@ -62,7 +64,7 @@ async function getScpClient() {
62
64
  let scpClient = null;
63
65
  // 不能放密码。。残念
64
66
  const scpClientConfig = {
65
- host: '192.168.0.35',
67
+ host: TEST_SERVER_HOST,
66
68
  username: 'root',
67
69
  tryKeyboard: true,
68
70
  }
@@ -77,8 +79,13 @@ async function getScpClient() {
77
79
  }
78
80
 
79
81
 
82
+
83
+
80
84
  async function main() {
85
+
86
+ // await devTest();
81
87
  // console.log(`process.argv: ${process.argv[2]} ${process.argv[3]} ${process.argv[4]}`);
88
+
82
89
  // return;
83
90
  // other commands
84
91
  let command = process.argv[2];
@@ -107,7 +114,7 @@ async function main() {
107
114
  process.exit(1);
108
115
  }
109
116
  const workDir = process.cwd(); // 当前项目根目录
110
-
117
+
111
118
  const srcFilePath = join(workDir, file);
112
119
  if (!fs.existsSync(srcFilePath)) {
113
120
  console.log(`${srcFilePath}不存在`);
@@ -121,20 +128,20 @@ async function main() {
121
128
  const fileDir = dirname(srcFilePath);
122
129
  const fileName = basename(srcFilePath);
123
130
  const dest = process.argv[4] || fileName;
124
- if(dest.includes("/") || dest.includes("\\")){
131
+ if (dest.includes("/") || dest.includes("\\")) {
125
132
  console.log(`dest不能包含/或\\`);
126
133
  process.exit(1);
127
134
  }
128
135
 
129
- if(workDir !== fileDir){
136
+ if (workDir !== fileDir) {
130
137
  console.log(`仅支持scp当前目录下的文件(不带子目录)`);
131
138
  process.exit(1);
132
139
  }
133
140
 
134
- 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;
135
142
 
136
143
  const scpClient = await getScpClient();
137
- console.log(`scp: ${srcFilePath} -> ${destPath}`);
144
+ console.log(`scp: ${srcFilePath} -> ${TEST_SERVER_HOST}:${destPath}`);
138
145
  await scpClient.uploadFile(srcFilePath, destPath);
139
146
  console.log(`scp done.`);
140
147
  process.exit(0);
@@ -161,6 +168,7 @@ async function main() {
161
168
  process.exit(1);
162
169
  }
163
170
 
171
+
164
172
  // 需要处理entry
165
173
  const needHandleEntry = mode === 'prod' || (mode === 'test' && !config.entry.onlyProd);
166
174
  const entryTestBranch = (config.entry && config.entry.testBranch) || "test";
@@ -168,6 +176,11 @@ async function main() {
168
176
  const entryTargetBranch = mode === 'prod' ? entryProdBranch : entryTestBranch;
169
177
  const currentProdBranch = config.prodBranch || "master";
170
178
 
179
+
180
+ // 检查流水线配置
181
+ checkYunxiaoToken(config, mode);
182
+
183
+
171
184
  // 检查项目
172
185
  // 1. 检查项目是否存在
173
186
  const assetsDest = join(workDir, config.assets.dest);
@@ -305,11 +318,19 @@ async function main() {
305
318
 
306
319
  // 7. 推送
307
320
  if (canPushAssets) {
308
- await assetsGit.push();
321
+ try {
322
+ await assetsGit.push();
323
+ } catch (error) {
324
+ await assetsGit.push();
325
+ }
309
326
  console.log(`assets push done.`);
310
327
  }
311
328
  if (canPushEntry) {
312
- await entryGit.push();
329
+ try {
330
+ await entryGit.push();
331
+ } catch (error) {
332
+ await entryGit.push();
333
+ }
313
334
  console.log(`entry push done.`);
314
335
  }
315
336
 
@@ -321,7 +342,7 @@ async function main() {
321
342
  for (const [src, dest] of Object.entries(syncTestFiles)) {
322
343
  const srcPath = join(workDir, src);
323
344
  const destPath = "/home/web/website/tuwan_www/templets/static/play/" + dest;
324
- console.log(`scp: ${srcPath} -> ${destPath}`);
345
+ console.log(`scp: ${srcPath} -> ${TEST_SERVER_HOST}:${destPath}`);
325
346
  await scpClient.uploadFile(srcPath, destPath)
326
347
  }
327
348
  }
@@ -333,6 +354,11 @@ async function main() {
333
354
  }
334
355
  process.exit(1);
335
356
  }
357
+
358
+
359
+ // 处理触发流水线的任务
360
+ await runPipeline(config, mode);
361
+
336
362
  console.log(`deploy-helper deploy done.`);
337
363
  process.exit(0);
338
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
+ }