@jira-deploy/core 1.0.16 → 1.0.17
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 +21 -4
- package/constants/issue-types.js +2 -2
- package/jira-client.js +15 -11
- package/notifier.js +3 -2
- package/package.json +2 -1
- package/platform-config.js +2 -1
- package/poller.js +4 -2
- package/tools/cd.js +6 -5
- package/tools/ci.js +2 -1
- package/tools/grayrelease.js +10 -5
- package/tools/helpers.js +4 -3
- package/tools/jabber.js +13 -5
- package/tools/library.js +2 -1
- package/tools/release.js +6 -5
package/constants/config.js
CHANGED
|
@@ -136,6 +136,7 @@ const DEFAULT_DEPLOY_CONFIG = {
|
|
|
136
136
|
};
|
|
137
137
|
|
|
138
138
|
let cachedConfig;
|
|
139
|
+
let runtimeConfigEnv = EMPTY_OBJECT;
|
|
139
140
|
|
|
140
141
|
function parseJsonConfig(value, source) {
|
|
141
142
|
try {
|
|
@@ -169,16 +170,32 @@ function expandHomePath(filePath) {
|
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
function loadExternalConfig() {
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
const deployConfigPath = getRuntimeConfigValue('JIRA_DEPLOY_CONFIG_PATH');
|
|
174
|
+
if (deployConfigPath) {
|
|
175
|
+
const configPath = expandHomePath(deployConfigPath);
|
|
174
176
|
return parseJsonConfig(
|
|
175
177
|
readFileSync(configPath, 'utf8'),
|
|
176
|
-
`JIRA_DEPLOY_CONFIG_PATH (${
|
|
178
|
+
`JIRA_DEPLOY_CONFIG_PATH (${deployConfigPath})`,
|
|
177
179
|
);
|
|
178
180
|
}
|
|
179
181
|
return EMPTY_OBJECT;
|
|
180
182
|
}
|
|
181
183
|
|
|
184
|
+
export function configureRuntimeConfig({env = EMPTY_OBJECT} = {}) {
|
|
185
|
+
runtimeConfigEnv = Object.freeze({...env});
|
|
186
|
+
cachedConfig = undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function getRuntimeConfigValue(key) {
|
|
190
|
+
return runtimeConfigEnv[key] ?? process.env[key];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function getRuntimeConfigNumber(key, fallback) {
|
|
194
|
+
const value = getRuntimeConfigValue(key);
|
|
195
|
+
const parsed = Number.parseInt(value ?? '', 10);
|
|
196
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
197
|
+
}
|
|
198
|
+
|
|
182
199
|
export function getDeployConfig() {
|
|
183
200
|
if (!cachedConfig) {
|
|
184
201
|
cachedConfig = deepMerge(DEFAULT_DEPLOY_CONFIG, loadExternalConfig());
|
|
@@ -187,7 +204,7 @@ export function getDeployConfig() {
|
|
|
187
204
|
}
|
|
188
205
|
|
|
189
206
|
export function getReleaseProjectKey() {
|
|
190
|
-
return
|
|
207
|
+
return getRuntimeConfigValue('JIRA_RELEASE_PROJECT_KEY') ?? getDeployConfig().jira.releaseProjectKey;
|
|
191
208
|
}
|
|
192
209
|
|
|
193
210
|
export function resetDeployConfigForTests() {
|
package/constants/issue-types.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {getDeployConfig} from './config.js';
|
|
1
|
+
import {getDeployConfig, getRuntimeConfigValue} from './config.js';
|
|
2
2
|
|
|
3
3
|
const config = getDeployConfig();
|
|
4
4
|
|
|
@@ -8,4 +8,4 @@ export const ISSUE_TYPE_IDS = config.issueTypes.ids;
|
|
|
8
8
|
|
|
9
9
|
export const JIRA_PROJECT_ID_NUMERIC = config.jira.projectIdNumeric;
|
|
10
10
|
|
|
11
|
-
export const JIRA_PROJECT_ID =
|
|
11
|
+
export const JIRA_PROJECT_ID = getRuntimeConfigValue('JIRA_PROJECT_KEY') ?? config.jira.projectKey;
|
package/jira-client.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import https from 'https';
|
|
3
|
+
import {getRuntimeConfigValue} from './constants/config.js';
|
|
3
4
|
|
|
4
5
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
function isDryRun() {
|
|
8
|
+
return getRuntimeConfigValue('DRY_RUN') === 'true';
|
|
9
|
+
}
|
|
7
10
|
|
|
8
11
|
export class JiraClient {
|
|
9
12
|
constructor() {
|
|
10
|
-
const
|
|
13
|
+
const JIRA_BASE_URL = getRuntimeConfigValue('JIRA_BASE_URL');
|
|
14
|
+
const JIRA_API_TOKEN = getRuntimeConfigValue('JIRA_API_TOKEN');
|
|
11
15
|
if (!JIRA_BASE_URL || !JIRA_API_TOKEN) {
|
|
12
16
|
throw new Error('Missing required Jira env vars: JIRA_BASE_URL, JIRA_API_TOKEN');
|
|
13
17
|
}
|
|
@@ -20,7 +24,7 @@ export class JiraClient {
|
|
|
20
24
|
Authorization: `Bearer ${JIRA_API_TOKEN}`,
|
|
21
25
|
},
|
|
22
26
|
});
|
|
23
|
-
this.dryRun =
|
|
27
|
+
this.dryRun = isDryRun();
|
|
24
28
|
this.fieldIdByName = new Map();
|
|
25
29
|
}
|
|
26
30
|
|
|
@@ -255,7 +259,7 @@ export class JiraClient {
|
|
|
255
259
|
|
|
256
260
|
// 取得 Bitbucket repo 的原始檔案內容(Bitbucket REST API 1.0)
|
|
257
261
|
async getBitbucketFileContent(project, repo, filePath, branch) {
|
|
258
|
-
const BB_BASE =
|
|
262
|
+
const BB_BASE = getRuntimeConfigValue('BITBUCKET_URL') ?? getRuntimeConfigValue('BITBUCKET_BASE_URL');
|
|
259
263
|
if (!BB_BASE) throw new Error('Missing required Bitbucket env var: BITBUCKET_URL');
|
|
260
264
|
const url = `${BB_BASE}/rest/api/1.0/projects/${project}/repos/${repo}/raw/${filePath}`;
|
|
261
265
|
const res = await axios
|
|
@@ -263,7 +267,7 @@ export class JiraClient {
|
|
|
263
267
|
httpsAgent,
|
|
264
268
|
headers: {
|
|
265
269
|
Accept: 'text/plain',
|
|
266
|
-
Authorization: `Bearer ${
|
|
270
|
+
Authorization: `Bearer ${getRuntimeConfigValue('BITBUCKET_API_TOKEN')}`,
|
|
267
271
|
},
|
|
268
272
|
params: { at: branch },
|
|
269
273
|
responseType: 'text',
|
|
@@ -281,7 +285,7 @@ export class JiraClient {
|
|
|
281
285
|
repo,
|
|
282
286
|
{ filterValue = '', orderBy = 'MODIFICATION', limit = 1 } = {},
|
|
283
287
|
) {
|
|
284
|
-
const BB_BASE =
|
|
288
|
+
const BB_BASE = getRuntimeConfigValue('BITBUCKET_URL') ?? getRuntimeConfigValue('BITBUCKET_BASE_URL');
|
|
285
289
|
if (!BB_BASE) throw new Error('Missing required Bitbucket env var: BITBUCKET_URL');
|
|
286
290
|
const url = `${BB_BASE}/rest/api/1.0/projects/${project}/repos/${repo}/tags`;
|
|
287
291
|
const res = await axios
|
|
@@ -290,7 +294,7 @@ export class JiraClient {
|
|
|
290
294
|
headers: {
|
|
291
295
|
'Content-Type': 'application/json',
|
|
292
296
|
Accept: 'application/json',
|
|
293
|
-
Authorization: `Bearer ${
|
|
297
|
+
Authorization: `Bearer ${getRuntimeConfigValue('BITBUCKET_API_TOKEN')}`,
|
|
294
298
|
},
|
|
295
299
|
params: { filterValue, orderBy, limit },
|
|
296
300
|
})
|
|
@@ -307,7 +311,7 @@ export class JiraClient {
|
|
|
307
311
|
repo,
|
|
308
312
|
{ filterValue = '', orderBy = 'MODIFICATION', limit = 1 } = {},
|
|
309
313
|
) {
|
|
310
|
-
const BB_BASE =
|
|
314
|
+
const BB_BASE = getRuntimeConfigValue('BITBUCKET_URL') ?? getRuntimeConfigValue('BITBUCKET_BASE_URL');
|
|
311
315
|
if (!BB_BASE) throw new Error('Missing required Bitbucket env var: BITBUCKET_URL');
|
|
312
316
|
const url = `${BB_BASE}/rest/api/1.0/projects/${project}/repos/${repo}/branches`;
|
|
313
317
|
const res = await axios
|
|
@@ -316,7 +320,7 @@ export class JiraClient {
|
|
|
316
320
|
headers: {
|
|
317
321
|
'Content-Type': 'application/json',
|
|
318
322
|
Accept: 'application/json',
|
|
319
|
-
Authorization: `Bearer ${
|
|
323
|
+
Authorization: `Bearer ${getRuntimeConfigValue('BITBUCKET_API_TOKEN')}`,
|
|
320
324
|
},
|
|
321
325
|
params: { filterValue, orderBy, limit },
|
|
322
326
|
})
|
|
@@ -333,7 +337,7 @@ export class JiraClient {
|
|
|
333
337
|
repo,
|
|
334
338
|
{ branch = '', state = 'OPEN', limit = 100 } = {},
|
|
335
339
|
) {
|
|
336
|
-
const BB_BASE =
|
|
340
|
+
const BB_BASE = getRuntimeConfigValue('BITBUCKET_URL') ?? getRuntimeConfigValue('BITBUCKET_BASE_URL');
|
|
337
341
|
if (!BB_BASE) throw new Error('Missing required Bitbucket env var: BITBUCKET_URL');
|
|
338
342
|
const url = `${BB_BASE}/rest/api/1.0/projects/${project}/repos/${repo}/pull-requests`;
|
|
339
343
|
const res = await axios
|
|
@@ -342,7 +346,7 @@ export class JiraClient {
|
|
|
342
346
|
headers: {
|
|
343
347
|
'Content-Type': 'application/json',
|
|
344
348
|
Accept: 'application/json',
|
|
345
|
-
Authorization: `Bearer ${
|
|
349
|
+
Authorization: `Bearer ${getRuntimeConfigValue('BITBUCKET_API_TOKEN')}`,
|
|
346
350
|
},
|
|
347
351
|
params: {
|
|
348
352
|
state,
|
package/notifier.js
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* 擴充 Slack:填入 .env 的 SLACK_BOT_TOKEN + SLACK_CHANNEL_ID 後,
|
|
6
6
|
* 把下方 notifySlack 的 TODO 實作即可,其他地方不用動。
|
|
7
7
|
*/
|
|
8
|
+
import {getRuntimeConfigValue} from './constants/config.js';
|
|
8
9
|
|
|
9
10
|
export class Notifier {
|
|
10
11
|
constructor(jiraClient) {
|
|
11
12
|
this.jira = jiraClient;
|
|
12
|
-
this.slackEnabled = !!
|
|
13
|
-
this.dryRun =
|
|
13
|
+
this.slackEnabled = !!getRuntimeConfigValue('SLACK_BOT_TOKEN') && !!getRuntimeConfigValue('SLACK_CHANNEL_ID');
|
|
14
|
+
this.dryRun = getRuntimeConfigValue('DRY_RUN') === 'true';
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
async notify(issueKey, message) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jira-deploy/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"repository": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"exports": {
|
|
12
12
|
".": "./index.js",
|
|
13
13
|
"./constants": "./constants/index.js",
|
|
14
|
+
"./runtime-config": "./constants/config.js",
|
|
14
15
|
"./jira-client": "./jira-client.js",
|
|
15
16
|
"./notifier": "./notifier.js",
|
|
16
17
|
"./poller": "./poller.js",
|
package/platform-config.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {SYSTEM_CODES, GRAY_RELEASE_MODULE_IDS} from './constants/index.js';
|
|
9
|
+
import {getRuntimeConfigValue} from './constants/config.js';
|
|
9
10
|
import {getServerList} from './tools/helpers.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -27,7 +28,7 @@ export function getPlatformConfig(platformName, environment = 'stg') {
|
|
|
27
28
|
if (!systemCode) {
|
|
28
29
|
console.warn(`Unknown platform: ${platformName}, using defaults`);
|
|
29
30
|
return {
|
|
30
|
-
projectKey:
|
|
31
|
+
projectKey: getRuntimeConfigValue('JIRA_PROJECT_KEY') ?? 'OPS',
|
|
31
32
|
systemCode: undefined,
|
|
32
33
|
clusters: [],
|
|
33
34
|
environment,
|
package/poller.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {getRuntimeConfigNumber} from './constants/config.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Poller — 輪詢等待 Jira issue 達到目標狀態
|
|
3
5
|
*
|
|
@@ -19,8 +21,8 @@ export class Poller {
|
|
|
19
21
|
* @returns {{ issueKey, status, elapsedMs, attempts }}
|
|
20
22
|
*/
|
|
21
23
|
async waitForStatus(issueKey, targetStatus, options = {}) {
|
|
22
|
-
const intervalMs = options.intervalMs ??
|
|
23
|
-
const timeoutMs = options.timeoutMs ??
|
|
24
|
+
const intervalMs = options.intervalMs ?? getRuntimeConfigNumber('POLL_INTERVAL_MS', 30000);
|
|
25
|
+
const timeoutMs = options.timeoutMs ?? getRuntimeConfigNumber('POLL_TIMEOUT_MS', 3600000);
|
|
24
26
|
const progress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
|
|
25
27
|
|
|
26
28
|
const startTime = Date.now();
|
package/tools/cd.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
SYSTEM_MODULES,
|
|
26
26
|
SYSTEM_TO_DEPT_MAP,
|
|
27
27
|
} from '../constants/index.js';
|
|
28
|
+
import {getRuntimeConfigValue} from '../constants/config.js';
|
|
28
29
|
|
|
29
30
|
// ── Flow Definition ──────────────────────────────────────────────
|
|
30
31
|
|
|
@@ -291,7 +292,7 @@ export async function handleCreateCDTicket(args, {jira, notifier, progress: repo
|
|
|
291
292
|
return ok({
|
|
292
293
|
issueKey: issue.key,
|
|
293
294
|
issueId: issue.id,
|
|
294
|
-
url:
|
|
295
|
+
url: getIssueUrl(issue.key),
|
|
295
296
|
type: 'CD Deploy',
|
|
296
297
|
system: normalizedArgs.systemCode,
|
|
297
298
|
environment: normalizedArgs.environment,
|
|
@@ -615,12 +616,12 @@ function resolveRequiredAccountId(alias, label) {
|
|
|
615
616
|
}
|
|
616
617
|
|
|
617
618
|
function getJabberJid(accountId) {
|
|
618
|
-
const domain =
|
|
619
|
+
const domain = getRuntimeConfigValue('JABBER_DOMAIN') ?? getDeployConfig().jabber?.domain;
|
|
619
620
|
return domain ? `${accountId}@${domain}` : accountId;
|
|
620
621
|
}
|
|
621
622
|
|
|
622
623
|
function getIssueUrl(issueKey) {
|
|
623
|
-
return `${
|
|
624
|
+
return `${getRuntimeConfigValue('JIRA_BASE_URL')}/browse/${issueKey}`;
|
|
624
625
|
}
|
|
625
626
|
|
|
626
627
|
function isPrdLikeEnv(env) {
|
|
@@ -971,7 +972,7 @@ async function generateReleaseNotes(jira, ciKey, env, systemCode) {
|
|
|
971
972
|
targetBranch = await findLatestReleaseBranchWithTag(jira, repoMeta, repoName);
|
|
972
973
|
if (!targetBranch) continue;
|
|
973
974
|
}
|
|
974
|
-
const baseUrl =
|
|
975
|
+
const baseUrl = getRuntimeConfigValue('BITBUCKET_URL') || 'https://bitbucket.example.com';
|
|
975
976
|
const compareUrl = `${baseUrl}/projects/${repoMeta.project}/repos/${repoName}/compare/diff?sourceBranch=${sourceBranch.replace(/\//g, '%2F')}&targetBranch=${targetBranch.replace(/\//g, '%2F')}&targetRepoId=${repoMeta.repoId}`;
|
|
976
977
|
notes.push(`${label}: ${compareUrl}`);
|
|
977
978
|
} catch (_) {
|
|
@@ -1056,7 +1057,7 @@ async function handleWeblink(jira, issue, notifier, args) {
|
|
|
1056
1057
|
});
|
|
1057
1058
|
|
|
1058
1059
|
if (versionId) {
|
|
1059
|
-
const versionUrl = `${
|
|
1060
|
+
const versionUrl = `${getRuntimeConfigValue('JIRA_BASE_URL')}/projects/LBPRJ/versions/${versionId}`;
|
|
1060
1061
|
await jira.addRemoteLink(issue.key, versionUrl, moduleName);
|
|
1061
1062
|
await notifier.notify(issue.key, `已附上版本 Web Link:${moduleName}(${libKey})`);
|
|
1062
1063
|
} else {
|
package/tools/ci.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
SYSTEM_TO_CI_REPO_MAP,
|
|
23
23
|
SYSTEM_TO_DEPT_MAP,
|
|
24
24
|
} from '../constants/index.js';
|
|
25
|
+
import {getRuntimeConfigValue} from '../constants/config.js';
|
|
25
26
|
import { assertNoOpenPRBeforeCreate } from './branch-prs.js';
|
|
26
27
|
|
|
27
28
|
// ── Flow Definition ──────────────────────────────────────────────
|
|
@@ -481,7 +482,7 @@ export async function handleCreateCITicket(args, { jira, notifier }) {
|
|
|
481
482
|
return ok({
|
|
482
483
|
issueKey: issue.key,
|
|
483
484
|
issueId: issue.id,
|
|
484
|
-
url: `${
|
|
485
|
+
url: `${getRuntimeConfigValue('JIRA_BASE_URL')}/browse/${issue.key}`,
|
|
485
486
|
type: 'CI Release',
|
|
486
487
|
system: systemCode,
|
|
487
488
|
goldenImageVersion,
|
package/tools/grayrelease.js
CHANGED
|
@@ -40,6 +40,7 @@ import { assertNoOpenPRBeforeCreate } from './branch-prs.js';
|
|
|
40
40
|
import { Poller } from '../poller.js';
|
|
41
41
|
import { handleGetReleaseManager, handleWaitForComment } from './release.js';
|
|
42
42
|
import { handleSendJabberMessage } from './jabber.js';
|
|
43
|
+
import {getRuntimeConfigValue} from '../constants/config.js';
|
|
43
44
|
import {
|
|
44
45
|
needSwitchExecutionNode as shouldSwitchExecutionNode,
|
|
45
46
|
waitForSwitchExecutionNode as waitForSharedSwitchExecutionNode,
|
|
@@ -122,10 +123,14 @@ function progress(ctx, event) {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
function getJabberJid(accountId) {
|
|
125
|
-
const domain =
|
|
126
|
+
const domain = getRuntimeConfigValue('JABBER_DOMAIN') ?? getDeployConfig().jabber?.domain;
|
|
126
127
|
return domain ? `${accountId}@${domain}` : accountId;
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
function getJiraIssueUrl(issueKey) {
|
|
131
|
+
return `${getRuntimeConfigValue('JIRA_BASE_URL')}/browse/${issueKey}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
129
134
|
function getGrayReleaseUatApprovers() {
|
|
130
135
|
const config = getDeployConfig().release.grayReleaseUatApprovers ?? {};
|
|
131
136
|
return {
|
|
@@ -527,7 +532,7 @@ export async function handleCreateGrayReleaseTicket(args, { jira, notifier }) {
|
|
|
527
532
|
const result = {
|
|
528
533
|
issueKey: issue.key,
|
|
529
534
|
issueId: issue.id,
|
|
530
|
-
url:
|
|
535
|
+
url: getJiraIssueUrl(issue.key),
|
|
531
536
|
type: 'GrayRelease',
|
|
532
537
|
system: normalizedArgs.systemCode,
|
|
533
538
|
module: normalizedArgs.module,
|
|
@@ -1311,7 +1316,7 @@ async function handleGrayReleaseApproval(issueKey, environment, systemCode, ctx)
|
|
|
1311
1316
|
await handleSendJabberMessage(
|
|
1312
1317
|
{
|
|
1313
1318
|
to: jabberTo,
|
|
1314
|
-
message: `[GrayRelease 簽核通知] ${issueKey} 需要您的簽核。環境: STG,系統: ${systemCode}\n${
|
|
1319
|
+
message: `[GrayRelease 簽核通知] ${issueKey} 需要您的簽核。環境: STG,系統: ${systemCode}\n${getJiraIssueUrl(issueKey)}`,
|
|
1315
1320
|
},
|
|
1316
1321
|
{},
|
|
1317
1322
|
);
|
|
@@ -1354,7 +1359,7 @@ async function handleGrayReleaseApproval(issueKey, environment, systemCode, ctx)
|
|
|
1354
1359
|
await handleSendJabberMessage(
|
|
1355
1360
|
{
|
|
1356
1361
|
to: commentReviewerJabber,
|
|
1357
|
-
message: `[GrayRelease 簽核通知] ${issueKey} 需要您的簽核並留言確認。環境: UAT,系統: ${systemCode}\n${
|
|
1362
|
+
message: `[GrayRelease 簽核通知] ${issueKey} 需要您的簽核並留言確認。環境: UAT,系統: ${systemCode}\n${getJiraIssueUrl(issueKey)}`,
|
|
1358
1363
|
},
|
|
1359
1364
|
{},
|
|
1360
1365
|
);
|
|
@@ -1401,7 +1406,7 @@ async function handleGrayReleaseApproval(issueKey, environment, systemCode, ctx)
|
|
|
1401
1406
|
await handleSendJabberMessage(
|
|
1402
1407
|
{
|
|
1403
1408
|
to: finalApproverJabber,
|
|
1404
|
-
message: `[GrayRelease 簽核通知] ${issueKey} 已由 ${commentReviewerAlias} 確認,需要您的最終簽核。環境: UAT,系統: ${systemCode}\n${
|
|
1409
|
+
message: `[GrayRelease 簽核通知] ${issueKey} 已由 ${commentReviewerAlias} 確認,需要您的最終簽核。環境: UAT,系統: ${systemCode}\n${getJiraIssueUrl(issueKey)}`,
|
|
1405
1410
|
},
|
|
1406
1411
|
{},
|
|
1407
1412
|
);
|
package/tools/helpers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {getDeployConfig, META_TEST_NODES, SERVER_MODULE_MAP, SERVERS} from '../constants/index.js';
|
|
2
|
+
import {getRuntimeConfigNumber} from '../constants/config.js';
|
|
2
3
|
|
|
3
4
|
export function ok(data) {
|
|
4
5
|
return {content: [{type: 'text', text: JSON.stringify(data, null, 2)}]};
|
|
@@ -66,11 +67,11 @@ export function getModuleName(systemCode, module, versionName) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export function getPollIntervalMs() {
|
|
69
|
-
return
|
|
70
|
+
return getRuntimeConfigNumber('POLL_INTERVAL_MS', 30000);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
export function getPollTimeoutMs() {
|
|
73
|
-
return
|
|
74
|
+
return getRuntimeConfigNumber('POLL_TIMEOUT_MS', 3600000);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
export function isPassingResult(value) {
|
|
@@ -83,4 +84,4 @@ export function isFailingResult(value) {
|
|
|
83
84
|
return ['fail', 'failed', 'failure', 'error'].includes(
|
|
84
85
|
String(value ?? '').trim().toLowerCase(),
|
|
85
86
|
);
|
|
86
|
-
}
|
|
87
|
+
}
|
package/tools/jabber.js
CHANGED
|
@@ -7,6 +7,7 @@ import fs from 'fs';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import {fileURLToPath} from 'url';
|
|
9
9
|
import {error, ok} from './helpers.js';
|
|
10
|
+
import {getRuntimeConfigValue} from '../constants/config.js';
|
|
10
11
|
|
|
11
12
|
const packageScriptPath = path.resolve(
|
|
12
13
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
@@ -32,8 +33,9 @@ export function resolveJabberNotifyScriptPath({
|
|
|
32
33
|
execPath = process.execPath,
|
|
33
34
|
cwd = process.cwd(),
|
|
34
35
|
} = {}) {
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
const configuredScript = getRuntimeConfigValue('JABBER_NOTIFY_SCRIPT') ?? env.JABBER_NOTIFY_SCRIPT;
|
|
37
|
+
if (configuredScript) {
|
|
38
|
+
return configuredScript;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
const resolvedExecPath = realpathOrNull(execPath);
|
|
@@ -91,14 +93,20 @@ export async function handleSendJabberMessage(args, _ctx) {
|
|
|
91
93
|
sent: false,
|
|
92
94
|
dryRun: true,
|
|
93
95
|
message: args.message,
|
|
94
|
-
to: args.to ??
|
|
95
|
-
room: args.room ??
|
|
96
|
-
preview: `[DRY RUN] 將發送 Jabber 給 ${args.to ??
|
|
96
|
+
to: args.to ?? getRuntimeConfigValue('JABBER_TO') ?? '(JABBER_TO not set)',
|
|
97
|
+
room: args.room ?? getRuntimeConfigValue('JABBER_ROOM'),
|
|
98
|
+
preview: `[DRY RUN] 將發送 Jabber 給 ${args.to ?? getRuntimeConfigValue('JABBER_TO') ?? '?'}: ${args.message}`,
|
|
97
99
|
});
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
// 允許透過 tool 參數覆蓋 JABBER_TO / JABBER_ROOM
|
|
101
103
|
const env = {...process.env};
|
|
104
|
+
for (const key of ['JABBER_SERVER', 'JABBER_USER', 'JABBER_DOMAIN', 'JABBER_KEYCHAIN_SERVICE', 'JABBER_KEYCHAIN_ACCOUNT', 'JABBER_TO', 'JABBER_ROOM', 'JABBER_PORT', 'JABBER_RESOURCE', 'JABBER_NICK']) {
|
|
105
|
+
const value = getRuntimeConfigValue(key);
|
|
106
|
+
if (value !== undefined) {
|
|
107
|
+
env[key] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
102
110
|
if (args.to) env.JABBER_TO = args.to;
|
|
103
111
|
if (args.room) env.JABBER_ROOM = args.room;
|
|
104
112
|
|
package/tools/library.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
SYSTEM_CODES,
|
|
20
20
|
SYSTEM_TO_DEPT_MAP,
|
|
21
21
|
} from '../constants/index.js';
|
|
22
|
+
import {getRuntimeConfigValue} from '../constants/config.js';
|
|
22
23
|
import { error, getPollIntervalMs, getPollTimeoutMs, ok, today } from './helpers.js';
|
|
23
24
|
import { assertNoOpenPRBeforeCreate } from './branch-prs.js';
|
|
24
25
|
import {
|
|
@@ -267,7 +268,7 @@ export async function handleCreateLibraryTicket(args, { jira, notifier }) {
|
|
|
267
268
|
return ok({
|
|
268
269
|
issueKey: issue.key,
|
|
269
270
|
issueId: issue.id,
|
|
270
|
-
url: `${
|
|
271
|
+
url: `${getRuntimeConfigValue('JIRA_BASE_URL')}/browse/${issue.key}`,
|
|
271
272
|
type: 'Library Release',
|
|
272
273
|
system: normalizedArgs.systemCode,
|
|
273
274
|
module: normalizedArgs.module,
|
package/tools/release.js
CHANGED
|
@@ -9,6 +9,7 @@ import https from 'https';
|
|
|
9
9
|
import http from 'http';
|
|
10
10
|
import {error, getModuleName, ok} from './helpers.js';
|
|
11
11
|
import {getDeployConfig, getReleaseProjectKey, SYSTEM_CODES, SYSTEM_MODULES} from '../constants/index.js';
|
|
12
|
+
import {getRuntimeConfigNumber, getRuntimeConfigValue} from '../constants/config.js';
|
|
12
13
|
|
|
13
14
|
// ── Schema definitions ───────────────────────────────────────────
|
|
14
15
|
export function getReleaseToolDefinitions() {
|
|
@@ -177,13 +178,13 @@ export async function handleGetReleaseManager(args = {}) {
|
|
|
177
178
|
});
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
const CONF_BASE_URL =
|
|
181
|
-
const CONF_TOKEN =
|
|
181
|
+
const CONF_BASE_URL = getRuntimeConfigValue('CONF_BASE_URL');
|
|
182
|
+
const CONF_TOKEN = getRuntimeConfigValue('CONF_TOKEN');
|
|
182
183
|
if (!CONF_BASE_URL || !CONF_TOKEN) {
|
|
183
184
|
return error('缺少環境變數 CONF_BASE_URL 或 CONF_TOKEN');
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
const SUB_CALENDAR_ID =
|
|
187
|
+
const SUB_CALENDAR_ID = getRuntimeConfigValue('CONF_RELEASE_MANAGER_SUB_CALENDAR_ID')
|
|
187
188
|
?? releaseConfig.managerSubCalendarId;
|
|
188
189
|
if (!SUB_CALENDAR_ID) {
|
|
189
190
|
return error('缺少環境變數 CONF_RELEASE_MANAGER_SUB_CALENDAR_ID 或 release.managerSubCalendarId config');
|
|
@@ -265,8 +266,8 @@ export async function handleWaitForComment(args, {
|
|
|
265
266
|
});
|
|
266
267
|
}
|
|
267
268
|
|
|
268
|
-
const intervalMs = args.intervalMs ??
|
|
269
|
-
const timeoutMs = args.timeoutMs ??
|
|
269
|
+
const intervalMs = args.intervalMs ?? getRuntimeConfigNumber('POLL_INTERVAL_MS', 30000);
|
|
270
|
+
const timeoutMs = args.timeoutMs ?? getRuntimeConfigNumber('POLL_TIMEOUT_MS', 3600000);
|
|
270
271
|
const startTime = Date.now();
|
|
271
272
|
let attempts = 0;
|
|
272
273
|
|