├── .github ├── CODEOWNERS └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierrc.json ├── LICENSE ├── README.md ├── app.json ├── assets └── deprecation-checklist.md ├── package-lock.json ├── package.json ├── spec ├── 24-hour-rule.spec.ts ├── add-triage-labels.spec.ts ├── api-review-state.spec.ts ├── deprecation-review-state.spec.ts ├── enforce-semver-labels.spec.ts ├── fixtures │ ├── add-triage-labels │ │ ├── build_pr_opened.json │ │ ├── ci_pr_opened.json │ │ ├── docs_pr_opened.json │ │ └── test_pr_opened.json │ ├── api-review-state │ │ ├── pull_request.api-skip-delay_label.json │ │ ├── pull_request.approved_review_label.json │ │ ├── pull_request.converted_to_draft.json │ │ ├── pull_request.declined_review_label.json │ │ ├── pull_request.labeled.exclusion_labels.json │ │ ├── pull_request.new-pr_label.json │ │ ├── pull_request.no_review_label.json │ │ ├── pull_request.ready_for_review.json │ │ ├── pull_request.requested_review_label.json │ │ ├── pull_request.semver-minor.json │ │ ├── pull_request.semver-patch.json │ │ ├── pull_request.unlabeled.json │ │ └── pull_request_review │ │ │ ├── base │ │ │ ├── comment_neutral.json │ │ │ ├── review_declined.json │ │ │ ├── review_lgtm.json │ │ │ ├── review_lgtm_2.json │ │ │ └── submitted.json │ │ │ └── fork │ │ │ ├── comment_neutral.json │ │ │ ├── review_declined.json │ │ │ ├── review_lgtm.json │ │ │ ├── review_lgtm_2.json │ │ │ └── submitted.json │ ├── deprecation-review-state │ │ ├── issue_comment.checklist_complete.json │ │ ├── issue_comment.checklist_incomplete.json │ │ ├── issue_comment.edited.json │ │ ├── pull_request.no_review_label.json │ │ ├── pull_request.requested_review_label.json │ │ ├── pull_request.review_complete_label.json │ │ └── pull_request.unlabeled.json │ ├── pr-open-time │ │ ├── pull_request.opened.json │ │ ├── pull_request.semver-major.json │ │ ├── pull_request.semver-minor.json │ │ ├── pull_request.semver-missing.json │ │ ├── pull_request.semver-none.json │ │ ├── pull_request.semver-patch.json │ │ ├── pull_request.should_label.json │ │ └── pull_request.should_not_label.json │ └── semver-enforcement │ │ ├── pull_request.labeled.json │ │ ├── pull_request.labeled.too_many.json │ │ └── pull_request.opened.json └── utils.ts ├── src ├── 24-hour-rule.ts ├── add-triage-labels.ts ├── api-review-state.ts ├── constants.ts ├── deprecation-review-state.ts ├── enforce-semver-labels.ts ├── enums.ts ├── index.ts └── utils │ ├── check-utils.ts │ ├── env-util.ts │ ├── label-utils.ts │ └── log-util.ts ├── tsconfig.json ├── typings └── ambient.d.ts └── vitest.config.ts /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @electron/wg-releases @electron/wg-api 2 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # tag: v4.1.1 19 | - name: Setup Node.js 20 | uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # tag: v4.0.1 21 | with: 22 | node-version: 20.x 23 | - name: Lint 24 | run: | 25 | npm install 26 | npm run lint 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | build: 16 | runs-on: macOS-latest 17 | steps: 18 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # tag: v4.1.1 19 | - name: Setup Node.js 20 | uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # tag: v4.0.1 21 | with: 22 | node-version: 20.x 23 | - name: npm install, build, and test 24 | run: | 25 | npm install 26 | npm test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | *.pem 4 | .env 5 | .vscode 6 | .envrc 7 | coverage 8 | .DS_Store -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "printWidth": 100, 6 | "parser": "typescript" 7 | } 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Contributors to the Electron project 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 | # cation 2 | 3 | [![Test](https://github.com/electron/cation/actions/workflows/test.yml/badge.svg)](https://github.com/electron/cation/actions/workflows/test.yml) 4 | 5 | `cation` is Electron's PR monitoring bot, which serves four primary functions - semver label enforcement, PR open time enforcement, API review bookkeeping, and deprecation review. Each of the four are discussed in further detail below. 6 | 7 | ## Semver Label 8 | 9 | This bot is responsible for ensuring that all open PRs to Electron are labeled with a label that reflects their effect on Electron's [Semantic Version](https://semver.org/). If a given PR affects no user-facing code, it must be labeled `semver/none`. The bot will add a pending GitHub Check to the PR, which will only complete successfully when the necessary label is added. 10 | 11 | **Before a label is added:** 12 | 13 | Screen Shot 2020-12-16 at 9 16 23 AM 14 | 15 | **After a label is added:** 16 | 17 | Screen Shot 2020-12-16 at 9 15 48 AM 18 | 19 | ## PR Open Time 20 | 21 | The bot is also responsible for ensuring that any given PR is open for an amount of time that reflects its impact on user-facing code. This is also done to ensure that all potential stakeholders for that PR are given ample time to review it and discuss API ergonomics amongst ways it may affect users. 22 | 23 | Timespans: 24 | * `semver/major` - 168 hours (7 days) 25 | * `semver/minor` - 168 hours (7 days) 26 | * `semver/patch` - 24 hours (1 day) 27 | * `semver/none` - 24 hours (1 days), but in some cases (depending on the PR and its goals) there is no minimum time. 28 | 29 | Backport PRs (PRs to a release branch that is not `main`) do not require a minimum time, and a `fast-track` label may be optionally applied to a PR to indicate that it is intended to bypass the expected minimum time if sufficient reason exists to do so. 30 | 31 | ## API Review 32 | 33 | The bot controls the API review lifecycle on behalf of the [API Working Group](https://github.com/electron/governance/tree/main/wg-api). 34 | 35 | This group's review is mandated on all API changes, and their goal is twofold: 36 | 1. To guide Electron’s API surface towards a more ergonomic and usable design. 37 | 2. To reduce the incidence of future breaking changes by anticipating such changes and accommodating them ahead of time with future-proofing. 38 | 39 | Even changes that seem trivial can often be made more consistent and future-proof with some modifications, and the folks on the API WG have the expertise to spot and suggest those changes. 40 | 41 | In accordance with the above goals, this bot performs several bookkeeping duties. When a new PR is opened which is either `semver/minor` or `semver/major`, it will automatically add an `api-review/requested 🗳` label to the PR. To add clarity to whether a review is occurring in a given Electron governance member's capacity as a member of the API WG, this bot then adds a GitHub Check on the PR which will update as members of the API WG indicate their approval statuses. 42 | 43 | Members of the API Working Group must indicate their approval by leaving a comment via a PR Review containing `API LGTM`. This may not necessarily be a full approval with the GitHub API since approval by the API is primarily about the API shape and design. Both a PR review that comments and includes an LGTM indication or which is an approval with LGTM are sufficient. 44 | 45 | Screen Shot 2021-11-02 at 10 46 57 AM 46 | 47 | Screen Shot 2021-11-02 at 10 49 27 AM 48 | 49 | If a PR has passed its minimum open time and has the requisite number of approvals with no outstanding requests for changes, the bot will then switch `api-review/requested 🗳` to `api-review/approved ✅`, and the PR is free to be merged. If outstanding change requests persist, then the group will initiate consensus-seeking procedures and ultimately choose to approve or decline the PR. If the decision is made to decline, the API WG chair will then comment on the PR with `API Declined` and the bot will update `api-review/requested 🗳` to `api-review/declined ❌`. 50 | 51 | For PRs that need to land faster than the minimum open time (e.g. to respond to OS or Chromium updates), the minimum open time can be bypassed by adding a `api-review/skip-delay ⏰` label to the PR. This label may be added to a PR if at least two members of the API WG representing two different employers approve fast-tracking the PR. 52 | 53 | ## Deprecation Review 54 | 55 | The bot controls the deprecation review lifecycle on behalf of the [Releases Working Group](https://github.com/electron/governance/tree/main/wg-releases). 56 | 57 | Proper deprecation of APIs requires several changes: calling out the deprecation in the "Breaking Changes" doc, adding deprecation warnings to usage of the APIs, updating the docs to mark the APIs as deprecated, etc. Removing a deprecated API requires similar changes. 58 | 59 | Since it's easy for one of these changes to be missed in a code review, this bot provides a checklist to ensure nothing has been forgotten. When a PR is labeled with `deprecation-review/requested 📝` the bot will make a comment on the PR with a checklist of items that should be confirmed to follow deprecation policy. 60 | 61 | When deprecation review is requested, the bot also adds a GitHub Check on the PR. When all items on the checklist have been checked off (and any non-applicable items are removed) the check will be marked as completed. At that point the bot will update `deprecation-review/requested 📝` to `deprecation-review/complete ✅`. 62 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cation", 3 | "description": "a bot that does issue / pr management for electron", 4 | "scripts": {}, 5 | "env": { 6 | "APP_ID": { 7 | "required": true 8 | }, 9 | "PRIVATE_KEY": { 10 | "required": true 11 | } 12 | }, 13 | "formation": {}, 14 | "addons": [ 15 | 16 | ], 17 | "buildpacks": [{ 18 | "url": "heroku/nodejs" 19 | }] 20 | } -------------------------------------------------------------------------------- /assets/deprecation-checklist.md: -------------------------------------------------------------------------------- 1 | ## 🪦 Deprecation Checklist 2 | 3 | ### 🔥 New deprecations in this PR 4 | 5 | - [ ] 📢 Are called out in [`docs/breaking-changes.md`][] 6 | - [ ] ⚠️ Use the deprecation helpers in [`lib/common/deprecate.ts`](https://github.com/electron/electron/blob/main/lib/common/deprecate.ts) to warn about usage (including events) 7 | - [ ] 📝 Are marked as deprecated in the docs, using `_Deprecated_` (including properties and events) 8 | - [ ] 🧪 Relevant tests are updated to expect deprecation messages using the helpers in [`spec/lib/warning-helpers.ts`](https://github.com/electron/electron/blob/main/spec/lib/warning-helpers.ts) 9 | 10 | ### 🗑️ Previous deprecations being removed in this PR 11 | 12 | - [ ] 🏷️ Pull request is labeled as https://github.com/electron/electron/labels/semver%2Fmajor 13 | - [ ] 📢 Are called out as removed in [`docs/breaking-changes.md`][] 14 | - [ ] 📝 Are fully removed from the docs 15 | - [ ] ⌨️ All relevant code is removed 16 | - [ ] 🧪 [`spec/ts-smoke`](https://github.com/electron/electron/tree/main/spec/ts-smoke) is updated to use `@ts-expect-error` for the removed APIs 17 | 18 | --- 19 | 20 | @electron/wg-releases: Please confirm these deprecation changes conform to our deprecation policies listed in [`docs/breaking-changes.md`][], then check the applicable items in the checklist and remove any non-applicable items. 21 | 22 | [`docs/breaking-changes.md`]: https://github.com/electron/electron/blob/main/docs/breaking-changes.md 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cation", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Electron's PR monitoring bot", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "lint": "prettier --list-different \"{src,spec}/**/*.ts\"", 10 | "start": "probot run ./lib/index.js", 11 | "postinstall": "tsc", 12 | "prettier:write": "prettier --write \"{src,spec}/**/*.ts\"", 13 | "test": "vitest run --reporter=verbose", 14 | "coverage": "vitest run --silent --coverage", 15 | "prepare": "husky" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/electron/cation.git" 20 | }, 21 | "author": "Shelley Vohr (@codebytere)", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/electron/cation/issues" 25 | }, 26 | "homepage": "https://github.com/electron/cation#readme", 27 | "devDependencies": { 28 | "@types/node": "^22.9.0", 29 | "@vitest/coverage-v8": "^3.0.5", 30 | "husky": "^9.1.6", 31 | "lint-staged": "^15.2.10", 32 | "nock": "^13.5.5", 33 | "prettier": "^3.3.3", 34 | "smee-client": "^2.0.4", 35 | "typescript": "^5.6.3", 36 | "vitest": "^3.0.5" 37 | }, 38 | "dependencies": { 39 | "@octokit/rest": "^20.1.1", 40 | "@sentry/node": "^7.119.2", 41 | "probot": "^12.4.0", 42 | "semver": "^7.5.2" 43 | }, 44 | "lint-staged": { 45 | "{src,spec}/**/*.ts": "prettier --write" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spec/24-hour-rule.spec.ts: -------------------------------------------------------------------------------- 1 | import { Context, Probot } from 'probot'; 2 | import nock from 'nock'; 3 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; 4 | 5 | import { 6 | setUp24HourRule, 7 | getMinimumOpenTime, 8 | shouldPRHaveLabel, 9 | labelShouldBeChecked, 10 | } from '../src/24-hour-rule'; 11 | import { 12 | BACKPORT_LABEL, 13 | BACKPORT_SKIP_LABEL, 14 | FAST_TRACK_LABEL, 15 | MINIMUM_MAJOR_OPEN_TIME, 16 | MINIMUM_MINOR_OPEN_TIME, 17 | MINIMUM_PATCH_OPEN_TIME, 18 | NEW_PR_LABEL, 19 | SEMVER_LABELS, 20 | SEMVER_NONE_LABEL, 21 | } from '../src/constants'; 22 | import { loadFixture } from './utils'; 23 | 24 | const handler = async (app: Probot) => { 25 | setUp24HourRule(app, true); 26 | }; 27 | 28 | describe('pr open time', () => { 29 | let robot: Probot; 30 | let moctokit: any; 31 | 32 | beforeEach(() => { 33 | nock.cleanAll(); 34 | nock.disableNetConnect(); 35 | 36 | robot = new Probot({ 37 | githubToken: 'test', 38 | secret: 'secret', 39 | privateKey: 'private key', 40 | appId: 690857, 41 | }); 42 | 43 | moctokit = { 44 | issues: { 45 | listEventsForTimeline: vi.fn().mockReturnValue({ data: [] }), 46 | }, 47 | } as any as Context['octokit']; 48 | 49 | robot.load(handler); 50 | }); 51 | 52 | afterEach(() => { 53 | expect(nock.isDone()).toEqual(true); 54 | nock.cleanAll(); 55 | }); 56 | 57 | it('correctly returns the time for a semver-patch label', async () => { 58 | const payload = loadFixture('pr-open-time/pull_request.semver-patch.json'); 59 | const minTime = getMinimumOpenTime(payload); 60 | 61 | expect(minTime).toEqual(MINIMUM_PATCH_OPEN_TIME); 62 | }); 63 | 64 | it('correctly returns the time for a semver-minor label', async () => { 65 | const payload = loadFixture('pr-open-time/pull_request.semver-minor.json'); 66 | const minTime = getMinimumOpenTime(payload); 67 | 68 | expect(minTime).toEqual(MINIMUM_MINOR_OPEN_TIME); 69 | }); 70 | 71 | it('correctly returns the time for a semver-major label', async () => { 72 | const payload = loadFixture('pr-open-time/pull_request.semver-major.json'); 73 | const minTime = getMinimumOpenTime(payload); 74 | 75 | expect(minTime).toEqual(MINIMUM_MAJOR_OPEN_TIME); 76 | }); 77 | 78 | it('correctly returns the time for a semver-none label', async () => { 79 | const payload = loadFixture('pr-open-time/pull_request.semver-none.json'); 80 | const minTime = getMinimumOpenTime(payload); 81 | 82 | expect(minTime).toEqual(MINIMUM_PATCH_OPEN_TIME); 83 | }); 84 | 85 | it('correctly returns the time for a missing semver label', async () => { 86 | const payload = loadFixture('pr-open-time/pull_request.semver-missing.json'); 87 | const minTime = getMinimumOpenTime(payload); 88 | 89 | expect(minTime).toEqual(MINIMUM_MAJOR_OPEN_TIME); 90 | }); 91 | 92 | it('correctly determines whether to exclude some PRs from labels', async () => { 93 | const noLabelPayload = loadFixture('pr-open-time/pull_request.should_not_label.json'); 94 | const yesLabelPayload = loadFixture('pr-open-time/pull_request.should_label.json'); 95 | 96 | // Set created_at to yesterday. 97 | yesLabelPayload.created_at = new Date(+new Date() - 1000 * 60 * 60 * 24 * 2); 98 | 99 | const yesLabel = await shouldPRHaveLabel(moctokit, yesLabelPayload); 100 | const noLabel = await shouldPRHaveLabel(moctokit, noLabelPayload); 101 | 102 | expect(yesLabel).toEqual(true); 103 | expect(noLabel).toEqual(false); 104 | }); 105 | 106 | it('does not add the new-pr label to merged PRs', async () => { 107 | const payload = loadFixture('pr-open-time/pull_request.should_label.json'); 108 | payload.merged = true; 109 | 110 | const label = await shouldPRHaveLabel(moctokit, payload); 111 | expect(label).toEqual(false); 112 | }); 113 | 114 | it('correctly determines whether a label if relevant to the decision tree', () => { 115 | expect( 116 | labelShouldBeChecked({ 117 | id: 12345, 118 | description: '', 119 | node_id: 'id', 120 | url: `https://api.github.com/repos/electron/electron/labels/${NEW_PR_LABEL}`, 121 | name: NEW_PR_LABEL, 122 | color: '6ac2dd', 123 | default: false, 124 | }), 125 | ).toEqual(true); 126 | 127 | expect( 128 | labelShouldBeChecked({ 129 | id: 12345, 130 | description: '', 131 | node_id: 'id', 132 | url: `https://api.github.com/repos/electron/electron/labels/${SEMVER_LABELS.MINOR}`, 133 | name: SEMVER_LABELS.MINOR, 134 | color: '6ac2dd', 135 | default: false, 136 | }), 137 | ).toEqual(true); 138 | 139 | expect( 140 | labelShouldBeChecked({ 141 | id: 12345, 142 | description: '', 143 | node_id: 'id', 144 | url: `https://api.github.com/repos/electron/electron/labels/${SEMVER_LABELS.PATCH}`, 145 | name: SEMVER_LABELS.PATCH, 146 | color: '6ac2dd', 147 | default: false, 148 | }), 149 | ).toEqual(true); 150 | 151 | expect( 152 | labelShouldBeChecked({ 153 | id: 12345, 154 | description: '', 155 | node_id: 'id', 156 | url: `https://api.github.com/repos/electron/electron/labels/${SEMVER_LABELS.MAJOR}`, 157 | name: SEMVER_LABELS.PATCH, 158 | color: '6ac2dd', 159 | default: false, 160 | }), 161 | ).toEqual(true); 162 | 163 | expect( 164 | labelShouldBeChecked({ 165 | id: 12345, 166 | description: '', 167 | node_id: 'id', 168 | url: `https://api.github.com/repos/electron/electron/labels/${SEMVER_NONE_LABEL}`, 169 | name: SEMVER_NONE_LABEL, 170 | color: '6ac2dd', 171 | default: false, 172 | }), 173 | ).toEqual(true); 174 | 175 | expect( 176 | labelShouldBeChecked({ 177 | id: 12345, 178 | description: '', 179 | node_id: 'id', 180 | url: `https://api.github.com/repos/electron/electron/labels/${BACKPORT_LABEL}`, 181 | name: BACKPORT_LABEL, 182 | color: '6ac2dd', 183 | default: false, 184 | }), 185 | ).toEqual(true); 186 | 187 | expect( 188 | labelShouldBeChecked({ 189 | id: 12345, 190 | description: '', 191 | node_id: 'id', 192 | url: `https://api.github.com/repos/electron/electron/labels/${BACKPORT_SKIP_LABEL}`, 193 | name: BACKPORT_SKIP_LABEL, 194 | color: '6ac2dd', 195 | default: false, 196 | }), 197 | ).toEqual(true); 198 | 199 | expect( 200 | labelShouldBeChecked({ 201 | id: 12345, 202 | description: '', 203 | node_id: 'id', 204 | url: `https://api.github.com/repos/electron/electron/labels/${FAST_TRACK_LABEL}`, 205 | name: FAST_TRACK_LABEL, 206 | color: '6ac2dd', 207 | default: false, 208 | }), 209 | ).toEqual(true); 210 | 211 | expect( 212 | labelShouldBeChecked({ 213 | id: 12345, 214 | description: '', 215 | node_id: 'id', 216 | url: 'https://api.github.com/repos/electron/electron/labels/random', 217 | name: 'random', 218 | color: '6ac2dd', 219 | default: false, 220 | }), 221 | ).toEqual(false); 222 | }); 223 | 224 | it(`can add a ${NEW_PR_LABEL} label to a pull request`, async () => { 225 | const payload = loadFixture('pr-open-time/pull_request.opened.json'); 226 | 227 | // Set created_at to yesterday. 228 | payload.pull_request.created_at = new Date(+new Date() - 1000 * 60 * 60 * 24 * 2); 229 | 230 | nock('https://api.github.com') 231 | .get(`/repos/electron/electron/issues/${payload.number}/timeline`) 232 | .reply(200, []); 233 | 234 | nock('https://api.github.com') 235 | .get(`/repos/electron/electron/issues/${payload.number}/labels?per_page=100&page=1`) 236 | .reply(200, [{ name: 'one' }, { name: 'two' }]); 237 | 238 | nock('https://api.github.com') 239 | .post(`/repos/electron/electron/issues/${payload.number}/labels`, (body) => { 240 | expect(body).toEqual([NEW_PR_LABEL]); 241 | return true; 242 | }) 243 | .reply(200); 244 | 245 | await robot.receive({ 246 | id: '123-456', 247 | name: 'pull_request', 248 | payload, 249 | }); 250 | }); 251 | 252 | it(`takes draft status into account when adding a ${NEW_PR_LABEL} label`, async () => { 253 | const payload = loadFixture('pr-open-time/pull_request.opened.json'); 254 | 255 | // Set created_at to 5 days ago. 256 | const msInADay = 1638370929101; 257 | payload.pull_request.created_at = new Date(+new Date() - msInADay * 5); 258 | 259 | nock('https://api.github.com') 260 | .get(`/repos/electron/electron/issues/${payload.number}/timeline`) 261 | .reply(200, [ 262 | { 263 | actor_name: 'codebytere', 264 | created_at: new Date(+new Date() - 1000 * 60 * 60 * 24 * 2), 265 | type: 'ready_for_review', 266 | }, 267 | ]); 268 | 269 | nock('https://api.github.com') 270 | .get(`/repos/electron/electron/issues/${payload.number}/labels?per_page=100&page=1`) 271 | .reply(200, [{ name: 'one' }, { name: 'two' }]); 272 | 273 | await robot.receive({ 274 | id: '123-456', 275 | name: 'pull_request', 276 | payload, 277 | }); 278 | }); 279 | }); 280 | -------------------------------------------------------------------------------- /spec/add-triage-labels.spec.ts: -------------------------------------------------------------------------------- 1 | import { Probot } from 'probot'; 2 | import nock from 'nock'; 3 | import { afterEach, beforeEach, describe, expect, it } from 'vitest'; 4 | 5 | import { addBasicPRLabels } from '../src/add-triage-labels'; 6 | import { DOCUMENTATION_LABEL, SEMVER_LABELS, SEMVER_NONE_LABEL } from '../src/constants'; 7 | import { loadFixture } from './utils'; 8 | 9 | const handler = async (app: Probot) => { 10 | addBasicPRLabels(app); 11 | }; 12 | 13 | describe('add-triage-labels', () => { 14 | let robot: Probot; 15 | 16 | beforeEach(() => { 17 | nock.cleanAll(); 18 | nock.disableNetConnect(); 19 | 20 | robot = new Probot({ 21 | githubToken: 'test', 22 | secret: 'secret', 23 | privateKey: 'private key', 24 | appId: 690857, 25 | }); 26 | 27 | robot.load(handler); 28 | }); 29 | 30 | afterEach(() => { 31 | expect(nock.isDone()).toEqual(true); 32 | nock.cleanAll(); 33 | }); 34 | 35 | it('adds correct labels to documentation PRs', async () => { 36 | const payload = loadFixture('add-triage-labels/docs_pr_opened.json'); 37 | 38 | nock('https://api.github.com') 39 | .get(`/repos/electron/electron/issues/${payload.number}/labels?per_page=100&page=1`) 40 | .reply(200, []); 41 | 42 | nock('https://api.github.com') 43 | .post(`/repos/electron/electron/issues/${payload.number}/labels`, (body) => { 44 | expect(body).toEqual([SEMVER_LABELS.PATCH, DOCUMENTATION_LABEL]); 45 | return true; 46 | }) 47 | .reply(200); 48 | 49 | await robot.receive({ 50 | id: '123-456', 51 | name: 'pull_request', 52 | payload, 53 | }); 54 | }); 55 | 56 | it('adds correct labels to build PRs', async () => { 57 | const payload = loadFixture('add-triage-labels/build_pr_opened.json'); 58 | 59 | nock('https://api.github.com') 60 | .get(`/repos/electron/electron/issues/${payload.number}/labels?per_page=100&page=1`) 61 | .reply(200, []); 62 | 63 | nock('https://api.github.com') 64 | .post(`/repos/electron/electron/issues/${payload.number}/labels`, (body) => { 65 | expect(body).toEqual([SEMVER_NONE_LABEL]); 66 | return true; 67 | }) 68 | .reply(200); 69 | 70 | await robot.receive({ 71 | id: '123-456', 72 | name: 'pull_request', 73 | payload, 74 | }); 75 | }); 76 | 77 | it('adds correct labels to test PRs', async () => { 78 | const payload = loadFixture('add-triage-labels/test_pr_opened.json'); 79 | 80 | nock('https://api.github.com') 81 | .get(`/repos/electron/electron/issues/${payload.number}/labels?per_page=100&page=1`) 82 | .reply(200, []); 83 | 84 | nock('https://api.github.com') 85 | .post(`/repos/electron/electron/issues/${payload.number}/labels`, (body) => { 86 | expect(body).toEqual([SEMVER_NONE_LABEL]); 87 | return true; 88 | }) 89 | .reply(200); 90 | 91 | await robot.receive({ 92 | id: '123-456', 93 | name: 'pull_request', 94 | payload, 95 | }); 96 | }); 97 | 98 | it('adds correct labels to CI PRs', async () => { 99 | const payload = loadFixture('add-triage-labels/ci_pr_opened.json'); 100 | 101 | nock('https://api.github.com') 102 | .get(`/repos/electron/electron/issues/${payload.number}/labels?per_page=100&page=1`) 103 | .reply(200, []); 104 | 105 | nock('https://api.github.com') 106 | .post(`/repos/electron/electron/issues/${payload.number}/labels`, (body) => { 107 | expect(body).toEqual([SEMVER_NONE_LABEL]); 108 | return true; 109 | }) 110 | .reply(200); 111 | 112 | await robot.receive({ 113 | id: '123-456', 114 | name: 'pull_request', 115 | payload, 116 | }); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /spec/deprecation-review-state.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | import { Probot, Context } from 'probot'; 5 | import nock from 'nock'; 6 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; 7 | 8 | import { 9 | addOrUpdateDeprecationReviewCheck, 10 | isChecklistComment, 11 | isReviewLabel, 12 | maybeAddChecklistComment, 13 | setupDeprecationReviewStateManagement, 14 | } from '../src/deprecation-review-state'; 15 | import { 16 | REVIEW_LABELS, 17 | DEPRECATION_REVIEW_CHECK_NAME, 18 | DEPRECATION_REVIEW_LABELS, 19 | } from '../src/constants'; 20 | 21 | import { CheckRunStatus } from '../src/enums'; 22 | import { loadFixture } from './utils'; 23 | 24 | const handler = async (app: Probot) => { 25 | setupDeprecationReviewStateManagement(app); 26 | }; 27 | 28 | const CHECKLIST_COMMENT = fs.readFileSync( 29 | path.join(__dirname, '..', 'assets', 'deprecation-checklist.md'), 30 | 'utf-8', 31 | ); 32 | 33 | describe('deprecation review', () => { 34 | let robot: Probot; 35 | let moctokit: any; 36 | 37 | beforeEach(() => { 38 | nock.cleanAll(); 39 | nock.disableNetConnect(); 40 | robot = new Probot({ 41 | githubToken: 'test', 42 | secret: 'secret', 43 | privateKey: 'private key', 44 | appId: 690857, 45 | }); 46 | 47 | moctokit = { 48 | issues: { 49 | addLabels: vi.fn().mockReturnValue({ data: [] }), 50 | createComment: vi.fn().mockResolvedValue({}), 51 | listLabelsOnIssue: vi.fn().mockReturnValue({ data: [] }), 52 | listComments: vi.fn().mockReturnValue({ data: [] }), 53 | removeLabel: vi.fn().mockReturnValue({ data: [] }), 54 | }, 55 | checks: { 56 | listForRef: vi.fn().mockReturnValue({ data: { check_runs: [] } }), 57 | create: vi.fn().mockReturnValue({ data: {} }), 58 | update: vi.fn().mockReturnValue({ data: {} }), 59 | }, 60 | teams: { 61 | listMembersInOrg: vi.fn().mockReturnValue({ data: [] }), 62 | }, 63 | pulls: { 64 | listReviews: vi.fn().mockReturnValue({ data: [] }), 65 | }, 66 | } as any as Context['octokit']; 67 | 68 | robot.load(handler); 69 | }); 70 | 71 | afterEach(() => { 72 | console.log(nock.pendingMocks()); 73 | expect(nock.isDone()).toEqual(true); 74 | nock.cleanAll(); 75 | }); 76 | 77 | describe('isReviewLabel', () => { 78 | it('should return true for review labels', () => { 79 | expect(isReviewLabel(DEPRECATION_REVIEW_LABELS.REQUESTED)).toEqual(true); 80 | expect(isReviewLabel(DEPRECATION_REVIEW_LABELS.COMPLETE)).toEqual(true); 81 | }); 82 | 83 | it('should return false for any other labels', () => { 84 | expect(isReviewLabel(REVIEW_LABELS.APPROVED)).toEqual(false); 85 | }); 86 | }); 87 | 88 | describe('isChecklistComment', () => { 89 | it('should return true for the checklist comment', () => { 90 | const { comment } = loadFixture( 91 | 'deprecation-review-state/issue_comment.checklist_complete.json', 92 | ); 93 | expect(isChecklistComment(comment)).toEqual(true); 94 | }); 95 | 96 | it('should false true for any other comment', () => { 97 | const { comment } = loadFixture('deprecation-review-state/issue_comment.edited.json'); 98 | expect(isChecklistComment(comment)).toEqual(false); 99 | }); 100 | }); 101 | 102 | describe('addOrUpdateDeprecationReviewCheck', () => { 103 | it('should reset the check when PR does not have a deprecation review label', async () => { 104 | const { pull_request } = loadFixture( 105 | 'deprecation-review-state/pull_request.no_review_label.json', 106 | ); 107 | 108 | moctokit.checks.listForRef = vi.fn().mockReturnValue({ 109 | data: { 110 | check_runs: [ 111 | { 112 | name: DEPRECATION_REVIEW_CHECK_NAME, 113 | id: '12345', 114 | }, 115 | ], 116 | }, 117 | }); 118 | 119 | const expected = { 120 | name: DEPRECATION_REVIEW_CHECK_NAME, 121 | status: 'completed', 122 | output: { 123 | title: 'Outdated', 124 | summary: `PR no longer requires ${DEPRECATION_REVIEW_CHECK_NAME}`, 125 | }, 126 | conclusion: CheckRunStatus.NEUTRAL, 127 | }; 128 | 129 | await addOrUpdateDeprecationReviewCheck(moctokit, pull_request); 130 | expect(moctokit.checks.update).toHaveBeenCalledWith(expect.objectContaining(expected)); 131 | 132 | expect(moctokit.issues.addLabels).not.toHaveBeenCalled(); 133 | expect(moctokit.issues.removeLabel).not.toHaveBeenCalled(); 134 | 135 | expect(moctokit.checks.listForRef).toHaveBeenCalled(); 136 | expect(moctokit.checks.update).toHaveBeenCalled(); 137 | }); 138 | 139 | it(`should create the check for a PR with the ${DEPRECATION_REVIEW_LABELS.REQUESTED} label`, async () => { 140 | const { pull_request } = loadFixture( 141 | 'deprecation-review-state/pull_request.requested_review_label.json', 142 | ); 143 | 144 | moctokit.checks.listForRef = vi.fn().mockReturnValue({ 145 | data: { 146 | check_runs: [], 147 | }, 148 | }); 149 | 150 | const expected = { 151 | name: DEPRECATION_REVIEW_CHECK_NAME, 152 | status: 'in_progress', 153 | output: { 154 | title: 'Pending', 155 | summary: 'Review in-progress', 156 | }, 157 | }; 158 | 159 | await addOrUpdateDeprecationReviewCheck(moctokit, pull_request); 160 | expect(moctokit.checks.create).toHaveBeenCalledWith(expect.objectContaining(expected)); 161 | }); 162 | 163 | it('should not use Checks API when the PR is from a fork', async () => { 164 | const { pull_request } = loadFixture( 165 | 'deprecation-review-state/pull_request.requested_review_label.json', 166 | ); 167 | 168 | pull_request.head.repo.fork = true; 169 | 170 | await addOrUpdateDeprecationReviewCheck(moctokit, pull_request); 171 | expect(moctokit.checks.create).not.toHaveBeenCalled(); 172 | expect(moctokit.checks.update).not.toHaveBeenCalled(); 173 | }); 174 | 175 | it(`should correctly update deprecation review check for ${DEPRECATION_REVIEW_LABELS.COMPLETE} label`, async () => { 176 | const payload = loadFixture( 177 | 'deprecation-review-state/pull_request.review_complete_label.json', 178 | ); 179 | 180 | moctokit.checks.listForRef = vi.fn().mockReturnValue({ 181 | data: { 182 | check_runs: [ 183 | { 184 | name: DEPRECATION_REVIEW_CHECK_NAME, 185 | id: '12345', 186 | }, 187 | ], 188 | }, 189 | }); 190 | 191 | const expected = { 192 | name: DEPRECATION_REVIEW_CHECK_NAME, 193 | status: 'completed', 194 | output: { 195 | title: 'Complete', 196 | summary: 'All review items have been checked off', 197 | }, 198 | conclusion: CheckRunStatus.SUCCESS, 199 | }; 200 | 201 | await addOrUpdateDeprecationReviewCheck(moctokit, payload.pull_request); 202 | expect(moctokit.checks.update).toHaveBeenCalledWith(expect.objectContaining(expected)); 203 | }); 204 | }); 205 | 206 | describe('maybeAddChecklistComment', () => { 207 | it('should comment on the PR when Deprecation Review is requested', async () => { 208 | const { pull_request } = loadFixture( 209 | 'deprecation-review-state/pull_request.requested_review_label.json', 210 | ); 211 | 212 | await maybeAddChecklistComment(moctokit, pull_request); 213 | expect(moctokit.issues.createComment).toHaveBeenCalledWith( 214 | expect.objectContaining({ 215 | body: CHECKLIST_COMMENT, 216 | }), 217 | ); 218 | }); 219 | 220 | it('should not comment on a PR when no Deprecation Review is requested', async () => { 221 | const { pull_request } = loadFixture( 222 | 'deprecation-review-state/pull_request.no_review_label.json', 223 | ); 224 | 225 | await maybeAddChecklistComment(moctokit, pull_request); 226 | expect(moctokit.issues.createComment).not.toHaveBeenCalled(); 227 | }); 228 | 229 | it('should not comment on the PR when the comment already exists', async () => { 230 | const { pull_request } = loadFixture( 231 | 'deprecation-review-state/pull_request.requested_review_label.json', 232 | ); 233 | 234 | moctokit.issues.listComments = vi.fn().mockReturnValue({ 235 | data: [ 236 | { 237 | user: { 238 | login: 'bot', 239 | }, 240 | body: CHECKLIST_COMMENT, 241 | }, 242 | ], 243 | }); 244 | 245 | await maybeAddChecklistComment(moctokit, pull_request); 246 | expect(moctokit.issues.createComment).not.toHaveBeenCalled(); 247 | }); 248 | }); 249 | 250 | it(`creates the deprecation review check and comments when review is requested`, async () => { 251 | const payload = loadFixture( 252 | 'deprecation-review-state/pull_request.requested_review_label.json', 253 | ); 254 | 255 | nock('https://api.github.com') 256 | .get( 257 | `/repos/dsanders11/deprecation-review/commits/${payload.pull_request.head.sha}/check-runs?per_page=100`, 258 | ) 259 | .reply(200, { 260 | check_runs: [], 261 | }); 262 | 263 | nock('https://api.github.com') 264 | .get( 265 | `/repos/dsanders11/deprecation-review/issues/${payload.pull_request.number}/comments?per_page=100`, 266 | ) 267 | .reply(200, []); 268 | 269 | const expected = { 270 | name: DEPRECATION_REVIEW_CHECK_NAME, 271 | status: 'in_progress', 272 | output: { 273 | title: 'Pending', 274 | summary: 'Review in-progress', 275 | }, 276 | }; 277 | 278 | nock('https://api.github.com') 279 | .post('/repos/dsanders11/deprecation-review/check-runs', (body) => { 280 | expect(body).toMatchObject(expected); 281 | return true; 282 | }) 283 | .reply(200); 284 | 285 | nock('https://api.github.com') 286 | .post( 287 | `/repos/dsanders11/deprecation-review/issues/${payload.pull_request.number}/comments`, 288 | ({ body }) => { 289 | expect(body).toEqual(CHECKLIST_COMMENT); 290 | return true; 291 | }, 292 | ) 293 | .reply(200); 294 | 295 | await robot.receive({ 296 | id: '123-456', 297 | name: 'pull_request', 298 | payload, 299 | }); 300 | }); 301 | 302 | it(`correctly updates deprecation review check when no review labels are found`, async () => { 303 | const payload = loadFixture('deprecation-review-state/pull_request.no_review_label.json'); 304 | 305 | nock('https://api.github.com') 306 | .get( 307 | `/repos/dsanders11/deprecation-review/commits/${payload.pull_request.head.sha}/check-runs?per_page=100`, 308 | ) 309 | .reply(200, { 310 | check_runs: [ 311 | { 312 | name: DEPRECATION_REVIEW_CHECK_NAME, 313 | id: '12345', 314 | }, 315 | ], 316 | }); 317 | 318 | const expected = { 319 | name: DEPRECATION_REVIEW_CHECK_NAME, 320 | status: 'completed', 321 | output: { 322 | title: 'Outdated', 323 | summary: `PR no longer requires ${DEPRECATION_REVIEW_CHECK_NAME}`, 324 | }, 325 | conclusion: CheckRunStatus.NEUTRAL, 326 | }; 327 | 328 | nock('https://api.github.com') 329 | .patch(`/repos/dsanders11/deprecation-review/check-runs/12345`, (body) => { 330 | expect(body).toMatchObject(expected); 331 | return true; 332 | }) 333 | .reply(200); 334 | 335 | await robot.receive({ 336 | id: '123-456', 337 | name: 'pull_request', 338 | payload, 339 | }); 340 | }); 341 | 342 | it('correctly updates deprecation review check and deprecation review label when pr is unlabeled', async () => { 343 | const payload = loadFixture('deprecation-review-state/pull_request.unlabeled.json'); 344 | 345 | nock('https://api.github.com') 346 | .get( 347 | `/repos/dsanders11/deprecation-review/issues/${payload.number}/labels?per_page=100&page=1`, 348 | ) 349 | .reply(200, []); 350 | 351 | nock('https://api.github.com') 352 | .post(`/repos/dsanders11/deprecation-review/issues/${payload.number}/labels`, (body) => { 353 | expect(body).toEqual([DEPRECATION_REVIEW_LABELS.COMPLETE]); 354 | return true; 355 | }) 356 | .reply(200); 357 | 358 | await robot.receive({ 359 | id: '123-456', 360 | name: 'pull_request', 361 | payload, 362 | }); 363 | }); 364 | 365 | it('does nothing for an edited comment that is not the deprecation checklist', async () => { 366 | const payload = loadFixture('deprecation-review-state/issue_comment.edited.json'); 367 | 368 | await robot.receive({ 369 | id: '123-456', 370 | name: 'issue_comment', 371 | payload, 372 | }); 373 | }); 374 | 375 | it('does nothing when checklist is incomplete', async () => { 376 | const payload = loadFixture('deprecation-review-state/issue_comment.checklist_incomplete.json'); 377 | 378 | await robot.receive({ 379 | id: '123-456', 380 | name: 'issue_comment', 381 | payload, 382 | }); 383 | }); 384 | 385 | it('correctly updates deprecation review check and deprecation review label when checklist complete', async () => { 386 | const payload = loadFixture('deprecation-review-state/issue_comment.checklist_complete.json'); 387 | 388 | nock('https://api.github.com') 389 | .get( 390 | `/repos/dsanders11/deprecation-review/issues/${payload.issue.number}/labels?per_page=100&page=1`, 391 | ) 392 | .reply(200, [{ name: DEPRECATION_REVIEW_LABELS.REQUESTED }]) 393 | .get( 394 | `/repos/dsanders11/deprecation-review/issues/${payload.issue.number}/labels?per_page=100&page=1`, 395 | ) 396 | .reply(200, [ 397 | { name: DEPRECATION_REVIEW_LABELS.REQUESTED }, 398 | { name: DEPRECATION_REVIEW_LABELS.COMPLETE }, 399 | ]); 400 | 401 | nock('https://api.github.com') 402 | .post( 403 | `/repos/dsanders11/deprecation-review/issues/${payload.issue.number}/labels`, 404 | (body) => { 405 | expect(body).toEqual([DEPRECATION_REVIEW_LABELS.COMPLETE]); 406 | return true; 407 | }, 408 | ) 409 | .reply(200); 410 | 411 | const encoded = encodeURIComponent(DEPRECATION_REVIEW_LABELS.REQUESTED); 412 | nock('https://api.github.com') 413 | .delete( 414 | `/repos/dsanders11/deprecation-review/issues/${payload.issue.number}/labels/${encoded}`, 415 | ) 416 | .reply(200, [{ name: DEPRECATION_REVIEW_LABELS.COMPLETE }]); 417 | 418 | await robot.receive({ 419 | id: '123-456', 420 | name: 'issue_comment', 421 | payload, 422 | }); 423 | }); 424 | }); 425 | -------------------------------------------------------------------------------- /spec/enforce-semver-labels.spec.ts: -------------------------------------------------------------------------------- 1 | import { Probot } from 'probot'; 2 | import nock from 'nock'; 3 | import { afterEach, beforeEach, describe, expect, it } from 'vitest'; 4 | 5 | import { setupSemverLabelEnforcement } from '../src/enforce-semver-labels'; 6 | import { loadFixture } from './utils'; 7 | 8 | const handler = async (app: Probot) => { 9 | setupSemverLabelEnforcement(app); 10 | }; 11 | 12 | describe('semver-enforcement', () => { 13 | let robot: Probot; 14 | 15 | beforeEach(() => { 16 | nock.cleanAll(); 17 | nock.disableNetConnect(); 18 | 19 | robot = new Probot({ 20 | githubToken: 'test', 21 | secret: 'secret', 22 | privateKey: 'private key', 23 | appId: 690857, 24 | }); 25 | 26 | robot.load(handler); 27 | }); 28 | 29 | afterEach(() => { 30 | expect(nock.isDone()).toEqual(true); 31 | nock.cleanAll(); 32 | }); 33 | 34 | it('correctly responds to a missing semver label', async () => { 35 | const payload = loadFixture('semver-enforcement/pull_request.opened.json'); 36 | 37 | const expected = { 38 | name: 'Semver Label Enforcement', 39 | head_sha: '578fd4af98861ef7e6374d7d1fa1ccca6bc7136d', 40 | status: 'in_progress', 41 | output: { 42 | title: 'No semver/* label found', 43 | summary: "We couldn't find a semver/* label, please add one", 44 | }, 45 | }; 46 | 47 | nock('https://api.github.com') 48 | .post('/repos/electron/electron/check-runs', (body) => { 49 | expect(body).toMatchObject(expected); 50 | return true; 51 | }) 52 | .reply(200); 53 | 54 | await robot.receive({ 55 | id: '123-456', 56 | name: 'pull_request', 57 | payload, 58 | }); 59 | }); 60 | 61 | it('correctly responds to a single applied semver label', async () => { 62 | const payload = loadFixture('semver-enforcement/pull_request.labeled.json'); 63 | 64 | const expected = { 65 | name: 'Semver Label Enforcement', 66 | head_sha: '6787d8adf4e17a1aab2f39327ca32ddfa8d9dbfd', 67 | status: 'completed', 68 | output: { 69 | title: 'Found "semver/patch"', 70 | summary: 'Found a single semver/* label, looking good here.', 71 | }, 72 | }; 73 | 74 | nock('https://api.github.com') 75 | .post('/repos/electron/electron/check-runs', (body) => { 76 | expect(body).toMatchObject(expected); 77 | return true; 78 | }) 79 | .reply(200); 80 | 81 | await robot.receive({ 82 | id: '123-456', 83 | name: 'pull_request', 84 | payload, 85 | }); 86 | }); 87 | 88 | it('correctly responds to too many semver labels', async () => { 89 | const payload = loadFixture('semver-enforcement/pull_request.labeled.too_many.json'); 90 | 91 | const expected = { 92 | name: 'Semver Label Enforcement', 93 | head_sha: '6787d8adf4e17a1aab2f39327ca32ddfa8d9dbfd', 94 | status: 'in_progress', 95 | output: { 96 | title: 'Multiple semver/* labels found', 97 | summary: 'We found multiple semver/* labels, please remove one', 98 | }, 99 | }; 100 | 101 | nock('https://api.github.com') 102 | .post('/repos/electron/electron/check-runs', (body) => { 103 | expect(body).toMatchObject(expected); 104 | return true; 105 | }) 106 | .reply(200); 107 | 108 | await robot.receive({ 109 | id: '123-456', 110 | name: 'pull_request', 111 | payload, 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /spec/fixtures/add-triage-labels/build_pr_opened.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 6 | "id": 534054584, 7 | "number": 26876, 8 | "state": "open", 9 | "title": "build: fix JS linting", 10 | "user": { 11 | "login": "MarshallOfSound" 12 | }, 13 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 14 | "created_at": "2020-12-08T01:24:55Z", 15 | "updated_at": "2020-12-08T01:24:55Z", 16 | "closed_at": null, 17 | "merged_at": null, 18 | "labels": [], 19 | "base": { 20 | "ref": "main", 21 | "repo": { 22 | "default_branch": "main" 23 | } 24 | } 25 | }, 26 | "repository": { 27 | "id": 9384267, 28 | "name": "electron", 29 | "full_name": "electron/electron", 30 | "owner": { 31 | "login": "electron" 32 | } 33 | }, 34 | "installation": { 35 | "id": 690857 36 | } 37 | } -------------------------------------------------------------------------------- /spec/fixtures/add-triage-labels/ci_pr_opened.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/12345", 6 | "id": 534054584, 7 | "number": 26876, 8 | "state": "open", 9 | "title": "ci: fix a weird CI failure", 10 | "user": { 11 | "login": "codebytere" 12 | }, 13 | "body": "fix a weird CI failure!!\r\n\r\nNotes: no-notes", 14 | "created_at": "2020-12-08T01:24:55Z", 15 | "updated_at": "2020-12-08T01:24:55Z", 16 | "closed_at": null, 17 | "merged_at": null, 18 | "labels": [], 19 | "base": { 20 | "ref": "main", 21 | "repo": { 22 | "default_branch": "main" 23 | } 24 | } 25 | }, 26 | "repository": { 27 | "id": 9384267, 28 | "name": "electron", 29 | "full_name": "electron/electron", 30 | "owner": { 31 | "login": "electron" 32 | } 33 | }, 34 | "installation": { 35 | "id": 690857 36 | } 37 | } -------------------------------------------------------------------------------- /spec/fixtures/add-triage-labels/docs_pr_opened.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/12345", 6 | "id": 534054584, 7 | "number": 26876, 8 | "state": "open", 9 | "title": "docs: fix a fun docs thing", 10 | "user": { 11 | "login": "codebytere" 12 | }, 13 | "body": "Do some very fun things with documentation!!\r\n\r\nNotes: no-notes", 14 | "created_at": "2020-12-08T01:24:55Z", 15 | "updated_at": "2020-12-08T01:24:55Z", 16 | "closed_at": null, 17 | "merged_at": null, 18 | "labels": [], 19 | "base": { 20 | "ref": "main", 21 | "repo": { 22 | "default_branch": "main" 23 | } 24 | } 25 | }, 26 | "repository": { 27 | "id": 9384267, 28 | "name": "electron", 29 | "full_name": "electron/electron", 30 | "owner": { 31 | "login": "electron" 32 | } 33 | }, 34 | "installation": { 35 | "id": 690857 36 | } 37 | } -------------------------------------------------------------------------------- /spec/fixtures/add-triage-labels/test_pr_opened.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/12345", 6 | "id": 534054584, 7 | "number": 26876, 8 | "state": "open", 9 | "title": "test: test a funky thing", 10 | "user": { 11 | "login": "codebytere" 12 | }, 13 | "body": "test a funky thing!!\r\n\r\nNotes: no-notes", 14 | "created_at": "2020-12-08T01:24:55Z", 15 | "updated_at": "2020-12-08T01:24:55Z", 16 | "closed_at": null, 17 | "merged_at": null, 18 | "labels": [], 19 | "base": { 20 | "ref": "main", 21 | "repo": { 22 | "default_branch": "main" 23 | } 24 | } 25 | }, 26 | "repository": { 27 | "id": 9384267, 28 | "name": "electron", 29 | "full_name": "electron/electron", 30 | "owner": { 31 | "login": "electron" 32 | } 33 | }, 34 | "installation": { 35 | "id": 690857 36 | } 37 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.api-skip-delay_label.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 3 | "id": 534054584, 4 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 5 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 6 | "number": 26876, 7 | "state": "closed", 8 | "locked": false, 9 | "title": "build: fix JS linting", 10 | "user": { 11 | "login": "MarshallOfSound" 12 | }, 13 | "labels": [ 14 | { 15 | "id": 1034512799, 16 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 17 | "url": "https://api.github.com/repos/electron/electron/labels/api-review/skip-delay%20%E2%8F%B0", 18 | "name": "api-review/skip-delay ⏰", 19 | "color": "6ac2dd", 20 | "default": false, 21 | "description": "skip the default API approval delay" 22 | } 23 | ], 24 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 25 | "created_at": "2020-12-08T01:24:55Z", 26 | "updated_at": "2020-12-10T18:57:11Z", 27 | "closed_at": "2020-12-10T18:57:07Z", 28 | "merged_at": "2020-12-10T18:57:07Z", 29 | "merge_commit_sha": "51db2a6b34792c99a9a685bdfbfe87a7343631b9", 30 | "assignee": null, 31 | "milestone": null, 32 | "draft": false, 33 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 34 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 35 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 36 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 37 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/c6b1b7168ab850a47f856c4a30f7a441bede1117", 38 | "head": { 39 | "label": "electron:fix-lint-js", 40 | "ref": "fix-lint-js", 41 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 42 | "user": { 43 | "login": "electron", 44 | "id": 13409222, 45 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 46 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 47 | "gravatar_id": "", 48 | "url": "https://api.github.com/users/electron" 49 | }, 50 | "repo": { 51 | "id": 9384267, 52 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 53 | "name": "electron", 54 | "full_name": "electron/electron", 55 | "private": false, 56 | "owner": { 57 | "login": "electron", 58 | "id": 13409222, 59 | "url": "https://api.github.com/users/electron" 60 | }, 61 | "license": { 62 | "key": "mit", 63 | "name": "MIT License", 64 | "spdx_id": "MIT", 65 | "url": "https://api.github.com/licenses/mit", 66 | "node_id": "MDc6TGljZW5zZTEz" 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.approved_review_label.json: -------------------------------------------------------------------------------- 1 | { 2 | "pull_request": { 3 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 4 | "id": 534054584, 5 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 6 | "html_url": "https://github.com/electron/electron/pull/26876", 7 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 8 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 9 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 10 | "number": 26876, 11 | "state": "open", 12 | "locked": false, 13 | "title": "chore: fix JS linting", 14 | "user": { 15 | "login": "MarshallOfSound", 16 | "id": 6634592, 17 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=" 18 | }, 19 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 20 | "created_at": "2020-12-08T01:24:55Z", 21 | "updated_at": "2020-12-08T01:24:55Z", 22 | "closed_at": null, 23 | "merged_at": null, 24 | "merge_commit_sha": null, 25 | "assignee": null, 26 | "labels": [ 27 | { 28 | "id": 2020255394, 29 | "node_id": "MDU6TGFiZWwyMDIwMjU1Mzk0", 30 | "url": "https://api.github.com/repos/electron/electron/labels/api-review/approved%20%E2%9C%85", 31 | "name": "api-review/approved ✅", 32 | "color": "c918ba", 33 | "default": false, 34 | "description": "" 35 | }, 36 | { 37 | "id": 1034512799, 38 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 39 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 40 | "name": "semver/minor", 41 | "color": "6ac2dd", 42 | "default": false, 43 | "description": "backwards-incompatible bug fixes" 44 | } 45 | ], 46 | "base": { 47 | "repo": { 48 | "owner": { 49 | "login": "electron" 50 | } 51 | } 52 | }, 53 | "head": { 54 | "label": "electron:fix-lint-js", 55 | "ref": "fix-lint-js", 56 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 57 | "user": { 58 | "login": "electron", 59 | "id": 13409222, 60 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 61 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 62 | "gravatar_id": "", 63 | "url": "https://api.github.com/users/electron", 64 | "html_url": "https://github.com/electron", 65 | "followers_url": "https://api.github.com/users/electron/followers", 66 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 67 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 68 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 69 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 70 | "organizations_url": "https://api.github.com/users/electron/orgs", 71 | "repos_url": "https://api.github.com/users/electron/repos", 72 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 73 | "received_events_url": "https://api.github.com/users/electron/received_events", 74 | "type": "Organization", 75 | "site_admin": false 76 | }, 77 | "repo": { 78 | "id": 9384267, 79 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 80 | "name": "electron", 81 | "full_name": "electron/electron", 82 | "private": false, 83 | "owner": { 84 | "login": "electron", 85 | "id": 13409222, 86 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 87 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 88 | "gravatar_id": "", 89 | "url": "https://api.github.com/users/electron", 90 | "html_url": "https://github.com/electron", 91 | "followers_url": "https://api.github.com/users/electron/followers", 92 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 93 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 94 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 95 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 96 | "organizations_url": "https://api.github.com/users/electron/orgs", 97 | "repos_url": "https://api.github.com/users/electron/repos", 98 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 99 | "received_events_url": "https://api.github.com/users/electron/received_events", 100 | "type": "Organization", 101 | "site_admin": false 102 | } 103 | }, 104 | "repository": { 105 | "id": 9384267, 106 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 107 | "name": "electron", 108 | "full_name": "electron/electron", 109 | "private": false, 110 | "owner": { 111 | "login": "electron", 112 | "id": 13409222 113 | }, 114 | "license": { 115 | "key": "mit", 116 | "name": "MIT License", 117 | "spdx_id": "MIT", 118 | "url": "https://api.github.com/licenses/mit", 119 | "node_id": "MDc6TGljZW5zZTEz" 120 | } 121 | } 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.converted_to_draft.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "converted_to_draft", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 6 | "id": 534054584, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 8 | "html_url": "https://github.com/electron/electron/pull/26876", 9 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 10 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 11 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 12 | "number": 26876, 13 | "state": "open", 14 | "locked": false, 15 | "draft": true, 16 | "title": "chore: fix JS linting", 17 | "user": { 18 | "login": "MarshallOfSound", 19 | "id": 6634592, 20 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 21 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 22 | "gravatar_id": "", 23 | "url": "https://api.github.com/users/MarshallOfSound" 24 | }, 25 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 26 | "created_at": "2020-12-08T01:24:55Z", 27 | "updated_at": "2020-12-08T01:24:55Z", 28 | "closed_at": null, 29 | "merged_at": null, 30 | "merge_commit_sha": null, 31 | "assignee": null, 32 | "labels": [ 33 | { 34 | "id": 1034512799, 35 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 36 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 37 | "name": "semver/minor", 38 | "color": "6ac2dd", 39 | "default": false, 40 | "description": "backwards-compatible bug fixes" 41 | }, 42 | { 43 | "id": 1603621692, 44 | "node_id": "MDU6TGFiZWwxNjAzNjIxNjky", 45 | "url": "https://api.github.com/repos/electron/electron/labels/api-review/requested%20%F0%9F%97%B3", 46 | "name": "api-review/requested 🗳", 47 | "color": "c918ba", 48 | "default": false, 49 | "description": "" 50 | } 51 | ], 52 | "head": { 53 | "label": "electron:fix-lint-js", 54 | "ref": "fix-lint-js", 55 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 56 | "user": { 57 | "login": "electron", 58 | "id": 13409222 59 | }, 60 | "repo": { 61 | "id": 9384267, 62 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 63 | "name": "electron", 64 | "full_name": "electron/electron", 65 | "private": false, 66 | "owner": { 67 | "login": "electron", 68 | "id": 13409222 69 | } 70 | } 71 | }, 72 | "base": { 73 | "label": "electron:main", 74 | "ref": "main", 75 | "sha": "c41b8d536b2d886abbe739374c0a46f99242a894", 76 | "user": { 77 | "login": "electron", 78 | "id": 13409222, 79 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 80 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 81 | "gravatar_id": "", 82 | "url": "https://api.github.com/users/electron", 83 | "html_url": "https://github.com/electron", 84 | "followers_url": "https://api.github.com/users/electron/followers", 85 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 86 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 87 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 88 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 89 | "organizations_url": "https://api.github.com/users/electron/orgs", 90 | "repos_url": "https://api.github.com/users/electron/repos", 91 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 92 | "received_events_url": "https://api.github.com/users/electron/received_events", 93 | "type": "Organization", 94 | "site_admin": false 95 | }, 96 | "repo": { 97 | "id": 9384267, 98 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 99 | "name": "electron", 100 | "full_name": "electron/electron", 101 | "default_branch": "main", 102 | "private": false, 103 | "owner": { 104 | "login": "electron", 105 | "id": 13409222, 106 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 107 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 108 | "gravatar_id": "", 109 | "url": "https://api.github.com/users/electron", 110 | "html_url": "https://github.com/electron", 111 | "followers_url": "https://api.github.com/users/electron/followers", 112 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 113 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 114 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 115 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 116 | "organizations_url": "https://api.github.com/users/electron/orgs", 117 | "repos_url": "https://api.github.com/users/electron/repos", 118 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 119 | "received_events_url": "https://api.github.com/users/electron/received_events", 120 | "type": "Organization", 121 | "site_admin": false 122 | } 123 | } 124 | } 125 | }, 126 | "repository": { 127 | "id": 9384267, 128 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 129 | "name": "electron", 130 | "full_name": "electron/electron", 131 | "private": false, 132 | "owner": { 133 | "login": "electron", 134 | "id": 13409222 135 | }, 136 | "license": { 137 | "key": "mit", 138 | "name": "MIT License", 139 | "spdx_id": "MIT", 140 | "url": "https://api.github.com/licenses/mit", 141 | "node_id": "MDc6TGljZW5zZTEz" 142 | } 143 | }, 144 | "sender": { 145 | "login": "electron" 146 | } 147 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.declined_review_label.json: -------------------------------------------------------------------------------- 1 | { 2 | "pull_request": { 3 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 4 | "id": 534054584, 5 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 6 | "html_url": "https://github.com/electron/electron/pull/26876", 7 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 8 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 9 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 10 | "number": 26876, 11 | "state": "open", 12 | "locked": false, 13 | "title": "chore: fix JS linting", 14 | "user": { 15 | "login": "MarshallOfSound", 16 | "id": 6634592, 17 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=" 18 | }, 19 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 20 | "created_at": "2020-12-08T01:24:55Z", 21 | "updated_at": "2020-12-08T01:24:55Z", 22 | "closed_at": null, 23 | "merged_at": null, 24 | "merge_commit_sha": null, 25 | "assignee": null, 26 | "labels": [ 27 | { 28 | "id": 2036774075, 29 | "node_id": "MDU6TGFiZWwyMDM2Nzc0MDc1", 30 | "url": "https://api.github.com/repos/electron/electron/labels/api-review/declined%20%E2%9D%8C", 31 | "name": "api-review/declined ❌", 32 | "color": "c918ba", 33 | "default": false, 34 | "description": "" 35 | }, 36 | { 37 | "id": 1034512799, 38 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 39 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 40 | "name": "semver/minor", 41 | "color": "6ac2dd", 42 | "default": false, 43 | "description": "backwards-incompatible bug fixes" 44 | } 45 | ], 46 | "base": { 47 | "repo": { 48 | "owner": { 49 | "login": "electron" 50 | } 51 | } 52 | }, 53 | "head": { 54 | "label": "electron:fix-lint-js", 55 | "ref": "fix-lint-js", 56 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 57 | "user": { 58 | "login": "electron", 59 | "id": 13409222, 60 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 61 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 62 | "gravatar_id": "", 63 | "url": "https://api.github.com/users/electron", 64 | "html_url": "https://github.com/electron", 65 | "followers_url": "https://api.github.com/users/electron/followers", 66 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 67 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 68 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 69 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 70 | "organizations_url": "https://api.github.com/users/electron/orgs", 71 | "repos_url": "https://api.github.com/users/electron/repos", 72 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 73 | "received_events_url": "https://api.github.com/users/electron/received_events", 74 | "type": "Organization", 75 | "site_admin": false 76 | }, 77 | "repo": { 78 | "id": 9384267, 79 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 80 | "name": "electron", 81 | "full_name": "electron/electron", 82 | "private": false, 83 | "owner": { 84 | "login": "electron", 85 | "id": 13409222, 86 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 87 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 88 | "gravatar_id": "", 89 | "url": "https://api.github.com/users/electron", 90 | "html_url": "https://github.com/electron", 91 | "followers_url": "https://api.github.com/users/electron/followers", 92 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 93 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 94 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 95 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 96 | "organizations_url": "https://api.github.com/users/electron/orgs", 97 | "repos_url": "https://api.github.com/users/electron/repos", 98 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 99 | "received_events_url": "https://api.github.com/users/electron/received_events", 100 | "type": "Organization", 101 | "site_admin": false 102 | } 103 | }, 104 | "repository": { 105 | "id": 9384267, 106 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 107 | "name": "electron", 108 | "full_name": "electron/electron", 109 | "private": false, 110 | "owner": { 111 | "login": "electron", 112 | "id": 13409222 113 | }, 114 | "license": { 115 | "key": "mit", 116 | "name": "MIT License", 117 | "spdx_id": "MIT", 118 | "url": "https://api.github.com/licenses/mit", 119 | "node_id": "MDc6TGljZW5zZTEz" 120 | } 121 | } 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.labeled.exclusion_labels.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "labeled", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 6 | "id": 534054584, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 8 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 9 | "number": 26876, 10 | "state": "open", 11 | "locked": false, 12 | "title": "chore: fix JS linting", 13 | "user": { 14 | "login": "MarshallOfSound", 15 | "id": 6634592, 16 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=" 17 | }, 18 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 19 | "created_at": "2020-12-08T01:24:55Z", 20 | "updated_at": "2020-12-08T01:24:55Z", 21 | "closed_at": null, 22 | "merged_at": null, 23 | "merge_commit_sha": null, 24 | "assignee": null, 25 | "labels": [ 26 | { 27 | "id": 865096932, 28 | "node_id": "MDU6TGFiZWw4NjUwOTY5MzI=", 29 | "url": "https://api.github.com/repos/electron/electron/labels/backport", 30 | "name": "backport", 31 | "color": "ddddde", 32 | "default": false, 33 | "description": "This is a backport PR" 34 | } 35 | ], 36 | "base": { 37 | "repo": { 38 | "owner": { 39 | "login": "electron" 40 | } 41 | } 42 | }, 43 | "head": { 44 | "label": "electron:fix-lint-js", 45 | "ref": "fix-lint-js", 46 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 47 | "user": { 48 | "login": "electron", 49 | "id": 13409222 50 | }, 51 | "repo": { 52 | "id": 9384267, 53 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 54 | "name": "electron", 55 | "full_name": "electron/electron", 56 | "private": false, 57 | "owner": { 58 | "login": "electron", 59 | "id": 13409222 60 | } 61 | }, 62 | "repository": { 63 | "id": 9384267, 64 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 65 | "name": "electron", 66 | "full_name": "electron/electron", 67 | "private": false, 68 | "owner": { 69 | "login": "electron", 70 | "id": 13409222 71 | }, 72 | "license": { 73 | "key": "mit", 74 | "name": "MIT License", 75 | "spdx_id": "MIT", 76 | "url": "https://api.github.com/licenses/mit", 77 | "node_id": "MDc6TGljZW5zZTEz" 78 | } 79 | } 80 | } 81 | }, 82 | "repository": { 83 | "id": 9384267, 84 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 85 | "name": "electron", 86 | "full_name": "electron/electron", 87 | "private": false, 88 | "owner": { 89 | "login": "electron", 90 | "id": 13409222 91 | }, 92 | "license": { 93 | "key": "mit", 94 | "name": "MIT License", 95 | "spdx_id": "MIT", 96 | "url": "https://api.github.com/licenses/mit", 97 | "node_id": "MDc6TGljZW5zZTEz" 98 | } 99 | }, 100 | "label": { 101 | "id": 1034512799, 102 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 103 | "url": "https://api.github.com/repos/electron/electron/labels/semver/patch", 104 | "name": "semver/patch", 105 | "color": "6ac2dd", 106 | "default": false, 107 | "description": "backwards-compatible bug fixes" 108 | }, 109 | "sender": { 110 | "login": "electron" 111 | } 112 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.new-pr_label.json: -------------------------------------------------------------------------------- 1 | { 2 | "pull_request": { 3 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 4 | "id": 534054584, 5 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 6 | "html_url": "https://github.com/electron/electron/pull/26876", 7 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 8 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 9 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 10 | "number": 26876, 11 | "state": "open", 12 | "locked": false, 13 | "title": "chore: fix JS linting", 14 | "user": { 15 | "login": "MarshallOfSound", 16 | "id": 6634592, 17 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=" 18 | }, 19 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 20 | "created_at": "2020-12-08T01:24:55Z", 21 | "updated_at": "2020-12-08T01:24:55Z", 22 | "closed_at": null, 23 | "merged_at": null, 24 | "merge_commit_sha": null, 25 | "assignee": null, 26 | "labels": [ 27 | { 28 | "id": 1243058793, 29 | "node_id": "MDU6TGFiZWwxMjQzMDU4Nzkz", 30 | "url": "https://api.github.com/repos/electron/electron/labels/new-pr%20%F0%9F%8C%B1", 31 | "name": "new-pr 🌱", 32 | "color": "8af297", 33 | "default": false, 34 | "description": "PR opened in the last 24 hours" 35 | } 36 | ], 37 | "head": { 38 | "label": "electron:fix-lint-js", 39 | "ref": "fix-lint-js", 40 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 41 | "user": { 42 | "login": "electron", 43 | "id": 13409222, 44 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 45 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 46 | "gravatar_id": "", 47 | "url": "https://api.github.com/users/electron", 48 | "html_url": "https://github.com/electron", 49 | "followers_url": "https://api.github.com/users/electron/followers", 50 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 51 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 52 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 53 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 54 | "organizations_url": "https://api.github.com/users/electron/orgs", 55 | "repos_url": "https://api.github.com/users/electron/repos", 56 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 57 | "received_events_url": "https://api.github.com/users/electron/received_events", 58 | "type": "Organization", 59 | "site_admin": false 60 | }, 61 | "repo": { 62 | "id": 9384267, 63 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 64 | "name": "electron", 65 | "full_name": "electron/electron", 66 | "private": false, 67 | "owner": { 68 | "login": "electron", 69 | "id": 13409222, 70 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 71 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 72 | "gravatar_id": "", 73 | "url": "https://api.github.com/users/electron", 74 | "html_url": "https://github.com/electron", 75 | "followers_url": "https://api.github.com/users/electron/followers", 76 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 77 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 78 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 79 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 80 | "organizations_url": "https://api.github.com/users/electron/orgs", 81 | "repos_url": "https://api.github.com/users/electron/repos", 82 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 83 | "received_events_url": "https://api.github.com/users/electron/received_events", 84 | "type": "Organization", 85 | "site_admin": false 86 | } 87 | }, 88 | "repository": { 89 | "id": 9384267, 90 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 91 | "name": "electron", 92 | "full_name": "electron/electron", 93 | "private": false, 94 | "owner": { 95 | "login": "electron", 96 | "id": 13409222 97 | }, 98 | "license": { 99 | "key": "mit", 100 | "name": "MIT License", 101 | "spdx_id": "MIT", 102 | "url": "https://api.github.com/licenses/mit", 103 | "node_id": "MDc6TGljZW5zZTEz" 104 | } 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.no_review_label.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 6 | "id": 534054584, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 8 | "html_url": "https://github.com/electron/electron/pull/26876", 9 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 10 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 11 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 12 | "number": 26876, 13 | "state": "open", 14 | "locked": false, 15 | "title": "chore: fix JS linting", 16 | "user": { 17 | "login": "MarshallOfSound", 18 | "id": 6634592, 19 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=" 20 | }, 21 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 22 | "created_at": "2020-12-08T01:24:55Z", 23 | "updated_at": "2020-12-08T01:24:55Z", 24 | "closed_at": null, 25 | "merged_at": null, 26 | "merge_commit_sha": null, 27 | "assignee": null, 28 | "labels": [], 29 | "base": { 30 | "repo": { 31 | "owner": { 32 | "login": "electron" 33 | } 34 | } 35 | }, 36 | "head": { 37 | "label": "electron:fix-lint-js", 38 | "ref": "fix-lint-js", 39 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 40 | "user": { 41 | "login": "electron", 42 | "id": 13409222, 43 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 44 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 45 | "gravatar_id": "", 46 | "url": "https://api.github.com/users/electron", 47 | "html_url": "https://github.com/electron", 48 | "followers_url": "https://api.github.com/users/electron/followers", 49 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 50 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 51 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 52 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 53 | "organizations_url": "https://api.github.com/users/electron/orgs", 54 | "repos_url": "https://api.github.com/users/electron/repos", 55 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 56 | "received_events_url": "https://api.github.com/users/electron/received_events", 57 | "type": "Organization", 58 | "site_admin": false 59 | }, 60 | "repo": { 61 | "id": 9384267, 62 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 63 | "name": "electron", 64 | "full_name": "electron/electron", 65 | "private": false, 66 | "owner": { 67 | "login": "electron", 68 | "id": 13409222, 69 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 70 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 71 | "gravatar_id": "", 72 | "url": "https://api.github.com/users/electron", 73 | "html_url": "https://github.com/electron", 74 | "followers_url": "https://api.github.com/users/electron/followers", 75 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 76 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 77 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 78 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 79 | "organizations_url": "https://api.github.com/users/electron/orgs", 80 | "repos_url": "https://api.github.com/users/electron/repos", 81 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 82 | "received_events_url": "https://api.github.com/users/electron/received_events", 83 | "type": "Organization", 84 | "site_admin": false 85 | } 86 | }, 87 | "repository": { 88 | "id": 9384267, 89 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 90 | "name": "electron", 91 | "full_name": "electron/electron", 92 | "private": false, 93 | "owner": { 94 | "login": "electron", 95 | "id": 13409222 96 | }, 97 | "license": { 98 | "key": "mit", 99 | "name": "MIT License", 100 | "spdx_id": "MIT", 101 | "url": "https://api.github.com/licenses/mit", 102 | "node_id": "MDc6TGljZW5zZTEz" 103 | } 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.ready_for_review.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "ready_for_review", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 6 | "id": 534054584, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 8 | "html_url": "https://github.com/electron/electron/pull/26876", 9 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 10 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 11 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 12 | "number": 26876, 13 | "state": "open", 14 | "locked": false, 15 | "draft": false, 16 | "title": "chore: fix JS linting", 17 | "user": { 18 | "login": "MarshallOfSound", 19 | "id": 6634592, 20 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 21 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 22 | "gravatar_id": "", 23 | "url": "https://api.github.com/users/MarshallOfSound" 24 | }, 25 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 26 | "created_at": "2020-12-08T01:24:55Z", 27 | "updated_at": "2020-12-08T01:24:55Z", 28 | "closed_at": null, 29 | "merged_at": null, 30 | "merge_commit_sha": null, 31 | "assignee": null, 32 | "labels": [ 33 | { 34 | "id": 1034512799, 35 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 36 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 37 | "name": "semver/minor", 38 | "color": "6ac2dd", 39 | "default": false, 40 | "description": "backwards-compatible bug fixes" 41 | } 42 | ], 43 | "head": { 44 | "label": "electron:fix-lint-js", 45 | "ref": "fix-lint-js", 46 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 47 | "user": { 48 | "login": "electron", 49 | "id": 13409222 50 | }, 51 | "repo": { 52 | "id": 9384267, 53 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 54 | "name": "electron", 55 | "full_name": "electron/electron", 56 | "private": false, 57 | "owner": { 58 | "login": "electron", 59 | "id": 13409222 60 | } 61 | } 62 | }, 63 | "base": { 64 | "label": "electron:main", 65 | "ref": "main", 66 | "sha": "c41b8d536b2d886abbe739374c0a46f99242a894", 67 | "user": { 68 | "login": "electron", 69 | "id": 13409222, 70 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 71 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 72 | "gravatar_id": "", 73 | "url": "https://api.github.com/users/electron", 74 | "html_url": "https://github.com/electron", 75 | "followers_url": "https://api.github.com/users/electron/followers", 76 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 77 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 78 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 79 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 80 | "organizations_url": "https://api.github.com/users/electron/orgs", 81 | "repos_url": "https://api.github.com/users/electron/repos", 82 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 83 | "received_events_url": "https://api.github.com/users/electron/received_events", 84 | "type": "Organization", 85 | "site_admin": false 86 | }, 87 | "repo": { 88 | "id": 9384267, 89 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 90 | "name": "electron", 91 | "full_name": "electron/electron", 92 | "default_branch": "main", 93 | "private": false, 94 | "owner": { 95 | "login": "electron", 96 | "id": 13409222, 97 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 98 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 99 | "gravatar_id": "", 100 | "url": "https://api.github.com/users/electron", 101 | "html_url": "https://github.com/electron", 102 | "followers_url": "https://api.github.com/users/electron/followers", 103 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 104 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 105 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 106 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 107 | "organizations_url": "https://api.github.com/users/electron/orgs", 108 | "repos_url": "https://api.github.com/users/electron/repos", 109 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 110 | "received_events_url": "https://api.github.com/users/electron/received_events", 111 | "type": "Organization", 112 | "site_admin": false 113 | } 114 | } 115 | } 116 | }, 117 | "repository": { 118 | "id": 9384267, 119 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 120 | "name": "electron", 121 | "full_name": "electron/electron", 122 | "private": false, 123 | "owner": { 124 | "login": "electron", 125 | "id": 13409222 126 | }, 127 | "license": { 128 | "key": "mit", 129 | "name": "MIT License", 130 | "spdx_id": "MIT", 131 | "url": "https://api.github.com/licenses/mit", 132 | "node_id": "MDc6TGljZW5zZTEz" 133 | } 134 | }, 135 | "sender": { 136 | "login": "electron" 137 | } 138 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.requested_review_label.json: -------------------------------------------------------------------------------- 1 | { 2 | "pull_request": { 3 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 4 | "id": 534054584, 5 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 6 | "html_url": "https://github.com/electron/electron/pull/26876", 7 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 8 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 9 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 10 | "number": 26876, 11 | "state": "open", 12 | "locked": false, 13 | "title": "chore: fix JS linting", 14 | "user": { 15 | "login": "MarshallOfSound", 16 | "id": 6634592, 17 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=" 18 | }, 19 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 20 | "created_at": "2020-12-08T01:24:55Z", 21 | "updated_at": "2020-12-08T01:24:55Z", 22 | "closed_at": null, 23 | "merged_at": null, 24 | "merge_commit_sha": null, 25 | "assignee": null, 26 | "labels": [ 27 | { 28 | "id": 1603621692, 29 | "node_id": "MDU6TGFiZWwxNjAzNjIxNjky", 30 | "url": "https://api.github.com/repos/electron/electron/labels/api-review/requested%20%F0%9F%97%B3", 31 | "name": "api-review/requested 🗳", 32 | "color": "c918ba", 33 | "default": false, 34 | "description": "" 35 | }, 36 | { 37 | "id": 1034512799, 38 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 39 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 40 | "name": "semver/minor", 41 | "color": "6ac2dd", 42 | "default": false, 43 | "description": "backwards-incompatible bug fixes" 44 | } 45 | ], 46 | "base": { 47 | "repo": { 48 | "owner": { 49 | "login": "electron" 50 | } 51 | } 52 | }, 53 | "head": { 54 | "label": "electron:fix-lint-js", 55 | "ref": "fix-lint-js", 56 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 57 | "user": { 58 | "login": "electron", 59 | "id": 13409222, 60 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 61 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 62 | "gravatar_id": "", 63 | "url": "https://api.github.com/users/electron", 64 | "html_url": "https://github.com/electron", 65 | "followers_url": "https://api.github.com/users/electron/followers", 66 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 67 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 68 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 69 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 70 | "organizations_url": "https://api.github.com/users/electron/orgs", 71 | "repos_url": "https://api.github.com/users/electron/repos", 72 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 73 | "received_events_url": "https://api.github.com/users/electron/received_events", 74 | "type": "Organization", 75 | "site_admin": false 76 | }, 77 | "repo": { 78 | "id": 9384267, 79 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 80 | "name": "electron", 81 | "full_name": "electron/electron", 82 | "private": false, 83 | "owner": { 84 | "login": "electron", 85 | "id": 13409222, 86 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 87 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 88 | "gravatar_id": "", 89 | "url": "https://api.github.com/users/electron", 90 | "html_url": "https://github.com/electron", 91 | "followers_url": "https://api.github.com/users/electron/followers", 92 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 93 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 94 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 95 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 96 | "organizations_url": "https://api.github.com/users/electron/orgs", 97 | "repos_url": "https://api.github.com/users/electron/repos", 98 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 99 | "received_events_url": "https://api.github.com/users/electron/received_events", 100 | "type": "Organization", 101 | "site_admin": false 102 | } 103 | }, 104 | "repository": { 105 | "id": 9384267, 106 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 107 | "name": "electron", 108 | "full_name": "electron/electron", 109 | "private": false, 110 | "owner": { 111 | "login": "electron", 112 | "id": 13409222 113 | }, 114 | "license": { 115 | "key": "mit", 116 | "name": "MIT License", 117 | "spdx_id": "MIT", 118 | "url": "https://api.github.com/licenses/mit", 119 | "node_id": "MDc6TGljZW5zZTEz" 120 | } 121 | } 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.semver-minor.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "labeled", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 6 | "id": 534054584, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 8 | "html_url": "https://github.com/electron/electron/pull/26876", 9 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 10 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 11 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 12 | "number": 26876, 13 | "state": "open", 14 | "locked": false, 15 | "title": "chore: fix JS linting", 16 | "user": { 17 | "login": "MarshallOfSound", 18 | "id": 6634592, 19 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 20 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 21 | "gravatar_id": "", 22 | "url": "https://api.github.com/users/MarshallOfSound" 23 | }, 24 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 25 | "created_at": "2020-12-08T01:24:55Z", 26 | "updated_at": "2020-12-08T01:24:55Z", 27 | "closed_at": null, 28 | "merged_at": null, 29 | "merge_commit_sha": null, 30 | "assignee": null, 31 | "labels": [ 32 | { 33 | "id": 1034512799, 34 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 35 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 36 | "name": "semver/minor", 37 | "color": "6ac2dd", 38 | "default": false, 39 | "description": "backwards-incompatible bug fixes" 40 | } 41 | ], 42 | "head": { 43 | "label": "electron:fix-lint-js", 44 | "ref": "fix-lint-js", 45 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 46 | "user": { 47 | "login": "electron", 48 | "id": 13409222 49 | }, 50 | "repo": { 51 | "id": 9384267, 52 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 53 | "name": "electron", 54 | "full_name": "electron/electron", 55 | "private": false, 56 | "owner": { 57 | "login": "electron", 58 | "id": 13409222 59 | } 60 | } 61 | }, 62 | "base": { 63 | "label": "electron:main", 64 | "ref": "main", 65 | "sha": "c41b8d536b2d886abbe739374c0a46f99242a894", 66 | "user": { 67 | "login": "electron", 68 | "id": 13409222, 69 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 70 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 71 | "gravatar_id": "", 72 | "url": "https://api.github.com/users/electron", 73 | "html_url": "https://github.com/electron", 74 | "followers_url": "https://api.github.com/users/electron/followers", 75 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 76 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 77 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 78 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 79 | "organizations_url": "https://api.github.com/users/electron/orgs", 80 | "repos_url": "https://api.github.com/users/electron/repos", 81 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 82 | "received_events_url": "https://api.github.com/users/electron/received_events", 83 | "type": "Organization", 84 | "site_admin": false 85 | }, 86 | "repo": { 87 | "id": 9384267, 88 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 89 | "name": "electron", 90 | "full_name": "electron/electron", 91 | "default_branch": "main", 92 | "private": false, 93 | "owner": { 94 | "login": "electron", 95 | "id": 13409222, 96 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 97 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 98 | "gravatar_id": "", 99 | "url": "https://api.github.com/users/electron", 100 | "html_url": "https://github.com/electron", 101 | "followers_url": "https://api.github.com/users/electron/followers", 102 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 103 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 104 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 105 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 106 | "organizations_url": "https://api.github.com/users/electron/orgs", 107 | "repos_url": "https://api.github.com/users/electron/repos", 108 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 109 | "received_events_url": "https://api.github.com/users/electron/received_events", 110 | "type": "Organization", 111 | "site_admin": false 112 | } 113 | } 114 | } 115 | }, 116 | "repository": { 117 | "id": 9384267, 118 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 119 | "name": "electron", 120 | "full_name": "electron/electron", 121 | "private": false, 122 | "owner": { 123 | "login": "electron", 124 | "id": 13409222 125 | }, 126 | "license": { 127 | "key": "mit", 128 | "name": "MIT License", 129 | "spdx_id": "MIT", 130 | "url": "https://api.github.com/licenses/mit", 131 | "node_id": "MDc6TGljZW5zZTEz" 132 | } 133 | }, 134 | "label": { 135 | "id": 1034512799, 136 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 137 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 138 | "name": "semver/minor", 139 | "color": "6ac2dd", 140 | "default": false, 141 | "description": "backwards-incompatible bug fixes" 142 | }, 143 | "sender": { 144 | "login": "electron" 145 | } 146 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.semver-patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 3 | "id": 534054584, 4 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 5 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 6 | "number": 26876, 7 | "state": "closed", 8 | "locked": false, 9 | "title": "build: fix JS linting", 10 | "user": { 11 | "login": "MarshallOfSound" 12 | }, 13 | "labels": [ 14 | { 15 | "id": 1034512799, 16 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 17 | "url": "https://api.github.com/repos/electron/electron/labels/semver/patch", 18 | "name": "semver/patch", 19 | "color": "6ac2dd", 20 | "default": false, 21 | "description": "backwards-compatible bug fixes" 22 | } 23 | ], 24 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 25 | "created_at": "2020-12-08T01:24:55Z", 26 | "updated_at": "2020-12-10T18:57:11Z", 27 | "closed_at": "2020-12-10T18:57:07Z", 28 | "merged_at": "2020-12-10T18:57:07Z", 29 | "merge_commit_sha": "51db2a6b34792c99a9a685bdfbfe87a7343631b9", 30 | "assignee": null, 31 | "milestone": null, 32 | "draft": false, 33 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 34 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 35 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 36 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 37 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/c6b1b7168ab850a47f856c4a30f7a441bede1117", 38 | "head": { 39 | "label": "electron:fix-lint-js", 40 | "ref": "fix-lint-js", 41 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 42 | "user": { 43 | "login": "electron", 44 | "id": 13409222, 45 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 46 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 47 | "gravatar_id": "", 48 | "url": "https://api.github.com/users/electron" 49 | }, 50 | "repo": { 51 | "id": 9384267, 52 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 53 | "name": "electron", 54 | "full_name": "electron/electron", 55 | "private": false, 56 | "owner": { 57 | "login": "electron", 58 | "id": 13409222, 59 | "url": "https://api.github.com/users/electron" 60 | }, 61 | "license": { 62 | "key": "mit", 63 | "name": "MIT License", 64 | "spdx_id": "MIT", 65 | "url": "https://api.github.com/licenses/mit", 66 | "node_id": "MDc6TGljZW5zZTEz" 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request.unlabeled.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "unlabeled", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 6 | "id": 534054584, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 8 | "html_url": "https://github.com/electron/electron/pull/26876", 9 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 10 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 11 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 12 | "number": 26876, 13 | "state": "open", 14 | "locked": false, 15 | "title": "chore: fix JS linting", 16 | "user": { 17 | "login": "MarshallOfSound", 18 | "id": 6634592, 19 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 20 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 21 | "gravatar_id": "", 22 | "url": "https://api.github.com/users/MarshallOfSound" 23 | }, 24 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 25 | "created_at": "2020-12-08T01:24:55Z", 26 | "updated_at": "2020-12-08T01:24:55Z", 27 | "closed_at": null, 28 | "merged_at": null, 29 | "merge_commit_sha": null, 30 | "assignee": null, 31 | "labels": [ 32 | { 33 | "id": 1034512799, 34 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 35 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 36 | "name": "semver/minor", 37 | "color": "6ac2dd", 38 | "default": false, 39 | "description": "backwards-incompatible bug fixes" 40 | } 41 | ], 42 | "head": { 43 | "label": "electron:fix-lint-js", 44 | "ref": "fix-lint-js", 45 | "sha": "c6b1b7168ab850a47f856c4a30f7a441bede1117", 46 | "user": { 47 | "login": "electron", 48 | "id": 13409222 49 | }, 50 | "repo": { 51 | "id": 9384267, 52 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 53 | "name": "electron", 54 | "full_name": "electron/electron", 55 | "private": false, 56 | "owner": { 57 | "login": "electron", 58 | "id": 13409222 59 | } 60 | } 61 | }, 62 | "base": { 63 | "label": "electron:main", 64 | "ref": "main", 65 | "sha": "c41b8d536b2d886abbe739374c0a46f99242a894", 66 | "user": { 67 | "login": "electron", 68 | "id": 13409222, 69 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 70 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 71 | "gravatar_id": "", 72 | "url": "https://api.github.com/users/electron", 73 | "html_url": "https://github.com/electron", 74 | "followers_url": "https://api.github.com/users/electron/followers", 75 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 76 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 77 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 78 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 79 | "organizations_url": "https://api.github.com/users/electron/orgs", 80 | "repos_url": "https://api.github.com/users/electron/repos", 81 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 82 | "received_events_url": "https://api.github.com/users/electron/received_events", 83 | "type": "Organization", 84 | "site_admin": false 85 | }, 86 | "repo": { 87 | "id": 9384267, 88 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 89 | "name": "electron", 90 | "full_name": "electron/electron", 91 | "default_branch": "main", 92 | "private": false, 93 | "owner": { 94 | "login": "electron", 95 | "id": 13409222, 96 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 97 | "avatar_url": "https://avatars.githubusercontent.com/u/13409222?v=4", 98 | "gravatar_id": "", 99 | "url": "https://api.github.com/users/electron", 100 | "html_url": "https://github.com/electron", 101 | "followers_url": "https://api.github.com/users/electron/followers", 102 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 103 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 104 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 105 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 106 | "organizations_url": "https://api.github.com/users/electron/orgs", 107 | "repos_url": "https://api.github.com/users/electron/repos", 108 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 109 | "received_events_url": "https://api.github.com/users/electron/received_events", 110 | "type": "Organization", 111 | "site_admin": false 112 | } 113 | } 114 | } 115 | }, 116 | "repository": { 117 | "id": 9384267, 118 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 119 | "name": "electron", 120 | "full_name": "electron/electron", 121 | "private": false, 122 | "owner": { 123 | "login": "electron", 124 | "id": 13409222 125 | }, 126 | "license": { 127 | "key": "mit", 128 | "name": "MIT License", 129 | "spdx_id": "MIT", 130 | "url": "https://api.github.com/licenses/mit", 131 | "node_id": "MDc6TGljZW5zZTEz" 132 | } 133 | }, 134 | "label": { 135 | "id": 2020255394, 136 | "node_id": "MDU6TGFiZWwyMDIwMjU1Mzk0", 137 | "url": "https://api.github.com/repos/electron/electron/labels/api-review/approved%20%E2%9C%85", 138 | "name": "api-review/approved ✅", 139 | "color": "c918ba", 140 | "default": false, 141 | "description": "" 142 | }, 143 | "sender": { 144 | "login": "electron" 145 | } 146 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request_review/base/comment_neutral.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "node_id": "MDEyOklzc3VlQ29tbWVudDE=", 4 | "url": "https://api.github.com/repos/electron/electron/issues/comments/1", 5 | "html_url": "https://api.github.com/repos/electron/electron/issues/36279#issuecomment-1", 6 | "body": "Me too", 7 | "user": { 8 | "login": "jkleinsc", 9 | "id": 1, 10 | "node_id": "MDQ6VXNlcjE=", 11 | "avatar_url": "https://github.com/images/error/jkleinsc_happy.gif", 12 | "gravatar_id": "", 13 | "url": "https://api.github.com/users/jkleinsc", 14 | "html_url": "https://github.com/jkleinsc", 15 | "followers_url": "https://api.github.com/users/jkleinsc/followers", 16 | "following_url": "https://api.github.com/users/jkleinsc/following{/other_user}", 17 | "gists_url": "https://api.github.com/users/jkleinsc/gists{/gist_id}", 18 | "starred_url": "https://api.github.com/users/jkleinsc/starred{/owner}{/repo}", 19 | "subscriptions_url": "https://api.github.com/users/jkleinsc/subscriptions", 20 | "organizations_url": "https://api.github.com/users/jkleinsc/orgs", 21 | "repos_url": "https://api.github.com/users/jkleinsc/repos", 22 | "events_url": "https://api.github.com/users/jkleinsc/events{/privacy}", 23 | "received_events_url": "https://api.github.com/users/jkleinsc/received_events", 24 | "type": "User", 25 | "site_admin": false 26 | }, 27 | "created_at": "2022-04-14T16:00:49Z", 28 | "updated_at": "2022-04-14T16:00:49Z", 29 | "issue_url": "https://api.github.com/repos/electron/electron/issues/36279", 30 | "author_association": "COLLABORATOR" 31 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request_review/base/review_declined.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1173610858, 3 | "node_id": "PRR_kwDOAI8xS85F8-Fq", 4 | "user": { 5 | "login": "jkleinsc", 6 | "id": 2036040, 7 | "node_id": "MDQ6VXNlcjIwMzYwNDA=", 8 | "avatar_url": "https://avatars.githubusercontent.com/u/2036040?v=4", 9 | "gravatar_id": "", 10 | "url": "https://api.github.com/users/jkleinsc", 11 | "html_url": "https://github.com/jkleinsc", 12 | "followers_url": "https://api.github.com/users/jkleinsc/followers", 13 | "following_url": "https://api.github.com/users/jkleinsc/following{/other_user}", 14 | "gists_url": "https://api.github.com/users/jkleinsc/gists{/gist_id}", 15 | "starred_url": "https://api.github.com/users/jkleinsc/starred{/owner}{/repo}", 16 | "subscriptions_url": "https://api.github.com/users/jkleinsc/subscriptions", 17 | "organizations_url": "https://api.github.com/users/jkleinsc/orgs", 18 | "repos_url": "https://api.github.com/users/jkleinsc/repos", 19 | "events_url": "https://api.github.com/users/jkleinsc/events{/privacy}", 20 | "received_events_url": "https://api.github.com/users/jkleinsc/received_events", 21 | "type": "User", 22 | "site_admin": false 23 | }, 24 | "body": "API DECLINED", 25 | "commit_id": "eeebabc362aab804afa5885188a1712f0125f9f8", 26 | "submitted_at": "2022-11-09T08:58:00Z", 27 | "state": "commented", 28 | "html_url": "https://github.com/electron/electron/pull/36279#pullrequestreview-1173610858", 29 | "pull_request_url": "https://api.github.com/repos/electron/electron/pulls/36279", 30 | "author_association": "MEMBER", 31 | "_links": { 32 | "html": { 33 | "href": "https://github.com/electron/electron/pull/36279#pullrequestreview-1173610858" 34 | }, 35 | "pull_request": { 36 | "href": "https://api.github.com/repos/electron/electron/pulls/36279" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request_review/base/review_lgtm.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1173610858, 3 | "node_id": "PRR_kwDOAI8xS85F8-Fq", 4 | "user": { 5 | "login": "codebytere", 6 | "id": 2036040, 7 | "node_id": "MDQ6VXNlcjIwMzYwNDA=", 8 | "avatar_url": "https://avatars.githubusercontent.com/u/2036040?v=4", 9 | "gravatar_id": "", 10 | "url": "https://api.github.com/users/codebytere", 11 | "html_url": "https://github.com/codebytere", 12 | "followers_url": "https://api.github.com/users/codebytere/followers", 13 | "following_url": "https://api.github.com/users/codebytere/following{/other_user}", 14 | "gists_url": "https://api.github.com/users/codebytere/gists{/gist_id}", 15 | "starred_url": "https://api.github.com/users/codebytere/starred{/owner}{/repo}", 16 | "subscriptions_url": "https://api.github.com/users/codebytere/subscriptions", 17 | "organizations_url": "https://api.github.com/users/codebytere/orgs", 18 | "repos_url": "https://api.github.com/users/codebytere/repos", 19 | "events_url": "https://api.github.com/users/codebytere/events{/privacy}", 20 | "received_events_url": "https://api.github.com/users/codebytere/received_events", 21 | "type": "User", 22 | "site_admin": false 23 | }, 24 | "body": "API LGTM", 25 | "commit_id": "eeebabc362aab804afa5885188a1712f0125f9f8", 26 | "submitted_at": "2022-11-09T08:58:00Z", 27 | "state": "commented", 28 | "html_url": "https://github.com/electron/electron/pull/36279#pullrequestreview-1173610858", 29 | "pull_request_url": "https://api.github.com/repos/electron/electron/pulls/36279", 30 | "author_association": "MEMBER", 31 | "_links": { 32 | "html": { 33 | "href": "https://github.com/electron/electron/pull/36279#pullrequestreview-1173610858" 34 | }, 35 | "pull_request": { 36 | "href": "https://api.github.com/repos/electron/electron/pulls/36279" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request_review/base/review_lgtm_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 11736108589, 3 | "node_id": "PRR_kwDOAI8xS85F8-Fq", 4 | "user": { 5 | "login": "nornagon", 6 | "id": 2036041, 7 | "node_id": "MDQ6VXNlcjIwMzYwNDA=", 8 | "avatar_url": "https://avatars.githubusercontent.com/u/2036041?v=4", 9 | "gravatar_id": "", 10 | "url": "https://api.github.com/users/nornagon", 11 | "html_url": "https://github.com/nornagon", 12 | "followers_url": "https://api.github.com/users/nornagon/followers", 13 | "following_url": "https://api.github.com/users/nornagon/following{/other_user}", 14 | "gists_url": "https://api.github.com/users/nornagon/gists{/gist_id}", 15 | "starred_url": "https://api.github.com/users/nornagon/starred{/owner}{/repo}", 16 | "subscriptions_url": "https://api.github.com/users/nornagon/subscriptions", 17 | "organizations_url": "https://api.github.com/users/nornagon/orgs", 18 | "repos_url": "https://api.github.com/users/nornagon/repos", 19 | "events_url": "https://api.github.com/users/nornagon/events{/privacy}", 20 | "received_events_url": "https://api.github.com/users/nornagon/received_events", 21 | "type": "User", 22 | "site_admin": false 23 | }, 24 | "body": "API LGTM", 25 | "commit_id": "eeebabc362aab804afa5885188a1712f0125f9f8", 26 | "submitted_at": "2022-11-09T08:58:00Z", 27 | "state": "commented", 28 | "html_url": "https://github.com/electron/electron/pull/36279#pullrequestreview-1173610858", 29 | "pull_request_url": "https://api.github.com/repos/electron/electron/pulls/36279", 30 | "author_association": "MEMBER", 31 | "_links": { 32 | "html": { 33 | "href": "https://github.com/electron/electron/pull/36279#pullrequestreview-1173610858" 34 | }, 35 | "pull_request": { 36 | "href": "https://api.github.com/repos/electron/electron/pulls/36279" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request_review/fork/comment_neutral.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "node_id": "MDEyOklzc3VlQ29tbWVudDE=", 4 | "url": "https://api.github.com/repos/electron/electron/issues/comments/1", 5 | "html_url": "https://api.github.com/repos/electron/electron/issues/36035#issuecomment-1", 6 | "body": "Me too", 7 | "user": { 8 | "login": "jkleinsc", 9 | "id": 1, 10 | "node_id": "MDQ6VXNlcjE=", 11 | "avatar_url": "https://github.com/images/error/jkleinsc_happy.gif", 12 | "gravatar_id": "", 13 | "url": "https://api.github.com/users/jkleinsc", 14 | "html_url": "https://github.com/jkleinsc", 15 | "followers_url": "https://api.github.com/users/jkleinsc/followers", 16 | "following_url": "https://api.github.com/users/jkleinsc/following{/other_user}", 17 | "gists_url": "https://api.github.com/users/jkleinsc/gists{/gist_id}", 18 | "starred_url": "https://api.github.com/users/jkleinsc/starred{/owner}{/repo}", 19 | "subscriptions_url": "https://api.github.com/users/jkleinsc/subscriptions", 20 | "organizations_url": "https://api.github.com/users/jkleinsc/orgs", 21 | "repos_url": "https://api.github.com/users/jkleinsc/repos", 22 | "events_url": "https://api.github.com/users/jkleinsc/events{/privacy}", 23 | "received_events_url": "https://api.github.com/users/jkleinsc/received_events", 24 | "type": "User", 25 | "site_admin": false 26 | }, 27 | "created_at": "2022-04-14T16:00:49Z", 28 | "updated_at": "2022-04-14T16:00:49Z", 29 | "issue_url": "https://api.github.com/repos/electron/electron/issues/36035", 30 | "author_association": "COLLABORATOR" 31 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request_review/fork/review_declined.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1173610858, 3 | "node_id": "PRR_kwDOAI8xS85F8-Fq", 4 | "user": { 5 | "login": "nornagon", 6 | "id": 2036042, 7 | "node_id": "MDQ6VXNlcjIwMzYwNDA=", 8 | "avatar_url": "https://avatars.githubusercontent.com/u/2036040?v=4", 9 | "gravatar_id": "", 10 | "url": "https://api.github.com/users/nornagon", 11 | "html_url": "https://github.com/nornagon", 12 | "followers_url": "https://api.github.com/users/nornagon/followers", 13 | "following_url": "https://api.github.com/users/nornagon/following{/other_user}", 14 | "gists_url": "https://api.github.com/users/nornagon/gists{/gist_id}", 15 | "starred_url": "https://api.github.com/users/nornagon/starred{/owner}{/repo}", 16 | "subscriptions_url": "https://api.github.com/users/nornagon/subscriptions", 17 | "organizations_url": "https://api.github.com/users/nornagon/orgs", 18 | "repos_url": "https://api.github.com/users/nornagon/repos", 19 | "events_url": "https://api.github.com/users/nornagon/events{/privacy}", 20 | "received_events_url": "https://api.github.com/users/nornagon/received_events", 21 | "type": "User", 22 | "site_admin": false 23 | }, 24 | "body": "API DECLINED", 25 | "commit_id": "eeebabc362aab804afa5885188a1712f0125f9f8", 26 | "submitted_at": "2022-11-09T08:58:00Z", 27 | "state": "commented", 28 | "html_url": "https://github.com/electron/electron/pull/36035#pullrequestreview-1173610858", 29 | "pull_request_url": "https://api.github.com/repos/electron/electron/pulls/36035", 30 | "author_association": "MEMBER", 31 | "_links": { 32 | "html": { 33 | "href": "https://github.com/electron/electron/pull/36035#pullrequestreview-1173610858" 34 | }, 35 | "pull_request": { 36 | "href": "https://api.github.com/repos/electron/electron/pulls/36035" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request_review/fork/review_lgtm.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1173610858, 3 | "node_id": "PRR_kwDOAI8xS85F8-Fq", 4 | "user": { 5 | "login": "codebytere", 6 | "id": 2036043, 7 | "node_id": "MDQ6VXNlcjIwMzYwNDA=", 8 | "avatar_url": "https://avatars.githubusercontent.com/u/2036043?v=4", 9 | "gravatar_id": "", 10 | "url": "https://api.github.com/users/codebytere", 11 | "html_url": "https://github.com/codebytere", 12 | "followers_url": "https://api.github.com/users/codebytere/followers", 13 | "following_url": "https://api.github.com/users/codebytere/following{/other_user}", 14 | "gists_url": "https://api.github.com/users/codebytere/gists{/gist_id}", 15 | "starred_url": "https://api.github.com/users/codebytere/starred{/owner}{/repo}", 16 | "subscriptions_url": "https://api.github.com/users/codebytere/subscriptions", 17 | "organizations_url": "https://api.github.com/users/codebytere/orgs", 18 | "repos_url": "https://api.github.com/users/codebytere/repos", 19 | "events_url": "https://api.github.com/users/codebytere/events{/privacy}", 20 | "received_events_url": "https://api.github.com/users/codebytere/received_events", 21 | "type": "User", 22 | "site_admin": false 23 | }, 24 | "body": "API LGTM", 25 | "commit_id": "eeebabc362aab804afa5885188a1712f0125f9f8", 26 | "submitted_at": "2022-11-09T08:58:00Z", 27 | "state": "commented", 28 | "html_url": "https://github.com/electron/electron/pull/36035#pullrequestreview-1173610858", 29 | "pull_request_url": "https://api.github.com/repos/electron/electron/pulls/36035", 30 | "author_association": "MEMBER", 31 | "_links": { 32 | "html": { 33 | "href": "https://github.com/electron/electron/pull/36035#pullrequestreview-1173610858" 34 | }, 35 | "pull_request": { 36 | "href": "https://api.github.com/repos/electron/electron/pulls/36035" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /spec/fixtures/api-review-state/pull_request_review/fork/review_lgtm_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1173610858, 3 | "node_id": "PRR_kwDOAI8xS85F8-Fq", 4 | "user": { 5 | "login": "nornagon", 6 | "id": 2036041, 7 | "node_id": "MDQ6VXNlcjIwMzYwNDA=", 8 | "avatar_url": "https://avatars.githubusercontent.com/u/2036040?v=4", 9 | "gravatar_id": "", 10 | "url": "https://api.github.com/users/nornagon", 11 | "html_url": "https://github.com/nornagon", 12 | "followers_url": "https://api.github.com/users/nornagon/followers", 13 | "following_url": "https://api.github.com/users/nornagon/following{/other_user}", 14 | "gists_url": "https://api.github.com/users/nornagon/gists{/gist_id}", 15 | "starred_url": "https://api.github.com/users/nornagon/starred{/owner}{/repo}", 16 | "subscriptions_url": "https://api.github.com/users/nornagon/subscriptions", 17 | "organizations_url": "https://api.github.com/users/nornagon/orgs", 18 | "repos_url": "https://api.github.com/users/nornagon/repos", 19 | "events_url": "https://api.github.com/users/nornagon/events{/privacy}", 20 | "received_events_url": "https://api.github.com/users/nornagon/received_events", 21 | "type": "User", 22 | "site_admin": false 23 | }, 24 | "body": "API LGTM", 25 | "commit_id": "eeebabc362aab804afa5885188a1712f0125f9f8", 26 | "submitted_at": "2022-11-09T08:58:00Z", 27 | "state": "commented", 28 | "html_url": "https://github.com/electron/electron/pull/36035#pullrequestreview-1173610858", 29 | "pull_request_url": "https://api.github.com/repos/electron/electron/pulls/36035", 30 | "author_association": "MEMBER", 31 | "_links": { 32 | "html": { 33 | "href": "https://github.com/electron/electron/pull/36035#pullrequestreview-1173610858" 34 | }, 35 | "pull_request": { 36 | "href": "https://api.github.com/repos/electron/electron/pulls/36035" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /spec/fixtures/deprecation-review-state/issue_comment.checklist_complete.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "edited", 3 | "changes": { 4 | "body": { 5 | "from": "## 🪦 Deprecation Checklist\r\n\r\n### 🔥 New deprecations in this PR\r\n\r\n- [x] 📢 Are called out in [`docs/breaking-changes.md`][]\r\n- [x] ⚠️ Use the deprecation helpers in [`lib/common/deprecate.ts`](https://github.com/electron/electron/blob/main/lib/common/deprecate.ts) to warn about usage (including events)\r\n- [ ] 📝 Are marked as deprecated in the docs, using `_Deprecated_` (including properties and events)\r\n\r\n[`docs/breaking-changes.md`]: https://github.com/electron/electron/blob/main/docs/breaking-changes.md" 6 | } 7 | }, 8 | "issue": { 9 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1", 10 | "repository_url": "https://api.github.com/repos/dsanders11/deprecation-review", 11 | "labels_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/labels{/name}", 12 | "comments_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/comments", 13 | "events_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/events", 14 | "html_url": "https://github.com/dsanders11/deprecation-review/pull/1", 15 | "id": 1846060956, 16 | "node_id": "PR_kwDOKE6NNM5Xr6RG", 17 | "number": 1, 18 | "title": "chore: test", 19 | "user": { 20 | "login": "dsanders11", 21 | "id": 5820654, 22 | "node_id": "MDQ6VXNlcjU4MjA2NTQ=", 23 | "avatar_url": "https://avatars.githubusercontent.com/u/5820654?v=4", 24 | "gravatar_id": "", 25 | "url": "https://api.github.com/users/dsanders11", 26 | "html_url": "https://github.com/dsanders11", 27 | "followers_url": "https://api.github.com/users/dsanders11/followers", 28 | "following_url": "https://api.github.com/users/dsanders11/following{/other_user}", 29 | "gists_url": "https://api.github.com/users/dsanders11/gists{/gist_id}", 30 | "starred_url": "https://api.github.com/users/dsanders11/starred{/owner}{/repo}", 31 | "subscriptions_url": "https://api.github.com/users/dsanders11/subscriptions", 32 | "organizations_url": "https://api.github.com/users/dsanders11/orgs", 33 | "repos_url": "https://api.github.com/users/dsanders11/repos", 34 | "events_url": "https://api.github.com/users/dsanders11/events{/privacy}", 35 | "received_events_url": "https://api.github.com/users/dsanders11/received_events", 36 | "type": "User", 37 | "site_admin": false 38 | }, 39 | "labels": [ 40 | { 41 | "id": 5824334761, 42 | "node_id": "LA_kwDOKE6NNM8AAAABWyhLqQ", 43 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/labels/deprecation-review/requested%20%F0%9F%93%9D", 44 | "name": "deprecation-review/requested 📝", 45 | "color": "c5def5", 46 | "default": false, 47 | "description": "" 48 | } 49 | ], 50 | "state": "open", 51 | "locked": false, 52 | "assignee": null, 53 | "assignees": [], 54 | "milestone": null, 55 | "comments": 2, 56 | "created_at": "2023-08-11T00:52:02Z", 57 | "updated_at": "2023-08-11T01:59:33Z", 58 | "closed_at": null, 59 | "author_association": "OWNER", 60 | "active_lock_reason": null, 61 | "draft": false, 62 | "pull_request": { 63 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/pulls/1", 64 | "html_url": "https://github.com/dsanders11/deprecation-review/pull/1", 65 | "diff_url": "https://github.com/dsanders11/deprecation-review/pull/1.diff", 66 | "patch_url": "https://github.com/dsanders11/deprecation-review/pull/1.patch", 67 | "merged_at": null 68 | }, 69 | "body": "This is a test PR with no deprecation review label.", 70 | "reactions": { 71 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/reactions", 72 | "total_count": 0, 73 | "+1": 0, 74 | "-1": 0, 75 | "laugh": 0, 76 | "hooray": 0, 77 | "confused": 0, 78 | "heart": 0, 79 | "rocket": 0, 80 | "eyes": 0 81 | }, 82 | "timeline_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/timeline", 83 | "performed_via_github_app": null, 84 | "state_reason": null 85 | }, 86 | "comment": { 87 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/comments/1674130054", 88 | "html_url": "https://github.com/dsanders11/deprecation-review/pull/1#issuecomment-1674130054", 89 | "issue_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1", 90 | "id": 1674130054, 91 | "node_id": "IC_kwDOKE6NNM5jyTKG", 92 | "user": { 93 | "login": "bot", 94 | "id": 41898282, 95 | "node_id": "MDM6Qm90NDE4OTgyODI=", 96 | "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", 97 | "gravatar_id": "", 98 | "url": "https://api.github.com/users/github-actions%5Bbot%5D", 99 | "html_url": "https://github.com/apps/github-actions", 100 | "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", 101 | "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", 102 | "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", 103 | "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", 104 | "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", 105 | "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", 106 | "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", 107 | "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", 108 | "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", 109 | "type": "Bot", 110 | "site_admin": false 111 | }, 112 | "created_at": "2023-08-11T01:58:23Z", 113 | "updated_at": "2023-08-11T01:59:33Z", 114 | "author_association": "NONE", 115 | "body": "## 🪦 Deprecation Checklist\r\n\r\n### 🔥 New deprecations in this PR\r\n\r\n- [x] 📢 Are called out in [`docs/breaking-changes.md`][]\r\n- [x] ⚠️ Use the deprecation helpers in [`lib/common/deprecate.ts`](https://github.com/electron/electron/blob/main/lib/common/deprecate.ts) to warn about usage (including events)\r\n- [x] 📝 Are marked as deprecated in the docs, using `_Deprecated_` (including properties and events)\r\n\r\n[`docs/breaking-changes.md`]: https://github.com/electron/electron/blob/main/docs/breaking-changes.md", 116 | "reactions": { 117 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/comments/1674130054/reactions", 118 | "total_count": 0, 119 | "+1": 0, 120 | "-1": 0, 121 | "laugh": 0, 122 | "hooray": 0, 123 | "confused": 0, 124 | "heart": 0, 125 | "rocket": 0, 126 | "eyes": 0 127 | }, 128 | "performed_via_github_app": null 129 | }, 130 | "repository": { 131 | "id": 676236596, 132 | "node_id": "R_kgDOKE6NNA", 133 | "name": "deprecation-review", 134 | "full_name": "dsanders11/deprecation-review", 135 | "private": false, 136 | "owner": { 137 | "login": "dsanders11", 138 | "id": 5820654, 139 | "node_id": "MDQ6VXNlcjU4MjA2NTQ=", 140 | "avatar_url": "https://avatars.githubusercontent.com/u/5820654?v=4", 141 | "gravatar_id": "", 142 | "url": "https://api.github.com/users/dsanders11", 143 | "html_url": "https://github.com/dsanders11", 144 | "followers_url": "https://api.github.com/users/dsanders11/followers", 145 | "following_url": "https://api.github.com/users/dsanders11/following{/other_user}", 146 | "gists_url": "https://api.github.com/users/dsanders11/gists{/gist_id}", 147 | "starred_url": "https://api.github.com/users/dsanders11/starred{/owner}{/repo}", 148 | "subscriptions_url": "https://api.github.com/users/dsanders11/subscriptions", 149 | "organizations_url": "https://api.github.com/users/dsanders11/orgs", 150 | "repos_url": "https://api.github.com/users/dsanders11/repos", 151 | "events_url": "https://api.github.com/users/dsanders11/events{/privacy}", 152 | "received_events_url": "https://api.github.com/users/dsanders11/received_events", 153 | "type": "User", 154 | "site_admin": false 155 | }, 156 | "html_url": "https://github.com/dsanders11/deprecation-review", 157 | "description": null, 158 | "fork": false, 159 | "url": "https://api.github.com/repos/dsanders11/deprecation-review", 160 | "forks_url": "https://api.github.com/repos/dsanders11/deprecation-review/forks", 161 | "keys_url": "https://api.github.com/repos/dsanders11/deprecation-review/keys{/key_id}", 162 | "collaborators_url": "https://api.github.com/repos/dsanders11/deprecation-review/collaborators{/collaborator}", 163 | "teams_url": "https://api.github.com/repos/dsanders11/deprecation-review/teams", 164 | "hooks_url": "https://api.github.com/repos/dsanders11/deprecation-review/hooks", 165 | "issue_events_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/events{/number}", 166 | "events_url": "https://api.github.com/repos/dsanders11/deprecation-review/events", 167 | "assignees_url": "https://api.github.com/repos/dsanders11/deprecation-review/assignees{/user}", 168 | "branches_url": "https://api.github.com/repos/dsanders11/deprecation-review/branches{/branch}", 169 | "tags_url": "https://api.github.com/repos/dsanders11/deprecation-review/tags", 170 | "blobs_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/blobs{/sha}", 171 | "git_tags_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/tags{/sha}", 172 | "git_refs_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/refs{/sha}", 173 | "trees_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/trees{/sha}", 174 | "statuses_url": "https://api.github.com/repos/dsanders11/deprecation-review/statuses/{sha}", 175 | "languages_url": "https://api.github.com/repos/dsanders11/deprecation-review/languages", 176 | "stargazers_url": "https://api.github.com/repos/dsanders11/deprecation-review/stargazers", 177 | "contributors_url": "https://api.github.com/repos/dsanders11/deprecation-review/contributors", 178 | "subscribers_url": "https://api.github.com/repos/dsanders11/deprecation-review/subscribers", 179 | "subscription_url": "https://api.github.com/repos/dsanders11/deprecation-review/subscription", 180 | "commits_url": "https://api.github.com/repos/dsanders11/deprecation-review/commits{/sha}", 181 | "git_commits_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/commits{/sha}", 182 | "comments_url": "https://api.github.com/repos/dsanders11/deprecation-review/comments{/number}", 183 | "issue_comment_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/comments{/number}", 184 | "contents_url": "https://api.github.com/repos/dsanders11/deprecation-review/contents/{+path}", 185 | "compare_url": "https://api.github.com/repos/dsanders11/deprecation-review/compare/{base}...{head}", 186 | "merges_url": "https://api.github.com/repos/dsanders11/deprecation-review/merges", 187 | "archive_url": "https://api.github.com/repos/dsanders11/deprecation-review/{archive_format}{/ref}", 188 | "downloads_url": "https://api.github.com/repos/dsanders11/deprecation-review/downloads", 189 | "issues_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues{/number}", 190 | "pulls_url": "https://api.github.com/repos/dsanders11/deprecation-review/pulls{/number}", 191 | "milestones_url": "https://api.github.com/repos/dsanders11/deprecation-review/milestones{/number}", 192 | "notifications_url": "https://api.github.com/repos/dsanders11/deprecation-review/notifications{?since,all,participating}", 193 | "labels_url": "https://api.github.com/repos/dsanders11/deprecation-review/labels{/name}", 194 | "releases_url": "https://api.github.com/repos/dsanders11/deprecation-review/releases{/id}", 195 | "deployments_url": "https://api.github.com/repos/dsanders11/deprecation-review/deployments", 196 | "created_at": "2023-08-08T18:33:36Z", 197 | "updated_at": "2023-08-11T00:41:43Z", 198 | "pushed_at": "2023-08-11T01:58:01Z", 199 | "git_url": "git://github.com/dsanders11/deprecation-review.git", 200 | "ssh_url": "git@github.com:dsanders11/deprecation-review.git", 201 | "clone_url": "https://github.com/dsanders11/deprecation-review.git", 202 | "svn_url": "https://github.com/dsanders11/deprecation-review", 203 | "homepage": null, 204 | "size": 1, 205 | "stargazers_count": 0, 206 | "watchers_count": 0, 207 | "language": null, 208 | "has_issues": false, 209 | "has_projects": false, 210 | "has_downloads": true, 211 | "has_wiki": false, 212 | "has_pages": false, 213 | "has_discussions": false, 214 | "forks_count": 0, 215 | "mirror_url": null, 216 | "archived": false, 217 | "disabled": false, 218 | "open_issues_count": 1, 219 | "license": null, 220 | "allow_forking": true, 221 | "is_template": false, 222 | "web_commit_signoff_required": false, 223 | "topics": [], 224 | "visibility": "public", 225 | "forks": 0, 226 | "open_issues": 1, 227 | "watchers": 0, 228 | "default_branch": "main" 229 | }, 230 | "sender": { 231 | "login": "dsanders11", 232 | "id": 5820654, 233 | "node_id": "MDQ6VXNlcjU4MjA2NTQ=", 234 | "avatar_url": "https://avatars.githubusercontent.com/u/5820654?v=4", 235 | "gravatar_id": "", 236 | "url": "https://api.github.com/users/dsanders11", 237 | "html_url": "https://github.com/dsanders11", 238 | "followers_url": "https://api.github.com/users/dsanders11/followers", 239 | "following_url": "https://api.github.com/users/dsanders11/following{/other_user}", 240 | "gists_url": "https://api.github.com/users/dsanders11/gists{/gist_id}", 241 | "starred_url": "https://api.github.com/users/dsanders11/starred{/owner}{/repo}", 242 | "subscriptions_url": "https://api.github.com/users/dsanders11/subscriptions", 243 | "organizations_url": "https://api.github.com/users/dsanders11/orgs", 244 | "repos_url": "https://api.github.com/users/dsanders11/repos", 245 | "events_url": "https://api.github.com/users/dsanders11/events{/privacy}", 246 | "received_events_url": "https://api.github.com/users/dsanders11/received_events", 247 | "type": "User", 248 | "site_admin": false 249 | } 250 | } -------------------------------------------------------------------------------- /spec/fixtures/deprecation-review-state/issue_comment.edited.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "edited", 3 | "changes": { 4 | "body": { 5 | "from": "This is a human comment." 6 | } 7 | }, 8 | "issue": { 9 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1", 10 | "repository_url": "https://api.github.com/repos/dsanders11/deprecation-review", 11 | "labels_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/labels{/name}", 12 | "comments_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/comments", 13 | "events_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/events", 14 | "html_url": "https://github.com/dsanders11/deprecation-review/pull/1", 15 | "id": 1846060956, 16 | "node_id": "PR_kwDOKE6NNM5Xr6RG", 17 | "number": 1, 18 | "title": "chore: test", 19 | "user": { 20 | "login": "dsanders11", 21 | "id": 5820654, 22 | "node_id": "MDQ6VXNlcjU4MjA2NTQ=", 23 | "avatar_url": "https://avatars.githubusercontent.com/u/5820654?v=4", 24 | "gravatar_id": "", 25 | "url": "https://api.github.com/users/dsanders11", 26 | "html_url": "https://github.com/dsanders11", 27 | "followers_url": "https://api.github.com/users/dsanders11/followers", 28 | "following_url": "https://api.github.com/users/dsanders11/following{/other_user}", 29 | "gists_url": "https://api.github.com/users/dsanders11/gists{/gist_id}", 30 | "starred_url": "https://api.github.com/users/dsanders11/starred{/owner}{/repo}", 31 | "subscriptions_url": "https://api.github.com/users/dsanders11/subscriptions", 32 | "organizations_url": "https://api.github.com/users/dsanders11/orgs", 33 | "repos_url": "https://api.github.com/users/dsanders11/repos", 34 | "events_url": "https://api.github.com/users/dsanders11/events{/privacy}", 35 | "received_events_url": "https://api.github.com/users/dsanders11/received_events", 36 | "type": "User", 37 | "site_admin": false 38 | }, 39 | "labels": [], 40 | "state": "open", 41 | "locked": false, 42 | "assignee": null, 43 | "assignees": [], 44 | "milestone": null, 45 | "comments": 1, 46 | "created_at": "2023-08-11T00:52:02Z", 47 | "updated_at": "2023-08-11T01:41:59Z", 48 | "closed_at": null, 49 | "author_association": "OWNER", 50 | "active_lock_reason": null, 51 | "draft": false, 52 | "pull_request": { 53 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/pulls/1", 54 | "html_url": "https://github.com/dsanders11/deprecation-review/pull/1", 55 | "diff_url": "https://github.com/dsanders11/deprecation-review/pull/1.diff", 56 | "patch_url": "https://github.com/dsanders11/deprecation-review/pull/1.patch", 57 | "merged_at": null 58 | }, 59 | "body": "This is a test PR with no deprecation review label.", 60 | "reactions": { 61 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/reactions", 62 | "total_count": 0, 63 | "+1": 0, 64 | "-1": 0, 65 | "laugh": 0, 66 | "hooray": 0, 67 | "confused": 0, 68 | "heart": 0, 69 | "rocket": 0, 70 | "eyes": 0 71 | }, 72 | "timeline_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1/timeline", 73 | "performed_via_github_app": null, 74 | "state_reason": null 75 | }, 76 | "comment": { 77 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/comments/1674121507", 78 | "html_url": "https://github.com/dsanders11/deprecation-review/pull/1#issuecomment-1674121507", 79 | "issue_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/1", 80 | "id": 1674121507, 81 | "node_id": "IC_kwDOKE6NNM5jyREj", 82 | "user": { 83 | "login": "dsanders11", 84 | "id": 5820654, 85 | "node_id": "MDQ6VXNlcjU4MjA2NTQ=", 86 | "avatar_url": "https://avatars.githubusercontent.com/u/5820654?v=4", 87 | "gravatar_id": "", 88 | "url": "https://api.github.com/users/dsanders11", 89 | "html_url": "https://github.com/dsanders11", 90 | "followers_url": "https://api.github.com/users/dsanders11/followers", 91 | "following_url": "https://api.github.com/users/dsanders11/following{/other_user}", 92 | "gists_url": "https://api.github.com/users/dsanders11/gists{/gist_id}", 93 | "starred_url": "https://api.github.com/users/dsanders11/starred{/owner}{/repo}", 94 | "subscriptions_url": "https://api.github.com/users/dsanders11/subscriptions", 95 | "organizations_url": "https://api.github.com/users/dsanders11/orgs", 96 | "repos_url": "https://api.github.com/users/dsanders11/repos", 97 | "events_url": "https://api.github.com/users/dsanders11/events{/privacy}", 98 | "received_events_url": "https://api.github.com/users/dsanders11/received_events", 99 | "type": "User", 100 | "site_admin": false 101 | }, 102 | "created_at": "2023-08-11T01:41:44Z", 103 | "updated_at": "2023-08-11T01:41:59Z", 104 | "author_association": "OWNER", 105 | "body": "This is a human comment being edited.", 106 | "reactions": { 107 | "url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/comments/1674121507/reactions", 108 | "total_count": 0, 109 | "+1": 0, 110 | "-1": 0, 111 | "laugh": 0, 112 | "hooray": 0, 113 | "confused": 0, 114 | "heart": 0, 115 | "rocket": 0, 116 | "eyes": 0 117 | }, 118 | "performed_via_github_app": null 119 | }, 120 | "repository": { 121 | "id": 676236596, 122 | "node_id": "R_kgDOKE6NNA", 123 | "name": "deprecation-review", 124 | "full_name": "dsanders11/deprecation-review", 125 | "private": false, 126 | "owner": { 127 | "login": "dsanders11", 128 | "id": 5820654, 129 | "node_id": "MDQ6VXNlcjU4MjA2NTQ=", 130 | "avatar_url": "https://avatars.githubusercontent.com/u/5820654?v=4", 131 | "gravatar_id": "", 132 | "url": "https://api.github.com/users/dsanders11", 133 | "html_url": "https://github.com/dsanders11", 134 | "followers_url": "https://api.github.com/users/dsanders11/followers", 135 | "following_url": "https://api.github.com/users/dsanders11/following{/other_user}", 136 | "gists_url": "https://api.github.com/users/dsanders11/gists{/gist_id}", 137 | "starred_url": "https://api.github.com/users/dsanders11/starred{/owner}{/repo}", 138 | "subscriptions_url": "https://api.github.com/users/dsanders11/subscriptions", 139 | "organizations_url": "https://api.github.com/users/dsanders11/orgs", 140 | "repos_url": "https://api.github.com/users/dsanders11/repos", 141 | "events_url": "https://api.github.com/users/dsanders11/events{/privacy}", 142 | "received_events_url": "https://api.github.com/users/dsanders11/received_events", 143 | "type": "User", 144 | "site_admin": false 145 | }, 146 | "html_url": "https://github.com/dsanders11/deprecation-review", 147 | "description": null, 148 | "fork": false, 149 | "url": "https://api.github.com/repos/dsanders11/deprecation-review", 150 | "forks_url": "https://api.github.com/repos/dsanders11/deprecation-review/forks", 151 | "keys_url": "https://api.github.com/repos/dsanders11/deprecation-review/keys{/key_id}", 152 | "collaborators_url": "https://api.github.com/repos/dsanders11/deprecation-review/collaborators{/collaborator}", 153 | "teams_url": "https://api.github.com/repos/dsanders11/deprecation-review/teams", 154 | "hooks_url": "https://api.github.com/repos/dsanders11/deprecation-review/hooks", 155 | "issue_events_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/events{/number}", 156 | "events_url": "https://api.github.com/repos/dsanders11/deprecation-review/events", 157 | "assignees_url": "https://api.github.com/repos/dsanders11/deprecation-review/assignees{/user}", 158 | "branches_url": "https://api.github.com/repos/dsanders11/deprecation-review/branches{/branch}", 159 | "tags_url": "https://api.github.com/repos/dsanders11/deprecation-review/tags", 160 | "blobs_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/blobs{/sha}", 161 | "git_tags_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/tags{/sha}", 162 | "git_refs_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/refs{/sha}", 163 | "trees_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/trees{/sha}", 164 | "statuses_url": "https://api.github.com/repos/dsanders11/deprecation-review/statuses/{sha}", 165 | "languages_url": "https://api.github.com/repos/dsanders11/deprecation-review/languages", 166 | "stargazers_url": "https://api.github.com/repos/dsanders11/deprecation-review/stargazers", 167 | "contributors_url": "https://api.github.com/repos/dsanders11/deprecation-review/contributors", 168 | "subscribers_url": "https://api.github.com/repos/dsanders11/deprecation-review/subscribers", 169 | "subscription_url": "https://api.github.com/repos/dsanders11/deprecation-review/subscription", 170 | "commits_url": "https://api.github.com/repos/dsanders11/deprecation-review/commits{/sha}", 171 | "git_commits_url": "https://api.github.com/repos/dsanders11/deprecation-review/git/commits{/sha}", 172 | "comments_url": "https://api.github.com/repos/dsanders11/deprecation-review/comments{/number}", 173 | "issue_comment_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues/comments{/number}", 174 | "contents_url": "https://api.github.com/repos/dsanders11/deprecation-review/contents/{+path}", 175 | "compare_url": "https://api.github.com/repos/dsanders11/deprecation-review/compare/{base}...{head}", 176 | "merges_url": "https://api.github.com/repos/dsanders11/deprecation-review/merges", 177 | "archive_url": "https://api.github.com/repos/dsanders11/deprecation-review/{archive_format}{/ref}", 178 | "downloads_url": "https://api.github.com/repos/dsanders11/deprecation-review/downloads", 179 | "issues_url": "https://api.github.com/repos/dsanders11/deprecation-review/issues{/number}", 180 | "pulls_url": "https://api.github.com/repos/dsanders11/deprecation-review/pulls{/number}", 181 | "milestones_url": "https://api.github.com/repos/dsanders11/deprecation-review/milestones{/number}", 182 | "notifications_url": "https://api.github.com/repos/dsanders11/deprecation-review/notifications{?since,all,participating}", 183 | "labels_url": "https://api.github.com/repos/dsanders11/deprecation-review/labels{/name}", 184 | "releases_url": "https://api.github.com/repos/dsanders11/deprecation-review/releases{/id}", 185 | "deployments_url": "https://api.github.com/repos/dsanders11/deprecation-review/deployments", 186 | "created_at": "2023-08-08T18:33:36Z", 187 | "updated_at": "2023-08-11T00:41:43Z", 188 | "pushed_at": "2023-08-11T00:52:03Z", 189 | "git_url": "git://github.com/dsanders11/deprecation-review.git", 190 | "ssh_url": "git@github.com:dsanders11/deprecation-review.git", 191 | "clone_url": "https://github.com/dsanders11/deprecation-review.git", 192 | "svn_url": "https://github.com/dsanders11/deprecation-review", 193 | "homepage": null, 194 | "size": 0, 195 | "stargazers_count": 0, 196 | "watchers_count": 0, 197 | "language": null, 198 | "has_issues": false, 199 | "has_projects": false, 200 | "has_downloads": true, 201 | "has_wiki": false, 202 | "has_pages": false, 203 | "has_discussions": false, 204 | "forks_count": 0, 205 | "mirror_url": null, 206 | "archived": false, 207 | "disabled": false, 208 | "open_issues_count": 1, 209 | "license": null, 210 | "allow_forking": true, 211 | "is_template": false, 212 | "web_commit_signoff_required": false, 213 | "topics": [], 214 | "visibility": "public", 215 | "forks": 0, 216 | "open_issues": 1, 217 | "watchers": 0, 218 | "default_branch": "main" 219 | }, 220 | "sender": { 221 | "login": "dsanders11", 222 | "id": 5820654, 223 | "node_id": "MDQ6VXNlcjU4MjA2NTQ=", 224 | "avatar_url": "https://avatars.githubusercontent.com/u/5820654?v=4", 225 | "gravatar_id": "", 226 | "url": "https://api.github.com/users/dsanders11", 227 | "html_url": "https://github.com/dsanders11", 228 | "followers_url": "https://api.github.com/users/dsanders11/followers", 229 | "following_url": "https://api.github.com/users/dsanders11/following{/other_user}", 230 | "gists_url": "https://api.github.com/users/dsanders11/gists{/gist_id}", 231 | "starred_url": "https://api.github.com/users/dsanders11/starred{/owner}{/repo}", 232 | "subscriptions_url": "https://api.github.com/users/dsanders11/subscriptions", 233 | "organizations_url": "https://api.github.com/users/dsanders11/orgs", 234 | "repos_url": "https://api.github.com/users/dsanders11/repos", 235 | "events_url": "https://api.github.com/users/dsanders11/events{/privacy}", 236 | "received_events_url": "https://api.github.com/users/dsanders11/received_events", 237 | "type": "User", 238 | "site_admin": false 239 | } 240 | } -------------------------------------------------------------------------------- /spec/fixtures/pr-open-time/pull_request.opened.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 26876, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 6 | "id": 534054584, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 8 | "html_url": "https://github.com/electron/electron/pull/26876", 9 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 10 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 11 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 12 | "number": 26876, 13 | "state": "open", 14 | "locked": false, 15 | "title": "chore: fix JS linting", 16 | "user": { 17 | "login": "MarshallOfSound", 18 | "id": 6634592, 19 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 20 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 21 | "gravatar_id": "", 22 | "url": "https://api.github.com/users/MarshallOfSound", 23 | "html_url": "https://github.com/MarshallOfSound", 24 | "followers_url": "https://api.github.com/users/MarshallOfSound/followers", 25 | "following_url": "https://api.github.com/users/MarshallOfSound/following{/other_user}", 26 | "gists_url": "https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 27 | "starred_url": "https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 28 | "subscriptions_url": "https://api.github.com/users/MarshallOfSound/subscriptions", 29 | "organizations_url": "https://api.github.com/users/MarshallOfSound/orgs", 30 | "repos_url": "https://api.github.com/users/MarshallOfSound/repos", 31 | "events_url": "https://api.github.com/users/MarshallOfSound/events{/privacy}", 32 | "received_events_url": "https://api.github.com/users/MarshallOfSound/received_events", 33 | "type": "User", 34 | "site_admin": false 35 | }, 36 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 37 | "created_at": "2020-12-08T01:24:55Z", 38 | "updated_at": "2020-12-08T01:24:55Z", 39 | "closed_at": null, 40 | "merged_at": null, 41 | "merge_commit_sha": null, 42 | "assignee": null, 43 | "labels": [ 44 | { 45 | "id": 1034512799, 46 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 47 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 48 | "name": "semver/major", 49 | "color": "6ac2dd", 50 | "default": false, 51 | "description": "backwards-incompatible bug fixes" 52 | } 53 | ], 54 | "base": { 55 | "repo": { 56 | "full_name": "electron/electron" 57 | } 58 | }, 59 | "milestone": null, 60 | "draft": false, 61 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 62 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 63 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 64 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 65 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/578fd4af98861ef7e6374d7d1fa1ccca6bc7136d", 66 | "author_association": "MEMBER", 67 | "active_lock_reason": null, 68 | "merged": false, 69 | "mergeable": null, 70 | "rebaseable": null, 71 | "mergeable_state": "unknown", 72 | "merged_by": null, 73 | "comments": 0, 74 | "review_comments": 0, 75 | "maintainer_can_modify": false, 76 | "commits": 1, 77 | "additions": 12, 78 | "deletions": 5, 79 | "changed_files": 2 80 | }, 81 | "repository": { 82 | "id": 9384267, 83 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 84 | "name": "electron", 85 | "full_name": "electron/electron", 86 | "private": false, 87 | "owner": { 88 | "login": "electron", 89 | "id": 13409222, 90 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 91 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 92 | "gravatar_id": "", 93 | "url": "https://api.github.com/users/electron", 94 | "html_url": "https://github.com/electron", 95 | "followers_url": "https://api.github.com/users/electron/followers", 96 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 97 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 98 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 99 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 100 | "organizations_url": "https://api.github.com/users/electron/orgs", 101 | "repos_url": "https://api.github.com/users/electron/repos", 102 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 103 | "received_events_url": "https://api.github.com/users/electron/received_events", 104 | "type": "Organization", 105 | "site_admin": false 106 | }, 107 | "html_url": "https://github.com/electron/electron", 108 | "description": ":electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS", 109 | "fork": false, 110 | "url": "https://api.github.com/repos/electron/electron", 111 | "forks_url": "https://api.github.com/repos/electron/electron/forks", 112 | "keys_url": "https://api.github.com/repos/electron/electron/keys{/key_id}", 113 | "collaborators_url": "https://api.github.com/repos/electron/electron/collaborators{/collaborator}", 114 | "teams_url": "https://api.github.com/repos/electron/electron/teams", 115 | "hooks_url": "https://api.github.com/repos/electron/electron/hooks", 116 | "issue_events_url": "https://api.github.com/repos/electron/electron/issues/events{/number}", 117 | "events_url": "https://api.github.com/repos/electron/electron/events", 118 | "assignees_url": "https://api.github.com/repos/electron/electron/assignees{/user}", 119 | "branches_url": "https://api.github.com/repos/electron/electron/branches{/branch}", 120 | "tags_url": "https://api.github.com/repos/electron/electron/tags", 121 | "blobs_url": "https://api.github.com/repos/electron/electron/git/blobs{/sha}", 122 | "git_tags_url": "https://api.github.com/repos/electron/electron/git/tags{/sha}", 123 | "git_refs_url": "https://api.github.com/repos/electron/electron/git/refs{/sha}", 124 | "trees_url": "https://api.github.com/repos/electron/electron/git/trees{/sha}", 125 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/{sha}", 126 | "languages_url": "https://api.github.com/repos/electron/electron/languages", 127 | "stargazers_url": "https://api.github.com/repos/electron/electron/stargazers", 128 | "contributors_url": "https://api.github.com/repos/electron/electron/contributors", 129 | "subscribers_url": "https://api.github.com/repos/electron/electron/subscribers", 130 | "subscription_url": "https://api.github.com/repos/electron/electron/subscription", 131 | "commits_url": "https://api.github.com/repos/electron/electron/commits{/sha}", 132 | "git_commits_url": "https://api.github.com/repos/electron/electron/git/commits{/sha}", 133 | "comments_url": "https://api.github.com/repos/electron/electron/comments{/number}", 134 | "issue_comment_url": "https://api.github.com/repos/electron/electron/issues/comments{/number}", 135 | "contents_url": "https://api.github.com/repos/electron/electron/contents/{+path}", 136 | "compare_url": "https://api.github.com/repos/electron/electron/compare/{base}...{head}", 137 | "merges_url": "https://api.github.com/repos/electron/electron/merges", 138 | "archive_url": "https://api.github.com/repos/electron/electron/{archive_format}{/ref}", 139 | "downloads_url": "https://api.github.com/repos/electron/electron/downloads", 140 | "issues_url": "https://api.github.com/repos/electron/electron/issues{/number}", 141 | "pulls_url": "https://api.github.com/repos/electron/electron/pulls{/number}", 142 | "milestones_url": "https://api.github.com/repos/electron/electron/milestones{/number}", 143 | "notifications_url": "https://api.github.com/repos/electron/electron/notifications{?since,all,participating}", 144 | "labels_url": "https://api.github.com/repos/electron/electron/labels{/name}", 145 | "releases_url": "https://api.github.com/repos/electron/electron/releases{/id}", 146 | "deployments_url": "https://api.github.com/repos/electron/electron/deployments", 147 | "created_at": "2013-04-12T01:47:36Z", 148 | "updated_at": "2020-12-08T00:51:34Z", 149 | "pushed_at": "2020-12-08T01:24:47Z", 150 | "git_url": "git://github.com/electron/electron.git", 151 | "ssh_url": "git@github.com:electron/electron.git", 152 | "clone_url": "https://github.com/electron/electron.git", 153 | "svn_url": "https://github.com/electron/electron", 154 | "homepage": "https://electronjs.org", 155 | "size": 87927, 156 | "stargazers_count": 87932, 157 | "watchers_count": 87932, 158 | "language": "C++", 159 | "has_issues": true, 160 | "has_projects": true, 161 | "has_downloads": true, 162 | "has_wiki": false, 163 | "has_pages": false, 164 | "forks_count": 11755, 165 | "mirror_url": null, 166 | "archived": false, 167 | "disabled": false, 168 | "open_issues_count": 1525, 169 | "license": { 170 | "key": "mit", 171 | "name": "MIT License", 172 | "spdx_id": "MIT", 173 | "url": "https://api.github.com/licenses/mit", 174 | "node_id": "MDc6TGljZW5zZTEz" 175 | }, 176 | "forks": 11755, 177 | "open_issues": 1525, 178 | "watchers": 87932, 179 | "default_branch": "main" 180 | } 181 | } -------------------------------------------------------------------------------- /spec/fixtures/pr-open-time/pull_request.semver-major.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 3 | "id": 534054584, 4 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 5 | "html_url": "https://github.com/electron/electron/pull/26876", 6 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 7 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 8 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 9 | "number": 26876, 10 | "state": "open", 11 | "locked": false, 12 | "title": "chore: fix JS linting", 13 | "user": { 14 | "login": "MarshallOfSound", 15 | "id": 6634592, 16 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 17 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/MarshallOfSound", 20 | "html_url": "https://github.com/MarshallOfSound", 21 | "followers_url": "https://api.github.com/users/MarshallOfSound/followers", 22 | "following_url": "https://api.github.com/users/MarshallOfSound/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/MarshallOfSound/subscriptions", 26 | "organizations_url": "https://api.github.com/users/MarshallOfSound/orgs", 27 | "repos_url": "https://api.github.com/users/MarshallOfSound/repos", 28 | "events_url": "https://api.github.com/users/MarshallOfSound/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/MarshallOfSound/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 34 | "created_at": "2020-12-08T01:24:55Z", 35 | "updated_at": "2020-12-08T01:24:55Z", 36 | "closed_at": null, 37 | "merged_at": null, 38 | "merge_commit_sha": null, 39 | "assignee": null, 40 | "labels": [ 41 | { 42 | "id": 1034512799, 43 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 44 | "url": "https://api.github.com/repos/electron/electron/labels/semver/major", 45 | "name": "semver/major", 46 | "color": "6ac2dd", 47 | "default": false, 48 | "description": "backwards-incompatible bug fixes" 49 | } 50 | ], 51 | "base": { 52 | "repo": { 53 | "full_name": "electron/electron" 54 | } 55 | }, 56 | "milestone": null, 57 | "draft": false, 58 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 59 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 60 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 61 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 62 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/578fd4af98861ef7e6374d7d1fa1ccca6bc7136d", 63 | "author_association": "MEMBER", 64 | "active_lock_reason": null, 65 | "merged": false, 66 | "mergeable": null, 67 | "rebaseable": null, 68 | "mergeable_state": "unknown", 69 | "merged_by": null, 70 | "comments": 0, 71 | "review_comments": 0, 72 | "maintainer_can_modify": false, 73 | "commits": 1, 74 | "additions": 12, 75 | "deletions": 5, 76 | "changed_files": 2 77 | } -------------------------------------------------------------------------------- /spec/fixtures/pr-open-time/pull_request.semver-minor.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 3 | "id": 534054584, 4 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 5 | "html_url": "https://github.com/electron/electron/pull/26876", 6 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 7 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 8 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 9 | "number": 26876, 10 | "state": "open", 11 | "locked": false, 12 | "title": "chore: fix JS linting", 13 | "user": { 14 | "login": "MarshallOfSound", 15 | "id": 6634592, 16 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 17 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/MarshallOfSound", 20 | "html_url": "https://github.com/MarshallOfSound", 21 | "followers_url": "https://api.github.com/users/MarshallOfSound/followers", 22 | "following_url": "https://api.github.com/users/MarshallOfSound/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/MarshallOfSound/subscriptions", 26 | "organizations_url": "https://api.github.com/users/MarshallOfSound/orgs", 27 | "repos_url": "https://api.github.com/users/MarshallOfSound/repos", 28 | "events_url": "https://api.github.com/users/MarshallOfSound/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/MarshallOfSound/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 34 | "created_at": "2020-12-08T01:24:55Z", 35 | "updated_at": "2020-12-08T01:24:55Z", 36 | "closed_at": null, 37 | "merged_at": null, 38 | "merge_commit_sha": null, 39 | "assignee": null, 40 | "labels": [ 41 | { 42 | "id": 1034512799, 43 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 44 | "url": "https://api.github.com/repos/electron/electron/labels/semver/minor", 45 | "name": "semver/minor", 46 | "color": "6ac2dd", 47 | "default": false, 48 | "description": "new features" 49 | } 50 | ], 51 | "base": { 52 | "repo": { 53 | "full_name": "electron/electron" 54 | } 55 | }, 56 | "milestone": null, 57 | "draft": false, 58 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 59 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 60 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 61 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 62 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/578fd4af98861ef7e6374d7d1fa1ccca6bc7136d", 63 | "author_association": "MEMBER", 64 | "active_lock_reason": null, 65 | "merged": false, 66 | "mergeable": null, 67 | "rebaseable": null, 68 | "mergeable_state": "unknown", 69 | "merged_by": null, 70 | "comments": 0, 71 | "review_comments": 0, 72 | "maintainer_can_modify": false, 73 | "commits": 1, 74 | "additions": 12, 75 | "deletions": 5, 76 | "changed_files": 2 77 | } -------------------------------------------------------------------------------- /spec/fixtures/pr-open-time/pull_request.semver-missing.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 3 | "id": 534054584, 4 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 5 | "html_url": "https://github.com/electron/electron/pull/26876", 6 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 7 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 8 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 9 | "number": 26876, 10 | "state": "open", 11 | "locked": false, 12 | "title": "build: fix JS linting", 13 | "user": { 14 | "login": "MarshallOfSound", 15 | "id": 6634592, 16 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 17 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/MarshallOfSound", 20 | "html_url": "https://github.com/MarshallOfSound", 21 | "followers_url": "https://api.github.com/users/MarshallOfSound/followers", 22 | "following_url": "https://api.github.com/users/MarshallOfSound/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/MarshallOfSound/subscriptions", 26 | "organizations_url": "https://api.github.com/users/MarshallOfSound/orgs", 27 | "repos_url": "https://api.github.com/users/MarshallOfSound/repos", 28 | "events_url": "https://api.github.com/users/MarshallOfSound/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/MarshallOfSound/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "base": { 34 | "repo": { 35 | "full_name": "electron/electron" 36 | } 37 | }, 38 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 39 | "created_at": "2020-12-08T01:24:55Z", 40 | "updated_at": "2020-12-08T01:24:55Z", 41 | "closed_at": null, 42 | "merged_at": null, 43 | "merge_commit_sha": null, 44 | "assignee": null, 45 | "labels": [ ], 46 | "milestone": null, 47 | "draft": false, 48 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 49 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 50 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 51 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 52 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/578fd4af98861ef7e6374d7d1fa1ccca6bc7136d", 53 | "author_association": "MEMBER", 54 | "active_lock_reason": null, 55 | "merged": false, 56 | "mergeable": null, 57 | "rebaseable": null, 58 | "mergeable_state": "unknown", 59 | "merged_by": null, 60 | "comments": 0, 61 | "review_comments": 0, 62 | "maintainer_can_modify": false, 63 | "commits": 1, 64 | "additions": 12, 65 | "deletions": 5, 66 | "changed_files": 2 67 | } -------------------------------------------------------------------------------- /spec/fixtures/pr-open-time/pull_request.semver-none.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 3 | "id": 534054584, 4 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 5 | "html_url": "https://github.com/electron/electron/pull/26876", 6 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 7 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 8 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 9 | "number": 26876, 10 | "state": "open", 11 | "locked": false, 12 | "title": "chore: fix JS linting", 13 | "user": { 14 | "login": "MarshallOfSound", 15 | "id": 6634592, 16 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 17 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/MarshallOfSound", 20 | "html_url": "https://github.com/MarshallOfSound", 21 | "followers_url": "https://api.github.com/users/MarshallOfSound/followers", 22 | "following_url": "https://api.github.com/users/MarshallOfSound/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/MarshallOfSound/subscriptions", 26 | "organizations_url": "https://api.github.com/users/MarshallOfSound/orgs", 27 | "repos_url": "https://api.github.com/users/MarshallOfSound/repos", 28 | "events_url": "https://api.github.com/users/MarshallOfSound/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/MarshallOfSound/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 34 | "created_at": "2020-12-08T01:24:55Z", 35 | "updated_at": "2020-12-08T01:24:55Z", 36 | "closed_at": null, 37 | "merged_at": null, 38 | "merge_commit_sha": null, 39 | "assignee": null, 40 | "labels": [ 41 | { 42 | "id": 1034512799, 43 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 44 | "url": "https://api.github.com/repos/electron/electron/labels/semver/none", 45 | "name": "semver/none", 46 | "color": "6ac2dd", 47 | "default": false, 48 | "description": "new features" 49 | } 50 | ], 51 | "base": { 52 | "repo": { 53 | "full_name": "electron/electron" 54 | } 55 | }, 56 | "milestone": null, 57 | "draft": false, 58 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 59 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 60 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 61 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 62 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/578fd4af98861ef7e6374d7d1fa1ccca6bc7136d", 63 | "author_association": "MEMBER", 64 | "active_lock_reason": null, 65 | "merged": false, 66 | "mergeable": null, 67 | "rebaseable": null, 68 | "mergeable_state": "unknown", 69 | "merged_by": null, 70 | "comments": 0, 71 | "review_comments": 0, 72 | "maintainer_can_modify": false, 73 | "commits": 1, 74 | "additions": 12, 75 | "deletions": 5, 76 | "changed_files": 2 77 | } -------------------------------------------------------------------------------- /spec/fixtures/pr-open-time/pull_request.semver-patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 3 | "id": 534054584, 4 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 5 | "html_url": "https://github.com/electron/electron/pull/26876", 6 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 7 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 8 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 9 | "number": 26876, 10 | "state": "open", 11 | "locked": false, 12 | "title": "build: fix JS linting", 13 | "user": { 14 | "login": "MarshallOfSound", 15 | "id": 6634592, 16 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 17 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/MarshallOfSound", 20 | "html_url": "https://github.com/MarshallOfSound", 21 | "followers_url": "https://api.github.com/users/MarshallOfSound/followers", 22 | "following_url": "https://api.github.com/users/MarshallOfSound/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/MarshallOfSound/subscriptions", 26 | "organizations_url": "https://api.github.com/users/MarshallOfSound/orgs", 27 | "repos_url": "https://api.github.com/users/MarshallOfSound/repos", 28 | "events_url": "https://api.github.com/users/MarshallOfSound/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/MarshallOfSound/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 34 | "created_at": "2020-12-08T01:24:55Z", 35 | "updated_at": "2020-12-08T01:24:55Z", 36 | "closed_at": null, 37 | "merged_at": null, 38 | "merge_commit_sha": null, 39 | "assignee": null, 40 | "labels": [ 41 | { 42 | "id": 1034512799, 43 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 44 | "url": "https://api.github.com/repos/electron/electron/labels/semver/patch", 45 | "name": "semver/patch", 46 | "color": "6ac2dd", 47 | "default": false, 48 | "description": "backwards-compatible bug fixes" 49 | } 50 | ], 51 | "base": { 52 | "repo": { 53 | "full_name": "electron/electron" 54 | } 55 | }, 56 | "milestone": null, 57 | "draft": false, 58 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 59 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 60 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 61 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 62 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/578fd4af98861ef7e6374d7d1fa1ccca6bc7136d", 63 | "author_association": "MEMBER", 64 | "active_lock_reason": null, 65 | "merged": false, 66 | "mergeable": null, 67 | "rebaseable": null, 68 | "mergeable_state": "unknown", 69 | "merged_by": null, 70 | "comments": 0, 71 | "review_comments": 0, 72 | "maintainer_can_modify": false, 73 | "commits": 1, 74 | "additions": 12, 75 | "deletions": 5, 76 | "changed_files": 2 77 | } -------------------------------------------------------------------------------- /spec/fixtures/pr-open-time/pull_request.should_label.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 3 | "id": 534054584, 4 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 5 | "html_url": "https://github.com/electron/electron/pull/26876", 6 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 7 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 8 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 9 | "number": 26876, 10 | "state": "open", 11 | "locked": false, 12 | "title": "chore: fix JS linting", 13 | "user": { 14 | "login": "MarshallOfSound", 15 | "id": 6634592, 16 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 17 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/MarshallOfSound", 20 | "html_url": "https://github.com/MarshallOfSound", 21 | "followers_url": "https://api.github.com/users/MarshallOfSound/followers", 22 | "following_url": "https://api.github.com/users/MarshallOfSound/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/MarshallOfSound/subscriptions", 26 | "organizations_url": "https://api.github.com/users/MarshallOfSound/orgs", 27 | "repos_url": "https://api.github.com/users/MarshallOfSound/repos", 28 | "events_url": "https://api.github.com/users/MarshallOfSound/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/MarshallOfSound/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 34 | "created_at": "2020-12-08T01:24:55Z", 35 | "updated_at": "2020-12-08T01:24:55Z", 36 | "closed_at": null, 37 | "merged_at": null, 38 | "merge_commit_sha": null, 39 | "assignee": null, 40 | "labels": [ 41 | { 42 | "id": 1034512799, 43 | "node_id": "MDU6TGFiZWwxMDM0NTEyNzk5", 44 | "url": "https://api.github.com/repos/electron/electron/labels/semver/minor", 45 | "name": "semver/minor", 46 | "color": "6ac2dd", 47 | "default": false, 48 | "description": "new features" 49 | } 50 | ], 51 | "base": { 52 | "repo": { 53 | "full_name": "electron/electron" 54 | } 55 | }, 56 | "milestone": null, 57 | "draft": false, 58 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 59 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 60 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 61 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 62 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/578fd4af98861ef7e6374d7d1fa1ccca6bc7136d", 63 | "author_association": "MEMBER", 64 | "active_lock_reason": null, 65 | "merged": false, 66 | "mergeable": null, 67 | "rebaseable": null, 68 | "mergeable_state": "unknown", 69 | "merged_by": null, 70 | "comments": 0, 71 | "review_comments": 0, 72 | "maintainer_can_modify": false, 73 | "commits": 1, 74 | "additions": 12, 75 | "deletions": 5, 76 | "changed_files": 2 77 | } -------------------------------------------------------------------------------- /spec/fixtures/pr-open-time/pull_request.should_not_label.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/electron/electron/pulls/26876", 3 | "id": 534054584, 4 | "node_id": "MDExOlB1bGxSZXF1ZXN0NTM0MDU0NTg0", 5 | "html_url": "https://github.com/electron/electron/pull/26876", 6 | "diff_url": "https://github.com/electron/electron/pull/26876.diff", 7 | "patch_url": "https://github.com/electron/electron/pull/26876.patch", 8 | "issue_url": "https://api.github.com/repos/electron/electron/issues/26876", 9 | "number": 26876, 10 | "state": "open", 11 | "locked": false, 12 | "title": "build: fix JS linting", 13 | "user": { 14 | "login": "MarshallOfSound", 15 | "id": 6634592, 16 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 17 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/MarshallOfSound", 20 | "html_url": "https://github.com/MarshallOfSound", 21 | "followers_url": "https://api.github.com/users/MarshallOfSound/followers", 22 | "following_url": "https://api.github.com/users/MarshallOfSound/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/MarshallOfSound/subscriptions", 26 | "organizations_url": "https://api.github.com/users/MarshallOfSound/orgs", 27 | "repos_url": "https://api.github.com/users/MarshallOfSound/repos", 28 | "events_url": "https://api.github.com/users/MarshallOfSound/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/MarshallOfSound/received_events", 30 | "type": "User", 31 | "site_admin": false 32 | }, 33 | "base": { 34 | "repo": { 35 | "full_name": "electron/electron" 36 | } 37 | }, 38 | "body": "* Ensure --fix output is actually written to disk\r\n* Cache bust on lint.js file changes\r\n* Ensure CI does not use the linting cache\r\n\r\nNotes: no-notes", 39 | "created_at": "2020-12-08T01:24:55Z", 40 | "updated_at": "2020-12-08T01:24:55Z", 41 | "closed_at": null, 42 | "merged_at": null, 43 | "merge_commit_sha": null, 44 | "assignee": null, 45 | "labels": [], 46 | "milestone": null, 47 | "draft": false, 48 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/26876/commits", 49 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/26876/comments", 50 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 51 | "comments_url": "https://api.github.com/repos/electron/electron/issues/26876/comments", 52 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/578fd4af98861ef7e6374d7d1fa1ccca6bc7136d", 53 | "author_association": "MEMBER", 54 | "active_lock_reason": null, 55 | "merged": false, 56 | "mergeable": null, 57 | "rebaseable": null, 58 | "mergeable_state": "unknown", 59 | "merged_by": null, 60 | "comments": 0, 61 | "review_comments": 0, 62 | "maintainer_can_modify": false, 63 | "commits": 1, 64 | "additions": 12, 65 | "deletions": 5, 66 | "changed_files": 2 67 | } -------------------------------------------------------------------------------- /spec/utils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | /** 5 | * Helper util to load JSON fixtures. This is necessary because 6 | * loading them through the handy "require('foo.json')" shortcut 7 | * can be problematic due to module caching - modifying a fixture 8 | * in one test can bleed over into another test, coupling them 9 | */ 10 | export function loadFixture(filePath: string) { 11 | return JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures', filePath), 'utf-8')); 12 | } 13 | -------------------------------------------------------------------------------- /src/24-hour-rule.ts: -------------------------------------------------------------------------------- 1 | import { Context, Probot } from 'probot'; 2 | 3 | import { 4 | NEW_PR_LABEL, 5 | EXCLUDE_LABELS, 6 | EXCLUDE_PREFIXES, 7 | EXCLUDE_USERS, 8 | SEMVER_LABELS, 9 | MINIMUM_MAJOR_OPEN_TIME, 10 | MINIMUM_PATCH_OPEN_TIME, 11 | MINIMUM_MINOR_OPEN_TIME, 12 | SEMVER_NONE_LABEL, 13 | } from './constants'; 14 | import { PullRequest, Label, PullRequestLabeledEvent } from '@octokit/webhooks-types'; 15 | import { addOrUpdateAPIReviewCheck, checkPRReadyForMerge } from './api-review-state'; 16 | import { log } from './utils/log-util'; 17 | import { addLabels, removeLabel } from './utils/label-utils'; 18 | import { LogLevel } from './enums'; 19 | 20 | const CHECK_INTERVAL = 1000 * 60 * 5; 21 | 22 | /** 23 | * @returns a number representing the minimum open time for the PR 24 | * based on its semantic prefix in milliseconds 25 | */ 26 | export const getMinimumOpenTime = (pr: PullRequest): number => { 27 | log('getMinimumOpenTime', LogLevel.INFO, `Fetching minimum open time for PR #${pr.number}.`); 28 | 29 | const hasLabel = (label: string) => pr.labels.some((l) => l.name === label); 30 | 31 | if (hasLabel(SEMVER_LABELS.MAJOR)) return MINIMUM_MAJOR_OPEN_TIME; 32 | if (hasLabel(SEMVER_LABELS.MINOR)) return MINIMUM_MINOR_OPEN_TIME; 33 | if (hasLabel(SEMVER_LABELS.PATCH) || hasLabel(SEMVER_NONE_LABEL)) return MINIMUM_PATCH_OPEN_TIME; 34 | 35 | // If it's not labeled yet, assume it is semver/major and do not remove the label. 36 | return MINIMUM_MAJOR_OPEN_TIME; 37 | }; 38 | 39 | /** 40 | * @param github - An Octokit instance 41 | * @returns a number representing the that cation should use as the 42 | * open time for the PR in milliseconds, taking draft status into account. 43 | */ 44 | export const getPROpenedTime = async ( 45 | github: Context['octokit'], 46 | pr: PullRequest, 47 | ): Promise => { 48 | const [owner, repo] = pr.base.repo.full_name.split('/'); 49 | 50 | // Fetch PR timeline events. 51 | const { data: events } = await github.issues.listEventsForTimeline({ 52 | owner, 53 | repo, 54 | issue_number: pr.number, 55 | }); 56 | 57 | // Filter out all except 'Ready For Review' events. 58 | const readyForReviewEvents = events 59 | .filter((e) => e.event === 'ready_for_review') 60 | .sort(({ created_at: cA }, { created_at: cB }) => { 61 | return new Date(cB!).getTime() - new Date(cA!).getTime(); 62 | }); 63 | 64 | // If this PR was a draft PR previously, set its opened time as a function 65 | // of when it was most recently marked ready for review instead of when it was opened, 66 | // otherwise return the PR open date. 67 | return readyForReviewEvents.length > 0 68 | ? new Date(readyForReviewEvents[0].created_at!).getTime() 69 | : new Date(pr.created_at).getTime(); 70 | }; 71 | 72 | export const shouldPRHaveLabel = async ( 73 | github: Context['octokit'], 74 | pr: PullRequest, 75 | ): Promise => { 76 | log('shouldPRHaveLabel', LogLevel.INFO, `Checking whether #${pr.number} should have label.`); 77 | 78 | const prefix = pr.title.split(':')[0]; 79 | const backportMatch = pr.title.match(/[bB]ackport/); 80 | const backportInTitle = backportMatch && backportMatch[0]; 81 | const hasExcludedLabel = pr.labels.some((label) => EXCLUDE_LABELS.includes(label.name)); 82 | 83 | if ( 84 | EXCLUDE_PREFIXES.includes(prefix) || 85 | hasExcludedLabel || 86 | backportInTitle || 87 | EXCLUDE_USERS.includes(pr.user.login) || 88 | pr.merged 89 | ) 90 | return false; 91 | 92 | const created = await getPROpenedTime(github, pr); 93 | const now = Date.now(); 94 | 95 | return now - created < getMinimumOpenTime(pr); 96 | }; 97 | 98 | export const applyLabelToPR = async ( 99 | github: Context['octokit'], 100 | pr: PullRequest, 101 | shouldHaveLabel: boolean, 102 | ) => { 103 | const [owner, repo] = pr.base.repo.full_name.split('/'); 104 | 105 | if (shouldHaveLabel) { 106 | log( 107 | 'applyLabelToPR', 108 | LogLevel.INFO, 109 | `Found PR ${owner}/${repo}#${pr.number} - should ensure ${NEW_PR_LABEL} label exists.`, 110 | ); 111 | 112 | await addLabels(github, { 113 | prNumber: pr.number, 114 | labels: [NEW_PR_LABEL], 115 | repo, 116 | owner, 117 | }); 118 | } else { 119 | log( 120 | 'applyLabelToPR', 121 | LogLevel.INFO, 122 | `Found PR ${owner}/${repo}#${pr.number} - should ensure ${NEW_PR_LABEL} label does not exist.`, 123 | ); 124 | 125 | try { 126 | await removeLabel(github, { 127 | owner, 128 | repo, 129 | prNumber: pr.number, 130 | name: NEW_PR_LABEL, 131 | }); 132 | 133 | pr.labels = pr.labels.filter((l) => l.name !== NEW_PR_LABEL); 134 | await addOrUpdateAPIReviewCheck(github, pr); 135 | } catch { 136 | // Ignore the error here, it's a race condition between the Cron job and GitHub webhooks 137 | } 138 | } 139 | }; 140 | 141 | // Returns whether or not a label is relevant to the new-pr decision tree. 142 | export const labelShouldBeChecked = (label: Label) => { 143 | const relevantLabels = [ 144 | NEW_PR_LABEL, 145 | SEMVER_NONE_LABEL, 146 | ...Object.values(SEMVER_LABELS), 147 | ...EXCLUDE_LABELS, 148 | ]; 149 | return relevantLabels.includes(label.name); 150 | }; 151 | 152 | export async function setUp24HourRule(probot: Probot, disableCronForTesting = false) { 153 | probot.on( 154 | ['pull_request.opened', 'pull_request.unlabeled', 'pull_request.labeled'], 155 | async (context: Context<'pull_request'>) => { 156 | const { action, pull_request: pr, repository } = context.payload; 157 | 158 | // We only care about user labels adds for new-pr and semver labels. 159 | if (action === 'labeled' || action === 'unlabeled') { 160 | const { label } = context.payload as PullRequestLabeledEvent; 161 | if (!labelShouldBeChecked(label!)) return; 162 | } 163 | 164 | probot.log(`24-hour rule received PR: ${repository.full_name}#${pr.number} checking now`); 165 | 166 | const shouldLabel = await shouldPRHaveLabel(context.octokit, pr); 167 | 168 | await applyLabelToPR(context.octokit, pr, shouldLabel); 169 | }, 170 | ); 171 | 172 | if (!disableCronForTesting) runInterval(); 173 | 174 | async function runInterval() { 175 | probot.log('Running 24 hour rule check'); 176 | const github = await probot.auth(); 177 | const { data: installs } = await github.apps.listInstallations({}); 178 | for (const install of installs) { 179 | try { 180 | await runCron(probot, install.id); 181 | } catch (err) { 182 | probot.log(`Failed to run cron for install: ${install.id} ${err}`); 183 | } 184 | } 185 | 186 | setTimeout(runInterval, CHECK_INTERVAL); 187 | } 188 | 189 | async function runCron(probot: Probot, installId: number) { 190 | const octokit = await probot.auth(installId); 191 | const { data } = await octokit.apps.listReposAccessibleToInstallation({}); 192 | 193 | for (const repo of data.repositories) { 194 | probot.log(`Running 24 hour cron job on repo: ${repo.owner.login}/${repo.name}`); 195 | let page = 0; 196 | const prs: PullRequest[] = []; 197 | let lastPRCount = -1; 198 | do { 199 | lastPRCount = prs.length; 200 | prs.push( 201 | ...(( 202 | await octokit.pulls.list({ 203 | owner: repo.owner.login, 204 | repo: repo.name, 205 | per_page: 100, 206 | state: 'open', 207 | page, 208 | }) 209 | ).data as PullRequest[]), 210 | ); 211 | page++; 212 | } while (lastPRCount < prs.length); 213 | 214 | probot.log(`Found ${prs.length} prs for repo: ${repo.owner.login}/${repo.name}`); 215 | 216 | for (const pr of prs) { 217 | const shouldLabel = await shouldPRHaveLabel(octokit, pr); 218 | 219 | // Ensure that API review labels are updated after waiting period. 220 | if (!shouldLabel) { 221 | const approvalState = await addOrUpdateAPIReviewCheck(octokit, pr); 222 | await checkPRReadyForMerge(octokit, pr, approvalState); 223 | } 224 | 225 | await applyLabelToPR(octokit, pr, shouldLabel); 226 | } 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/add-triage-labels.ts: -------------------------------------------------------------------------------- 1 | import { Probot } from 'probot'; 2 | import { 3 | DOCUMENTATION_LABEL, 4 | SEMANTIC_PREFIXES, 5 | SEMVER_LABELS, 6 | SEMVER_NONE_LABEL, 7 | SEMVER_PREFIX, 8 | } from './constants'; 9 | import { LogLevel } from './enums'; 10 | import { addLabels } from './utils/label-utils'; 11 | import { log } from './utils/log-util'; 12 | 13 | export function addBasicPRLabels(probot: Probot) { 14 | probot.on(['pull_request.opened', 'pull_request.edited'], async (context) => { 15 | const { pull_request: pr } = context.payload; 16 | 17 | // Only add triage labels to the default branch. 18 | if (pr.base.ref !== pr.base.repo.default_branch) return; 19 | 20 | const hasSemverLabel = pr.labels.some(({ name }) => name.startsWith(SEMVER_PREFIX)); 21 | 22 | // Respect existing semver labels. 23 | if (hasSemverLabel) { 24 | log( 25 | 'addBasicPRLabels', 26 | LogLevel.INFO, 27 | `#${pr.number} has an existing semver label - aborting`, 28 | ); 29 | return; 30 | } 31 | 32 | const semanticPrefix = pr.title.split(':')[0]; 33 | 34 | const isDocsPR = semanticPrefix === SEMANTIC_PREFIXES.DOCS; 35 | const isCIPR = semanticPrefix === SEMANTIC_PREFIXES.CI; 36 | const isTestPR = semanticPrefix === SEMANTIC_PREFIXES.TEST; 37 | const isBuildPR = semanticPrefix === SEMANTIC_PREFIXES.BUILD; 38 | 39 | // Label Docs PRs as Semver-Patch PRs. 40 | if (isDocsPR) { 41 | log( 42 | 'addBasicPRLabels', 43 | LogLevel.INFO, 44 | `#${pr.number} is a docs PR - adding ${SEMVER_LABELS.PATCH} label`, 45 | ); 46 | await addLabels(context.octokit, { 47 | ...context.repo({}), 48 | prNumber: pr.number, 49 | labels: [SEMVER_LABELS.PATCH, DOCUMENTATION_LABEL], 50 | }); 51 | } else if (isCIPR || isTestPR || isBuildPR) { 52 | // CI, Test, and Build PRs do not affect Semver. 53 | log( 54 | 'addBasicPRLabels', 55 | LogLevel.INFO, 56 | `#${pr.number} is a ci, test, or build PR - adding ${SEMVER_NONE_LABEL} label`, 57 | ); 58 | await addLabels(context.octokit, { 59 | ...context.repo({}), 60 | prNumber: pr.number, 61 | labels: [SEMVER_NONE_LABEL], 62 | }); 63 | } 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const ONE_HOUR = 1000 * 60 * 60; 2 | // 24 Hour Minimum Time 3 | export const MINIMUM_PATCH_OPEN_TIME = ONE_HOUR * 24; 4 | // 168 Hour Minimum Time 5 | export const MINIMUM_MINOR_OPEN_TIME = ONE_HOUR * 24 * 7; 6 | // 168 Hour Minimum Time 7 | export const MINIMUM_MAJOR_OPEN_TIME = ONE_HOUR * 24 * 7; 8 | 9 | // backport type labels 10 | export const NEW_PR_LABEL = 'new-pr 🌱'; 11 | export const BACKPORT_LABEL = 'backport'; 12 | export const BACKPORT_SKIP_LABEL = 'backport-check-skip'; 13 | export const FAST_TRACK_LABEL = 'fast-track 🚅'; 14 | 15 | export const DOCUMENTATION_LABEL = 'documentation :notebook:'; 16 | 17 | export const SEMVER_PREFIX = 'semver/'; 18 | export const SEMVER_NONE_LABEL = 'semver/none'; 19 | export const SEMVER_LABELS = { 20 | PATCH: 'semver/patch', 21 | MINOR: 'semver/minor', 22 | MAJOR: 'semver/major', 23 | }; 24 | 25 | export const SEMANTIC_PREFIXES = { 26 | DOCS: 'docs', 27 | TEST: 'test', 28 | CI: 'ci', 29 | BUILD: 'build', 30 | }; 31 | 32 | export const OWNER = process.env.OWNER || 'electron'; 33 | export const REPO = process.env.REPO || 'electron'; 34 | 35 | export const API_REVIEW_PREFIX = 'api-review/'; 36 | 37 | export const REVIEW_LABELS = { 38 | REQUESTED: 'api-review/requested 🗳', 39 | APPROVED: 'api-review/approved ✅', 40 | DECLINED: 'api-review/declined ❌', 41 | }; 42 | export const API_SKIP_DELAY_LABEL = 'api-review/skip-delay ⏰'; 43 | 44 | export const DEPRECATION_REVIEW_LABELS = { 45 | REQUESTED: 'deprecation-review/requested 📝', 46 | COMPLETE: 'deprecation-review/complete ✅', 47 | }; 48 | 49 | export const REVIEW_STATUS = { 50 | APPROVED: 'APPROVED', 51 | CHANGES_REQUESTED: 'CHANGES_REQUESTED', 52 | COMMENTED: 'COMMENTED', 53 | }; 54 | 55 | export const API_REVIEW_CHECK_NAME = 'API Review'; 56 | 57 | export const DEPRECATION_REVIEW_CHECK_NAME = 'Deprecation Review'; 58 | 59 | export const API_WORKING_GROUP = 'wg-api'; 60 | 61 | // exclusion labels 62 | export const EXCLUDE_LABELS = [BACKPORT_LABEL, BACKPORT_SKIP_LABEL, FAST_TRACK_LABEL]; 63 | export const EXCLUDE_PREFIXES = ['build', 'ci', 'test']; 64 | export const EXCLUDE_USERS = ['roller-bot[bot]', 'electron-bot', 'trop[bot]']; 65 | -------------------------------------------------------------------------------- /src/deprecation-review-state.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | import { Context, Probot } from 'probot'; 5 | import { log } from './utils/log-util'; 6 | import { DEPRECATION_REVIEW_CHECK_NAME, DEPRECATION_REVIEW_LABELS } from './constants'; 7 | import { CheckRunStatus, LogLevel } from './enums'; 8 | import { getEnvVar } from './utils/env-util'; 9 | import { IssueComment, PullRequest } from '@octokit/webhooks-types'; 10 | import { Endpoints } from '@octokit/types'; 11 | import { addLabels, removeLabel } from './utils/label-utils'; 12 | 13 | const checkTitles = { 14 | [DEPRECATION_REVIEW_LABELS.COMPLETE]: 'Complete', 15 | [DEPRECATION_REVIEW_LABELS.REQUESTED]: 'Pending', 16 | }; 17 | 18 | const isBot = (user: string) => user === getEnvVar('BOT_USER_NAME', 'bot'); 19 | export const isReviewLabel = (label: string) => 20 | Object.values(DEPRECATION_REVIEW_LABELS).includes(label); 21 | export const isChecklistComment = (comment: IssueComment) => 22 | isBot(comment.user.login) && comment.body.startsWith('## 🪦 Deprecation Checklist'); 23 | 24 | export async function addOrUpdateDeprecationReviewCheck( 25 | octokit: Context['octokit'], 26 | pr: PullRequest, 27 | ) { 28 | log( 29 | 'addOrUpdateDeprecationReviewCheck', 30 | LogLevel.INFO, 31 | `Validating ${pr.number} by ${pr.user.login}`, 32 | ); 33 | 34 | const owner = pr.base.repo.owner.login; 35 | const repo = pr.head.repo.name; 36 | 37 | if (pr.head.repo.fork) { 38 | log( 39 | 'addOrUpdateDeprecationReviewCheck', 40 | LogLevel.INFO, 41 | `${pr.number} is a fork - checks will not be created or updated`, 42 | ); 43 | // If the PR is a fork PR, return early as the Checks API doesn't work. 44 | return; 45 | } 46 | 47 | // Fetch the latest Deprecation Review check for the PR. 48 | const checkRun = ( 49 | await octokit.checks.listForRef({ 50 | ref: pr.head.sha, 51 | per_page: 100, 52 | owner, 53 | repo, 54 | }) 55 | ).data.check_runs.find((run) => run.name === DEPRECATION_REVIEW_CHECK_NAME); 56 | 57 | const resetToNeutral = async () => { 58 | if (!checkRun) return; 59 | return await octokit.checks.update({ 60 | owner, 61 | repo, 62 | name: DEPRECATION_REVIEW_CHECK_NAME, 63 | status: 'completed', 64 | output: { 65 | title: 'Outdated', 66 | summary: `PR no longer requires ${DEPRECATION_REVIEW_CHECK_NAME}`, 67 | }, 68 | check_run_id: checkRun.id, 69 | conclusion: CheckRunStatus.NEUTRAL, 70 | }); 71 | }; 72 | 73 | // We do not care about PRs without a deprecation review label of any kind. 74 | const currentReviewLabel = pr.labels.find(({ name }) => isReviewLabel(name)); 75 | if (!currentReviewLabel) { 76 | await resetToNeutral(); 77 | return; 78 | } 79 | 80 | // Update the GitHub Check with appropriate deprecation review information. 81 | const updateCheck = async ( 82 | opts: Omit< 83 | Endpoints['POST /repos/{owner}/{repo}/check-runs']['parameters'], 84 | 'baseUrl' | 'headers' | 'mediaType' | 'owner' | 'repo' | 'name' | 'head_sha' 85 | >, 86 | ) => { 87 | if ( 88 | checkRun && 89 | (checkRun.status === opts.status || !opts.status || opts.status === 'completed') 90 | ) { 91 | await octokit.checks.update({ 92 | owner: pr.head.repo.owner.login, 93 | repo: pr.head.repo.name, 94 | name: DEPRECATION_REVIEW_CHECK_NAME, 95 | check_run_id: checkRun.id, 96 | ...opts, 97 | }); 98 | } else { 99 | await octokit.checks.create({ 100 | owner: pr.head.repo.owner.login, 101 | repo: pr.head.repo.name, 102 | name: DEPRECATION_REVIEW_CHECK_NAME, 103 | head_sha: pr.head.sha, 104 | ...opts, 105 | }); 106 | } 107 | }; 108 | 109 | if (currentReviewLabel.name === DEPRECATION_REVIEW_LABELS.REQUESTED) { 110 | log( 111 | 'addOrUpdateDeprecationReviewCheck', 112 | LogLevel.INFO, 113 | `Marking Check for ${pr.number} as Deprecation Review requested`, 114 | ); 115 | return updateCheck({ 116 | status: 'in_progress', 117 | output: { 118 | title: `${checkTitles[currentReviewLabel.name]}`, 119 | summary: 'Review in-progress', 120 | }, 121 | }); 122 | } else if (currentReviewLabel.name === DEPRECATION_REVIEW_LABELS.COMPLETE) { 123 | log( 124 | 'addOrUpdateDeprecationReviewCheck', 125 | LogLevel.INFO, 126 | `Marking Check for ${pr.number} as complete`, 127 | ); 128 | return updateCheck({ 129 | status: 'completed', 130 | conclusion: 'success', 131 | output: { 132 | title: checkTitles[currentReviewLabel.name], 133 | summary: 'All review items have been checked off', 134 | }, 135 | }); 136 | } 137 | } 138 | 139 | export async function maybeAddChecklistComment(octokit: Context['octokit'], pr: PullRequest) { 140 | const owner = pr.base.repo.owner.login; 141 | const repo = pr.head.repo.name; 142 | 143 | // We do not care about PRs without the deprecation review requested label. 144 | const currentReviewLabel = pr.labels.find(({ name }) => isReviewLabel(name)); 145 | if (currentReviewLabel?.name !== DEPRECATION_REVIEW_LABELS.REQUESTED) return; 146 | 147 | // Find the checklist comment from the bot, if it exists 148 | const comment = ( 149 | await octokit.issues.listComments({ 150 | owner, 151 | repo, 152 | issue_number: pr.number, 153 | per_page: 100, 154 | }) 155 | ).data.find((comment) => comment.user && isChecklistComment(comment as IssueComment)); 156 | 157 | if (!comment) { 158 | await octokit.issues.createComment({ 159 | owner, 160 | repo, 161 | issue_number: pr.number, 162 | body: fs.readFileSync( 163 | path.join(__dirname, '..', 'assets', 'deprecation-checklist.md'), 164 | 'utf-8', 165 | ), 166 | }); 167 | } 168 | } 169 | 170 | export function setupDeprecationReviewStateManagement(probot: Probot) { 171 | probot.on( 172 | ['pull_request.synchronize', 'pull_request.opened'], 173 | async (context: Context<'pull_request'>) => { 174 | await addOrUpdateDeprecationReviewCheck(context.octokit, context.payload.pull_request); 175 | }, 176 | ); 177 | 178 | /** 179 | * The deprecation-review/requested label initiates deprecation review, 180 | * but the deprecation-review/complete label is solely controlled by cation 181 | */ 182 | probot.on('pull_request.labeled', async (context: Context<'pull_request.labeled'>) => { 183 | const { 184 | label, 185 | pull_request: pr, 186 | sender: { login: initiator }, 187 | } = context.payload; 188 | 189 | if (!label) { 190 | throw new Error('Something went wrong - label does not exist.'); 191 | } 192 | 193 | // Once a PR is merged, allow tampering. 194 | if (pr.merged) return; 195 | 196 | if (isReviewLabel(label.name)) { 197 | if (!isBot(initiator) && label.name !== DEPRECATION_REVIEW_LABELS.REQUESTED) { 198 | probot.log( 199 | `${initiator} tried to add ${label.name} to PR #${pr.number} - this is not permitted.`, 200 | ); 201 | 202 | await removeLabel(context.octokit, { 203 | ...context.repo({}), 204 | prNumber: pr.number, 205 | name: label.name, 206 | }); 207 | } 208 | 209 | if (label.name === DEPRECATION_REVIEW_LABELS.REQUESTED) { 210 | await maybeAddChecklistComment(context.octokit, context.payload.pull_request); 211 | } 212 | } 213 | 214 | await addOrUpdateDeprecationReviewCheck(context.octokit, pr); 215 | }); 216 | 217 | /** 218 | * If a PR is unlabeled, we want to ensure solely that a human 219 | * did not remove a deprecation-review state label other than 220 | * deprecation-review-requested. 221 | */ 222 | probot.on('pull_request.unlabeled', async (context: Context<'pull_request.unlabeled'>) => { 223 | const { 224 | label, 225 | pull_request: pr, 226 | sender: { login: initiator }, 227 | } = context.payload; 228 | 229 | if (!label) { 230 | throw new Error('Something went wrong - label does not exist.'); 231 | } 232 | 233 | // Once a PR is merged, allow tampering. 234 | if (pr.merged) return; 235 | 236 | // We want to prevent tampering with deprecation-review/* labels other than 237 | // request labels - the bot should control the full review lifecycle. 238 | if (isReviewLabel(label.name)) { 239 | // The 'deprecation-review/requested 📝' label can be removed. 240 | if (label.name === DEPRECATION_REVIEW_LABELS.REQUESTED) { 241 | // Check will be removed by addOrUpdateDeprecationReviewCheck 242 | } else { 243 | if (!isBot(initiator)) { 244 | probot.log( 245 | `${initiator} tried to remove ${label.name} from PR #${pr.number} - this is not permitted.`, 246 | ); 247 | 248 | await addLabels(context.octokit, { 249 | ...context.repo({}), 250 | prNumber: pr.number, 251 | labels: [label.name], 252 | }); 253 | 254 | return; 255 | } 256 | } 257 | 258 | await addOrUpdateDeprecationReviewCheck(context.octokit, pr); 259 | } 260 | }); 261 | 262 | probot.on('issue_comment.edited', async (context: Context<'issue_comment.edited'>) => { 263 | const { 264 | comment, 265 | issue: { labels, number: prNumber, pull_request: pr }, 266 | } = context.payload; 267 | 268 | if (!pr) return; 269 | 270 | // We do not care about PRs without a deprecation review label of any kind, or 271 | // ones which do not have a deprecation-review/requested label. 272 | const currentReviewLabel = labels.find(({ name }) => isReviewLabel(name)); 273 | if (currentReviewLabel?.name !== DEPRECATION_REVIEW_LABELS.REQUESTED) return; 274 | 275 | // We only care about the checklist comment from this bot 276 | if (!isChecklistComment(comment)) return; 277 | 278 | // If there are no unchecked items then add the review completed label 279 | if (comment.body.search(/^- \[ \] /gm) === -1) { 280 | await addLabels(context.octokit, { 281 | ...context.repo({}), 282 | prNumber: prNumber, 283 | labels: [DEPRECATION_REVIEW_LABELS.COMPLETE], 284 | }); 285 | await removeLabel(context.octokit, { 286 | ...context.repo({}), 287 | prNumber: prNumber, 288 | name: DEPRECATION_REVIEW_LABELS.REQUESTED, 289 | }); 290 | } 291 | }); 292 | } 293 | -------------------------------------------------------------------------------- /src/enforce-semver-labels.ts: -------------------------------------------------------------------------------- 1 | import { Probot } from 'probot'; 2 | import { SEMVER_LABELS, SEMVER_NONE_LABEL } from './constants'; 3 | import { log } from './utils/log-util'; 4 | import { LogLevel } from './enums'; 5 | 6 | const ALL_SEMVER_LABELS = [ 7 | SEMVER_LABELS.MAJOR, 8 | SEMVER_LABELS.MINOR, 9 | SEMVER_LABELS.PATCH, 10 | SEMVER_NONE_LABEL, 11 | ]; 12 | 13 | export function setupSemverLabelEnforcement(probot: Probot) { 14 | probot.on( 15 | [ 16 | 'pull_request.opened', 17 | 'pull_request.unlabeled', 18 | 'pull_request.labeled', 19 | 'pull_request.synchronize', 20 | ], 21 | async (context) => { 22 | const { pull_request: pr } = context.payload; 23 | 24 | log('setupSemverLabelEnforcement', LogLevel.INFO, `Checking #${pr.number} for semver label`); 25 | 26 | const semverLabels = pr.labels.filter((l) => ALL_SEMVER_LABELS.includes(l.name)); 27 | if (semverLabels.length === 0) { 28 | log('setupSemverLabelEnforcement', LogLevel.ERROR, `#${pr.number} is missing semver label`); 29 | 30 | await context.octokit.checks.create( 31 | context.repo({ 32 | name: 'Semver Label Enforcement', 33 | head_sha: pr.head.sha, 34 | status: 'in_progress', 35 | output: { 36 | title: 'No semver/* label found', 37 | summary: "We couldn't find a semver/* label, please add one", 38 | }, 39 | }), 40 | ); 41 | } else if (semverLabels.length > 1) { 42 | log( 43 | 'setupSemverLabelEnforcement', 44 | LogLevel.ERROR, 45 | `#${pr.number} has duplicate semver labels`, 46 | ); 47 | 48 | await context.octokit.checks.create( 49 | context.repo({ 50 | name: 'Semver Label Enforcement', 51 | head_sha: pr.head.sha, 52 | status: 'in_progress', 53 | output: { 54 | title: 'Multiple semver/* labels found', 55 | summary: 'We found multiple semver/* labels, please remove one', 56 | }, 57 | }), 58 | ); 59 | } else { 60 | log('setupSemverLabelEnforcement', LogLevel.INFO, `#${pr.number} has a valid semver label`); 61 | 62 | await context.octokit.checks.create( 63 | context.repo({ 64 | name: 'Semver Label Enforcement', 65 | head_sha: pr.head.sha, 66 | status: 'completed', 67 | conclusion: 'success', 68 | output: { 69 | title: `Found "${semverLabels[0].name}"`, 70 | summary: 'Found a single semver/* label, looking good here.', 71 | }, 72 | }), 73 | ); 74 | } 75 | }, 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/enums.ts: -------------------------------------------------------------------------------- 1 | export enum CheckRunStatus { 2 | NEUTRAL = 'neutral', 3 | FAILURE = 'failure', 4 | SUCCESS = 'success', 5 | } 6 | 7 | export enum LogLevel { 8 | LOG, 9 | INFO, 10 | WARN, 11 | ERROR, 12 | } 13 | 14 | export enum ApiReviewAction { 15 | LGTM = 'lgtm', 16 | REQUEST_CHANGES = 'r-changes', 17 | DECLINE = 'decline', 18 | } 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from '@sentry/node'; 2 | 3 | if (process.env.SENTRY_DSN) { 4 | Sentry.init({ 5 | dsn: process.env.SENTRY_DSN, 6 | }); 7 | } 8 | 9 | import { Probot } from 'probot'; 10 | import { setUp24HourRule } from './24-hour-rule'; 11 | import { setupSemverLabelEnforcement } from './enforce-semver-labels'; 12 | import { setupAPIReviewStateManagement } from './api-review-state'; 13 | import { addBasicPRLabels } from './add-triage-labels'; 14 | import { setupDeprecationReviewStateManagement } from './deprecation-review-state'; 15 | 16 | const probotHandler = async (app: Probot) => { 17 | app.onError((errorEvent) => { 18 | for (const error of Array.from(errorEvent)) { 19 | if (process.env.SENTRY_DSN) { 20 | Sentry.captureException(error, { 21 | req: error.request, 22 | extra: { 23 | event: errorEvent.event, 24 | status: error.status, 25 | }, 26 | } as any); 27 | } 28 | } 29 | }); 30 | setUp24HourRule(app); 31 | setupSemverLabelEnforcement(app); 32 | setupAPIReviewStateManagement(app); 33 | addBasicPRLabels(app); 34 | setupDeprecationReviewStateManagement(app); 35 | }; 36 | 37 | module.exports = probotHandler; 38 | 39 | type ProbotHandler = typeof probotHandler; 40 | export { ProbotHandler }; 41 | -------------------------------------------------------------------------------- /src/utils/check-utils.ts: -------------------------------------------------------------------------------- 1 | import { PullRequest } from '@octokit/webhooks-types'; 2 | import { SEMVER_LABELS } from '../constants'; 3 | 4 | /** 5 | * Returns whether or not the 'api-review/requested 🗳' label 6 | * can be removed from a given pull request without violating 7 | * requirements. 8 | * 9 | * @param pr The pull request being checked for API Review requirements. 10 | */ 11 | export function isAPIReviewRequired(pr: PullRequest): boolean { 12 | if (!pr) return false; 13 | 14 | // If it's not a semver-patch PR it must be reviewed. 15 | for (const label of pr.labels) { 16 | if ([SEMVER_LABELS.MAJOR, SEMVER_LABELS.MINOR].includes(label.name)) { 17 | return true; 18 | } 19 | } 20 | 21 | return false; 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/env-util.ts: -------------------------------------------------------------------------------- 1 | import { LogLevel } from '../enums'; 2 | import { log } from './log-util'; 3 | 4 | /** 5 | * Checks that a given environment variable exists, and returns 6 | * its value if it does. Conditionally throws an error on failure. 7 | * 8 | * @param envVar - the environment variable to retrieve 9 | * @param defaultValue - default value to use if no environment var is found 10 | * @returns the value of the env var being checked, or the default value if one is passed 11 | */ 12 | export function getEnvVar(envVar: string, defaultValue?: string): string { 13 | log('getEnvVar', LogLevel.INFO, `Fetching env var '${envVar}'`); 14 | 15 | const value = process.env[envVar] || defaultValue; 16 | if (!value && value !== '') { 17 | log('getEnvVar', LogLevel.ERROR, `Missing environment variable '${envVar}'`); 18 | throw new Error(`Missing environment variable '${envVar}'`); 19 | } 20 | return value; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/label-utils.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'probot'; 2 | import { log } from './log-util'; 3 | import { LogLevel } from '../enums'; 4 | 5 | export const addLabels = async ( 6 | octokit: Context['octokit'], 7 | data: { 8 | prNumber: number; 9 | owner: string; 10 | repo: string; 11 | labels: string[]; 12 | }, 13 | ) => { 14 | log( 15 | 'addLabels', 16 | LogLevel.INFO, 17 | `Attempting to add ${data.labels.join(', ')} to PR #${data.prNumber}`, 18 | ); 19 | 20 | // If the PR already has the label, don't try to add it. 21 | const existingLabels = await getLabelsForPR(octokit, data); 22 | const labels = data.labels.filter(async (label) => !existingLabels.includes(label)); 23 | 24 | await octokit.issues.addLabels({ 25 | owner: data.owner, 26 | repo: data.repo, 27 | issue_number: data.prNumber, 28 | labels, 29 | }); 30 | }; 31 | 32 | export const removeLabel = async ( 33 | octokit: Context['octokit'], 34 | data: { 35 | prNumber: number; 36 | name: string; 37 | owner: string; 38 | repo: string; 39 | }, 40 | ) => { 41 | log('removeLabel', LogLevel.INFO, `Attempting to remove ${data.name} from PR #${data.prNumber}`); 42 | 43 | // If the issue does not have the label, don't try remove it. 44 | const labelExists = await labelExistsOnPR(octokit, data); 45 | if (labelExists) { 46 | await octokit.issues.removeLabel({ 47 | owner: data.owner, 48 | repo: data.repo, 49 | issue_number: data.prNumber, 50 | name: data.name, 51 | }); 52 | log('removeLabel', LogLevel.INFO, `Successfully removed ${data.name} from #${data.prNumber}.`); 53 | } else { 54 | log( 55 | 'removeLabel', 56 | LogLevel.INFO, 57 | `Determined ${data.name} does not exist on #${data.prNumber}.`, 58 | ); 59 | } 60 | }; 61 | 62 | export const getLabelsForPR = async ( 63 | octokit: Context['octokit'], 64 | data: { 65 | prNumber: number; 66 | owner: string; 67 | repo: string; 68 | }, 69 | ) => { 70 | log('getLabelsForPR', LogLevel.INFO, `Fetching all labels for #${data.prNumber}`); 71 | 72 | const { data: labels } = await octokit.issues.listLabelsOnIssue({ 73 | owner: data.owner, 74 | repo: data.repo, 75 | issue_number: data.prNumber, 76 | per_page: 100, 77 | page: 1, 78 | }); 79 | 80 | log('getLabelsForPR', LogLevel.INFO, `Found ${labels.length} labels for #${data.prNumber}`); 81 | 82 | return labels.map((l) => l.name); 83 | }; 84 | 85 | export const labelExistsOnPR = async ( 86 | octokit: Context['octokit'], 87 | data: { 88 | prNumber: number; 89 | name: string; 90 | owner: string; 91 | repo: string; 92 | }, 93 | ) => { 94 | log('labelExistsOnPR', LogLevel.INFO, `Checking if ${data.name} exists on #${data.prNumber}`); 95 | 96 | const labels = await getLabelsForPR(octokit, data); 97 | return labels.some((label) => label === data.name); 98 | }; 99 | -------------------------------------------------------------------------------- /src/utils/log-util.ts: -------------------------------------------------------------------------------- 1 | import { LogLevel } from '../enums'; 2 | 3 | /** 4 | * Logs information about different actions taking place to console. 5 | * 6 | * @param functionName - the name of the function where the logging is happening 7 | * @param logLevel - the severity level of the log 8 | * @param message - the message to write to console 9 | */ 10 | export const log = (functionName: string, logLevel: LogLevel, ...message: unknown[]) => { 11 | const output = `${functionName}: ${message}`; 12 | 13 | if (logLevel === LogLevel.INFO) { 14 | console.info(output); 15 | } else if (logLevel === LogLevel.WARN) { 16 | console.warn(output); 17 | } else if (logLevel === LogLevel.ERROR) { 18 | console.error(output); 19 | } else { 20 | console.log(output); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2018", 5 | "outDir": "lib", 6 | "lib": [ 7 | "es6", 8 | "es2016.array.include" 9 | ], 10 | "sourceMap": true, 11 | "rootDir": "src", 12 | "experimentalDecorators": true, 13 | "allowJs": true, 14 | "strict": true, 15 | "allowSyntheticDefaultImports": true 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | "spec", 20 | "lib", 21 | "coverage", 22 | "vitest.config.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /typings/ambient.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'promise-events' { 2 | export class EventEmitter {} 3 | } -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | test: { 6 | clearMocks: true, 7 | }, 8 | }); 9 | --------------------------------------------------------------------------------