diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 154b5f46..c289cfce 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -2,13 +2,138 @@ name: Close inactive issues on: workflow_dispatch: + issues: + types: [opened, edited] + schedule: # Github Action 只支持 UTC 时间。 # '0 18 * * *' 对应 UTC 时间的 18:00,也就是中国时区 (UTC+8) 的第二天凌晨 02:00。 - cron: "0 18 * * *" jobs: + label-opened-issue: + if: github.event_name == 'issues' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/github-script@v7 + with: + script: | + const issue = context.payload.issue; + const title = issue.title || ''; + const body = issue.body || ''; + const currentLabels = (issue.labels || []).map((label) => label.name); + + // 网页 Issue Form 已经会自动带模板 labels;这里只兜底处理 + // API 创建或异常路径产生的无 label issue,避免重复补标。 + if (currentLabels.length > 0) { + core.info(`Issue #${issue.number} already has labels: ${currentLabels.join(', ')}`); + return; + } + + const hasAllMarkers = (markers) => markers.every((marker) => body.includes(marker)); + const labelRules = [ + { + label: 'bug', + titlePrefix: '[错误报告]:', + markers: ['### 当前程序版本', '### 运行环境', '### 问题类型', '### 问题描述'], + }, + { + label: 'feature request', + titlePrefix: '[Feature Request]:', + markers: ['### 当前程序版本', '### 运行环境', '### 功能改进类型', '### 功能改进'], + }, + { + label: 'RFC', + titlePrefix: '[RFC]', + markers: ['### 背景 or 问题', '### 目标 & 方案简述'], + }, + ]; + + const matched = labelRules.find((rule) => ( + title.startsWith(rule.titlePrefix) || hasAllMarkers(rule.markers) + )); + + if (!matched) { + core.info(`Issue #${issue.number} does not match known issue templates.`); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: [matched.label], + }); + core.info(`Added label "${matched.label}" to issue #${issue.number}.`); + + label-unlabeled-issues: + if: github.event_name != 'issues' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/github-script@v7 + with: + script: | + const labelRules = [ + { + label: 'bug', + titlePrefix: '[错误报告]:', + markers: ['### 当前程序版本', '### 运行环境', '### 问题类型', '### 问题描述'], + }, + { + label: 'feature request', + titlePrefix: '[Feature Request]:', + markers: ['### 当前程序版本', '### 运行环境', '### 功能改进类型', '### 功能改进'], + }, + { + label: 'RFC', + titlePrefix: '[RFC]', + markers: ['### 背景 or 问题', '### 目标 & 方案简述'], + }, + ]; + + const hasAllMarkers = (body, markers) => markers.every((marker) => body.includes(marker)); + const getMatchedRule = (issue) => { + const title = issue.title || ''; + const body = issue.body || ''; + return labelRules.find((rule) => ( + title.startsWith(rule.titlePrefix) || hasAllMarkers(body, rule.markers) + )); + }; + + // Search API 支持 no:label 查询;issues.listForRepo 的 labels=none + // 会被当作名为 none 的标签,不能用于扫描无 label issue。 + const query = `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open no:label`; + for await (const response of github.paginate.iterator(github.rest.search.issuesAndPullRequests, { + q: query, + per_page: 100, + })) { + for (const issue of response.data) { + if (issue.pull_request) { + continue; + } + + const matched = getMatchedRule(issue); + if (!matched) { + continue; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: [matched.label], + }); + core.info(`Added label "${matched.label}" to issue #${issue.number}.`); + } + } + close-issues: + if: github.event_name != 'issues' + needs: label-unlabeled-issues runs-on: ubuntu-latest permissions: issues: write @@ -30,4 +155,4 @@ jobs: # 排除带有RFC标签的issue exempt-issue-labels: "RFC" operations-per-run: 500 - repo-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + repo-token: ${{ secrets.GITHUB_TOKEN }}