feat(settings): add theme preset selection support #2884
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: GSSoC Label Automation | |
| on: | |
| pull_request_target: | |
| types: [opened, reopened, synchronize] | |
| pull_request_review: | |
| types: [submitted] | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| issues: write | |
| jobs: | |
| auto-type-label: | |
| name: Auto-apply type labels | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request_target' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect and apply type labels | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const { owner, repo } = context.repo; | |
| const prNumber = pr.number; | |
| const title = pr.title.toLowerCase(); | |
| const body = (pr.body || '').toLowerCase(); | |
| // Fetch changed files | |
| const filesRes = await github.rest.pulls.listFiles({ | |
| owner, repo, pull_number: prNumber, per_page: 100 | |
| }); | |
| const files = filesRes.data.map(f => f.filename.toLowerCase()); | |
| const labelsToAdd = new Set(); | |
| // --- Type detection from title --- | |
| if (/^fix|^bug|hotfix/.test(title)) labelsToAdd.add('type:bug'); | |
| if (/^feat|^feature|add |implement/.test(title)) labelsToAdd.add('type:feature'); | |
| if (/^docs|^doc|readme|changelog|contributing/.test(title)) labelsToAdd.add('type:docs'); | |
| if (/^refactor|cleanup|clean up|reorgani/.test(title)) labelsToAdd.add('type:refactor'); | |
| if (/test|spec|coverage/.test(title)) labelsToAdd.add('type:testing'); | |
| if (/perf|performance|optimi|speed|latency/.test(title)) labelsToAdd.add('type:performance'); | |
| if (/a11y|accessibility|aria|wcag|screen.?reader/.test(title)) labelsToAdd.add('type:accessibility'); | |
| if (/security|auth|cve|xss|injection|csrf/.test(title)) labelsToAdd.add('type:security'); | |
| if (/ci|cd|deploy|docker|kubernetes|workflow|pipeline|devops/.test(title)) labelsToAdd.add('type:devops'); | |
| if (/design|ui|ux|theme|style|layout|responsive|dark.?mode/.test(title)) labelsToAdd.add('type:design'); | |
| // --- Type detection from files changed --- | |
| const hasMarkdown = files.some(f => f.endsWith('.md') || f.endsWith('.mdx')); | |
| const hasTests = files.some(f => | |
| f.includes('.test.') || f.includes('.spec.') || | |
| f.includes('__tests__') || f.includes('/tests/') | |
| ); | |
| const hasWorkflow = files.some(f => f.includes('.github/workflows')); | |
| const hasSecurity = files.some(f => | |
| f.includes('auth') || f.includes('middleware') || f.includes('security') | |
| ); | |
| const hasStyle = files.some(f => | |
| f.endsWith('.css') || f.endsWith('.scss') || | |
| f.includes('globals') || f.includes('tailwind') | |
| ); | |
| if (hasMarkdown) labelsToAdd.add('type:docs'); | |
| if (hasTests) labelsToAdd.add('type:testing'); | |
| if (hasWorkflow) labelsToAdd.add('type:devops'); | |
| if (hasSecurity && !labelsToAdd.has('type:bug') && !labelsToAdd.has('type:feature')) { | |
| labelsToAdd.add('type:security'); | |
| } | |
| if (hasStyle && !labelsToAdd.has('type:bug')) labelsToAdd.add('type:design'); | |
| // Fetch current labels | |
| const currentLabels = pr.labels.map(l => l.name); | |
| // Always add gssoc26 to every PR | |
| if (!currentLabels.includes('gssoc26')) labelsToAdd.add('gssoc26'); | |
| // Only add labels not already present | |
| const newLabels = [...labelsToAdd].filter(l => !currentLabels.includes(l)); | |
| if (newLabels.length > 0) { | |
| await github.rest.issues.addLabels({ | |
| owner, repo, issue_number: prNumber, labels: newLabels | |
| }); | |
| console.log(`Added labels: ${newLabels.join(', ')}`); | |
| } | |
| // Auto-assign PR author to PR | |
| const creator = pr.user.login; | |
| const currentAssignees = pr.assignees.map(a => a.login); | |
| if (!currentAssignees.includes(creator)) { | |
| await github.rest.issues.addAssignees({ | |
| owner, | |
| repo, | |
| issue_number: prNumber, | |
| assignees: [creator] | |
| }); | |
| console.log(`Assigned PR #${prNumber} to author @${creator}`); | |
| } | |
| - name: Post admin checklist comment | |
| uses: actions/github-script@v7 | |
| if: github.event.action == 'opened' | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const { owner, repo } = context.repo; | |
| const body = `## GSSoC Label Checklist 🏷️ | |
| @${context.repo.owner} — please apply the appropriate labels before merging: | |
| **Difficulty** (pick one): | |
| - [ ] \`level:beginner\` — 20 pts | |
| - [ ] \`level:intermediate\` — 35 pts | |
| - [ ] \`level:advanced\` — 55 pts | |
| - [ ] \`level:critical\` — 80 pts | |
| **Quality** (optional): | |
| - [ ] \`quality:clean\` — ×1.2 multiplier | |
| - [ ] \`quality:exceptional\` — ×1.5 multiplier | |
| **Validation** (required to score): | |
| - [ ] \`gssoc:approved\` — counts for points | |
| - [ ] \`gssoc:invalid\` / \`gssoc:spam\` / \`gssoc:ai-slop\` — does not score | |
| > Type labels (\`type:*\`) are auto-detected from files and title. Review and adjust if needed. | |
| > Points formula: \`(difficulty × quality_multiplier) + type_bonus\``; | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: pr.number, body | |
| }); | |
| notify-on-approve: | |
| name: Remind admin to add gssoc:approved | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'pull_request_review' && | |
| github.event.review.state == 'approved' | |
| steps: | |
| - name: Check for gssoc:approved label | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const { owner, repo } = context.repo; | |
| const hasApproved = pr.labels.some(l => l.name === 'gssoc:approved'); | |
| const hasDifficulty = pr.labels.some(l => l.name.startsWith('level:')); | |
| if (!hasApproved || !hasDifficulty) { | |
| const missing = []; | |
| if (!hasDifficulty) missing.push('a `level:*` difficulty label'); | |
| if (!hasApproved) missing.push('`gssoc:approved`'); | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: pr.number, | |
| body: `PR is approved but missing ${missing.join(' and ')} — add before merging so it scores in GSSoC. 🏷️` | |
| }); | |
| } |