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