├── .all-contributorsrc ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── pr-labeler.yml ├── .gitignore ├── .licensed.yml ├── .node-version ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── __tests__ ├── action.test.ts ├── fixtures │ ├── config.yml │ └── pullrequest_created.json └── utils │ └── config.test.ts ├── action.yml ├── dist └── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── action.ts ├── index.ts └── utils │ └── config.ts └── tsconfig.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": ["README.md"], 3 | "imageSize": 100, 4 | "commit": false, 5 | "contributors": [ 6 | { 7 | "login": "TimonVS", 8 | "name": "Timon van Spronsen", 9 | "avatar_url": "https://avatars2.githubusercontent.com/u/876666?v=4", 10 | "profile": "http://www.timonvanspronsen.nl/", 11 | "contributions": ["code", "ideas", "test", "doc"] 12 | }, 13 | { 14 | "login": "amacado", 15 | "name": "Clemens Bastian", 16 | "avatar_url": "https://avatars2.githubusercontent.com/u/8781699?v=4", 17 | "profile": "http://clemensbastian.de", 18 | "contributions": ["code", "doc", "bug"] 19 | }, 20 | { 21 | "login": "hugo-vrijswijk", 22 | "name": "Hugo van Rijswijk", 23 | "avatar_url": "https://avatars3.githubusercontent.com/u/10114577?v=4", 24 | "profile": "https://github.com/hugo-vrijswijk", 25 | "contributions": ["code"] 26 | } 27 | ], 28 | "contributorsPerLine": 7, 29 | "projectName": "pr-labeler-action", 30 | "projectOwner": "TimonVS", 31 | "repoType": "github", 32 | "repoHost": "https://github.com" 33 | } 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | dist/** -diff linguist-generated=true -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Use Node.js 20 11 | uses: actions/setup-node@v3 12 | with: 13 | node-version: 20 14 | - name: Install dependencies 15 | run: npm ci 16 | env: 17 | CI: true 18 | - name: Run tests 19 | run: npm test 20 | env: 21 | CI: true 22 | -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | name: PR Labeler 2 | on: 3 | pull_request: 4 | types: [opened] 5 | 6 | jobs: 7 | pr-labeler: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read # for TimonVS/pr-labeler-action to read config 11 | pull-requests: write # for TimonVS/pr-labeler-action to add labels in PR 12 | steps: 13 | - uses: TimonVS/pr-labeler-action@v4 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | .env.test 65 | 66 | # parcel-bundler cache (https://parceljs.org/) 67 | .cache 68 | 69 | # next.js build output 70 | .next 71 | 72 | # nuxt.js build output 73 | .nuxt 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless/ 80 | 81 | # FuseBox cache 82 | .fusebox/ 83 | 84 | # DynamoDB Local files 85 | .dynamodb/ 86 | 87 | # End of https://www.gitignore.io/api/node 88 | -------------------------------------------------------------------------------- /.licensed.yml: -------------------------------------------------------------------------------- 1 | # Config for https://github.com/github/licensed 2 | # Allow open-source licenses, scan npm packages 3 | 4 | sources: 5 | npm: true 6 | 7 | allowed: 8 | - apache-2.0 9 | - bsd-2-clause 10 | - bsd-3-clause 11 | - isc 12 | - mit 13 | - cc0-1.0 14 | - unlicense 15 | 16 | reviewed: 17 | npm: 18 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 20 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .licenses 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "endOfLine": "lf" 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[javascript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.formatOnSave": true 5 | }, 6 | "[javascriptreact]": { 7 | "editor.defaultFormatter": "esbenp.prettier-vscode", 8 | "editor.formatOnSave": true 9 | }, 10 | "[typescript]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode", 12 | "editor.formatOnSave": true 13 | }, 14 | "[typescriptreact]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode", 16 | "editor.formatOnSave": true 17 | }, 18 | "[html]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode", 20 | "editor.formatOnSave": true 21 | }, 22 | "[css]": { 23 | "editor.defaultFormatter": "esbenp.prettier-vscode", 24 | "editor.formatOnSave": true 25 | }, 26 | "[markdown]": { 27 | "editor.defaultFormatter": "esbenp.prettier-vscode", 28 | "editor.formatOnSave": true 29 | }, 30 | "[json]": { 31 | "editor.defaultFormatter": "esbenp.prettier-vscode", 32 | "editor.formatOnSave": true 33 | }, 34 | "[jsonc]": { 35 | "editor.defaultFormatter": "esbenp.prettier-vscode", 36 | "editor.formatOnSave": true 37 | }, 38 | "[yaml]": { 39 | "editor.defaultFormatter": "esbenp.prettier-vscode", 40 | "editor.formatOnSave": true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Timon van Spronsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PR Labeler 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors) 4 | 5 | A GitHub Action that automatically applies labels to your PRs based on branch name patterns like `feature/*` or `fix/*`. 6 | Can be used in combination with [Release Drafter](https://github.com/toolmantim/release-drafter) to automatically [categorize pull requests](https://github.com/toolmantim/release-drafter#categorize-pull-requests). 7 | 8 | ## Usage 9 | 10 | Add `.github/workflows/pr-labeler.yml` with the following: 11 | 12 | ```yml 13 | name: PR Labeler 14 | on: 15 | pull_request: 16 | types: [opened] 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | pr-labeler: 23 | permissions: 24 | contents: read # for TimonVS/pr-labeler-action to read config file 25 | pull-requests: write # for TimonVS/pr-labeler-action to add labels in PR 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: TimonVS/pr-labeler-action@v5 29 | with: 30 | repo-token: ${{ secrets.GITHUB_TOKEN }} 31 | configuration-path: .github/pr-labeler.yml # optional, .github/pr-labeler.yml is the default value 32 | ``` 33 | 34 | ## Configuration 35 | 36 | Configure by creating a `.github/pr-labeler.yml` file. 37 | 38 | For example: 39 | 40 | ```yml 41 | feature: ['feature/*', 'feat/*'] 42 | fix: fix/* 43 | chore :hammer:: chore/* 44 | ``` 45 | 46 | Then if a pull request is opened with the branch name `feature/218-add-emoji-support` the Action will automatically apply the `feature` label. 47 | 48 | Similarly, if a pull requests is opened with the branch name `fix/weird-bug` or `chore/annual-refactoring-job`, the Action will apply the `fix` or `chore 🔨` label, respectively. 49 | 50 | If the label does not exist in your repo, a new one will be created (with no color and blank description), but it will not be permanently saved to the `github.com//labels` page. 51 | 52 | ### Wildcard branches in configuration 53 | 54 | You can use `*` as a wildcard for matching multiple branch names. See https://www.npmjs.com/package/matcher for more information about wildcard options. 55 | 56 | ### Default configuration 57 | 58 | When no configuration is provided, the following defaults will be used: 59 | 60 | ```yml 61 | feature: ['feature/*', 'feat/*'] 62 | fix: 'fix/*' 63 | chore: 'chore/*' 64 | ``` 65 | 66 | ## Contributors ✨ 67 | 68 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
Timon van Spronsen
Timon van Spronsen

