diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 5df0eb4..e5a173b 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -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: '\<*_&' diff --git a/.github/workflows/setup-labels.yml b/.github/workflows/setup-labels.yml new file mode 100644 index 0000000..fb98bf8 --- /dev/null +++ b/.github/workflows/setup-labels.yml @@ -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', + 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.'); + } diff --git a/.github/workflows/sync-pr-labels.yml b/.github/workflows/sync-pr-labels.yml index 8e137cf..b253334 100644 --- a/.github/workflows/sync-pr-labels.yml +++ b/.github/workflows/sync-pr-labels.yml @@ -1,8 +1,6 @@ name: Sync PR Labels on: - pull_request: - types: [opened, reopened, synchronize, edited] pull_request_target: types: [opened, reopened, synchronize, edited] @@ -219,29 +217,40 @@ 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, @@ -249,7 +258,7 @@ jobs: 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: @@ -270,23 +279,8 @@ 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, @@ -294,13 +288,23 @@ jobs: 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(', ')}`);