Skip to content

Release

Release #13

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
ref:
description: 'Commit ref or existing version tag (e.g., abc1234 or v1.2.3)'
required: true
type: string
new_version:
description: 'New version for npm version (e.g., patch, minor, major, 1.2.3). Must be empty when providing an existing tag.'
required: false
type: string
skip_ci_check:
description: 'Skip CI status check'
required: false
type: boolean
default: false
jobs:
preflight:
name: Validate
runs-on: ubuntu-slim
permissions:
actions: read
contents: read
outputs:
ref_is_tag: ${{steps.validation.outputs.ref_is_tag}}
steps:
- name: Validate inputs
id: validation
env:
REF: ${{inputs.ref}}
NEW_VERSION: ${{inputs.new_version}}
run: |
if [[ "$REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "ref_is_tag=true" >> "$GITHUB_OUTPUT"
if [[ -n "$NEW_VERSION" ]]; then
echo "::error::new_version must be empty when an existing tag is provided"
exit 1
fi
else
echo "ref_is_tag=false" >> "$GITHUB_OUTPUT"
if [[ -z "$NEW_VERSION" ]]; then
echo "::error::new_version is required when a commit ref is provided"
exit 1
fi
fi
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{inputs.ref}}
fetch-depth: 0
- name: Verify ref is HEAD of main
if: steps.validation.outputs.ref_is_tag == 'false'
env:
REF: ${{inputs.ref}}
run: |
MAIN_SHA=$(git rev-parse origin/main)
CURRENT_SHA=$(git rev-parse HEAD)
if [[ "$CURRENT_SHA" != "$MAIN_SHA" ]]; then
echo "::error::ref ${REF} (${CURRENT_SHA}) is not the HEAD of main (${MAIN_SHA})"
exit 1
fi
- name: Verify tag matches package.json version
if: steps.validation.outputs.ref_is_tag == 'true'
env:
REF: ${{inputs.ref}}
run: |
jq --raw-output --exit-status --arg tag "$REF" '
if (.version == ($tag | ltrimstr("v"))) then
"Package version (\(.version)) matches tag version (\($tag | ltrimstr(\"v\")))"
else
"Package version (\(.version)) does not match tag version (\($tag | ltrimstr(\"v\")))" | halt_error(1)
end' package.json
- name: Check CI status
if: '!inputs.skip_ci_check'
run: |
gh run list --commit "$(git rev-parse HEAD)" --workflow ci.yml --status success --json databaseId \
| jq --raw-output --exit-status '
if (length > 0) then
"CI checks have passed"
else
"CI has not completed successfully for this commit" | halt_error(1)
end'
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
release:
name: Release
runs-on: ubuntu-latest
needs: preflight
environment: npm
permissions:
contents: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{inputs.ref}}
fetch-depth: 0
- name: Verify ref is still HEAD of main
if: needs.preflight.outputs.ref_is_tag == 'false'
env:
REF: ${{inputs.ref}}
run: |
MAIN_SHA=$(git rev-parse origin/main)
CURRENT_SHA=$(git rev-parse HEAD)
if [[ "$CURRENT_SHA" != "$MAIN_SHA" ]]; then
echo "::error::ref ${REF} (${CURRENT_SHA}) is no longer the HEAD of main (${MAIN_SHA})"
exit 1
fi
- name: Generate app token
if: needs.preflight.outputs.ref_is_tag == 'false'
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{vars.LAUNCHBOT_ID}}
private-key: ${{secrets.LAUNCHBOT_PRIVATE_KEY}}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: npm
registry-url: https://registry.npmjs.org
- name: Bump version
if: needs.preflight.outputs.ref_is_tag == 'false'
env:
NEW_VERSION: ${{inputs.new_version}}
run: npm version "$NEW_VERSION" --no-git-tag-version
- name: Push version commit
if: needs.preflight.outputs.ref_is_tag == 'false'
id: push-commit
env:
GH_TOKEN: ${{steps.app-token.outputs.token}}
run: |
RELEASE_TAG="v$(jq --raw-output .version package.json)"
PARENT_SHA=$(git rev-parse HEAD)
jq --null-input \
--arg repo "$GITHUB_REPOSITORY" \
--arg parentSha "$PARENT_SHA" \
--arg headline "$RELEASE_TAG" \
--rawfile pkgContent package.json \
--rawfile lockContent package-lock.json \
'{
query: "mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid } } }",
variables: {
input: {
branch: { repositoryNameWithOwner: $repo, branchName: "main" },
message: { headline: $headline },
expectedHeadOid: $parentSha,
fileChanges: {
additions: [
{ path: "package.json", contents: ($pkgContent | @base64) },
{ path: "package-lock.json", contents: ($lockContent | @base64) }
]
}
}
}
}' > /tmp/request.json
COMMIT_SHA=$(gh api graphql --input /tmp/request.json \
--jq '.data.createCommitOnBranch.commit.oid')
echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
echo "version_commit_sha=$COMMIT_SHA" >> "$GITHUB_OUTPUT"
- name: Create version tag
if: needs.preflight.outputs.ref_is_tag == 'false'
run: |
gh api "repos/$GITHUB_REPOSITORY/git/refs" \
-f ref="refs/tags/${{steps.push-commit.outputs.release_tag}}" \
-f sha="${{steps.push-commit.outputs.version_commit_sha}}"
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Publish to npm with provenance
run: npm publish --provenance
- name: Create GitHub Release
run: |
RELEASE_TAG="v$(jq --raw-output .version package.json)"
gh release create "$RELEASE_TAG" \
--title "$RELEASE_TAG" \
--draft \
--generate-notes
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}