[投稿] 网吧模拟器(3.1) — by Daoyu268 #65
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: '⚠️ 自动重建失败,可能需要手动处理。' | |
| }); | |
| } | |
| } |