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 pull-requests: write steps: - uses: actions/stale@v5 with: # 标记 stale 标签时间 days-before-issue-stale: 30 # 关闭 issues 标签时间 days-before-issue-close: 14 # 自定义标签名 stale-issue-label: "stale" stale-issue-message: "此问题已过时,因为它已打开 30 天且没有任何活动。" close-issue-message: "此问题已关闭,因为它在标记为 stale 后,已处于无更新状态 14 天。" # 忽略所有的 Pull Request,只处理 Issue days-before-pr-stale: -1 days-before-pr-close: -1 # 排除带有RFC标签的issue exempt-issue-labels: "RFC" operations-per-run: 500 repo-token: ${{ secrets.GITHUB_TOKEN }}