@iki-inc/test-registry 1.0.10 → 1.0.18
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/.editorconfig +15 -0
- package/.env.sample +12 -0
- package/.npm/_cacache/content-v2/sha512/09/60/735d0adcfea3a4862770d4ecc14d0b9845b7f2c435c93152755cdc141c644e566b9a7a2126dceb25a59d5157de9ded228a8d92ab489bb7fadff1970dd1f4 +0 -0
- package/.npm/_cacache/content-v2/sha512/0b/a0/8b0190f2ea6340287aaab7e4f6b3444d5dfeed9a34fd3cb0314d909db52d36efa7808f5c66aa63cfcbd44328a3f38466a2522727f6ada90372321033358d +0 -0
- package/.npm/_cacache/content-v2/sha512/14/22/c7b510ff827a428821c48892cec1d9853fec330a60c491cf72ecdb18c5e178bbb06db27d59bb0830246c4898898789c240acb3f8474c97e1cd8a0ab32b4c +0 -0
- package/.npm/_cacache/content-v2/sha512/14/68/07da1f3328d8a6f658e3edd6a79053dc20220af42a796e6f9cda041261e3e1a5a1b9f9eb2b2ce0e2848a2b9fe3dee85189cd6857428b4fbfbde34da95d5c +0 -0
- package/.npm/_cacache/content-v2/sha512/15/8c/64d48ef03efdcad9705aa321f67139e871012ef00399eb484f6eccae90c3cbbf9ff9aabed5d62f0f2c7765e997fcec8fb165716f3c25d88e24c5abe6120d +0 -0
- package/.npm/_cacache/content-v2/sha512/1c/98/3329206d747adfa5b1f7bc78fed6a4b21fd451d0bff33030b0be8e0d136160f4fda791631f8f6033872b44347193b4bea071f48f392d7a1cb2d3438b957a +0 -0
- package/.npm/_cacache/content-v2/sha512/20/9a/2978f18ee879cc59575e2c28a19f15d9d16096d26c57894e4b1376d9d34187112b02680be00fc84dfc04c825e6619eb55d53971f4724b5b95eb7566738ce +0 -0
- package/.npm/_cacache/content-v2/sha512/28/83/7f9c3241411717c3430b561644f62407986ebca80548060f42aa65188e64088608a3f54e4c16faea9142f915bb72cb366e39e3add3375e45ee1463b72df8 +0 -0
- package/.npm/_cacache/content-v2/sha512/2d/fb/25e8313fd91905be948484c12737ab0db98d08791ebc50442adaacd1d0c91417544c0a6385943dcec9d9d3f7263e7e45a307814f486e1203fa0155961315 +0 -0
- package/.npm/_cacache/content-v2/sha512/41/25/fcd4a845a191b07a75723e86deae081d5b59eb4194af7d50c02145ebdd6a8b459199a9693f3f84fca7648ff9d7b64abe473dcbe385c273122ba52a09f1b4 +0 -0
- package/.npm/_cacache/content-v2/sha512/4a/9d/5a6e52748af0e44b38dc68977112e9cde7f5ef92c149dac30115fabac74af285057fd9bfcac057b6d5c329987b4f3928a3f0af7dff049fa04b9339b9ae31 +0 -0
- package/.npm/_cacache/content-v2/sha512/52/61/5720f53ef4483713d9bfef6f018a2d25008ba873eea76eb35429a44f24e169f758a1e38aaa1a55adc4c07a91716b443ea9a3ef3a92c1cce571c27e4ea23f +0 -0
- package/.npm/_cacache/content-v2/sha512/54/28/c235f80cb1bcb7b53768d369db8ed33f7b0adaea33c79a94e17a7913621f291bdb9c67fd4ff12a38bb814605e93f063a4e56c0c23282c0fe2b8128815744 +0 -0
- package/.npm/_cacache/content-v2/sha512/58/f4/bf1ef1d04d89c78ac2e8f4c72a0473899361641cefed969be5772ae77a6e1a790a7885a8b7832b61b3083aa74d684a84e5e7cadca621408c5d9baf6024d8 +0 -0
- package/.npm/_cacache/content-v2/sha512/5b/ae/e22e5e09d845c41936df78709f7eb8c37e2b6f2c0360d14957df01545124f1f762974457a0307515812a84fb0be101b8b85aa8c683d733cac4d5d84a5b7b +0 -0
- package/.npm/_cacache/content-v2/sha512/61/65/938e000148a72fb3f9d6062f4fc9c63f2623c9a8e0f8240ea8f527a3d80b6f4866ab5f1282dce412938a406ab6dd4252808790f814242424d916f86027df +0 -0
- package/.npm/_cacache/content-v2/sha512/65/42/9187afe4505a0089302d4d83d9277870f70371c7e04804e8a39e51bd3e7ac9b027128ecd70cb20fabc9a5a62d827cc3aca6114aa7f738ee917daf77c6c46 +0 -0
- package/.npm/_cacache/content-v2/sha512/65/7f/7d7bab51c1ea145ea47e541aec96175ae75361e4c4d0c28bb9b6750381bb723347418268440ed5863ffc5b2a7ea1a9f3d11ee8d4370cf97f2ff06db867a7 +0 -0
- package/.npm/_cacache/content-v2/sha512/65/fe/47d8ac6ddb18d3bdb26f3f66562c4202c40ea3fa1026333225ca9cb8c5c060d6f2959f1f3d5b2d066d2fa47f9730095145cdd0858765d20853542d2e9cb3 +0 -0
- package/.npm/_cacache/content-v2/sha512/6a/e1/f2278020333eef812f07a7737a1d74a694c754ca1494adf045d7ac35e77af1b4b204384f871aa681a6973366f9c72ea4097e21e8b4262936197495444e15 +0 -0
- package/.npm/_cacache/content-v2/sha512/72/46/b8edf552a3a95f4032004d8a9bfef3b59ef15f6cfc3bb962dac885c88461138f4c1c38e96b964a1b38c0cd40e16bf5039a7de8143ead77576fbabed0fbca +0 -0
- package/.npm/_cacache/content-v2/sha512/7b/79/d17e07d4678acd18bdb7da05205f4e90372c9ecf4e0a76316b17e2d34683979ab3a014a0e0e0109db235bc1274faf5ea9d606991a49c223d560dac2696de +0 -0
- package/.npm/_cacache/content-v2/sha512/82/83/9a72a304d8663239965f6f92e0b6efb520398094012ee719c3dfec53cd2d74853d2fce97f448f8e45f0030f499bee27fe64060cfdcb3bc08f6d844e7817d +0 -0
- package/.npm/_cacache/content-v2/sha512/b1/34/9f063a17069f3d26f20a21e7eac3b53608279bb1cef892263a6b0886a202ada1219b823604fc6ffe97db05dcc5853cd73d21ca0e0b83837ca1dfc459a9d2 +0 -0
- package/.npm/_cacache/content-v2/sha512/b1/e4/b64e3dba4c154e0b6348736ace7b6cb664eede7f1213b4b65c1923a71c734e43b0a489405fc34230d9c93ac642213f02e128d2d2f013be844a6781096acf +0 -0
- package/.npm/_cacache/content-v2/sha512/d2/12/54f5208fbe633320175916a34f5d66ba76a87b59d1f470823dcbe0b24bcac6de72f8f01725adaf4798a8555541f23d6347e58ef10f0001edb7e04a391431 +0 -0
- package/.npm/_cacache/content-v2/sha512/d3/2a/6c390b7d0f87b7873895f0ee1e943f6e374cff8ed5047f1551b4d68002d5c8c94c7d9bf4b394ed36d22f1834bd9a2ff13a6e53afbc741c8a7e87665172ff +0 -0
- package/.npm/_cacache/content-v2/sha512/d5/c0/cd77027625aa2199bdec8383a629a301c2e0b8f2c6278b91d4c360efb02f0b8c64cb2bd87e79bd57e91cae3877b8853d142c25baf22a26863528294aa53d +0 -0
- package/.npm/_cacache/content-v2/sha512/ed/71/cdc47eea5fdc46e66230c6486e993a31fcc21135c3a00ebc56b0cb76a40af6dd61e9e8cad194dec50521690a9afea153b417be38894811f369c931f1b648 +0 -0
- package/.npm/_cacache/content-v2/sha512/f5/18/862af0b06aac4eda8c0feb5b90e0180d6e8ac042c90c47a42eb1f5b342abfb0193a0e6819e85d636a5124c4cabb0c5dd480c13f8ad26c8e651f353b48cb1 +0 -0
- package/.npm/_cacache/content-v2/sha512/f5/f4/a349aa2cfdf448548a7ec5226513a95fc21112ecb36d29a08121a987b23af69dad418800493e8d263a38f3f062435116ab9823c6a9a89583999f8dbf7c09 +0 -0
- package/.npm/_cacache/content-v2/sha512/fb/02/a330d53dc3f11a41ac875dbed603b65a1d8ea137c5a07ef1cd437b753e753dd7fecef137139c88420d84d9c27b7c26c3bd201a99df42e7d6bee93c5e7603 +0 -0
- package/.npm/_cacache/content-v2/sha512/fb/2b/3df7b53dea9a382b1fc0069042aa103d12ec49690583420ef6f791f8841a61bf72198346e804abb0629b78617a7a319e4099942753fb72313951a5a49e8e +0 -0
- package/.npm/_cacache/content-v2/sha512/fc/85/ed6f0124e474cfc84c32297ea11a4617c4cf676e3eb807e8a55499c2fd1e81d291f91b85776f4a556cbec3063e2d921040a696d05257fa17a5e5f4b1eed6 +0 -0
- package/.npm/_cacache/index-v5/00/60/6a2298196c9cd65881a7267db2824adff4d9c24f0ed6d3a2ca4c81d3e235 +2 -0
- package/.npm/_cacache/index-v5/18/55/9c21c8d8605592da1a2d8ad777878425c65933e52599a58e642960dce2e5 +2 -0
- package/.npm/_cacache/index-v5/1d/67/152bf23a6b2f18b828f3bba90e56430367ec7471521e92ab71ec92263d0f +2 -0
- package/.npm/_cacache/index-v5/1d/f8/e0e47cb46e4fa089b0801bd0a1ae48912c4e323215f569ad1c3832070f9f +2 -0
- package/.npm/_cacache/index-v5/20/af/2322ca225285ead2f23e5dbe0416b3ae6e18d91ed2d463d97780850fc40f +2 -0
- package/.npm/_cacache/index-v5/22/50/6e01e0a33342f552257e2351f2cd7d26148bd9d2c73fc3797a4c4ddb0e39 +2 -0
- package/.npm/_cacache/index-v5/24/e3/6a5b57ca4d63f37ccae38ea6b710ed659eeb763c63181c02e9470f8b29b9 +2 -0
- package/.npm/_cacache/index-v5/25/7f/0b973fb10d0d62b5962532a6e5691f81dd1c20b63fcce513967941450a82 +2 -0
- package/.npm/_cacache/index-v5/25/98/f306f68eac58ac50c3a0a0453cb8f29d78b9505c01947c965b4768a4e60d +2 -0
- package/.npm/_cacache/index-v5/32/e2/5912d88421e6b76e5e0365ae8015aac3093d6e53b5b60fdfa91116720417 +2 -0
- package/.npm/_cacache/index-v5/34/cb/dd5e6f02ebea3574006624e5dd81e72b124d66a1bd1598ab078fda7118f7 +2 -0
- package/.npm/_cacache/index-v5/36/e7/0b6493f625a039ca4cb935e7526c5a564658f0781f039bc1e79ea8aa7a81 +2 -0
- package/.npm/_cacache/index-v5/3d/d0/639107e3f13a812eb6cb868ab09827c1e4a913b741a0db28c7b348e62204 +2 -0
- package/.npm/_cacache/index-v5/4d/de/54bca7c04d5de2f23c10f555ac8bcc9c61e9c96e203394bb7afa2cbbc867 +2 -0
- package/.npm/_cacache/index-v5/54/c8/008397b4ada8d972166fe22e555240d3dd695fd6526b397c6e271de4f66e +2 -0
- package/.npm/_cacache/index-v5/56/9a/f71b742ff3706d89fbad8eea6d75d14f5fb0aed00409ee07d85ccf03a4ec +2 -0
- package/.npm/_cacache/index-v5/56/bc/31c9c48635fb47969a293eba4f565d970b8b2dd86e021ae1713753ddbaec +2 -0
- package/.npm/_cacache/index-v5/6b/33/9648614194cfe05bcf87e96e6d1a9d596eff2ccd2be75d5a63840ff5c5fa +2 -0
- package/.npm/_cacache/index-v5/7d/65/c2c4e4cf88f6f5a26a474eec1eb28a1a0c087f156fa82df974013a50081f +2 -0
- package/.npm/_cacache/index-v5/84/3c/a34aeaf29195f5f9401c15f6857af1bd76c7606e0b093aab991c9bb6dc49 +2 -0
- package/.npm/_cacache/index-v5/85/10/8e7b4fa59d85fc845f40d555750cde254b8f420226a92e17aa2aa651402e +2 -0
- package/.npm/_cacache/index-v5/87/da/af28f1133468f98432a0caf94d8a9e1a4f1bcd830398a06a4e51d9d17bcd +2 -0
- package/.npm/_cacache/index-v5/8d/ef/9d2076851d6fe0fa0268eaff636ef6e5460e964a4ef2dd328962a5bcaf60 +2 -0
- package/.npm/_cacache/index-v5/97/34/dbe58f025608dd514d67beb18cc0629d4de7ddaf177a9db1e4055f87ea95 +2 -0
- package/.npm/_cacache/index-v5/9a/85/0413827ea05586904775c2a90fc65067ec0bb008d3fdf73a12fa898b7a04 +2 -0
- package/.npm/_cacache/index-v5/9b/97/3672a25ed2ffa8387e2aef3d8efe3ab063f3468ecc8f8fd04357c75571ef +2 -0
- package/.npm/_cacache/index-v5/af/a8/e6580916548ffab34b60d8768e6aee8675ef59ad518a9c183058685c39c3 +2 -0
- package/.npm/_cacache/index-v5/be/fa/ec64da8a1ca11da9a4e002cd9e3e5c9a9bac8f1f61de82ff2495e7cadb90 +2 -0
- package/.npm/_cacache/index-v5/c9/10/e51aeab9f2ed39d1416132dca81cf2f580dc5de2fd02b9a85d1df3b191f3 +2 -0
- package/.npm/_cacache/index-v5/e6/ee/a7f609296af013f459dea95f5b86155b1d4d3fa7dc9ec423562d0c55294f +2 -0
- package/.npm/_cacache/index-v5/eb/bf/3d55cbdfa15db3275139376e851469f22db49100d7fe1c648784b7c07c58 +2 -0
- package/.npm/_cacache/index-v5/f0/3b/58c14b3461e0a75ddd4a0be80707cae7f82bf1ef1ae03a0dfc8054aa2570 +2 -0
- package/.npm/_cacache/index-v5/f5/91/b4bddca1df9d1a5db8fc8bd09d22078228fd3c1f50ad12a3c9886499c03f +2 -0
- package/.npm/_cacache/index-v5/fd/fb/c68ba9a1320725050b57ca9c7fbc237e694f1f4a6cb94104f7e3e654f7f4 +2 -0
- package/.npm/_logs/2025-10-11T01_26_43_124Z-debug-0.log +283 -0
- package/.npm/_logs/2025-10-11T23_03_53_873Z-debug-0.log +283 -0
- package/.npm/_logs/2025-10-11T23_04_45_411Z-debug-0.log +283 -0
- package/.npm/_logs/2025-10-11T23_12_37_676Z-debug-0.log +283 -0
- package/.npm/_logs/2025-10-11T23_13_52_627Z-debug-0.log +283 -0
- package/.npm/_logs/2025-10-11T23_29_34_855Z-debug-0.log +283 -0
- package/.npm/_logs/2025-10-11T23_35_33_841Z-debug-0.log +283 -0
- package/.npm/_logs/2025-10-12T00_02_59_292Z-debug-0.log +284 -0
- package/.npm/_logs/2025-10-12T00_04_16_309Z-debug-0.log +284 -0
- package/.npm/_logs/2025-10-17T01_57_18_990Z-debug-0.log +156 -0
- package/.npm/_logs/2025-10-17T02_27_57_689Z-debug-0.log +156 -0
- package/mise.toml +5 -0
- package/package.json +12 -3
- package/scripts/closeIssues/index.ts +23 -0
- package/scripts/closeIssues/modules/closeIssuesOperation.ts +167 -0
- package/scripts/modules/gitlabApi.ts +49 -0
- package/scripts/modules/utils.ts +5 -0
- package/scripts/releaseNote/index.ts +19 -0
- package/scripts/releaseNote/modules/releaseNoteGenerator.ts +231 -0
- package/tsconfig.json +4 -0
- package/tsconfig.node.json +29 -0
- package/types/env.d.ts +10 -0
- package/types/reset.d.ts +1 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import CloseIssuesOperation from "./modules/closeIssuesOperation";
|
|
2
|
+
|
|
3
|
+
const operation = new CloseIssuesOperation()
|
|
4
|
+
|
|
5
|
+
if (!operation.isMergeRequest) {
|
|
6
|
+
console.log('⚠️ Not a merge request. Skip closing issues.')
|
|
7
|
+
process.exit(0)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
operation.execute()
|
|
11
|
+
.then((result) => {
|
|
12
|
+
const error = result.find(r => r.status === 'error')
|
|
13
|
+
if (error) {
|
|
14
|
+
console.error('😭 Failed to close issues.')
|
|
15
|
+
process.exit(1)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log('👏 Issues closed successfully.')
|
|
19
|
+
})
|
|
20
|
+
.catch((error) => {
|
|
21
|
+
console.error('💥 Fatal error', error)
|
|
22
|
+
process.exit(1)
|
|
23
|
+
})
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Camelize, ExpandedMergeRequestSchema } from '@gitbeaker/rest'
|
|
2
|
+
import GitLabApi from '../../modules/gitlabApi'
|
|
3
|
+
|
|
4
|
+
type TargetIssue = {
|
|
5
|
+
keyword: string
|
|
6
|
+
id: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type OperationResultError = {id: string; status: 'error'; message: string}
|
|
10
|
+
type OperationResult = {id: string; status: 'closed' | 'skipped'} | OperationResultError
|
|
11
|
+
|
|
12
|
+
export default class CloseIssuesOperation extends GitLabApi {
|
|
13
|
+
// マージリクエストのID
|
|
14
|
+
readonly #mergeRequestIid = NaN
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super()
|
|
19
|
+
this.#mergeRequestIid = this.getMergeRequestIidFromCommitMessage(process.env.CI_COMMIT_MESSAGE ?? '')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** マージリクエストが存在するか */
|
|
23
|
+
get isMergeRequest(): boolean {
|
|
24
|
+
return !isNaN(this.#mergeRequestIid)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* マージリクエストに紐づくイシューをクローズする
|
|
29
|
+
*/
|
|
30
|
+
public async execute() {
|
|
31
|
+
const mr = await this.api.MergeRequests.show(this.projectId, this.#mergeRequestIid)
|
|
32
|
+
|
|
33
|
+
const issues = this.#getIssues(mr)
|
|
34
|
+
const result = await this.#closeIssues(issues)
|
|
35
|
+
|
|
36
|
+
this.#printSummary(issues, result)
|
|
37
|
+
|
|
38
|
+
return result
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* マージリクエストの説明文からクローズ対象のイシューIDリストを取得する
|
|
43
|
+
* @param mr
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
#getIssues(mr: ExpandedMergeRequestSchema | Camelize<ExpandedMergeRequestSchema>) {
|
|
47
|
+
const issues = Array.from([...mr.description?.matchAll(this.closeIssuesRegex) ?? '']
|
|
48
|
+
.map((match) => {
|
|
49
|
+
if (typeof match === 'string') {
|
|
50
|
+
return {
|
|
51
|
+
keyword: 'fixes',
|
|
52
|
+
id: NaN
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
keyword: match.groups?.keyword ?? 'fixes',
|
|
58
|
+
id: Number(match.groups?.number)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.filter(({ id }) => !isNaN(id))
|
|
62
|
+
.reduce((map, issue) => {
|
|
63
|
+
// イシューIDの重複排除
|
|
64
|
+
map.set(issue.id, issue)
|
|
65
|
+
return map
|
|
66
|
+
}, new Map<number, TargetIssue>())
|
|
67
|
+
.values())
|
|
68
|
+
|
|
69
|
+
return issues
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async #closeIssues(issues: TargetIssue[]) {
|
|
73
|
+
if (Array.from(issues).length === 0) return []
|
|
74
|
+
|
|
75
|
+
const result: OperationResult[] = []
|
|
76
|
+
|
|
77
|
+
for (const issue of issues) {
|
|
78
|
+
try {
|
|
79
|
+
const targetIssue = await this.api.Issues.show(issue.id, {
|
|
80
|
+
projectId: this.projectId
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
if (targetIssue.state === 'closed') {
|
|
84
|
+
result.push({
|
|
85
|
+
id: `#${issue.id}`,
|
|
86
|
+
status: 'skipped'
|
|
87
|
+
})
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (targetIssue.state === 'opened') {
|
|
92
|
+
await this.api.Issues.edit(this.projectId, issue.id, {
|
|
93
|
+
stateEvent: 'close',
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
result.push({
|
|
97
|
+
id: `#${issue.id}`,
|
|
98
|
+
status: 'closed'
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
result.push({
|
|
103
|
+
id: `#${issue.id}`,
|
|
104
|
+
status: 'error',
|
|
105
|
+
message: (error as Error).message
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return result
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 処理結果のログ出力
|
|
115
|
+
* @param issues
|
|
116
|
+
* @param result
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
#printSummary(issues: TargetIssue[], result: OperationResult[]) {
|
|
120
|
+
console.log('📊 Summary')
|
|
121
|
+
|
|
122
|
+
console.log(`\nMerge Request IID: !${this.#mergeRequestIid}`)
|
|
123
|
+
|
|
124
|
+
// ターゲットリスト
|
|
125
|
+
const targetIssues = Array.from(issues, (issue) => `#${issue.id}`)
|
|
126
|
+
const targetLength = targetIssues.length
|
|
127
|
+
|
|
128
|
+
console.log(`\nTarget issues: ${targetLength}件`)
|
|
129
|
+
if (targetIssues.length > 0) {
|
|
130
|
+
console.log(`${targetIssues.join(', ')}`)
|
|
131
|
+
} else {
|
|
132
|
+
// 0件なら終了
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log('\n')
|
|
137
|
+
|
|
138
|
+
const closed = result.filter(({ status }) => status === 'closed')
|
|
139
|
+
this.#printResultData('✅ Closed issues:', closed)
|
|
140
|
+
|
|
141
|
+
const skipped = result.filter(({ status }) => status === 'skipped')
|
|
142
|
+
this.#printResultData('⏭️ Skipped issues:', skipped)
|
|
143
|
+
|
|
144
|
+
const errors = result.filter(({ status }) => status === 'error')
|
|
145
|
+
this.#printResultData('❌ Error issues:', errors, true)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 処理結果の詳細ログ
|
|
150
|
+
* @param label
|
|
151
|
+
* @param result
|
|
152
|
+
* @param isError
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
#printResultData(label: string, result: OperationResult[], isError = false) {
|
|
156
|
+
console.log(`${label} ${result.length}件`)
|
|
157
|
+
|
|
158
|
+
for (const res of result) {
|
|
159
|
+
console.log(` - ${res.id}`)
|
|
160
|
+
|
|
161
|
+
if (isError) {
|
|
162
|
+
console.log(` Error: ${(res as OperationResultError).message}`)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Gitlab } from '@gitbeaker/rest'
|
|
2
|
+
|
|
3
|
+
export default class GitLabApi {
|
|
4
|
+
protected readonly api: InstanceType<typeof Gitlab>
|
|
5
|
+
protected readonly projectId: number
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
this.api = new Gitlab({
|
|
9
|
+
host: `https://${process.env.CI_SERVER_HOST ?? 'gitlab.com'}`,
|
|
10
|
+
token: process.env.CI_API_TOKEN,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
this.projectId = Number(process.env.CI_PROJECT_ID)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** MRからクローズ対象のイシューIDを抽出するための正規表現 */
|
|
17
|
+
protected get closeIssuesRegex() {
|
|
18
|
+
return /(?<keyword>fix(?:es)?|closes?|resolves?)\s*#(?<number>\d+)/gi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* コミットメッセージからマージリクエストのIIDを抽出する
|
|
23
|
+
* @param commitMessage
|
|
24
|
+
* @protected
|
|
25
|
+
*/
|
|
26
|
+
protected getMergeRequestIidFromCommitMessage(commitMessage: string): number {
|
|
27
|
+
// 検索したいコミットメッセージの正規表現パターン
|
|
28
|
+
const searchRegExpPattern = [
|
|
29
|
+
// パターン1:"See merge request project/repo!123"
|
|
30
|
+
/See merge request [^\s!]+!(?<iid>\d+)/i,
|
|
31
|
+
// パターン2:"See merge request !123"
|
|
32
|
+
/See merge request !(?<iid>\d+)/i,
|
|
33
|
+
// パターン3:"Closes !123" または "Merge !123"
|
|
34
|
+
/(?:closes?|merges?|see)\s+!(?<iid>\d+)/i,
|
|
35
|
+
// パターン4:"https://gitlab.com/project/repo/-/merge_requests/123"
|
|
36
|
+
/https?:\/\/[^\s\/]+\/[^\s\/]+\/[^\s\/]+\/-\/merge_requests\/(?<iid>\d+)/i,
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
for (const pattern of searchRegExpPattern) {
|
|
40
|
+
const match = commitMessage.match(pattern)
|
|
41
|
+
if (match && match.groups?.iid) {
|
|
42
|
+
return Number(match.groups.iid)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// マッチしなかった場合
|
|
47
|
+
return NaN
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import releaseNoteGenerator from './modules/releaseNoteGenerator'
|
|
2
|
+
|
|
3
|
+
const generator = new releaseNoteGenerator()
|
|
4
|
+
|
|
5
|
+
if (!generator.isReleaseTag) {
|
|
6
|
+
console.log('⚠️ Not a release tag. Skip generating release notes.')
|
|
7
|
+
process.exit(0)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
generator.execute()
|
|
11
|
+
.then((body: string) => {
|
|
12
|
+
console.log(body)
|
|
13
|
+
console.log('👏 Release notes generated successfully.')
|
|
14
|
+
})
|
|
15
|
+
.catch((error) => {
|
|
16
|
+
console.error('💥 Fatal error', error)
|
|
17
|
+
process.exit(1)
|
|
18
|
+
})
|
|
19
|
+
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import GitLabApi from '../../modules/gitlabApi'
|
|
2
|
+
import type { ExpandedMergeRequestSchema, SimpleLabelSchema } from '@gitbeaker/rest'
|
|
3
|
+
import { writeFile } from 'node:fs/promises'
|
|
4
|
+
import { isString } from '../../modules/utils'
|
|
5
|
+
|
|
6
|
+
interface ScopedMergeRequest {
|
|
7
|
+
scope: string
|
|
8
|
+
title: string
|
|
9
|
+
webUrl: string
|
|
10
|
+
iid: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 最古のコミットを指す定数
|
|
14
|
+
const OLDEST_COMMIT_REF = 'HEAD~9999'
|
|
15
|
+
|
|
16
|
+
export default class ReleaseNoteGenerator extends GitLabApi {
|
|
17
|
+
readonly #currentTag: string;
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super()
|
|
21
|
+
|
|
22
|
+
this.#currentTag = process.env.CI_COMMIT_TAG ?? ''
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** リリースタグが存在している */
|
|
26
|
+
get isReleaseTag() {
|
|
27
|
+
return this.#currentTag !== ''
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** リリースノート作成! */
|
|
31
|
+
public async execute() {
|
|
32
|
+
const previousTag = await this.#getPreviousTag()
|
|
33
|
+
console.log({ previousTag, currentTag: this.#currentTag })
|
|
34
|
+
|
|
35
|
+
const mergeRequest = await this.#getMergeRequestsBetweenTags(previousTag, this.#currentTag)
|
|
36
|
+
const scopedMergeRequests = this.#categorizeMergeRequestsByScope(mergeRequest)
|
|
37
|
+
|
|
38
|
+
const releaseNoteBody = this.#generateReleaseNoteBody(scopedMergeRequests)
|
|
39
|
+
|
|
40
|
+
// flag: 'a' だと末尾に追記する。先頭に追記したい場合は既存のドキュメントを読み込んで結合する必要がある
|
|
41
|
+
await writeFile('release_notes.md', releaseNoteBody, { encoding: 'utf8', flag: 'w' })
|
|
42
|
+
|
|
43
|
+
return releaseNoteBody
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 前のリリースタグを取得
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
async #getPreviousTag(): Promise<string> {
|
|
51
|
+
const tags = await this.api.Tags.all(this.projectId, {
|
|
52
|
+
orderBy: 'updated',
|
|
53
|
+
sort: 'desc',
|
|
54
|
+
perPage: 100
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const currentTagIndex = tags.findIndex((tag) => tag.name === this.#currentTag)
|
|
58
|
+
|
|
59
|
+
if (currentTagIndex === -1) {
|
|
60
|
+
throw new Error(`Current tag ${this.#currentTag} not found`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (currentTagIndex === tags.length - 1) {
|
|
64
|
+
// 最初のタグの場合は、リポジトリの最初のコミットから
|
|
65
|
+
return OLDEST_COMMIT_REF
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return tags[currentTagIndex + 1].name
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 指定された2つのタグ間のマージリクエストを取得
|
|
73
|
+
* @param fromTag
|
|
74
|
+
* @param toTag
|
|
75
|
+
*/
|
|
76
|
+
async #getMergeRequestsBetweenTags(fromTag: string, toTag: string): Promise<ExpandedMergeRequestSchema[]> {
|
|
77
|
+
const fromTagInfo = await this.#getTagInfo(fromTag)
|
|
78
|
+
|
|
79
|
+
const createdAt = fromTagInfo?.commit.created_at
|
|
80
|
+
const since = createdAt && isString(createdAt) ? createdAt : undefined
|
|
81
|
+
|
|
82
|
+
const commits = await this.api.Commits.all(this.projectId, {
|
|
83
|
+
refName: toTag,
|
|
84
|
+
since
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// マージコミットからコミットリクエストを特定
|
|
88
|
+
const mergeRequestIids = new Set<number>()
|
|
89
|
+
|
|
90
|
+
// デフォルトブランチへのマージコミットを判定する正規表現
|
|
91
|
+
const defaultBranchMergeRegExp = new RegExp(`^Merge branch.*into\\s+'?${process.env.CI_DEFAULT_BRANCH}'?`, 'i')
|
|
92
|
+
|
|
93
|
+
for (const commit of commits) {
|
|
94
|
+
// デフォルトブランチへのマージコミットは対象外
|
|
95
|
+
if (commit.title.match(defaultBranchMergeRegExp)) continue
|
|
96
|
+
|
|
97
|
+
const iid = this.getMergeRequestIidFromCommitMessage(commit.message)
|
|
98
|
+
if (!isNaN(iid)) {
|
|
99
|
+
mergeRequestIids.add(iid)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const promises = Array.from(mergeRequestIids).map((iid) => this.api.MergeRequests.show(this.projectId, iid))
|
|
104
|
+
const results = await Promise.allSettled(promises)
|
|
105
|
+
|
|
106
|
+
return results
|
|
107
|
+
.filter((result): result is PromiseFulfilledResult<ExpandedMergeRequestSchema> => {
|
|
108
|
+
if (result.status === 'rejected') {
|
|
109
|
+
// マージリクエストIIDを取得するために、元の配列のインデックスを使用
|
|
110
|
+
const iid = Array.from(mergeRequestIids)[results.indexOf(result)]
|
|
111
|
+
console.warn(`⚠️ Could not fetch MR !${iid}:`, result.reason?.message || result.reason)
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true
|
|
116
|
+
})
|
|
117
|
+
.map((result) => result.value)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* タグの情報を取得
|
|
122
|
+
* @param tag
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
async #getTagInfo(tag: string) {
|
|
126
|
+
if (tag === OLDEST_COMMIT_REF) return undefined
|
|
127
|
+
|
|
128
|
+
return await this.api.Tags.show(this.projectId, tag)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* マージリクエストをスコープごとに分類
|
|
133
|
+
* @param mergeRequests
|
|
134
|
+
* @private
|
|
135
|
+
*/
|
|
136
|
+
#categorizeMergeRequestsByScope(mergeRequests: ExpandedMergeRequestSchema[]): Map<string, ScopedMergeRequest[]> {
|
|
137
|
+
const scopedMrs = new Map<string, ScopedMergeRequest[]>()
|
|
138
|
+
|
|
139
|
+
for (const mr of mergeRequests) {
|
|
140
|
+
const scope = this.#extractScopesFromLabels(this.#getLabelNames(mr.labels ?? []))
|
|
141
|
+
if (scope == null) continue
|
|
142
|
+
|
|
143
|
+
const scopedMr: ScopedMergeRequest = {
|
|
144
|
+
scope,
|
|
145
|
+
title: mr.title,
|
|
146
|
+
webUrl: mr.web_url,
|
|
147
|
+
iid: mr.iid
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!scopedMrs.has(scope)) {
|
|
151
|
+
scopedMrs.set(scope, [])
|
|
152
|
+
}
|
|
153
|
+
scopedMrs.get(scope)!.push(scopedMr)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return scopedMrs
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* ラベル名を配列で取得
|
|
161
|
+
* @param labels
|
|
162
|
+
* @private
|
|
163
|
+
*/
|
|
164
|
+
#getLabelNames(labels: string[] | SimpleLabelSchema[]): string[] {
|
|
165
|
+
if (labels.length === 0) return []
|
|
166
|
+
|
|
167
|
+
// GitLab APIのレスポンスは一貫しているので方が混在することはないため最初の型をチェックしてstring[]になるようにする
|
|
168
|
+
return typeof labels.at(0) === 'string'
|
|
169
|
+
? labels as string[]
|
|
170
|
+
: (labels as SimpleLabelSchema[]).map(label => label.name)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* ラベルからリリースノート用のラベルを抽出
|
|
175
|
+
* リリース用のラベルはスコープドなので必ず一つだけしか存在しない
|
|
176
|
+
* @param labels
|
|
177
|
+
* @private
|
|
178
|
+
*/
|
|
179
|
+
#extractScopesFromLabels(labels: string[]): string | null {
|
|
180
|
+
const scopes = labels
|
|
181
|
+
.filter((label) => label.startsWith('log::'))
|
|
182
|
+
.map((label) => label.replace('log::', ''))
|
|
183
|
+
|
|
184
|
+
return scopes.at(0) ?? null
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* リリースノートの本文を生成
|
|
189
|
+
* @param scopedMRs
|
|
190
|
+
* @private
|
|
191
|
+
*/
|
|
192
|
+
#generateReleaseNoteBody(scopedMRs: Map<string, ScopedMergeRequest[]>): string {
|
|
193
|
+
const header = `# ${this.#currentTag}`
|
|
194
|
+
|
|
195
|
+
if (scopedMRs.size === 0) {
|
|
196
|
+
return [
|
|
197
|
+
header,
|
|
198
|
+
'',
|
|
199
|
+
'No changes.'
|
|
200
|
+
].join('\n')
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const sortedScopes = Array.from(scopedMRs.keys()).sort()
|
|
204
|
+
|
|
205
|
+
const scopeSections: string[] = []
|
|
206
|
+
|
|
207
|
+
for (const scope of sortedScopes) {
|
|
208
|
+
const mrs = scopedMRs.get(scope)!
|
|
209
|
+
const sectionHeader = `## ${scope.charAt(0).toUpperCase() + scope.slice(1)}`
|
|
210
|
+
|
|
211
|
+
const mrList: string[] = []
|
|
212
|
+
for (const mr of mrs) {
|
|
213
|
+
mrList.push(`- ${mr.title} ([!${mr.iid}](${mr.webUrl}))`)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
scopeSections.push([
|
|
217
|
+
'',
|
|
218
|
+
sectionHeader,
|
|
219
|
+
...mrList,
|
|
220
|
+
]
|
|
221
|
+
.join('\n'))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return [
|
|
225
|
+
header,
|
|
226
|
+
...scopeSections,
|
|
227
|
+
'',
|
|
228
|
+
''
|
|
229
|
+
].join('\n')
|
|
230
|
+
}
|
|
231
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022"],
|
|
5
|
+
"module": "ES2022",
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
|
|
8
|
+
/* Bundler mode */
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": false,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"types": ["node"],
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"noUncheckedSideEffectImports": true,
|
|
23
|
+
"verbatimModuleSyntax": true,
|
|
24
|
+
"stripInternal": true,
|
|
25
|
+
"erasableSyntaxOnly": true
|
|
26
|
+
},
|
|
27
|
+
"include": ["types/**/*", "scripts/**/*"],
|
|
28
|
+
"exclude": ["node_modules"]
|
|
29
|
+
}
|
package/types/env.d.ts
ADDED
package/types/reset.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@total-typescript/ts-reset'
|