Skip to content
3 changes: 2 additions & 1 deletion .github/release-drafter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ categories:
- title: '👥 Contributors'
labels:
- 'first-time-contributor'
- 'external-contributor'
- 'repeat-contributor'
- 'org-member'

change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&'
Expand Down
210 changes: 210 additions & 0 deletions .github/workflows/setup-labels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
name: Setup Repository Labels

on:
workflow_dispatch: # Manual trigger
push:
branches: [main, master]
paths:
- '.github/workflows/setup-labels.yml'

permissions:
issues: write

jobs:
create-labels:
runs-on: ubuntu-latest
steps:
- name: Create all required labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// Define all labels with colors and descriptions
const requiredLabels = [
// ==================== CONTRIBUTOR LABELS ====================
{
name: 'org-member',
color: '0E8A16',
description: 'Member of the organization with admin/maintain permissions'
},
{
name: 'first-time-contributor',
color: '7057FF',
description: 'First PR of an external contributor'
},
{
name: 'repeat-contributor',
color: '6F42C1',
description: 'PR from an external contributor who already had PRs merged'
},

// ==================== ISSUE TRACKING LABELS ====================
{
name: 'no-issue-linked',
color: 'D73A4A',
description: 'PR is not linked to any issue'
},

// ==================== FILE TYPE LABELS ====================
{
name: 'documentation',
color: '0075CA',
description: 'Changes to documentation files'
},
{
name: 'frontend',
color: 'FEF2C0',
description: 'Changes to frontend code'
},
{
name: 'backend',
color: 'BFD4F2',
description: 'Changes to backend code'
},
{
name: 'javascript',
color: 'F1E05A',
description: 'JavaScript/TypeScript code changes'
},
{
name: 'python',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all repos contain Python code. Should we really have this label?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub auto-creates a label when we request GitHub bot to apply a label (if we not create that label), but those labels have no descriptions and all use the same color #ededed , see:

image

This setup explicitly defines a small, clean label set with proper descriptions and colors which include common language js, py

The main reason for introducing this workflow was the internal check pointing out the need for a contributor labels:
https://github.com/AOSSIE-Org/Template-Repo/actions/runs/21186875613/job/60943766803?pr=52

Language labels can always be adjusted per repo if needed

and also note that this is execute on workflow_dispatch means you need to manual trigger it for apply

color: '3572A5',
description: 'Python code changes'
},
{
name: 'configuration',
color: 'EDEDED',
description: 'Configuration file changes'
},
{
name: 'github-actions',
color: '2088FF',
description: 'GitHub Actions workflow changes'
},
{
name: 'dependencies',
color: '0366D6',
description: 'Dependency file changes'
},
{
name: 'tests',
color: 'C5DEF5',
description: 'Test file changes'
},
{
name: 'docker',
color: '0DB7ED',
description: 'Docker-related changes'
},
{
name: 'ci-cd',
color: '6E5494',
description: 'CI/CD pipeline changes'
},

// ==================== SIZE LABELS ====================
{
name: 'size/XS',
color: '00FF00',
description: 'Extra small PR (≤10 lines changed)'
},
{
name: 'size/S',
color: '77FF00',
description: 'Small PR (11-50 lines changed)'
},
{
name: 'size/M',
color: 'FFFF00',
description: 'Medium PR (51-200 lines changed)'
},
{
name: 'size/L',
color: 'FF9900',
description: 'Large PR (201-500 lines changed)'
},
{
name: 'size/XL',
color: 'FF0000',
description: 'Extra large PR (>500 lines changed)'
}
];

console.log('='.repeat(60));
console.log('🏷️ REPOSITORY LABEL SETUP');
console.log('='.repeat(60));
console.log(`Total labels to create: ${requiredLabels.length}\n`);

// Get existing labels with pagination
const existingLabels = await github.paginate(
github.rest.issues.listLabelsForRepo,
{
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
}
);

const existingLabelNames = existingLabels.map(label => label.name);

let created = 0;
let updated = 0;
let skipped = 0;
let failed = 0;

