From 5abe4bcbc6190f3ea8397eff6dd5ddea95968790 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 28 May 2026 11:56:02 -0400 Subject: [PATCH] Merge `community_champion_auto_labeler` into `pr_labeler` (#57898) Self-Review Checklist: - [X] I've reviewed my own diff for quality, security, and reliability - [n/a] Unsafe blocks (if any) have justifying comments - [n/a] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [n/a] Tests cover the new/changed behavior - [n/a] Performance impact has been considered and is acceptable Release Notes: - N/A --- .../community_champion_auto_labeler.yml | 113 -------- .github/workflows/pr_issue_labeler.yml | 247 ++++++++++++++++++ .github/workflows/pr_labeler.yml | 150 ----------- 3 files changed, 247 insertions(+), 263 deletions(-) delete mode 100644 .github/workflows/community_champion_auto_labeler.yml create mode 100644 .github/workflows/pr_issue_labeler.yml delete mode 100644 .github/workflows/pr_labeler.yml diff --git a/.github/workflows/community_champion_auto_labeler.yml b/.github/workflows/community_champion_auto_labeler.yml deleted file mode 100644 index 82a9e274d64..00000000000 --- a/.github/workflows/community_champion_auto_labeler.yml +++ /dev/null @@ -1,113 +0,0 @@ -name: Community Champion Auto Labeler - -on: - issues: - types: [opened] - pull_request_target: - types: [opened] - -jobs: - label_community_champion: - if: github.repository_owner == 'zed-industries' - runs-on: namespace-profile-2x4-ubuntu-2404 - steps: - - name: Check if author is a community champion and apply label - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 - env: - 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 - 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 - with: - script: | - const communityChampions = process.env.COMMUNITY_CHAMPIONS - .split('\n') - .map(handle => handle.trim().toLowerCase()) - .filter(handle => handle.length > 0); - - let author; - if (context.eventName === 'issues') { - author = context.payload.issue.user.login; - } else if (context.eventName === 'pull_request_target') { - author = context.payload.pull_request.user.login; - } - - if (!author || !communityChampions.includes(author.toLowerCase())) { - return; - } - - const issueNumber = context.payload.issue?.number || context.payload.pull_request?.number; - - try { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: ['community champion'] - }); - - console.log(`Applied 'community champion' label to #${issueNumber} by ${author}`); - } catch (error) { - console.error(`Failed to apply label: ${error.message}`); - } diff --git a/.github/workflows/pr_issue_labeler.yml b/.github/workflows/pr_issue_labeler.yml new file mode 100644 index 00000000000..e27877b0a9f --- /dev/null +++ b/.github/workflows/pr_issue_labeler.yml @@ -0,0 +1,247 @@ +# 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'; + const GUILD_MEMBERS = [ + '11happy', + 'AidanV', + 'AmaanBilwar', + 'MostlyKIGuess', + 'OmChillure', + 'Palanikannan1437', + 'Shivansh-25', + 'SkandaBhat', + 'TwistingTwists', + 'YEDASAVG', + 'Ziqi-Yang', + 'alanpjohn', + 'arjunkomath', + 'austincummings', + 'ayushk-1801', + 'criticic', + 'dongdong867', + 'emamulandalib', + 'eureka928', + 'feitreim', + 'iam-liam', + 'iksuddle', + 'ishaksebsib', + 'lingyaochu', + 'loadingalias', + 'marcocondrache', + 'mchisolm0', + 'nairadithya', + 'nihalxkumar', + 'notJoon', + 'polyesterswing', + 'prayanshchh', + 'razeghi71', + 'sarmadgulzar', + 'seanstrom', + 'th0jensen', + 'tommyming', + 'transitoryangel', + 'virajbhartiya', + ]; + 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', + '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 isStaffMember = async (author) => { + try { + const response = await github.rest.teams.getMembershipForUserInOrg({ + org: 'zed-industries', + team_slug: STAFF_TEAM_SLUG, + username: author + }); + return response.data.state === 'active'; + } 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 (listIncludesAuthor(GUILD_MEMBERS, 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}`); + } diff --git a/.github/workflows/pr_labeler.yml b/.github/workflows/pr_labeler.yml deleted file mode 100644 index 9ea70385432..00000000000 --- a/.github/workflows/pr_labeler.yml +++ /dev/null @@ -1,150 +0,0 @@ -# Labels pull requests by author: 'bot' for bot accounts, 'staff' for -# staff team members, 'guild' for guild members, 'first contribution' for -# first-time external contributors. -name: PR Labeler - -on: - 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 GUILD_LABEL = 'guild'; - const FIRST_CONTRIBUTION_LABEL = 'first contribution'; - const STAFF_TEAM_SLUG = 'staff'; - const GUILD_MEMBERS = [ - '11happy', - 'AidanV', - 'AmaanBilwar', - 'MostlyKIGuess', - 'OmChillure', - 'Palanikannan1437', - 'Shivansh-25', - 'SkandaBhat', - 'TwistingTwists', - 'YEDASAVG', - 'Ziqi-Yang', - 'alanpjohn', - 'arjunkomath', - 'austincummings', - 'ayushk-1801', - 'criticic', - 'dongdong867', - 'emamulandalib', - 'eureka928', - 'feitreim', - 'iam-liam', - 'iksuddle', - 'ishaksebsib', - 'lingyaochu', - 'loadingalias', - 'marcocondrache', - 'mchisolm0', - 'nairadithya', - 'nihalxkumar', - 'notJoon', - 'polyesterswing', - 'prayanshchh', - 'razeghi71', - 'sarmadgulzar', - 'seanstrom', - 'th0jensen', - 'tommyming', - 'transitoryangel', - 'virajbhartiya', - ]; - - const pr = context.payload.pull_request; - const author = pr.user.login; - - if (pr.user.type === 'Bot') { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - labels: [BOT_LABEL] - }); - console.log(`PR #${pr.number} by ${author}: labeled '${BOT_LABEL}' (user type: '${pr.user.type}')`); - return; - } - - let isStaff = false; - try { - const response = await github.rest.teams.getMembershipForUserInOrg({ - org: 'zed-industries', - team_slug: STAFF_TEAM_SLUG, - username: author - }); - isStaff = response.data.state === 'active'; - } catch (error) { - if (error.status !== 404) { - throw error; - } - } - - if (isStaff) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - labels: [STAFF_LABEL] - }); - console.log(`PR #${pr.number} by ${author}: labeled '${STAFF_LABEL}' (staff team member)`); - return; - } - - const authorLower = author.toLowerCase(); - const isGuildMember = GUILD_MEMBERS.some( - (member) => member.toLowerCase() === authorLower - ); - if (isGuildMember) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - labels: [GUILD_LABEL] - }); - console.log(`PR #${pr.number} by ${author}: labeled '${GUILD_LABEL}' (guild member)`); - // No early return: guild members can also get 'first contribution' - } - - // 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}')`); - return; - } - - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - labels: [FIRST_CONTRIBUTION_LABEL] - }); - console.log(`PR #${pr.number} by ${author}: labeled '${FIRST_CONTRIBUTION_LABEL}' (association: '${association}')`);