-
Notifications
You must be signed in to change notification settings - Fork 12
213 lines (183 loc) · 8.77 KB
/
Copy pathapprove_submission.yml
File metadata and controls
213 lines (183 loc) · 8.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
name: Rebuild open submission PRs after merge
on:
pull_request:
types: [closed]
branches: [main]
concurrency:
group: rebuild-submissions
cancel-in-progress: false
jobs:
rebuild:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 1
token: ${{ secrets.GITHUB_TOKEN }}
- name: Rebuild open PRs onto latest main
uses: actions/github-script@v7
with:
script: |
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
execSync('git config user.name "github-actions[bot]"');
execSync('git config user.email "github-actions[bot]@users.noreply.github.com"');
// 获取所有 open 的 PR
const { data: openPRs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
base: 'main',
sort: 'created',
direction: 'asc'
});
if (openPRs.length === 0) {
core.info('No open PRs to rebuild.');
return;
}
core.info(`Found ${openPRs.length} open PR(s) to rebuild.`);
for (const pr of openPRs) {
const branch = pr.head.ref;
core.info(`\nRebuilding PR #${pr.number}: ${pr.title} (branch: ${branch})`);
try {
// 1. 获取 PR 的文件变更列表
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number
});
// 2. 找到新增的脚本文件(排除 index.json)
const scriptFiles = files.filter(f =>
f.status === 'added' && f.filename !== 'script_library/index.json'
);
if (scriptFiles.length === 0) {
core.warning(`PR #${pr.number}: No new script files found, skipping.`);
continue;
}
// 3. 从 PR 分支获取每个脚本文件的内容
const scriptContents = [];
for (const sf of scriptFiles) {
try {
const { data: fileData } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: sf.filename,
ref: branch
});
const content = Buffer.from(fileData.content, 'base64').toString('utf8');
scriptContents.push({ path: sf.filename, content });
} catch (e) {
core.warning(`PR #${pr.number}: Failed to read ${sf.filename}: ${e.message}`);
}
}
if (scriptContents.length === 0) {
core.warning(`PR #${pr.number}: Could not read script content, skipping.`);
continue;
}
// 4. 从 PR body 中提取元数据
const body = pr.body || '';
const field = (key) => {
const re = new RegExp(`${key}[::]\\s*(.+)`, 'i');
const m = body.match(re);
return m ? m[1].trim() : '';
};
const author = field('作者') || field('Author') || '社区投稿';
const category = field('分类') || field('Category') || 'basic';
const desc = field('描述') || field('Description') || pr.title;
const scriptName = pr.title.replace(/^\[投稿[::]\s*\w+\]\s*/, '').replace(/^\[投稿\]\s*/, '').trim() || 'untitled';
const catMap = {
'基础脚本': 'basic', 'basic': 'basic',
'UI 界面': 'ui', 'UI': 'ui', 'ui': 'ui',
'游戏': 'games', 'Games': 'games', 'games': 'games',
'小组件': 'widgets', 'Widgets': 'widgets', 'widgets': 'widgets',
'其他': 'other', 'Other': 'other', 'other': 'other'
};
let catId = catMap[category] || 'basic';
// 4b. 基于代码关键词自动修正分类
const codeForClassify = scriptContents.map(s => s.content).join('\n');
if (/\bimport\s+scene\b|from\s+scene\s+import\b|scene\.run\s*\(/.test(codeForClassify)) {
catId = 'games';
core.info(`PR #${pr.number}: Auto-classified as "games" (detected scene module)`);
} else if (/\bimport\s+ui\b|from\s+ui\s+import\b|\.present\s*\(/.test(codeForClassify)) {
catId = 'ui';
core.info(`PR #${pr.number}: Auto-classified as "ui" (detected ui module)`);
} else if (/from\s+widget\s+import\b|__WIDGET_LAYOUT__|Widget\s*\(/.test(codeForClassify)) {
catId = 'widgets';
core.info(`PR #${pr.number}: Auto-classified as "widgets" (detected widget module)`);
}
// 5. 基于最新 main 重建分支
execSync('git checkout main', { stdio: 'pipe' });
execSync('git pull origin main', { stdio: 'pipe' });
try {
execSync(`git branch -D ${branch}`, { stdio: 'pipe' });
} catch (_) {}
execSync(`git checkout -b ${branch}`, { stdio: 'pipe' });
// 6. 写入脚本文件
for (const sc of scriptContents) {
const dir = path.dirname(sc.path);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(sc.path, sc.content, 'utf8');
}
// 7. 读取最新 index.json 并添加条目
const indexPath = 'script_library/index.json';
const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
const safeId = scriptName
.toLowerCase()
.replace(/[\s]+/g, '_')
.replace(/[^a-z0-9_\u4e00-\u9fff]/g, '')
.substring(0, 40) || `submission_${pr.number}`;
const scriptId = `community_${safeId}`;
// 去重:如果已存在则跳过
if (!index.scripts.some(s => s.id === scriptId)) {
const mainScript = scriptContents[0];
const lineCount = mainScript.content.split('\n').length;
const fileType = path.extname(mainScript.path).replace('.', '') || 'py';
index.data_version += 1;
index.updated = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
index.scripts.push({
id: scriptId,
name: scriptName,
name_en: scriptName,
desc: desc,
desc_en: desc,
category: catId,
file: mainScript.path.replace('script_library/', ''),
thumbnail: null,
version: 1,
file_type: fileType,
author: author,
author_en: author,
tags: ['community', catId],
requires: [],
min_app_version: '1.5.0',
added: new Date().toISOString().split('T')[0],
updated: null,
status: 'active',
lines: lineCount
});
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2) + '\n', 'utf8');
}
// 8. 提交并 force push
execSync(`git add -A`, { stdio: 'pipe' });
execSync(`git commit -m "投稿: ${scriptName}" --allow-empty`, { stdio: 'pipe' });
execSync(`git push origin ${branch} --force`, { stdio: 'pipe' });
core.info(`✅ PR #${pr.number} rebuilt successfully on latest main.`);
// 切回 main
execSync('git checkout main', { stdio: 'pipe' });
} catch (e) {
core.warning(`❌ PR #${pr.number} rebuild failed: ${e.message}`);
try { execSync('git checkout main', { stdio: 'pipe' }); } catch (_) {}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: '⚠️ 自动重建失败,可能需要手动处理。'
});
}
}