Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,7 @@ jobs:
- name: Release notes
env:
GH_TOKEN: ${{ steps.octo-sts.outputs.token }}
VERSION: ${{ fromJson(steps.pkg.outputs.json).version }}
run: |
# The release notes are the body of the proposal PR, looked up by its
# head branch. This resolves even after the branch is deleted on merge.
notes="$RUNNER_TEMP/v$VERSION.md"
gh pr view "v$VERSION-proposal" --json body --jq .body > "$notes"
# v5.x is currently the only release line. Once a second one ships,
# --latest has to be decided per branch rather than hardcoded.
if gh release view "v$VERSION" > /dev/null 2>&1; then
gh release edit "v$VERSION" --title "$VERSION" -F "$notes" --latest
else
gh release create "v$VERSION" --target "${{ github.ref_name }}" --title "$VERSION" -F "$notes" --latest
fi
run: node scripts/release-notes.js

publish_dev:
needs: build
Expand Down
99 changes: 99 additions & 0 deletions scripts/release-notes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use strict'

// Usage: node scripts/release-notes.js [version] [--dry-run]
//
// Publishes the release proposal PR's body as the GitHub release notes for
// that version, expanding bare pull request references into their titles.
//
// The proposal PR is resolved by its head branch, which keeps working after
// the branch is deleted on merge. `--latest` is hardcoded because v5.x is the
// only release line; once a second one ships it has to be decided per branch.

const { execFileSync } = require('child_process')
const { writeFileSync } = require('fs')
const { join } = require('path')
const { tmpdir } = require('os')

const owner = 'DataDog'
const name = 'pprof-nodejs'
const repo = `${owner}/${name}`

const args = process.argv.slice(2)
const dryRun = args.includes('--dry-run')
const version = args.find(arg => !arg.startsWith('--')) || require('../package.json').version
const tag = `v${version}`

function gh (params) {
return execFileSync('gh', params, { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }).trim()
}

function ghSucceeds (params) {
try {
gh(params)
return true
} catch {
return false
}
}

function readTitles (numbers) {
const titles = new Map()
if (numbers.length === 0) return titles

const fields = numbers
.map(number => `pr${number}: pullRequest(number: ${number}) { number title }`)
.join(' ')
const response = JSON.parse(gh([
'api', 'graphql', '-f',
`query=query { repository(owner: "${owner}", name: "${name}") { ${fields} } }`
]))

if (response.errors && response.errors.length > 0) {
throw new Error(`Pull request title lookup failed: ${response.errors[0].message}`)
}

for (const pullRequest of Object.values(response.data.repository)) {
if (pullRequest) titles.set(String(pullRequest.number), pullRequest.title)
}

return titles
}

// Only a list item that is nothing but a reference is expanded, so bodies that
// already spell out their titles are left exactly as the author wrote them.
const bareReference = /^([ \t]*[-*][ \t]+)#(\d+)[ \t]*$/gm

const body = gh(['pr', 'view', `${tag}-proposal`, '--repo', repo, '--json', 'body', '--jq', '.body'])
.replace(/\r\n/g, '\n')
const numbers = [...new Set([...body.matchAll(bareReference)].map(match => match[2]))]
const titles = readTitles(numbers)
const notes = body.replace(bareReference, (line, bullet, number) => {
const title = titles.get(number)
return title ? `${bullet}${title} (#${number})` : line
})

if (dryRun) {
console.log(notes)
process.exit(0)
}

const file = join(process.env.RUNNER_TEMP || tmpdir(), `${tag}.md`)
writeFileSync(file, notes)

const exists = ghSucceeds(['release', 'view', tag, '--repo', repo, '--json', 'tagName'])
const prerelease = version.includes('-')

// --verify-tag keeps a lagging tag push from silently creating the tag on the
// default branch instead of on the release commit.
const params = [
'release', exists ? 'edit' : 'create', tag,
'--repo', repo,
'--verify-tag',
'--title', version,
'--notes-file', file,
prerelease ? '--latest=false' : '--latest'
]

if (prerelease) params.push('--prerelease')

console.log(gh(params))
Loading