├── .github ├── CODEOWNERS └── workflows │ ├── lint.yml │ ├── needs-manual-audit.yml │ ├── test.yml │ └── unreleased-audit.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc.json ├── LICENSE ├── README.md ├── action-audit.js ├── app.json ├── constants.js ├── eslint.config.js ├── package-lock.json ├── package.json ├── server.js ├── test ├── fixtures │ ├── needs-manual.json │ ├── unmerged.json │ └── unreleased-commits.json ├── gh-api-calls.js ├── needs-manual.spec.js ├── unmerged.spec.js └── unreleased-commits.spec.js └── utils ├── helpers.js ├── needs-manual-prs.js ├── octokit.js ├── unmerged-prs.js └── unreleased-commits.js /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Fall back to authors for all PR review 2 | * @electron/wg-releases -------------------------------------------------------------------------------- /.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 # v4.1.1 19 | - name: Setup Node.js 20 | uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 21 | with: 22 | node-version: 20.x 23 | - name: Lint 24 | run: | 25 | npm ci 26 | npm run lint 27 | -------------------------------------------------------------------------------- /.github/workflows/needs-manual-audit.yml: -------------------------------------------------------------------------------- 1 | name: Perform Manual Backport Audit 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 18 * * 1' 7 | 8 | jobs: 9 | audit: 10 | name: Perform Manual Backport Audit 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 14 | - name: Setup Node.js 15 | uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 16 | with: 17 | node-version: lts/* 18 | - name: Install Dependencies 19 | run: npm install 20 | - name: Check PRs Needing Manual Backport 21 | run: node action-audit.js 22 | env: 23 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 24 | UNRELEASED_GITHUB_APP_CREDS: ${{ secrets.UNRELEASED_GITHUB_APP_CREDS }} 25 | ACTION_TYPE: 'needs-manual' 26 | -------------------------------------------------------------------------------- /.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: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 19 | - name: Setup Node.js 20 | uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 21 | with: 22 | node-version: 20.x 23 | - name: Test - Unit Tests 24 | run: | 25 | npm ci 26 | npm test 27 | - name: Test - GitHub API calls 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | run: npm run test:ghapi 31 | -------------------------------------------------------------------------------- /.github/workflows/unreleased-audit.yml: -------------------------------------------------------------------------------- 1 | name: Perform Unreleased Audit 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 16 * * 1' 7 | 8 | jobs: 9 | audit: 10 | name: Perform Unreleased Audit 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 14 | - name: Setup Node.js 15 | uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 16 | with: 17 | node-version: lts/* 18 | - name: Install Dependencies 19 | run: npm install 20 | - name: Check Unreleased Commits 21 | run: node action-audit.js 22 | env: 23 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 24 | UNRELEASED_GITHUB_APP_CREDS: ${{ secrets.UNRELEASED_GITHUB_APP_CREDS }} 25 | ACTION_TYPE: 'unreleased' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .envrc 3 | yarn.lock 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run lint-staged 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "printWidth": 80, 6 | "parser": "typescript", 7 | "endOfLine": "lf", 8 | "semi": true 9 | } 10 | -------------------------------------------------------------------------------- /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 | ## Electron Unreleased Commit Audit 2 | 3 | [![Test](https://github.com/electron/unreleased/actions/workflows/test.yml/badge.svg)](https://github.com/electron/unreleased/actions/workflows/test.yml) 4 | [![Perform Manual Backport Audit](https://github.com/electron/unreleased/actions/workflows/needs-manual-audit.yml/badge.svg)](https://github.com/electron/unreleased/actions/workflows/needs-manual-audit.yml) 5 | [![Perform Unreleased Audit](https://github.com/electron/unreleased/actions/workflows/unreleased-audit.yml/badge.svg)](https://github.com/electron/unreleased/actions/workflows/unreleased-audit.yml) 6 | 7 | This repository allows users to query information relating to release branches on a desired repository. 8 | 9 | There are four potential actions possible: 10 | 1. Reporting commits unreleased for a specific release branch. 11 | 2. Reporting pull requests targeting a specific release branch that have not yet been merged. 12 | 3. Reporting pull requests which need to be manually backported to a particular release line. 13 | 4. Perform a pre-release audit combining actions 2 and 3. 14 | 15 | An unreleased commit audit is triggered automatically via cron job on Monday mornings at 9AM PST for all supported release branches of your repository. 16 | 17 | ### Setup 18 | 19 | This tool will default to setting the organization and repository name to [`electron/electron`](https://github.com/electron/electron), but you can set your own by setting `ORGANIZATION_NAME` and `REPO_NAME` as environment variables. 20 | 21 | You can also set the number of currently supported release lines with the `NUM_SUPPORTED_VERSIONS` env var. 22 | 23 | ### Check Unreleased 24 | 25 | An unreleased commit audit can be triggered via Slack using the following: 26 | 27 | ```sh 28 | /check-unreleased 29 | ``` 30 | 31 | where `branch-name` matches the name of a release line branch of the desired repository. 32 | 33 | Example: 34 | 35 | ```sh 36 | /check-unreleased 9-x-y 37 | ``` 38 | 39 | To manually query the status of all currently supported release branches: 40 | 41 | ```sh 42 | /check-unreleased all 43 | ``` 44 | 45 | ### Check Unmerged 46 | 47 | An unmerged pull request audit can be triggered via Slack using the following: 48 | 49 | ```sh 50 | /check-unmerged 51 | ``` 52 | 53 | where `branch-name` matches the name of a release line branch of the repository. 54 | 55 | Example: 56 | 57 | ```sh 58 | /check-unmerged 10-x-y 59 | ``` 60 | 61 | ### Check Needs Manual 62 | 63 | An audit of pull requests needing manual backport to a particular release line can be triggered via Slack using the following: 64 | 65 | ```sh 66 | /check-needs-manual 67 | ``` 68 | 69 | where `branch_name` matches the name of a release line branch of the repository. 70 | 71 | Example: 72 | 73 | ```sh 74 | /check-needs-manual 8-x-y 75 | ``` 76 | 77 | ### Verify Upcoming Release Type 78 | 79 | An verification of the semver type of the next release for a given branch can be triggered via Slack using the following: 80 | 81 | ```sh 82 | /verify-semver 83 | ``` 84 | 85 | where `branch_name` matches the name of a release line branch of the repository. 86 | 87 | Example: 88 | 89 | ```sh 90 | /verify-semver 91 | ``` 92 | 93 | Example output: 94 | 95 | > Next release type for `12-x-y` is: **semver/patch** 96 | 97 | #### Scoping By Author 98 | 99 | This command can be scoped by author of the original PR. For example: 100 | 101 | ```sh 102 | /check-needs-manual 8-x-y codebytere 103 | ``` 104 | 105 | will return all pull requests needing manual backport to a particular release line where the author of the original PR was @codebytere 106 | 107 | ``` 108 | PRs needing manual backport to 8-x-y (from @codebytere): 109 | * #23782 - fix: volume key globalShortcut registration 110 | * #23776 - fix: asynchronous URL loading in BW Proxy 111 | * #22342 - fix: don't run environment bootstrapper 112 | There are 3 PR(s) needing manual backport to 8-x-y! 113 | ``` 114 | 115 | #### Reminding Authors 116 | 117 | You can `@mention` authors in the audit to remind them of the manual backports they need to handle: 118 | 119 | ```sh 120 | /check-needs-manual 8-x-y remind 121 | ``` 122 | 123 | This will produce a list similar to the following: 124 | 125 | ``` 126 | PR(s) needing manual backport to 8-x-y (from @codebytere): 127 | * #23782 - fix: volume key globalShortcut registration (@codebytere) 128 | * #23776 - fix: asynchronous URL loading in BW Proxy (@codebytere) 129 | * #23678 - fix: read GTK dark theme setting on Linux (@zcbenz) 130 | * #23653 - docs: errors in isolated world are not dispatched to foreign worlds (@zcbenz) 131 | * #23415 - test: skip "handles Promise timeouts correctly" when ELECTRON_RUN_AS_NODE is disabled (@miniak) 132 | * #22342 - fix: don't run environment bootstrapper (@codebytere) 133 | There are 6 PR(s) needing manual backport to 8-x-y! 134 | ``` 135 | 136 | ### Perform Pre-Release Audit 137 | 138 | A pre-release audit combines the needs-manual audit with the unmerged audit to return a full list of action items that may needs to occur before a beta or stable release. 139 | 140 | ```sh 141 | /audit-pre-release 142 | ``` 143 | 144 | where `branch_name` matches the name of a release line branch of the repository. 145 | 146 | Example: 147 | 148 | ```sh 149 | /audit-pre-release 8-x-y 150 | ``` 151 | 152 | ## Environment Variables 153 | 154 | If you would like to use `unreleased`, there are several environment variables you will need to leverage to customize it to serve your needs. 155 | 156 | * `ORGANIZATION_NAME` - the name of your organization, e.g. `electron`. 157 | * `REPO_NAME` - the name of the repository to run audits in, e.g. `electron`. 158 | * `NUM_SUPPORTED_VERSIONS` - the number of supported backport release lines (default is 4). 159 | * `UNRELEASED_GITHUB_APP_CREDS` - private key credentials generated for a GitHub App (required). 160 | * `SLACK_BOT_TOKEN` - the token that will be used to post audit result into a Slack workspace channel (required). 161 | * `BLOCKS_RELEASE_LABEL` - the GitHub label used to denote unmerged pull requests that should block a release (default is `blocks-release`). 162 | * `AUDIT_POST_CHANNEL` - the Slack workspace channel in which to post audit results. 163 | -------------------------------------------------------------------------------- /action-audit.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | const { WebClient } = require('@slack/web-api'); 3 | 4 | const { 5 | buildUnreleasedCommitsMessage, 6 | fetchUnreleasedCommits, 7 | } = require('./utils/unreleased-commits'); 8 | 9 | const { 10 | buildNeedsManualPRsMessage, 11 | fetchNeedsManualPRs, 12 | } = require('./utils/needs-manual-prs'); 13 | 14 | const { 15 | SLACK_BOT_TOKEN, 16 | ACTION_TYPE, 17 | AUDIT_POST_CHANNEL, 18 | MILLISECONDS_PER_DAY, 19 | UNRELEASED_DAYS_WARN_THRESHOLD, 20 | } = require('./constants'); 21 | 22 | const Actions = { 23 | UNRELEASED: 'unreleased', 24 | NEEDS_MANUAL: 'needs-manual', 25 | }; 26 | 27 | const { getSupportedBranches } = require('./utils/helpers'); 28 | 29 | const slackWebClient = new WebClient(SLACK_BOT_TOKEN); 30 | 31 | async function run() { 32 | const initiatedBy = 'automatic audit'; 33 | const branches = await getSupportedBranches(); 34 | for (const branch of branches) { 35 | core.info(`Auditing ${ACTION_TYPE} on branch ${branch}`); 36 | 37 | let commits; 38 | if (ACTION_TYPE === Actions.UNRELEASED) { 39 | commits = (await fetchUnreleasedCommits(branch)).commits; 40 | } else if (ACTION_TYPE === Actions.NEEDS_MANUAL) { 41 | commits = await fetchNeedsManualPRs(branch, null /* author */); 42 | } 43 | 44 | core.info(`Found ${commits.length} commits on ${branch}`); 45 | if (ACTION_TYPE === 'unreleased' && commits.length >= 10) { 46 | core.info( 47 | `Reached ${commits.length} commits on ${branch}, time to release.`, 48 | ); 49 | } 50 | 51 | let text = ''; 52 | if (ACTION_TYPE === Actions.UNRELEASED) { 53 | text += buildUnreleasedCommitsMessage(branch, commits, initiatedBy); 54 | const earliestCommit = commits[0]; 55 | if (earliestCommit !== undefined) { 56 | const unreleasedDays = Math.floor( 57 | (Date.now() - new Date(earliestCommit.committer.date).getTime()) / 58 | MILLISECONDS_PER_DAY, 59 | ); 60 | if (unreleasedDays > UNRELEASED_DAYS_WARN_THRESHOLD) { 61 | text += `\n ⚠️ *There have been unreleased commits on \`${branch}\` for ${unreleasedDays} days!*`; 62 | } 63 | } 64 | } else if (ACTION_TYPE === Actions.NEEDS_MANUAL) { 65 | text += buildNeedsManualPRsMessage( 66 | branch, 67 | commits, 68 | true /* shouldRemind */, 69 | ); 70 | } 71 | 72 | const result = await slackWebClient.chat.postMessage({ 73 | channel: AUDIT_POST_CHANNEL, 74 | unfurl_links: false, 75 | text, 76 | }); 77 | 78 | if (result.ok) { 79 | core.info(`✅ Audit message sent for ${branch} 🚀`); 80 | } else { 81 | core.setFailed( 82 | `❌ Unable to send audit info for ${branch}: ` + result.error, 83 | ); 84 | } 85 | } 86 | core.info(` ✅ All release branches audited successfully`); 87 | } 88 | 89 | run().catch((e) => { 90 | core.setFailed(`Workflow failed: ${e.message}`); 91 | }); 92 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unreleased", 3 | "description": "", 4 | "scripts": {}, 5 | "env": {}, 6 | "formation": {}, 7 | "addons": [], 8 | "buildpacks": [ 9 | { 10 | "url": "heroku/nodejs" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | const ORGANIZATION_NAME = process.env.ORGANIZATION_NAME || 'electron'; 2 | const REPO_NAME = process.env.REPO_NAME || 'electron'; 3 | 4 | const NUM_SUPPORTED_VERSIONS = process.env.NUM_SUPPORTED_VERSIONS || 4; 5 | 6 | const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN; 7 | const ACTION_TYPE = process.env.ACTION_TYPE; 8 | const UNRELEASED_GITHUB_APP_CREDS = process.env.UNRELEASED_GITHUB_APP_CREDS; 9 | 10 | const BLOCKS_RELEASE_LABEL = 11 | process.env.BLOCKS_RELEASE_LABEL || 'blocks-release'; 12 | 13 | const AUDIT_POST_CHANNEL = process.env.AUDIT_POST_CHANNEL || '#wg-releases'; 14 | 15 | const RELEASE_BRANCH_PATTERN = /^(\d)+-(?:(?:[0-9]+-x$)|(?:x+-y$))$/; 16 | 17 | const EXCLUDED_COMMIT_PATTERN = /^(?:build|ci|test)(?:\(\w+\))?:/; 18 | 19 | const MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; 20 | 21 | const UNRELEASED_DAYS_WARN_THRESHOLD = 8; 22 | 23 | module.exports = { 24 | ACTION_TYPE, 25 | AUDIT_POST_CHANNEL, 26 | BLOCKS_RELEASE_LABEL, 27 | EXCLUDED_COMMIT_PATTERN, 28 | NUM_SUPPORTED_VERSIONS, 29 | ORGANIZATION_NAME, 30 | RELEASE_BRANCH_PATTERN, 31 | REPO_NAME, 32 | SLACK_BOT_TOKEN, 33 | UNRELEASED_GITHUB_APP_CREDS, 34 | MILLISECONDS_PER_DAY, 35 | UNRELEASED_DAYS_WARN_THRESHOLD, 36 | }; 37 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const js = require('@eslint/js'); 2 | const globals = require('globals'); 3 | 4 | module.exports = [ 5 | js.configs.recommended, 6 | { 7 | languageOptions: { 8 | ecmaVersion: 12, 9 | globals: { 10 | ...globals.node, 11 | ...globals.commonjs, 12 | ...globals.es2021, 13 | }, 14 | }, 15 | }, 16 | ]; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unreleased", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Checks for and reports commits unreleased for all currently supported branches.", 6 | "scripts": { 7 | "eslint:format": "eslint --fix '**/*.js'", 8 | "eslint:lint": "eslint '**/*.js'", 9 | "lint": "npm run prettier:lint && npm run eslint:lint", 10 | "lint-staged": "lint-staged", 11 | "prepare": "husky", 12 | "prettier:format": "prettier --write '**/*.js'", 13 | "prettier:lint": "prettier --check '**/*.js'", 14 | "start": "node server.js", 15 | "test": "node --test **/*.spec.js", 16 | "test:ghapi": "node --test test/gh-api-calls.js" 17 | }, 18 | "engines": { 19 | "node": ">=20" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/electron/unreleased.git" 24 | }, 25 | "author": "Electron Community", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/electron/unreleased/issues" 29 | }, 30 | "homepage": "https://github.com/electron/unreleased#readme", 31 | "dependencies": { 32 | "@actions/core": "^1.10.0", 33 | "@electron/github-app-auth": "^2.0.0", 34 | "@octokit/rest": "^19.0.11", 35 | "@slack/web-api": "^6.10.0", 36 | "body-parser": "^1.18.3", 37 | "express": "^5.0.1" 38 | }, 39 | "devDependencies": { 40 | "@octokit/graphql": "^5.0.6", 41 | "eslint": "^9.12.0", 42 | "globals": "^15.11.0", 43 | "husky": "^9.1.6", 44 | "lint-staged": "^15.2.10", 45 | "prettier": "^3.3.3" 46 | }, 47 | "lint-staged": { 48 | "**/*.js": [ 49 | "eslint --fix", 50 | "prettier --write" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | 4 | const { 5 | buildUnreleasedCommitsMessage, 6 | fetchUnreleasedCommits, 7 | } = require('./utils/unreleased-commits'); 8 | const { 9 | buildUnmergedPRsMessage, 10 | fetchUnmergedPRs, 11 | } = require('./utils/unmerged-prs'); 12 | const { 13 | buildNeedsManualPRsMessage, 14 | fetchNeedsManualPRs, 15 | } = require('./utils/needs-manual-prs'); 16 | const { 17 | fetchInitiator, 18 | getSemverForCommitRange, 19 | getSupportedBranches, 20 | isInvalidBranch, 21 | postToSlack, 22 | SEMVER_TYPE, 23 | timingSafeEqual, 24 | } = require('./utils/helpers'); 25 | const { getOctokit } = require('./utils/octokit'); 26 | 27 | const app = express(); 28 | app.use(bodyParser.urlencoded({ extended: true })); 29 | app.use(bodyParser.json()); 30 | 31 | app.use(express.static('public')); 32 | 33 | app.get('/verify-semver', async (req, res) => { 34 | if ( 35 | !timingSafeEqual( 36 | req.headers.authorization, 37 | process.env.VERIFY_SEMVER_AUTH_HEADER, 38 | ) 39 | ) { 40 | return res.status(401).end(); 41 | } 42 | 43 | const { branch } = req.query; 44 | 45 | const branches = await getSupportedBranches(); 46 | if (isInvalidBranch(branches, branch)) { 47 | res.status(400).json({ error: `${branch} is not a valid branch` }); 48 | return; 49 | } 50 | 51 | try { 52 | const { commits, lastTag } = await fetchUnreleasedCommits(branch); 53 | console.info(`Found ${commits.length} commits unreleased on ${branch}`); 54 | 55 | const semverType = lastTag?.prerelease 56 | ? SEMVER_TYPE.PATCH 57 | : await getSemverForCommitRange(commits, branch); 58 | console.info(`Determined that next release on ${branch} is ${semverType}`); 59 | 60 | return res.json({ semverType }); 61 | } catch (err) { 62 | console.error(err); 63 | res.status(500).json({ 64 | error: true, 65 | }); 66 | } 67 | }); 68 | 69 | app.post('/verify-semver', async (req, res) => { 70 | res.status(200).end(); 71 | 72 | const branches = await getSupportedBranches(); 73 | const branch = req.body.text; 74 | 75 | const initiator = await fetchInitiator(req); 76 | console.log( 77 | `${initiator.name} initiated release semver verification for branch: ${branch}`, 78 | ); 79 | 80 | if (isInvalidBranch(branches, branch)) { 81 | console.error(`${branch} is not a valid branch`); 82 | await postToSlack( 83 | { 84 | response_type: 'ephemeral', 85 | text: `Invalid branch *${branch}*. Try again?`, 86 | }, 87 | req.body.response_url, 88 | ); 89 | return; 90 | } 91 | 92 | try { 93 | const { commits, lastTag } = await fetchUnreleasedCommits(branch); 94 | console.info(`Found ${commits.length} commits unreleased on ${branch}`); 95 | 96 | const semverType = lastTag?.prerelease 97 | ? SEMVER_TYPE.PATCH 98 | : await getSemverForCommitRange(commits, branch); 99 | console.info(`Determined that next release on ${branch} is ${semverType}`); 100 | 101 | await postToSlack( 102 | { 103 | response_type: 'in_channel', 104 | text: `Next release type for \`${branch}\` is: *${semverType}*`, 105 | }, 106 | req.body.response_url, 107 | ); 108 | } catch (err) { 109 | console.error(err); 110 | await postToSlack( 111 | { 112 | response_type: 'ephemeral', 113 | text: `Error: ${err.message}`, 114 | }, 115 | req.body.response_url, 116 | ); 117 | } 118 | }); 119 | 120 | // Check for pull requests targeting a specified release branch 121 | // that have not yet been merged. 122 | app.post('/unmerged', async (req, res) => { 123 | const branches = await getSupportedBranches(); 124 | const branch = req.body.text; 125 | 126 | const initiator = await fetchInitiator(req); 127 | console.log( 128 | `${initiator.name} initiated unmerged audit for branch: ${branch}`, 129 | ); 130 | 131 | if (branch !== 'all' && isInvalidBranch(branches, branch)) { 132 | console.error(`${branch} is not a valid branch`); 133 | await postToSlack( 134 | { 135 | response_type: 'ephemeral', 136 | text: `Invalid branch *${branch}*. Try again?`, 137 | }, 138 | req.body.response_url, 139 | ); 140 | return res.status(200).end(); 141 | } 142 | 143 | console.log(`Auditing unmerged PRs on branch: ${branch}`); 144 | 145 | try { 146 | const branchesToCheck = branch === 'all' ? branches : [branch]; 147 | 148 | let messages = []; 149 | for (const branch of branchesToCheck) { 150 | const prs = await fetchUnmergedPRs(branch); 151 | console.log(`Found ${prs.length} unmerged PR(s) targeting ${branch}`); 152 | 153 | let message; 154 | if (!prs || prs.length === 0) { 155 | message = `*No PR(s) pending merge to ${branch}*`; 156 | } else { 157 | message = `Unmerged pull requests targeting *${branch}* (from <@${initiator.id}>):\n`; 158 | message += buildUnmergedPRsMessage(branch, prs); 159 | } 160 | messages.push(message); 161 | } 162 | 163 | await postToSlack( 164 | { 165 | response_type: 'in_channel', 166 | text: messages.join('\n'), 167 | }, 168 | req.body.response_url, 169 | ); 170 | } catch (err) { 171 | console.error(err); 172 | await postToSlack( 173 | { 174 | response_type: 'ephemeral', 175 | text: `Error: ${err.message}`, 176 | }, 177 | req.body.response_url, 178 | ); 179 | } 180 | 181 | return res.status(200).end(); 182 | }); 183 | 184 | // Check for pull requests which have been merged to main and labeled 185 | // with target/BRANCH_NAME that trop failed for and which still need manual backports. 186 | app.post('/needs-manual', async (req, res) => { 187 | const branches = await getSupportedBranches(); 188 | const REMIND = 'remind'; 189 | 190 | let [branch, author, remind] = req.body.text.split(' '); 191 | 192 | let shouldRemind = false; 193 | if (author === REMIND && remind === undefined) { 194 | shouldRemind = true; 195 | author = null; 196 | } else if (remind === REMIND) { 197 | shouldRemind = true; 198 | } 199 | 200 | const initiator = await fetchInitiator(req); 201 | console.log( 202 | `${initiator.name} initiated needs-manual audit for branch: ${branch}`, 203 | ); 204 | 205 | if (branch !== 'all' && isInvalidBranch(branches, branch)) { 206 | console.error(`${branch} is not a valid branch`); 207 | await postToSlack( 208 | { 209 | response_type: 'ephemeral', 210 | text: `Invalid branch *${branch}*. Try again?`, 211 | }, 212 | req.body.response_url, 213 | ); 214 | return res.status(200).end(); 215 | } 216 | 217 | if (author) { 218 | try { 219 | const octokit = await getOctokit(); 220 | await octokit.users.getByUsername({ username: author }); 221 | } catch { 222 | console.error(`${author} is not a valid GitHub user`); 223 | await postToSlack( 224 | { 225 | response_type: 'ephemeral', 226 | text: `GitHub user *${author}* does not exist. Try again?`, 227 | }, 228 | req.body.response_url, 229 | ); 230 | return res.status(200).end(); 231 | } 232 | 233 | console.log(`Scoping needs-manual PRs to those opened by ${author}`); 234 | } 235 | 236 | try { 237 | const branchesToCheck = branch === 'all' ? branches : [branch]; 238 | 239 | let messages = []; 240 | for (const branch of branchesToCheck) { 241 | const prs = await fetchNeedsManualPRs(branch, author); 242 | console.log(`Found ${prs.length} prs on ${branch}`); 243 | 244 | let message; 245 | if (!prs || prs.length === 0) { 246 | message = `*No PR(s) needing manual backport to ${branch}*`; 247 | } else { 248 | message = `PR(s) needing manual backport to *${branch}* (from <@${initiator.id}>):\n`; 249 | message += buildNeedsManualPRsMessage(branch, prs, shouldRemind); 250 | } 251 | messages.push(message); 252 | } 253 | 254 | // If someone is running an audit on the needs-manual PRs that only 255 | // they are responsible for, make the response ephemeral. 256 | const responseType = initiator.name === author ? 'ephemeral' : 'in_channel'; 257 | 258 | await postToSlack( 259 | { 260 | response_type: responseType, 261 | text: messages.join('\n'), 262 | }, 263 | req.body.response_url, 264 | ); 265 | } catch (err) { 266 | console.error(err); 267 | await postToSlack( 268 | { 269 | response_type: 'ephemeral', 270 | text: `Error: ${err.message}`, 271 | }, 272 | req.body.response_url, 273 | ); 274 | } 275 | 276 | return res.status(200).end(); 277 | }); 278 | 279 | app.get('/unreleased', async (req, res) => { 280 | if ( 281 | !timingSafeEqual( 282 | req.headers.authorization, 283 | process.env.VERIFY_SEMVER_AUTH_HEADER, 284 | ) 285 | ) { 286 | return res.status(401).end('Unauthorized'); 287 | } 288 | 289 | const { branch } = req.query; 290 | 291 | try { 292 | const branches = await getSupportedBranches(); 293 | const result = {}; 294 | 295 | if (branch === 'all') { 296 | for (const b of branches) { 297 | const { commits } = await fetchUnreleasedCommits(b); 298 | result[b] = commits; 299 | } 300 | } else { 301 | if (isInvalidBranch(branches, branch)) { 302 | return res 303 | .status(400) 304 | .json({ error: `${branch} is not a valid branch` }); 305 | } 306 | 307 | const { commits } = await fetchUnreleasedCommits(branch); 308 | result[branch] = commits; 309 | } 310 | 311 | return res.json(result); 312 | } catch { 313 | return res 314 | .status(500) 315 | .json({ error: `Failed to fetch unreleased for ${branch}` }); 316 | } 317 | }); 318 | 319 | // Check for commits which have been merged to a release branch but 320 | // not been released in a beta or stable. 321 | app.post('/unreleased', async (req, res) => { 322 | const branches = await getSupportedBranches(); 323 | const branch = req.body.text; 324 | 325 | const initiator = await fetchInitiator(req); 326 | 327 | // Allow for manual batch audit of all supported release branches. 328 | if (branch === 'all') { 329 | console.log( 330 | `${initiator.name} triggered audit for all supported release branches`, 331 | ); 332 | 333 | for (const b of branches) { 334 | console.log(`Auditing branch ${b}`); 335 | try { 336 | const { commits } = await fetchUnreleasedCommits(b); 337 | console.log(`Found ${commits.length} commits on ${b}`); 338 | await postToSlack( 339 | { 340 | response_type: 'in_channel', 341 | text: buildUnreleasedCommitsMessage(b, commits, initiator.id), 342 | }, 343 | req.body.response_url, 344 | ); 345 | } catch (err) { 346 | console.error(err); 347 | await postToSlack( 348 | { 349 | response_type: 'ephemeral', 350 | text: `Error: ${err.message}`, 351 | }, 352 | req.body.response_url, 353 | ); 354 | } 355 | } 356 | 357 | return res.status(200).end(); 358 | } 359 | 360 | console.log( 361 | `${initiator.name} initiated unreleased commit audit for branch: ${branch}`, 362 | ); 363 | 364 | if (isInvalidBranch(branches, branch)) { 365 | console.error(`${branch} is not a valid branch`); 366 | await postToSlack( 367 | { 368 | response_type: 'ephemeral', 369 | text: `Invalid branch *${branch}*. Try again?`, 370 | }, 371 | req.body.response_url, 372 | ); 373 | return res.status(200).end(); 374 | } 375 | 376 | try { 377 | const { commits } = await fetchUnreleasedCommits(branch); 378 | console.log(`Found ${commits.length} commits on ${branch}`); 379 | 380 | await postToSlack( 381 | { 382 | response_type: 'in_channel', 383 | text: buildUnreleasedCommitsMessage(branch, commits, initiator.id), 384 | }, 385 | req.body.response_url, 386 | ); 387 | } catch (err) { 388 | console.error(err); 389 | await postToSlack( 390 | { 391 | response_type: 'ephemeral', 392 | text: `Error: ${err.message}`, 393 | }, 394 | req.body.response_url, 395 | ); 396 | } 397 | 398 | return res.status(200).end(); 399 | }); 400 | 401 | // Combines checks for all PRs that either need manual backport to a given 402 | // release line or which are targeting said line and haven't been merged. 403 | app.post('/audit-pre-release', async (req, res) => { 404 | const branches = await getSupportedBranches(); 405 | const branch = req.body.text; 406 | 407 | const initiator = await fetchInitiator(req); 408 | console.log( 409 | `${initiator.name} initiated pre-release audit for branch: ${branch}`, 410 | ); 411 | 412 | if (isInvalidBranch(branches, branch)) { 413 | console.error(`${branch} is not a valid branch`); 414 | await postToSlack( 415 | { 416 | response_type: 'ephemeral', 417 | text: `Invalid branch *${branch}*. Try again?`, 418 | }, 419 | req.body.response_url, 420 | ); 421 | return res.status(200).end(); 422 | } 423 | 424 | try { 425 | // In a prerelease audit, we don't want to scope by author so we pass null intentionally. 426 | const needsManualPRs = await fetchNeedsManualPRs(branch, null); 427 | console.log( 428 | `Found ${needsManualPRs.length} PR(s) needing manual backport on ${branch}`, 429 | ); 430 | 431 | const unmergedPRs = await fetchUnmergedPRs(branch); 432 | console.log(`Found ${unmergedPRs.length} unmerged PRs targeting ${branch}`); 433 | 434 | let message; 435 | if (needsManualPRs.length + unmergedPRs.length === 0) { 436 | message = `*No PR(s) unmerged or needing manual backport for ${branch}*`; 437 | } else { 438 | message = `Pre-release audit for *${branch}* (from <@${initiator.id}>)\n`; 439 | 440 | if (needsManualPRs.length !== 0) { 441 | message += `PR(s) needing manual backport to *${branch}*:\n`; 442 | message += `${buildNeedsManualPRsMessage(branch, needsManualPRs)}\n`; 443 | } 444 | 445 | if (unmergedPRs.length !== 0) { 446 | message += `Unmerged pull requests targeting *${branch}*:\n`; 447 | message += `${buildUnmergedPRsMessage(branch, unmergedPRs)}\n`; 448 | } 449 | } 450 | 451 | await postToSlack( 452 | { 453 | response_type: 'in_channel', 454 | text: message, 455 | }, 456 | req.body.response_url, 457 | ); 458 | } catch (err) { 459 | console.error(err); 460 | await postToSlack( 461 | { 462 | response_type: 'ephemeral', 463 | text: `Error: ${err.message}`, 464 | }, 465 | req.body.response_url, 466 | ); 467 | } 468 | 469 | return res.status(200).end(); 470 | }); 471 | 472 | const listener = app.listen(process.env.PORT, () => { 473 | console.log(`release-branch-auditor listening on ${listener.address().port}`); 474 | }); 475 | -------------------------------------------------------------------------------- /test/fixtures/needs-manual.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url":"https://api.github.com/repos/electron/electron/issues/24856", 4 | "repository_url":"https://api.github.com/repos/electron/electron", 5 | "labels_url":"https://api.github.com/repos/electron/electron/issues/24856/labels{/name}", 6 | "comments_url":"https://api.github.com/repos/electron/electron/issues/24856/comments", 7 | "events_url":"https://api.github.com/repos/electron/electron/issues/24856/events", 8 | "html_url":"https://github.com/electron/electron/pull/24856", 9 | "id":673832323, 10 | "node_id":"MDExOlB1bGxSZXF1ZXN0NDYzNjMwMzY5", 11 | "number":24856, 12 | "title":"build: ensure symbol files are named lowercase on disk so that boto can find them", 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 | "labels":[ 34 | { 35 | "id":2079356337, 36 | "node_id":"MDU6TGFiZWwyMDc5MzU2MzM3", 37 | "url":"https://api.github.com/repos/electron/electron/labels/merged/10-x-y", 38 | "name":"merged/10-x-y", 39 | "color":"61a3c6", 40 | "default":false, 41 | "description":"" 42 | }, 43 | { 44 | "id":1831709348, 45 | "node_id":"MDU6TGFiZWwxODMxNzA5MzQ4", 46 | "url":"https://api.github.com/repos/electron/electron/labels/merged/9-x-y", 47 | "name":"merged/9-x-y", 48 | "color":"61a3c6", 49 | "default":false, 50 | "description":"" 51 | }, 52 | { 53 | "id":1639703462, 54 | "node_id":"MDU6TGFiZWwxNjM5NzAzNDYy", 55 | "url":"https://api.github.com/repos/electron/electron/labels/needs-manual-bp/8-x-y", 56 | "name":"needs-manual-bp/8-x-y", 57 | "color":"8b5dba", 58 | "default":false, 59 | "description":"" 60 | } 61 | ], 62 | "state":"closed", 63 | "locked":false, 64 | "assignee":null, 65 | "assignees":[ 66 | 67 | ], 68 | "milestone":null, 69 | "comments":8, 70 | "created_at":"2020-08-05T20:38:30Z", 71 | "updated_at":"2020-08-07T20:51:53Z", 72 | "closed_at":"2020-08-05T21:03:51Z", 73 | "author_association":"MEMBER", 74 | "active_lock_reason":null, 75 | "draft":false, 76 | "pull_request":{ 77 | "url":"https://api.github.com/repos/electron/electron/pulls/24856", 78 | "html_url":"https://github.com/electron/electron/pull/24856", 79 | "diff_url":"https://github.com/electron/electron/pull/24856.diff", 80 | "patch_url":"https://github.com/electron/electron/pull/24856.patch" 81 | }, 82 | "body":"This was only an issue on linux as it has a case sensitive fs\r\n\r\nNotes: no-notes", 83 | "performed_via_github_app":null, 84 | "score":1 85 | }, 86 | { 87 | "url":"https://api.github.com/repos/electron/electron/issues/24534", 88 | "repository_url":"https://api.github.com/repos/electron/electron", 89 | "labels_url":"https://api.github.com/repos/electron/electron/issues/24534/labels{/name}", 90 | "comments_url":"https://api.github.com/repos/electron/electron/issues/24534/comments", 91 | "events_url":"https://api.github.com/repos/electron/electron/issues/24534/events", 92 | "html_url":"https://github.com/electron/electron/pull/24534", 93 | "id":656128034, 94 | "node_id":"MDExOlB1bGxSZXF1ZXN0NDQ4NDgwODI1", 95 | "number":24534, 96 | "title":"fix: ensure that errors thrown in the context bridge are created in the correct context", 97 | "user":{ 98 | "login":"MarshallOfSound", 99 | "id":6634592, 100 | "node_id":"MDQ6VXNlcjY2MzQ1OTI=", 101 | "avatar_url":"https://avatars3.githubusercontent.com/u/6634592?v=4", 102 | "gravatar_id":"", 103 | "url":"https://api.github.com/users/MarshallOfSound", 104 | "html_url":"https://github.com/MarshallOfSound", 105 | "followers_url":"https://api.github.com/users/MarshallOfSound/followers", 106 | "following_url":"https://api.github.com/users/MarshallOfSound/following{/other_user}", 107 | "gists_url":"https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 108 | "starred_url":"https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 109 | "subscriptions_url":"https://api.github.com/users/MarshallOfSound/subscriptions", 110 | "organizations_url":"https://api.github.com/users/MarshallOfSound/orgs", 111 | "repos_url":"https://api.github.com/users/MarshallOfSound/repos", 112 | "events_url":"https://api.github.com/users/MarshallOfSound/events{/privacy}", 113 | "received_events_url":"https://api.github.com/users/MarshallOfSound/received_events", 114 | "type":"User", 115 | "site_admin":false 116 | }, 117 | "labels":[ 118 | { 119 | "id":2079356337, 120 | "node_id":"MDU6TGFiZWwyMDc5MzU2MzM3", 121 | "url":"https://api.github.com/repos/electron/electron/labels/merged/10-x-y", 122 | "name":"merged/10-x-y", 123 | "color":"61a3c6", 124 | "default":false, 125 | "description":"" 126 | }, 127 | { 128 | "id":1831709348, 129 | "node_id":"MDU6TGFiZWwxODMxNzA5MzQ4", 130 | "url":"https://api.github.com/repos/electron/electron/labels/merged/9-x-y", 131 | "name":"merged/9-x-y", 132 | "color":"61a3c6", 133 | "default":false, 134 | "description":"" 135 | }, 136 | { 137 | "id":2061831942, 138 | "node_id":"MDU6TGFiZWwyMDYxODMxOTQy", 139 | "url":"https://api.github.com/repos/electron/electron/labels/needs-manual-bp/7-3-x", 140 | "name":"needs-manual-bp/7-3-x", 141 | "color":"8b5dba", 142 | "default":false, 143 | "description":"" 144 | }, 145 | { 146 | "id":1639703462, 147 | "node_id":"MDU6TGFiZWwxNjM5NzAzNDYy", 148 | "url":"https://api.github.com/repos/electron/electron/labels/needs-manual-bp/8-x-y", 149 | "name":"needs-manual-bp/8-x-y", 150 | "color":"8b5dba", 151 | "default":false, 152 | "description":"" 153 | } 154 | ], 155 | "state":"closed", 156 | "locked":false, 157 | "assignee":null, 158 | "assignees":[ 159 | 160 | ], 161 | "milestone":null, 162 | "comments":6, 163 | "created_at":"2020-07-13T20:46:21Z", 164 | "updated_at":"2020-07-29T05:26:52Z", 165 | "closed_at":"2020-07-23T21:32:39Z", 166 | "author_association":"MEMBER", 167 | "active_lock_reason":null, 168 | "draft":false, 169 | "pull_request":{ 170 | "url":"https://api.github.com/repos/electron/electron/pulls/24534", 171 | "html_url":"https://github.com/electron/electron/pull/24534", 172 | "diff_url":"https://github.com/electron/electron/pull/24534.diff", 173 | "patch_url":"https://github.com/electron/electron/pull/24534.patch" 174 | }, 175 | "body":"This PR fixes two cases where errors were being created in context A but thrown in context B.\r\n\r\n* When the V8 serializer threw an error, it always threw it in the source context, now it throws in the appropriate error context\r\n* If we throw an error as a result of a return value failing to be converted, it created the error in the source context when it should create it in the destination context as that is where the error will be thrown / caught.\r\n\r\nNotes: no-notes", 176 | "performed_via_github_app":null, 177 | "score":1 178 | }, 179 | { 180 | "url":"https://api.github.com/repos/electron/electron/issues/24114", 181 | "repository_url":"https://api.github.com/repos/electron/electron", 182 | "labels_url":"https://api.github.com/repos/electron/electron/issues/24114/labels{/name}", 183 | "comments_url":"https://api.github.com/repos/electron/electron/issues/24114/comments", 184 | "events_url":"https://api.github.com/repos/electron/electron/issues/24114/events", 185 | "html_url":"https://github.com/electron/electron/pull/24114", 186 | "id":638039833, 187 | "node_id":"MDExOlB1bGxSZXF1ZXN0NDMzOTE0NjU3", 188 | "number":24114, 189 | "title":"feat: add worldSafe flag for executeJS results", 190 | "user":{ 191 | "login":"MarshallOfSound", 192 | "id":6634592, 193 | "node_id":"MDQ6VXNlcjY2MzQ1OTI=", 194 | "avatar_url":"https://avatars3.githubusercontent.com/u/6634592?v=4", 195 | "gravatar_id":"", 196 | "url":"https://api.github.com/users/MarshallOfSound", 197 | "html_url":"https://github.com/MarshallOfSound", 198 | "followers_url":"https://api.github.com/users/MarshallOfSound/followers", 199 | "following_url":"https://api.github.com/users/MarshallOfSound/following{/other_user}", 200 | "gists_url":"https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 201 | "starred_url":"https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 202 | "subscriptions_url":"https://api.github.com/users/MarshallOfSound/subscriptions", 203 | "organizations_url":"https://api.github.com/users/MarshallOfSound/orgs", 204 | "repos_url":"https://api.github.com/users/MarshallOfSound/repos", 205 | "events_url":"https://api.github.com/users/MarshallOfSound/events{/privacy}", 206 | "received_events_url":"https://api.github.com/users/MarshallOfSound/received_events", 207 | "type":"User", 208 | "site_admin":false 209 | }, 210 | "labels":[ 211 | { 212 | "id":2079356337, 213 | "node_id":"MDU6TGFiZWwyMDc5MzU2MzM3", 214 | "url":"https://api.github.com/repos/electron/electron/labels/merged/10-x-y", 215 | "name":"merged/10-x-y", 216 | "color":"61a3c6", 217 | "default":false, 218 | "description":"" 219 | }, 220 | { 221 | "id":1831709348, 222 | "node_id":"MDU6TGFiZWwxODMxNzA5MzQ4", 223 | "url":"https://api.github.com/repos/electron/electron/labels/merged/9-x-y", 224 | "name":"merged/9-x-y", 225 | "color":"61a3c6", 226 | "default":false, 227 | "description":"" 228 | }, 229 | { 230 | "id":1639703462, 231 | "node_id":"MDU6TGFiZWwxNjM5NzAzNDYy", 232 | "url":"https://api.github.com/repos/electron/electron/labels/needs-manual-bp/8-x-y", 233 | "name":"needs-manual-bp/8-x-y", 234 | "color":"8b5dba", 235 | "default":false, 236 | "description":"" 237 | } 238 | ], 239 | "state":"closed", 240 | "locked":false, 241 | "assignee":null, 242 | "assignees":[ 243 | 244 | ], 245 | "milestone":null, 246 | "comments":4, 247 | "created_at":"2020-06-12T22:54:38Z", 248 | "updated_at":"2020-08-05T17:24:55Z", 249 | "closed_at":"2020-07-23T21:32:21Z", 250 | "author_association":"MEMBER", 251 | "active_lock_reason":null, 252 | "draft":false, 253 | "pull_request":{ 254 | "url":"https://api.github.com/repos/electron/electron/pulls/24114", 255 | "html_url":"https://github.com/electron/electron/pull/24114", 256 | "diff_url":"https://github.com/electron/electron/pull/24114.diff", 257 | "patch_url":"https://github.com/electron/electron/pull/24114.patch" 258 | }, 259 | "body":"Previously the return values of `webFrame.executeJavaScript` crossed the world boundary when context isolation was enabled. This allows apps to makes themselves insecure by accidentally sending objects from the isolated world back to the main world. To help devs avoid this we're adding this new flag, and this flag will be turned on by default in Electron 12 (and removed) ensuring that this kind of issue can't become a thing again.\r\n\r\nThis PR is also requesting new minors of 8 and 9 😄 \r\n\r\nNotes: Added new `worldSafeExecuteJavaScript` webPreference to ensure that the return values from `webFrame.executeJavaScript` are world safe when context isolation is enabled", 260 | "performed_via_github_app":null, 261 | "score":1 262 | }, 263 | { 264 | "url":"https://api.github.com/repos/electron/electron/issues/24014", 265 | "repository_url":"https://api.github.com/repos/electron/electron", 266 | "labels_url":"https://api.github.com/repos/electron/electron/issues/24014/labels{/name}", 267 | "comments_url":"https://api.github.com/repos/electron/electron/issues/24014/comments", 268 | "events_url":"https://api.github.com/repos/electron/electron/issues/24014/events", 269 | "html_url":"https://github.com/electron/electron/pull/24014", 270 | "id":634723646, 271 | "node_id":"MDExOlB1bGxSZXF1ZXN0NDMxMjI2OTQ2", 272 | "number":24014, 273 | "title":"fix: Close protocol response streams when aborted", 274 | "user":{ 275 | "login":"pfrazee", 276 | "id":1270099, 277 | "node_id":"MDQ6VXNlcjEyNzAwOTk=", 278 | "avatar_url":"https://avatars2.githubusercontent.com/u/1270099?v=4", 279 | "gravatar_id":"", 280 | "url":"https://api.github.com/users/pfrazee", 281 | "html_url":"https://github.com/pfrazee", 282 | "followers_url":"https://api.github.com/users/pfrazee/followers", 283 | "following_url":"https://api.github.com/users/pfrazee/following{/other_user}", 284 | "gists_url":"https://api.github.com/users/pfrazee/gists{/gist_id}", 285 | "starred_url":"https://api.github.com/users/pfrazee/starred{/owner}{/repo}", 286 | "subscriptions_url":"https://api.github.com/users/pfrazee/subscriptions", 287 | "organizations_url":"https://api.github.com/users/pfrazee/orgs", 288 | "repos_url":"https://api.github.com/users/pfrazee/repos", 289 | "events_url":"https://api.github.com/users/pfrazee/events{/privacy}", 290 | "received_events_url":"https://api.github.com/users/pfrazee/received_events", 291 | "type":"User", 292 | "site_admin":false 293 | }, 294 | "labels":[ 295 | { 296 | "id":2079356337, 297 | "node_id":"MDU6TGFiZWwyMDc5MzU2MzM3", 298 | "url":"https://api.github.com/repos/electron/electron/labels/merged/10-x-y", 299 | "name":"merged/10-x-y", 300 | "color":"61a3c6", 301 | "default":false, 302 | "description":"" 303 | }, 304 | { 305 | "id":1831709348, 306 | "node_id":"MDU6TGFiZWwxODMxNzA5MzQ4", 307 | "url":"https://api.github.com/repos/electron/electron/labels/merged/9-x-y", 308 | "name":"merged/9-x-y", 309 | "color":"61a3c6", 310 | "default":false, 311 | "description":"" 312 | }, 313 | { 314 | "id":1639703462, 315 | "node_id":"MDU6TGFiZWwxNjM5NzAzNDYy", 316 | "url":"https://api.github.com/repos/electron/electron/labels/needs-manual-bp/8-x-y", 317 | "name":"needs-manual-bp/8-x-y", 318 | "color":"8b5dba", 319 | "default":false, 320 | "description":"" 321 | } 322 | ], 323 | "state":"closed", 324 | "locked":false, 325 | "assignee":null, 326 | "assignees":[ 327 | 328 | ], 329 | "milestone":null, 330 | "comments":6, 331 | "created_at":"2020-06-08T15:34:05Z", 332 | "updated_at":"2020-07-21T04:47:12Z", 333 | "closed_at":"2020-07-21T00:51:39Z", 334 | "author_association":"CONTRIBUTOR", 335 | "active_lock_reason":null, 336 | "draft":false, 337 | "pull_request":{ 338 | "url":"https://api.github.com/repos/electron/electron/pulls/24014", 339 | "html_url":"https://github.com/electron/electron/pull/24014", 340 | "diff_url":"https://github.com/electron/electron/pull/24014.diff", 341 | "patch_url":"https://github.com/electron/electron/pull/24014.patch" 342 | }, 343 | "body":"#### Description of Change\r\n\r\nWe noticed that streams were not being closed if the response is aborted prematurely (eg due to a page navigation) which has the potential to create memory leaks. This PR ensures that the stream is destroyed in that scenario.\r\n\r\n#### Checklist\r\n\r\n- [x] PR description included and stakeholders cc'd\r\n- [x] `npm test` passes\r\n- [x] tests are [changed or added](https://github.com/electron/electron/blob/master/docs/development/testing.md)\r\n- [x] PR title follows semantic [commit guidelines](https://github.com/electron/electron/blob/master/docs/development/pull-requests.md#commit-message-guidelines)\r\n- [x] [PR release notes](https://github.com/electron/clerk/blob/master/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/master/README.md#examples).\r\n- [x] This is **NOT A BREAKING CHANGE**. Breaking changes may not be merged to master until 11-x-y is branched.\r\n\r\n#### Release Notes\r\n\r\nNotes: Protocol response streams are now destroyed if the request is aborted.", 344 | "performed_via_github_app":null, 345 | "score":1 346 | }, 347 | { 348 | "url":"https://api.github.com/repos/electron/electron/issues/20625", 349 | "repository_url":"https://api.github.com/repos/electron/electron", 350 | "labels_url":"https://api.github.com/repos/electron/electron/issues/20625/labels{/name}", 351 | "comments_url":"https://api.github.com/repos/electron/electron/issues/20625/comments", 352 | "events_url":"https://api.github.com/repos/electron/electron/issues/20625/events", 353 | "html_url":"https://github.com/electron/electron/pull/20625", 354 | "id":508802195, 355 | "node_id":"MDExOlB1bGxSZXF1ZXN0MzI5NTI1MTI5", 356 | "number":20625, 357 | "title":"fix: loading dedicated/shared worker scripts over custom protocol", 358 | "user":{ 359 | "login":"deepak1556", 360 | "id":964386, 361 | "node_id":"MDQ6VXNlcjk2NDM4Ng==", 362 | "avatar_url":"https://avatars1.githubusercontent.com/u/964386?v=4", 363 | "gravatar_id":"", 364 | "url":"https://api.github.com/users/deepak1556", 365 | "html_url":"https://github.com/deepak1556", 366 | "followers_url":"https://api.github.com/users/deepak1556/followers", 367 | "following_url":"https://api.github.com/users/deepak1556/following{/other_user}", 368 | "gists_url":"https://api.github.com/users/deepak1556/gists{/gist_id}", 369 | "starred_url":"https://api.github.com/users/deepak1556/starred{/owner}{/repo}", 370 | "subscriptions_url":"https://api.github.com/users/deepak1556/subscriptions", 371 | "organizations_url":"https://api.github.com/users/deepak1556/orgs", 372 | "repos_url":"https://api.github.com/users/deepak1556/repos", 373 | "events_url":"https://api.github.com/users/deepak1556/events{/privacy}", 374 | "received_events_url":"https://api.github.com/users/deepak1556/received_events", 375 | "type":"User", 376 | "site_admin":false 377 | }, 378 | "labels":[ 379 | { 380 | "id":1831441136, 381 | "node_id":"MDU6TGFiZWwxODMxNDQxMTM2", 382 | "url":"https://api.github.com/repos/electron/electron/labels/in-flight/9-x-y", 383 | "name":"in-flight/9-x-y", 384 | "color":"db69a6", 385 | "default":false, 386 | "description":"" 387 | }, 388 | { 389 | "id":2079356337, 390 | "node_id":"MDU6TGFiZWwyMDc5MzU2MzM3", 391 | "url":"https://api.github.com/repos/electron/electron/labels/merged/10-x-y", 392 | "name":"merged/10-x-y", 393 | "color":"61a3c6", 394 | "default":false, 395 | "description":"" 396 | }, 397 | { 398 | "id":1639703462, 399 | "node_id":"MDU6TGFiZWwxNjM5NzAzNDYy", 400 | "url":"https://api.github.com/repos/electron/electron/labels/needs-manual-bp/8-x-y", 401 | "name":"needs-manual-bp/8-x-y", 402 | "color":"8b5dba", 403 | "default":false, 404 | "description":"" 405 | } 406 | ], 407 | "state":"closed", 408 | "locked":false, 409 | "assignee":null, 410 | "assignees":[ 411 | 412 | ], 413 | "milestone":null, 414 | "comments":8, 415 | "created_at":"2019-10-18T01:27:29Z", 416 | "updated_at":"2020-08-06T16:59:28Z", 417 | "closed_at":"2020-07-28T01:48:38Z", 418 | "author_association":"MEMBER", 419 | "active_lock_reason":null, 420 | "draft":false, 421 | "pull_request":{ 422 | "url":"https://api.github.com/repos/electron/electron/pulls/20625", 423 | "html_url":"https://github.com/electron/electron/pull/20625", 424 | "diff_url":"https://github.com/electron/electron/pull/20625.diff", 425 | "patch_url":"https://github.com/electron/electron/pull/20625.patch" 426 | }, 427 | "body":"#### Description of Change\r\n\r\nCloses https://github.com/electron/electron/issues/20557.\r\nCloses https://github.com/electron/electron/issues/24715.\r\n\r\nThis is a two part fix,\r\n\r\n**Part I: Fix crash when loading over data: protocol mentioned in the above issue**\r\n\r\nThis issue will appear only with nodeintegration enabled in the worker. Adds patch `worker_feat_add_hook_to_notify_script_ready.patch` that explains the root cause of this issue, I am gonna try upstreaming this, will see how it goes. The access to global properties from Node.js happens in `node::MaybeInitializeContext` which modifies the global object for primordials.\r\n\r\n**Part II: Cannot load worker scripts over custom protocol**\r\n\r\nAfter https://chromium-review.googlesource.com/c/chromium/src/+/1798250 we need to explicitly register custom URLLoaderFactories for dedicated/shared workers.\r\n\r\n#### Checklist\r\n\r\n- [x] PR description included and stakeholders cc'd\r\n- [ ] `npm test` passes\r\n- [ ] tests are [changed or added](https://github.com/electron/electron/blob/master/docs/development/testing.md)\r\n- [ ] relevant documentation is changed or added\r\n- [x] PR title follows semantic [commit guidelines](https://github.com/electron/electron/blob/master/docs/development/pull-requests.md#commit-message-guidelines)\r\n- [x] [PR release notes](https://github.com/electron/clerk/blob/master/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/master/README.md#examples).\r\n\r\n#### Release Notes\r\n\r\nNotes:\r\n* fix loading shared worker scripts over custom protocol\r\n* fix crash when loading worker scripts with nodeIntegration enabled\r\n", 428 | "performed_via_github_app":null, 429 | "score":1 430 | } 431 | ] 432 | -------------------------------------------------------------------------------- /test/fixtures/unmerged.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "https://api.github.com/repos/electron/electron/pulls/24838", 4 | "id": 463056759, 5 | "node_id": "MDExOlB1bGxSZXF1ZXN0NDYzMDU2NzU5", 6 | "html_url": "https://github.com/electron/electron/pull/24838", 7 | "diff_url": "https://github.com/electron/electron/pull/24838.diff", 8 | "patch_url": "https://github.com/electron/electron/pull/24838.patch", 9 | "issue_url": "https://api.github.com/repos/electron/electron/issues/24838", 10 | "number": 24838, 11 | "state": "open", 12 | "locked": false, 13 | "title": "fix: provide AXTextChangeValueStartMarker for macOS a11y value change notifications", 14 | "user": { 15 | "login": "MarshallOfSound", 16 | "id": 6634592, 17 | "node_id": "MDQ6VXNlcjY2MzQ1OTI=", 18 | "avatar_url": "https://avatars3.githubusercontent.com/u/6634592?v=4", 19 | "gravatar_id": "", 20 | "url": "https://api.github.com/users/MarshallOfSound", 21 | "html_url": "https://github.com/MarshallOfSound", 22 | "followers_url": "https://api.github.com/users/MarshallOfSound/followers", 23 | "following_url": "https://api.github.com/users/MarshallOfSound/following{/other_user}", 24 | "gists_url": "https://api.github.com/users/MarshallOfSound/gists{/gist_id}", 25 | "starred_url": "https://api.github.com/users/MarshallOfSound/starred{/owner}{/repo}", 26 | "subscriptions_url": "https://api.github.com/users/MarshallOfSound/subscriptions", 27 | "organizations_url": "https://api.github.com/users/MarshallOfSound/orgs", 28 | "repos_url": "https://api.github.com/users/MarshallOfSound/repos", 29 | "events_url": "https://api.github.com/users/MarshallOfSound/events{/privacy}", 30 | "received_events_url": "https://api.github.com/users/MarshallOfSound/received_events", 31 | "type": "User", 32 | "site_admin": false 33 | }, 34 | "body": "Backport of https://github.com/electron/electron/pull/24801\r\n\r\nSee that PR for details.\r\n\r\nThis PR includes the final upstreamed CL so it looks slightly different.\r\n\r\nNotes: Fixed issued where voice over would not read words as you typed them", 35 | "created_at": "2020-08-04T23:13:47Z", 36 | "updated_at": "2020-08-10T14:16:09Z", 37 | "closed_at": null, 38 | "merged_at": null, 39 | "merge_commit_sha": "37c215c81bdba7ceeaa4e56708aaacdd8444a6ec", 40 | "assignee": null, 41 | "assignees": [], 42 | "requested_reviewers": [], 43 | "requested_teams": [], 44 | "labels": [ 45 | { 46 | "id": 1858180153, 47 | "node_id": "MDU6TGFiZWwxODU4MTgwMTUz", 48 | "url": "https://api.github.com/repos/electron/electron/labels/10-x-y", 49 | "name": "10-x-y", 50 | "color": "8d9ee8", 51 | "default": false, 52 | "description": "" 53 | }, 54 | { 55 | "id": 865096932, 56 | "node_id": "MDU6TGFiZWw4NjUwOTY5MzI=", 57 | "url": "https://api.github.com/repos/electron/electron/labels/backport", 58 | "name": "backport", 59 | "color": "ddddde", 60 | "default": false, 61 | "description": "This is a backport PR" 62 | } 63 | ], 64 | "milestone": null, 65 | "draft": false, 66 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/24838/commits", 67 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/24838/comments", 68 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 69 | "comments_url": "https://api.github.com/repos/electron/electron/issues/24838/comments", 70 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/324b5f80bf8b1ce35520a41d79d875c44d1be9f8", 71 | "head": { 72 | "label": "electron:a11y-text-change-marker-10", 73 | "ref": "a11y-text-change-marker-10", 74 | "sha": "324b5f80bf8b1ce35520a41d79d875c44d1be9f8", 75 | "user": { 76 | "login": "electron", 77 | "id": 13409222, 78 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 79 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 80 | "gravatar_id": "", 81 | "url": "https://api.github.com/users/electron", 82 | "html_url": "https://github.com/electron", 83 | "followers_url": "https://api.github.com/users/electron/followers", 84 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 85 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 86 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 87 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 88 | "organizations_url": "https://api.github.com/users/electron/orgs", 89 | "repos_url": "https://api.github.com/users/electron/repos", 90 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 91 | "received_events_url": "https://api.github.com/users/electron/received_events", 92 | "type": "Organization", 93 | "site_admin": false 94 | }, 95 | "repo": { 96 | "id": 9384267, 97 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 98 | "name": "electron", 99 | "full_name": "electron/electron", 100 | "private": false, 101 | "owner": { 102 | "login": "electron", 103 | "id": 13409222, 104 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 105 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 106 | "gravatar_id": "", 107 | "url": "https://api.github.com/users/electron", 108 | "html_url": "https://github.com/electron", 109 | "followers_url": "https://api.github.com/users/electron/followers", 110 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 111 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 112 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 113 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 114 | "organizations_url": "https://api.github.com/users/electron/orgs", 115 | "repos_url": "https://api.github.com/users/electron/repos", 116 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 117 | "received_events_url": "https://api.github.com/users/electron/received_events", 118 | "type": "Organization", 119 | "site_admin": false 120 | }, 121 | "html_url": "https://github.com/electron/electron", 122 | "description": ":electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS", 123 | "fork": false, 124 | "url": "https://api.github.com/repos/electron/electron", 125 | "forks_url": "https://api.github.com/repos/electron/electron/forks", 126 | "keys_url": "https://api.github.com/repos/electron/electron/keys{/key_id}", 127 | "collaborators_url": "https://api.github.com/repos/electron/electron/collaborators{/collaborator}", 128 | "teams_url": "https://api.github.com/repos/electron/electron/teams", 129 | "hooks_url": "https://api.github.com/repos/electron/electron/hooks", 130 | "issue_events_url": "https://api.github.com/repos/electron/electron/issues/events{/number}", 131 | "events_url": "https://api.github.com/repos/electron/electron/events", 132 | "assignees_url": "https://api.github.com/repos/electron/electron/assignees{/user}", 133 | "branches_url": "https://api.github.com/repos/electron/electron/branches{/branch}", 134 | "tags_url": "https://api.github.com/repos/electron/electron/tags", 135 | "blobs_url": "https://api.github.com/repos/electron/electron/git/blobs{/sha}", 136 | "git_tags_url": "https://api.github.com/repos/electron/electron/git/tags{/sha}", 137 | "git_refs_url": "https://api.github.com/repos/electron/electron/git/refs{/sha}", 138 | "trees_url": "https://api.github.com/repos/electron/electron/git/trees{/sha}", 139 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/{sha}", 140 | "languages_url": "https://api.github.com/repos/electron/electron/languages", 141 | "stargazers_url": "https://api.github.com/repos/electron/electron/stargazers", 142 | "contributors_url": "https://api.github.com/repos/electron/electron/contributors", 143 | "subscribers_url": "https://api.github.com/repos/electron/electron/subscribers", 144 | "subscription_url": "https://api.github.com/repos/electron/electron/subscription", 145 | "commits_url": "https://api.github.com/repos/electron/electron/commits{/sha}", 146 | "git_commits_url": "https://api.github.com/repos/electron/electron/git/commits{/sha}", 147 | "comments_url": "https://api.github.com/repos/electron/electron/comments{/number}", 148 | "issue_comment_url": "https://api.github.com/repos/electron/electron/issues/comments{/number}", 149 | "contents_url": "https://api.github.com/repos/electron/electron/contents/{+path}", 150 | "compare_url": "https://api.github.com/repos/electron/electron/compare/{base}...{head}", 151 | "merges_url": "https://api.github.com/repos/electron/electron/merges", 152 | "archive_url": "https://api.github.com/repos/electron/electron/{archive_format}{/ref}", 153 | "downloads_url": "https://api.github.com/repos/electron/electron/downloads", 154 | "issues_url": "https://api.github.com/repos/electron/electron/issues{/number}", 155 | "pulls_url": "https://api.github.com/repos/electron/electron/pulls{/number}", 156 | "milestones_url": "https://api.github.com/repos/electron/electron/milestones{/number}", 157 | "notifications_url": "https://api.github.com/repos/electron/electron/notifications{?since,all,participating}", 158 | "labels_url": "https://api.github.com/repos/electron/electron/labels{/name}", 159 | "releases_url": "https://api.github.com/repos/electron/electron/releases{/id}", 160 | "deployments_url": "https://api.github.com/repos/electron/electron/deployments", 161 | "created_at": "2013-04-12T01:47:36Z", 162 | "updated_at": "2020-08-10T17:21:54Z", 163 | "pushed_at": "2020-08-10T17:22:10Z", 164 | "git_url": "git://github.com/electron/electron.git", 165 | "ssh_url": "git@github.com:electron/electron.git", 166 | "clone_url": "https://github.com/electron/electron.git", 167 | "svn_url": "https://github.com/electron/electron", 168 | "homepage": "https://electronjs.org", 169 | "size": 78475, 170 | "stargazers_count": 85015, 171 | "watchers_count": 85015, 172 | "language": "C++", 173 | "has_issues": true, 174 | "has_projects": true, 175 | "has_downloads": true, 176 | "has_wiki": false, 177 | "has_pages": false, 178 | "forks_count": 11363, 179 | "mirror_url": null, 180 | "archived": false, 181 | "disabled": false, 182 | "open_issues_count": 1369, 183 | "license": { 184 | "key": "mit", 185 | "name": "MIT License", 186 | "spdx_id": "MIT", 187 | "url": "https://api.github.com/licenses/mit", 188 | "node_id": "MDc6TGljZW5zZTEz" 189 | }, 190 | "forks": 11363, 191 | "open_issues": 1369, 192 | "watchers": 85015, 193 | "default_branch": "master" 194 | } 195 | }, 196 | "base": { 197 | "label": "electron:10-x-y", 198 | "ref": "10-x-y", 199 | "sha": "04f652a53bf99404ec0fc122de6cabc5f91d240b", 200 | "user": { 201 | "login": "electron", 202 | "id": 13409222, 203 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 204 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 205 | "gravatar_id": "", 206 | "url": "https://api.github.com/users/electron", 207 | "html_url": "https://github.com/electron", 208 | "followers_url": "https://api.github.com/users/electron/followers", 209 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 210 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 211 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 212 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 213 | "organizations_url": "https://api.github.com/users/electron/orgs", 214 | "repos_url": "https://api.github.com/users/electron/repos", 215 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 216 | "received_events_url": "https://api.github.com/users/electron/received_events", 217 | "type": "Organization", 218 | "site_admin": false 219 | }, 220 | "repo": { 221 | "id": 9384267, 222 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 223 | "name": "electron", 224 | "full_name": "electron/electron", 225 | "private": false, 226 | "owner": { 227 | "login": "electron", 228 | "id": 13409222, 229 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 230 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 231 | "gravatar_id": "", 232 | "url": "https://api.github.com/users/electron", 233 | "html_url": "https://github.com/electron", 234 | "followers_url": "https://api.github.com/users/electron/followers", 235 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 236 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 237 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 238 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 239 | "organizations_url": "https://api.github.com/users/electron/orgs", 240 | "repos_url": "https://api.github.com/users/electron/repos", 241 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 242 | "received_events_url": "https://api.github.com/users/electron/received_events", 243 | "type": "Organization", 244 | "site_admin": false 245 | }, 246 | "html_url": "https://github.com/electron/electron", 247 | "description": ":electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS", 248 | "fork": false, 249 | "url": "https://api.github.com/repos/electron/electron", 250 | "forks_url": "https://api.github.com/repos/electron/electron/forks", 251 | "keys_url": "https://api.github.com/repos/electron/electron/keys{/key_id}", 252 | "collaborators_url": "https://api.github.com/repos/electron/electron/collaborators{/collaborator}", 253 | "teams_url": "https://api.github.com/repos/electron/electron/teams", 254 | "hooks_url": "https://api.github.com/repos/electron/electron/hooks", 255 | "issue_events_url": "https://api.github.com/repos/electron/electron/issues/events{/number}", 256 | "events_url": "https://api.github.com/repos/electron/electron/events", 257 | "assignees_url": "https://api.github.com/repos/electron/electron/assignees{/user}", 258 | "branches_url": "https://api.github.com/repos/electron/electron/branches{/branch}", 259 | "tags_url": "https://api.github.com/repos/electron/electron/tags", 260 | "blobs_url": "https://api.github.com/repos/electron/electron/git/blobs{/sha}", 261 | "git_tags_url": "https://api.github.com/repos/electron/electron/git/tags{/sha}", 262 | "git_refs_url": "https://api.github.com/repos/electron/electron/git/refs{/sha}", 263 | "trees_url": "https://api.github.com/repos/electron/electron/git/trees{/sha}", 264 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/{sha}", 265 | "languages_url": "https://api.github.com/repos/electron/electron/languages", 266 | "stargazers_url": "https://api.github.com/repos/electron/electron/stargazers", 267 | "contributors_url": "https://api.github.com/repos/electron/electron/contributors", 268 | "subscribers_url": "https://api.github.com/repos/electron/electron/subscribers", 269 | "subscription_url": "https://api.github.com/repos/electron/electron/subscription", 270 | "commits_url": "https://api.github.com/repos/electron/electron/commits{/sha}", 271 | "git_commits_url": "https://api.github.com/repos/electron/electron/git/commits{/sha}", 272 | "comments_url": "https://api.github.com/repos/electron/electron/comments{/number}", 273 | "issue_comment_url": "https://api.github.com/repos/electron/electron/issues/comments{/number}", 274 | "contents_url": "https://api.github.com/repos/electron/electron/contents/{+path}", 275 | "compare_url": "https://api.github.com/repos/electron/electron/compare/{base}...{head}", 276 | "merges_url": "https://api.github.com/repos/electron/electron/merges", 277 | "archive_url": "https://api.github.com/repos/electron/electron/{archive_format}{/ref}", 278 | "downloads_url": "https://api.github.com/repos/electron/electron/downloads", 279 | "issues_url": "https://api.github.com/repos/electron/electron/issues{/number}", 280 | "pulls_url": "https://api.github.com/repos/electron/electron/pulls{/number}", 281 | "milestones_url": "https://api.github.com/repos/electron/electron/milestones{/number}", 282 | "notifications_url": "https://api.github.com/repos/electron/electron/notifications{?since,all,participating}", 283 | "labels_url": "https://api.github.com/repos/electron/electron/labels{/name}", 284 | "releases_url": "https://api.github.com/repos/electron/electron/releases{/id}", 285 | "deployments_url": "https://api.github.com/repos/electron/electron/deployments", 286 | "created_at": "2013-04-12T01:47:36Z", 287 | "updated_at": "2020-08-10T17:21:54Z", 288 | "pushed_at": "2020-08-10T17:22:10Z", 289 | "git_url": "git://github.com/electron/electron.git", 290 | "ssh_url": "git@github.com:electron/electron.git", 291 | "clone_url": "https://github.com/electron/electron.git", 292 | "svn_url": "https://github.com/electron/electron", 293 | "homepage": "https://electronjs.org", 294 | "size": 78475, 295 | "stargazers_count": 85015, 296 | "watchers_count": 85015, 297 | "language": "C++", 298 | "has_issues": true, 299 | "has_projects": true, 300 | "has_downloads": true, 301 | "has_wiki": false, 302 | "has_pages": false, 303 | "forks_count": 11363, 304 | "mirror_url": null, 305 | "archived": false, 306 | "disabled": false, 307 | "open_issues_count": 1369, 308 | "license": { 309 | "key": "mit", 310 | "name": "MIT License", 311 | "spdx_id": "MIT", 312 | "url": "https://api.github.com/licenses/mit", 313 | "node_id": "MDc6TGljZW5zZTEz" 314 | }, 315 | "forks": 11363, 316 | "open_issues": 1369, 317 | "watchers": 85015, 318 | "default_branch": "master" 319 | } 320 | }, 321 | "_links": { 322 | "self": { 323 | "href": "https://api.github.com/repos/electron/electron/pulls/24838" 324 | }, 325 | "html": { 326 | "href": "https://github.com/electron/electron/pull/24838" 327 | }, 328 | "issue": { 329 | "href": "https://api.github.com/repos/electron/electron/issues/24838" 330 | }, 331 | "comments": { 332 | "href": "https://api.github.com/repos/electron/electron/issues/24838/comments" 333 | }, 334 | "review_comments": { 335 | "href": "https://api.github.com/repos/electron/electron/pulls/24838/comments" 336 | }, 337 | "review_comment": { 338 | "href": "https://api.github.com/repos/electron/electron/pulls/comments{/number}" 339 | }, 340 | "commits": { 341 | "href": "https://api.github.com/repos/electron/electron/pulls/24838/commits" 342 | }, 343 | "statuses": { 344 | "href": "https://api.github.com/repos/electron/electron/statuses/324b5f80bf8b1ce35520a41d79d875c44d1be9f8" 345 | } 346 | }, 347 | "author_association": "MEMBER", 348 | "active_lock_reason": null 349 | }, 350 | { 351 | "url": "https://api.github.com/repos/electron/electron/pulls/24706", 352 | "id": 455692395, 353 | "node_id": "MDExOlB1bGxSZXF1ZXN0NDU1NjkyMzk1", 354 | "html_url": "https://github.com/electron/electron/pull/24706", 355 | "diff_url": "https://github.com/electron/electron/pull/24706.diff", 356 | "patch_url": "https://github.com/electron/electron/pull/24706.patch", 357 | "issue_url": "https://api.github.com/repos/electron/electron/issues/24706", 358 | "number": 24706, 359 | "state": "open", 360 | "locked": false, 361 | "title": "chore: bump chromium to 85.0.4183.65 (10-x-y)", 362 | "user": { 363 | "login": "electron-bot", 364 | "id": 18403005, 365 | "node_id": "MDQ6VXNlcjE4NDAzMDA1", 366 | "avatar_url": "https://avatars3.githubusercontent.com/u/18403005?v=4", 367 | "gravatar_id": "", 368 | "url": "https://api.github.com/users/electron-bot", 369 | "html_url": "https://github.com/electron-bot", 370 | "followers_url": "https://api.github.com/users/electron-bot/followers", 371 | "following_url": "https://api.github.com/users/electron-bot/following{/other_user}", 372 | "gists_url": "https://api.github.com/users/electron-bot/gists{/gist_id}", 373 | "starred_url": "https://api.github.com/users/electron-bot/starred{/owner}{/repo}", 374 | "subscriptions_url": "https://api.github.com/users/electron-bot/subscriptions", 375 | "organizations_url": "https://api.github.com/users/electron-bot/orgs", 376 | "repos_url": "https://api.github.com/users/electron-bot/repos", 377 | "events_url": "https://api.github.com/users/electron-bot/events{/privacy}", 378 | "received_events_url": "https://api.github.com/users/electron-bot/received_events", 379 | "type": "User", 380 | "site_admin": false 381 | }, 382 | "body": "Updating Chromium to 85.0.4183.65.\n\nSee [all changes in 85.0.4183.39..85.0.4183.65](https://chromium.googlesource.com/chromium/src/+log/85.0.4183.39..85.0.4183.65?n=10000&pretty=fuller)\n\n\n\nNotes: Updated Chromium to 85.0.4183.65.", 383 | "created_at": "2020-07-23T13:01:20Z", 384 | "updated_at": "2020-08-10T13:23:28Z", 385 | "closed_at": null, 386 | "merged_at": null, 387 | "merge_commit_sha": "83252ec704d90423e2cdcf498c563f47af4e84b8", 388 | "assignee": null, 389 | "assignees": [], 390 | "requested_reviewers": [], 391 | "requested_teams": [ 392 | { 393 | "name": "wg-upgrades", 394 | "id": 3118509, 395 | "node_id": "MDQ6VGVhbTMxMTg1MDk=", 396 | "slug": "wg-upgrades", 397 | "description": "Upgrades Working Group", 398 | "privacy": "closed", 399 | "url": "https://api.github.com/organizations/13409222/team/3118509", 400 | "html_url": "https://github.com/orgs/electron/teams/wg-upgrades", 401 | "members_url": "https://api.github.com/organizations/13409222/team/3118509/members{/member}", 402 | "repositories_url": "https://api.github.com/organizations/13409222/team/3118509/repos", 403 | "permission": "pull", 404 | "parent": { 405 | "name": "gov", 406 | "id": 3132543, 407 | "node_id": "MDQ6VGVhbTMxMzI1NDM=", 408 | "slug": "gov", 409 | "description": "Electron Governance", 410 | "privacy": "closed", 411 | "url": "https://api.github.com/organizations/13409222/team/3132543", 412 | "html_url": "https://github.com/orgs/electron/teams/gov", 413 | "members_url": "https://api.github.com/organizations/13409222/team/3132543/members{/member}", 414 | "repositories_url": "https://api.github.com/organizations/13409222/team/3132543/repos", 415 | "permission": "pull" 416 | } 417 | } 418 | ], 419 | "labels": [], 420 | "milestone": null, 421 | "draft": false, 422 | "commits_url": "https://api.github.com/repos/electron/electron/pulls/24706/commits", 423 | "review_comments_url": "https://api.github.com/repos/electron/electron/pulls/24706/comments", 424 | "review_comment_url": "https://api.github.com/repos/electron/electron/pulls/comments{/number}", 425 | "comments_url": "https://api.github.com/repos/electron/electron/issues/24706/comments", 426 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/34ad915c6ab24f6c7d475007fbc7ada7fd77073a", 427 | "head": { 428 | "label": "electron:roller/chromium/10-x-y", 429 | "ref": "roller/chromium/10-x-y", 430 | "sha": "34ad915c6ab24f6c7d475007fbc7ada7fd77073a", 431 | "user": { 432 | "login": "electron", 433 | "id": 13409222, 434 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 435 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 436 | "gravatar_id": "", 437 | "url": "https://api.github.com/users/electron", 438 | "html_url": "https://github.com/electron", 439 | "followers_url": "https://api.github.com/users/electron/followers", 440 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 441 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 442 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 443 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 444 | "organizations_url": "https://api.github.com/users/electron/orgs", 445 | "repos_url": "https://api.github.com/users/electron/repos", 446 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 447 | "received_events_url": "https://api.github.com/users/electron/received_events", 448 | "type": "Organization", 449 | "site_admin": false 450 | }, 451 | "repo": { 452 | "id": 9384267, 453 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 454 | "name": "electron", 455 | "full_name": "electron/electron", 456 | "private": false, 457 | "owner": { 458 | "login": "electron", 459 | "id": 13409222, 460 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 461 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 462 | "gravatar_id": "", 463 | "url": "https://api.github.com/users/electron", 464 | "html_url": "https://github.com/electron", 465 | "followers_url": "https://api.github.com/users/electron/followers", 466 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 467 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 468 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 469 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 470 | "organizations_url": "https://api.github.com/users/electron/orgs", 471 | "repos_url": "https://api.github.com/users/electron/repos", 472 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 473 | "received_events_url": "https://api.github.com/users/electron/received_events", 474 | "type": "Organization", 475 | "site_admin": false 476 | }, 477 | "html_url": "https://github.com/electron/electron", 478 | "description": ":electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS", 479 | "fork": false, 480 | "url": "https://api.github.com/repos/electron/electron", 481 | "forks_url": "https://api.github.com/repos/electron/electron/forks", 482 | "keys_url": "https://api.github.com/repos/electron/electron/keys{/key_id}", 483 | "collaborators_url": "https://api.github.com/repos/electron/electron/collaborators{/collaborator}", 484 | "teams_url": "https://api.github.com/repos/electron/electron/teams", 485 | "hooks_url": "https://api.github.com/repos/electron/electron/hooks", 486 | "issue_events_url": "https://api.github.com/repos/electron/electron/issues/events{/number}", 487 | "events_url": "https://api.github.com/repos/electron/electron/events", 488 | "assignees_url": "https://api.github.com/repos/electron/electron/assignees{/user}", 489 | "branches_url": "https://api.github.com/repos/electron/electron/branches{/branch}", 490 | "tags_url": "https://api.github.com/repos/electron/electron/tags", 491 | "blobs_url": "https://api.github.com/repos/electron/electron/git/blobs{/sha}", 492 | "git_tags_url": "https://api.github.com/repos/electron/electron/git/tags{/sha}", 493 | "git_refs_url": "https://api.github.com/repos/electron/electron/git/refs{/sha}", 494 | "trees_url": "https://api.github.com/repos/electron/electron/git/trees{/sha}", 495 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/{sha}", 496 | "languages_url": "https://api.github.com/repos/electron/electron/languages", 497 | "stargazers_url": "https://api.github.com/repos/electron/electron/stargazers", 498 | "contributors_url": "https://api.github.com/repos/electron/electron/contributors", 499 | "subscribers_url": "https://api.github.com/repos/electron/electron/subscribers", 500 | "subscription_url": "https://api.github.com/repos/electron/electron/subscription", 501 | "commits_url": "https://api.github.com/repos/electron/electron/commits{/sha}", 502 | "git_commits_url": "https://api.github.com/repos/electron/electron/git/commits{/sha}", 503 | "comments_url": "https://api.github.com/repos/electron/electron/comments{/number}", 504 | "issue_comment_url": "https://api.github.com/repos/electron/electron/issues/comments{/number}", 505 | "contents_url": "https://api.github.com/repos/electron/electron/contents/{+path}", 506 | "compare_url": "https://api.github.com/repos/electron/electron/compare/{base}...{head}", 507 | "merges_url": "https://api.github.com/repos/electron/electron/merges", 508 | "archive_url": "https://api.github.com/repos/electron/electron/{archive_format}{/ref}", 509 | "downloads_url": "https://api.github.com/repos/electron/electron/downloads", 510 | "issues_url": "https://api.github.com/repos/electron/electron/issues{/number}", 511 | "pulls_url": "https://api.github.com/repos/electron/electron/pulls{/number}", 512 | "milestones_url": "https://api.github.com/repos/electron/electron/milestones{/number}", 513 | "notifications_url": "https://api.github.com/repos/electron/electron/notifications{?since,all,participating}", 514 | "labels_url": "https://api.github.com/repos/electron/electron/labels{/name}", 515 | "releases_url": "https://api.github.com/repos/electron/electron/releases{/id}", 516 | "deployments_url": "https://api.github.com/repos/electron/electron/deployments", 517 | "created_at": "2013-04-12T01:47:36Z", 518 | "updated_at": "2020-08-10T17:21:54Z", 519 | "pushed_at": "2020-08-10T17:22:10Z", 520 | "git_url": "git://github.com/electron/electron.git", 521 | "ssh_url": "git@github.com:electron/electron.git", 522 | "clone_url": "https://github.com/electron/electron.git", 523 | "svn_url": "https://github.com/electron/electron", 524 | "homepage": "https://electronjs.org", 525 | "size": 78475, 526 | "stargazers_count": 85015, 527 | "watchers_count": 85015, 528 | "language": "C++", 529 | "has_issues": true, 530 | "has_projects": true, 531 | "has_downloads": true, 532 | "has_wiki": false, 533 | "has_pages": false, 534 | "forks_count": 11363, 535 | "mirror_url": null, 536 | "archived": false, 537 | "disabled": false, 538 | "open_issues_count": 1369, 539 | "license": { 540 | "key": "mit", 541 | "name": "MIT License", 542 | "spdx_id": "MIT", 543 | "url": "https://api.github.com/licenses/mit", 544 | "node_id": "MDc6TGljZW5zZTEz" 545 | }, 546 | "forks": 11363, 547 | "open_issues": 1369, 548 | "watchers": 85015, 549 | "default_branch": "master" 550 | } 551 | }, 552 | "base": { 553 | "label": "electron:10-x-y", 554 | "ref": "10-x-y", 555 | "sha": "fdde45379e107580341820510d047eb3cc9c7f36", 556 | "user": { 557 | "login": "electron", 558 | "id": 13409222, 559 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 560 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 561 | "gravatar_id": "", 562 | "url": "https://api.github.com/users/electron", 563 | "html_url": "https://github.com/electron", 564 | "followers_url": "https://api.github.com/users/electron/followers", 565 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 566 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 567 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 568 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 569 | "organizations_url": "https://api.github.com/users/electron/orgs", 570 | "repos_url": "https://api.github.com/users/electron/repos", 571 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 572 | "received_events_url": "https://api.github.com/users/electron/received_events", 573 | "type": "Organization", 574 | "site_admin": false 575 | }, 576 | "repo": { 577 | "id": 9384267, 578 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 579 | "name": "electron", 580 | "full_name": "electron/electron", 581 | "private": false, 582 | "owner": { 583 | "login": "electron", 584 | "id": 13409222, 585 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 586 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 587 | "gravatar_id": "", 588 | "url": "https://api.github.com/users/electron", 589 | "html_url": "https://github.com/electron", 590 | "followers_url": "https://api.github.com/users/electron/followers", 591 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 592 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 593 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 594 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 595 | "organizations_url": "https://api.github.com/users/electron/orgs", 596 | "repos_url": "https://api.github.com/users/electron/repos", 597 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 598 | "received_events_url": "https://api.github.com/users/electron/received_events", 599 | "type": "Organization", 600 | "site_admin": false 601 | }, 602 | "html_url": "https://github.com/electron/electron", 603 | "description": ":electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS", 604 | "fork": false, 605 | "url": "https://api.github.com/repos/electron/electron", 606 | "forks_url": "https://api.github.com/repos/electron/electron/forks", 607 | "keys_url": "https://api.github.com/repos/electron/electron/keys{/key_id}", 608 | "collaborators_url": "https://api.github.com/repos/electron/electron/collaborators{/collaborator}", 609 | "teams_url": "https://api.github.com/repos/electron/electron/teams", 610 | "hooks_url": "https://api.github.com/repos/electron/electron/hooks", 611 | "issue_events_url": "https://api.github.com/repos/electron/electron/issues/events{/number}", 612 | "events_url": "https://api.github.com/repos/electron/electron/events", 613 | "assignees_url": "https://api.github.com/repos/electron/electron/assignees{/user}", 614 | "branches_url": "https://api.github.com/repos/electron/electron/branches{/branch}", 615 | "tags_url": "https://api.github.com/repos/electron/electron/tags", 616 | "blobs_url": "https://api.github.com/repos/electron/electron/git/blobs{/sha}", 617 | "git_tags_url": "https://api.github.com/repos/electron/electron/git/tags{/sha}", 618 | "git_refs_url": "https://api.github.com/repos/electron/electron/git/refs{/sha}", 619 | "trees_url": "https://api.github.com/repos/electron/electron/git/trees{/sha}", 620 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/{sha}", 621 | "languages_url": "https://api.github.com/repos/electron/electron/languages", 622 | "stargazers_url": "https://api.github.com/repos/electron/electron/stargazers", 623 | "contributors_url": "https://api.github.com/repos/electron/electron/contributors", 624 | "subscribers_url": "https://api.github.com/repos/electron/electron/subscribers", 625 | "subscription_url": "https://api.github.com/repos/electron/electron/subscription", 626 | "commits_url": "https://api.github.com/repos/electron/electron/commits{/sha}", 627 | "git_commits_url": "https://api.github.com/repos/electron/electron/git/commits{/sha}", 628 | "comments_url": "https://api.github.com/repos/electron/electron/comments{/number}", 629 | "issue_comment_url": "https://api.github.com/repos/electron/electron/issues/comments{/number}", 630 | "contents_url": "https://api.github.com/repos/electron/electron/contents/{+path}", 631 | "compare_url": "https://api.github.com/repos/electron/electron/compare/{base}...{head}", 632 | "merges_url": "https://api.github.com/repos/electron/electron/merges", 633 | "archive_url": "https://api.github.com/repos/electron/electron/{archive_format}{/ref}", 634 | "downloads_url": "https://api.github.com/repos/electron/electron/downloads", 635 | "issues_url": "https://api.github.com/repos/electron/electron/issues{/number}", 636 | "pulls_url": "https://api.github.com/repos/electron/electron/pulls{/number}", 637 | "milestones_url": "https://api.github.com/repos/electron/electron/milestones{/number}", 638 | "notifications_url": "https://api.github.com/repos/electron/electron/notifications{?since,all,participating}", 639 | "labels_url": "https://api.github.com/repos/electron/electron/labels{/name}", 640 | "releases_url": "https://api.github.com/repos/electron/electron/releases{/id}", 641 | "deployments_url": "https://api.github.com/repos/electron/electron/deployments", 642 | "created_at": "2013-04-12T01:47:36Z", 643 | "updated_at": "2020-08-10T17:21:54Z", 644 | "pushed_at": "2020-08-10T17:22:10Z", 645 | "git_url": "git://github.com/electron/electron.git", 646 | "ssh_url": "git@github.com:electron/electron.git", 647 | "clone_url": "https://github.com/electron/electron.git", 648 | "svn_url": "https://github.com/electron/electron", 649 | "homepage": "https://electronjs.org", 650 | "size": 78475, 651 | "stargazers_count": 85015, 652 | "watchers_count": 85015, 653 | "language": "C++", 654 | "has_issues": true, 655 | "has_projects": true, 656 | "has_downloads": true, 657 | "has_wiki": false, 658 | "has_pages": false, 659 | "forks_count": 11363, 660 | "mirror_url": null, 661 | "archived": false, 662 | "disabled": false, 663 | "open_issues_count": 1369, 664 | "license": { 665 | "key": "mit", 666 | "name": "MIT License", 667 | "spdx_id": "MIT", 668 | "url": "https://api.github.com/licenses/mit", 669 | "node_id": "MDc6TGljZW5zZTEz" 670 | }, 671 | "forks": 11363, 672 | "open_issues": 1369, 673 | "watchers": 85015, 674 | "default_branch": "master" 675 | } 676 | }, 677 | "_links": { 678 | "self": { 679 | "href": "https://api.github.com/repos/electron/electron/pulls/24706" 680 | }, 681 | "html": { 682 | "href": "https://github.com/electron/electron/pull/24706" 683 | }, 684 | "issue": { 685 | "href": "https://api.github.com/repos/electron/electron/issues/24706" 686 | }, 687 | "comments": { 688 | "href": "https://api.github.com/repos/electron/electron/issues/24706/comments" 689 | }, 690 | "review_comments": { 691 | "href": "https://api.github.com/repos/electron/electron/pulls/24706/comments" 692 | }, 693 | "review_comment": { 694 | "href": "https://api.github.com/repos/electron/electron/pulls/comments{/number}" 695 | }, 696 | "commits": { 697 | "href": "https://api.github.com/repos/electron/electron/pulls/24706/commits" 698 | }, 699 | "statuses": { 700 | "href": "https://api.github.com/repos/electron/electron/statuses/34ad915c6ab24f6c7d475007fbc7ada7fd77073a" 701 | } 702 | }, 703 | "author_association": "MEMBER", 704 | "active_lock_reason": null 705 | }, 706 | { 707 | "url": "https://api.github.com/repos/electron/electron/issues/20625", 708 | "repository_url": "https://api.github.com/repos/electron/electron", 709 | "labels_url": "https://api.github.com/repos/electron/electron/issues/20625/labels{/name}", 710 | "comments_url": "https://api.github.com/repos/electron/electron/issues/20625/comments", 711 | "events_url": "https://api.github.com/repos/electron/electron/issues/20625/events", 712 | "html_url": "https://github.com/electron/electron/pull/20625", 713 | "id": 508802195, 714 | "node_id": "MDExOlB1bGxSZXF1ZXN0MzI5NTI1MTI5", 715 | "number": 20625, 716 | "title": "fix: loading dedicated/shared worker scripts over custom protocol", 717 | "user": { 718 | "login": "deepak1556", 719 | "id": 964386, 720 | "node_id": "MDQ6VXNlcjk2NDM4Ng==", 721 | "avatar_url": "https://avatars1.githubusercontent.com/u/964386?v=4", 722 | "gravatar_id": "", 723 | "url": "https://api.github.com/users/deepak1556", 724 | "html_url": "https://github.com/deepak1556", 725 | "followers_url": "https://api.github.com/users/deepak1556/followers", 726 | "following_url": "https://api.github.com/users/deepak1556/following{/other_user}", 727 | "gists_url": "https://api.github.com/users/deepak1556/gists{/gist_id}", 728 | "starred_url": "https://api.github.com/users/deepak1556/starred{/owner}{/repo}", 729 | "subscriptions_url": "https://api.github.com/users/deepak1556/subscriptions", 730 | "organizations_url": "https://api.github.com/users/deepak1556/orgs", 731 | "repos_url": "https://api.github.com/users/deepak1556/repos", 732 | "events_url": "https://api.github.com/users/deepak1556/events{/privacy}", 733 | "received_events_url": "https://api.github.com/users/deepak1556/received_events", 734 | "type": "User", 735 | "site_admin": false 736 | }, 737 | "base": { 738 | "label": "electron:10-x-y", 739 | "ref": "10-x-y", 740 | "sha": "04f652a53bf99404ec0fc122de6cabc5f91d240b", 741 | "user": { 742 | "login": "electron", 743 | "id": 13409222, 744 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 745 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 746 | "gravatar_id": "", 747 | "url": "https://api.github.com/users/electron", 748 | "html_url": "https://github.com/electron", 749 | "followers_url": "https://api.github.com/users/electron/followers", 750 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 751 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 752 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 753 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 754 | "organizations_url": "https://api.github.com/users/electron/orgs", 755 | "repos_url": "https://api.github.com/users/electron/repos", 756 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 757 | "received_events_url": "https://api.github.com/users/electron/received_events", 758 | "type": "Organization", 759 | "site_admin": false 760 | }, 761 | "repo": { 762 | "id": 9384267, 763 | "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg0MjY3", 764 | "name": "electron", 765 | "full_name": "electron/electron", 766 | "private": false, 767 | "owner": { 768 | "login": "electron", 769 | "id": 13409222, 770 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDA5MjIy", 771 | "avatar_url": "https://avatars1.githubusercontent.com/u/13409222?v=4", 772 | "gravatar_id": "", 773 | "url": "https://api.github.com/users/electron", 774 | "html_url": "https://github.com/electron", 775 | "followers_url": "https://api.github.com/users/electron/followers", 776 | "following_url": "https://api.github.com/users/electron/following{/other_user}", 777 | "gists_url": "https://api.github.com/users/electron/gists{/gist_id}", 778 | "starred_url": "https://api.github.com/users/electron/starred{/owner}{/repo}", 779 | "subscriptions_url": "https://api.github.com/users/electron/subscriptions", 780 | "organizations_url": "https://api.github.com/users/electron/orgs", 781 | "repos_url": "https://api.github.com/users/electron/repos", 782 | "events_url": "https://api.github.com/users/electron/events{/privacy}", 783 | "received_events_url": "https://api.github.com/users/electron/received_events", 784 | "type": "Organization", 785 | "site_admin": false 786 | }, 787 | "html_url": "https://github.com/electron/electron", 788 | "description": ":electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS", 789 | "fork": false, 790 | "url": "https://api.github.com/repos/electron/electron", 791 | "forks_url": "https://api.github.com/repos/electron/electron/forks", 792 | "keys_url": "https://api.github.com/repos/electron/electron/keys{/key_id}", 793 | "collaborators_url": "https://api.github.com/repos/electron/electron/collaborators{/collaborator}", 794 | "teams_url": "https://api.github.com/repos/electron/electron/teams", 795 | "hooks_url": "https://api.github.com/repos/electron/electron/hooks", 796 | "issue_events_url": "https://api.github.com/repos/electron/electron/issues/events{/number}", 797 | "events_url": "https://api.github.com/repos/electron/electron/events", 798 | "assignees_url": "https://api.github.com/repos/electron/electron/assignees{/user}", 799 | "branches_url": "https://api.github.com/repos/electron/electron/branches{/branch}", 800 | "tags_url": "https://api.github.com/repos/electron/electron/tags", 801 | "blobs_url": "https://api.github.com/repos/electron/electron/git/blobs{/sha}", 802 | "git_tags_url": "https://api.github.com/repos/electron/electron/git/tags{/sha}", 803 | "git_refs_url": "https://api.github.com/repos/electron/electron/git/refs{/sha}", 804 | "trees_url": "https://api.github.com/repos/electron/electron/git/trees{/sha}", 805 | "statuses_url": "https://api.github.com/repos/electron/electron/statuses/{sha}", 806 | "languages_url": "https://api.github.com/repos/electron/electron/languages", 807 | "stargazers_url": "https://api.github.com/repos/electron/electron/stargazers", 808 | "contributors_url": "https://api.github.com/repos/electron/electron/contributors", 809 | "subscribers_url": "https://api.github.com/repos/electron/electron/subscribers", 810 | "subscription_url": "https://api.github.com/repos/electron/electron/subscription", 811 | "commits_url": "https://api.github.com/repos/electron/electron/commits{/sha}", 812 | "git_commits_url": "https://api.github.com/repos/electron/electron/git/commits{/sha}", 813 | "comments_url": "https://api.github.com/repos/electron/electron/comments{/number}", 814 | "issue_comment_url": "https://api.github.com/repos/electron/electron/issues/comments{/number}", 815 | "contents_url": "https://api.github.com/repos/electron/electron/contents/{+path}", 816 | "compare_url": "https://api.github.com/repos/electron/electron/compare/{base}...{head}", 817 | "merges_url": "https://api.github.com/repos/electron/electron/merges", 818 | "archive_url": "https://api.github.com/repos/electron/electron/{archive_format}{/ref}", 819 | "downloads_url": "https://api.github.com/repos/electron/electron/downloads", 820 | "issues_url": "https://api.github.com/repos/electron/electron/issues{/number}", 821 | "pulls_url": "https://api.github.com/repos/electron/electron/pulls{/number}", 822 | "milestones_url": "https://api.github.com/repos/electron/electron/milestones{/number}", 823 | "notifications_url": "https://api.github.com/repos/electron/electron/notifications{?since,all,participating}", 824 | "labels_url": "https://api.github.com/repos/electron/electron/labels{/name}", 825 | "releases_url": "https://api.github.com/repos/electron/electron/releases{/id}", 826 | "deployments_url": "https://api.github.com/repos/electron/electron/deployments", 827 | "created_at": "2013-04-12T01:47:36Z", 828 | "updated_at": "2020-08-10T17:21:54Z", 829 | "pushed_at": "2020-08-10T17:22:10Z", 830 | "git_url": "git://github.com/electron/electron.git", 831 | "ssh_url": "git@github.com:electron/electron.git", 832 | "clone_url": "https://github.com/electron/electron.git", 833 | "svn_url": "https://github.com/electron/electron", 834 | "homepage": "https://electronjs.org", 835 | "size": 78475, 836 | "stargazers_count": 85015, 837 | "watchers_count": 85015, 838 | "language": "C++", 839 | "has_issues": true, 840 | "has_projects": true, 841 | "has_downloads": true, 842 | "has_wiki": false, 843 | "has_pages": false, 844 | "forks_count": 11363, 845 | "mirror_url": null, 846 | "archived": false, 847 | "disabled": false, 848 | "open_issues_count": 1369, 849 | "license": { 850 | "key": "mit", 851 | "name": "MIT License", 852 | "spdx_id": "MIT", 853 | "url": "https://api.github.com/licenses/mit", 854 | "node_id": "MDc6TGljZW5zZTEz" 855 | }, 856 | "forks": 11363, 857 | "open_issues": 1369, 858 | "watchers": 85015, 859 | "default_branch": "master" 860 | } 861 | }, 862 | "labels": [ 863 | { 864 | "id": 1831441136, 865 | "node_id": "MDU6TGFiZWwxODMxNDQxMTM2", 866 | "url": "https://api.github.com/repos/electron/electron/labels/in-flight/9-x-y", 867 | "name": "in-flight/9-x-y", 868 | "color": "db69a6", 869 | "default": false, 870 | "description": "" 871 | }, 872 | { 873 | "id": 2079356337, 874 | "node_id": "MDU6TGFiZWwyMDc5MzU2MzM3", 875 | "url": "https://api.github.com/repos/electron/electron/labels/merged/10-x-y", 876 | "name": "merged/10-x-y", 877 | "color": "61a3c6", 878 | "default": false, 879 | "description": "" 880 | }, 881 | { 882 | "id": 1639703462, 883 | "node_id": "MDU6TGFiZWwxNjM5NzAzNDYy", 884 | "url": "https://api.github.com/repos/electron/electron/labels/needs-manual-bp/8-x-y", 885 | "name": "needs-manual-bp/8-x-y", 886 | "color": "8b5dba", 887 | "default": false, 888 | "description": "" 889 | } 890 | ], 891 | "state": "closed", 892 | "locked": false, 893 | "assignee": null, 894 | "assignees": [], 895 | "milestone": null, 896 | "comments": 8, 897 | "created_at": "2019-10-18T01:27:29Z", 898 | "updated_at": "2020-08-06T16:59:28Z", 899 | "closed_at": "2020-07-28T01:48:38Z", 900 | "author_association": "MEMBER", 901 | "active_lock_reason": null, 902 | "draft": true, 903 | "pull_request": { 904 | "url": "https://api.github.com/repos/electron/electron/pulls/20625", 905 | "html_url": "https://github.com/electron/electron/pull/20625", 906 | "diff_url": "https://github.com/electron/electron/pull/20625.diff", 907 | "patch_url": "https://github.com/electron/electron/pull/20625.patch" 908 | }, 909 | "body": "#### Description of Change\r\n\r\nCloses https://github.com/electron/electron/issues/20557.\r\nCloses https://github.com/electron/electron/issues/24715.\r\n\r\nThis is a two part fix,\r\n\r\n**Part I: Fix crash when loading over data: protocol mentioned in the above issue**\r\n\r\nThis issue will appear only with nodeintegration enabled in the worker. Adds patch `worker_feat_add_hook_to_notify_script_ready.patch` that explains the root cause of this issue, I am gonna try upstreaming this, will see how it goes. The access to global properties from Node.js happens in `node::MaybeInitializeContext` which modifies the global object for primordials.\r\n\r\n**Part II: Cannot load worker scripts over custom protocol**\r\n\r\nAfter https://chromium-review.googlesource.com/c/chromium/src/+/1798250 we need to explicitly register custom URLLoaderFactories for dedicated/shared workers.\r\n\r\n#### Checklist\r\n\r\n- [x] PR description included and stakeholders cc'd\r\n- [ ] `npm test` passes\r\n- [ ] tests are [changed or added](https://github.com/electron/electron/blob/master/docs/development/testing.md)\r\n- [ ] relevant documentation is changed or added\r\n- [x] PR title follows semantic [commit guidelines](https://github.com/electron/electron/blob/master/docs/development/pull-requests.md#commit-message-guidelines)\r\n- [x] [PR release notes](https://github.com/electron/clerk/blob/master/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/master/README.md#examples).\r\n\r\n#### Release Notes\r\n\r\nNotes:\r\n* fix loading shared worker scripts over custom protocol\r\n* fix crash when loading worker scripts with nodeIntegration enabled\r\n", 910 | "performed_via_github_app": null, 911 | "score": 1 912 | } 913 | ] 914 | -------------------------------------------------------------------------------- /test/fixtures/unreleased-commits.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "sha": "f799b6eb37cb8ef40f8f65c002fe1d752ca439ef", 4 | "node_id": "MDY6Q29tbWl0OTM4NDI2NzpmNzk5YjZlYjM3Y2I4ZWY0MGY4ZjY1YzAwMmZlMWQ3NTJjYTQzOWVm", 5 | "commit": { 6 | "author": { 7 | "name": "trop[bot]", 8 | "email": "37223003+trop[bot]@users.noreply.github.com", 9 | "date": "2020-08-07T20:51:49Z" 10 | }, 11 | "committer": { 12 | "name": "GitHub", 13 | "email": "noreply@github.com", 14 | "date": "2020-08-07T20:51:49Z" 15 | }, 16 | "message": "build: ensure symbol files are named lowercase on disk so that boto can find them (#24858)\n\n* build: ensure symbol files are named lowercase on disk so that boto can find them\r\n\r\n* build: only do the lower case symbol copy on case sensitive file systems (#24876)\r\n\r\nCo-authored-by: Samuel Attard \r\nCo-authored-by: Samuel Attard ", 17 | "tree": { 18 | "sha": "c746224e4f351943970ee461e7a62137ba8c01db", 19 | "url": "https://api.github.com/repos/electron/electron/git/trees/c746224e4f351943970ee461e7a62137ba8c01db" 20 | }, 21 | "url": "https://api.github.com/repos/electron/electron/git/commits/f799b6eb37cb8ef40f8f65c002fe1d752ca439ef", 22 | "comment_count": 0, 23 | "verification": { 24 | "verified": true, 25 | "reason": "valid", 26 | "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJfLb7lCRBK7hj4Ov3rIwAAdHIIALB20ZZlIeyVmFArrspMdaiM\nfOmXiwCwPJfXJbag/fZxGhAUqdcUUp3jhE7tGmlC5cSsD9Z5nFcHp2zdELkaGxO6\ncfGvvFHX2nWNW0hBg3vQ9yDQflNsPck5927iGRHTcea23vA1n082Tbj7JEYYO1ZH\ngZ32v6bn/OQCeBsRHyszjZUpVhS/kxKHsCo3YIi7z9GyJFpV+tv5zkAuxfBZLU5s\n5RpmDhDF6F44enlIVVVyiqkxHuRxr9Y3CKlosQxebexHIqAL0ddt0nhS0An42O42\nLyWuaoqjYU5alYaSPSWNgHY3GO2NmmTfgwytY1/RqVmdJj4rgE28Ha0P3hYoJKA=\n=+3qq\n-----END PGP SIGNATURE-----\n", 27 | "payload": "tree c746224e4f351943970ee461e7a62137ba8c01db\nparent 7063ba73dfe8862e02d6b1a01b7742e52bac2515\nauthor trop[bot] <37223003+trop[bot]@users.noreply.github.com> 1596833509 -0700\ncommitter GitHub 1596833509 -0700\n\nbuild: ensure symbol files are named lowercase on disk so that boto can find them (#24858)\n\n* build: ensure symbol files are named lowercase on disk so that boto can find them\r\n\r\n* build: only do the lower case symbol copy on case sensitive file systems (#24876)\r\n\r\nCo-authored-by: Samuel Attard \r\nCo-authored-by: Samuel Attard " 28 | } 29 | }, 30 | "url": "https://api.github.com/repos/electron/electron/commits/f799b6eb37cb8ef40f8f65c002fe1d752ca439ef", 31 | "html_url": "https://github.com/electron/electron/commit/f799b6eb37cb8ef40f8f65c002fe1d752ca439ef", 32 | "comments_url": "https://api.github.com/repos/electron/electron/commits/f799b6eb37cb8ef40f8f65c002fe1d752ca439ef/comments", 33 | "author": { 34 | "login": "trop[bot]", 35 | "id": 37223003, 36 | "node_id": "MDM6Qm90MzcyMjMwMDM=", 37 | "avatar_url": "https://avatars1.githubusercontent.com/in/9879?v=4", 38 | "gravatar_id": "", 39 | "url": "https://api.github.com/users/trop%5Bbot%5D", 40 | "html_url": "https://github.com/apps/trop", 41 | "followers_url": "https://api.github.com/users/trop%5Bbot%5D/followers", 42 | "following_url": "https://api.github.com/users/trop%5Bbot%5D/following{/other_user}", 43 | "gists_url": "https://api.github.com/users/trop%5Bbot%5D/gists{/gist_id}", 44 | "starred_url": "https://api.github.com/users/trop%5Bbot%5D/starred{/owner}{/repo}", 45 | "subscriptions_url": "https://api.github.com/users/trop%5Bbot%5D/subscriptions", 46 | "organizations_url": "https://api.github.com/users/trop%5Bbot%5D/orgs", 47 | "repos_url": "https://api.github.com/users/trop%5Bbot%5D/repos", 48 | "events_url": "https://api.github.com/users/trop%5Bbot%5D/events{/privacy}", 49 | "received_events_url": "https://api.github.com/users/trop%5Bbot%5D/received_events", 50 | "type": "Bot", 51 | "site_admin": false 52 | }, 53 | "committer": { 54 | "login": "web-flow", 55 | "id": 19864447, 56 | "node_id": "MDQ6VXNlcjE5ODY0NDQ3", 57 | "avatar_url": "https://avatars3.githubusercontent.com/u/19864447?v=4", 58 | "gravatar_id": "", 59 | "url": "https://api.github.com/users/web-flow", 60 | "html_url": "https://github.com/web-flow", 61 | "followers_url": "https://api.github.com/users/web-flow/followers", 62 | "following_url": "https://api.github.com/users/web-flow/following{/other_user}", 63 | "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}", 64 | "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}", 65 | "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions", 66 | "organizations_url": "https://api.github.com/users/web-flow/orgs", 67 | "repos_url": "https://api.github.com/users/web-flow/repos", 68 | "events_url": "https://api.github.com/users/web-flow/events{/privacy}", 69 | "received_events_url": "https://api.github.com/users/web-flow/received_events", 70 | "type": "User", 71 | "site_admin": false 72 | }, 73 | "parents": [ 74 | { 75 | "sha": "7063ba73dfe8862e02d6b1a01b7742e52bac2515", 76 | "url": "https://api.github.com/repos/electron/electron/commits/7063ba73dfe8862e02d6b1a01b7742e52bac2515", 77 | "html_url": "https://github.com/electron/electron/commit/7063ba73dfe8862e02d6b1a01b7742e52bac2515" 78 | } 79 | ] 80 | }, 81 | { 82 | "sha": "7063ba73dfe8862e02d6b1a01b7742e52bac2515", 83 | "node_id": "MDY6Q29tbWl0OTM4NDI2Nzo3MDYzYmE3M2RmZTg4NjJlMDJkNmIxYTAxYjc3NDJlNTJiYWMyNTE1", 84 | "commit": { 85 | "author": { 86 | "name": "trop[bot]", 87 | "email": "37223003+trop[bot]@users.noreply.github.com", 88 | "date": "2020-08-07T03:46:59Z" 89 | }, 90 | "committer": { 91 | "name": "GitHub", 92 | "email": "noreply@github.com", 93 | "date": "2020-08-07T03:46:59Z" 94 | }, 95 | "message": "fix: do not render inactive titlebar as active on Windows (#24873)\n\nCo-authored-by: Cheng Zhao ", 96 | "tree": { 97 | "sha": "aed651b145c8aef64d61042ee3ee7d073b2495eb", 98 | "url": "https://api.github.com/repos/electron/electron/git/trees/aed651b145c8aef64d61042ee3ee7d073b2495eb" 99 | }, 100 | "url": "https://api.github.com/repos/electron/electron/git/commits/7063ba73dfe8862e02d6b1a01b7742e52bac2515", 101 | "comment_count": 0, 102 | "verification": { 103 | "verified": true, 104 | "reason": "valid", 105 | "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJfLM6zCRBK7hj4Ov3rIwAAdHIIAIxCmdvfVVQi8xbvhJ44CGP6\nUGO0bqlMPXkvPVFQaL6C9N7WwRkS0srztdMIQfXC6xqIcCtC11BxXa+nT/ESDTuw\nkqg0vmBh/i0y6gmL2KKxYFBDFttJ4z2++vEqhV2g5dZLkSZ0iNsE41eX0b9fhSGv\nMKs/wFSqDpwow7TnUWRhUDDBWVqZH0N1XUiFUh1Dqnct7v3hJgYMOxHMQWvozJTH\nwPOCmAzGfA2ZvFYnLeljBi0Ys3tNqyXFPraOExo0o56W9gGQSD4SrsZAbgfRuudk\nUgv6DFKr4oDm944c8g9872k5cjcp8t/YDb6QmJZzgjoqCxBLXZHhTBW9a28RhMg=\n=m9uJ\n-----END PGP SIGNATURE-----\n", 106 | "payload": "tree aed651b145c8aef64d61042ee3ee7d073b2495eb\nparent f01bb5f43b384527b7f1cdebfc4e5c1d067b9af6\nauthor trop[bot] <37223003+trop[bot]@users.noreply.github.com> 1596772019 -0700\ncommitter GitHub 1596772019 -0700\n\nfix: do not render inactive titlebar as active on Windows (#24873)\n\nCo-authored-by: Cheng Zhao " 107 | } 108 | }, 109 | "url": "https://api.github.com/repos/electron/electron/commits/7063ba73dfe8862e02d6b1a01b7742e52bac2515", 110 | "html_url": "https://github.com/electron/electron/commit/7063ba73dfe8862e02d6b1a01b7742e52bac2515", 111 | "comments_url": "https://api.github.com/repos/electron/electron/commits/7063ba73dfe8862e02d6b1a01b7742e52bac2515/comments", 112 | "author": { 113 | "login": "trop[bot]", 114 | "id": 37223003, 115 | "node_id": "MDM6Qm90MzcyMjMwMDM=", 116 | "avatar_url": "https://avatars1.githubusercontent.com/in/9879?v=4", 117 | "gravatar_id": "", 118 | "url": "https://api.github.com/users/trop%5Bbot%5D", 119 | "html_url": "https://github.com/apps/trop", 120 | "followers_url": "https://api.github.com/users/trop%5Bbot%5D/followers", 121 | "following_url": "https://api.github.com/users/trop%5Bbot%5D/following{/other_user}", 122 | "gists_url": "https://api.github.com/users/trop%5Bbot%5D/gists{/gist_id}", 123 | "starred_url": "https://api.github.com/users/trop%5Bbot%5D/starred{/owner}{/repo}", 124 | "subscriptions_url": "https://api.github.com/users/trop%5Bbot%5D/subscriptions", 125 | "organizations_url": "https://api.github.com/users/trop%5Bbot%5D/orgs", 126 | "repos_url": "https://api.github.com/users/trop%5Bbot%5D/repos", 127 | "events_url": "https://api.github.com/users/trop%5Bbot%5D/events{/privacy}", 128 | "received_events_url": "https://api.github.com/users/trop%5Bbot%5D/received_events", 129 | "type": "Bot", 130 | "site_admin": false 131 | }, 132 | "committer": { 133 | "login": "web-flow", 134 | "id": 19864447, 135 | "node_id": "MDQ6VXNlcjE5ODY0NDQ3", 136 | "avatar_url": "https://avatars3.githubusercontent.com/u/19864447?v=4", 137 | "gravatar_id": "", 138 | "url": "https://api.github.com/users/web-flow", 139 | "html_url": "https://github.com/web-flow", 140 | "followers_url": "https://api.github.com/users/web-flow/followers", 141 | "following_url": "https://api.github.com/users/web-flow/following{/other_user}", 142 | "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}", 143 | "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}", 144 | "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions", 145 | "organizations_url": "https://api.github.com/users/web-flow/orgs", 146 | "repos_url": "https://api.github.com/users/web-flow/repos", 147 | "events_url": "https://api.github.com/users/web-flow/events{/privacy}", 148 | "received_events_url": "https://api.github.com/users/web-flow/received_events", 149 | "type": "User", 150 | "site_admin": false 151 | }, 152 | "parents": [ 153 | { 154 | "sha": "f01bb5f43b384527b7f1cdebfc4e5c1d067b9af6", 155 | "url": "https://api.github.com/repos/electron/electron/commits/f01bb5f43b384527b7f1cdebfc4e5c1d067b9af6", 156 | "html_url": "https://github.com/electron/electron/commit/f01bb5f43b384527b7f1cdebfc4e5c1d067b9af6" 157 | } 158 | ] 159 | }, 160 | { 161 | "sha": "f01bb5f43b384527b7f1cdebfc4e5c1d067b9af6", 162 | "node_id": "MDY6Q29tbWl0OTM4NDI2NzpmMDFiYjVmNDNiMzg0NTI3YjdmMWNkZWJmYzRlNWMxZDA2N2I5YWY2", 163 | "commit": { 164 | "author": { 165 | "name": "Jeremy Rose", 166 | "email": "jeremya@chromium.org", 167 | "date": "2020-08-06T15:02:46Z" 168 | }, 169 | "committer": { 170 | "name": "GitHub", 171 | "email": "noreply@github.com", 172 | "date": "2020-08-06T15:02:46Z" 173 | }, 174 | "message": "fix: increase max crash key value length (#24854)", 175 | "tree": { 176 | "sha": "09be2849114c4ff6fce60ed1416e4843b964d721", 177 | "url": "https://api.github.com/repos/electron/electron/git/trees/09be2849114c4ff6fce60ed1416e4843b964d721" 178 | }, 179 | "url": "https://api.github.com/repos/electron/electron/git/commits/f01bb5f43b384527b7f1cdebfc4e5c1d067b9af6", 180 | "comment_count": 0, 181 | "verification": { 182 | "verified": true, 183 | "reason": "valid", 184 | "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJfLBuWCRBK7hj4Ov3rIwAAdHIIADvCN0TT8xtAV4r597pkAQky\nhD1KeCzkkT+uDd76M+DrW3HZOsxsQUadIXjCkkCgVOXmL2QRfVViB+1ouZVEv/mH\ngOconsG5/m0iCixbzY0kSU8dsM/xUAjcip+W5UnGJWe+Fv47emK7YWhXXe4MvjsU\n82hBA3jBqs0czJjoE43HnqHvpa45wAftQo6uoYSWFylfLacuf3mjHx5Z9bVZVJ8d\n5oiIB9M4uN0mn5jwh9XB8LYy1ixSZ81IKnRzCOrio1NLjQi/N3FQIKfZCH0AY4g0\n/ImYWKVmE3bCUX1FUBsNseG9oovkwvrVJBTbHqJvvFGqYR3wG+2nX6iADxy625I=\n=YCbk\n-----END PGP SIGNATURE-----\n", 185 | "payload": "tree 09be2849114c4ff6fce60ed1416e4843b964d721\nparent 0c2cb59b6283fe8d6bb4b14f8a832e2166aeaa0c\nauthor Jeremy Rose 1596726166 -0700\ncommitter GitHub 1596726166 -0700\n\nfix: increase max crash key value length (#24854)\n\n" 186 | } 187 | }, 188 | "url": "https://api.github.com/repos/electron/electron/commits/f01bb5f43b384527b7f1cdebfc4e5c1d067b9af6", 189 | "html_url": "https://github.com/electron/electron/commit/f01bb5f43b384527b7f1cdebfc4e5c1d067b9af6", 190 | "comments_url": "https://api.github.com/repos/electron/electron/commits/f01bb5f43b384527b7f1cdebfc4e5c1d067b9af6/comments", 191 | "author": { 192 | "login": "nornagon", 193 | "id": 172800, 194 | "node_id": "MDQ6VXNlcjE3MjgwMA==", 195 | "avatar_url": "https://avatars2.githubusercontent.com/u/172800?v=4", 196 | "gravatar_id": "", 197 | "url": "https://api.github.com/users/nornagon", 198 | "html_url": "https://github.com/nornagon", 199 | "followers_url": "https://api.github.com/users/nornagon/followers", 200 | "following_url": "https://api.github.com/users/nornagon/following{/other_user}", 201 | "gists_url": "https://api.github.com/users/nornagon/gists{/gist_id}", 202 | "starred_url": "https://api.github.com/users/nornagon/starred{/owner}{/repo}", 203 | "subscriptions_url": "https://api.github.com/users/nornagon/subscriptions", 204 | "organizations_url": "https://api.github.com/users/nornagon/orgs", 205 | "repos_url": "https://api.github.com/users/nornagon/repos", 206 | "events_url": "https://api.github.com/users/nornagon/events{/privacy}", 207 | "received_events_url": "https://api.github.com/users/nornagon/received_events", 208 | "type": "User", 209 | "site_admin": false 210 | }, 211 | "committer": { 212 | "login": "web-flow", 213 | "id": 19864447, 214 | "node_id": "MDQ6VXNlcjE5ODY0NDQ3", 215 | "avatar_url": "https://avatars3.githubusercontent.com/u/19864447?v=4", 216 | "gravatar_id": "", 217 | "url": "https://api.github.com/users/web-flow", 218 | "html_url": "https://github.com/web-flow", 219 | "followers_url": "https://api.github.com/users/web-flow/followers", 220 | "following_url": "https://api.github.com/users/web-flow/following{/other_user}", 221 | "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}", 222 | "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}", 223 | "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions", 224 | "organizations_url": "https://api.github.com/users/web-flow/orgs", 225 | "repos_url": "https://api.github.com/users/web-flow/repos", 226 | "events_url": "https://api.github.com/users/web-flow/events{/privacy}", 227 | "received_events_url": "https://api.github.com/users/web-flow/received_events", 228 | "type": "User", 229 | "site_admin": false 230 | }, 231 | "parents": [ 232 | { 233 | "sha": "0c2cb59b6283fe8d6bb4b14f8a832e2166aeaa0c", 234 | "url": "https://api.github.com/repos/electron/electron/commits/0c2cb59b6283fe8d6bb4b14f8a832e2166aeaa0c", 235 | "html_url": "https://github.com/electron/electron/commit/0c2cb59b6283fe8d6bb4b14f8a832e2166aeaa0c" 236 | } 237 | ] 238 | } 239 | ] 240 | -------------------------------------------------------------------------------- /test/gh-api-calls.js: -------------------------------------------------------------------------------- 1 | const assert = require('node:assert/strict'); 2 | const { describe, it } = require('node:test'); 3 | const { parseArgs } = require('node:util'); 4 | 5 | const { fetchUnreleasedCommits } = require('../utils/unreleased-commits'); 6 | const { getSemverForCommitRange, releaseIsDraft } = require('../utils/helpers'); 7 | const { fetchNeedsManualPRs } = require('../utils/needs-manual-prs'); 8 | const { fetchUnmergedPRs } = require('../utils/unmerged-prs'); 9 | 10 | const argOptions = { 11 | author: { type: 'string' }, 12 | branch: { type: 'string' }, 13 | tag: { type: 'string' }, 14 | }; 15 | 16 | const { values: argValues } = parseArgs({ options: argOptions }); 17 | 18 | const branch = argValues?.branch || '17-x-y'; 19 | const tag = argValues?.tag || 'v17.0.0'; 20 | const author = argValues?.author || null; 21 | 22 | describe('API tests', () => { 23 | it('can fetch unreleased commits', async () => { 24 | const { commits } = await fetchUnreleasedCommits(branch); 25 | assert.ok(Array.isArray(commits)); 26 | }); 27 | 28 | it('can get the semver value for a commit range', async () => { 29 | const { commits } = await fetchUnreleasedCommits(branch); 30 | const semverType = await getSemverForCommitRange(commits, branch); 31 | const values = ['semver/major', 'semver/minor', 'semver/patch']; 32 | assert.ok(values.includes(semverType)); 33 | }); 34 | 35 | it('can determine if a given release is a draft release', async () => { 36 | const isDraft = await releaseIsDraft(tag); 37 | assert.strictEqual(isDraft, false); 38 | }); 39 | 40 | it('can fetch PRs needing manual backport', async () => { 41 | const prs = await fetchNeedsManualPRs(branch, author); 42 | assert.ok(Array.isArray(prs)); 43 | }); 44 | 45 | it('can fetch PRs needing merge', async () => { 46 | const prs = await fetchUnmergedPRs(branch); 47 | assert.ok(Array.isArray(prs)); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/needs-manual.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('node:assert/strict'); 2 | const { describe, it } = require('node:test'); 3 | 4 | const { buildNeedsManualPRsMessage } = require('../utils/needs-manual-prs'); 5 | 6 | describe('needs manual', () => { 7 | it('can build the needs-manual PRs message', () => { 8 | const prs = require('./fixtures/needs-manual.json'); 9 | const branch = '8-x-y'; 10 | const message = buildNeedsManualPRsMessage( 11 | branch, 12 | prs, 13 | null /* shouldRemind */, 14 | ); 15 | 16 | const expected = `* - build: ensure symbol files are named lowercase on disk so that boto can find them 17 | * - fix: ensure that errors thrown in the context bridge are created in the correct context 18 | * - feat: add worldSafe flag for executeJS results 19 | * - fix: Close protocol response streams when aborted 20 | * - fix: loading dedicated/shared worker scripts over custom protocol 21 | *5 PR(s) needing manual backport to \`8-x-y\`!*`; 22 | 23 | assert.strictEqual(message, expected); 24 | }); 25 | 26 | it('can build the needs-manual PRs message with remind', () => { 27 | const prs = require('./fixtures/needs-manual.json'); 28 | const branch = '8-x-y'; 29 | const message = buildNeedsManualPRsMessage( 30 | branch, 31 | prs, 32 | true /* shouldRemind */, 33 | ); 34 | 35 | const expected = `* - build: ensure symbol files are named lowercase on disk so that boto can find them (<@marshallofsound>) 36 | * - fix: ensure that errors thrown in the context bridge are created in the correct context (<@marshallofsound>) 37 | * - feat: add worldSafe flag for executeJS results (<@marshallofsound>) 38 | * - fix: Close protocol response streams when aborted (<@pfrazee>) 39 | * - fix: loading dedicated/shared worker scripts over custom protocol (<@deepak1556>) 40 | *5 PR(s) needing manual backport to \`8-x-y\`!*`; 41 | 42 | assert.strictEqual(message, expected); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/unmerged.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('node:assert/strict'); 2 | const { describe, it } = require('node:test'); 3 | 4 | const { buildUnmergedPRsMessage } = require('../utils/unmerged-prs'); 5 | 6 | describe('unmerged', () => { 7 | it('can build the unmerged PRs message', () => { 8 | const prs = require('./fixtures/unmerged.json'); 9 | const branch = '10-x-y'; 10 | const message = buildUnmergedPRsMessage(branch, prs); 11 | 12 | const expected = `* - fix: provide AXTextChangeValueStartMarker for macOS a11y value change notifications 13 | * - chore: bump chromium to 85.0.4183.65 (10-x-y) 14 | * (*DRAFT*) - fix: loading dedicated/shared worker scripts over custom protocol 15 | *3 unmerged PR(s) targeting \`10-x-y\`!*`; 16 | 17 | assert.strictEqual(message, expected); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unreleased-commits.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('node:assert/strict'); 2 | const { describe, it } = require('node:test'); 3 | 4 | const { 5 | buildUnreleasedCommitsMessage, 6 | } = require('../utils/unreleased-commits'); 7 | 8 | describe('unreleased', () => { 9 | it('can build the unreleased PRs message', () => { 10 | const commits = require('./fixtures/unreleased-commits.json'); 11 | const branch = '9-x-y'; 12 | const initiator = 'codebytere'; 13 | const message = buildUnreleasedCommitsMessage(branch, commits, initiator); 14 | 15 | const expected = `Unreleased commits in *9-x-y* (from <@codebytere>): 16 | * \`\` build: ensure symbol files are named lowercase on disk so that boto can find them () 17 | * \`\` fix: do not render inactive titlebar as active on Windows () 18 | * \`\` fix: increase max crash key value length ()`; 19 | 20 | assert.strictEqual(message, expected); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /utils/helpers.js: -------------------------------------------------------------------------------- 1 | const crypto = require('node:crypto'); 2 | 3 | const { WebClient } = require('@slack/web-api'); 4 | 5 | const { 6 | ORGANIZATION_NAME, 7 | REPO_NAME, 8 | NUM_SUPPORTED_VERSIONS, 9 | RELEASE_BRANCH_PATTERN, 10 | SLACK_BOT_TOKEN, 11 | } = require('../constants'); 12 | const { getOctokit } = require('./octokit'); 13 | 14 | const slackWebClient = new WebClient(SLACK_BOT_TOKEN); 15 | 16 | const SEMVER_TYPE = { 17 | MAJOR: 'semver/major', 18 | MINOR: 'semver/minor', 19 | PATCH: 'semver/patch', 20 | }; 21 | 22 | const isInvalidBranch = (branches, branch) => { 23 | return !RELEASE_BRANCH_PATTERN.test(branch) || !branches.includes(branch); 24 | }; 25 | 26 | // Filter through commits in a given range and determine the overall semver type. 27 | async function getSemverForCommitRange(commits, branch) { 28 | let resultantSemver = SEMVER_TYPE.PATCH; 29 | const octokit = await getOctokit(); 30 | const allClosedPrs = await octokit.paginate(octokit.pulls.list, { 31 | owner: ORGANIZATION_NAME, 32 | repo: REPO_NAME, 33 | state: 'closed', 34 | base: branch, 35 | }); 36 | 37 | for (const commit of commits) { 38 | const prs = allClosedPrs.filter((pr) => pr.merge_commit_sha === commit.sha); 39 | if (prs.length > 0) { 40 | if (prs.length === 1) { 41 | const pr = prs[0]; 42 | const isMajor = pr.labels.some( 43 | (label) => label.name === SEMVER_TYPE.MAJOR, 44 | ); 45 | const isMinor = pr.labels.some( 46 | (label) => label.name === SEMVER_TYPE.MINOR, 47 | ); 48 | if (isMajor) { 49 | resultantSemver = SEMVER_TYPE.MAJOR; 50 | } else if (isMinor) { 51 | resultantSemver = SEMVER_TYPE.MINOR; 52 | } 53 | } else { 54 | throw new Error( 55 | `Invalid number of PRs associated with ${commit.sha}`, 56 | prs, 57 | ); 58 | } 59 | } 60 | } 61 | 62 | return resultantSemver; 63 | } 64 | 65 | // Add a live PR link to a given commit. 66 | function linkifyPRs(msg) { 67 | return msg.replace( 68 | /#(\d+)/g, 69 | (_, pr_id) => 70 | ``, 71 | ); 72 | } 73 | 74 | async function fetchInitiator(req) { 75 | const { profile } = await slackWebClient.users.profile.get({ 76 | user: req.body.user_id, 77 | }); 78 | 79 | return { 80 | id: req.body.user_id, 81 | name: profile.display_name_normalized, 82 | }; 83 | } 84 | 85 | // Determine whether a given release is in draft state or not. 86 | async function releaseIsDraft(tag) { 87 | const octokit = await getOctokit(); 88 | 89 | try { 90 | const { 91 | data: { draft }, 92 | } = await octokit.repos.getReleaseByTag({ 93 | owner: ORGANIZATION_NAME, 94 | repo: REPO_NAME, 95 | tag, 96 | }); 97 | return draft; 98 | } catch { 99 | return false; 100 | } 101 | } 102 | 103 | // Fetch an array of the currently supported branches. 104 | async function getSupportedBranches() { 105 | const octokit = await getOctokit(); 106 | 107 | const branches = await octokit.paginate( 108 | octokit.repos.listBranches.endpoint.merge({ 109 | owner: ORGANIZATION_NAME, 110 | repo: REPO_NAME, 111 | protected: true, 112 | }), 113 | ); 114 | 115 | const releaseBranches = branches.filter((branch) => { 116 | return branch.name.match(RELEASE_BRANCH_PATTERN); 117 | }); 118 | 119 | const filtered = {}; 120 | releaseBranches 121 | .sort((a, b) => { 122 | const aParts = a.name.split('-'); 123 | const bParts = b.name.split('-'); 124 | for (let i = 0; i < aParts.length; i++) { 125 | if (aParts[i] === bParts[i]) continue; 126 | return parseInt(aParts[i], 10) - parseInt(bParts[i], 10); 127 | } 128 | return 0; 129 | }) 130 | .forEach((branch) => { 131 | return (filtered[branch.name.split('-')[0]] = branch.name); 132 | }); 133 | 134 | const values = Object.values(filtered); 135 | return values 136 | .sort((a, b) => parseInt(a, 10) - parseInt(b, 10)) 137 | .slice(-NUM_SUPPORTED_VERSIONS); 138 | } 139 | 140 | // Post a message to a Slack workspace. 141 | const postToSlack = async (data, postUrl) => { 142 | await fetch(postUrl, { 143 | body: JSON.stringify(data), 144 | method: 'POST', 145 | headers: { 146 | 'content-type': 'application/json', 147 | }, 148 | }); 149 | }; 150 | 151 | // NOTE: This assumes `a` is a user-controlled string and 152 | // `b` is our sensitive value. This ensures that even in 153 | // length mismatch cases this function does a 154 | // crypto.timingSafeEqual comparison of `b.length`, so an 155 | // attacker can't change `a.length` to estimate `b.length` 156 | function timingSafeEqual(a, b) { 157 | const bufferA = Buffer.from(a, 'utf-8'); 158 | const bufferB = Buffer.from(b, 'utf-8'); 159 | 160 | if (bufferA.length !== bufferB.length) { 161 | crypto.timingSafeEqual(bufferB, bufferB); 162 | return false; 163 | } 164 | 165 | return crypto.timingSafeEqual(bufferA, bufferB); 166 | } 167 | 168 | module.exports = { 169 | fetchInitiator, 170 | getSemverForCommitRange, 171 | getSupportedBranches, 172 | isInvalidBranch, 173 | linkifyPRs, 174 | postToSlack, 175 | releaseIsDraft, 176 | SEMVER_TYPE, 177 | timingSafeEqual, 178 | }; 179 | -------------------------------------------------------------------------------- /utils/needs-manual-prs.js: -------------------------------------------------------------------------------- 1 | const { ORGANIZATION_NAME, REPO_NAME } = require('../constants'); 2 | const { getOctokit } = require('./octokit'); 3 | 4 | // Fetch issues matching the given search criteria. 5 | // e.g. 6 | // { 7 | // repo: 'electron/electron', 8 | // type: 'pr', 9 | // state: 'open', 10 | // label: 'needs-manual-bp/10-x-y' 11 | // } 12 | async function searchIssues(search) { 13 | const octokit = await getOctokit(); 14 | const { data } = await octokit.search.issuesAndPullRequests({ 15 | q: `${[...Object.entries(search)].map(([k, v]) => `${k}:${v}`).join('+')}`, 16 | }); 17 | return data.items; 18 | } 19 | 20 | // Fetch all PRs targeting 'main' that have a 'needs-manual/' label on them. 21 | async function fetchNeedsManualPRs(branch, prAuthor) { 22 | const search = { 23 | repo: `${ORGANIZATION_NAME}/${REPO_NAME}`, 24 | type: 'pr', 25 | state: 'closed', 26 | label: `"needs-manual-bp/${branch}"`, 27 | }; 28 | 29 | if (prAuthor) { 30 | search.author = prAuthor; 31 | } 32 | 33 | return await searchIssues(search); 34 | } 35 | 36 | // Build the text blob that will be posted to Slack. 37 | function buildNeedsManualPRsMessage(branch, prs, shouldRemind) { 38 | if (prs.length === 0) { 39 | return `*No PR(s) needing manual backport to ${branch}*`; 40 | } 41 | 42 | let formattedPRs = prs 43 | .map((c) => { 44 | let line = `* <${c.html_url}|#${c.number}> - ${ 45 | c.title.split(/[\r\n]/, 1)[0] 46 | }`; 47 | if (shouldRemind) line += ` (<@${c.user.login.toLowerCase()}>)`; 48 | return line; 49 | }) 50 | .join('\n'); 51 | 52 | formattedPRs += `\n *${prs.length} PR(s) needing manual backport to \`${branch}\`!*`; 53 | 54 | return formattedPRs; 55 | } 56 | 57 | module.exports = { 58 | buildNeedsManualPRsMessage, 59 | fetchNeedsManualPRs, 60 | }; 61 | -------------------------------------------------------------------------------- /utils/octokit.js: -------------------------------------------------------------------------------- 1 | const { 2 | appCredentialsFromString, 3 | getAuthOptionsForRepo, 4 | } = require('@electron/github-app-auth'); 5 | const { 6 | ORGANIZATION_NAME, 7 | REPO_NAME, 8 | UNRELEASED_GITHUB_APP_CREDS, 9 | } = require('../constants'); 10 | const { Octokit } = require('@octokit/rest'); 11 | 12 | let octokit; 13 | const getOctokit = async () => { 14 | if (octokit) return octokit; 15 | 16 | if (UNRELEASED_GITHUB_APP_CREDS) { 17 | const creds = appCredentialsFromString(UNRELEASED_GITHUB_APP_CREDS); 18 | const authOpts = await getAuthOptionsForRepo( 19 | { 20 | owner: ORGANIZATION_NAME, 21 | name: REPO_NAME, 22 | }, 23 | creds, 24 | ); 25 | octokit = new Octokit({ ...authOpts }); 26 | } else if (process.env.GITHUB_TOKEN) { 27 | octokit = new Octokit({ 28 | auth: process.env.GITHUB_TOKEN, 29 | }); 30 | } else { 31 | octokit = new Octokit(); 32 | } 33 | 34 | return octokit; 35 | }; 36 | 37 | module.exports = { getOctokit }; 38 | -------------------------------------------------------------------------------- /utils/unmerged-prs.js: -------------------------------------------------------------------------------- 1 | const { getOctokit } = require('./octokit'); 2 | 3 | const { 4 | BLOCKS_RELEASE_LABEL, 5 | ORGANIZATION_NAME, 6 | REPO_NAME, 7 | } = require('../constants'); 8 | 9 | const formatMessage = (pr) => { 10 | return `* <${pr.html_url}|#${pr.number}>${pr.draft ? ' (*DRAFT*)' : ''} - ${ 11 | pr.title.split(/[\r\n]/, 1)[0] 12 | }`; 13 | }; 14 | 15 | function getReleaseBlockers(prs) { 16 | return prs.filter((pr) => { 17 | return pr.labels.some((label) => label.name === BLOCKS_RELEASE_LABEL); 18 | }); 19 | } 20 | 21 | // Fetch all PRs targeting a specified release line branch that have NOT been merged. 22 | async function fetchUnmergedPRs(branch) { 23 | const octokit = await getOctokit(); 24 | return await octokit.paginate(octokit.pulls.list, { 25 | owner: ORGANIZATION_NAME, 26 | repo: REPO_NAME, 27 | base: branch, 28 | }); 29 | } 30 | 31 | // Build the text blob that will be posted to Slack. 32 | function buildUnmergedPRsMessage(branch, prs) { 33 | if (prs.length === 0) { 34 | return `*No unmerged PRs targeting \`${branch}\`!*`; 35 | } 36 | 37 | let message = prs.map(formatMessage).join('\n'); 38 | 39 | message += `\n *${prs.length} unmerged PR(s) targeting \`${branch}\`!*`; 40 | 41 | const releaseBlockers = getReleaseBlockers(prs); 42 | if (releaseBlockers.length > 0) { 43 | message += '\n\n'; 44 | message += releaseBlockers.map(formatMessage).join('\n'); 45 | message += `\n *${releaseBlockers.length} unmerged PR(s) blocking release of \`${branch}\`!*`; 46 | } 47 | 48 | return message; 49 | } 50 | 51 | module.exports = { 52 | buildUnmergedPRsMessage, 53 | fetchUnmergedPRs, 54 | }; 55 | -------------------------------------------------------------------------------- /utils/unreleased-commits.js: -------------------------------------------------------------------------------- 1 | const { linkifyPRs, releaseIsDraft } = require('./helpers'); 2 | const { getOctokit } = require('./octokit'); 3 | const { graphql } = require('@octokit/graphql'); 4 | const { 5 | appCredentialsFromString, 6 | getTokenForRepo, 7 | } = require('@electron/github-app-auth'); 8 | 9 | const { 10 | EXCLUDED_COMMIT_PATTERN, 11 | ORGANIZATION_NAME, 12 | REPO_NAME, 13 | UNRELEASED_GITHUB_APP_CREDS, 14 | } = require('../constants'); 15 | 16 | async function fetchTags() { 17 | let authorization; 18 | 19 | if (UNRELEASED_GITHUB_APP_CREDS) { 20 | const creds = appCredentialsFromString(UNRELEASED_GITHUB_APP_CREDS); 21 | authorization = `token ${await getTokenForRepo( 22 | { 23 | owner: ORGANIZATION_NAME, 24 | name: REPO_NAME, 25 | }, 26 | creds, 27 | )}`; 28 | } else if (process.env.GITHUB_TOKEN) { 29 | authorization = `token ${process.env.GITHUB_TOKEN}`; 30 | } 31 | 32 | return graphql({ 33 | query: `{ 34 | repository(owner: "electron", name: "electron") { 35 | refs(refPrefix: "refs/tags/", first: 100, orderBy: { field: TAG_COMMIT_DATE, direction: DESC }) { 36 | edges { 37 | node { 38 | name 39 | target { 40 | commitUrl 41 | } 42 | } 43 | } 44 | } 45 | } 46 | }`, 47 | headers: { 48 | authorization, 49 | }, 50 | }).then(({ repository }) => { 51 | return repository.refs.edges.map((edge) => { 52 | const url = edge.node.target.commitUrl.split('/'); 53 | return { 54 | name: edge.node.name, 55 | prerelease: edge.node.name.includes('-'), 56 | commit_sha: url[url.length - 1], 57 | }; 58 | }); 59 | }); 60 | } 61 | 62 | // Fetch all unreleased commits for a specified release line branch. 63 | async function fetchUnreleasedCommits(branch) { 64 | const tags = await fetchTags(); 65 | const unreleased = []; 66 | let lastTag = null; 67 | 68 | const octokit = await getOctokit(); 69 | await (async () => { 70 | for await (const response of octokit.paginate.iterator( 71 | octokit.repos.listCommits, 72 | { 73 | owner: ORGANIZATION_NAME, 74 | repo: REPO_NAME, 75 | sha: branch, 76 | per_page: 100, 77 | }, 78 | )) { 79 | let foundLastRelease = false; 80 | for (const payload of response.data) { 81 | const tag = tags.find((t) => t.commit_sha === payload.sha); 82 | if (tag) { 83 | const isDraft = await releaseIsDraft(tag.name); 84 | if (!isDraft) { 85 | foundLastRelease = true; 86 | lastTag = tag; 87 | break; 88 | } 89 | } 90 | 91 | // Filter out commits that aren't releasable on their own. 92 | if (EXCLUDED_COMMIT_PATTERN.test(payload.commit.message)) continue; 93 | 94 | unreleased.push(payload); 95 | } 96 | if (foundLastRelease) { 97 | break; 98 | } 99 | } 100 | })(); 101 | 102 | return { commits: unreleased, lastTag }; 103 | } 104 | 105 | // Build the text blob that will be posted to Slack. 106 | function buildUnreleasedCommitsMessage(branch, commits, initiator) { 107 | if (!commits || commits.length === 0) 108 | return `*No unreleased commits on ${branch}*`; 109 | 110 | const formattedCommits = commits 111 | .map((c) => { 112 | const prLink = linkifyPRs(c.commit.message.split(/[\r\n]/, 1)[0]); 113 | return `* \`<${c.html_url}|${c.sha.slice(0, 8)}>\` ${prLink}`; 114 | }) 115 | .join('\n'); 116 | 117 | // For unreleased commits only, we don't want to deduce slack user for auto-audit. 118 | const from = initiator === 'automatic audit' ? initiator : `<@${initiator}>`; 119 | let response = `Unreleased commits in *${branch}* (from ${from}):\n${formattedCommits}`; 120 | if (commits.length >= 10) { 121 | response += `\n *There are a lot of unreleased commits on \`${branch}\`! Time for a release?*`; 122 | } 123 | return response; 124 | } 125 | 126 | module.exports = { 127 | buildUnreleasedCommitsMessage, 128 | fetchUnreleasedCommits, 129 | }; 130 | --------------------------------------------------------------------------------