@taole/deploy-helper 0.4.1 → 0.5.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.
package/index.mjs CHANGED
@@ -7,6 +7,12 @@ import { simpleGit } from 'simple-git';
7
7
  import { homedir } from 'os'
8
8
  import { runPipeline, checkYunxiaoToken, triggerPipeline } from './lib/pipelineApi.mjs';
9
9
  import { setDebug, log } from './lib/util.mjs';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
10
16
 
11
17
  const TEST_SERVER_HOST = "192.168.0.35";
12
18
  /**
@@ -79,10 +85,15 @@ async function getScpClient() {
79
85
  return scpClient;
80
86
  }
81
87
 
82
-
88
+ async function getVersion() {
89
+ const packageJsonPath = join(__dirname, "package.json");
90
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
91
+ return packageJson.version;
92
+ }
83
93
 
84
94
 
85
95
  async function main() {
96
+ const version = await getVersion();
86
97
 
87
98
  // process.argv.includes("--debug") ||
88
99
  // 默认开启debug debug目前只是打印日志的时候额外打印时间
@@ -100,7 +111,7 @@ async function main() {
100
111
  }
101
112
 
102
113
  if (command === "help") {
103
- console.log(`deploy-helper 部署脚本`);
114
+ console.log(`deploy-helper v${version}`);
104
115
  console.log(`usage: deploy-helper [command]`);
105
116
  console.log(`command: init 初始化配置`);
106
117
  console.log(`command: prod 生产环境部署`);
@@ -109,11 +120,11 @@ async function main() {
109
120
  console.log(`command: scp {file} {dest} 复制文件{file}到OfficialSite测试服务器{dest}`);
110
121
  console.log(`command: scpevt {file} 复制文件{file}到events测试服务器{file}`);
111
122
  console.log(`command: scpevt {file} {dest} 复制文件{file}到events测试服务器{dest}`);
112
- console.log(`command: pipeline {pipelineName|pipelineId} 触发流水线{pipelineName|pipelineId}`);
123
+ console.log(`command: pipeline {pipelineName|pipelineId} [branch] 触发流水线{pipelineName|pipelineId}, 指定分支(可省略)`);
113
124
  process.exit(0);
114
125
  }
115
- log("deploy-helper start");
116
-
126
+ log(`deploy-helper v${version} start`);
127
+
117
128
  // 其他命令
118
129
  if (command === "init") {
119
130
  await initConfigJson();
@@ -162,11 +173,12 @@ async function main() {
162
173
  log(`pipeline参数不能为空`);
163
174
  process.exit(1);
164
175
  }
176
+ const branch = process.argv[4] || "";
165
177
  try {
166
- await triggerPipeline(pipelineName, true);
178
+ await triggerPipeline(pipelineName, { waitResult: true, branch });
167
179
  process.exit(0);
168
180
  } catch (error) {
169
- log(`触发流水线失败: ${error}`);
181
+ log(`触发流水线失败: `, error);
170
182
  process.exit(1);
171
183
  }
172
184
 
@@ -181,12 +193,6 @@ async function main() {
181
193
  const config = await loadConfig();
182
194
  // log(`config`, config);
183
195
 
184
- // 检查配置格式
185
- if (!config.assets || !config.assets.dest || !config.entry || !config.entry.dest) {
186
- log(`配置格式错误, 请检查deploy.config.js或deploy.config.json`);
187
- return;
188
- }
189
-
190
196
  const result = hasChangeMe(JSON.stringify(config));
191
197
  if (result) {
192
198
  log(`配置中存在默认值(CHANGE_ME), 请先修改配置`);
@@ -194,8 +200,10 @@ async function main() {
194
200
  }
195
201
 
196
202
 
203
+ const needHandleAssets = config.assets && config.assets.dest;
204
+
197
205
  // 需要处理entry
198
- const needHandleEntry = mode === 'prod' || (mode === 'test' && !config.entry.onlyProd);
206
+ const needHandleEntry = config.entry && config.entry.dest && (mode === 'prod' || (mode === 'test' && !config.entry.onlyProd));
199
207
  const entryTestBranch = (config.entry && config.entry.testBranch) || "test";
200
208
  const entryProdBranch = (config.entry && config.entry.prodBranch) || "master";
201
209
  const entryTargetBranch = mode === 'prod' ? entryProdBranch : entryTestBranch;
@@ -208,24 +216,34 @@ async function main() {
208
216
 
209
217
  // 检查项目
210
218
  // 1. 检查项目是否存在
211
- const assetsDest = join(workDir, config.assets.dest);
212
- const entryDest = join(workDir, config.entry.dest);
213
- if (!fs.existsSync(assetsDest)) {
214
- log(`assets.dest路径不存在: ${assetsDest}, 请先创建`);
215
- return;
219
+ let assetsDest = null;
220
+ let entryDest = null;
221
+ if (needHandleAssets) {
222
+ assetsDest = join(workDir, config.assets.dest);
223
+ if (!fs.existsSync(assetsDest)) {
224
+ log(`assets.dest路径不存在: ${assetsDest}, 请先创建`);
225
+ process.exit(1);
226
+ }
216
227
  }
217
228
  // entry.dest
218
- if (needHandleEntry && !fs.existsSync(entryDest)) {
219
- log(`entry.dest路径不存在: ${entryDest}, 请先创建`);
220
- return;
229
+ if (needHandleEntry) {
230
+ entryDest = join(workDir, config.entry.dest);
231
+ if (!fs.existsSync(entryDest)) {
232
+ log(`entry.dest路径不存在: ${entryDest}, 请先创建`);
233
+ process.exit(1);
234
+ }
221
235
  }
222
236
 
223
237
  // 2. 检查项目是否clean
224
- const assetsGit = simpleGit(assetsDest);
225
- let assetsStatus = await assetsGit.status();
226
- if (!assetsStatus.isClean()) {
227
- log(`${assetsDest}目前有未提交内容, 请先处理`);
228
- return;
238
+ let assetsGit = null;
239
+ let assetsStatus = null;
240
+ if (needHandleAssets) {
241
+ assetsGit = simpleGit(assetsDest);
242
+ assetsStatus = await assetsGit.status();
243
+ if (!assetsStatus.isClean()) {
244
+ log(`${assetsDest}目前有未提交内容, 请先处理`);
245
+ return;
246
+ }
229
247
  }
230
248
 
231
249
  let entryGit;
@@ -255,7 +273,7 @@ async function main() {
255
273
 
256
274
  // 3. 检查assets项目分支,并切换至master分支
257
275
  // assets项目固定使用master分支
258
- if (assetsStatus.current !== "master") {
276
+ if (needHandleAssets && assetsStatus.current !== "master") {
259
277
  log(`${assetsDest}当前分支不是master, 切换至master分支`);
260
278
  await assetsGit.checkout("master");
261
279
  log(`${assetsDest}切换至master分支成功`);
@@ -267,9 +285,11 @@ async function main() {
267
285
  }
268
286
 
269
287
  // 4. 执行git pull
270
- log(`${assetsDest}执行git pull`);
271
- await assetsGit.pull();
272
- log(`${assetsDest} git pull done`);
288
+ if (assetsGit) {
289
+ log(`${assetsDest}执行git pull`);
290
+ await assetsGit.pull();
291
+ log(`${assetsDest} git pull done`);
292
+ }
273
293
  if (entryGit) {
274
294
  log(`${entryDest}执行git pull`);
275
295
  await entryGit.pull();
@@ -278,24 +298,26 @@ async function main() {
278
298
 
279
299
  // 5. 复制构建产物
280
300
  // 5.1 复制assets
281
- let assetsFilesCount = 0;
282
- const assetsFiles = config.assets.files;
283
- for (const [src, dest] of Object.entries(assetsFiles)) {
284
- const srcPath = join(workDir, src);
285
- if (!fs.existsSync(srcPath)) {
286
- log(`${srcPath}不存在,请确认构建产物是否正确`);
287
- return;
288
- }
289
- const destPath = join(assetsDest, dest);
290
- if (fs.statSync(srcPath).isDirectory()) {
291
- fs.cpSync(srcPath, destPath, { recursive: true });
292
- } else {
293
- fs.copyFileSync(srcPath, destPath);
301
+ if (needHandleAssets) {
302
+ let assetsFilesCount = 0;
303
+ const assetsFiles = config.assets.files;
304
+ for (const [src, dest] of Object.entries(assetsFiles)) {
305
+ const srcPath = join(workDir, src);
306
+ if (!fs.existsSync(srcPath)) {
307
+ log(`${srcPath}不存在,请确认构建产物是否正确`);
308
+ return;
309
+ }
310
+ const destPath = join(assetsDest, dest);
311
+ if (fs.statSync(srcPath).isDirectory()) {
312
+ fs.cpSync(srcPath, destPath, { recursive: true });
313
+ } else {
314
+ fs.copyFileSync(srcPath, destPath);
315
+ }
316
+ log(`assets copy: ${srcPath} -> ${destPath}`);
317
+ assetsFilesCount++;
294
318
  }
295
- log(`assets copy: ${srcPath} -> ${destPath}`);
296
- assetsFilesCount++;
319
+ // log(`assets copy done.`);
297
320
  }
298
- // log(`assets copy done.`);
299
321
  // 5.2 复制entry
300
322
  if (needHandleEntry) {
301
323
  const entryFiles = config.entry.files;
@@ -319,18 +341,19 @@ async function main() {
319
341
  // 6. 提交
320
342
  let canPushAssets = false;
321
343
  let canPushEntry = false;
322
- assetsStatus = await assetsGit.status();
323
- if (!assetsStatus.isClean()) {
324
- canPushAssets = true;
325
- await assetsGit.add(".");
326
- const assetsCommit = `${config.assets.commit || "feat:部署资源文件"} by deploy-helper`;
327
- const assetsCommitResult = await assetsGit.commit(assetsCommit);
328
- log(`assets commit: ${assetsCommit}`);
329
- log(`assets commit: ${JSON.stringify(assetsCommitResult.summary)}`);
330
- } else {
331
- log(`${assetsDest} 未发现修改内容,跳过提交`);
344
+ if (needHandleAssets) {
345
+ assetsStatus = await assetsGit.status();
346
+ if (!assetsStatus.isClean()) {
347
+ canPushAssets = true;
348
+ await assetsGit.add(".");
349
+ const assetsCommit = `${config.assets.commit || "feat:部署资源文件"} by deploy-helper`;
350
+ const assetsCommitResult = await assetsGit.commit(assetsCommit);
351
+ log(`assets commit: ${assetsCommit}`);
352
+ log(`assets commit: ${JSON.stringify(assetsCommitResult.summary)}`);
353
+ } else {
354
+ log(`${assetsDest} 未发现修改内容,跳过提交`);
355
+ }
332
356
  }
333
-
334
357
  if (needHandleEntry) {
335
358
  entryStatus = await entryGit.status();
336
359
  if (!entryStatus.isClean()) {
@@ -26,27 +26,27 @@ function getCurrentJob(runDetail) {
26
26
  const stages = runDetail.stages;
27
27
 
28
28
  // 找到当前阶段
29
- for(let i = 0; i < stages.length; i++) {
29
+ for (let i = 0; i < stages.length; i++) {
30
30
  const stage = stages[i];
31
31
  currentStage = stage;
32
32
  const stageStatus = stage.stageInfo.status;
33
- if(stageStatus === "RUNNING" || stageStatus === "WAITING" || stageStatus === "FAIL") {
33
+ if (stageStatus === "INIT" || stageStatus === "RUNNING" || stageStatus === "WAITING" || stageStatus === "FAIL") {
34
34
  break;
35
35
  }
36
36
  }
37
37
 
38
- if(currentStage) {
38
+ if (currentStage) {
39
39
  const jobs = currentStage.stageInfo.jobs;
40
- for(let i = 0; i < jobs.length; i++) {
40
+ for (let i = 0; i < jobs.length; i++) {
41
41
  const job = jobs[i];
42
42
  currentJob = job;
43
43
  const jobStatus = job.status;
44
- if(jobStatus === "RUNNING" || jobStatus === "WAITING" || jobStatus === "FAIL") {
44
+ if (jobStatus === "INIT" || jobStatus === "RUNNING" || jobStatus === "WAITING" || jobStatus === "FAIL") {
45
45
  break;
46
46
  }
47
47
  }
48
48
  }
49
- return {stage: currentStage, job: currentJob};
49
+ return { stage: currentStage, job: currentJob };
50
50
  }
51
51
 
52
52
  /**
@@ -161,9 +161,9 @@ async function waitPipelineRunFinish(pipelineID, runId, interval = 5000) {
161
161
  let runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
162
162
  let passFailedCount = 0;
163
163
  let passFailedMaxCount = 3;
164
- while (runDetail.status === "RUNNING" || runDetail.status === "WAITING") {
164
+ while (runDetail.status === "RUNNING" || runDetail.status === "WAITING" || runDetail.status === "INIT") {
165
165
  const { stage, job } = getCurrentJob(runDetail);
166
- log(`流水线执行状态: ${runDetail.status},阶段: ${stage.name},任务: ${job.name},等${interval / 1000}秒后重新轮询`);
166
+ log(`流水线执行状态: ${runDetail.status},阶段: ${stage.name},任务: ${job.name},${interval / 1000}秒后再次查询`);
167
167
  if (runDetail.status === "WAITING") {
168
168
  if (stage && job) {
169
169
  log(`尝试自动通过人工卡点`);
@@ -179,9 +179,8 @@ async function waitPipelineRunFinish(pipelineID, runId, interval = 5000) {
179
179
  passFailedCount++;
180
180
  log(`人工卡点通过失败`, error);
181
181
  }
182
-
183
182
  }
184
- if(passFailedCount >= passFailedMaxCount) {
183
+ if (passFailedCount >= passFailedMaxCount) {
185
184
  log(`人工卡点通过失败次数超过最大值, 放弃`);
186
185
  break;
187
186
  }
@@ -197,33 +196,78 @@ async function waitPipelineRunFinish(pipelineID, runId, interval = 5000) {
197
196
  return runDetail;
198
197
  }
199
198
 
199
+ /**
200
+ * 获取流水线信息和源码仓库信息, 不指定分支则不返回源码仓库信息
201
+ * @param {*} pipelineName 流水线名称或id
202
+ * @param {*} targetBranch 指定分支, 如果为空,则不指定分支
203
+ * @returns 流水线信息和源码仓库信息, 触发流水线参数
204
+ */
205
+ async function getPipelineInfoDetail(pipelineName, targetBranch = ""){
206
+ let pipelineID = "";
207
+ let pipelineInfo = null;
208
+ let repoInfo = null;
209
+ let hasDetail = false;
210
+ if (/^\d+$/.test(pipelineName)) {
211
+ pipelineID = pipelineName;
212
+ pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
213
+ pipelineInfo.id = pipelineID;
214
+ hasDetail = true;
215
+ } else {
216
+ const info = await getPipelineInfoByName(pipelineName);
217
+ pipelineInfo = info;
218
+ pipelineID = info.id;
219
+ hasDetail = false;
220
+ }
221
+ // 如果需要指定分支,则需要获取源码仓库信息
222
+ if (targetBranch) {
223
+ if (!hasDetail) {
224
+ pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
225
+ pipelineInfo.id = pipelineID;
226
+ }
227
+ if (pipelineInfo && pipelineInfo.pipelineConfig && Array.isArray(pipelineInfo.pipelineConfig.sources)) {
228
+ repoInfo = pipelineInfo.pipelineConfig.sources[0];
229
+ }
230
+ }
231
+
232
+ if (targetBranch && !repoInfo) {
233
+ throw new Error(`未找到源码仓库信息,请检查流水线配置是否正确`);
234
+ }
235
+ const params = {
236
+ "envs": {
237
+ "Change_Log": "deploy-helper触发", // 此处不可以带空格,否则会报错
238
+ },
239
+ "needCreateBranch": false,
240
+ "comment": "triggered by deploy-helper"
241
+ }
242
+ if (targetBranch && repoInfo) {
243
+ params.runningBranchs = {
244
+ [`${repoInfo.data.repo}`]: targetBranch
245
+ }
246
+ log(`指定分支: ${targetBranch}, 源码仓库: ${repoInfo.data.repo}`);
247
+ }
248
+ const runParams = {
249
+ params: JSON.stringify(params)
250
+ };
251
+ return { pipelineInfo, repoInfo, runParams };
252
+ }
200
253
 
