Skip to content

Commit b4fc09b

Browse files
替换为 PR 自动 rebase workflow
1 parent b9620c9 commit b4fc09b

1 file changed

Lines changed: 70 additions & 147 deletions

File tree

Lines changed: 70 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,176 +1,99 @@
1-
name: Auto-merge approved submission
1+
name: Auto-rebase submission PRs
22

3+
# 每当有 PR 合并到 main 时触发
34
on:
4-
issues:
5-
types: [labeled]
5+
pull_request:
6+
types: [closed]
7+
branches: [main]
68

79
concurrency:
8-
group: approve-submission
9-
cancel-in-progress: false # 不取消排队中的,按顺序跑
10+
group: rebase-submissions
11+
cancel-in-progress: false
1012

1113
jobs:
12-
merge:
13-
if: github.event.label.name == 'approved'
14+
rebase-open-prs:
15+
# 只在 PR 被合并(不是关闭)时运行
16+
if: github.event.pull_request.merged == true
1417
runs-on: ubuntu-latest
1518
permissions:
1619
contents: write
17-
issues: write
20+
pull-requests: write
1821

1922
steps:
2023
- uses: actions/checkout@v4
2124
with:
2225
ref: main
23-
fetch-depth: 1
26+
fetch-depth: 0
27+
token: ${{ secrets.GITHUB_TOKEN }}
2428

