fix(templates): add pnpm.onlyBuiltDependencies for native addons #798
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: PR Triage | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize, reopened] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| label: | |
| name: Label PR | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Triage PR | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const labels = new Set(); | |
| // --- Bot detection --- | |
| const botLogins = ['dependabot[bot]', 'renovate[bot]', 'emdashbot[bot]']; | |
| if (botLogins.includes(pr.user.login)) { | |
| labels.add('bot'); | |
| } | |
| // --- Size labels --- | |
| const { data: files } = await github.rest.pulls.listFiles({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| per_page: 100, | |
| }); | |
| const linesChanged = files.reduce((sum, f) => sum + f.additions + f.deletions, 0); | |
| const sizeLabels = ['size/XS', 'size/S', 'size/M', 'size/L', 'size/XL']; | |
| let sizeLabel; | |
| if (linesChanged < 10) sizeLabel = 'size/XS'; | |
| else if (linesChanged < 50) sizeLabel = 'size/S'; | |
| else if (linesChanged < 200) sizeLabel = 'size/M'; | |
| else if (linesChanged < 500) sizeLabel = 'size/L'; | |
| else sizeLabel = 'size/XL'; | |
| labels.add(sizeLabel); | |
| // --- Area labels --- | |
| const areaMap = { | |
| 'area/core': (f) => f.startsWith('packages/core/'), | |
| 'area/admin': (f) => f.startsWith('packages/admin/'), | |
| 'area/plugins': (f) => f.startsWith('packages/plugins/'), | |
| 'area/docs': (f) => f.startsWith('docs/'), | |
| 'area/templates': (f) => f.startsWith('templates/'), | |
| 'area/ci': (f) => f.startsWith('.github/'), | |
| 'area/auth': (f) => f.startsWith('packages/auth/'), | |
| 'area/cloudflare': (f) => f.startsWith('packages/cloudflare/'), | |
| }; | |
| for (const file of files) { | |
| for (const [label, matcher] of Object.entries(areaMap)) { | |
| if (matcher(file.filename)) { | |
| labels.add(label); | |
| } | |
| } | |
| } | |
| // --- Merge conflict detection --- | |
| // mergeable is available on the full PR object (may need a separate fetch | |
| // since the webhook payload sometimes has mergeable=null while computing) | |
| try { | |
| const { data: fullPr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| }); | |
| if (fullPr.mergeable === false) { | |
| labels.add('needs-rebase'); | |
| } | |
| } catch { | |
| // Ignore -- mergeable state may not be computed yet | |
| } | |
| // CLA labels are managed by the CLA workflow (cla.yml), not here. | |
| // --- Ensure all labels exist, then apply --- | |
| const labelColors = { | |
| 'bot': 'ededed', | |
| 'size/XS': '3cbf00', | |
| 'size/S': '5dba3f', | |
| 'size/M': 'fbca04', | |
| 'size/L': 'ee9b00', | |
| 'size/XL': 'd93f0b', | |
| 'area/core': '0052cc', | |
| 'area/admin': '7057ff', | |
| 'area/plugins': '008672', | |
| 'area/docs': '0075ca', | |
| 'area/templates': 'bfdadc', | |
| 'area/ci': '000000', | |
| 'area/auth': 'd4c5f9', | |
| 'area/cloudflare': 'f9a825', | |
| 'needs-rebase': 'e11d48', | |
| }; | |
| // Get existing labels on the repo | |
| const existingLabels = new Set(); | |
| for await (const response of github.paginate.iterator( | |
| github.rest.issues.listLabelsForRepo, | |
| { owner: context.repo.owner, repo: context.repo.repo, per_page: 100 } | |
| )) { | |
| for (const label of response.data) { | |
| existingLabels.add(label.name); | |
| } | |
| } | |
| // Create any missing labels | |
| for (const label of labels) { | |
| if (!existingLabels.has(label) && labelColors[label]) { | |
| await github.rest.issues.createLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: label, | |
| color: labelColors[label], | |
| }); | |
| } | |
| } | |
| // Get current labels on the PR to remove stale ones | |
| const currentLabels = new Set(pr.labels.map(l => l.name)); | |
| // Helper: remove a label, ignoring 404 if it's already gone | |
| async function safeRemoveLabel(name) { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| name, | |
| }); | |
| } catch (e) { | |
| if (e.status !== 404) throw e; | |
| } | |
| } | |
| // Remove stale size labels (only one size label at a time) | |
| for (const sl of sizeLabels) { | |
| if (sl !== sizeLabel && currentLabels.has(sl)) { | |
| await safeRemoveLabel(sl); | |
| } | |
| } | |
| // Remove needs-rebase if PR is now mergeable | |
| if (!labels.has('needs-rebase') && currentLabels.has('needs-rebase')) { | |
| await safeRemoveLabel('needs-rebase'); | |
| } | |
| // Add new labels | |
| const toAdd = [...labels].filter(l => !currentLabels.has(l)); | |
| if (toAdd.length > 0) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels: toAdd, | |
| }); | |
| } | |
| core.info(`Applied labels: ${[...labels].join(', ')}`); | |
| // --- Scope guard: comment on oversized PRs --- | |
| // Only on open (not synchronize) to avoid spamming on every push | |
| if (context.payload.action === 'opened') { | |
| const warnings = []; | |
| if (linesChanged > 500) { | |
| warnings.push(`This PR changes **${linesChanged.toLocaleString()} lines** across **${files.length} files**. Large PRs are harder to review and more likely to be closed without review.`); | |
| } else if (files.length > 20) { | |
| warnings.push(`This PR touches **${files.length} files**. PRs with a broad scope are harder to review. Please confirm the scope hasn't drifted beyond the intended change.`); | |
| } | |
| // Check for cross-area changes (touching multiple packages) | |
| const areas = Object.keys(areaMap).filter(a => labels.has(a)); | |
| if (areas.length > 3) { | |
| warnings.push(`This PR spans ${areas.length} different areas (${areas.join(', ')}). Consider breaking it into smaller, focused PRs.`); | |
| } | |
| if (warnings.length > 0) { | |
| const marker = '<!-- scope-guard -->'; | |
| const body = [ | |
| marker, | |
| '## Scope check', | |
| '', | |
| ...warnings, | |
| '', | |
| 'If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.', | |
| '', | |
| 'See [CONTRIBUTING.md](https://github.com/emdash-cms/emdash/blob/main/CONTRIBUTING.md) for contribution guidelines.', | |
| ].join('\n'); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body, | |
| }); | |
| } | |
| } |