Compare commits

...

2 Commits

Author SHA1 Message Date
snomiao
b3dc094844 refactor: migrate release PR label tagger from inline JS to TypeScript
Extract the inline JavaScript from the `actions/github-script@v7` step
in the release-pr-label-tagger workflow into a proper TypeScript script
at `scripts/release-pr-label-tagger.ts`.

- Add `@actions/github` and `@actions/core` devDependencies for typing
- Create `scripts/release-pr-label-tagger.ts` with full TypeScript types
- Update workflow to use checkout + setup-frontend composite action + tsx
- Logic is split into focused named functions for readability

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-19 02:36:06 +00:00
snomiao
4400f7d1f5 feat: add GitHub Actions workflow to auto-label PRs on release 2026-02-19 02:07:12 +00:00
4 changed files with 485 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
---
name: Release PR Label Tagger
on:
release:
types: [published]
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag to process (e.g. core/v1.40.0)'
required: true
type: string
jobs:
label-prs:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
steps:
- uses: actions/checkout@v6
- name: Setup frontend
uses: ./.github/actions/setup-frontend
- name: Tag PRs with release label
run: pnpm exec tsx scripts/release-pr-label-tagger.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INPUT_RELEASE_TAG: ${{ inputs.release_tag }}

View File

@@ -116,6 +116,8 @@
"zod-validation-error": "catalog:"
},
"devDependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"@eslint/js": "catalog:",
"@intlify/eslint-plugin-vue-i18n": "catalog:",
"@lobehub/i18n-cli": "catalog:",

188
pnpm-lock.yaml generated
View File

