Add draft-release:pr script to create release PRs

This commit is contained in:
Sergio Padrino 2022-06-08 15:47:13 +02:00
parent 0875026c1b
commit fe093ae481
5 changed files with 180 additions and 63 deletions

View file

@ -36,6 +36,7 @@
"rebuild-hard:prod": "yarn clean-slate && yarn build:prod",
"draft-release": "ts-node -P script/tsconfig.json script/draft-release/index.ts",
"draft-release:format": "prettier --check --write changelog.json app/package.json && yarn validate-changelog",
"draft-release:pr": "ts-node -P script/tsconfig.json script/draft-release/draft-pull-request.ts",
"validate-changelog": "ts-node -P script/tsconfig.json script/validate-changelog.ts"
},
"author": {

View file

@ -1,62 +0,0 @@
import * as HTTPS from 'https'
export interface IAPIPR {
readonly title: string
readonly body: string
readonly headRefName: string
}
type GraphQLResponse = {
readonly data: {
readonly repository: {
readonly pullRequest: IAPIPR
}
}
}
export function fetchPR(id: number): Promise<IAPIPR | null> {
return new Promise((resolve, reject) => {
const options: HTTPS.RequestOptions = {
host: 'api.github.com',
protocol: 'https:',
path: '/graphql',
method: 'POST',
headers: {
Authorization: `bearer ${process.env.GITHUB_ACCESS_TOKEN}`,
'User-Agent': 'what-the-changelog',
},
}
const request = HTTPS.request(options, response => {
let received = ''
response.on('data', chunk => {
received += chunk
})
response.on('end', () => {
try {
const json: GraphQLResponse = JSON.parse(received)
const pr = json.data.repository.pullRequest
resolve(pr)
} catch (e) {
resolve(null)
}
})
})
const graphql = `
{
repository(owner: "desktop", name: "desktop") {
pullRequest(number: ${id}) {
title
body
headRefName
}
}
}
`
request.write(JSON.stringify({ query: graphql }))
request.end()
})
}

View file

@ -2,7 +2,7 @@ import * as Path from 'path'
import * as Fs from 'fs'
import { gt as greaterThan } from 'semver'
import { fetchPR, IAPIPR } from './api'
import { fetchPR, IAPIPR } from '../pr-api'
const PlaceholderChangeType = '???'
const OfficialOwner = 'desktop'

View file

@ -0,0 +1,61 @@
/// <reference path="../globals.d.ts" />
import appPackage from '../../app/package.json'
import { createPR } from '../pr-api'
const numberToOrdinal = (n: number) => {
const s = ['th', 'st', 'nd', 'rd']
const v = n % 100
return n + (s[(v - 20) % 10] || s[v] || s[0])
}
function getPullRequestBody(fullVersion: string): string {
const versionComponents = fullVersion.split('-')
const version = versionComponents[0]
let releaseDescription = `v${version} production release`
if (versionComponents.length > 1) {
const channelVersion = versionComponents[1]
if (!channelVersion.startsWith('beta')) {
throw new Error('We should not create release PRs for test builds')
}
const buildNumber = parseInt(channelVersion.substring('beta'.length))
releaseDescription = `${numberToOrdinal(
buildNumber
)} beta of the v${version} series`
}
return `## Description
Looking for the PR for the upcoming ${releaseDescription}? Well, you've just found it, congratulations!
## Release checklist
- [ ] Check to see if there are any errors in Sentry that have only occurred since the last production release
- [ ] Verify that all feature flags are flipped appropriately
- [ ] If there are any new metrics, ensure that central and desktop.github.com have been updated
`
}
function getPullRequestTitle(fullVersion: string): string {
return `Release ${fullVersion}`
}
process.on('unhandledRejection', error => {
console.error(error.message)
})
async function run() {
const version = appPackage.version
console.log(`Creating release Pull Request for ${version}...`)
const title = getPullRequestTitle(version)
const body = getPullRequestBody(version)
const response = await createPR(title, body, `releases/${version}`)
if (response === null) {
console.error('Failed to create release Pull Request')
process.exit(1)
}
console.log(`Done: ${response.permalink}`)
}
run()

117
script/pr-api.ts Normal file
View file

@ -0,0 +1,117 @@
import * as HTTPS from 'https'
export interface IAPIPR {
readonly title: string
readonly body: string
readonly headRefName: string
readonly permalink: string
}
type GraphQLResponse = {
readonly data: {
readonly repository: {
readonly pullRequest: IAPIPR
}
}
}
function gitHubRequest(
options: HTTPS.RequestOptions,
body: Record<string, any>
): Promise<Record<string, any> | null> {
const opts: HTTPS.RequestOptions = {
host: 'api.github.com',
protocol: 'https:',
method: 'POST',
headers: {
Authorization: `bearer ${process.env.GITHUB_ACCESS_TOKEN}`,
'User-Agent': 'what-the-changelog',
},
...options,
}
return new Promise((resolve, reject) => {
const request = HTTPS.request(opts, response => {
let received = ''
response.on('data', chunk => {
received += chunk
})
response.on('end', () => {
try {
resolve(JSON.parse(received))
} catch (e) {
resolve(null)
}
})
})
request.write(JSON.stringify(body))
request.end()
})
}
export async function fetchPR(id: number): Promise<IAPIPR | null> {
const options: HTTPS.RequestOptions = {
path: '/graphql',
}
const graphql = `
{
repository(owner: "desktop", name: "desktop") {
pullRequest(number: ${id}) {
title
body
headRefName
permalink
}
}
}
`
const body = { query: graphql }
const response = await gitHubRequest(options, body)
if (response === null) {
return null
}
try {
const json: GraphQLResponse = response as GraphQLResponse
return json.data.repository.pullRequest
} catch (e) {
return null
}
}
export async function createPR(
title: string,
body: string,
branch: string
): Promise<IAPIPR | null> {
const options: HTTPS.RequestOptions = {
path: '/repos/sergiou87/desktop/pulls',
}
const response = await gitHubRequest(options, {
title,
body,
base: 'development',
head: branch,
})
if (response === null) {
return null
}
try {
return {
title: response.title,
body: response.body,
headRefName: response.head.ref,
permalink: response.html_url,
}
} catch (e) {
return null
}
}