25-
- name: Parse issue and merge script
29+
- name: Rebase all open submission PRs
2630
uses: actions/github-script@v7
2731
with:
2832
script: |
29-
const issue = context.payload.issue;
30-
const body = issue.body || '';
31-
32-
// ── 1. 解析 Issue body ──
33-
const field = (key) => {
34-
const re = new RegExp(`${key}:\\s*(.+)`, 'i');
35-
const m = body.match(re);
36-
return m ? m[1].trim() : '';
37-
};
38-
39-
const author = field('作者') || field('Author');
40-
const category = field('分类') || field('Category');
41-
const name = field('描述') || issue.title.replace(/^\[投稿\]\s*/, '');
42-
const desc = field('描述') || '';
43-
const scriptName = issue.title.replace(/^\[投稿\]\s*/, '').trim();
44-
45-
// 提取代码块
46-
const codeMatch = body.match(/---\s*代码\s*---\s*\n([\s\S]+)$/i)
47-
|| body.match(/```(?:python)?\n([\s\S]+?)```/);
48-
if (!codeMatch) {
49-
await github.rest.issues.createComment({
50-
owner: context.repo.owner,
51-
repo: context.repo.repo,
52-
issue_number: issue.number,
53-
body: '❌ 无法从 Issue 中提取代码,请检查格式。'
54-
});
55-
return;
56-
}
57-
const code = codeMatch[1].trimEnd();
58-
59-
// 映射分类
60-
const catMap = {
61-
'基础脚本': 'basic', 'basic': 'basic', 'Basics': 'basic',
62-
'UI 界面': 'ui', 'UI': 'ui', 'ui': 'ui',
63-
'游戏': 'games', 'Games': 'games', 'games': 'games',
64-
'小组件': 'widgets', 'Widgets': 'widgets', 'widgets': 'widgets',
65-
'其他': 'other', 'Other': 'other', 'other': 'other'
66-
};
67-
const catId = catMap[category] || 'basic';
68-
69-
// 生成安全的文件 ID
70-
const safeId = scriptName
71-
.toLowerCase()
72-
.replace(/[\s]+/g, '_')
73-
.replace(/[^a-z0-9_\u4e00-\u9fff]/g, '')
74-
.substring(0, 40) || `submission_${issue.number}`;
75-
const scriptId = `community_${safeId}`;
76-
77-
const catDir = { basic: 'basic', ui: 'ui', games: 'games', widgets: 'widgets', other: 'other' };
78-
const dir = catDir[catId] || 'basic';
79-
80-
// ── 2. 读取现有 index.json ──
81-
const fs = require('fs');
82-
const indexPath = 'script_library/index.json';
83-
const indexRaw = fs.readFileSync(indexPath, 'utf8');
84-
const index = JSON.parse(indexRaw);
85-
86-
// 检查重复
87-
if (index.scripts.some(s => s.id === scriptId)) {
88-
await github.rest.issues.createComment({
89-
owner: context.repo.owner,
90-
repo: context.repo.repo,
91-
issue_number: issue.number,
92-
body: `⚠️ 脚本 ID \`${scriptId}\` 已存在,跳过。`
93-
});
33+
const { execSync } = require('child_process');
34+
35+
execSync('git config user.name "github-actions[bot]"');
36+
execSync('git config user.email "github-actions[bot]@users.noreply.github.com"');
37+
38+
// 获取所有 open 的 PR
39+
const { data: openPRs } = await github.rest.pulls.list({
40+
owner: context.repo.owner,
41+
repo: context.repo.repo,
42+
state: 'open',
43+
base: 'main',
44+
sort: 'created',
45+
direction: 'asc'
46+
});
47+
48+
if (openPRs.length === 0) {
49+
core.info('No open PRs to rebase.');
9450
return;
9551
}
9652
97-
// ── 3. 写入脚本文件 ──
98-
const scriptRelPath = `scripts/${dir}/${scriptId}.py`;
99-
const scriptFullPath = `script_library/${scriptRelPath}`;
100-
const scriptDir = require('path').dirname(scriptFullPath);
101-
fs.mkdirSync(scriptDir, { recursive: true });
102-
fs.writeFileSync(scriptFullPath, code + '\n', 'utf8');
103-
104-
const lineCount = code.split('\n').length;
105-
106-
// ── 4. 更新 index.json ──
107-
index.data_version += 1;
108-
index.updated = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
109-
110-
index.scripts.push({
111-
id: scriptId,
112-
name: scriptName,
113-
name_en: scriptName,
114-
desc: desc || scriptName,
115-
desc_en: desc || scriptName,
116-
category: catId,
117-
file: scriptRelPath,
118-
thumbnail: null,
119-
version: 1,
120-
file_type: 'py',
121-
author: author || '社区投稿',
122-
author_en: author || 'Community',
123-
tags: ['community', catId],
124-
requires: [],
125-
min_app_version: '1.5.0',
126-
added: new Date().toISOString().split('T')[0],
127-
updated: null,
128-
status: 'active',
129-
lines: lineCount
130-
});
53+
core.info(`Found ${openPRs.length} open PR(s) to rebase.`);
13154
132-
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2) + '\n', 'utf8');
55+
let succeeded = 0;
56+
let failed = 0;
13357
134-
// ── 5. Git commit & push ──
135-
const { execSync } = require('child_process');
136-
execSync('git config user.name "github-actions[bot]"');
137-
execSync('git config user.email "github-actions[bot]@users.noreply.github.com"');
138-
execSync(`git add "${scriptFullPath}" "${indexPath}"`);
139-
execSync(`git commit -m "✅ 收录社区投稿: ${scriptName} (#${issue.number})"`);
58+
for (const pr of openPRs) {
59+
const branch = pr.head.ref;
60+
core.info(`\nRebasing PR #${pr.number}: ${pr.title} (branch: ${branch})`);
14061
141-
// 推送前拉取最新(防止与其他 workflow 微小时差冲突)
142-
for (let attempt = 0; attempt < 3; attempt++) {
14362
try {
144-
execSync('git pull --rebase origin main');
145-
execSync('git push origin main');
146-
break;
63+
// 获取远程分支
64+
execSync(`git fetch origin ${branch}:${branch}`, { stdio: 'pipe' });
65+
66+
// 切到该分支并 rebase 到最新 main
67+
execSync(`git checkout ${branch}`, { stdio: 'pipe' });
68+
execSync('git rebase origin/main', { stdio: 'pipe' });
69+
70+
// force push 更新 PR
71+
execSync(`git push origin ${branch} --force-with-lease`, { stdio: 'pipe' });
72+
73+
core.info(`✅ PR #${pr.number} rebased successfully.`);
74+
succeeded++;
75+
76+
// 切回 main 准备下一个
77+
execSync('git checkout main', { stdio: 'pipe' });
14778
} catch (e) {
148-
if (attempt === 2) throw e;
149-
execSync('sleep 3');
79+
core.warning(`❌ PR #${pr.number} rebase failed, aborting.`);
80+
failed++;
81+
82+
try {
83+
execSync('git rebase --abort', { stdio: 'pipe' });
84+
} catch (_) {}
85+
86+
execSync('git checkout main', { stdio: 'pipe' });
87+
88+
// 在 PR 上留言提示冲突
89+
await github.rest.pulls.createReview({
90+
owner: context.repo.owner,
91+
repo: context.repo.repo,
92+
pull_number: pr.number,
93+
event: 'COMMENT',
94+
body: '⚠️ 自动 rebase 失败,此 PR 存在无法自动解决的冲突,需要手动处理。'
95+
});
15096
}
15197
}
15298
153-
// ── 6. 关闭 Issue 并评论 ──
154-
await github.rest.issues.createComment({
155-
owner: context.repo.owner,
156-
repo: context.repo.repo,
157-
issue_number: issue.number,
158-
body: [
159-
`✅ **已收录!** 感谢 @${issue.user?.login || author} 的投稿。`,
160-
'',
161-
`- 📄 脚本: \`${scriptId}.py\``,
162-
`- 📁 分类: ${category}`,
163-
`- 📊 ${lineCount} 行代码`,
164-
'',
165-
'脚本将在用户下次刷新脚本库时可见。'
166-
].join('\n')
167-
});
168-
169-
await github.rest.issues.update({
170-
owner: context.repo.owner,
171-
repo: context.repo.repo,
172-
issue_number: issue.number,
173-
state: 'closed'
174-
});
175-
176-
core.info(`Successfully merged: ${scriptId} (${lineCount} lines)`);
99+
core.info(`\nDone. Succeeded: ${succeeded}, Failed: ${failed}`);

0 commit comments

Comments
 (0)