@@ -513,6 +513,12 @@ importers:
specifier: 'catalog:'
version: 3.3.0(zod@3.24.1)
devDependencies:
'@actions/core':
specifier: ^1.10.1
version: 1.11.1
'@actions/github':
specifier: ^6.0.0
version: 6.0.1
'@eslint/js':
specifier: 'catalog:'
version: 9.39.1
@@ -846,6 +852,21 @@ packages:
'@acemir/cssom@0.9.30':
resolution: {integrity: sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==}
'@actions/core@1.11.1':
resolution: {integrity: sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==}
'@actions/exec@1.1.1':
resolution: {integrity: sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==}
'@actions/github@6.0.1':
resolution: {integrity: sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw==}
'@actions/http-client@2.2.3':
resolution: {integrity: sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==}
'@actions/io@1.1.3':
resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==}
'@adobe/css-tools@4.4.4':
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
@@ -1899,6 +1920,10 @@ packages:
'@exodus/crypto':
optional: true
'@fastify/busboy@2.1.1':
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
engines: {node: '>=14'}
'@firebase/analytics-compat@0.2.18':
resolution: {integrity: sha512-Hw9mzsSMZaQu6wrTbi3kYYwGw9nBqOHr47pVLxfr5v8CalsdrG5gfs9XUlPOZjHRVISp3oQrh1j7d3E+ulHPjQ==}
peerDependencies:
@@ -2516,6 +2541,54 @@ packages:
'@nx/workspace@22.2.6':
resolution: {integrity: sha512-DQnyxoFcf0oDrYIlwAHU4JOxDTA9AKODF3L3JUjNyKqMBSj9Z9o9FEf32wZZCzi0Akf4LDZxyPxU7wz+o1u/WA==}
'@octokit/auth-token@4.0.0':
resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==}
engines: {node: '>= 18'}
'@octokit/core@5.2.2':
resolution: {integrity: sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==}
engines: {node: '>= 18'}
'@octokit/endpoint@9.0.6':
resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==}
engines: {node: '>= 18'}
'@octokit/graphql@7.1.1':
resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==}
engines: {node: '>= 18'}
'@octokit/openapi-types@20.0.0':
resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==}
'@octokit/openapi-types@24.2.0':
resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==}
'@octokit/plugin-paginate-rest@9.2.2':
resolution: {integrity: sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': '5'
'@octokit/plugin-rest-endpoint-methods@10.4.1':
resolution: {integrity: sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': '5'
'@octokit/request-error@5.1.1':
resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==}
engines: {node: '>= 18'}
'@octokit/request@8.4.1':
resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==}
engines: {node: '>= 18'}
'@octokit/types@12.6.0':
resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==}
'@octokit/types@13.10.0':
resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==}
'@one-ini/wasm@0.1.1':
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
@@ -4415,6 +4488,9 @@ packages:
resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==}
hasBin: true
before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
@@ -4867,6 +4943,9 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
deprecation@2.3.1:
resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -7849,6 +7928,10 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
tunnel@0.0.6:
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}
tw-animate-css@1.3.8:
resolution: {integrity: sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==}
@@ -7927,6 +8010,10 @@ packages:
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
undici@5.29.0:
resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
engines: {node: '>=14.0'}
unescape-js@1.1.4:
resolution: {integrity: sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g==}
@@ -7961,6 +8048,9 @@ packages:
unist-util-visit@5.0.0:
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
universal-user-agent@6.0.1:
resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==}
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
@@ -8540,6 +8630,32 @@ snapshots:
'@acemir/cssom@0.9.30': {}
'@actions/core@1.11.1':
dependencies:
'@actions/exec': 1.1.1
'@actions/http-client': 2.2.3
'@actions/exec@1.1.1':
dependencies:
'@actions/io': 1.1.3
'@actions/github@6.0.1':
dependencies:
'@actions/http-client': 2.2.3
'@octokit/core': 5.2.2
'@octokit/plugin-paginate-rest': 9.2.2(@octokit/core@5.2.2)
'@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.2.2)
'@octokit/request': 8.4.1
'@octokit/request-error': 5.1.1
undici: 5.29.0
'@actions/http-client@2.2.3':
dependencies:
tunnel: 0.0.6
undici: 5.29.0
'@actions/io@1.1.3': {}
'@adobe/css-tools@4.4.4': {}
'@alcalzone/ansi-tokenize@0.2.0':
@@ -9643,6 +9759,8 @@ snapshots:
'@exodus/bytes@1.7.0': {}
'@fastify/busboy@2.1.1': {}
'@firebase/analytics-compat@0.2.18(@firebase/app-compat@0.2.53)(@firebase/app@0.11.4)':
dependencies:
'@firebase/analytics': 0.10.12(@firebase/app@0.11.4)
@@ -10658,6 +10776,64 @@ snapshots:
- '@swc/core'
- debug
'@octokit/auth-token@4.0.0': {}
'@octokit/core@5.2.2':
dependencies:
'@octokit/auth-token': 4.0.0
'@octokit/graphql': 7.1.1
'@octokit/request': 8.4.1
'@octokit/request-error': 5.1.1
'@octokit/types': 13.10.0
before-after-hook: 2.2.3
universal-user-agent: 6.0.1
'@octokit/endpoint@9.0.6':
dependencies:
'@octokit/types': 13.10.0
universal-user-agent: 6.0.1
'@octokit/graphql@7.1.1':
dependencies:
'@octokit/request': 8.4.1
'@octokit/types': 13.10.0
universal-user-agent: 6.0.1
'@octokit/openapi-types@20.0.0': {}
'@octokit/openapi-types@24.2.0': {}
'@octokit/plugin-paginate-rest@9.2.2(@octokit/core@5.2.2)':
dependencies:
'@octokit/core': 5.2.2
'@octokit/types': 12.6.0
'@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.2.2)':
dependencies:
'@octokit/core': 5.2.2
'@octokit/types': 12.6.0
'@octokit/request-error@5.1.1':
dependencies:
'@octokit/types': 13.10.0
deprecation: 2.3.1
once: 1.4.0
'@octokit/request@8.4.1':
dependencies:
'@octokit/endpoint': 9.0.6
'@octokit/request-error': 5.1.1
'@octokit/types': 13.10.0
universal-user-agent: 6.0.1
'@octokit/types@12.6.0':
dependencies:
'@octokit/openapi-types': 20.0.0
'@octokit/types@13.10.0':
dependencies:
'@octokit/openapi-types': 24.2.0
'@one-ini/wasm@0.1.1': {}
'@oxc-project/runtime@0.112.0': {}
@@ -12615,6 +12791,8 @@ snapshots:
baseline-browser-mapping@2.9.7: {}
before-after-hook@2.2.3: {}
bidi-js@1.0.3:
dependencies:
require-from-string: 2.0.2
@@ -13075,6 +13253,8 @@ snapshots:
delayed-stream@1.0.0: {}
deprecation@2.3.1: {}
dequal@2.0.3: {}
detect-libc@2.1.2: {}
@@ -16769,6 +16949,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
tunnel@0.0.6: {}
tw-animate-css@1.3.8: {}
type-check@0.4.0:
@@ -16858,6 +17040,10 @@ snapshots:
undici-types@7.16.0: {}
undici@5.29.0:
dependencies:
'@fastify/busboy': 2.1.1
unescape-js@1.1.4:
dependencies:
string.fromcodepoint: 0.2.1
@@ -16902,6 +17088,8 @@ snapshots:
unist-util-is: 6.0.0
unist-util-visit-parents: 6.0.1
universal-user-agent@6.0.1: {}
universalify@2.0.1: {}
unplugin-icons@22.5.0(@vue/compiler-sfc@3.5.25):