💻 🤔 ⚠️ 📖
Clemens Bastian
Clemens Bastian

💻 📖 🐛
Hugo van Rijswijk
Hugo van Rijswijk

💻
79 | 80 | 81 | 82 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 83 | -------------------------------------------------------------------------------- /__tests__/action.test.ts: -------------------------------------------------------------------------------- 1 | import nock from 'nock'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import action from '../src/action'; 5 | import { Context } from '@actions/github/lib/context'; 6 | import { WebhookPayload } from '@actions/github/lib/interfaces'; 7 | 8 | nock.disableNetConnect(); 9 | 10 | describe('pr-labeler-action', () => { 11 | beforeEach(() => { 12 | setupEnvironmentVariables(); 13 | }); 14 | 15 | it('adds the "fix" label for "fix/510-logging" branch', async () => { 16 | nock('https://api.github.com') 17 | .get('/repos/Codertocat/Hello-World/contents/.github%2Fpr-labeler.yml?ref=fix%2F510-logging') 18 | .reply(200, configFixture()) 19 | .post('/repos/Codertocat/Hello-World/issues/1/labels', (body) => { 20 | expect(body).toMatchObject({ 21 | labels: ['fix'], 22 | }); 23 | return true; 24 | }) 25 | .reply(200); 26 | 27 | await action(new MockContext(pullRequestOpenedFixture({ ref: 'fix/510-logging' }))); 28 | expect.assertions(1); 29 | }); 30 | 31 | it('adds the "feature" label for "feature/sign-in-page/101" branch', async () => { 32 | nock('https://api.github.com') 33 | .get('/repos/Codertocat/Hello-World/contents/.github%2Fpr-labeler.yml?ref=feature%2Fsign-in-page%2F101') 34 | .reply(200, configFixture()) 35 | .post('/repos/Codertocat/Hello-World/issues/1/labels', (body) => { 36 | expect(body).toMatchObject({ 37 | labels: ['🎉 feature'], 38 | }); 39 | return true; 40 | }) 41 | .reply(200); 42 | 43 | await action(new MockContext(pullRequestOpenedFixture({ ref: 'feature/sign-in-page/101' }))); 44 | expect.assertions(1); 45 | }); 46 | 47 | it('adds the "release" label for "release/2.0" branch', async () => { 48 | nock('https://api.github.com') 49 | .get('/repos/Codertocat/Hello-World/contents/.github%2Fpr-labeler.yml?ref=release%2F2.0') 50 | .reply(200, configFixture()) 51 | .post('/repos/Codertocat/Hello-World/issues/1/labels', (body) => { 52 | expect(body).toMatchObject({ 53 | labels: ['release'], 54 | }); 55 | return true; 56 | }) 57 | .reply(200); 58 | 59 | await action(new MockContext(pullRequestOpenedFixture({ ref: 'release/2.0' }))); 60 | expect.assertions(1); 61 | }); 62 | 63 | it('uses the default config when no config was provided', async () => { 64 | nock('https://api.github.com') 65 | .get('/repos/Codertocat/Hello-World/contents/.github%2Fpr-labeler.yml?ref=fix%2F510-logging') 66 | .reply(404) 67 | .post('/repos/Codertocat/Hello-World/issues/1/labels', (body) => { 68 | expect(body).toMatchObject({ 69 | labels: ['fix'], 70 | }); 71 | return true; 72 | }) 73 | .reply(200); 74 | 75 | await action(new MockContext(pullRequestOpenedFixture({ ref: 'fix/510-logging' }))); 76 | expect.assertions(1); 77 | }); 78 | 79 | it('adds only one label if the branch matches a negative pattern', async () => { 80 | nock('https://api.github.com') 81 | .get('/repos/Codertocat/Hello-World/contents/.github%2Fpr-labeler.yml?ref=release%2Fskip-this-one') 82 | .reply(200, configFixture()) 83 | .post('/repos/Codertocat/Hello-World/issues/1/labels', (body) => { 84 | expect(body).toMatchObject({ 85 | labels: ['skip-release'], 86 | }); 87 | return true; 88 | }) 89 | .reply(200); 90 | 91 | await action(new MockContext(pullRequestOpenedFixture({ ref: 'release/skip-this-one' }))); 92 | expect.assertions(1); 93 | }); 94 | 95 | it("adds no labels if the branch doesn't match any patterns", async () => { 96 | nock('https://api.github.com') 97 | .get('/repos/Codertocat/Hello-World/contents/.github%2Fpr-labeler.yml?ref=hello_world') 98 | .reply(200, configFixture()) 99 | .post('/repos/Codertocat/Hello-World/issues/1/labels', (body) => { 100 | throw new Error("Shouldn't edit labels"); 101 | }) 102 | .reply(200); 103 | 104 | await action(new MockContext(pullRequestOpenedFixture({ ref: 'hello_world' }))); 105 | }); 106 | }); 107 | 108 | class MockContext extends Context { 109 | constructor(payload: WebhookPayload) { 110 | super(); 111 | this.payload = payload; 112 | } 113 | } 114 | 115 | function encodeContent(content: Buffer) { 116 | return Buffer.from(content).toString('base64'); 117 | } 118 | 119 | function configFixture(fileName = 'config.yml') { 120 | return { 121 | type: 'file', 122 | encoding: 'base64', 123 | size: 5362, 124 | name: fileName, 125 | path: `.github/${fileName}`, 126 | content: encodeContent(fs.readFileSync(path.join(__dirname, `fixtures/${fileName}`))), 127 | sha: '3d21ec53a331a6f037a91c368710b99387d012c1', 128 | url: 'https://api.github.com/repos/octokit/octokit.rb/contents/.github/release-drafter.yml', 129 | git_url: 'https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1', 130 | html_url: 'https://github.com/octokit/octokit.rb/blob/master/.github/release-drafter.yml', 131 | download_url: 'https://raw.githubusercontent.com/octokit/octokit.rb/master/.github/release-drafter.yml', 132 | _links: { 133 | git: 'https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1', 134 | self: 'https://api.github.com/repos/octokit/octokit.rb/contents/.github/release-drafter.yml', 135 | html: 'https://github.com/octokit/octokit.rb/blob/master/.github/release-drafter.yml', 136 | }, 137 | }; 138 | } 139 | 140 | function pullRequestOpenedFixture({ ref }: { ref: string }) { 141 | return { 142 | pull_request: { 143 | number: 1, 144 | head: { 145 | ref, 146 | }, 147 | }, 148 | repository: { 149 | name: 'Hello-World', 150 | owner: { 151 | login: 'Codertocat', 152 | }, 153 | }, 154 | }; 155 | } 156 | 157 | function setupEnvironmentVariables() { 158 | // reset process.env otherwise `Context` will use those variables 159 | process.env = {}; 160 | 161 | // GITHUB_TOKEN is required for Octokit 162 | process.env['GITHUB_TOKEN'] = '123'; 163 | 164 | // configuration-path parameter is required 165 | // parameters are exposed as environment variables: https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswith 166 | process.env['INPUT_CONFIGURATION-PATH'] = '.github/pr-labeler.yml'; 167 | } 168 | -------------------------------------------------------------------------------- /__tests__/fixtures/config.yml: -------------------------------------------------------------------------------- 1 | '🎉 feature': ['feature/*', 'feat/*'] 2 | fix: fix/* 3 | chore: chore/* 4 | release: ['release/*', 'hotfix/*', '!release/skip-*'] 5 | skip-release: release/skip-* 6 | dont-add: [] 7 | -------------------------------------------------------------------------------- /__tests__/fixtures/pullrequest_created.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 1, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1", 6 | "id": 191568743, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0MTkxNTY4NzQz", 8 | "html_url": "https://github.com/Codertocat/Hello-World/pull/1", 9 | "diff_url": "https://github.com/Codertocat/Hello-World/pull/1.diff", 10 | "patch_url": "https://github.com/Codertocat/Hello-World/pull/1.patch", 11 | "issue_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1", 12 | "number": 1, 13 | "state": "closed", 14 | "locked": false, 15 | "title": "Update the README with new information", 16 | "user": { 17 | "login": "Codertocat", 18 | "id": 21031067, 19 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 20 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 21 | "gravatar_id": "", 22 | "url": "https://api.github.com/users/Codertocat", 23 | "html_url": "https://github.com/Codertocat", 24 | "followers_url": "https://api.github.com/users/Codertocat/followers", 25 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 26 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 27 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 28 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 29 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 30 | "repos_url": "https://api.github.com/users/Codertocat/repos", 31 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 32 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 33 | "type": "User", 34 | "site_admin": false 35 | }, 36 | "body": "This is a pretty simple change that we need to pull into master.", 37 | "created_at": "2018-05-30T20:18:30Z", 38 | "updated_at": "2018-05-30T20:18:50Z", 39 | "closed_at": "2018-05-30T20:18:50Z", 40 | "merged_at": null, 41 | "merge_commit_sha": "414cb0069601a32b00bd122a2380cd283626a8e5", 42 | "assignee": null, 43 | "assignees": [], 44 | "requested_reviewers": [], 45 | "requested_teams": [], 46 | "labels": [], 47 | "milestone": null, 48 | "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/commits", 49 | "review_comments_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/comments", 50 | "review_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}", 51 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/comments", 52 | "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/34c5c7793cb3b279e22454cb6750c80560547b3a", 53 | "head": { 54 | "label": "Codertocat:fix/510-logging", 55 | "ref": "fix/510-logging", 56 | "sha": "34c5c7793cb3b279e22454cb6750c80560547b3a", 57 | "user": { 58 | "login": "Codertocat", 59 | "id": 21031067, 60 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 61 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 62 | "gravatar_id": "", 63 | "url": "https://api.github.com/users/Codertocat", 64 | "html_url": "https://github.com/Codertocat", 65 | "followers_url": "https://api.github.com/users/Codertocat/followers", 66 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 67 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 68 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 69 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 70 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 71 | "repos_url": "https://api.github.com/users/Codertocat/repos", 72 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 73 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 74 | "type": "User", 75 | "site_admin": false 76 | }, 77 | "repo": { 78 | "id": 135493233, 79 | "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", 80 | "name": "Hello-World", 81 | "full_name": "Codertocat/Hello-World", 82 | "owner": { 83 | "login": "Codertocat", 84 | "id": 21031067, 85 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 86 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 87 | "gravatar_id": "", 88 | "url": "https://api.github.com/users/Codertocat", 89 | "html_url": "https://github.com/Codertocat", 90 | "followers_url": "https://api.github.com/users/Codertocat/followers", 91 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 92 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 93 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 94 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 95 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 96 | "repos_url": "https://api.github.com/users/Codertocat/repos", 97 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 98 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 99 | "type": "User", 100 | "site_admin": false 101 | }, 102 | "private": false, 103 | "html_url": "https://github.com/Codertocat/Hello-World", 104 | "description": null, 105 | "fork": false, 106 | "url": "https://api.github.com/repos/Codertocat/Hello-World", 107 | "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", 108 | "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", 109 | "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", 110 | "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", 111 | "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", 112 | "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", 113 | "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", 114 | "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", 115 | "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", 116 | "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", 117 | "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", 118 | "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", 119 | "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", 120 | "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", 121 | "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", 122 | "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", 123 | "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", 124 | "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", 125 | "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", 126 | "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", 127 | "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", 128 | "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", 129 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", 130 | "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", 131 | "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", 132 | "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", 133 | "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", 134 | "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", 135 | "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", 136 | "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", 137 | "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", 138 | "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", 139 | "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", 140 | "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", 141 | "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", 142 | "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", 143 | "created_at": "2018-05-30T20:18:04Z", 144 | "updated_at": "2018-05-30T20:18:50Z", 145 | "pushed_at": "2018-05-30T20:18:48Z", 146 | "git_url": "git://github.com/Codertocat/Hello-World.git", 147 | "ssh_url": "git@github.com:Codertocat/Hello-World.git", 148 | "clone_url": "https://github.com/Codertocat/Hello-World.git", 149 | "svn_url": "https://github.com/Codertocat/Hello-World", 150 | "homepage": null, 151 | "size": 0, 152 | "stargazers_count": 0, 153 | "watchers_count": 0, 154 | "language": null, 155 | "has_issues": true, 156 | "has_projects": true, 157 | "has_downloads": true, 158 | "has_wiki": true, 159 | "has_pages": true, 160 | "forks_count": 0, 161 | "mirror_url": null, 162 | "archived": false, 163 | "open_issues_count": 1, 164 | "license": null, 165 | "forks": 0, 166 | "open_issues": 1, 167 | "watchers": 0, 168 | "default_branch": "master" 169 | } 170 | }, 171 | "base": { 172 | "label": "Codertocat:master", 173 | "ref": "master", 174 | "sha": "a10867b14bb761a232cd80139fbd4c0d33264240", 175 | "user": { 176 | "login": "Codertocat", 177 | "id": 21031067, 178 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 179 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 180 | "gravatar_id": "", 181 | "url": "https://api.github.com/users/Codertocat", 182 | "html_url": "https://github.com/Codertocat", 183 | "followers_url": "https://api.github.com/users/Codertocat/followers", 184 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 185 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 186 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 187 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 188 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 189 | "repos_url": "https://api.github.com/users/Codertocat/repos", 190 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 191 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 192 | "type": "User", 193 | "site_admin": false 194 | }, 195 | "repo": { 196 | "id": 135493233, 197 | "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", 198 | "name": "Hello-World", 199 | "full_name": "Codertocat/Hello-World", 200 | "owner": { 201 | "login": "Codertocat", 202 | "id": 21031067, 203 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 204 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 205 | "gravatar_id": "", 206 | "url": "https://api.github.com/users/Codertocat", 207 | "html_url": "https://github.com/Codertocat", 208 | "followers_url": "https://api.github.com/users/Codertocat/followers", 209 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 210 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 211 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 212 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 213 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 214 | "repos_url": "https://api.github.com/users/Codertocat/repos", 215 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 216 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 217 | "type": "User", 218 | "site_admin": false 219 | }, 220 | "private": false, 221 | "html_url": "https://github.com/Codertocat/Hello-World", 222 | "description": null, 223 | "fork": false, 224 | "url": "https://api.github.com/repos/Codertocat/Hello-World", 225 | "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", 226 | "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", 227 | "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", 228 | "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", 229 | "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", 230 | "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", 231 | "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", 232 | "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", 233 | "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", 234 | "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", 235 | "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", 236 | "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", 237 | "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", 238 | "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", 239 | "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", 240 | "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", 241 | "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", 242 | "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", 243 | "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", 244 | "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", 245 | "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", 246 | "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", 247 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", 248 | "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", 249 | "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", 250 | "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", 251 | "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", 252 | "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", 253 | "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", 254 | "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", 255 | "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", 256 | "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", 257 | "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", 258 | "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", 259 | "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", 260 | "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", 261 | "created_at": "2018-05-30T20:18:04Z", 262 | "updated_at": "2018-05-30T20:18:50Z", 263 | "pushed_at": "2018-05-30T20:18:48Z", 264 | "git_url": "git://github.com/Codertocat/Hello-World.git", 265 | "ssh_url": "git@github.com:Codertocat/Hello-World.git", 266 | "clone_url": "https://github.com/Codertocat/Hello-World.git", 267 | "svn_url": "https://github.com/Codertocat/Hello-World", 268 | "homepage": null, 269 | "size": 0, 270 | "stargazers_count": 0, 271 | "watchers_count": 0, 272 | "language": null, 273 | "has_issues": true, 274 | "has_projects": true, 275 | "has_downloads": true, 276 | "has_wiki": true, 277 | "has_pages": true, 278 | "forks_count": 0, 279 | "mirror_url": null, 280 | "archived": false, 281 | "open_issues_count": 1, 282 | "license": null, 283 | "forks": 0, 284 | "open_issues": 1, 285 | "watchers": 0, 286 | "default_branch": "master" 287 | } 288 | }, 289 | "_links": { 290 | "self": { 291 | "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1" 292 | }, 293 | "html": { 294 | "href": "https://github.com/Codertocat/Hello-World/pull/1" 295 | }, 296 | "issue": { 297 | "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/1" 298 | }, 299 | "comments": { 300 | "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/comments" 301 | }, 302 | "review_comments": { 303 | "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/comments" 304 | }, 305 | "review_comment": { 306 | "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}" 307 | }, 308 | "commits": { 309 | "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/1/commits" 310 | }, 311 | "statuses": { 312 | "href": "https://api.github.com/repos/Codertocat/Hello-World/statuses/34c5c7793cb3b279e22454cb6750c80560547b3a" 313 | } 314 | }, 315 | "author_association": "OWNER", 316 | "merged": false, 317 | "mergeable": true, 318 | "rebaseable": true, 319 | "mergeable_state": "clean", 320 | "merged_by": null, 321 | "comments": 0, 322 | "review_comments": 1, 323 | "maintainer_can_modify": false, 324 | "commits": 1, 325 | "additions": 1, 326 | "deletions": 1, 327 | "changed_files": 1 328 | }, 329 | "repository": { 330 | "id": 135493233, 331 | "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", 332 | "name": "Hello-World", 333 | "full_name": "Codertocat/Hello-World", 334 | "owner": { 335 | "login": "Codertocat", 336 | "id": 21031067, 337 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 338 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 339 | "gravatar_id": "", 340 | "url": "https://api.github.com/users/Codertocat", 341 | "html_url": "https://github.com/Codertocat", 342 | "followers_url": "https://api.github.com/users/Codertocat/followers", 343 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 344 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 345 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 346 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 347 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 348 | "repos_url": "https://api.github.com/users/Codertocat/repos", 349 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 350 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 351 | "type": "User", 352 | "site_admin": false 353 | }, 354 | "private": false, 355 | "html_url": "https://github.com/Codertocat/Hello-World", 356 | "description": null, 357 | "fork": false, 358 | "url": "https://api.github.com/repos/Codertocat/Hello-World", 359 | "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", 360 | "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", 361 | "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", 362 | "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", 363 | "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", 364 | "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", 365 | "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", 366 | "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", 367 | "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", 368 | "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", 369 | "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", 370 | "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", 371 | "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", 372 | "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", 373 | "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", 374 | "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", 375 | "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", 376 | "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", 377 | "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", 378 | "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", 379 | "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", 380 | "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", 381 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", 382 | "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", 383 | "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", 384 | "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", 385 | "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", 386 | "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", 387 | "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", 388 | "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", 389 | "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", 390 | "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", 391 | "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", 392 | "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", 393 | "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", 394 | "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", 395 | "created_at": "2018-05-30T20:18:04Z", 396 | "updated_at": "2018-05-30T20:18:50Z", 397 | "pushed_at": "2018-05-30T20:18:48Z", 398 | "git_url": "git://github.com/Codertocat/Hello-World.git", 399 | "ssh_url": "git@github.com:Codertocat/Hello-World.git", 400 | "clone_url": "https://github.com/Codertocat/Hello-World.git", 401 | "svn_url": "https://github.com/Codertocat/Hello-World", 402 | "homepage": null, 403 | "size": 0, 404 | "stargazers_count": 0, 405 | "watchers_count": 0, 406 | "language": null, 407 | "has_issues": true, 408 | "has_projects": true, 409 | "has_downloads": true, 410 | "has_wiki": true, 411 | "has_pages": true, 412 | "forks_count": 0, 413 | "mirror_url": null, 414 | "archived": false, 415 | "open_issues_count": 1, 416 | "license": null, 417 | "forks": 0, 418 | "open_issues": 1, 419 | "watchers": 0, 420 | "default_branch": "master" 421 | }, 422 | "sender": { 423 | "login": "Codertocat", 424 | "id": 21031067, 425 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 426 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 427 | "gravatar_id": "", 428 | "url": "https://api.github.com/users/Codertocat", 429 | "html_url": "https://github.com/Codertocat", 430 | "followers_url": "https://api.github.com/users/Codertocat/followers", 431 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 432 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 433 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 434 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 435 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 436 | "repos_url": "https://api.github.com/users/Codertocat/repos", 437 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 438 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 439 | "type": "User", 440 | "site_admin": false 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /__tests__/utils/config.test.ts: -------------------------------------------------------------------------------- 1 | import getConfig from '../../src/utils/config'; 2 | 3 | describe('getConfig', () => { 4 | it('returns default config when GitHub returns a 404 for given path', async () => { 5 | const defaultConfig = { 6 | foo: 'bar', 7 | }; 8 | 9 | const githubMock = { 10 | repos: { 11 | getContent() { 12 | throw new HTTPError(404); 13 | }, 14 | }, 15 | }; 16 | 17 | const config = await getConfig( 18 | githubMock as any, 19 | 'path/to/config', 20 | { owner: 'repo-owner', repo: 'repo-name' }, 21 | 'ref', 22 | defaultConfig, 23 | ); 24 | 25 | expect(config).toBe(defaultConfig); 26 | }); 27 | }); 28 | 29 | class HTTPError { 30 | constructor(public status: number) {} 31 | } 32 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'PR Labeler' 2 | description: 'Automatically labels your PRs based on branch name patterns like feature/* or fix/*.' 3 | author: 'Timon van Spronsen' 4 | inputs: 5 | repo-token: 6 | description: 'Token for the repository. Can be passed in using `{{ secrets.GITHUB_TOKEN }}`' 7 | default: ${{ github.token }} 8 | configuration-path: 9 | description: 'Path to label configurations' 10 | default: '.github/pr-labeler.yml' 11 | branding: 12 | icon: 'tag' 13 | color: 'white' 14 | runs: 15 | using: 'node20' 16 | main: 'dist/index.js' 17 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pr-labeler-action", 3 | "private": true, 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "node ./index.js", 7 | "build": "ncc build src/index.ts -o dist", 8 | "test": "jest", 9 | "test:watch": "jest --watch", 10 | "format": "prettier . --ignore-unknown --write", 11 | "format:check": "prettier . --ignore-unknown --check" 12 | }, 13 | "dependencies": { 14 | "@actions/core": "^1.10.1", 15 | "@actions/github": "^5.1.1", 16 | "matcher": "^4.0.0" 17 | }, 18 | "devDependencies": { 19 | "@types/jest": "^29.5.12", 20 | "@types/js-yaml": "^4.0.9", 21 | "@types/node": "^20.11.17", 22 | "@vercel/ncc": "^0.38.1", 23 | "jest": "^29.7.0", 24 | "js-yaml": "^4.1.0", 25 | "nock": "^13.5.1", 26 | "prettier": "^3.2.5", 27 | "ts-jest": "^29.1.2", 28 | "typescript": "^5.3.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/action.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as github from '@actions/github'; 3 | import { Context } from '@actions/github/lib/context'; 4 | import matcher from 'matcher'; 5 | import getConfig, { Config } from './utils/config'; 6 | 7 | const defaultConfig = { 8 | feature: ['feature/*', 'feat/*'], 9 | fix: 'fix/*', 10 | chore: 'chore/*', 11 | }; 12 | 13 | async function action(context: Context = github.context) { 14 | try { 15 | const GITHUB_TOKEN = process.env.GITHUB_TOKEN ?? core.getInput('repo-token', { required: true }); 16 | const octokit = github.getOctokit(GITHUB_TOKEN).rest; 17 | const configPath = core.getInput('configuration-path', { required: true }); 18 | 19 | if (!context.payload.pull_request) { 20 | throw new Error( 21 | "Payload doesn't contain `pull_request`. Make sure this Action is being triggered by a pull_request event (https://help.github.com/en/articles/events-that-trigger-workflows#pull-request-event-pull_request).", 22 | ); 23 | } 24 | 25 | const ref: string = context.payload.pull_request.head.ref; 26 | const config = await getConfig(octokit, configPath, context.repo, ref, defaultConfig); 27 | const labelsToAdd = getLabelsToAdd(config, ref); 28 | 29 | if (labelsToAdd.length > 0) { 30 | await octokit.issues.addLabels({ 31 | ...context.repo, 32 | issue_number: context.payload.pull_request.number, 33 | labels: labelsToAdd, 34 | }); 35 | } 36 | } catch (error: any) { 37 | if (process.env.NODE_ENV === 'test') { 38 | throw error; 39 | } 40 | 41 | core.error(error); 42 | core.setFailed(error.message); 43 | } 44 | } 45 | 46 | function getLabelsToAdd(config: Config, branchName: string): string[] { 47 | const labelsToAdd: string[] = []; 48 | 49 | for (const label in config) { 50 | const matches = matcher(branchName, config[label]); 51 | 52 | if (matches.length > 0) { 53 | labelsToAdd.push(label); 54 | } 55 | } 56 | 57 | return labelsToAdd; 58 | } 59 | 60 | export default action; 61 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import action from './action'; 2 | 3 | action(); 4 | -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | import yaml from 'js-yaml'; 2 | import { getOctokit } from '@actions/github'; 3 | 4 | interface RepoInfo { 5 | owner: string; 6 | repo: string; 7 | } 8 | 9 | export type Config = Record; 10 | 11 | export default async function getConfig( 12 | github: ReturnType['rest'], 13 | path: string, 14 | { owner, repo }: RepoInfo, 15 | ref: string, 16 | defaultConfig: Config, 17 | ): Promise { 18 | try { 19 | const response = await github.repos.getContent({ 20 | owner, 21 | repo, 22 | path, 23 | ref, 24 | }); 25 | 26 | if ('content' in response.data) { 27 | return parseConfig(response.data.content); 28 | } 29 | 30 | throw new Error(`${path} does not point to a config file`); 31 | } catch (error: any) { 32 | if (error.status === 404) { 33 | // TODO: add log 34 | return defaultConfig; 35 | } 36 | 37 | throw error; 38 | } 39 | } 40 | 41 | function parseConfig(content: string): Config { 42 | const decodedContent = Buffer.from(content, 'base64').toString(); 43 | const parsedConfig = yaml.load(decodedContent); 44 | // TODO: validate config 45 | return parsedConfig as Config; 46 | } 47 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, 4 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 5 | "strict": true /* Enable all strict type-checking options. */, 6 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 7 | }, 8 | "include": ["src/**/*.ts", "__tests__/**/*.ts"] 9 | } 10 | --------------------------------------------------------------------------------