@jira-deploy/core 1.0.15 → 1.0.17
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/constants/config.js +34 -5
- package/constants/issue-types.js +2 -2
- package/index.js +1 -1
- package/jira-client.js +79 -44
- package/notifier.js +3 -2
- package/package.json +9 -2
- package/platform-config.js +2 -1
- package/poller.js +4 -2
- package/tools/branch-prs.js +164 -0
- package/tools/build.js +117 -0
- package/tools/cd.js +506 -119
- package/tools/ci.js +497 -29
- package/tools/deployment-helpers.js +103 -0
- package/tools/deployment.js +269 -0
- package/tools/grayrelease.js +244 -231
- package/tools/helpers.js +29 -7
- package/tools/index.js +131 -566
- package/tools/jabber.js +13 -5
- package/tools/library.js +221 -25
- package/tools/release.js +137 -38
- package/tools/transition-helpers.js +22 -0
- package/tools/workflows.js +388 -110
package/tools/index.js
CHANGED
|
@@ -1,40 +1,46 @@
|
|
|
1
1
|
import { Poller } from '../poller.js';
|
|
2
|
+
import { getBuildToolDefinitions, handleWaitBuildResult } from './build.js';
|
|
3
|
+
import { getBranchPRToolDefinitions, handleValidateBranchNoOpenPR } from './branch-prs.js';
|
|
2
4
|
import {
|
|
3
5
|
getLibraryToolDefinitions,
|
|
6
|
+
handleBuildLibrary,
|
|
4
7
|
handleCreateLibraryTicket,
|
|
5
|
-
handleGetNextLibVersion,
|
|
6
8
|
} from './library.js';
|
|
7
|
-
import { getCIToolDefinitions, handleCreateCITicket, handleGetNextCIVersion } from './ci.js';
|
|
8
9
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
getCIToolDefinitions,
|
|
11
|
+
handleBuildCI,
|
|
12
|
+
handleCreateCITicket,
|
|
13
|
+
handleGetNextCIVersion,
|
|
14
|
+
handleWaitToDev,
|
|
15
|
+
handleWaitToPrdDr,
|
|
16
|
+
handleWaitToStg,
|
|
17
|
+
handleWaitToUat,
|
|
18
|
+
} from './ci.js';
|
|
19
|
+
import { CD_FIELD_IDS, ENV_CODES, resolveAccountId, SUPPORTED_ENVS, } from '../constants/index.js';
|
|
20
|
+
import { getCDToolDefinitions, handleCDApproval, handleCreateCDTicket } from './cd.js';
|
|
21
|
+
import { getDeploymentToolDefinitions, handleTriggerDeployment } from './deployment.js';
|
|
16
22
|
import {
|
|
17
23
|
getGrayReleaseToolDefinitions,
|
|
18
|
-
handleCreateGrayReleaseTicket,
|
|
19
|
-
handleLinkStgGrayRelease,
|
|
20
24
|
handleAutoGrayRelease,
|
|
25
|
+
handleBuildGrayRelease,
|
|
26
|
+
handleContinueGrayRelease,
|
|
27
|
+
handleCreateGrayReleaseTicket,
|
|
21
28
|
handleDeployGrayRelease,
|
|
22
29
|
handleGetGrayReleaseStatus,
|
|
23
|
-
|
|
24
|
-
handleContinueGrayRelease,
|
|
30
|
+
handleLinkStgGrayRelease,
|
|
25
31
|
} from './grayrelease.js';
|
|
26
32
|
import {
|
|
27
33
|
getReleaseToolDefinitions,
|
|
34
|
+
handleGetReleaseManager,
|
|
28
35
|
handleGetUnreleasedVersions,
|
|
29
36
|
handleTransitionToWaitApproval,
|
|
30
|
-
handleGetReleaseManager,
|
|
31
37
|
handleWaitForComment,
|
|
32
38
|
} from './release.js';
|
|
33
39
|
import { getJabberToolDefinitions, handleSendJabberMessage } from './jabber.js';
|
|
34
40
|
import {
|
|
35
41
|
getWorkflowToolDefinitions,
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
handleContinueReleaseToCdReadyWorkflow,
|
|
43
|
+
handleRunReleaseToStgWorkflow,
|
|
38
44
|
} from './workflows.js';
|
|
39
45
|
|
|
40
46
|
const READ_ONLY_TOOL_NAMES = new Set([
|
|
@@ -46,6 +52,7 @@ const READ_ONLY_TOOL_NAMES = new Set([
|
|
|
46
52
|
'wait_for_comment',
|
|
47
53
|
'get_grayrelease_status',
|
|
48
54
|
'wait_build_result',
|
|
55
|
+
'validate_branch_no_open_pr',
|
|
49
56
|
]);
|
|
50
57
|
|
|
51
58
|
function withToolAnnotations(tools) {
|
|
@@ -77,9 +84,12 @@ function withToolAnnotations(tools) {
|
|
|
77
84
|
*/
|
|
78
85
|
export function getToolDefinitions() {
|
|
79
86
|
return withToolAnnotations([
|
|
87
|
+
...getBuildToolDefinitions(),
|
|
88
|
+
...getBranchPRToolDefinitions(),
|
|
80
89
|
...getLibraryToolDefinitions(),
|
|
81
90
|
...getCIToolDefinitions(),
|
|
82
91
|
...getCDToolDefinitions(),
|
|
92
|
+
...getDeploymentToolDefinitions(),
|
|
83
93
|
...getGrayReleaseToolDefinitions(),
|
|
84
94
|
...getReleaseToolDefinitions(),
|
|
85
95
|
...getJabberToolDefinitions(),
|
|
@@ -177,83 +187,10 @@ export function getToolDefinitions() {
|
|
|
177
187
|
},
|
|
178
188
|
},
|
|
179
189
|
},
|
|
180
|
-
{
|
|
181
|
-
name: 'build_ticket',
|
|
182
|
-
description:
|
|
183
|
-
'觸發 Library、CI 或 GrayRelease 上版單的 Jenkins Build。自動處理前置狀態切換與 Jira Automation 等待,不需要手動切換狀態。GrayRelease 僅在使用者明確要求 build/rebuild 時使用此 tool;若使用者要求 GrayRelease deploy、dev deploy 或部署灰度單,必須改用 deploy_grayrelease,不可用此 tool 觸發 rebuild。',
|
|
184
|
-
inputSchema: {
|
|
185
|
-
type: 'object',
|
|
186
|
-
required: ['issueKey'],
|
|
187
|
-
properties: {
|
|
188
|
-
issueKey: {
|
|
189
|
-
type: 'string',
|
|
190
|
-
description: '要 build 的 issue key,例如 CI、Library 或 GrayRelease 單',
|
|
191
|
-
},
|
|
192
|
-
rebuild: {
|
|
193
|
-
type: 'boolean',
|
|
194
|
-
description: '(選填) 只有明確要重 build GrayRelease VERIFY 單時才設定 rebuild=true。GrayRelease deploy 請用 deploy_grayrelease。',
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
name: 'wait_to_dev',
|
|
201
|
-
description:
|
|
202
|
-
'CI 單 build 完成後,自動走完掃描流程切到 Wait To DEV 狀態。流程:Compliance Scan → Upload Scan Report → Accept → Wait To DEV(不執行 Dev Done)',
|
|
203
|
-
inputSchema: {
|
|
204
|
-
type: 'object',
|
|
205
|
-
required: ['issueKey'],
|
|
206
|
-
properties: {
|
|
207
|
-
issueKey: {
|
|
208
|
-
type: 'string',
|
|
209
|
-
description: 'CI issue key',
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
name: 'wait_to_stg',
|
|
216
|
-
description:
|
|
217
|
-
'CI 單 build 完成後,自動走完掃描流程切到 Wait To STG 狀態。流程:Compliance Scan → Upload Scan Report → Accept → Dev Done → Wait To STG',
|
|
218
|
-
inputSchema: {
|
|
219
|
-
type: 'object',
|
|
220
|
-
required: ['issueKey'],
|
|
221
|
-
properties: {
|
|
222
|
-
issueKey: {
|
|
223
|
-
type: 'string',
|
|
224
|
-
description: 'CI issue key',
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
name: 'trigger_deployment',
|
|
231
|
-
description:
|
|
232
|
-
'在 CD 單的 Deployment sub-task 上依序觸發部署 transitions(To Pretask → To AutoDeploy → Trigger AutoDeploy)。接在 prepare_cd_deployment 之後使用。當使用者說「幫我 Deploy」、「上版」、「deploy」時優先使用這個 tool',
|
|
233
|
-
inputSchema: {
|
|
234
|
-
type: 'object',
|
|
235
|
-
required: ['cdIssueKey', 'environment'],
|
|
236
|
-
properties: {
|
|
237
|
-
cdIssueKey: {
|
|
238
|
-
type: 'string',
|
|
239
|
-
description: 'CD 單 issue key',
|
|
240
|
-
},
|
|
241
|
-
environment: {
|
|
242
|
-
type: 'string',
|
|
243
|
-
enum: SUPPORTED_ENVS.cd,
|
|
244
|
-
description: `部署目標環境:${SUPPORTED_ENVS.cd.join(' / ')}`,
|
|
245
|
-
},
|
|
246
|
-
applyForClose: {
|
|
247
|
-
type: 'boolean',
|
|
248
|
-
description: '部署觸發後是否同步對 CD 單執行 Apply for close(預設 false)',
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
190
|
{
|
|
254
191
|
name: 'prepare_cd_deployment',
|
|
255
192
|
description:
|
|
256
|
-
'在 CD 單點擊「Prepare to Create Deployment
|
|
193
|
+
'在 CD 單點擊「Prepare to Create Deployment」觸發部署前置流程;非 DEV 會依環境處理 CD 簽核,核准後自動推進到 Wait Deploy。',
|
|
257
194
|
inputSchema: {
|
|
258
195
|
type: 'object',
|
|
259
196
|
required: ['issueKey', 'environment'],
|
|
@@ -335,7 +272,8 @@ export function getToolDefinitions() {
|
|
|
335
272
|
*/
|
|
336
273
|
export async function executeTool(name, args, deps) {
|
|
337
274
|
const { jira, notifier } = deps;
|
|
338
|
-
const progress = typeof deps.progress === 'function' ? deps.progress : () => {
|
|
275
|
+
const progress = typeof deps.progress === 'function' ? deps.progress : () => {
|
|
276
|
+
};
|
|
339
277
|
const poller = new Poller(jira);
|
|
340
278
|
|
|
341
279
|
switch (name) {
|
|
@@ -349,6 +287,9 @@ export async function executeTool(name, args, deps) {
|
|
|
349
287
|
}
|
|
350
288
|
}
|
|
351
289
|
|
|
290
|
+
case 'validate_branch_no_open_pr':
|
|
291
|
+
return handleValidateBranchNoOpenPR(args, { jira });
|
|
292
|
+
|
|
352
293
|
case 'wait_for_approval': {
|
|
353
294
|
const targetStatus = args.targetStatus ?? 'Approved';
|
|
354
295
|
|
|
@@ -390,12 +331,18 @@ export async function executeTool(name, args, deps) {
|
|
|
390
331
|
case 'create_library_ticket':
|
|
391
332
|
return handleCreateLibraryTicket(args, { jira, notifier });
|
|
392
333
|
|
|
334
|
+
case 'build_library':
|
|
335
|
+
return handleBuildLibrary(args, { jira, notifier, progress });
|
|
336
|
+
|
|
393
337
|
case 'create_ci_ticket': {
|
|
394
338
|
return handleCreateCITicket(args, { jira, notifier });
|
|
395
339
|
}
|
|
396
340
|
|
|
341
|
+
case 'build_ci':
|
|
342
|
+
return handleBuildCI(args, { jira, notifier, progress });
|
|
343
|
+
|
|
397
344
|
case 'create_cd_ticket': {
|
|
398
|
-
return handleCreateCDTicket(args, { jira, notifier });
|
|
345
|
+
return handleCreateCDTicket(args, { jira, notifier, progress });
|
|
399
346
|
}
|
|
400
347
|
|
|
401
348
|
case 'create_grayrelease_ticket': {
|
|
@@ -411,6 +358,9 @@ export async function executeTool(name, args, deps) {
|
|
|
411
358
|
case 'deploy_grayrelease':
|
|
412
359
|
return handleDeployGrayRelease(args, { jira, notifier, progress });
|
|
413
360
|
|
|
361
|
+
case 'build_grayrelease':
|
|
362
|
+
return handleBuildGrayRelease(args, { jira, notifier, progress });
|
|
363
|
+
|
|
414
364
|
case 'get_grayrelease_status':
|
|
415
365
|
return handleGetGrayReleaseStatus(args, { jira });
|
|
416
366
|
|
|
@@ -427,7 +377,7 @@ export async function executeTool(name, args, deps) {
|
|
|
427
377
|
return handleTransitionToWaitApproval(args, { jira, notifier });
|
|
428
378
|
|
|
429
379
|
case 'get_release_manager':
|
|
430
|
-
return handleGetReleaseManager(args
|
|
380
|
+
return handleGetReleaseManager(args);
|
|
431
381
|
|
|
432
382
|
case 'wait_for_comment':
|
|
433
383
|
return handleWaitForComment(args, { jira, progress });
|
|
@@ -435,8 +385,8 @@ export async function executeTool(name, args, deps) {
|
|
|
435
385
|
case 'send_jabber_message':
|
|
436
386
|
return handleSendJabberMessage(args, {});
|
|
437
387
|
|
|
438
|
-
case '
|
|
439
|
-
return
|
|
388
|
+
case 'run_release_to_stg':
|
|
389
|
+
return handleRunReleaseToStgWorkflow(args, {
|
|
440
390
|
jira,
|
|
441
391
|
notifier,
|
|
442
392
|
executeToolImpl: deps.executeToolImpl ?? executeTool,
|
|
@@ -444,8 +394,8 @@ export async function executeTool(name, args, deps) {
|
|
|
444
394
|
progress,
|
|
445
395
|
});
|
|
446
396
|
|
|
447
|
-
case '
|
|
448
|
-
return
|
|
397
|
+
case 'continue_release_to_cd_ready':
|
|
398
|
+
return handleContinueReleaseToCdReadyWorkflow(args, {
|
|
449
399
|
jira,
|
|
450
400
|
notifier,
|
|
451
401
|
executeToolImpl: deps.executeToolImpl ?? executeTool,
|
|
@@ -464,467 +414,20 @@ export async function executeTool(name, args, deps) {
|
|
|
464
414
|
}
|
|
465
415
|
}
|
|
466
416
|
|
|
467
|
-
case '
|
|
468
|
-
|
|
469
|
-
const log = [];
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* CI Release 完整狀態流程:
|
|
473
|
-
* TO DO → (Accept) → Wait for Build → (Build) → Compliance Scan
|
|
474
|
-
* → (Upload Scan Report) → Upload Report → (Accept) → Wait To DEV
|
|
475
|
-
* → (Dev Done) → Wait To STG → (STG Done) → Wait To UAT
|
|
476
|
-
* → (UAT Done) → Wait For Upload → (Upload To Pre-Release)
|
|
477
|
-
* → Wait For Upload → (Upload Done) → Wait To PRD/DR
|
|
478
|
-
* → (PRD/DR Done)
|
|
479
|
-
*
|
|
480
|
-
* Library Release 完整狀態流程:
|
|
481
|
-
* TO DO → (Upload Lib Report) → UPLOAD LIB REPORT → (Apply for approval) → Wait Approval → (Approved) → WAIT FOR LIB BUILD → (Build) → Released
|
|
482
|
-
* 或:TO DO → (Upload Lib Report) → UPLOAD LIB REPORT → Jira Automation 自動觸發 Jenkins(無手動 Build)
|
|
483
|
-
*
|
|
484
|
-
* GrayRelease build/rebuild:
|
|
485
|
-
* - 一般 build:PLANNING → Accept → WAIT FOR BUILD → GrayRelease Build
|
|
486
|
-
* - VERIFY rebuild:只有 args.rebuild=true 時,才允許 Verify fail 回到 PLANNING 後重 build
|
|
487
|
-
* - GrayRelease deploy 已拆到 deploy_grayrelease,這裡不可處理部署流程
|
|
488
|
-
*/
|
|
489
|
-
|
|
490
|
-
const BUILD_TRANSITIONS = ['Build', 'GrayRelease Build'];
|
|
491
|
-
const PRE_TRANSITIONS = ['Upload Lib Report', 'Apply for approval', 'Approved', 'Accept'];
|
|
492
|
-
const GRAYRELEASE_REBUILD_TRANSITIONS = [
|
|
493
|
-
'Verify fail',
|
|
494
|
-
'Back to Planning',
|
|
495
|
-
'To Planning',
|
|
496
|
-
'Planning',
|
|
497
|
-
];
|
|
498
|
-
const POLL_INTERVAL = 3000; // 3s
|
|
499
|
-
const MAX_WAIT_MS = 30000; // 最多等 30s(避免 MCP client timeout)
|
|
500
|
-
|
|
501
|
-
const findTransition = async (name) => {
|
|
502
|
-
const list = await jira.getTransitions(issueKey);
|
|
503
|
-
return list.find((t) => t.name.toLowerCase() === name.toLowerCase());
|
|
504
|
-
};
|
|
417
|
+
case 'wait_to_dev':
|
|
418
|
+
return handleWaitToDev(args, { jira, notifier, progress });
|
|
505
419
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
return list.find((t) => names.some((name) => t.name.toLowerCase() === name.toLowerCase()));
|
|
509
|
-
};
|
|
420
|
+
case 'wait_to_stg':
|
|
421
|
+
return handleWaitToStg(args, { jira, notifier, progress });
|
|
510
422
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
while (Date.now() < deadline) {
|
|
514
|
-
const t = await findTransition(name);
|
|
515
|
-
if (t) return t;
|
|
516
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
517
|
-
}
|
|
518
|
-
return null;
|
|
519
|
-
};
|
|
423
|
+
case 'wait_to_uat':
|
|
424
|
+
return handleWaitToUat(args, { jira, notifier, progress });
|
|
520
425
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
while (Date.now() < deadline) {
|
|
524
|
-
const t = await findAnyTransition(names);
|
|
525
|
-
if (t) return t;
|
|
526
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
527
|
-
}
|
|
528
|
-
return null;
|
|
529
|
-
};
|
|
426
|
+
case 'wait_to_prd_dr':
|
|
427
|
+
return handleWaitToPrdDr(args, { jira, notifier, progress });
|
|
530
428
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const initIssue = await jira.getIssue(issueKey);
|
|
534
|
-
const initStatus = initIssue.fields.status.name;
|
|
535
|
-
|
|
536
|
-
// Step 1: 確認目前是否已有 Build transition(GrayRelease 名稱為 GrayRelease Build)
|
|
537
|
-
let buildTrans = await findAnyTransition(BUILD_TRANSITIONS);
|
|
538
|
-
|
|
539
|
-
const normalizedInitStatus = initStatus.trim().toUpperCase();
|
|
540
|
-
|
|
541
|
-
// GrayRelease 特例:VERIFY 狀態只有明確 rebuild=true 才允許回到可 build 流程。
|
|
542
|
-
if (!buildTrans && initStatus.toLowerCase() === 'verify') {
|
|
543
|
-
const resetTrans = await findAnyTransition(GRAYRELEASE_REBUILD_TRANSITIONS);
|
|
544
|
-
if (resetTrans && !rebuild) {
|
|
545
|
-
return error(
|
|
546
|
-
'目前為 VERIFY,build_ticket 不會自動重 build GrayRelease。若要部署請使用 deploy_grayrelease;若要重 build 請呼叫 build_ticket 並設定 rebuild=true。',
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
if (resetTrans) {
|
|
550
|
-
log.push(`目前為 VERIFY,GrayRelease rebuild 先觸發「${resetTrans.name}」回到可 build 流程...`);
|
|
551
|
-
await jira.transitionById(issueKey, resetTrans.id);
|
|
552
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
553
|
-
buildTrans = await findAnyTransition(BUILD_TRANSITIONS);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
if (!buildTrans && ['WAIT APPROVAL', 'WAIT DEPLOY'].includes(normalizedInitStatus)) {
|
|
558
|
-
const transitions = await jira.getTransitions(issueKey);
|
|
559
|
-
const grayDeployTransition = transitions.find((t) => (
|
|
560
|
-
['approve', 'grayrelease deploy', 'to verify', 'switch execution node']
|
|
561
|
-
.includes(t.name.toLowerCase())
|
|
562
|
-
));
|
|
563
|
-
if (grayDeployTransition) {
|
|
564
|
-
return error(
|
|
565
|
-
`目前狀態 ${initStatus} 屬於 GrayRelease deploy 流程,請使用 deploy_grayrelease,不要用 build_ticket。`,
|
|
566
|
-
);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Step 2: 若沒有,逐步觸發前置 transitions
|
|
571
|
-
if (!buildTrans) {
|
|
572
|
-
log.push('未找到 Build/GrayRelease Build transition,逐步觸發前置狀態...');
|
|
573
|
-
let preTriggered = false;
|
|
574
|
-
|
|
575
|
-
// 最多嘗試 PRE_TRANSITIONS.length 輪,每輪觸發一個可用的前置 transition
|
|
576
|
-
for (let step = 0; step < PRE_TRANSITIONS.length; step++) {
|
|
577
|
-
const transitions = await jira.getTransitions(issueKey);
|
|
578
|
-
const pre = transitions.find((t) =>
|
|
579
|
-
PRE_TRANSITIONS.some((name) => t.name.toLowerCase() === name.toLowerCase()),
|
|
580
|
-
);
|
|
581
|
-
if (!pre) break;
|
|
582
|
-
|
|
583
|
-
log.push(` [step ${step + 1}] 觸發「${pre.name}」...`);
|
|
584
|
-
await jira.transitionById(issueKey, pre.id).catch(() => {
|
|
585
|
-
});
|
|
586
|
-
preTriggered = true;
|
|
587
|
-
// 等 Jira 更新狀態
|
|
588
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
589
|
-
|
|
590
|
-
buildTrans = await findAnyTransition(BUILD_TRANSITIONS);
|
|
591
|
-
if (buildTrans) {
|
|
592
|
-
log.push(` 已找到 ${buildTrans.name} transition`);
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// 若還沒出現,再給 Jira Automation 最多 30s
|
|
598
|
-
if (!buildTrans) {
|
|
599
|
-
log.push(' 等待 Jira Automation 推進(最多 30s)...');
|
|
600
|
-
buildTrans = await waitForAnyTransition(BUILD_TRANSITIONS);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// 若仍未找到 Build transition,但有觸發前置 transition 且狀態已推進,
|
|
604
|
-
// 代表此 Library workflow 由 Jira Automation 自動觸發 Jenkins(無需手動 Build)
|
|
605
|
-
if (!buildTrans && preTriggered) {
|
|
606
|
-
const currentIssue = await jira.getIssue(issueKey);
|
|
607
|
-
const currentStatus = currentIssue.fields.status.name;
|
|
608
|
-
if (currentStatus !== initStatus) {
|
|
609
|
-
log.push(
|
|
610
|
-
`⚠️ 無手動 Build transition,但狀態已推進:${initStatus} → ${currentStatus}`,
|
|
611
|
-
);
|
|
612
|
-
log.push(' Jenkins Build 可能已由 Jira Automation 自動觸發');
|
|
613
|
-
await notifier.notify(
|
|
614
|
-
issueKey,
|
|
615
|
-
`Library Build 已觸發(Jira Auto)狀態:${initStatus} → ${currentStatus}`,
|
|
616
|
-
);
|
|
617
|
-
return ok({ issueKey, status: currentStatus, steps: log, autoTriggered: true });
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if (!buildTrans) {
|
|
623
|
-
const issue = await jira.getIssue(issueKey);
|
|
624
|
-
return error(`找不到 Build/GrayRelease Build transition,目前狀態:${issue.fields.status.name}`);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// Step 4: 執行 Build
|
|
628
|
-
log.push(`執行 ${buildTrans.name} transition (id: ${buildTrans.id})...`);
|
|
629
|
-
await jira.transitionById(issueKey, buildTrans.id);
|
|
630
|
-
|
|
631
|
-
const issue = await jira.getIssue(issueKey);
|
|
632
|
-
const newStatus = issue.fields.status.name;
|
|
633
|
-
log.push(`✅ ${buildTrans.name} 已觸發,目前狀態:${newStatus}`);
|
|
634
|
-
await notifier.notify(issueKey, `Jenkins ${buildTrans.name} 已觸發(${newStatus})`);
|
|
635
|
-
|
|
636
|
-
return ok({ issueKey, status: newStatus, steps: log });
|
|
637
|
-
} catch (err) {
|
|
638
|
-
return error(`build_ticket 失敗: ${err.message}`);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
case 'wait_to_dev': {
|
|
643
|
-
const { issueKey } = args;
|
|
644
|
-
const log = [];
|
|
645
|
-
|
|
646
|
-
const STEPS = [
|
|
647
|
-
{ transition: 'Upload Scan Report', targetStatus: 'Upload Report' },
|
|
648
|
-
{ transition: 'Accept', targetStatus: 'Wait To DEV' },
|
|
649
|
-
];
|
|
650
|
-
const finalTargetStatus = STEPS.at(-1).targetStatus;
|
|
651
|
-
|
|
652
|
-
try {
|
|
653
|
-
for (const step of STEPS) {
|
|
654
|
-
const transitions = await jira.getTransitions(issueKey);
|
|
655
|
-
const t = transitions.find((t) => t.name.toLowerCase() === step.transition.toLowerCase());
|
|
656
|
-
if (!t) {
|
|
657
|
-
const issue = await jira.getIssue(issueKey);
|
|
658
|
-
const current = issue.fields.status.name;
|
|
659
|
-
if (current.toLowerCase() === step.targetStatus.toLowerCase()) {
|
|
660
|
-
log.push(` 已是 ${current},跳過「${step.transition}」`);
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
if (current.toLowerCase() === finalTargetStatus.toLowerCase()) {
|
|
664
|
-
log.push(` 已是 ${current},流程已完成`);
|
|
665
|
-
break;
|
|
666
|
-
}
|
|
667
|
-
return error(`找不到 transition「${step.transition}」,目前狀態:${current}`);
|
|
668
|
-
}
|
|
669
|
-
log.push(`執行「${t.name}」→ ${step.targetStatus}`);
|
|
670
|
-
await jira.transitionById(issueKey, t.id);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const issue = await jira.getIssue(issueKey);
|
|
674
|
-
const finalStatus = issue.fields.status.name;
|
|
675
|
-
log.push(`✅ 完成,目前狀態:${finalStatus}`);
|
|
676
|
-
await notifier.notify(issueKey, `已切換至 ${finalStatus},可進行 DEV 部署`);
|
|
677
|
-
|
|
678
|
-
return ok({ issueKey, status: finalStatus, steps: log });
|
|
679
|
-
} catch (err) {
|
|
680
|
-
return error(`wait_to_dev 失敗: ${err.message}`);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
case 'wait_to_stg': {
|
|
685
|
-
const { issueKey } = args;
|
|
686
|
-
const log = [];
|
|
687
|
-
|
|
688
|
-
// CI 掃描 → STG 的標準流程步驟
|
|
689
|
-
// 每個元素:{ transition: '名稱', targetStatus: '到達後的狀態名稱' }
|
|
690
|
-
const STEPS = [
|
|
691
|
-
{ transition: 'Upload Scan Report', targetStatus: 'Upload Report' },
|
|
692
|
-
{ transition: 'Accept', targetStatus: 'Wait To DEV' },
|
|
693
|
-
{ transition: 'Dev Done', targetStatus: 'Wait To STG' },
|
|
694
|
-
];
|
|
695
|
-
|
|
696
|
-
try {
|
|
697
|
-
for (const step of STEPS) {
|
|
698
|
-
const transitions = await jira.getTransitions(issueKey);
|
|
699
|
-
const t = transitions.find((t) => t.name.toLowerCase() === step.transition.toLowerCase());
|
|
700
|
-
if (!t) {
|
|
701
|
-
// 確認目前狀態是否已達目標,若是則跳過此步
|
|
702
|
-
const issue = await jira.getIssue(issueKey);
|
|
703
|
-
const current = issue.fields.status.name;
|
|
704
|
-
if (current.toLowerCase() === step.targetStatus.toLowerCase()) {
|
|
705
|
-
log.push(` 已是 ${current},跳過「${step.transition}」`);
|
|
706
|
-
continue;
|
|
707
|
-
}
|
|
708
|
-
return error(`找不到 transition「${step.transition}」,目前狀態:${current}`);
|
|
709
|
-
}
|
|
710
|
-
log.push(`執行「${t.name}」→ ${step.targetStatus}`);
|
|
711
|
-
await jira.transitionById(issueKey, t.id);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
const issue = await jira.getIssue(issueKey);
|
|
715
|
-
const finalStatus = issue.fields.status.name;
|
|
716
|
-
log.push(`✅ 完成,目前狀態:${finalStatus}`);
|
|
717
|
-
await notifier.notify(issueKey, `已切換至 ${finalStatus},可進行 STG 部署`);
|
|
718
|
-
|
|
719
|
-
return ok({ issueKey, status: finalStatus, steps: log });
|
|
720
|
-
} catch (err) {
|
|
721
|
-
return error(`wait_to_stg 失敗: ${err.message}`);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
case 'trigger_deployment': {
|
|
726
|
-
const { cdIssueKey, environment } = args;
|
|
727
|
-
const log = [];
|
|
728
|
-
|
|
729
|
-
// 判斷環境是否屬於 PRD 群組(prd / dr / prd/dr / prd&dr)
|
|
730
|
-
const isPrdEnv = (env) =>
|
|
731
|
-
['prd', 'dr', 'prd/dr', 'prd&dr'].includes(env.toLowerCase().trim());
|
|
732
|
-
|
|
733
|
-
// 查詢同系統最近一筆已完成的部署(CD 或 GrayRelease),回傳 'prd' | 'nonPrd' | null
|
|
734
|
-
const getLastDeployEnvGroup = async (systemCode, excludeKey) => {
|
|
735
|
-
const jql_cd = `project = CID AND issuetype = CD AND text ~ "${systemCode}" AND status = Done AND issueKey != "${excludeKey}" ORDER BY updated DESC`;
|
|
736
|
-
const jql_gr = `project = CID AND issuetype = GrayRelease AND text ~ "${systemCode}" AND status = Done ORDER BY updated DESC`;
|
|
737
|
-
|
|
738
|
-
const [cdResults, grResults] = await Promise.all([
|
|
739
|
-
jira.searchIssues(jql_cd, ['customfield_13436', 'updated'], 1).catch(() => []),
|
|
740
|
-
jira.searchIssues(jql_gr, ['updated'], 1).catch(() => []),
|
|
741
|
-
]);
|
|
742
|
-
|
|
743
|
-
const cdIssue = cdResults[0] ?? null;
|
|
744
|
-
const grIssue = grResults[0] ?? null;
|
|
745
|
-
|
|
746
|
-
// 沒有任何記錄 → 保守執行
|
|
747
|
-
if (!cdIssue && !grIssue) return null;
|
|
748
|
-
|
|
749
|
-
// 若只有其中一筆,直接用那筆
|
|
750
|
-
// 若兩筆都有,取 updated 較新者
|
|
751
|
-
let lastIssue = null;
|
|
752
|
-
let lastIsGray = false;
|
|
753
|
-
|
|
754
|
-
if (cdIssue && grIssue) {
|
|
755
|
-
const cdUpdated = new Date(cdIssue.fields?.updated ?? 0);
|
|
756
|
-
const grUpdated = new Date(grIssue.fields?.updated ?? 0);
|
|
757
|
-
if (grUpdated > cdUpdated) {
|
|
758
|
-
lastIssue = grIssue;
|
|
759
|
-
lastIsGray = true;
|
|
760
|
-
} else {
|
|
761
|
-
lastIssue = cdIssue;
|
|
762
|
-
}
|
|
763
|
-
} else if (grIssue) {
|
|
764
|
-
lastIssue = grIssue;
|
|
765
|
-
lastIsGray = true;
|
|
766
|
-
} else {
|
|
767
|
-
lastIssue = cdIssue;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// GrayRelease 永遠是 nonPrd
|
|
771
|
-
if (lastIsGray) return 'nonPrd';
|
|
772
|
-
|
|
773
|
-
// CD:讀 customfield_13436.value 判斷群組
|
|
774
|
-
const envVal = lastIssue.fields?.customfield_13436?.value ?? '';
|
|
775
|
-
return isPrdEnv(envVal) ? 'prd' : 'nonPrd';
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
// 依序觸發的 Deployment transitions
|
|
779
|
-
// conditionalWait: true → 依 needSwitch 決定是否執行並等待 180s
|
|
780
|
-
const DEPLOY_TRANSITIONS = [
|
|
781
|
-
{ name: 'To Pretask' },
|
|
782
|
-
{ name: 'Swtich Execution Node', conditionalWait: true },
|
|
783
|
-
{ name: 'To AutoDeploy' },
|
|
784
|
-
{ name: 'Trigger AutoDeploy' },
|
|
785
|
-
];
|
|
786
|
-
// 環境字串對照(用於 sub-task summary 比對)
|
|
787
|
-
const envUpper = environment.toUpperCase().replace('/', '').replace('&', '');
|
|
788
|
-
|
|
789
|
-
try {
|
|
790
|
-
// Step 1: 取得 CD 單的 sub-tasks
|
|
791
|
-
const subTasks = await jira.getSubTasks(cdIssueKey);
|
|
792
|
-
if (!subTasks.length) {
|
|
793
|
-
return error(
|
|
794
|
-
`CD 單 ${cdIssueKey} 目前沒有 Deployment sub-task,請先執行 prepare_cd_deployment`,
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
log.push(`找到 ${subTasks.length} 個 sub-task:${subTasks.map((t) => t.key).join(', ')}`);
|
|
799
|
-
|
|
800
|
-
// Step 2: 依環境篩選 Deployment sub-task(比對 summary 中的環境標籤)
|
|
801
|
-
// 支援格式:[STG]、[UAT]、[PRD]、[DR]、[PRD/DR]、PRDDR 等
|
|
802
|
-
const envVariants = [envUpper, `[${envUpper}]`];
|
|
803
|
-
let deploymentKey = null;
|
|
804
|
-
|
|
805
|
-
for (const st of subTasks) {
|
|
806
|
-
const summaryUpper = (st.fields?.summary ?? '').toUpperCase().replace(/[\s/]/g, '');
|
|
807
|
-
if (envVariants.some((v) => summaryUpper.includes(v.replace(/[\s/]/g, '')))) {
|
|
808
|
-
deploymentKey = st.key;
|
|
809
|
-
break;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// fallback:若沒有符合環境的,取第一個 sub-task
|
|
814
|
-
if (!deploymentKey) {
|
|
815
|
-
deploymentKey = subTasks[0].key;
|
|
816
|
-
log.push(
|
|
817
|
-
` ⚠️ 未找到明確符合環境 ${environment} 的 sub-task,使用第一個:${deploymentKey}`,
|
|
818
|
-
);
|
|
819
|
-
} else {
|
|
820
|
-
log.push(` 對應環境 ${environment} 的 Deployment 單:${deploymentKey}`);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// Step 2.5: 判斷是否需要執行 Switch Execution Node
|
|
824
|
-
// 規則:上次部署環境群組(prd/nonPrd)與本次不同時才需要切換 Ansible instance
|
|
825
|
-
const thisGroup = isPrdEnv(environment) ? 'prd' : 'nonPrd';
|
|
826
|
-
let needSwitch = true; // 預設保守執行
|
|
827
|
-
|
|
828
|
-
try {
|
|
829
|
-
const cdFields = await jira.getIssueFields(cdIssueKey, ['customfield_13443']);
|
|
830
|
-
const systemCode = cdFields?.customfield_13443?.value;
|
|
831
|
-
if (systemCode) {
|
|
832
|
-
const lastGroup = await getLastDeployEnvGroup(systemCode, cdIssueKey);
|
|
833
|
-
if (lastGroup === null) {
|
|
834
|
-
log.push(` ⚠️ 找不到 ${systemCode} 的歷史部署記錄,保守執行 Switch Execution Node`);
|
|
835
|
-
} else if (lastGroup === thisGroup) {
|
|
836
|
-
needSwitch = false;
|
|
837
|
-
log.push(` ✅ 上次部署同為 ${lastGroup} 群組,跳過 Switch Execution Node`);
|
|
838
|
-
} else {
|
|
839
|
-
log.push(` 🔄 環境群組變更(${lastGroup} → ${thisGroup}),需執行 Switch Execution Node`);
|
|
840
|
-
}
|
|
841
|
-
} else {
|
|
842
|
-
log.push(` ⚠️ 無法取得 systemCode,保守執行 Switch Execution Node`);
|
|
843
|
-
}
|
|
844
|
-
} catch (e) {
|
|
845
|
-
log.push(` ⚠️ 判斷 Switch 條件失敗(${e.message}),保守執行`);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// Step 3: 依序執行 transitions
|
|
849
|
-
for (const step of DEPLOY_TRANSITIONS) {
|
|
850
|
-
// conditionalWait: 依 needSwitch 決定是否執行
|
|
851
|
-
if (step.conditionalWait && !needSwitch) {
|
|
852
|
-
log.push(` ⏭️ 跳過「${step.name}」(同環境群組,無需切換 Ansible instance)`);
|
|
853
|
-
continue;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
const transitions = await jira.getTransitions(deploymentKey);
|
|
857
|
-
const t = transitions.find((t) => t.name.toLowerCase() === step.name.toLowerCase());
|
|
858
|
-
|
|
859
|
-
if (!t) {
|
|
860
|
-
const issue = await jira.getIssue(deploymentKey);
|
|
861
|
-
const available = transitions.map((t) => t.name).join(', ');
|
|
862
|
-
if (['To AutoDeploy', 'Trigger AutoDeploy'].includes(step.name)) {
|
|
863
|
-
return error(
|
|
864
|
-
`找不到必要部署 transition「${step.name}」,目前狀態:${issue.fields.status.name},可用:${available || '無'}`,
|
|
865
|
-
);
|
|
866
|
-
}
|
|
867
|
-
log.push(
|
|
868
|
-
` ⚠️ 找不到「${step.name}」(目前狀態:${issue.fields.status.name},可用:${available || '無'}),跳過`,
|
|
869
|
-
);
|
|
870
|
-
continue;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
log.push(` 執行「${t.name}」...`);
|
|
874
|
-
await jira.transitionById(deploymentKey, t.id);
|
|
875
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
876
|
-
|
|
877
|
-
if (step.conditionalWait) {
|
|
878
|
-
const SWITCH_WAIT_MS = 180_000;
|
|
879
|
-
const secs = SWITCH_WAIT_MS / 1000;
|
|
880
|
-
log.push(` ⏳ 等待 ${secs}s(等待 Ansible instance 切換完成)...`);
|
|
881
|
-
await notifier.notify(deploymentKey, `⏳ 等待 ${secs}s 後繼續部署...`);
|
|
882
|
-
await new Promise((r) => setTimeout(r, SWITCH_WAIT_MS));
|
|
883
|
-
log.push(` ✅ 等待完成,繼續執行`);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
const finalIssue = await jira.getIssue(deploymentKey);
|
|
888
|
-
const finalStatus = finalIssue.fields.status.name;
|
|
889
|
-
log.push(`✅ Deployment 已觸发,目前狀態:${finalStatus}`);
|
|
890
|
-
await notifier.notify(
|
|
891
|
-
deploymentKey,
|
|
892
|
-
`Deployment 部署已觸發(環境:${environment.toUpperCase()},狀態:${finalStatus})`,
|
|
893
|
-
);
|
|
894
|
-
|
|
895
|
-
// 若 applyForClose=true,同步對 CD 單觸發 Apply for close
|
|
896
|
-
if (args.applyForClose) {
|
|
897
|
-
try {
|
|
898
|
-
const cdTransitions = await jira.getTransitions(cdIssueKey);
|
|
899
|
-
const closeT = cdTransitions.find((t) => t.name.toLowerCase() === 'apply for close');
|
|
900
|
-
if (closeT) {
|
|
901
|
-
log.push(` 對 CD 單 ${cdIssueKey} 執行「${closeT.name}」...`);
|
|
902
|
-
await jira.transitionById(cdIssueKey, closeT.id);
|
|
903
|
-
const cdIssue = await jira.getIssue(cdIssueKey);
|
|
904
|
-
log.push(` CD 單狀態:${cdIssue.fields.status.name}`);
|
|
905
|
-
await notifier.notify(cdIssueKey, `CD 單已提交關閉申請(Apply for close)`);
|
|
906
|
-
} else {
|
|
907
|
-
const available = cdTransitions.map((t) => t.name).join(', ');
|
|
908
|
-
log.push(
|
|
909
|
-
` ⚠️ CD 單找不到「Apply for close」transition(可用:${available || '無'})`,
|
|
910
|
-
);
|
|
911
|
-
}
|
|
912
|
-
} catch (e) {
|
|
913
|
-
log.push(` ⚠️ Apply for close 失敗:${e.message}`);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
return ok({
|
|
918
|
-
cdIssueKey,
|
|
919
|
-
deploymentKey,
|
|
920
|
-
environment,
|
|
921
|
-
status: finalStatus,
|
|
922
|
-
steps: log,
|
|
923
|
-
});
|
|
924
|
-
} catch (err) {
|
|
925
|
-
return error(`trigger_deployment 失敗: ${err.message}`);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
429
|
+
case 'trigger_deployment':
|
|
430
|
+
return handleTriggerDeployment(args, { jira, notifier, progress });
|
|
928
431
|
|
|
929
432
|
case 'prepare_cd_deployment': {
|
|
930
433
|
const { issueKey } = args;
|
|
@@ -946,7 +449,6 @@ export async function executeTool(name, args, deps) {
|
|
|
946
449
|
'Accept',
|
|
947
450
|
'Prepare to create deployment ticket',
|
|
948
451
|
'Apply for approval',
|
|
949
|
-
'To Wait Deploy',
|
|
950
452
|
];
|
|
951
453
|
|
|
952
454
|
const DEV_SELF_SERVICE_TRANSITIONS = [
|
|
@@ -996,6 +498,11 @@ export async function executeTool(name, args, deps) {
|
|
|
996
498
|
return issue.fields.status.name;
|
|
997
499
|
};
|
|
998
500
|
|
|
501
|
+
const getSystemCode = async () => {
|
|
502
|
+
const fields = await jira.getIssueFields(issueKey, [CD_FIELD_IDS.systemCode]);
|
|
503
|
+
return fields?.[CD_FIELD_IDS.systemCode]?.value ?? fields?.[CD_FIELD_IDS.systemCode] ?? '';
|
|
504
|
+
};
|
|
505
|
+
|
|
999
506
|
const runDevSelfServiceTransitions = async () => {
|
|
1000
507
|
if (envCode !== 'dev') return;
|
|
1001
508
|
for (const transitionName of DEV_SELF_SERVICE_TRANSITIONS) {
|
|
@@ -1049,6 +556,52 @@ export async function executeTool(name, args, deps) {
|
|
|
1049
556
|
}
|
|
1050
557
|
}
|
|
1051
558
|
|
|
559
|
+
const runNonDevApprovalFlow = async () => {
|
|
560
|
+
if (envCode === 'dev') return false;
|
|
561
|
+
|
|
562
|
+
for (let attempts = 0; attempts < 12; attempts++) {
|
|
563
|
+
const currentStatus = await getCurrentStatus();
|
|
564
|
+
const normalizedStatus = currentStatus.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
565
|
+
|
|
566
|
+
if (normalizedStatus === 'wait deploy') {
|
|
567
|
+
log.push('✅ CD 單已在 Wait Deploy,簽核前置流程完成');
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (normalizedStatus === 'wait approval') {
|
|
572
|
+
const systemCode = await getSystemCode();
|
|
573
|
+
log.push(` 處理 CD 簽核流程 (環境: ${envCode})`);
|
|
574
|
+
const approvalResult = await handleCDApproval(issueKey, envCode, systemCode, {
|
|
575
|
+
jira,
|
|
576
|
+
notifier,
|
|
577
|
+
progress,
|
|
578
|
+
});
|
|
579
|
+
log.push(` ✅ CD 簽核完成 by ${approvalResult.by}`);
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const transitions = await jira.getTransitions(issueKey);
|
|
584
|
+
const nextTransitionNames = normalizedStatus === 'wait for send notice email'
|
|
585
|
+
? ['To Wait Deploy']
|
|
586
|
+
: ['Apply for approval', 'To Wait Deploy'];
|
|
587
|
+
const next = nextTransitionNames
|
|
588
|
+
.map((transitionName) => transitions.find(
|
|
589
|
+
(transition) => transition.name.toLowerCase() === transitionName.toLowerCase(),
|
|
590
|
+
))
|
|
591
|
+
.find(Boolean);
|
|
592
|
+
|
|
593
|
+
if (!next) {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
log.push(` 觸發「${next.name}」...`);
|
|
598
|
+
await jira.transitionById(issueKey, next.id);
|
|
599
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
throw new Error('CD 簽核前置流程超過最大嘗試次數');
|
|
603
|
+
};
|
|
604
|
+
|
|
1052
605
|
if (!deployTrans && envCode === 'dev') {
|
|
1053
606
|
await runDevSelfServiceTransitions();
|
|
1054
607
|
const currentStatus = await getCurrentStatus();
|
|
@@ -1096,6 +649,7 @@ export async function executeTool(name, args, deps) {
|
|
|
1096
649
|
log.push(`✅ 部署已觸发,目前狀態:${newStatus}`);
|
|
1097
650
|
|
|
1098
651
|
await runDevSelfServiceTransitions();
|
|
652
|
+
await runNonDevApprovalFlow();
|
|
1099
653
|
|
|
1100
654
|
const finalIssue = await jira.getIssue(issueKey);
|
|
1101
655
|
const finalStatus = finalIssue.fields.status.name;
|
|
@@ -1249,21 +803,25 @@ export async function executeTool(name, args, deps) {
|
|
|
1249
803
|
const describeStatus = (issueType, status) => {
|
|
1250
804
|
const s = status.toLowerCase();
|
|
1251
805
|
if (issueType === 'Library') {
|
|
1252
|
-
if (s === 'to do') return { emoji: '⏳', desc: '尚未開始', next: `執行
|
|
1253
|
-
if (s === 'upload lib report') return { emoji: '⏳', desc: '等待上傳 Lib Report', next: `執行
|
|
806
|
+
if (s === 'to do') return { emoji: '⏳', desc: '尚未開始', next: `執行 build_library` };
|
|
807
|
+
if (s === 'upload lib report') return { emoji: '⏳', desc: '等待上傳 Lib Report', next: `執行 build_library` };
|
|
1254
808
|
if (s === 'wait approval') return { emoji: '⏳', desc: '等待主管核准', next: `wait_for_approval` };
|
|
1255
|
-
if (s === 'wait for lib build') return { emoji: '🔨', desc: '等待 Jenkins Build', next: `執行
|
|
809
|
+
if (s === 'wait for lib build') return { emoji: '🔨', desc: '等待 Jenkins Build', next: `執行 build_library` };
|
|
1256
810
|
if (s === 'released') return { emoji: '✅', desc: '已完成', next: '可開 CI' };
|
|
1257
811
|
if (s === 'cancelled') return { emoji: '❌', desc: '已取消', next: null };
|
|
1258
812
|
return { emoji: '🔄', desc: status, next: null };
|
|
1259
813
|
}
|
|
1260
814
|
if (issueType === 'CI') {
|
|
1261
|
-
if (s === 'to do') return { emoji: '⏳', desc: '尚未開始', next: '執行
|
|
1262
|
-
if (s === 'wait for build') return { emoji: '⏳', desc: '等待 Jenkins Build', next: '執行
|
|
815
|
+
if (s === 'to do') return { emoji: '⏳', desc: '尚未開始', next: '執行 build_ci' };
|
|
816
|
+
if (s === 'wait for build') return { emoji: '⏳', desc: '等待 Jenkins Build', next: '執行 build_ci' };
|
|
1263
817
|
if (s === 'compliance scan') return { emoji: '🔍', desc: '掃描中', next: '執行 wait_to_stg' };
|
|
1264
818
|
if (s === 'upload report') return { emoji: '📋', desc: '上傳掃描報告', next: '執行 wait_to_stg' };
|
|
1265
819
|
if (s === 'wait to dev') return { emoji: '🔄', desc: 'Build 完成,等待 DEV', next: '執行 wait_to_stg' };
|
|
1266
|
-
if (s === 'wait to stg') return {
|
|
820
|
+
if (s === 'wait to stg') return {
|
|
821
|
+
emoji: '✅',
|
|
822
|
+
desc: 'Build 完成,等待 STG 部署',
|
|
823
|
+
next: '可建 CD(STG) 並執行 prepare_cd_deployment'
|
|
824
|
+
};
|
|
1267
825
|
if (s === 'wait to uat') return { emoji: '✅', desc: 'STG 完成,等待 UAT', next: '可建 CD(UAT)' };
|
|
1268
826
|
if (s === 'wait for upload') return { emoji: '📦', desc: '等待上傳 Pre-Release', next: null };
|
|
1269
827
|
if (s === 'wait to prd/dr') return { emoji: '✅', desc: 'UAT 完成,等待 PRD/DR', next: '可建 CD(PRD/DR)' };
|
|
@@ -1273,7 +831,11 @@ export async function executeTool(name, args, deps) {
|
|
|
1273
831
|
}
|
|
1274
832
|
if (issueType === 'CD') {
|
|
1275
833
|
if (s === 'to do') return { emoji: '⏳', desc: '尚未開始', next: '執行 prepare_cd_deployment' };
|
|
1276
|
-
if (s === 'wait for send notice email') return {
|
|
834
|
+
if (s === 'wait for send notice email') return {
|
|
835
|
+
emoji: '⏳',
|
|
836
|
+
desc: '等待通知信',
|
|
837
|
+
next: '執行 prepare_cd_deployment'
|
|
838
|
+
};
|
|
1277
839
|
if (s === 'prepare for deploy') return { emoji: '⏳', desc: '部署單建立中', next: null };
|
|
1278
840
|
if (s === 'wait approval') return { emoji: '⏳', desc: '等待主管核准', next: 'wait_for_approval' };
|
|
1279
841
|
if (s === 'wait deploy') return { emoji: '✅', desc: '已核准,等待部署', next: '執行 trigger_deployment' };
|
|
@@ -1300,7 +862,13 @@ export async function executeTool(name, args, deps) {
|
|
|
1300
862
|
const ciIssue = ciIssues[0];
|
|
1301
863
|
const ciStatus = describeStatus('CI', ciIssue.fields.status.name);
|
|
1302
864
|
const ciVersion = ciIssue.fields.customfield_13438
|
|
1303
|
-
? (() => {
|
|
865
|
+
? (() => {
|
|
866
|
+
try {
|
|
867
|
+
return JSON.stringify(JSON.parse(ciIssue.fields.customfield_13438));
|
|
868
|
+
} catch {
|
|
869
|
+
return ciIssue.fields.customfield_13438;
|
|
870
|
+
}
|
|
871
|
+
})()
|
|
1304
872
|
: '(尚無版本)';
|
|
1305
873
|
|
|
1306
874
|
// Step 2: 從 CI issuelinks 找 Library 單(Relates),並平行查 gitBranch
|
|
@@ -1369,9 +937,6 @@ export async function executeTool(name, args, deps) {
|
|
|
1369
937
|
}
|
|
1370
938
|
}
|
|
1371
939
|
|
|
1372
|
-
case 'get_next_lib_version':
|
|
1373
|
-
return handleGetNextLibVersion(args, { jira });
|
|
1374
|
-
|
|
1375
940
|
case 'get_next_ci_version':
|
|
1376
941
|
return handleGetNextCIVersion(args, { jira });
|
|
1377
942
|
|