@taole/deploy-helper 0.4.1 → 0.5.0

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
@@ -109,11 +109,11 @@ async function main() {
109
109
  console.log(`command: scp {file} {dest} 复制文件{file}到OfficialSite测试服务器{dest}`);
110
110
  console.log(`command: scpevt {file} 复制文件{file}到events测试服务器{file}`);
111
111
  console.log(`command: scpevt {file} {dest} 复制文件{file}到events测试服务器{dest}`);
112
- console.log(`command: pipeline {pipelineName|pipelineId} 触发流水线{pipelineName|pipelineId}`);
112
+ console.log(`command: pipeline {pipelineName|pipelineId} [branch] 触发流水线{pipelineName|pipelineId}, 指定分支(可省略)`);
113
113
  process.exit(0);
114
114
  }
115
115
  log("deploy-helper start");
116
-
116
+
117
117
  // 其他命令
118
118
  if (command === "init") {
119
119
  await initConfigJson();
@@ -162,11 +162,12 @@ async function main() {
162
162
  log(`pipeline参数不能为空`);
163
163
  process.exit(1);
164
164
  }
165
+ const branch = process.argv[4] || "";
165
166
  try {
166
- await triggerPipeline(pipelineName, true);
167
+ await triggerPipeline(pipelineName, { waitResult: true, branch });
167
168
  process.exit(0);
168
169
  } catch (error) {
169
- log(`触发流水线失败: ${error}`);
170
+ log(`触发流水线失败: `, error);
170
171
  process.exit(1);
171
172
  }
172
173
 
@@ -181,12 +182,6 @@ async function main() {
181
182
  const config = await loadConfig();
182
183
  // log(`config`, config);
183
184
 
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
185
  const result = hasChangeMe(JSON.stringify(config));
191
186
  if (result) {
192
187
  log(`配置中存在默认值(CHANGE_ME), 请先修改配置`);
@@ -194,8 +189,10 @@ async function main() {
194
189
  }
195
190
 
196
191
 
192
+ const needHandleAssets = config.assets && config.assets.dest;
193
+
197
194
  // 需要处理entry
198
- const needHandleEntry = mode === 'prod' || (mode === 'test' && !config.entry.onlyProd);
195
+ const needHandleEntry = config.entry && config.entry.dest && (mode === 'prod' || (mode === 'test' && !config.entry.onlyProd));
199
196
  const entryTestBranch = (config.entry && config.entry.testBranch) || "test";
200
197
  const entryProdBranch = (config.entry && config.entry.prodBranch) || "master";
201
198
  const entryTargetBranch = mode === 'prod' ? entryProdBranch : entryTestBranch;
@@ -208,24 +205,34 @@ async function main() {
208
205
 
209
206
  // 检查项目
210
207
  // 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;
208
+ let assetsDest = null;
209
+ let entryDest = null;
210
+ if (needHandleAssets) {
211
+ assetsDest = join(workDir, config.assets.dest);
212
+ if (!fs.existsSync(assetsDest)) {
213
+ log(`assets.dest路径不存在: ${assetsDest}, 请先创建`);
214
+ process.exit(1);
215
+ }
216
216
  }
217
217
  // entry.dest
218
- if (needHandleEntry && !fs.existsSync(entryDest)) {
219
- log(`entry.dest路径不存在: ${entryDest}, 请先创建`);
220
- return;
218
+ if (needHandleEntry) {
219
+ entryDest = join(workDir, config.entry.dest);
220
+ if (!fs.existsSync(entryDest)) {
221
+ log(`entry.dest路径不存在: ${entryDest}, 请先创建`);
222
+ process.exit(1);
223
+ }
221
224
  }
222
225
 
223
226
  // 2. 检查项目是否clean
224
- const assetsGit = simpleGit(assetsDest);
225
- let assetsStatus = await assetsGit.status();
226
- if (!assetsStatus.isClean()) {
227
- log(`${assetsDest}目前有未提交内容, 请先处理`);
228
- return;
227
+ let assetsGit = null;
228
+ let assetsStatus = null;
229
+ if (needHandleAssets) {
230
+ assetsGit = simpleGit(assetsDest);
231
+ assetsStatus = await assetsGit.status();
232
+ if (!assetsStatus.isClean()) {
233
+ log(`${assetsDest}目前有未提交内容, 请先处理`);
234
+ return;
235
+ }
229
236
  }
230
237
 
231
238
  let entryGit;
@@ -255,7 +262,7 @@ async function main() {
255
262
 
256
263
  // 3. 检查assets项目分支,并切换至master分支
257
264
  // assets项目固定使用master分支
258
- if (assetsStatus.current !== "master") {
265
+ if (needHandleAssets && assetsStatus.current !== "master") {
259
266
  log(`${assetsDest}当前分支不是master, 切换至master分支`);
260
267
  await assetsGit.checkout("master");
261
268
  log(`${assetsDest}切换至master分支成功`);
@@ -267,9 +274,11 @@ async function main() {
267
274
  }
268
275
 
269
276
  // 4. 执行git pull
270
- log(`${assetsDest}执行git pull`);
271
- await assetsGit.pull();
272
- log(`${assetsDest} git pull done`);
277
+ if (assetsGit) {
278
+ log(`${assetsDest}执行git pull`);
279
+ await assetsGit.pull();
280
+ log(`${assetsDest} git pull done`);
281
+ }
273
282
  if (entryGit) {
274
283
  log(`${entryDest}执行git pull`);
275
284
  await entryGit.pull();
@@ -278,24 +287,26 @@ async function main() {
278
287
 
279
288
  // 5. 复制构建产物
280
289
  // 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);
290
+ if (needHandleAssets) {
291
+ let assetsFilesCount = 0;
292
+ const assetsFiles = config.assets.files;
293
+ for (const [src, dest] of Object.entries(assetsFiles)) {
294
+ const srcPath = join(workDir, src);
295
+ if (!fs.existsSync(srcPath)) {
296
+ log(`${srcPath}不存在,请确认构建产物是否正确`);
297
+ return;
298
+ }
299
+ const destPath = join(assetsDest, dest);
300
+ if (fs.statSync(srcPath).isDirectory()) {
301
+ fs.cpSync(srcPath, destPath, { recursive: true });
302
+ } else {
303
+ fs.copyFileSync(srcPath, destPath);
304
+ }
305
+ log(`assets copy: ${srcPath} -> ${destPath}`);
306
+ assetsFilesCount++;
294
307
  }
295
- log(`assets copy: ${srcPath} -> ${destPath}`);
296
- assetsFilesCount++;
308
+ // log(`assets copy done.`);
297
309
  }
298
- // log(`assets copy done.`);
299
310
  // 5.2 复制entry
300
311
  if (needHandleEntry) {
301
312
  const entryFiles = config.entry.files;
@@ -319,18 +330,19 @@ async function main() {
319
330
  // 6. 提交
320
331
  let canPushAssets = false;
321
332
  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} 未发现修改内容,跳过提交`);
333
+ if (needHandleAssets) {
334
+ assetsStatus = await assetsGit.status();
335
+ if (!assetsStatus.isClean()) {
336
+ canPushAssets = true;
337
+ await assetsGit.add(".");
338
+ const assetsCommit = `${config.assets.commit || "feat:部署资源文件"} by deploy-helper`;
339
+ const assetsCommitResult = await assetsGit.commit(assetsCommit);
340
+ log(`assets commit: ${assetsCommit}`);
341
+ log(`assets commit: ${JSON.stringify(assetsCommitResult.summary)}`);
342
+ } else {
343
+ log(`${assetsDest} 未发现修改内容,跳过提交`);
344
+ }
332
345
  }
333
-
334
346
  if (needHandleEntry) {
335
347
  entryStatus = await entryGit.status();
336
348
  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,7 +161,7 @@ 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
166
  log(`流水线执行状态: ${runDetail.status},阶段: ${stage.name},任务: ${job.name},等${interval / 1000}秒后重新轮询`);
167
167
  if (runDetail.status === "WAITING") {
@@ -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.0",
4
4
  "description": "脚本部署工具,用于将项目部署到测试环境或生产环境",
5
5
  "main": "index.mjs",
6
6
  "type": "module",