@taole/deploy-helper 0.4.0 → 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 +67 -55
- package/lib/pipelineApi.mjs +93 -44
- package/package.json +1 -1
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(`触发流水线失败:
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
225
|
-
let assetsStatus =
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
fs.
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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()) {
|
package/lib/pipelineApi.mjs
CHANGED
|
@@ -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
|
/**
|
|
@@ -155,24 +155,35 @@ async function getPipelineInfoByName(name) {
|
|
|
155
155
|
* @param {number} interval 轮询间隔时间
|
|
156
156
|
* @returns 流水线执行详情
|
|
157
157
|
*/
|
|
158
|
-
async function waitPipelineRunFinish(pipelineID, runId, interval =
|
|
158
|
+
async function waitPipelineRunFinish(pipelineID, runId, interval = 5000) {
|
|
159
159
|
const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineID}/builds/${runId}`;
|
|
160
160
|
log(`开始轮询至流水线执行完成`);
|
|
161
161
|
let runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
|
|
162
|
-
|
|
162
|
+
let passFailedCount = 0;
|
|
163
|
+
let passFailedMaxCount = 3;
|
|
164
|
+
while (runDetail.status === "RUNNING" || runDetail.status === "WAITING" || runDetail.status === "INIT") {
|
|
163
165
|
const { stage, job } = getCurrentJob(runDetail);
|
|
164
166
|
log(`流水线执行状态: ${runDetail.status},阶段: ${stage.name},任务: ${job.name},等${interval / 1000}秒后重新轮询`);
|
|
165
167
|
if (runDetail.status === "WAITING") {
|
|
166
168
|
if (stage && job) {
|
|
167
169
|
log(`尝试自动通过人工卡点`);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
try {
|
|
171
|
+
const isPass = await passPipelineJob(pipelineID, runId, job.id);
|
|
172
|
+
if (isPass) {
|
|
173
|
+
log(`人工卡点通过`);
|
|
174
|
+
} else {
|
|
175
|
+
log(`人工卡点通过失败`);
|
|
176
|
+
passFailedCount++;
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
passFailedCount++;
|
|
180
|
+
log(`人工卡点通过失败`, error);
|
|
174
181
|
}
|
|
175
182
|
}
|
|
183
|
+
if (passFailedCount >= passFailedMaxCount) {
|
|
184
|
+
log(`人工卡点通过失败次数超过最大值, 放弃`);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
176
187
|
}
|
|
177
188
|
await new Promise(resolve => setTimeout(resolve, interval));
|
|
178
189
|
runDetail = await getPipelineRunFunc(organizationId, pipelineID, runId);
|
|
@@ -185,33 +196,78 @@ async function waitPipelineRunFinish(pipelineID, runId, interval = 3000) {
|
|
|
185
196
|
return runDetail;
|
|
186
197
|
}
|
|
187
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
|
+
}
|
|
188
253
|
|
|
189
254
|
/**
|
|
190
255
|
* 触发流水线
|
|
191
256
|
* @param {*} pipelineName 流水线名称或id
|
|
192
257
|
* @param {*} waitResult 是否等待流水线执行完成
|
|
193
258
|
*/
|
|
194
|
-
export async function triggerPipeline(pipelineName,
|
|
259
|
+
export async function triggerPipeline(pipelineName, opts = {}) {
|
|
260
|
+
const { waitResult = false } = opts;
|
|
195
261
|
const yunxiaoToken = getYunxiaoToken({});
|
|
196
262
|
if (!yunxiaoToken) {
|
|
197
263
|
throw new Error(`未设置云效token, 请检查云效token是否正确`);
|
|
198
264
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const pipelineInfo = await getPipelineFunc(organizationId, pipelineID);
|
|
204
|
-
name = pipelineInfo.name;
|
|
205
|
-
} else {
|
|
206
|
-
const pipelineInfo = await getPipelineInfoByName(pipelineName);
|
|
207
|
-
pipelineID = pipelineInfo.id;
|
|
208
|
-
name = pipelineInfo.name;
|
|
209
|
-
}
|
|
210
|
-
const runId = await createPipelineRunFunc(organizationId, pipelineID, {});
|
|
211
|
-
const pipelineRunDetailUrl = `https://flow.aliyun.com/pipelines/${pipelineID}/builds/${runId}`;
|
|
212
|
-
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}`);
|
|
213
269
|
if (waitResult) {
|
|
214
|
-
await waitPipelineRunFinish(
|
|
270
|
+
await waitPipelineRunFinish(pipelineInfo.id, runId);
|
|
215
271
|
}
|
|
216
272
|
}
|
|
217
273
|
|
|
@@ -249,15 +305,10 @@ export function checkYunxiaoToken(config, mode) {
|
|
|
249
305
|
|
|
250
306
|
async function runSinglePipeline(pipeline, index, total) {
|
|
251
307
|
log(`开始处理流水线: ${pipeline.name}, 当前是第${index + 1}个, 总共${total}个`);
|
|
252
|
-
|
|
253
|
-
if (!pipelineId) {
|
|
254
|
-
// 获取流水线信息
|
|
255
|
-
const pipelineInfo = await getPipelineInfoByName(pipeline.name);
|
|
256
|
-
pipelineId = pipelineInfo.id;
|
|
257
|
-
}
|
|
258
|
-
log(`流水线${pipeline.name}的id: ${pipelineId}`);
|
|
308
|
+
const { pipelineInfo, runParams } = await getPipelineInfoDetail(pipeline.id || pipeline.name, pipeline.branch);
|
|
259
309
|
|
|
260
310
|
// 处理分支强推的问题
|
|
311
|
+
// 将要废弃force2Branch这个配置
|
|
261
312
|
const force2Branch = pipeline.force2Branch;
|
|
262
313
|
if (force2Branch && force2Branch.src && force2Branch.dest) {
|
|
263
314
|
let repo = pipeline.repo || "./";
|
|
@@ -284,13 +335,11 @@ async function runSinglePipeline(pipeline, index, total) {
|
|
|
284
335
|
await git.raw("push", "origin", `${force2Branch.src}:${force2Branch.dest}`, "--force");
|
|
285
336
|
log(`仓库${repo}强推${force2Branch.src}到${force2Branch.dest}完成`);
|
|
286
337
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if(pipeline.waitResult) {
|
|
293
|
-
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);
|
|
294
343
|
}
|
|
295
344
|
}
|
|
296
345
|
|