ci: test release workflow with pnpm version consistency fix #5
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
# Automated Pull Request Labeling | |
# | |
# Purpose: Automatically applies labels to pull requests based on: | |
# 1. File paths changed (using .github/labeler.yml config) | |
# 2. PR title format (conventional commits) | |
# 3. Community contribution status | |
# | |
# Triggers: | |
# - PR opened | |
# - PR synchronized (new commits pushed) | |
# | |
# Features: | |
# - Path-based labeling (frontend, backend, docs, etc.) | |
# - Type detection from conventional commit format | |
# - Priority detection from keywords | |
# - Breaking change detection | |
# - Community contribution identification | |
# | |
# Security Note: | |
# Uses pull_request_target for write permissions on PRs from forks | |
name: Label Pull Requests | |
on: | |
pull_request_target: # Allows labeling PRs from forks | |
types: [opened, synchronize] | |
# Prevent multiple labeling runs for the same PR | |
concurrency: | |
group: label-pr-${{ github.event.pull_request.number }} | |
cancel-in-progress: true | |
jobs: | |
label: | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read # Read repository content | |
pull-requests: write # Add labels to PRs | |
steps: | |
# Debug: Log PR files changed | |
- name: Debug - List changed files | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
console.log('=== Debug: PR File Changes ==='); | |
console.log(`PR #${context.payload.pull_request.number} - ${context.payload.pull_request.title}`); | |
try { | |
const files = await github.rest.pulls.listFiles({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
pull_number: context.payload.pull_request.number | |
}); | |
console.log(`Total files changed: ${files.data.length}`); | |
files.data.forEach(file => { | |
console.log(`- ${file.filename} (${file.status})`); | |
}); | |
} catch (error) { | |
console.error('Failed to list files:', error.message); | |
} | |
console.log('=============================='); | |
# Apply labels based on file paths changed | |
# Configuration is in .github/labeler.yml | |
- name: Label based on file paths | |
uses: actions/labeler@v5 | |
with: | |
repo-token: "${{ secrets.GITHUB_TOKEN }}" | |
configuration-path: .github/labeler.yml # Maps file patterns to labels | |
sync-labels: true # Remove labels when files no longer match | |
# Debug: Check labels after path-based labeling | |
- name: Debug - Check labels after path labeling | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
console.log('=== Debug: Labels after path-based labeling ==='); | |
try { | |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: context.payload.pull_request.number | |
}); | |
if (labels.length > 0) { | |
console.log('Current labels:'); | |
labels.forEach(label => { | |
console.log(`- ${label.name}`); | |
}); | |
} else { | |
console.log('No labels applied yet'); | |
} | |
} catch (error) { | |
console.error('Failed to list labels:', error.message); | |
} | |
console.log('=============================================='); | |
# Parse PR title for conventional commit type and apply appropriate labels | |
- name: Label based on PR title | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const title = context.payload.pull_request.title.toLowerCase(); | |
const labels = []; | |
// Type labels based on conventional commit format | |
// Support both regular format (feat:) and breaking change format (feat!:) | |
// e.g., "feat: add new feature" -> "type: feature" label | |
// e.g., "fix!: breaking change" -> "type: fix" label | |
if (title.startsWith('feat:') || title.startsWith('feat(') || title.startsWith('feat!:')) { | |
labels.push('type: feature'); | |
} else if (title.startsWith('fix:') || title.startsWith('fix(') || title.startsWith('fix!:')) { | |
labels.push('type: fix'); | |
} else if (title.startsWith('docs:') || title.startsWith('docs(') || title.startsWith('docs!:')) { | |
labels.push('type: documentation'); | |
} else if (title.startsWith('chore:') || title.startsWith('chore(') || title.startsWith('chore!:')) { | |
labels.push('type: chore'); | |
} else if (title.startsWith('test:') || title.startsWith('test(') || title.startsWith('test!:')) { | |
labels.push('type: test'); | |
} else if (title.startsWith('refactor:') || title.startsWith('refactor(') || title.startsWith('refactor!:')) { | |
labels.push('type: refactor'); | |
} else if (title.startsWith('perf:') || title.startsWith('perf(') || title.startsWith('perf!:')) { | |
labels.push('type: performance'); | |
} else if (title.startsWith('ci:') || title.startsWith('ci(') || title.startsWith('ci!:')) { | |
labels.push('type: ci'); | |
} else if (title.startsWith('build:') || title.startsWith('build(') || title.startsWith('build!:')) { | |
labels.push('type: build'); | |
} | |
// Priority labels based on keywords in title | |
if (title.includes('critical') || title.includes('urgent')) { | |
labels.push('priority: critical'); | |
} else if (title.includes('high priority')) { | |
labels.push('priority: high'); | |
} | |
// Breaking change label - look for ! after type or 'breaking' keyword | |
// e.g., "feat!: change API" or "feat(api): breaking change" | |
if (title.includes('!') || title.includes('breaking')) { | |
labels.push('breaking change'); | |
} | |
// Log detected labels for debugging | |
console.log('=== Debug: Title-based Labels ==='); | |
console.log(`Title: "${context.payload.pull_request.title}"`); | |
console.log(`Detected labels: ${labels.join(', ') || 'none'}`); | |
console.log('================================='); | |
// Apply all identified labels to the PR with retry logic | |
if (labels.length > 0) { | |
const maxRetries = 3; | |
let retryCount = 0; | |
while (retryCount < maxRetries) { | |
try { | |
await github.rest.issues.addLabels({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: context.payload.pull_request.number, | |
labels: labels | |
}); | |
console.log(`Successfully added labels: ${labels.join(', ')}`); | |
break; // Success, exit retry loop | |
} catch (error) { | |
retryCount++; | |
console.warn(`Attempt ${retryCount}/${maxRetries} failed: ${error.message}`); | |
if (error.status === 403) { | |
console.warn('Permission denied - some labels may need to be created manually'); | |
break; // Don't retry permission errors | |
} | |
if (retryCount < maxRetries) { | |
console.log(`Retrying in ${retryCount * 2} seconds...`); | |
await new Promise(resolve => setTimeout(resolve, retryCount * 2000)); | |
} else { | |
console.error(`Failed to add labels after ${maxRetries} attempts`); | |
} | |
} | |
} | |
} | |
# Identify and label PRs from community contributors (non-org members) | |
- name: Check for community contribution | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const author = context.payload.pull_request.user.login; | |
const org = context.repo.owner; | |
console.log('=== Debug: Community Contribution Check ==='); | |
console.log(`PR Author: ${author}`); | |
console.log(`Organization: ${org}`); | |
try { | |
// Check if the PR author is a member of the organization | |
await github.rest.orgs.checkMembershipForUser({ | |
org: org, | |
username: author | |
}); | |
// If no error, user is an org member - no label needed | |
console.log(`${author} is an organization member - no community label needed`); | |
} catch (error) { | |
// 404 error means user is not an org member | |
if (error.status === 404) { | |
console.log(`${author} is NOT an organization member - adding community label`); | |
// Add community contribution label with retry logic | |
const maxRetries = 3; | |
let retryCount = 0; | |
while (retryCount < maxRetries) { | |
try { | |
await github.rest.issues.addLabels({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: context.payload.pull_request.number, | |
labels: ['community contribution'] | |
}); | |
console.log('Successfully added community contribution label'); | |
break; | |
} catch (labelError) { | |
retryCount++; | |
console.warn(`Attempt ${retryCount}/${maxRetries} failed: ${labelError.message}`); | |
if (labelError.status === 403) { | |
console.warn('Permission denied - label may need to be created manually'); | |
break; | |
} | |
if (retryCount < maxRetries) { | |
console.log(`Retrying in ${retryCount * 2} seconds...`); | |
await new Promise(resolve => setTimeout(resolve, retryCount * 2000)); | |
} | |
} | |
} | |
} else { | |
console.error(`Error checking membership: ${error.message}`); | |
} | |
} | |
console.log('=========================================='); | |
# Final debug: Show all labels applied | |
- name: Debug - Final label summary | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
console.log('=== Debug: Final Label Summary ==='); | |
console.log(`PR #${context.payload.pull_request.number}: ${context.payload.pull_request.title}`); | |
try { | |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: context.payload.pull_request.number | |
}); | |
if (labels.length > 0) { | |
console.log(`Total labels applied: ${labels.length}`); | |
console.log('Final labels:'); | |
labels.forEach(label => { | |
console.log(`- ${label.name} (${label.description || 'no description'})`); | |
}); | |
} else { | |
console.log('WARNING: No labels were applied to this PR'); | |
} | |
} catch (error) { | |
console.error('Failed to get final labels:', error.message); | |
} | |
console.log('=================================='); |