201
254
  /**
202
255
  * 触发流水线
203
256
  * @param {*} pipelineName 流水线名称或id
204
257
  * @param {*} waitResult 是否等待流水线执行完成
205
258
  */
206
- export async function triggerPipeline(pipelineName, waitResult = false) {
259
+ export async function triggerPipeline(pipelineName, opts = {}) {
260
+ const { waitResult = false } = opts;
207
261
  const yunxiaoToken = getYunxiaoToken({});
208
262
  if (!yunxiaoToken) {
209
263
  throw new Error(`未设置云效token, 请检查云效token是否正确`);
210
264
  }
211
- let pipelineID = "";
212
- let name = pipelineName;
213
- if (/^\d+$/.test(pipelineName)) {
214
- pipelineID = pipelineName;
215
- const pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
216
- name = pipelineInfo.name;
217
- } else {
218
- const pipelineInfo = await getPipelineInfoByName(pipelineName);
219
- pipelineID = pipelineInfo.id;
220
- name = pipelineInfo.name;
221
- }
222
- const runId = await createPipelineRunFunc(organizationId, pipelineID, {});
223
- const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineID}/builds/${runId}`;
224
- log(`流水线${name}[${pipelineID}]触发成功,流水线执行id: ${runId}, 流水线执行详情: ${pipelineRunDetailUrl}`);
265
+ const { pipelineInfo, runParams } = await getPipelineInfoDetail(pipelineName, opts.branch);
266
+ const runId = await createPipelineRunFunc(organizationId, pipelineInfo.id, runParams);
267
+ const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineInfo.id}/builds/${runId}`;
268
+ log(`流水线${pipelineInfo.name}[${pipelineInfo.id}]触发成功,流水线执行id: ${runId}, 流水线执行详情: ${pipelineRunDetailUrl}`);
225
269
  if (waitResult) {
226
- await waitPipelineRunFinish(pipelineID, runId);
270
+ await waitPipelineRunFinish(pipelineInfo.id, runId);
227
271
  }
228
272
  }
