@jira-deploy/core 1.0.2 → 1.0.3
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/environments.js +1 -1
- package/constants/field-ids.js +3 -0
- package/dry-run.js +4 -0
- package/jira-client.js +13 -0
- package/package.json +1 -1
- package/tools/cd.js +3 -1
- package/tools/grayrelease.js +781 -0
- package/tools/index.js +158 -5
- package/tools/library.js +2 -1
- package/tools.test.js +558 -0
package/tools/index.js
CHANGED
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
getGrayReleaseToolDefinitions,
|
|
18
18
|
handleCreateGrayReleaseTicket,
|
|
19
19
|
handleLinkStgGrayRelease,
|
|
20
|
+
handleAutoGrayRelease,
|
|
21
|
+
handleGetGrayReleaseStatus,
|
|
22
|
+
handleContinueGrayRelease,
|
|
20
23
|
} from './grayrelease.js';
|
|
21
24
|
import {
|
|
22
25
|
getReleaseToolDefinitions,
|
|
@@ -39,6 +42,7 @@ const READ_ONLY_TOOL_NAMES = new Set([
|
|
|
39
42
|
'get_unreleased_versions',
|
|
40
43
|
'get_release_manager',
|
|
41
44
|
'wait_for_comment',
|
|
45
|
+
'get_grayrelease_status',
|
|
42
46
|
]);
|
|
43
47
|
|
|
44
48
|
function withToolAnnotations(tools) {
|
|
@@ -185,6 +189,21 @@ export function getToolDefinitions() {
|
|
|
185
189
|
},
|
|
186
190
|
},
|
|
187
191
|
},
|
|
192
|
+
{
|
|
193
|
+
name: 'wait_to_dev',
|
|
194
|
+
description:
|
|
195
|
+
'CI 單 build 完成後,自動走完掃描流程切到 Wait To DEV 狀態。流程:Compliance Scan → Upload Scan Report → Accept → Wait To DEV(不執行 Dev Done)',
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
required: ['issueKey'],
|
|
199
|
+
properties: {
|
|
200
|
+
issueKey: {
|
|
201
|
+
type: 'string',
|
|
202
|
+
description: 'CI issue key,例如 CID-1668',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
188
207
|
{
|
|
189
208
|
name: 'wait_to_stg',
|
|
190
209
|
description:
|
|
@@ -377,6 +396,15 @@ export async function executeTool(name, args, deps) {
|
|
|
377
396
|
case 'link_stg_grayrelease':
|
|
378
397
|
return handleLinkStgGrayRelease(args, {jira, notifier});
|
|
379
398
|
|
|
399
|
+
case 'auto_grayrelease':
|
|
400
|
+
return handleAutoGrayRelease(args, {jira, notifier});
|
|
401
|
+
|
|
402
|
+
case 'get_grayrelease_status':
|
|
403
|
+
return handleGetGrayReleaseStatus(args, {jira});
|
|
404
|
+
|
|
405
|
+
case 'continue_grayrelease':
|
|
406
|
+
return handleContinueGrayRelease(args, {jira, notifier});
|
|
407
|
+
|
|
380
408
|
case 'get_unreleased_versions':
|
|
381
409
|
return handleGetUnreleasedVersions(args, {jira});
|
|
382
410
|
|
|
@@ -538,6 +566,48 @@ export async function executeTool(name, args, deps) {
|
|
|
538
566
|
}
|
|
539
567
|
}
|
|
540
568
|
|
|
569
|
+
case 'wait_to_dev': {
|
|
570
|
+
const {issueKey} = args;
|
|
571
|
+
const log = [];
|
|
572
|
+
|
|
573
|
+
const STEPS = [
|
|
574
|
+
{transition: 'Upload Scan Report', targetStatus: 'Upload Report'},
|
|
575
|
+
{transition: 'Accept', targetStatus: 'Wait To DEV'},
|
|
576
|
+
];
|
|
577
|
+
const finalTargetStatus = STEPS.at(-1).targetStatus;
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
for (const step of STEPS) {
|
|
581
|
+
const transitions = await jira.getTransitions(issueKey);
|
|
582
|
+
const t = transitions.find((t) => t.name.toLowerCase() === step.transition.toLowerCase());
|
|
583
|
+
if (!t) {
|
|
584
|
+
const issue = await jira.getIssue(issueKey);
|
|
585
|
+
const current = issue.fields.status.name;
|
|
586
|
+
if (current.toLowerCase() === step.targetStatus.toLowerCase()) {
|
|
587
|
+
log.push(` 已是 ${current},跳過「${step.transition}」`);
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (current.toLowerCase() === finalTargetStatus.toLowerCase()) {
|
|
591
|
+
log.push(` 已是 ${current},流程已完成`);
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
return error(`找不到 transition「${step.transition}」,目前狀態:${current}`);
|
|
595
|
+
}
|
|
596
|
+
log.push(`執行「${t.name}」→ ${step.targetStatus}`);
|
|
597
|
+
await jira.transitionById(issueKey, t.id);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const issue = await jira.getIssue(issueKey);
|
|
601
|
+
const finalStatus = issue.fields.status.name;
|
|
602
|
+
log.push(`✅ 完成,目前狀態:${finalStatus}`);
|
|
603
|
+
await notifier.notify(issueKey, `已切換至 ${finalStatus},可進行 DEV 部署`);
|
|
604
|
+
|
|
605
|
+
return ok({issueKey, status: finalStatus, steps: log});
|
|
606
|
+
} catch (err) {
|
|
607
|
+
return error(`wait_to_dev 失敗: ${err.message}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
541
611
|
case 'wait_to_stg': {
|
|
542
612
|
const {issueKey} = args;
|
|
543
613
|
const log = [];
|
|
@@ -790,19 +860,28 @@ export async function executeTool(name, args, deps) {
|
|
|
790
860
|
const log = [];
|
|
791
861
|
|
|
792
862
|
// CD 部署前置 transitions(依序嘗試,直到找到部署 transition 或完成申請流程)
|
|
793
|
-
//
|
|
863
|
+
// DEV 流程會跳過通知主管簽核,建立 deployment 後直接嘗試自助 Approved / To Wait Deploy。
|
|
864
|
+
// STG/UAT/PRD 完整流程:
|
|
794
865
|
// TO DO → (Accept) → Wait For Send Notice Email
|
|
795
866
|
// → (Prepare to create deployment ticket) → Prepare For Deploy ← 建立 Deployment sub-task
|
|
796
867
|
// → (Apply for approval) → Wait Approval ← 等 Reviewer 核准
|
|
797
868
|
// → Wait Deploy ← 核准後自動切換,再呼叫 trigger_deployment
|
|
798
869
|
// ⚠️ 不加 'Approved':Approved 需要其他主管才能執行
|
|
799
|
-
const CD_PRE_TRANSITIONS = [
|
|
870
|
+
const CD_PRE_TRANSITIONS = envCode === 'dev' ? [
|
|
871
|
+
'Accept',
|
|
872
|
+
] : [
|
|
800
873
|
'Accept',
|
|
801
874
|
'Prepare to create deployment ticket',
|
|
802
875
|
'Apply for approval',
|
|
803
876
|
'To Wait Deploy',
|
|
804
877
|
];
|
|
805
878
|
|
|
879
|
+
const DEV_SELF_SERVICE_TRANSITIONS = [
|
|
880
|
+
'Apply for approval',
|
|
881
|
+
'Approved',
|
|
882
|
+
'To Wait Deploy',
|
|
883
|
+
];
|
|
884
|
+
|
|
806
885
|
// 部署 transition 名稱(支援多種命名)
|
|
807
886
|
const DEPLOY_TRANSITION_NAMES = [
|
|
808
887
|
'Prepare to create deployment ticket', // 實際 Jira transition 名稱
|
|
@@ -839,6 +918,45 @@ export async function executeTool(name, args, deps) {
|
|
|
839
918
|
|
|
840
919
|
let deployTrans = await findDeployTrans();
|
|
841
920
|
|
|
921
|
+
const getCurrentStatus = async () => {
|
|
922
|
+
const issue = await jira.getIssue(issueKey);
|
|
923
|
+
return issue.fields.status.name;
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
const runDevSelfServiceTransitions = async () => {
|
|
927
|
+
if (envCode !== 'dev') return;
|
|
928
|
+
for (const transitionName of DEV_SELF_SERVICE_TRANSITIONS) {
|
|
929
|
+
const currentStatus = await getCurrentStatus();
|
|
930
|
+
if (currentStatus.toLowerCase() === 'wait deploy') {
|
|
931
|
+
log.push(' DEV 自助流程已到 Wait Deploy,停止續跑');
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const transitions = await jira.getTransitions(issueKey);
|
|
936
|
+
const next = transitions.find(
|
|
937
|
+
(t) => t.name.toLowerCase() === transitionName.toLowerCase(),
|
|
938
|
+
);
|
|
939
|
+
if (!next) continue;
|
|
940
|
+
|
|
941
|
+
log.push(` DEV 自助流程觸發「${next.name}」...`);
|
|
942
|
+
await jira.transitionById(issueKey, next.id);
|
|
943
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
if (envCode === 'dev') {
|
|
948
|
+
const currentStatus = await getCurrentStatus();
|
|
949
|
+
if (currentStatus.toLowerCase() === 'wait deploy') {
|
|
950
|
+
log.push('✅ CD 單已在 Wait Deploy,無需重複 prepare');
|
|
951
|
+
return ok({
|
|
952
|
+
issueKey,
|
|
953
|
+
environment: envCode,
|
|
954
|
+
status: currentStatus,
|
|
955
|
+
steps: log,
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
842
960
|
if (!deployTrans) {
|
|
843
961
|
log.push('未找到部署 transition,逐步觸發前置狀態...');
|
|
844
962
|
for (const preName of CD_PRE_TRANSITIONS) {
|
|
@@ -858,6 +976,25 @@ export async function executeTool(name, args, deps) {
|
|
|
858
976
|
}
|
|
859
977
|
}
|
|
860
978
|
|
|
979
|
+
if (!deployTrans && envCode === 'dev') {
|
|
980
|
+
await runDevSelfServiceTransitions();
|
|
981
|
+
const currentStatus = await getCurrentStatus();
|
|
982
|
+
if (currentStatus.toLowerCase() === 'wait deploy') {
|
|
983
|
+
log.push(`✅ DEV 自助流程完成,目前狀態:${currentStatus}`);
|
|
984
|
+
await notifier.notify(
|
|
985
|
+
issueKey,
|
|
986
|
+
`CD 部署已觸發(環境: ${envCode.toUpperCase()},狀態: ${currentStatus})`,
|
|
987
|
+
);
|
|
988
|
+
return ok({
|
|
989
|
+
issueKey,
|
|
990
|
+
environment: envCode,
|
|
991
|
+
status: currentStatus,
|
|
992
|
+
steps: log,
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
deployTrans = await findDeployTrans();
|
|
996
|
+
}
|
|
997
|
+
|
|
861
998
|
if (!deployTrans) {
|
|
862
999
|
const issue = await jira.getIssue(issueKey);
|
|
863
1000
|
const transitions = await jira.getTransitions(issueKey);
|
|
@@ -871,21 +1008,37 @@ export async function executeTool(name, args, deps) {
|
|
|
871
1008
|
|
|
872
1009
|
// Step 4: 觸发部署 transition
|
|
873
1010
|
log.push(`執行「${deployTrans.name}」transition(id: ${deployTrans.id})...`);
|
|
874
|
-
|
|
1011
|
+
try {
|
|
1012
|
+
await jira.transitionById(issueKey, deployTrans.id);
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
if (envCode === 'dev' && err.message.includes('Already create deployment ticket')) {
|
|
1015
|
+
log.push(' Deployment ticket 已存在,繼續 DEV approval 續跑流程');
|
|
1016
|
+
} else {
|
|
1017
|
+
throw err;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
875
1020
|
|
|
876
1021
|
const issue = await jira.getIssue(issueKey);
|
|
877
1022
|
const newStatus = issue.fields.status.name;
|
|
878
1023
|
log.push(`✅ 部署已觸发,目前狀態:${newStatus}`);
|
|
879
1024
|
|
|
1025
|
+
await runDevSelfServiceTransitions();
|
|
1026
|
+
|
|
1027
|
+
const finalIssue = await jira.getIssue(issueKey);
|
|
1028
|
+
const finalStatus = finalIssue.fields.status.name;
|
|
1029
|
+
if (finalStatus !== newStatus) {
|
|
1030
|
+
log.push(`✅ DEV 自助流程完成,目前狀態:${finalStatus}`);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
880
1033
|
await notifier.notify(
|
|
881
1034
|
issueKey,
|
|
882
|
-
`CD 部署已觸發(環境: ${envCode.toUpperCase()},狀態: ${
|
|
1035
|
+
`CD 部署已觸發(環境: ${envCode.toUpperCase()},狀態: ${finalStatus})`,
|
|
883
1036
|
);
|
|
884
1037
|
|
|
885
1038
|
return ok({
|
|
886
1039
|
issueKey,
|
|
887
1040
|
environment: envCode,
|
|
888
|
-
status:
|
|
1041
|
+
status: finalStatus,
|
|
889
1042
|
steps: log,
|
|
890
1043
|
});
|
|
891
1044
|
} catch (err) {
|
package/tools/library.js
CHANGED
|
@@ -210,7 +210,7 @@ export async function handleCreateLibraryTicket(args, {jira, notifier}) {
|
|
|
210
210
|
const issue = await jira.createIssue(fields);
|
|
211
211
|
await notifier.notify(
|
|
212
212
|
issue.key,
|
|
213
|
-
`Library Release 單已建立。系統: ${
|
|
213
|
+
`Library Release 單已建立。系統: ${normalizedArgs.systemCode}, 模組: ${normalizedArgs.module}, 環境: ${envCode}`,
|
|
214
214
|
);
|
|
215
215
|
return ok({
|
|
216
216
|
issueKey: issue.key,
|
|
@@ -218,6 +218,7 @@ export async function handleCreateLibraryTicket(args, {jira, notifier}) {
|
|
|
218
218
|
url: `${process.env.JIRA_BASE_URL}/browse/${issue.key}`,
|
|
219
219
|
type: 'Library Release',
|
|
220
220
|
system: normalizedArgs.systemCode,
|
|
221
|
+
module: normalizedArgs.module,
|
|
221
222
|
});
|
|
222
223
|
} catch (err) {
|
|
223
224
|
return error(`無法建立 Library 單: ${err.message}`);
|