# Labels pull requests by author: # - 'community champion' for community champions # - 'bot' for bot accounts # - 'staff' for staff team members # - 'guild' for guild members # - 'first contribution' for first-time external contributors # Labels issues by author: # - 'community champion' for community champions name: PR Issue Labeler on: issues: types: [opened] pull_request_target: types: [opened] permissions: contents: read jobs: check-authorship-and-label: if: github.repository == 'zed-industries/zed' runs-on: namespace-profile-2x4-ubuntu-2404 timeout-minutes: 5 steps: - id: get-app-token uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.ZED_COMMUNITY_BOT_APP_ID }} private-key: ${{ secrets.ZED_COMMUNITY_BOT_PRIVATE_KEY }} owner: zed-industries - id: apply-authorship-label uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: github-token: ${{ steps.get-app-token.outputs.token }} script: | const BOT_LABEL = 'bot'; const STAFF_LABEL = 'staff'; const STAFF_TEAM_SLUG = 'staff'; const FIRST_CONTRIBUTION_LABEL = 'first contribution'; const GUILD_LABEL = 'guild'; // Guild cohort members are outside collaborators holding this custom // repository role, not members of an org team. const GUILD_ROLE_NAME = 'Guild Assign issues/PRs'; const COMMUNITY_CHAMPION_LABEL = 'community champion'; const COMMUNITY_CHAMPIONS = [ '0x2CA', '5brian', '5herlocked', 'abdelq', 'afgomez', 'AidanV', 'akbxr', 'AlvaroParker', 'amtoaer', 'artemevsevev', 'bajrangCoder', 'bcomnes', 'Be-ing', 'blopker', 'bnjjj', 'bobbymannino', 'CharlesChen0823', 'chbk', 'davewa', 'davidbarsky', 'ddoemonn', 'djsauble', 'errmayank', 'fantacell', 'fdncred', 'findrakecil', 'FloppyDisco', 'gko', 'huacnlee', 'imumesh18', 'injust', 'jacobtread', 'jansol', 'jeffreyguenther', 'jenslys', 'jongretar', 'KyleBarton', 'lemorage', 'lingyaochu', 'lnay', 'marcocondrache', 'marius851000', 'mikebronner', 'ognevny', 'PKief', 'playdohface', 'RemcoSmitsDev', 'rgbkrk', 'romaninsh', 'rxptr', 'Simek', 'someone13574', 'sourcefrog', 'suxiaoshao', 'Takk8IS', 'tartarughina', 'thedadams', 'tidely', 'timvermeulen', 'valentinegb', 'versecafe', 'vitallium', 'WhySoBad', 'ya7010', 'Zertsov', ]; const pr = context.payload.pull_request; const issue = context.payload.issue; const target = pr || issue; const author = target.user.login; const listIncludesAuthor = (members, author) => { const authorLower = author.toLowerCase(); return members.some((member) => member.toLowerCase() === authorLower); }; const isTeamMember = async (teamSlug, author) => { try { const response = await github.rest.teams.getMembershipForUserInOrg({ org: 'zed-industries', team_slug: teamSlug, username: author }); return response.data.state === 'active'; } catch (error) { if (error.status !== 404) { throw error; } return false; } }; const isStaffMember = (author) => isTeamMember(STAFF_TEAM_SLUG, author); const isGuildMember = async (author) => { try { const response = await github.rest.repos.getCollaboratorPermissionLevel({ owner: 'zed-industries', repo: 'zed', username: author }); // role_name is the effective (highest) role; for cohort outside // collaborators that is the custom role. Built-in roles come back // lowercased and won't match. return (response.data.role_name || '').toLowerCase() === GUILD_ROLE_NAME.toLowerCase(); } catch (error) { if (error.status !== 404) { throw error; } return false; } }; const getIssueLabels = () => { if (listIncludesAuthor(COMMUNITY_CHAMPIONS, author)) { return [COMMUNITY_CHAMPION_LABEL]; } return []; }; const getPullRequestLabels = async () => { if (target.user.type === 'Bot') { return [BOT_LABEL]; } if (await isStaffMember(author)) { return [STAFF_LABEL]; } // External contributors const labelsToAdd = []; if (listIncludesAuthor(COMMUNITY_CHAMPIONS, author)) { labelsToAdd.push(COMMUNITY_CHAMPION_LABEL); } if (await isGuildMember(author)) { labelsToAdd.push(GUILD_LABEL); } // We use inverted logic here due to a suspected GitHub bug where first-time contributors // get 'NONE' instead of 'FIRST_TIME_CONTRIBUTOR' or 'FIRST_TIMER'. // https://github.com/orgs/community/discussions/78038 // This will break if GitHub ever adds new associations. const association = pr.author_association; const knownAssociations = ['CONTRIBUTOR', 'COLLABORATOR', 'MEMBER', 'OWNER', 'MANNEQUIN']; if (knownAssociations.includes(association)) { console.log(`PR #${pr.number} by ${author}: not a first-time contributor (association: '${association}')`); } else { labelsToAdd.push(FIRST_CONTRIBUTION_LABEL); } return labelsToAdd; }; const labelsToAdd = pr ? await getPullRequestLabels() : getIssueLabels(); if (labelsToAdd.length === 0) { return; } try { await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: target.number, labels: labelsToAdd }); const targetType = pr ? 'PR' : 'issue'; const labels = labelsToAdd.map((label) => `'${label}'`).join(', '); console.log(`${targetType} #${target.number} by ${author}: labeled ${labels}`); } catch (error) { if (pr) { throw error; } console.error(`Failed to label issue #${target.number}: ${error.message}`); }