@taole/deploy-helper 1.0.1 → 1.0.3

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 (29) hide show
  1. package/README.md +1 -4
  2. package/lib/offlinePkg.mjs +21 -2
  3. package/lib/pipelineApi.mjs +17 -16
  4. package/lib/yunxiaoFlowApi.mjs +115 -0
  5. package/package.json +4 -5
  6. package/modules/alibabacloud-devops-mcp-server/dist/common/errors.js +0 -69
  7. package/modules/alibabacloud-devops-mcp-server/dist/common/modularTemplates.js +0 -483
  8. package/modules/alibabacloud-devops-mcp-server/dist/common/pipelineTemplates.js +0 -19
  9. package/modules/alibabacloud-devops-mcp-server/dist/common/types.js +0 -1119
  10. package/modules/alibabacloud-devops-mcp-server/dist/common/utils.js +0 -353
  11. package/modules/alibabacloud-devops-mcp-server/dist/common/version.js +0 -1
  12. package/modules/alibabacloud-devops-mcp-server/dist/index.js +0 -1067
  13. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/branches.js +0 -144
  14. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/changeRequestComments.js +0 -89
  15. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/changeRequests.js +0 -203
  16. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/compare.js +0 -26
  17. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/files.js +0 -233
  18. package/modules/alibabacloud-devops-mcp-server/dist/operations/codeup/repositories.js +0 -64
  19. package/modules/alibabacloud-devops-mcp-server/dist/operations/flow/hostGroup.js +0 -48
  20. package/modules/alibabacloud-devops-mcp-server/dist/operations/flow/pipeline.js +0 -514
  21. package/modules/alibabacloud-devops-mcp-server/dist/operations/flow/pipelineJob.js +0 -113
  22. package/modules/alibabacloud-devops-mcp-server/dist/operations/flow/serviceConnection.js +0 -23
  23. package/modules/alibabacloud-devops-mcp-server/dist/operations/organization/members.js +0 -94
  24. package/modules/alibabacloud-devops-mcp-server/dist/operations/organization/organization.js +0 -73
  25. package/modules/alibabacloud-devops-mcp-server/dist/operations/packages/artifacts.js +0 -64
  26. package/modules/alibabacloud-devops-mcp-server/dist/operations/packages/repositories.js +0 -35
  27. package/modules/alibabacloud-devops-mcp-server/dist/operations/projex/project.js +0 -206
  28. package/modules/alibabacloud-devops-mcp-server/dist/operations/projex/sprint.js +0 -30
  29. package/modules/alibabacloud-devops-mcp-server/dist/operations/projex/workitem.js +0 -264
package/README.md CHANGED
@@ -1,8 +1,5 @@
1
1
  # deploy-helper
2
2
  脚本部署用
3
3
 
4
- # 关于modules/alibabacloud-devops-mcp-server
5
-
6
- 使用里边封装的api, 但是太吵了(debug信息有点多,还没法关闭, 所以没有直接使用它的npm包)
7
- npm包地址 https://www.npmjs.com/package/alibabacloud-devops-mcp-server
4
+ 云效流水线相关 HTTP 调用已收敛为 `lib/yunxiaoFlowApi.mjs`(直接调 OpenAPI,无 MCP/Zod 依赖)。
8
5
 
