Skip to content

Feat/home page

Feat/home page #467

Workflow file for this run

name: Sync PR Labels
on:
pull_request_target:
types: [opened, reopened, synchronize, edited]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
sync-labels:
if: ${{ github.repository_owner == 'AOSSIE-Org' }}
runs-on: ubuntu-latest
steps:
# STEP 0: Ensure all labels exist with correct colors
- name: Upsert colored labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const labels = [
{ name: 'documentation', color: '0075ca', description: 'Documentation updates' },
{ name: 'enhancement', color: 'a2eeef', description: 'New feature or request' },
{ name: 'frontend', color: '7057ff', description: 'Frontend changes' },
{ name: 'javascript', color: 'f0e040', description: 'JavaScript/TypeScript changes' },
{ name: 'dependencies', color: 'e4e669', description: 'Dependency updates' },
{ name: 'configuration', color: 'f9d0c4', description: 'Config file changes' },
{ name: 'good first issue', color: '7cfc00', description: 'Good for newcomers' },
{ name: 'first-time-contributor', color: 'ff9500', description: 'First time contributor' },
{ name: 'no-issue-linked', color: 'd93f0b', description: 'PR has no linked issue' },
{ name: 'size/XS', color: '3cbf00', description: '1-10 lines changed' },
{ name: 'size/S', color: '5d9801', description: '11-50 lines changed' },
{ name: 'size/M', color: 'ffd700', description: '51-200 lines changed' },
{ name: 'size/L', color: 'ff8c00', description: '201-500 lines changed' },
{ name: 'size/XL', color: 'e11d48', description: '500+ lines changed' },
{ name: 'ci-cd', color: '00c0ef', description: 'CI/CD changes' },
{ name: 'github-actions', color: '0052cc', description: 'GitHub Actions changes' },
{ name: 'backend', color: 'c5def5', description: 'Backend changes' },
{ name: 'python', color: 'ffe066', description: 'Python changes' },
{ name: 'tests', color: 'bfd4f2', description: 'Test changes' },
{ name: 'docker', color: '0db7ed', description: 'Docker changes' },
{ name: 'member', color: '006b75', description: 'Org member' },
{ name: 'external-contributor', color: 'e6e6e6', description: 'External contributor' },
{ name: 'maintainer', color: 'b60205', description: 'Maintainer' },
];
for (const label of labels) {
try {
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description
});
} catch (e) {
if (e.status === 404) {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description
});
} else {
throw e;
}
}
}
# STEP 1: Issue-based labels
- name: Get PR details
id: pr-details
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request;
return {
number: pr.number,
body: pr.body || '',
base: pr.base.ref,
head: pr.head.ref
};
- name: Extract linked issue number
id: extract-issue
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prBody = context.payload.pull_request.body || '';
// Match patterns: Fixes #123, Closes #123, Resolves #123, etc.
const issuePatterns = [
/(?:fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved)\s+#(\d+)/gi,
/#(\d+)/g
];
let issueNumber = null;
for (const pattern of issuePatterns) {
const match = prBody.match(pattern);
if (match) {
const numbers = match.map(m => m.match(/\d+/)[0]);
issueNumber = numbers[0];
break;
}
}
core.setOutput('issue_number', issueNumber || '');
return issueNumber;
- name: Apply issue-based labels
if: steps.extract-issue.outputs.issue_number != ''
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issueNumber = '${{ steps.extract-issue.outputs.issue_number }}';
const prNumber = context.payload.pull_request.number;
try {
const issue = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(issueNumber)
});
const issueLabels = issue.data.labels.map(label =>
typeof label === 'string' ? label : label.name
);
if (issueLabels.length > 0) {
console.log(`Applying issue-based labels: ${issueLabels.join(', ')}`);
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: issueLabels
});
}
} catch (error) {
console.log(`Error fetching issue #${issueNumber}: ${error.message}`);
}
- name: Mark no issue linked
if: steps.extract-issue.outputs.issue_number == ''
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
console.log('No issue linked to this PR');
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: ['no-issue-linked']
});
# STEP 2: File-based labels
- name: Get changed files
id: changed-files
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const files = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const changedFiles = files.data.map(file => file.filename);
core.setOutput('files', JSON.stringify(changedFiles));
return changedFiles;
- name: Apply file-based labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const changedFiles = JSON.parse('${{ steps.changed-files.outputs.files }}');
const fileLabels = [];
const labelMappings = {
'documentation': ['.md', 'README', 'CONTRIBUTING', 'LICENSE', '.txt'],
'frontend': ['.html', '.css', '.scss', '.jsx', '.tsx', '.vue'],
'backend': ['.py', '.java', '.go', '.rb', '.php', '.rs'],
'javascript': ['.js', '.ts', '.jsx', '.tsx'],
'python': ['.py'],
'configuration': ['.yml', '.yaml', '.json', '.toml', '.ini', '.env', '.config'],
'github-actions': ['.github/workflows/'],
'dependencies': ['package.json', 'requirements.txt', 'Gemfile', 'Cargo.toml', 'go.mod', 'pom.xml'],
'tests': ['test/', '__tests__/', '.test.', '.spec.', '_test.'],
'docker': ['Dockerfile', 'docker-compose', '.dockerignore'],
'ci-cd': ['.github/', '.gitlab-ci', 'Jenkinsfile', '.circleci']
};
for (const file of changedFiles) {
for (const [label, patterns] of Object.entries(labelMappings)) {
for (const pattern of patterns) {
if (file.includes(pattern) || file.endsWith(pattern)) {
if (!fileLabels.includes(label)) {
fileLabels.push(label);
}
}
}
}
}
if (fileLabels.length > 0) {
console.log(`Applying file-based labels: ${fileLabels.join(', ')}`);
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: fileLabels
});
} else {
console.log('No file-based labels matched');
}
# STEP 3: PR size labels
- name: Apply PR size label
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const additions = pr.data.additions;
const deletions = pr.data.deletions;
const totalChanges = additions + deletions;
console.log(`PR has ${additions} additions and ${deletions} deletions (${totalChanges} total changes)`);
let sizeLabel = '';
if (totalChanges <= 10) {
sizeLabel = 'size/XS';
} else if (totalChanges <= 50) {
sizeLabel = 'size/S';
} else if (totalChanges <= 200) {
sizeLabel = 'size/M';
} else if (totalChanges <= 500) {
sizeLabel = 'size/L';
} else {
sizeLabel = 'size/XL';
}
console.log(`Applying size label: ${sizeLabel}`);
const currentLabels = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const sizeLabelsToRemove = currentLabels.data
.map(label => label.name)
.filter(name => name.startsWith('size/'));
for (const label of sizeLabelsToRemove) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: label
});
}
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [sizeLabel]
});
# STEP 4: Contributor-based labels
- name: Apply contributor-based labels
uses: actions/github-script@v7
env:
LABELLER_TOKEN: ${{ secrets.EXTERNAL_LABELLER_TOKEN || secrets.GITHUB_TOKEN }}
with:
github-token: ${{ env.LABELLER_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const prAuthor = context.payload.pull_request.user.login;
try {
const commits = await github.rest.repos.listCommits({
owner: context.repo.owner,
repo: context.repo.repo,
author: prAuthor
});
const contributorLabels = [];
try {
await github.rest.orgs.checkMembershipForUser({
org: context.repo.owner,
username: prAuthor
});
contributorLabels.push('member');
} catch (error) {
if (commits.data.length <= 1) {
contributorLabels.push('first-time-contributor');
} else {
contributorLabels.push('external-contributor');
}
}
try {
const permissionLevel = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: prAuthor
});
if (permissionLevel.data.permission === 'admin' || permissionLevel.data.permission === 'maintain') {
contributorLabels.push('maintainer');
}
} catch (error) {
console.log('Could not check collaborator status');
}
if (contributorLabels.length > 0) {
console.log(`Applying contributor-based labels: ${contributorLabels.join(', ')}`);
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: contributorLabels
});
}
} catch (error) {
console.log(`Error applying contributor labels: ${error.message}`);
}
# Summary step
- name: Label sync summary
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const pr = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const currentLabels = pr.data.labels.map(label => label.name);
console.log('='.repeat(50));
console.log('PR Label Sync Complete');
console.log('='.repeat(50));
console.log(`Current labels on PR #${prNumber}:`);
console.log(currentLabels.join(', ') || 'No labels');
console.log('='.repeat(50));