# This workflow is used to release a new version of the package.
# It can be triggered via:
# - workflow_call: Called by push.yml for feature branch releases (dev)
# - workflow_dispatch: Manual trigger for production releases
# - push to main: Automatic production releases (commented out by default)
# Reference: https://semantic-release.gitbook.io/semantic-release/recipes/ci-configurations/github-actions
name: Semantic Release
on:
# Uncomment to enable automatic releases on push to main
# push:
# branches:
# - main
workflow_call:
inputs:
is-dev-release:
description: 'Whether this is a dev/pre-release (uses dev config)'
required: false
type: boolean
default: false
skip-checks:
description: 'Skip quality checks (when caller already ran them)'
required: false
type: boolean
default: false
outputs:
new-release-published:
description: 'Whether a new release was published'
value: ${{ jobs.release.outputs.new-release-published }}
new-release-version:
description: 'The version of the new release'
value: ${{ jobs.release.outputs.new-release-version }}
workflow_dispatch:
inputs:
is-dev-release:
description: 'Whether this is a dev/pre-release'
required: false
type: boolean
default: false
# Do not set concurrency group since this workflow is called by other workflows. Otherwise, deadlock will occur.
permissions:
contents: write # Required: create tags, GitHub releases, and commit changes (git plugin)
env:
BUN_VERSION: '1.3.8'
NODE_VERSION: '22'
jobs:
release:
name: Semantic Release${{ inputs.is-dev-release && ' - Dev' || '' }}
runs-on: ubuntu-latest
env:
HUSKY: 0
timeout-minutes: 15
outputs:
new-release-published: ${{ steps.semantic-release.outputs.new-release-published }}
new-release-version: ${{ steps.semantic-release.outputs.new-release-version }}
steps:
- name: Checkout source code
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_TOKEN }}
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Setup Node.js for semantic-release
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Run typecheck
if: ${{ !inputs.skip-checks }}
run: bun run typecheck
- name: Run lint
if: ${{ !inputs.skip-checks }}
run: bun run lint
- name: Run format
if: ${{ !inputs.skip-checks }}
run: bun run format
- name: Run test
if: ${{ !inputs.skip-checks }}
run: bun test
- name: Build project
run: bun run build
- name: Install semantic-release packages
run: |
npm install --no-save --no-package-lock \
semantic-release@^25.0.3 \
@semantic-release/changelog@^6.0.3 \
@semantic-release/commit-analyzer@^13.0.1 \
@semantic-release/git@^10.0.1 \
@semantic-release/github@^12.0.3 \
@semantic-release/npm@^13.1.3 \
@semantic-release/release-notes-generator@^14.1.0 \
@semantic-release/exec@^7.1.0
- name: Semantic Release
id: semantic-release
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
# For dev releases, swap config files since semantic-release doesn't support specifying config path via CLI
if [ "${{ inputs.is-dev-release }}" == "true" ]; then
echo "Running semantic-release with dev config..."
mv release.config.mjs release.config.mjs.backup
mv release.config.dev.mjs release.config.mjs
else
echo "Running semantic-release with production config..."
fi
npx semantic-release
# Restore config files if dev release
if [ "${{ inputs.is-dev-release }}" == "true" ]; then
mv release.config.mjs release.config.dev.mjs
mv release.config.mjs.backup release.config.mjs
fi
# Check if release output files created by @semantic-release/exec
if [ -f "RELEASE_VERSION" ]; then
VERSION=$(cat RELEASE_VERSION)
echo "New release published: v$VERSION"
echo "new-release-published=true" >> $GITHUB_OUTPUT
echo "new-release-version=$VERSION" >> $GITHUB_OUTPUT
else
echo "No new release published"
echo "new-release-published=false" >> $GITHUB_OUTPUT
fi
# Call Docker workflow after successful release
docker:
needs: release
if: needs.release.outputs.new-release-published == 'true'
uses: ./.github/workflows/docker-build.yml
with:
tag-name: 'v${{ needs.release.outputs.new-release-version }}'
is-dev-release: ${{ inputs.is-dev-release || false }}
secrets: inherit