@@ -13,8 +13,27 @@ function genArchive(outputPath, dir) {
13
13
  const archive = archiver('zip', {
14
14
  zlib: { level: 1 } // Sets the compression level.
15
15
  });
16
- archive.on('error', reject);
17
- archive.on("finish", resolve);
16
+ let settled = false;
17
+ const onError = (err) => {
18
+ if (settled) return;
19
+ settled = true;
20
+ try {
21
+ archive.abort();
22
+ } catch {
23
+ // ignore
24
+ }
25
+ output.destroy(err);
26
+ reject(err);
27
+ };
28
+ archive.on('error', onError);
29
+ output.on('error', onError);
30
+ // 必须等目标文件流关闭后再继续:仅 archive 'finish' 时缓冲区可能尚未完全刷盘,
31
+ // 后续 md5/上传会读到缺 END 中央目录的截断 zip(如 ADM-ZIP: No END header found)。
32
+ output.on('close', () => {
33
+ if (settled) return;
34
+ settled = true;
35
+ resolve();
36
+ });
18
37
  archive.pipe(output);
19
38
  archive.directory(dir, false);
20
39
  archive.finalize();
@@ -2,13 +2,14 @@ import { homedir } from 'node:os';
2
2
  import { join } from 'node:path';
3
3
  import fs from 'node:fs';
4
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'
5
+ import { log } from './util.mjs';
6
+ import {
7
+ yunxiaoRequest,
8
+ listPipelines,
9
+ getPipeline,
10
+ getPipelineRun,
11
+ createPipelineRun,
12
+ } from './yunxiaoFlowApi.mjs';
12
13
 
13
14
  export const organizationId = "5ec8bb7bd1d1abe63b55cd33";
14
15
 
@@ -18,7 +19,7 @@ export const organizationId = "5ec8bb7bd1d1abe63b55cd33";
18
19
 
19
20
  /**
20
21
  * 获取当前阶段和任务
21
- * @param {z.infer<typeof PipelineRunSchema>} runDetail 流水线执行详情
22
+ * @param {object} runDetail 流水线执行详情(云效 runs 接口原始结构)
22
23
  */
23
24
  function getCurrentJob(runDetail) {
24
25
  let currentStage = null;
@@ -58,7 +59,7 @@ function getCurrentJob(runDetail) {
58
59
  */
59
60
  async function passPipelineJob(pipelineID, runId, jobId) {
60
61
  const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineID}/pipelineRuns/${runId}/jobs/${jobId}/pass`;
61
- const response = await utils.yunxiaoRequest(url, {
62
+ const response = await yunxiaoRequest(url, {
62
63
  method: "POST",
63
64
  });
64
65
  return Boolean(response);
@@ -135,7 +136,7 @@ export function setDevToken(token) {
135
136
  async function getPipelineInfoByName(name) {
136
137
  let pipeline = null;
137
138
  try {
138
- const pipelines = await listPipelinesFunc(organizationId, { pipelineName: name, perPage: 50, page: 1 });
139
+ const pipelines = await listPipelines(organizationId, { pipelineName: name, perPage: 50, page: 1 });
139
140
  if (pipelines && Array.isArray(pipelines.items) && pipelines.items.length > 0) {
140
141
  pipeline = pipelines.items.find(item => item.name === name);
141
142
  }
@@ -158,7 +159,7 @@ async function getPipelineInfoByName(name) {
158
159
  async function waitPipelineRunFinish(pipelineID, runId, interval = 5000) {
159
160
  const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineID}/builds/${runId}`;
160
161
  log(`开始轮询至流水线执行完成`);
161
- let runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
162
+ let runDetail = await getPipelineRun(organizationId, pipelineID, runId);
162
163
  let passFailedCount = 0;
163
164
  let passFailedMaxCount = 3;
164
165
  while (runDetail.status === "RUNNING" || runDetail.status === "WAITING" || runDetail.status === "INIT") {
@@ -189,7 +190,7 @@ async function waitPipelineRunFinish(pipelineID, runId, interval = 5000) {
189
190
  }
190
191
  }
191
192
  await new Promise(resolve => setTimeout(resolve, interval));
192
- runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
193
+ runDetail = await getPipelineRun(organizationId, pipelineID, runId);
193
194
  }
194
195
  if (runDetail.status === "SUCCESS") {
195
196
  log(`流水线执行完成,当前状态: ${runDetail.status}`);
@@ -212,7 +213,7 @@ async function getPipelineInfoDetail(pipelineName, targetBranch = "") {
212
213
  let hasDetail = false;
213
214
  if (/^\d+$/.test(pipelineName)) {
214
215
  pipelineID = pipelineName;
215
- pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
216
+ pipelineInfo = await getPipeline(organizationId, pipelineID);
216
217
  pipelineInfo.id = pipelineID;
217
218
  hasDetail = true;
218
219
  } else {
@@ -224,7 +225,7 @@ async function getPipelineInfoDetail(pipelineName, targetBranch = "") {
224
225
  // 如果需要指定分支,则需要获取源码仓库信息
225
226
  if (targetBranch) {
226
227
  if (!hasDetail) {
227
- pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
228
+ pipelineInfo = await getPipeline(organizationId, pipelineID);
228
229
  pipelineInfo.id = pipelineID;
229
230
  }
230
231
  if (pipelineInfo && pipelineInfo.pipelineConfig && Array.isArray(pipelineInfo.pipelineConfig.sources)) {
@@ -266,7 +267,7 @@ export async function triggerPipeline(pipelineName, opts = {}) {
266
267
  throw new Error(`未设置云效token, 请检查云效token是否正确`);
267
268
  }
268
269
  const { pipelineInfo, runParams } = await getPipelineInfoDetail(pipelineName, opts.branch);
269
- const runId = await createPipelineRunFunc(organizationId, pipelineInfo.id, runParams);
270
+ const runId = await createPipelineRun(organizationId, pipelineInfo.id, runParams);
270
271
  const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineInfo.id}/builds/${runId}`;
271
272
  log(`流水线${pipelineInfo.name}[${pipelineInfo.id}]触发成功,流水线执行id: ${runId}, 流水线执行详情: ${pipelineRunDetailUrl}`);
272
273
  if (waitResult) {
@@ -338,7 +339,7 @@ async function runSinglePipeline(pipeline, index, total) {
338
339
  await git.raw("push", "origin", `${force2Branch.src}:${force2Branch.dest}`, "--force");
339
340
  log(`仓库${repo}强推${force2Branch.src}到${force2Branch.dest}完成`);
340
341
  }
341
- const runId = await createPipelineRunFunc(organizationId, pipelineInfo.id, runParams);
342
+ const runId = await createPipelineRun(organizationId, pipelineInfo.id, runParams);
342
343
  const piplineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineInfo.id}/builds/${runId}`;
343
344
  log(`流水线${pipelineInfo.name}触发成功,流水线执行id: ${runId}, 流水线执行详情: ${piplineRunDetailUrl}`);
344
345
  if (pipeline.waitResult) {
@@ -0,0 +1,115 @@
1
+ /**
2
+ * 云效 Flow OpenAPI 薄封装(仅 deploy-helper 流水线触发/查询所需),不做 Zod 等严格校验。
3
+ * 文档基址与鉴权与官方 OpenAPI 一致。
4
+ */
5
+
6
+ const DEFAULT_BASE = "https://openapi-rdc.aliyuncs.com";
7
+
8
+ function getBaseUrl() {
9
+ return process.env.YUNXIAO_API_BASE_URL || DEFAULT_BASE;
10
+ }
11
+
12
+ function buildUrl(path, query = {}) {
13
+ const base = getBaseUrl();
14
+ const full = `${base}${path.startsWith("/") ? path : `/${path}`}`;
15
+ const u = new URL(full);
16
+ for (const [k, v] of Object.entries(query)) {
17
+ if (v !== undefined && v !== null) {
18
+ u.searchParams.append(k, String(v));
19
+ }
20
+ }
21
+ return u.pathname + u.search;
22
+ }
23
+
24
+ async function parseBody(response) {
25
+ const ct = response.headers.get("content-type") || "";
26
+ if (ct.includes("application/json")) {
27
+ return response.json();
28
+ }
29
+ return response.text();
30
+ }
31
+
32
+ /**
33
+ * @param {string} urlPath 相对路径或绝对 URL
34
+ * @param {{ method?: string, body?: object, headers?: Record<string, string> }} [options]
35
+ */
36
+ export async function yunxiaoRequest(urlPath, options = {}) {
37
+ const isAbs = urlPath.startsWith("http://") || urlPath.startsWith("https://");
38
+ const url = isAbs ? urlPath : `${getBaseUrl()}${urlPath.startsWith("/") ? urlPath : `/${urlPath}`}`;
39
+ const headers = {
40
+ Accept: "application/json",
41
+ "Content-Type": "application/json",
42
+ "User-Agent": "@taole/deploy-helper/yunxiao-flow",
43
+ ...options.headers,
44
+ };
45
+ if (process.env.YUNXIAO_ACCESS_TOKEN) {
46
+ headers["x-yunxiao-token"] = process.env.YUNXIAO_ACCESS_TOKEN;
47
+ }
48
+ const res = await fetch(url, {
49
+ method: options.method || "GET",
50
+ headers,
51
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
52
+ });
53
+ const data = await parseBody(res);
54
+ if (!res.ok) {
55
+ const msg =
56
+ data && typeof data === "object" && "message" in data
57
+ ? String(data.message)
58
+ : typeof data === "string"
59
+ ? data
60
+ : res.statusText;
61
+ throw new Error(`云效 API ${res.status}: ${msg}`);
62
+ }
63
+ return data;
64
+ }
65
+
66
+ /**
67
+ * @param {string} organizationId
68
+ * @param {{ pipelineName?: string, perPage?: number, page?: number }} [query]
69
+ */
70
+ export async function listPipelines(organizationId, query = {}) {
71
+ const path = `/oapi/v1/flow/organizations/${organizationId}/pipelines`;
72
+ const url = buildUrl(path, {
73
+ pipelineName: query.pipelineName,
74
+ perPage: query.perPage,
75
+ page: query.page,
76
+ });
77
+ const response = await yunxiaoRequest(url, { method: "GET" });
78
+ let items = [];
79
+ if (Array.isArray(response)) {
80
+ items = response.map((item) => ({
81
+ ...item,
82
+ id: item.id ?? item.pipelineId,
83
+ name: item.name ?? item.pipelineName,
84
+ }));
85
+ }
86
+ return { items };
87
+ }
88
+
89
+ export async function getPipeline(organizationId, pipelineId) {
90
+ const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineId}`;
91
+ return yunxiaoRequest(url, { method: "GET" });
92
+ }
93
+
94
+ export async function getPipelineRun(organizationId, pipelineId, pipelineRunId) {
95
+ const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineId}/runs/${pipelineRunId}`;
96
+ return yunxiaoRequest(url, { method: "GET" });
97
+ }
98
+
99
+ /**
100
+ * @param {string} organizationId
101
+ * @param {string|number} pipelineId
102
+ * @param {{ params?: string }} options params 为 JSON 字符串(与云效 OpenAPI 一致)
103
+ */
104
+ export async function createPipelineRun(organizationId, pipelineId, options = {}) {
105
+ const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineId}/runs`;
106
+ const body = {};
107
+ if (options.params !== undefined) {
108
+ body.params = options.params;
109
+ }
110
+ const response = await yunxiaoRequest(url, {
111
+ method: "POST",
112
+ body,
113
+ });
114
+ return Number(response);
115
+ }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@taole/deploy-helper",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "脚本部署工具,用于将项目部署到测试环境或生产环境",
5
5
  "main": "index.mjs",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "start": "node index.mjs",
9
- "test": "node test.mjs"
9
+ "test": "node test.mjs",
10
+ "test:unit": "node --test ./test/yunxiaoFlowApi.test.mjs"
10
11
  },
11
12
  "bin": {
12
13
  "deploy-helper": "index.mjs",
@@ -18,15 +19,13 @@
18
19
  },
19
20
  "files": [
20
21
  "index.mjs",
21
- "lib",
22
- "modules/alibabacloud-devops-mcp-server/dist"
22
+ "lib"
23
23
  ],
24
24
  "keywords": [],
25
25
  "author": "",
26
26
  "license": "ISC",
27
27
  "dependencies": {
28
28
  "ali-oss": "^6.23.0",
29
- "alibabacloud-devops-mcp-server": "*",
30
29
  "archiver": "^7.0.1",
31
30
  "form-data": "^4.0.5",
32
31
  "md5-file": "^5.0.0",
@@ -1,69 +0,0 @@
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
- }