Release #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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' }} |