Skip to content
78 changes: 78 additions & 0 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Prepare Release

on:
workflow_dispatch:
inputs:
version:
description: "Version to release (e.g. 1.2.3)"
required: true
type: string
base_branch:
description: "Base branch for release (e.g. release/v1.x, hotfix/v1.2.x)"
required: false
default: "main"
type: string

jobs:
prepare-release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.base_branch }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Create draft release
run: gh release create v${{ inputs.version }} --draft --generate-notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create release branch
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git checkout -b "release/prep-release-${{ inputs.version }}"

- name: Set version and run build
run: |
npm run setversion ${{ inputs.version }}

- name: Commit version changes
run: |
git add .
git commit -s -m "Release ${{ inputs.version }}"
git push --set-upstream origin "release/prep-release-${{ inputs.version }}"

- name: Get release notes
id: release_notes
run: |
RELEASE_NOTES=$(gh release view v${{ inputs.version }} --json body | jq -r ".body")
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create pull request
run: |
gh pr create \
--title "Release ${{ inputs.version }}" \
--body "${{ steps.release_notes.outputs.notes }}" \
--base "${{ inputs.base_branch }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
105 changes: 105 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: Publish Release

on:
pull_request:
types: [closed]
branches:
- main
- "release/**"
- "hotfix/**"

jobs:
publish-release:
runs-on: ubuntu-latest
# Only run if PR was merged and branch name starts with release/prep-release-
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/prep-release-')
permissions:
id-token: write # Required for OIDC
contents: write
pull-requests: write
issues: write

steps:
- name: Extract version from branch name
id: extract_version
run: |
BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
VERSION=$(echo "$BRANCH_NAME" | sed 's/release\/prep-release-//')
echo "version=$VERSION" >> $GITHUB_OUTPUT

- name: Checkout base branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Get updated release notes from PR
id: pr_notes
run: |
RELEASE_NOTES=$(gh pr view ${{ github.event.pull_request.number }} --json body | jq -r ".body")
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish to npm
run: npm run release

- name: Publish GitHub release
run: |
gh release edit v${{ steps.extract_version.outputs.version }} \
--notes "${{ steps.pr_notes.outputs.notes }}" \
--draft=false
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
cleanup-canceled-release:
runs-on: ubuntu-latest
# Only run if PR was closed without merge and branch name starts with release/prep-release-
if: github.event.pull_request.merged == false && startsWith(github.event.pull_request.head.ref, 'release/prep-release-')
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout base branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from branch name
id: extract_version
run: |
BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
VERSION=$(echo "$BRANCH_NAME" | sed 's/release\/prep-release-//')
echo "version=$VERSION" >> $GITHUB_OUTPUT

- name: Delete draft release
run: |
if gh release view v${{ steps.extract_version.outputs.version }} --json isDraft | jq -r ".isDraft" | grep -q "true"; then
echo "Deleting draft release v${{ steps.extract_version.outputs.version }}"
gh release delete v${{ steps.extract_version.outputs.version }} --yes
else
echo "Release v${{ steps.extract_version.outputs.version }} is not a draft, skipping deletion"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Delete release branch
continue-on-error: true
run: |
echo "Deleting release branch ${{ github.event.pull_request.head.ref }}"
git push origin --delete ${{ github.event.pull_request.head.ref }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 changes: 11 additions & 1 deletion scripts/set-workspace-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
import { readFileSync, writeFileSync, existsSync, globSync } from "node:fs";
import { dirname, join } from "node:path";

if (process.argv.length !== 3 || !/^\d+\.\d+\.\d+$/.test(process.argv[2])) {
// Ensures that a valid semver version is provided
// See https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
const versionRegex =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
if (process.argv.length !== 3 || !versionRegex.test(process.argv[2])) {
process.stderr.write(
[
`USAGE: ${process.argv[1]} <new-version>`,
Expand All @@ -28,6 +32,12 @@ if (process.argv.length !== 3 || !/^\d+\.\d+\.\d+$/.test(process.argv[2])) {
"If a package depends on another package from the workspace, the",
"dependency version is updated as well.",
"",
...(versionRegex.test(process.argv[2])
? []
: [
"Version provided is not a valid semver version.",
"Please provide a version in the format MAJOR.MINOR.PATCH[-PRERELEASE+BUILD].",
]),
].join("\n"),
);
process.exit(1);
Expand Down