View File

@@ -0,0 +1,263 @@
import * as core from '@actions/core'
import { context, getOctokit } from '@actions/github'
type OctokitInstance = ReturnType<typeof getOctokit>
type ReleaseLabel = 'released:core' | 'released:cloud'
const LABEL_COLORS: Record<ReleaseLabel, string> = {
'released:core': '0075ca',
'released:cloud': 'e4e669'
}
function getLabelForBranch(branchName: string | null): ReleaseLabel | null {
if (branchName?.startsWith('core/')) return 'released:core'
if (branchName?.startsWith('cloud/')) return 'released:cloud'
return null
}
function getLabelForTag(tag: string): ReleaseLabel | null {
if (tag.startsWith('core/')) return 'released:core'
if (tag.startsWith('cloud/')) return 'released:cloud'
return null
}
function isHttpError(err: unknown): err is { status: number } {
return typeof err === 'object' && err !== null && 'status' in err
}
async function ensureLabelExists(
octokit: OctokitInstance,
label: ReleaseLabel
): Promise<void> {
try {
await octokit.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label
})
core.info(`Label '${label}' already exists`)
} catch (err: unknown) {
if (isHttpError(err) && err.status === 404) {
await octokit.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
color: LABEL_COLORS[label]
})
core.info(`Created label '${label}'`)
} else {
throw err
}
}
}
async function findPreviousReleaseTag(
octokit: OctokitInstance,
branchPrefix: string,
currentTag: string
): Promise<string | null> {
const releases = await octokit.paginate(octokit.rest.repos.listReleases, {
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
})
const samePrefix = releases.filter(
(r) => r.tag_name !== currentTag && r.tag_name.startsWith(branchPrefix)
)
if (samePrefix.length === 0) return null
const currentRelease = releases.find((r) => r.tag_name === currentTag)
const currentDate = currentRelease?.published_at
? new Date(currentRelease.published_at)
: new Date()
const previous = samePrefix
.filter((r) => r.published_at && new Date(r.published_at) < currentDate)
.sort(
(a, b) =>
new Date(b.published_at ?? 0).getTime() -
new Date(a.published_at ?? 0).getTime()
)
return previous[0]?.tag_name ?? null
}
async function getCommitsInRange(
octokit: OctokitInstance,
releaseTag: string,
previousTag: string | null
): Promise<Array<{ sha: string }>> {
if (previousTag) {
const comparison = await octokit.rest.repos.compareCommitsWithBasehead({
owner: context.repo.owner,
repo: context.repo.repo,
basehead: `${previousTag}...${releaseTag}`
})
const { commits } = comparison.data
core.info(
`Found ${commits.length} commits between ${previousTag} and ${releaseTag}`
)
return commits
}
const { data: tagCommit } = await octokit.rest.repos.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
ref: releaseTag
})
core.info(
`No previous release; processing single HEAD commit ${tagCommit.sha}`
)
return [tagCommit]
}
async function collectMergedPRNumbers(
octokit: OctokitInstance,
commits: Array<{ sha: string }>
): Promise<Set<number>> {
const prNumbers = new Set<number>()
for (const commit of commits) {
const { data: prs } =
await octokit.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: commit.sha
})
for (const pr of prs) {
if (pr.merged_at) prNumbers.add(pr.number)
}
}
return prNumbers
}
async function labelPRs(
octokit: OctokitInstance,
prNumbers: Set<number>,
label: ReleaseLabel
): Promise<{ labeled: string[]; skipped: string[] }> {
const labeled: string[] = []
const skipped: string[] = []
for (const prNumber of prNumbers) {
const { data: pr } = await octokit.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
})
if (pr.labels.some((l) => l.name === label)) {
skipped.push(`#${prNumber}`)
continue
}
await octokit.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [label]
})
labeled.push(`#${prNumber} ${pr.title}`)
core.info(`Labeled PR #${prNumber}: ${pr.title}`)
}
return { labeled, skipped }
}
async function writeSummary(
releaseTag: string,
label: ReleaseLabel,
previousTag: string | null,
labeled: string[],
skipped: string[]
): Promise<void> {
const summaryLines = [
`## Release PR Label Tagger`,
``,
`**Release tag:** \`${releaseTag}\``,
`**Label applied:** \`${label}\``,
`**Previous release:** ${previousTag ? `\`${previousTag}\`` : '_none_'}`,
``
]
if (labeled.length > 0) {
summaryLines.push(`### Labeled PRs (${labeled.length})`)
summaryLines.push(...labeled.map((entry) => `- ${entry}`))
summaryLines.push(``)
}
if (skipped.length > 0) {
summaryLines.push(`### Skipped (already labeled): ${skipped.join(', ')}`)
summaryLines.push(``)
}
if (labeled.length === 0 && skipped.length === 0) {
summaryLines.push(`_No PRs found to label._`)
}
await core.summary.addRaw(summaryLines.join('\n')).write()
}
async function main(): Promise<void> {
const token = process.env.GITHUB_TOKEN
if (!token) throw new Error('GITHUB_TOKEN is required')
const octokit = getOctokit(token)
const isManual = context.eventName === 'workflow_dispatch'
const releaseTag: string = isManual
? (process.env.INPUT_RELEASE_TAG ?? context.payload.inputs?.release_tag)
: context.payload.release?.tag_name
if (!releaseTag) throw new Error('Could not determine release tag')
const branch: string | null = isManual
? null
: (context.payload.release?.target_commitish ?? null)
core.info(`Processing release tag: ${releaseTag}`)
core.info(
`Target branch: ${branch ?? '(manual trigger, will be derived from tag)'}`
)
const label = getLabelForBranch(branch) ?? getLabelForTag(releaseTag)
if (!label) {
core.notice(
`Skipping: branch '${branch}' / tag '${releaseTag}' does not match core/ or cloud/ pattern`
)
return
}
core.info(`Will apply label: ${label}`)
const branchPrefix = label === 'released:core' ? 'core/' : 'cloud/'
await ensureLabelExists(octokit, label)
const previousTag = await findPreviousReleaseTag(
octokit,
branchPrefix,
releaseTag
)
core.info(
previousTag
? `Previous release tag: ${previousTag}`
: 'No previous release found; will use all commits reachable from this tag'
)
const commits = await getCommitsInRange(octokit, releaseTag, previousTag)
const prNumbers = await collectMergedPRNumbers(octokit, commits)
core.info(`Found ${prNumbers.size} merged PRs associated with these commits`)
const { labeled, skipped } = await labelPRs(octokit, prNumbers, label)
await writeSummary(releaseTag, label, previousTag, labeled, skipped)
}
main().catch((err: unknown) => {
core.setFailed(err instanceof Error ? err.message : String(err))
process.exit(1)
})