Skip to content

Release

Release #20

Workflow file for this run

name: Release
on:
# Only trigger releases manually or on version tags
workflow_dispatch:
inputs:
release-type:
description: "Release type"
required: true
default: "patch"
type: choice
options: [patch, minor, major, prerelease]
push:
tags:
- "v*.*.*"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write # create commits / tags
id-token: write # provenance for npm publish
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Validate native binaries
run: bun run validate-binaries
- name: Build TypeScript
run: bun run build:ts
- name: Verify package can be built
run: npm pack --dry-run
# ============ VERSION MANAGEMENT ============
- name: Check if triggered by tag
id: tag-check
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
TAG_VERSION=${GITHUB_REF#refs/tags/v}
echo "is_tag=true" >> $GITHUB_OUTPUT
echo "tag_version=$TAG_VERSION" >> $GITHUB_OUTPUT
echo "🏷️ Triggered by tag: v$TAG_VERSION"
else
echo "is_tag=false" >> $GITHUB_OUTPUT
echo "🚀 Triggered by manual dispatch"
fi
- name: Determine version bump (manual dispatch only)
id: version-bump
if: steps.tag-check.outputs.is_tag == 'false'
run: |
CURRENT=$(node -p "require('./package.json').version")
echo "current_version=$CURRENT" >> $GITHUB_OUTPUT
# Use the input release type for manual dispatch
BUMP_TYPE="${{ github.event.inputs.release-type }}"
echo "bump_type=$BUMP_TYPE" >> $GITHUB_OUTPUT
# Calculate new version
if [ "$BUMP_TYPE" = "major" ]; then
NEW=$(node -p "require('semver').inc('$CURRENT', 'major')" 2>/dev/null || echo "")
elif [ "$BUMP_TYPE" = "minor" ]; then
NEW=$(node -p "require('semver').inc('$CURRENT', 'minor')" 2>/dev/null || echo "")
elif [ "$BUMP_TYPE" = "prerelease" ]; then
NEW=$(node -p "require('semver').inc('$CURRENT', 'prerelease')" 2>/dev/null || echo "")
else
NEW=$(node -p "require('semver').inc('$CURRENT', 'patch')" 2>/dev/null || echo "")
fi
# Fallback if semver not available
if [ -z "$NEW" ]; then
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}
if [ "$BUMP_TYPE" = "major" ]; then
NEW="$((MAJOR + 1)).0.0"
elif [ "$BUMP_TYPE" = "minor" ]; then
NEW="$MAJOR.$((MINOR + 1)).0"
else
NEW="$MAJOR.$MINOR.$((PATCH + 1))"
fi
fi
echo "new_version=$NEW" >> $GITHUB_OUTPUT
echo "tag=v$NEW" >> $GITHUB_OUTPUT
echo "📈 Version bump: $CURRENT → $NEW ($BUMP_TYPE)"
- name: Update package.json version (manual dispatch only)
if: steps.tag-check.outputs.is_tag == 'false'
run: |
NEW_VERSION="${{ steps.version-bump.outputs.new_version }}"
node -e "
const pkg = require('./package.json');
pkg.version = '$NEW_VERSION';
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
"
echo "✅ Updated package.json to version $NEW_VERSION"
# ============ CHANGELOG GENERATION ============
- name: Generate changelog entry
id: changelog
run: |
if [[ "${{ steps.tag-check.outputs.is_tag }}" == "true" ]]; then
VERSION="${{ steps.tag-check.outputs.tag_version }}"
else
VERSION="${{ steps.version-bump.outputs.new_version }}"
fi
DATE=$(date +%Y-%m-%d)
# Get commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
COMMITS=$(git log ${LAST_TAG}..HEAD --oneline --pretty=format:"- %s" | head -20)
else
COMMITS=$(git log --oneline --pretty=format:"- %s" | head -10)
fi
# Create changelog entry
echo "## [$VERSION] - $DATE" > /tmp/changelog_entry.md
echo "" >> /tmp/changelog_entry.md
if [ -n "$COMMITS" ]; then
echo "### Changes" >> /tmp/changelog_entry.md
echo "$COMMITS" >> /tmp/changelog_entry.md
else
echo "### Changes" >> /tmp/changelog_entry.md
echo "- Release $VERSION" >> /tmp/changelog_entry.md
fi
echo "" >> /tmp/changelog_entry.md
# Update CHANGELOG.md
if [[ ! -f CHANGELOG.md ]]; then
echo "# Changelog" > CHANGELOG.md
echo "" >> CHANGELOG.md
fi
# Prepend new entry
cat /tmp/changelog_entry.md CHANGELOG.md > /tmp/new_changelog.md
mv /tmp/new_changelog.md CHANGELOG.md
echo "changelog_updated=true" >> $GITHUB_OUTPUT
echo "✅ Updated CHANGELOG.md with version $VERSION"
# ============ COMMIT AND TAG (manual dispatch only) ============
- name: Commit version bump and changelog (manual dispatch only)
if: steps.tag-check.outputs.is_tag == 'false'
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git add package.json CHANGELOG.md
git commit -m "chore(release): ${{ steps.version-bump.outputs.new_version }}"
git tag ${{ steps.version-bump.outputs.tag }}
- name: Push changes (manual dispatch only)
if: steps.tag-check.outputs.is_tag == 'false'
run: |
git push origin main
git push origin ${{ steps.version-bump.outputs.tag }}
# ============ PUBLISH TO NPM ============
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
if [[ "${{ steps.tag-check.outputs.is_tag }}" == "true" ]]; then
VERSION="${{ steps.tag-check.outputs.tag_version }}"
else
VERSION="${{ steps.version-bump.outputs.new_version }}"
fi
echo "🚀 Publishing version $VERSION to npm..."
npm publish --access public --provenance
echo "✅ Successfully published $VERSION to npm"
# ============ CREATE GITHUB RELEASE ============
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.tag-check.outputs.is_tag == 'true' && github.ref_name || steps.version-bump.outputs.tag }}
name: ${{ steps.tag-check.outputs.is_tag == 'true' && github.ref_name || steps.version-bump.outputs.tag }}
body_path: /tmp/changelog_entry.md
draft: false
prerelease: ${{ contains(github.ref_name, 'pre') || github.event.inputs.release-type == 'prerelease' }}