@jira-deploy/core 1.0.2 → 1.0.4
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/defaults.js +1 -1
- package/constants/environments.js +1 -1
- package/constants/field-ids.js +5 -2
- package/dry-run.js +4 -0
- package/jira-client.js +13 -0
- package/package.json +1 -1
- package/poller.js +16 -3
- package/tools/cd.js +9 -5
- package/tools/grayrelease.js +1046 -15
- package/tools/index.js +322 -104
- package/tools/library.js +2 -1
- package/tools/release.js +48 -21
- package/tools/workflows.js +20 -0
- package/tools.test.js +932 -27
package/tools/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {Poller} from '../poller.js';
|
|
1
|
+
import { Poller } from '../poller.js';
|
|
2
2
|
import {
|
|
3
3
|
getLibraryToolDefinitions,
|
|
4
4
|
handleCreateLibraryTicket,
|
|
5
5
|
handleGetNextLibVersion,
|
|
6
6
|
} from './library.js';
|
|
7
|
-
import {getCIToolDefinitions, handleCreateCITicket, handleGetNextCIVersion} from './ci.js';
|
|
7
|
+
import { getCIToolDefinitions, handleCreateCITicket, handleGetNextCIVersion } from './ci.js';
|
|
8
8
|
import {
|
|
9
9
|
SYSTEM_CODES,
|
|
10
10
|
ENV_CODES,
|
|
@@ -12,11 +12,15 @@ import {
|
|
|
12
12
|
SUPPORTED_ENVS,
|
|
13
13
|
resolveAccountId,
|
|
14
14
|
} from '../constants/index.js';
|
|
15
|
-
import {getCDToolDefinitions, handleCreateCDTicket} from './cd.js';
|
|
15
|
+
import { getCDToolDefinitions, handleCreateCDTicket } from './cd.js';
|
|
16
16
|
import {
|
|
17
17
|
getGrayReleaseToolDefinitions,
|
|
18
18
|
handleCreateGrayReleaseTicket,
|
|
19
19
|
handleLinkStgGrayRelease,
|
|
20
|
+
handleAutoGrayRelease,
|
|
21
|
+
handleDeployGrayRelease,
|
|
22
|
+
handleGetGrayReleaseStatus,
|
|
23
|
+
handleContinueGrayRelease,
|
|
20
24
|
} from './grayrelease.js';
|
|
21
25
|
import {
|
|
22
26
|
getReleaseToolDefinitions,
|
|
@@ -25,7 +29,7 @@ import {
|
|
|
25
29
|
handleGetReleaseManager,
|
|
26
30
|
handleWaitForComment,
|
|
27
31
|
} from './release.js';
|
|
28
|
-
import {getJabberToolDefinitions, handleSendJabberMessage} from './jabber.js';
|
|
32
|
+
import { getJabberToolDefinitions, handleSendJabberMessage } from './jabber.js';
|
|
29
33
|
import {
|
|
30
34
|
getWorkflowToolDefinitions,
|
|
31
35
|
handleRunLibToStgReleaseWorkflow,
|
|
@@ -39,6 +43,7 @@ const READ_ONLY_TOOL_NAMES = new Set([
|
|
|
39
43
|
'get_unreleased_versions',
|
|
40
44
|
'get_release_manager',
|
|
41
45
|
'wait_for_comment',
|
|
46
|
+
'get_grayrelease_status',
|
|
42
47
|
]);
|
|
43
48
|
|
|
44
49
|
function withToolAnnotations(tools) {
|
|
@@ -84,7 +89,7 @@ export function getToolDefinitions() {
|
|
|
84
89
|
type: 'object',
|
|
85
90
|
required: ['issueKey', 'transitionName'],
|
|
86
91
|
properties: {
|
|
87
|
-
issueKey: {type: 'string', description: '例如 OPS-123'},
|
|
92
|
+
issueKey: { type: 'string', description: '例如 OPS-123' },
|
|
88
93
|
transitionName: {
|
|
89
94
|
type: 'string',
|
|
90
95
|
description: '狀態名稱,例如 "Pending Approval"、"Approved"、"In Progress"、"Done"',
|
|
@@ -100,7 +105,7 @@ export function getToolDefinitions() {
|
|
|
100
105
|
type: 'object',
|
|
101
106
|
required: ['issueKey'],
|
|
102
107
|
properties: {
|
|
103
|
-
issueKey: {type: 'string'},
|
|
108
|
+
issueKey: { type: 'string' },
|
|
104
109
|
targetStatus: {
|
|
105
110
|
type: 'string',
|
|
106
111
|
default: 'Approved',
|
|
@@ -124,7 +129,7 @@ export function getToolDefinitions() {
|
|
|
124
129
|
type: 'object',
|
|
125
130
|
required: ['issueKey'],
|
|
126
131
|
properties: {
|
|
127
|
-
issueKey: {type: 'string'},
|
|
132
|
+
issueKey: { type: 'string' },
|
|
128
133
|
},
|
|
129
134
|
},
|
|
130
135
|
},
|
|
@@ -135,8 +140,8 @@ export function getToolDefinitions() {
|
|
|
135
140
|
type: 'object',
|
|
136
141
|
required: ['issueKey', 'message'],
|
|
137
142
|
properties: {
|
|
138
|
-
issueKey: {type: 'string'},
|
|
139
|
-
message: {type: 'string', description: '留言內容'},
|
|
143
|
+
issueKey: { type: 'string' },
|
|
144
|
+
message: { type: 'string', description: '留言內容' },
|
|
140
145
|
},
|
|
141
146
|
},
|
|
142
147
|
},
|
|
@@ -147,7 +152,7 @@ export function getToolDefinitions() {
|
|
|
147
152
|
type: 'object',
|
|
148
153
|
required: ['issueKey'],
|
|
149
154
|
properties: {
|
|
150
|
-
issueKey: {type: 'string'},
|
|
155
|
+
issueKey: { type: 'string' },
|
|
151
156
|
},
|
|
152
157
|
},
|
|
153
158
|
},
|
|
@@ -159,8 +164,8 @@ export function getToolDefinitions() {
|
|
|
159
164
|
type: 'object',
|
|
160
165
|
required: ['inwardKey', 'outwardKey'],
|
|
161
166
|
properties: {
|
|
162
|
-
inwardKey: {type: 'string', description: '被包含方 issue key,例如 CD 單 CID-1669'},
|
|
163
|
-
outwardKey: {type: 'string', description: '包含方 issue key,例如 CI 單 CID-1668'},
|
|
167
|
+
inwardKey: { type: 'string', description: '被包含方 issue key,例如 CD 單 CID-1669' },
|
|
168
|
+
outwardKey: { type: 'string', description: '包含方 issue key,例如 CI 單 CID-1668' },
|
|
164
169
|
linkType: {
|
|
165
170
|
type: 'string',
|
|
166
171
|
description:
|
|
@@ -173,14 +178,33 @@ export function getToolDefinitions() {
|
|
|
173
178
|
{
|
|
174
179
|
name: 'build_ticket',
|
|
175
180
|
description:
|
|
176
|
-
'觸發 Library 或
|
|
181
|
+
'觸發 Library、CI 或 GrayRelease 上版單的 Jenkins Build。自動處理前置狀態切換與 Jira Automation 等待,不需要手動切換狀態。GrayRelease 僅在使用者明確要求 build/rebuild 時使用此 tool;若使用者要求 GrayRelease deploy、dev deploy 或部署灰度單,必須改用 deploy_grayrelease,不可用此 tool 觸發 rebuild。',
|
|
177
182
|
inputSchema: {
|
|
178
183
|
type: 'object',
|
|
179
184
|
required: ['issueKey'],
|
|
180
185
|
properties: {
|
|
181
186
|
issueKey: {
|
|
182
187
|
type: 'string',
|
|
183
|
-
description: '要 build 的 issue key,例如 CID-1668',
|
|
188
|
+
description: '要 build 的 issue key,例如 CI/Library 單 CID-1668,或可重複 rebuild 的 GrayRelease 單 CID-822',
|
|
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,例如 CID-1668',
|
|
184
208
|
},
|
|
185
209
|
},
|
|
186
210
|
},
|
|
@@ -308,7 +332,8 @@ export function getToolDefinitions() {
|
|
|
308
332
|
* 執行 tool,回傳 { content: [{ type: 'text', text }] }
|
|
309
333
|
*/
|
|
310
334
|
export async function executeTool(name, args, deps) {
|
|
311
|
-
const {jira, notifier} = deps;
|
|
335
|
+
const { jira, notifier } = deps;
|
|
336
|
+
const progress = typeof deps.progress === 'function' ? deps.progress : () => {};
|
|
312
337
|
const poller = new Poller(jira);
|
|
313
338
|
|
|
314
339
|
switch (name) {
|
|
@@ -330,6 +355,7 @@ export async function executeTool(name, args, deps) {
|
|
|
330
355
|
const result = await poller.waitForStatus(args.issueKey, targetStatus, {
|
|
331
356
|
intervalMs: args.pollIntervalMs,
|
|
332
357
|
timeoutMs: args.timeoutMs,
|
|
358
|
+
onProgress: progress,
|
|
333
359
|
});
|
|
334
360
|
|
|
335
361
|
await notifier.notify(
|
|
@@ -351,43 +377,55 @@ export async function executeTool(name, args, deps) {
|
|
|
351
377
|
|
|
352
378
|
case 'add_deploy_comment': {
|
|
353
379
|
await notifier.notify(args.issueKey, args.message);
|
|
354
|
-
return ok({issueKey: args.issueKey, commented: true});
|
|
380
|
+
return ok({ issueKey: args.issueKey, commented: true });
|
|
355
381
|
}
|
|
356
382
|
|
|
357
383
|
case 'list_transitions': {
|
|
358
384
|
const transitions = await jira.getTransitions(args.issueKey);
|
|
359
|
-
return ok(transitions.map((t) => ({id: t.id, name: t.name, to: t.to.name})));
|
|
385
|
+
return ok(transitions.map((t) => ({ id: t.id, name: t.name, to: t.to.name })));
|
|
360
386
|
}
|
|
361
387
|
|
|
362
388
|
case 'create_library_ticket':
|
|
363
|
-
return handleCreateLibraryTicket(args, {jira, notifier});
|
|
389
|
+
return handleCreateLibraryTicket(args, { jira, notifier });
|
|
364
390
|
|
|
365
391
|
case 'create_ci_ticket': {
|
|
366
|
-
return handleCreateCITicket(args, {jira, notifier});
|
|
392
|
+
return handleCreateCITicket(args, { jira, notifier });
|
|
367
393
|
}
|
|
368
394
|
|
|
369
395
|
case 'create_cd_ticket': {
|
|
370
|
-
return handleCreateCDTicket(args, {jira, notifier});
|
|
396
|
+
return handleCreateCDTicket(args, { jira, notifier });
|
|
371
397
|
}
|
|
372
398
|
|
|
373
399
|
case 'create_grayrelease_ticket': {
|
|
374
|
-
return handleCreateGrayReleaseTicket(args, {jira, notifier});
|
|
400
|
+
return handleCreateGrayReleaseTicket(args, { jira, notifier, progress });
|
|
375
401
|
}
|
|
376
402
|
|
|
377
403
|
case 'link_stg_grayrelease':
|
|
378
|
-
return handleLinkStgGrayRelease(args, {jira, notifier});
|
|
404
|
+
return handleLinkStgGrayRelease(args, { jira, notifier, progress });
|
|
405
|
+
|
|
406
|
+
case 'auto_grayrelease':
|
|
407
|
+
return handleAutoGrayRelease(args, { jira, notifier, progress });
|
|
408
|
+
|
|
409
|
+
case 'deploy_grayrelease':
|
|
410
|
+
return handleDeployGrayRelease(args, { jira, notifier, progress });
|
|
411
|
+
|
|
412
|
+
case 'get_grayrelease_status':
|
|
413
|
+
return handleGetGrayReleaseStatus(args, { jira });
|
|
414
|
+
|
|
415
|
+
case 'continue_grayrelease':
|
|
416
|
+
return handleContinueGrayRelease(args, { jira, notifier, progress });
|
|
379
417
|
|
|
380
418
|
case 'get_unreleased_versions':
|
|
381
|
-
return handleGetUnreleasedVersions(args, {jira});
|
|
419
|
+
return handleGetUnreleasedVersions(args, { jira });
|
|
382
420
|
|
|
383
421
|
case 'transition_to_wait_approval':
|
|
384
|
-
return handleTransitionToWaitApproval(args, {jira, notifier});
|
|
422
|
+
return handleTransitionToWaitApproval(args, { jira, notifier });
|
|
385
423
|
|
|
386
424
|
case 'get_release_manager':
|
|
387
425
|
return handleGetReleaseManager(args, {});
|
|
388
426
|
|
|
389
427
|
case 'wait_for_comment':
|
|
390
|
-
return handleWaitForComment(args, {jira});
|
|
428
|
+
return handleWaitForComment(args, { jira, progress });
|
|
391
429
|
|
|
392
430
|
case 'send_jabber_message':
|
|
393
431
|
return handleSendJabberMessage(args, {});
|
|
@@ -398,6 +436,7 @@ export async function executeTool(name, args, deps) {
|
|
|
398
436
|
notifier,
|
|
399
437
|
executeToolImpl: deps.executeToolImpl ?? executeTool,
|
|
400
438
|
workflowWaitOptions: deps.workflowWaitOptions,
|
|
439
|
+
progress,
|
|
401
440
|
});
|
|
402
441
|
|
|
403
442
|
case 'run_lib_to_stg_release':
|
|
@@ -406,6 +445,7 @@ export async function executeTool(name, args, deps) {
|
|
|
406
445
|
notifier,
|
|
407
446
|
executeToolImpl: deps.executeToolImpl ?? executeTool,
|
|
408
447
|
workflowWaitOptions: deps.workflowWaitOptions,
|
|
448
|
+
progress,
|
|
409
449
|
});
|
|
410
450
|
|
|
411
451
|
case 'link_issues': {
|
|
@@ -420,7 +460,7 @@ export async function executeTool(name, args, deps) {
|
|
|
420
460
|
}
|
|
421
461
|
|
|
422
462
|
case 'build_ticket': {
|
|
423
|
-
const {issueKey} = args;
|
|
463
|
+
const { issueKey, rebuild = false } = args;
|
|
424
464
|
const log = [];
|
|
425
465
|
|
|
426
466
|
/**
|
|
@@ -435,11 +475,21 @@ export async function executeTool(name, args, deps) {
|
|
|
435
475
|
* Library Release 完整狀態流程:
|
|
436
476
|
* TO DO → (Upload Lib Report) → UPLOAD LIB REPORT → (Apply for approval) → Wait Approval → (Approved) → WAIT FOR LIB BUILD → (Build) → Released
|
|
437
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,這裡不可處理部署流程
|
|
438
483
|
*/
|
|
439
484
|
|
|
440
|
-
|
|
441
|
-
// 'Upload Lib Report':Library 票從 TO DO 的第一步(對應 UI 的 Upload Library Report)
|
|
485
|
+
const BUILD_TRANSITIONS = ['Build', 'GrayRelease Build'];
|
|
442
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
|
+
];
|
|
443
493
|
const POLL_INTERVAL = 3000; // 3s
|
|
444
494
|
const MAX_WAIT_MS = 30000; // 最多等 30s(避免 MCP client timeout)
|
|
445
495
|
|
|
@@ -448,6 +498,11 @@ export async function executeTool(name, args, deps) {
|
|
|
448
498
|
return list.find((t) => t.name.toLowerCase() === name.toLowerCase());
|
|
449
499
|
};
|
|
450
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
|
+
|
|
451
506
|
const waitForTransition = async (name) => {
|
|
452
507
|
const deadline = Date.now() + MAX_WAIT_MS;
|
|
453
508
|
while (Date.now() < deadline) {
|
|
@@ -458,17 +513,58 @@ export async function executeTool(name, args, deps) {
|
|
|
458
513
|
return null;
|
|
459
514
|
};
|
|
460
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
|
+
|
|
461
526
|
try {
|
|
462
527
|
// 記錄初始狀態(用於判斷是否有狀態推進)
|
|
463
528
|
const initIssue = await jira.getIssue(issueKey);
|
|
464
529
|
const initStatus = initIssue.fields.status.name;
|
|
465
530
|
|
|
466
|
-
// Step 1: 確認目前是否已有 Build transition
|
|
467
|
-
let buildTrans = await
|
|
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
|
+
}
|
|
468
564
|
|
|
469
565
|
// Step 2: 若沒有,逐步觸發前置 transitions
|
|
470
566
|
if (!buildTrans) {
|
|
471
|
-
log.push('未找到 Build transition,逐步觸發前置狀態...');
|
|
567
|
+
log.push('未找到 Build/GrayRelease Build transition,逐步觸發前置狀態...');
|
|
472
568
|
let preTriggered = false;
|
|
473
569
|
|
|
474
570
|
// 最多嘗試 PRE_TRANSITIONS.length 輪,每輪觸發一個可用的前置 transition
|
|
@@ -486,9 +582,9 @@ export async function executeTool(name, args, deps) {
|
|
|
486
582
|
// 等 Jira 更新狀態
|
|
487
583
|
await new Promise((r) => setTimeout(r, 2000));
|
|
488
584
|
|
|
489
|
-
buildTrans = await
|
|
585
|
+
buildTrans = await findAnyTransition(BUILD_TRANSITIONS);
|
|
490
586
|
if (buildTrans) {
|
|
491
|
-
log.push(` 已找到
|
|
587
|
+
log.push(` 已找到 ${buildTrans.name} transition`);
|
|
492
588
|
break;
|
|
493
589
|
}
|
|
494
590
|
}
|
|
@@ -496,7 +592,7 @@ export async function executeTool(name, args, deps) {
|
|
|
496
592
|
// 若還沒出現,再給 Jira Automation 最多 30s
|
|
497
593
|
if (!buildTrans) {
|
|
498
594
|
log.push(' 等待 Jira Automation 推進(最多 30s)...');
|
|
499
|
-
buildTrans = await
|
|
595
|
+
buildTrans = await waitForAnyTransition(BUILD_TRANSITIONS);
|
|
500
596
|
}
|
|
501
597
|
|
|
502
598
|
// 若仍未找到 Build transition,但有觸發前置 transition 且狀態已推進,
|
|
@@ -513,41 +609,83 @@ export async function executeTool(name, args, deps) {
|
|
|
513
609
|
issueKey,
|
|
514
610
|
`Library Build 已觸發(Jira Auto)狀態:${initStatus} → ${currentStatus}`,
|
|
515
611
|
);
|
|
516
|
-
return ok({issueKey, status: currentStatus, steps: log, autoTriggered: true});
|
|
612
|
+
return ok({ issueKey, status: currentStatus, steps: log, autoTriggered: true });
|
|
517
613
|
}
|
|
518
614
|
}
|
|
519
615
|
}
|
|
520
616
|
|
|
521
617
|
if (!buildTrans) {
|
|
522
618
|
const issue = await jira.getIssue(issueKey);
|
|
523
|
-
return error(`找不到 Build transition,目前狀態:${issue.fields.status.name}`);
|
|
619
|
+
return error(`找不到 Build/GrayRelease Build transition,目前狀態:${issue.fields.status.name}`);
|
|
524
620
|
}
|
|
525
621
|
|
|
526
622
|
// Step 4: 執行 Build
|
|
527
|
-
log.push(`執行
|
|
623
|
+
log.push(`執行 ${buildTrans.name} transition (id: ${buildTrans.id})...`);
|
|
528
624
|
await jira.transitionById(issueKey, buildTrans.id);
|
|
529
625
|
|
|
530
626
|
const issue = await jira.getIssue(issueKey);
|
|
531
627
|
const newStatus = issue.fields.status.name;
|
|
532
|
-
log.push(`✅
|
|
533
|
-
await notifier.notify(issueKey, `Jenkins
|
|
628
|
+
log.push(`✅ ${buildTrans.name} 已觸發,目前狀態:${newStatus}`);
|
|
629
|
+
await notifier.notify(issueKey, `Jenkins ${buildTrans.name} 已觸發(${newStatus})`);
|
|
534
630
|
|
|
535
|
-
return ok({issueKey, status: newStatus, steps: log});
|
|
631
|
+
return ok({ issueKey, status: newStatus, steps: log });
|
|
536
632
|
} catch (err) {
|
|
537
633
|
return error(`build_ticket 失敗: ${err.message}`);
|
|
538
634
|
}
|
|
539
635
|
}
|
|
540
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
|
+
|
|
541
679
|
case 'wait_to_stg': {
|
|
542
|
-
const {issueKey} = args;
|
|
680
|
+
const { issueKey } = args;
|
|
543
681
|
const log = [];
|
|
544
682
|
|
|
545
683
|
// CI 掃描 → STG 的標準流程步驟
|
|
546
684
|
// 每個元素:{ transition: '名稱', targetStatus: '到達後的狀態名稱' }
|
|
547
685
|
const STEPS = [
|
|
548
|
-
{transition: 'Upload Scan Report', targetStatus: 'Upload Report'},
|
|
549
|
-
{transition: 'Accept', targetStatus: 'Wait To DEV'},
|
|
550
|
-
{transition: 'Dev Done', targetStatus: 'Wait To STG'},
|
|
686
|
+
{ transition: 'Upload Scan Report', targetStatus: 'Upload Report' },
|
|
687
|
+
{ transition: 'Accept', targetStatus: 'Wait To DEV' },
|
|
688
|
+
{ transition: 'Dev Done', targetStatus: 'Wait To STG' },
|
|
551
689
|
];
|
|
552
690
|
|
|
553
691
|
try {
|
|
@@ -573,14 +711,14 @@ export async function executeTool(name, args, deps) {
|
|
|
573
711
|
log.push(`✅ 完成,目前狀態:${finalStatus}`);
|
|
574
712
|
await notifier.notify(issueKey, `已切換至 ${finalStatus},可進行 STG 部署`);
|
|
575
713
|
|
|
576
|
-
return ok({issueKey, status: finalStatus, steps: log});
|
|
714
|
+
return ok({ issueKey, status: finalStatus, steps: log });
|
|
577
715
|
} catch (err) {
|
|
578
716
|
return error(`wait_to_stg 失敗: ${err.message}`);
|
|
579
717
|
}
|
|
580
718
|
}
|
|
581
719
|
|
|
582
720
|
case 'trigger_deployment': {
|
|
583
|
-
const {cdIssueKey, environment} = args;
|
|
721
|
+
const { cdIssueKey, environment } = args;
|
|
584
722
|
const log = [];
|
|
585
723
|
|
|
586
724
|
// 判斷環境是否屬於 PRD 群組(prd / dr / prd/dr / prd&dr)
|
|
@@ -635,10 +773,10 @@ export async function executeTool(name, args, deps) {
|
|
|
635
773
|
// 依序觸發的 Deployment transitions
|
|
636
774
|
// conditionalWait: true → 依 needSwitch 決定是否執行並等待 180s
|
|
637
775
|
const DEPLOY_TRANSITIONS = [
|
|
638
|
-
{name: 'To Pretask'},
|
|
639
|
-
{name: 'Swtich Execution Node', conditionalWait: true},
|
|
640
|
-
{name: 'To AutoDeploy'},
|
|
641
|
-
{name: 'Trigger AutoDeploy'},
|
|
776
|
+
{ name: 'To Pretask' },
|
|
777
|
+
{ name: 'Swtich Execution Node', conditionalWait: true },
|
|
778
|
+
{ name: 'To AutoDeploy' },
|
|
779
|
+
{ name: 'Trigger AutoDeploy' },
|
|
642
780
|
];
|
|
643
781
|
// 環境字串對照(用於 sub-task summary 比對)
|
|
644
782
|
const envUpper = environment.toUpperCase().replace('/', '').replace('&', '');
|
|
@@ -784,25 +922,34 @@ export async function executeTool(name, args, deps) {
|
|
|
784
922
|
}
|
|
785
923
|
|
|
786
924
|
case 'prepare_cd_deployment': {
|
|
787
|
-
const {issueKey} = args;
|
|
925
|
+
const { issueKey } = args;
|
|
788
926
|
// 正規化環境名稱(prd&dr → prd/dr)
|
|
789
927
|
const envCode = args.environment.toLowerCase().replace('&', '/');
|
|
790
928
|
const log = [];
|
|
791
929
|
|
|
792
930
|
// CD 部署前置 transitions(依序嘗試,直到找到部署 transition 或完成申請流程)
|
|
793
|
-
//
|
|
931
|
+
// DEV 流程會跳過通知主管簽核,建立 deployment 後直接嘗試自助 Approved / To Wait Deploy。
|
|
932
|
+
// STG/UAT/PRD 完整流程:
|
|
794
933
|
// TO DO → (Accept) → Wait For Send Notice Email
|
|
795
934
|
// → (Prepare to create deployment ticket) → Prepare For Deploy ← 建立 Deployment sub-task
|
|
796
935
|
// → (Apply for approval) → Wait Approval ← 等 Reviewer 核准
|
|
797
936
|
// → Wait Deploy ← 核准後自動切換,再呼叫 trigger_deployment
|
|
798
937
|
// ⚠️ 不加 'Approved':Approved 需要其他主管才能執行
|
|
799
|
-
const CD_PRE_TRANSITIONS = [
|
|
938
|
+
const CD_PRE_TRANSITIONS = envCode === 'dev' ? [
|
|
939
|
+
'Accept',
|
|
940
|
+
] : [
|
|
800
941
|
'Accept',
|
|
801
942
|
'Prepare to create deployment ticket',
|
|
802
943
|
'Apply for approval',
|
|
803
944
|
'To Wait Deploy',
|
|
804
945
|
];
|
|
805
946
|
|
|
947
|
+
const DEV_SELF_SERVICE_TRANSITIONS = [
|
|
948
|
+
'Apply for approval',
|
|
949
|
+
'Approved',
|
|
950
|
+
'To Wait Deploy',
|
|
951
|
+
];
|
|
952
|
+
|
|
806
953
|
// 部署 transition 名稱(支援多種命名)
|
|
807
954
|
const DEPLOY_TRANSITION_NAMES = [
|
|
808
955
|
'Prepare to create deployment ticket', // 實際 Jira transition 名稱
|
|
@@ -822,7 +969,7 @@ export async function executeTool(name, args, deps) {
|
|
|
822
969
|
// Step 2: 更新 CD 單的環境欄位(customfield_13436)
|
|
823
970
|
if (CD_FIELD_IDS.env && ENV_CODES[envCode]) {
|
|
824
971
|
await jira.updateIssue(issueKey, {
|
|
825
|
-
[CD_FIELD_IDS.env]: {id: ENV_CODES[envCode]},
|
|
972
|
+
[CD_FIELD_IDS.env]: { id: ENV_CODES[envCode] },
|
|
826
973
|
});
|
|
827
974
|
log.push(`✅ 環境欄位更新為 ${envCode.toUpperCase()}`);
|
|
828
975
|
}
|
|
@@ -839,6 +986,45 @@ export async function executeTool(name, args, deps) {
|
|
|
839
986
|
|
|
840
987
|
let deployTrans = await findDeployTrans();
|
|
841
988
|
|
|
989
|
+
const getCurrentStatus = async () => {
|
|
990
|
+
const issue = await jira.getIssue(issueKey);
|
|
991
|
+
return issue.fields.status.name;
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
const runDevSelfServiceTransitions = async () => {
|
|
995
|
+
if (envCode !== 'dev') return;
|
|
996
|
+
for (const transitionName of DEV_SELF_SERVICE_TRANSITIONS) {
|
|
997
|
+
const currentStatus = await getCurrentStatus();
|
|
998
|
+
if (currentStatus.toLowerCase() === 'wait deploy') {
|
|
999
|
+
log.push(' DEV 自助流程已到 Wait Deploy,停止續跑');
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const transitions = await jira.getTransitions(issueKey);
|
|
1004
|
+
const next = transitions.find(
|
|
1005
|
+
(t) => t.name.toLowerCase() === transitionName.toLowerCase(),
|
|
1006
|
+
);
|
|
1007
|
+
if (!next) continue;
|
|
1008
|
+
|
|
1009
|
+
log.push(` DEV 自助流程觸發「${next.name}」...`);
|
|
1010
|
+
await jira.transitionById(issueKey, next.id);
|
|
1011
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
if (envCode === 'dev') {
|
|
1016
|
+
const currentStatus = await getCurrentStatus();
|
|
1017
|
+
if (currentStatus.toLowerCase() === 'wait deploy') {
|
|
1018
|
+
log.push('✅ CD 單已在 Wait Deploy,無需重複 prepare');
|
|
1019
|
+
return ok({
|
|
1020
|
+
issueKey,
|
|
1021
|
+
environment: envCode,
|
|
1022
|
+
status: currentStatus,
|
|
1023
|
+
steps: log,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
842
1028
|
if (!deployTrans) {
|
|
843
1029
|
log.push('未找到部署 transition,逐步觸發前置狀態...');
|
|
844
1030
|
for (const preName of CD_PRE_TRANSITIONS) {
|
|
@@ -858,6 +1044,25 @@ export async function executeTool(name, args, deps) {
|
|
|
858
1044
|
}
|
|
859
1045
|
}
|
|
860
1046
|
|
|
1047
|
+
if (!deployTrans && envCode === 'dev') {
|
|
1048
|
+
await runDevSelfServiceTransitions();
|
|
1049
|
+
const currentStatus = await getCurrentStatus();
|
|
1050
|
+
if (currentStatus.toLowerCase() === 'wait deploy') {
|
|
1051
|
+
log.push(`✅ DEV 自助流程完成,目前狀態:${currentStatus}`);
|
|
1052
|
+
await notifier.notify(
|
|
1053
|
+
issueKey,
|
|
1054
|
+
`CD 部署已觸發(環境: ${envCode.toUpperCase()},狀態: ${currentStatus})`,
|
|
1055
|
+
);
|
|
1056
|
+
return ok({
|
|
1057
|
+
issueKey,
|
|
1058
|
+
environment: envCode,
|
|
1059
|
+
status: currentStatus,
|
|
1060
|
+
steps: log,
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
deployTrans = await findDeployTrans();
|
|
1064
|
+
}
|
|
1065
|
+
|
|
861
1066
|
if (!deployTrans) {
|
|
862
1067
|
const issue = await jira.getIssue(issueKey);
|
|
863
1068
|
const transitions = await jira.getTransitions(issueKey);
|
|
@@ -871,21 +1076,37 @@ export async function executeTool(name, args, deps) {
|
|
|
871
1076
|
|
|
872
1077
|
// Step 4: 觸发部署 transition
|
|
873
1078
|
log.push(`執行「${deployTrans.name}」transition(id: ${deployTrans.id})...`);
|
|
874
|
-
|
|
1079
|
+
try {
|
|
1080
|
+
await jira.transitionById(issueKey, deployTrans.id);
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
if (envCode === 'dev' && err.message.includes('Already create deployment ticket')) {
|
|
1083
|
+
log.push(' Deployment ticket 已存在,繼續 DEV approval 續跑流程');
|
|
1084
|
+
} else {
|
|
1085
|
+
throw err;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
875
1088
|
|
|
876
1089
|
const issue = await jira.getIssue(issueKey);
|
|
877
1090
|
const newStatus = issue.fields.status.name;
|
|
878
1091
|
log.push(`✅ 部署已觸发,目前狀態:${newStatus}`);
|
|
879
1092
|
|
|
1093
|
+
await runDevSelfServiceTransitions();
|
|
1094
|
+
|
|
1095
|
+
const finalIssue = await jira.getIssue(issueKey);
|
|
1096
|
+
const finalStatus = finalIssue.fields.status.name;
|
|
1097
|
+
if (finalStatus !== newStatus) {
|
|
1098
|
+
log.push(`✅ DEV 自助流程完成,目前狀態:${finalStatus}`);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
880
1101
|
await notifier.notify(
|
|
881
1102
|
issueKey,
|
|
882
|
-
`CD 部署已觸發(環境: ${envCode.toUpperCase()},狀態: ${
|
|
1103
|
+
`CD 部署已觸發(環境: ${envCode.toUpperCase()},狀態: ${finalStatus})`,
|
|
883
1104
|
);
|
|
884
1105
|
|
|
885
1106
|
return ok({
|
|
886
1107
|
issueKey,
|
|
887
1108
|
environment: envCode,
|
|
888
|
-
status:
|
|
1109
|
+
status: finalStatus,
|
|
889
1110
|
steps: log,
|
|
890
1111
|
});
|
|
891
1112
|
} catch (err) {
|
|
@@ -894,8 +1115,8 @@ export async function executeTool(name, args, deps) {
|
|
|
894
1115
|
}
|
|
895
1116
|
|
|
896
1117
|
case 'update_assignee': {
|
|
897
|
-
const {issueKey} = args;
|
|
898
|
-
let {accountId, displayName} = args;
|
|
1118
|
+
const { issueKey } = args;
|
|
1119
|
+
let { accountId, displayName } = args;
|
|
899
1120
|
|
|
900
1121
|
// 若只提供 displayName(或名稱),嘗試從 USER_MAP 查找 accountId
|
|
901
1122
|
if (!accountId && displayName) {
|
|
@@ -916,7 +1137,7 @@ export async function executeTool(name, args, deps) {
|
|
|
916
1137
|
await jira.updateAssignee(issueKey, accountId);
|
|
917
1138
|
return ok({
|
|
918
1139
|
issueKey,
|
|
919
|
-
assignee: {accountId, displayName: displayName ?? accountId},
|
|
1140
|
+
assignee: { accountId, displayName: displayName ?? accountId },
|
|
920
1141
|
message: `✅ ${issueKey} Assignee 已更新為 ${displayName ? `${displayName}(${accountId})` : accountId}`,
|
|
921
1142
|
});
|
|
922
1143
|
} catch (err) {
|
|
@@ -925,7 +1146,7 @@ export async function executeTool(name, args, deps) {
|
|
|
925
1146
|
}
|
|
926
1147
|
|
|
927
1148
|
case 'cancel_release': {
|
|
928
|
-
const {systemCode} = args;
|
|
1149
|
+
const { systemCode } = args;
|
|
929
1150
|
let ciIssueKey = args.ciIssueKey ?? null;
|
|
930
1151
|
|
|
931
1152
|
try {
|
|
@@ -1017,46 +1238,46 @@ export async function executeTool(name, args, deps) {
|
|
|
1017
1238
|
}
|
|
1018
1239
|
|
|
1019
1240
|
case 'get_release_status': {
|
|
1020
|
-
const {systemCode} = args;
|
|
1241
|
+
const { systemCode } = args;
|
|
1021
1242
|
|
|
1022
1243
|
// 狀態 → 說明 + 建議下一步
|
|
1023
1244
|
const describeStatus = (issueType, status) => {
|
|
1024
1245
|
const s = status.toLowerCase();
|
|
1025
1246
|
if (issueType === 'Library') {
|
|
1026
|
-
if (s === 'to do') return {emoji: '⏳', desc: '尚未開始', next: `執行 build_ticket`};
|
|
1027
|
-
if (s === 'upload lib report') return {emoji: '⏳', desc: '等待上傳 Lib Report', next: `執行 build_ticket`};
|
|
1028
|
-
if (s === 'wait approval') return {emoji: '⏳', desc: '等待主管核准', next: `wait_for_approval`};
|
|
1029
|
-
if (s === 'wait for lib build') return {emoji: '🔨', desc: '等待 Jenkins Build', next: `執行 build_ticket`};
|
|
1030
|
-
if (s === 'released') return {emoji: '✅', desc: '已完成', next: '可開 CI'};
|
|
1031
|
-
if (s === 'cancelled') return {emoji: '❌', desc: '已取消', next: null};
|
|
1032
|
-
return {emoji: '🔄', desc: status, next: null};
|
|
1247
|
+
if (s === 'to do') return { emoji: '⏳', desc: '尚未開始', next: `執行 build_ticket` };
|
|
1248
|
+
if (s === 'upload lib report') return { emoji: '⏳', desc: '等待上傳 Lib Report', next: `執行 build_ticket` };
|
|
1249
|
+
if (s === 'wait approval') return { emoji: '⏳', desc: '等待主管核准', next: `wait_for_approval` };
|
|
1250
|
+
if (s === 'wait for lib build') return { emoji: '🔨', desc: '等待 Jenkins Build', next: `執行 build_ticket` };
|
|
1251
|
+
if (s === 'released') return { emoji: '✅', desc: '已完成', next: '可開 CI' };
|
|
1252
|
+
if (s === 'cancelled') return { emoji: '❌', desc: '已取消', next: null };
|
|
1253
|
+
return { emoji: '🔄', desc: status, next: null };
|
|
1033
1254
|
}
|
|
1034
1255
|
if (issueType === 'CI') {
|
|
1035
|
-
if (s === 'to do') return {emoji: '⏳', desc: '尚未開始', next: '執行 build_ticket'};
|
|
1036
|
-
if (s === 'wait for build') return {emoji: '⏳', desc: '等待 Jenkins Build', next: '執行 build_ticket'};
|
|
1037
|
-
if (s === 'compliance scan') return {emoji: '🔍', desc: '掃描中', next: '執行 wait_to_stg'};
|
|
1038
|
-
if (s === 'upload report') return {emoji: '📋', desc: '上傳掃描報告', next: '執行 wait_to_stg'};
|
|
1039
|
-
if (s === 'wait to dev') return {emoji: '🔄', desc: 'Build 完成,等待 DEV', next: '執行 wait_to_stg'};
|
|
1040
|
-
if (s === 'wait to stg') return {emoji: '✅', desc: 'Build 完成,等待 STG 部署', next: '可建 CD(STG) 並執行 prepare_cd_deployment'};
|
|
1041
|
-
if (s === 'wait to uat') return {emoji: '✅', desc: 'STG 完成,等待 UAT', next: '可建 CD(UAT)'};
|
|
1042
|
-
if (s === 'wait for upload') return {emoji: '📦', desc: '等待上傳 Pre-Release', next: null};
|
|
1043
|
-
if (s === 'wait to prd/dr') return {emoji: '✅', desc: 'UAT 完成,等待 PRD/DR', next: '可建 CD(PRD/DR)'};
|
|
1044
|
-
if (s === 'done') return {emoji: '✅', desc: '已完成', next: null};
|
|
1045
|
-
if (s === 'cancelled') return {emoji: '❌', desc: '已取消', next: null};
|
|
1046
|
-
return {emoji: '🔄', desc: status, next: null};
|
|
1256
|
+
if (s === 'to do') return { emoji: '⏳', desc: '尚未開始', next: '執行 build_ticket' };
|
|
1257
|
+
if (s === 'wait for build') return { emoji: '⏳', desc: '等待 Jenkins Build', next: '執行 build_ticket' };
|
|
1258
|
+
if (s === 'compliance scan') return { emoji: '🔍', desc: '掃描中', next: '執行 wait_to_stg' };
|
|
1259
|
+
if (s === 'upload report') return { emoji: '📋', desc: '上傳掃描報告', next: '執行 wait_to_stg' };
|
|
1260
|
+
if (s === 'wait to dev') return { emoji: '🔄', desc: 'Build 完成,等待 DEV', next: '執行 wait_to_stg' };
|
|
1261
|
+
if (s === 'wait to stg') return { emoji: '✅', desc: 'Build 完成,等待 STG 部署', next: '可建 CD(STG) 並執行 prepare_cd_deployment' };
|
|
1262
|
+
if (s === 'wait to uat') return { emoji: '✅', desc: 'STG 完成,等待 UAT', next: '可建 CD(UAT)' };
|
|
1263
|
+
if (s === 'wait for upload') return { emoji: '📦', desc: '等待上傳 Pre-Release', next: null };
|
|
1264
|
+
if (s === 'wait to prd/dr') return { emoji: '✅', desc: 'UAT 完成,等待 PRD/DR', next: '可建 CD(PRD/DR)' };
|
|
1265
|
+
if (s === 'done') return { emoji: '✅', desc: '已完成', next: null };
|
|
1266
|
+
if (s === 'cancelled') return { emoji: '❌', desc: '已取消', next: null };
|
|
1267
|
+
return { emoji: '🔄', desc: status, next: null };
|
|
1047
1268
|
}
|
|
1048
1269
|
if (issueType === 'CD') {
|
|
1049
|
-
if (s === 'to do') return {emoji: '⏳', desc: '尚未開始', next: '執行 prepare_cd_deployment'};
|
|
1050
|
-
if (s === 'wait for send notice email') return {emoji: '⏳', desc: '等待通知信', next: '執行 prepare_cd_deployment'};
|
|
1051
|
-
if (s === 'prepare for deploy') return {emoji: '⏳', desc: '部署單建立中', next: null};
|
|
1052
|
-
if (s === 'wait approval') return {emoji: '⏳', desc: '等待主管核准', next: 'wait_for_approval'};
|
|
1053
|
-
if (s === 'wait deploy') return {emoji: '✅', desc: '已核准,等待部署', next: '執行 trigger_deployment'};
|
|
1054
|
-
if (s === 'deploying') return {emoji: '🚀', desc: '部署中', next: null};
|
|
1055
|
-
if (s === 'done') return {emoji: '✅', desc: '部署完成', next: null};
|
|
1056
|
-
if (s === 'cancelled') return {emoji: '❌', desc: '已取消', next: null};
|
|
1057
|
-
return {emoji: '🔄', desc: status, next: null};
|
|
1270
|
+
if (s === 'to do') return { emoji: '⏳', desc: '尚未開始', next: '執行 prepare_cd_deployment' };
|
|
1271
|
+
if (s === 'wait for send notice email') return { emoji: '⏳', desc: '等待通知信', next: '執行 prepare_cd_deployment' };
|
|
1272
|
+
if (s === 'prepare for deploy') return { emoji: '⏳', desc: '部署單建立中', next: null };
|
|
1273
|
+
if (s === 'wait approval') return { emoji: '⏳', desc: '等待主管核准', next: 'wait_for_approval' };
|
|
1274
|
+
if (s === 'wait deploy') return { emoji: '✅', desc: '已核准,等待部署', next: '執行 trigger_deployment' };
|
|
1275
|
+
if (s === 'deploying') return { emoji: '🚀', desc: '部署中', next: null };
|
|
1276
|
+
if (s === 'done') return { emoji: '✅', desc: '部署完成', next: null };
|
|
1277
|
+
if (s === 'cancelled') return { emoji: '❌', desc: '已取消', next: null };
|
|
1278
|
+
return { emoji: '🔄', desc: status, next: null };
|
|
1058
1279
|
}
|
|
1059
|
-
return {emoji: '🔄', desc: status, next: null};
|
|
1280
|
+
return { emoji: '🔄', desc: status, next: null };
|
|
1060
1281
|
};
|
|
1061
1282
|
|
|
1062
1283
|
try {
|
|
@@ -1096,9 +1317,8 @@ export async function executeTool(name, args, deps) {
|
|
|
1096
1317
|
const linked = l.inwardIssue ?? l.outwardIssue;
|
|
1097
1318
|
const d = describeStatus('Library', linked.fields.status?.name ?? '');
|
|
1098
1319
|
const gitBranch = libraryExtraFields[i]?.customfield_13431 ?? '(無)';
|
|
1099
|
-
return ` ${linked.key} [${linked.fields.status?.name}] ${d.emoji} ${d.desc}${
|
|
1100
|
-
|
|
1101
|
-
}\n branch: ${gitBranch}\n ${linked.fields.summary ?? ''}`;
|
|
1320
|
+
return ` ${linked.key} [${linked.fields.status?.name}] ${d.emoji} ${d.desc}${d.next ? ` → ${d.next}(${linked.key})` : ''
|
|
1321
|
+
}\n branch: ${gitBranch}\n ${linked.fields.summary ?? ''}`;
|
|
1102
1322
|
});
|
|
1103
1323
|
|
|
1104
1324
|
// Step 3: 查近期 CD 單(取 5 筆)
|
|
@@ -1117,9 +1337,8 @@ export async function executeTool(name, args, deps) {
|
|
|
1117
1337
|
|
|
1118
1338
|
const cdLines = Object.entries(cdByEnv).map(([env, cd]) => {
|
|
1119
1339
|
const d = describeStatus('CD', cd.fields.status.name);
|
|
1120
|
-
return ` ${env.toUpperCase()}:${cd.key} [${cd.fields.status.name}] ${d.emoji} ${d.desc}${
|
|
1121
|
-
|
|
1122
|
-
}`;
|
|
1340
|
+
return ` ${env.toUpperCase()}:${cd.key} [${cd.fields.status.name}] ${d.emoji} ${d.desc}${d.next ? ` → ${d.next}` : ''
|
|
1341
|
+
}`;
|
|
1123
1342
|
});
|
|
1124
1343
|
|
|
1125
1344
|
// Step 4: 組合輸出
|
|
@@ -1128,8 +1347,7 @@ export async function executeTool(name, args, deps) {
|
|
|
1128
1347
|
`📦 ${systemCode} Release 現況(${today})`,
|
|
1129
1348
|
'',
|
|
1130
1349
|
'── CI ──────────────────────────────',
|
|
1131
|
-
` ${ciIssue.key} [${ciIssue.fields.status.name}] ${ciStatus.emoji} ${ciStatus.desc}${
|
|
1132
|
-
ciStatus.next ? ` → ${ciStatus.next}` : ''
|
|
1350
|
+
` ${ciIssue.key} [${ciIssue.fields.status.name}] ${ciStatus.emoji} ${ciStatus.desc}${ciStatus.next ? ` → ${ciStatus.next}` : ''
|
|
1133
1351
|
}`,
|
|
1134
1352
|
` 版本:${ciVersion}`,
|
|
1135
1353
|
'',
|
|
@@ -1140,17 +1358,17 @@ export async function executeTool(name, args, deps) {
|
|
|
1140
1358
|
...(cdLines.length ? cdLines : [' (尚無 CD 單)']),
|
|
1141
1359
|
];
|
|
1142
1360
|
|
|
1143
|
-
return ok({summary: lines.join('\n'), ci: ciIssue.key, version: ciVersion});
|
|
1361
|
+
return ok({ summary: lines.join('\n'), ci: ciIssue.key, version: ciVersion });
|
|
1144
1362
|
} catch (err) {
|
|
1145
1363
|
return error(`get_release_status 失敗: ${err.message}`);
|
|
1146
1364
|
}
|
|
1147
1365
|
}
|
|
1148
1366
|
|
|
1149
1367
|
case 'get_next_lib_version':
|
|
1150
|
-
return handleGetNextLibVersion(args, {jira});
|
|
1368
|
+
return handleGetNextLibVersion(args, { jira });
|
|
1151
1369
|
|
|
1152
1370
|
case 'get_next_ci_version':
|
|
1153
|
-
return handleGetNextCIVersion(args, {jira});
|
|
1371
|
+
return handleGetNextCIVersion(args, { jira });
|
|
1154
1372
|
|
|
1155
1373
|
default:
|
|
1156
1374
|
throw new Error(`Unknown tool: ${name}`);
|
|
@@ -1159,13 +1377,13 @@ export async function executeTool(name, args, deps) {
|
|
|
1159
1377
|
|
|
1160
1378
|
function ok(data) {
|
|
1161
1379
|
return {
|
|
1162
|
-
content: [{type: 'text', text: JSON.stringify(data, null, 2)}],
|
|
1380
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
1163
1381
|
};
|
|
1164
1382
|
}
|
|
1165
1383
|
|
|
1166
1384
|
function error(msg) {
|
|
1167
1385
|
return {
|
|
1168
|
-
content: [{type: 'text', text: `❌ 錯誤: ${msg}`}],
|
|
1386
|
+
content: [{ type: 'text', text: `❌ 錯誤: ${msg}` }],
|
|
1169
1387
|
isError: true,
|
|
1170
1388
|
};
|
|
1171
1389
|
}
|