229
273
 
@@ -261,15 +305,10 @@ export function checkYunxiaoToken(config, mode) {
261
305
 
262
306
  async function runSinglePipeline(pipeline, index, total) {
263
307
  log(`开始处理流水线: ${pipeline.name}, 当前是第${index + 1}个, 总共${total}个`);
264
- let pipelineId = pipeline.id || "";
265
- if (!pipelineId) {
266
- // 获取流水线信息
267
- const pipelineInfo = await getPipelineInfoByName(pipeline.name);
268
- pipelineId = pipelineInfo.id;
269
- }
270
- log(`流水线${pipeline.name}的id: ${pipelineId}`);
308
+ const { pipelineInfo, runParams } = await getPipelineInfoDetail(pipeline.id || pipeline.name, pipeline.branch);
271
309
 
272
310
  // 处理分支强推的问题
311
+ // 将要废弃force2Branch这个配置
273
312
  const force2Branch = pipeline.force2Branch;
274
313
  if (force2Branch && force2Branch.src && force2Branch.dest) {
275
314
  let repo = pipeline.repo || "./";
@@ -296,13 +335,11 @@ async function runSinglePipeline(pipeline, index, total) {
296
335
  await git.raw("push", "origin", `${force2Branch.src}:${force2Branch.dest}`, "--force");
297
336
  log(`仓库${repo}强推${force2Branch.src}到${force2Branch.dest}完成`);
298
337
  }
299
- // TODO: PERF: 获取流水线信息后, 可以缓存下来, 避免每次都重新获取
300
- // TODO: 触发流水线时, 可以传入参数, 比如分支, 比如环境, 但是不知道为什么覆盖不了。这里先不传了
301
- const runId = await createPipelineRunFunc(organizationId, pipelineId, {});
302
- const piplineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineId}/builds/${runId}`;
303
- log(`流水线${pipeline.name}触发成功,流水线执行id: ${runId}, 流水线执行详情: ${piplineRunDetailUrl}`);
304
- if(pipeline.waitResult) {
305
- await waitPipelineRunFinish(pipelineId, runId);
338
+ const runId = await createPipelineRunFunc(organizationId, pipelineInfo.id, runParams);
339
+ const piplineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineInfo.id}/builds/${runId}`;
340
+ log(`流水线${pipelineInfo.name}触发成功,流水线执行id: ${runId}, 流水线执行详情: ${piplineRunDetailUrl}`);
341
+ if (pipeline.waitResult) {
342
+ await waitPipelineRunFinish(pipelineInfo.id, runId);
306
343
  }
307
344
  }
308
345
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taole/deploy-helper",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "脚本部署工具,用于将项目部署到测试环境或生产环境",
5
5
  "main": "index.mjs",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  "author": "",
25
25
  "license": "ISC",
26
26
  "dependencies": {
27
- "alibabacloud-devops-mcp-server": "*",
27
+ "alibabacloud-devops-mcp-server": "0.1.26",
28
28
  "node-scp": "^0.0.25",
29
29
  "simple-git": "^3.28.0"
30
30
  }