@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/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
- // STG 完整流程:
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
- await jira.transitionById(issueKey, deployTrans.id);
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()},狀態: ${newStatus})`,
1035
+ `CD 部署已觸發(環境: ${envCode.toUpperCase()},狀態: ${finalStatus})`,
883
1036
  );
884
1037
 
885
1038
  return ok({
886
1039
  issueKey,
887
1040
  environment: envCode,
888
- status: newStatus,
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 單已建立。系統: ${args.systemCode}, 模組: ${args.module}, 環境: ${envCode}`,
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}`);