@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/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}`);
package/tools/release.js CHANGED
@@ -7,8 +7,8 @@
7
7
  */
8
8
  import https from 'https';
9
9
  import http from 'http';
10
- import {error, getModuleName, ok, today} from './helpers.js';
11
- import {SYSTEM_CODES, SYSTEM_MODULES} from '../constants/index.js';
10
+ import { error, getModuleName, ok } from './helpers.js';
11
+ import { SYSTEM_CODES, SYSTEM_MODULES } from '../constants/index.js';
12
12
 
13
13
  // ── Schema definitions ───────────────────────────────────────────
14
14
  export function getReleaseToolDefinitions() {
@@ -100,7 +100,7 @@ export function getReleaseToolDefinitions() {
100
100
 
101
101
  // ── Handlers ─────────────────────────────────────────────────────
102
102
 
103
- export async function handleGetUnreleasedVersions(args, {jira}) {
103
+ export async function handleGetUnreleasedVersions(args, { jira }) {
104
104
  try {
105
105
  const projectKey = args.projectKey ?? 'LBPRJ';
106
106
  const versions = await jira.getUnreleasedVersionsList(projectKey);
@@ -109,7 +109,7 @@ export async function handleGetUnreleasedVersions(args, {jira}) {
109
109
  return ok({
110
110
  projectKey,
111
111
  total: versions.length,
112
- versions: versions.map((v) => ({id: v.id, name: v.name})),
112
+ versions: versions.map((v) => ({ id: v.id, name: v.name })),
113
113
  });
114
114
  }
115
115
 
@@ -135,13 +135,13 @@ export async function handleGetUnreleasedVersions(args, {jira}) {
135
135
  };
136
136
  });
137
137
 
138
- return ok({projectKey, systemCode: args.systemCode, total: parsed.length, versions: parsed});
138
+ return ok({ projectKey, systemCode: args.systemCode, total: parsed.length, versions: parsed });
139
139
  } catch (err) {
140
140
  return error(`無法查詢 unreleased versions: ${err.message}`);
141
141
  }
142
142
  }
143
143
 
144
- export async function handleTransitionToWaitApproval(args, {jira, notifier}) {
144
+ export async function handleTransitionToWaitApproval(args, { jira, notifier }) {
145
145
  try {
146
146
  await jira.transitionByName(args.cdIssueKey, 'Apply for approval');
147
147
  await notifier.notify(args.cdIssueKey, '已切換到 Wait Approval,等待主管簽核');
@@ -157,7 +157,8 @@ export async function handleTransitionToWaitApproval(args, {jira, notifier}) {
157
157
 
158
158
  export async function handleGetReleaseManager(args, _ctx) {
159
159
  try {
160
- const date = args.date ?? today();
160
+
161
+ const date = new Date().toISOString();
161
162
 
162
163
  // dryRun 模式:回傳 mock 資料
163
164
  if (args.dryRun) {
@@ -166,7 +167,7 @@ export async function handleGetReleaseManager(args, _ctx) {
166
167
  found: true,
167
168
  name: 'Alvin Wang',
168
169
  dryRun: true,
169
- event: {what: 'Sign off staff', who: 'Alvin Wang (BK00236)'},
170
+ event: { what: 'Sign off staff', who: 'Alvin Wang (BK00236)' },
170
171
  });
171
172
  }
172
173
 
@@ -177,42 +178,55 @@ export async function handleGetReleaseManager(args, _ctx) {
177
178
  }
178
179
 
179
180
  // Release Manager 值班表的 sub-calendar ID(來自 wiki 頁 109861360)
180
- const SUB_CALENDAR_ID = 'afcd2271-8dfa-410e-a922-454f1eec03c0';
181
+ const SUB_CALENDAR_ID = '7e83502d-7994-4d90-87c2-9268e5d7d565';
181
182
 
182
183
  const nextDay = new Date(date);
183
184
  nextDay.setDate(nextDay.getDate() + 1);
184
- const endDate = nextDay.toISOString().slice(0, 10);
185
+ const endDate = nextDay.toISOString();
185
186
 
186
187
  const path =
187
188
  `/rest/calendar-services/1.0/calendar/events.json` +
188
189
  `?subCalendarId=${encodeURIComponent(SUB_CALENDAR_ID)}` +
189
- `&start=${date}&end=${endDate}` +
190
+ `&start=${encodeURIComponent(date)}&end=${encodeURIComponent(endDate)}` +
190
191
  `&userTimeZoneId=Asia%2FTaipei`;
191
192
 
192
193
  const data = await calendarRequest(CONF_BASE_URL, path, CONF_TOKEN);
193
194
  const events = data.events ?? [];
194
195
 
195
- // 找 what 欄位(event title)包含 "Sign off staff" 的事件
196
+ // 找 event title/what 欄位包含 "Sign off staff" 的事件。
196
197
  const signOffEvent = events.find(
197
198
  (e) =>
198
- (e.what ?? '').toLowerCase().includes('sign off staff') ||
199
- (e.title ?? '').toLowerCase().includes('sign off staff'),
199
+ (e.title ?? '').toLowerCase().includes('sign off staff') ||
200
+ (e.what ?? '').toLowerCase().includes('sign off staff'),
200
201
  );
201
202
 
202
203
  if (!signOffEvent) {
203
204
  return ok({
204
205
  date,
205
206
  found: false,
206
- allEvents: events.map((e) => ({what: e.what, who: e.who})),
207
+ allEvents: events.map((e) => ({
208
+ title: e.title,
209
+ what: e.what,
210
+ who: e.who,
211
+ })),
207
212
  message: `${date} 找不到 Sign off staff 事件,請手動確認值班組長`,
208
213
  });
209
214
  }
210
215
 
211
216
  // 回傳值班人員資訊
217
+ const invitee = signOffEvent.invitees?.[0];
218
+ const legacyWho = signOffEvent.who ?? '';
219
+ const legacyName = legacyWho.replace(/\s*\([^)]*\)\s*$/, '').trim();
220
+ const legacyAccountId = legacyWho.match(/\(([^)]+)\)/)?.[1];
221
+ const managerName = (invitee?.displayName ?? legacyName)
222
+ || signOffEvent.title
223
+ || signOffEvent.what
224
+ || 'Unknown';
212
225
  return ok({
213
226
  date,
214
227
  found: true,
215
- name: signOffEvent.who ?? signOffEvent.what ?? '',
228
+ name: managerName,
229
+ authorAccountId: invitee?.name ?? legacyAccountId ?? 'Unknown',
216
230
  event: signOffEvent,
217
231
  });
218
232
  } catch (err) {
@@ -220,7 +234,7 @@ export async function handleGetReleaseManager(args, _ctx) {
220
234
  }
221
235
  }
222
236
 
223
- export async function handleWaitForComment(args, {jira}) {
237
+ export async function handleWaitForComment(args, { jira, progress = () => {} }) {
224
238
  // dryRun 模式:直接回傳 mock 結果
225
239
  if (args.dryRun) {
226
240
  return ok({
@@ -248,6 +262,7 @@ export async function handleWaitForComment(args, {jira}) {
248
262
  while (true) {
249
263
  attempts++;
250
264
  const comments = await jira.getComments(args.issueKey);
265
+ const elapsedMs = Date.now() - startTime;
251
266
 
252
267
  const match = comments.find((c) => {
253
268
  const bodyMatch = (c.body ?? '').toLowerCase().includes(args.keyword.toLowerCase());
@@ -255,6 +270,18 @@ export async function handleWaitForComment(args, {jira}) {
255
270
  return bodyMatch && authorMatch;
256
271
  });
257
272
 
273
+ progress({
274
+ phase: 'polling',
275
+ title: '等待 Jira comment',
276
+ detail: `keyword="${args.keyword}"` +
277
+ (args.authorAccountId ? `, author=${args.authorAccountId}` : ''),
278
+ issueKey: args.issueKey,
279
+ attempts,
280
+ elapsedMs,
281
+ timeoutMs,
282
+ nextPollMs: intervalMs,
283
+ });
284
+
258
285
  if (match) {
259
286
  return ok({
260
287
  found: true,
@@ -267,10 +294,9 @@ export async function handleWaitForComment(args, {jira}) {
267
294
  });
268
295
  }
269
296
 
270
- const elapsed = Date.now() - startTime;
271
- if (elapsed >= timeoutMs) {
297
+ if (elapsedMs >= timeoutMs) {
272
298
  return error(
273
- `Timeout:等待 "${args.keyword}" comment in ${args.issueKey}(${attempts} 次輪詢,${Math.round(elapsed / 1000)}s)`,
299
+ `Timeout:等待 "${args.keyword}" comment in ${args.issueKey}(${attempts} 次輪詢,${Math.round(elapsedMs / 1000)}s)`,
274
300
  );
275
301
  }
276
302
 
@@ -292,7 +318,7 @@ function calendarRequest(baseUrl, path, token) {
292
318
  path: url.pathname + url.search,
293
319
  method: 'GET',
294
320
  rejectUnauthorized: false,
295
- headers: {Authorization: `Bearer ${token}`, Accept: 'application/json'},
321
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
296
322
  },
297
323
  (res) => {
298
324
  let data = '';
@@ -300,6 +326,7 @@ function calendarRequest(baseUrl, path, token) {
300
326
  res.on('end', () => {
301
327
  if (res.statusCode >= 200 && res.statusCode < 300) {
302
328
  try {
329
+
303
330
  resolve(JSON.parse(data));
304
331
  } catch (e) {
305
332
  reject(new Error(`JSON parse error: ${e.message}`));
@@ -80,6 +80,12 @@ function parseToolJson(result) {
80
80
 
81
81
  async function runToolOrThrow(name, args, deps, workflowLog) {
82
82
  workflowLog.push(`- ${name}: ${formatJson(args)}`);
83
+ deps.progress?.({
84
+ phase: 'action',
85
+ title: `執行 workflow step: ${name}`,
86
+ toolName: name,
87
+ issueKey: args.issueKey ?? args.cdIssueKey ?? args.linkedCiKey,
88
+ });
83
89
  const result = await deps.executeToolImpl(name, args, deps);
84
90
  return parseToolJson(result);
85
91
  }
@@ -89,10 +95,24 @@ async function waitForIssueStatus(issueKey, targetStatuses, deps, options = {})
89
95
  const intervalMs = options.intervalMs ?? 5000;
90
96
  const startedAt = Date.now();
91
97
  const wanted = targetStatuses.map((status) => status.toLowerCase());
98
+ let attempts = 0;
92
99
 
93
100
  while (Date.now() - startedAt < timeoutMs) {
101
+ attempts++;
94
102
  const issue = await deps.jira.getIssue(issueKey);
95
103
  const current = issue.fields?.status?.name ?? '';
104
+ deps.progress?.({
105
+ phase: 'polling',
106
+ title: `等待 ${issueKey} workflow 狀態`,
107
+ detail: `target=${targetStatuses.join(' / ')}`,
108
+ issueKey,
109
+ currentStatus: current,
110
+ targetStatus: targetStatuses.join(' / '),
111
+ attempts,
112
+ elapsedMs: Date.now() - startedAt,
113
+ timeoutMs,
114
+ nextPollMs: intervalMs,
115
+ });
96
116
  if (wanted.includes(current.toLowerCase())) {
97
117
  return current;
98
118
  }