// Process each label
for (const label of requiredLabels) {
try {
if (!existingLabelNames.includes(label.name)) {
// Create new label
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description
});
console.log(`✅ Created: ${label.name} (#${label.color})`);
created++;
} else {
// Update existing label (in case color/description changed)
const existingLabel = existingLabels.find(l => l.name === label.name);
if (existingLabel.color !== label.color || existingLabel.description !== label.description) {
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description
});
console.log(`🔄 Updated: ${label.name} (#${label.color})`);
updated++;
} else {
console.log(`⏭️ Skipped: ${label.name} (already exists)`);
skipped++;
}
}
} catch (error) {
console.log(`❌ Failed: ${label.name} - ${error.message}`);
failed++;
}
}

// Summary
console.log('\n' + '='.repeat(60));
console.log('📊 SUMMARY');
console.log('='.repeat(60));
console.log(`✅ Created: ${created}`);
console.log(`🔄 Updated: ${updated}`);
console.log(`⏭️ Skipped: ${skipped}`);
console.log(`❌ Failed: ${failed}`);
console.log('='.repeat(60));

// Fail the step if any labels failed to create/update
if (failed > 0) {
core.setFailed(`Label setup failed! ${failed} label(s) could not be created or updated.`);
} else if (created > 0 || updated > 0) {
console.log('\n🎉 Label setup complete! Your repository is ready.');
} else {
console.log('\n✨ All labels are already up to date.');
}
68 changes: 36 additions & 32 deletions .github/workflows/sync-pr-labels.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Sync PR Labels

on:
pull_request:
types: [opened, reopened, synchronize, edited]
pull_request_target:
types: [opened, reopened, synchronize, edited]

Expand Down Expand Up @@ -219,37 +217,48 @@ jobs:
sizeLabel = 'size/XL';
}

console.log(`Applying size label: ${sizeLabel}`);
console.log(`Calculated size label: ${sizeLabel}`);

// Remove any existing size labels first
// Get current labels on the PR
const currentLabels = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});

const sizeLabelsToRemove = currentLabels.data
const existingSizeLabels = 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
});
// Check if the size label needs to be changed
if (existingSizeLabels.length === 1 && existingSizeLabels[0] === sizeLabel) {
console.log(`Size label ${sizeLabel} is already correct, no changes needed`);
return;
}

// Remove outdated size labels only if they differ
if (existingSizeLabels.length > 0) {
console.log(`Removing outdated size labels: ${existingSizeLabels.join(', ')}`);
for (const label of existingSizeLabels) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: label
});
}
}

// Apply the new size label
console.log(`Applying new size label: ${sizeLabel}`);
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [sizeLabel]
});

# STEP 4: Contributor-based labels
# STEP 4: Contributor-based labels(in this later we can add logic of team p as discussed on discord)
- name: Apply contributor-based labels
uses: actions/github-script@v7
env:
Expand All @@ -270,37 +279,32 @@ jobs:

const contributorLabels = [];

// Check if contributor is a member of the organization
try {
await github.rest.orgs.checkMembershipForUser({
org: context.repo.owner,
username: prAuthor
});
contributorLabels.push('member');
} catch (error) {
// Not a member
if (commits.data.length <= 1) {
contributorLabels.push('first-time-contributor');
} else {
contributorLabels.push('external-contributor');
}
}

// Check if PR author is a collaborator
// First check if maintainer
let isMaintainer = false;
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');
if (['admin', 'maintain'].includes(permissionLevel.data.permission)) {
contributorLabels.push('org-Member');
isMaintainer = true;
}
} catch (error) {
console.log('Could not check collaborator status');
}

// If not maintainer, check contributor type
if (!isMaintainer) {
if (commits.data.length <= 1) {
contributorLabels.push('first-time-contributor');
} else {
contributorLabels.push('repeat-contributor');
}
}

if (contributorLabels.length > 0) {
console.log(`Applying contributor-based labels: ${contributorLabels.join(', ')}`);

Expand Down