fix(ci): release trigger regex not matching conventional commit bang … #45
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: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| release-type: | |
| description: "Type of release" | |
| required: true | |
| default: "auto" | |
| type: choice | |
| options: | |
| - auto | |
| - patch | |
| - minor | |
| - major | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| # Pre-release validation | |
| validate: | |
| name: Pre-release Validation | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| outputs: | |
| should-release: ${{ steps.check-changes.outputs.should-release }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Check for release trigger | |
| id: check-changes | |
| run: | | |
| # Always release on manual trigger | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "should-release=true" >> $GITHUB_OUTPUT | |
| echo "✅ Manual release triggered" | |
| else | |
| # For push events, check if there are releasable changes | |
| last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| if [ -n "$last_tag" ]; then | |
| commits=$(git log $last_tag..HEAD --pretty=format:"%s") | |
| else | |
| commits=$(git log --pretty=format:"%s") | |
| fi | |
| # Check for releasable commits (skip version bump commits and minor changes) | |
| releasable_commits=$(echo "$commits" | grep -v "chore: bump version\|\\[skip ci\\]\|^docs:\|^style:\|^test:" || true) | |
| # Only release if there are significant changes (fixes, features, refactors, etc.) | |
| significant_commits=$(echo "$releasable_commits" | grep -E "^(feat|fix|refactor|perf|chore)(\([^)]+\))?!?:" || true) | |
| if [ -n "$significant_commits" ]; then | |
| echo "should-release=true" >> $GITHUB_OUTPUT | |
| echo "✅ Found significant changes since last release" | |
| echo "📝 Significant changes:" | |
| echo "$significant_commits" | |
| else | |
| echo "should-release=false" >> $GITHUB_OUTPUT | |
| echo "ℹ️ No significant changes found - skipping release" | |
| if [ -n "$releasable_commits" ]; then | |
| echo "📝 Non-significant changes found:" | |
| echo "$releasable_commits" | |
| fi | |
| fi | |
| fi | |
| - name: Build packages | |
| run: npm run build | |
| - name: Run tests | |
| run: npm test | |
| timeout-minutes: 10 | |
| - name: Verify package can be packed | |
| run: npm pack --dry-run | |
| # Release job - only runs if validation passes and changes detected | |
| release: | |
| name: Release | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| if: needs.validate.outputs.should-release == 'true' | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| id-token: write | |
| packages: write | |
| actions: write | |
| outputs: | |
| published: ${{ steps.publish.outputs.published }} | |
| version: ${{ steps.get-version.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: "npm" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build package | |
| run: npm run build | |
| - name: Determine version bump type | |
| id: version-bump | |
| run: | | |
| # Get commits since last tag (or all commits if no tags) | |
| last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| if [ -n "$last_tag" ]; then | |
| commits=$(git log $last_tag..HEAD --pretty=format:"%s") | |
| echo "📝 Commits since $last_tag:" | |
| else | |
| commits=$(git log --pretty=format:"%s") | |
| echo "📝 All commits (no previous tags):" | |
| fi | |
| echo "$commits" | |
| # Determine bump type based on conventional commits (more conservative) | |
| bump_type="patch" # Default to patch for most changes | |
| if echo "$commits" | grep -q "BREAKING CHANGE\|!:"; then | |
| bump_type="major" | |
| echo "🔥 Found breaking changes → major bump" | |
| elif echo "$commits" | grep -qE "^feat(\([^)]+\))?!:|^feat(\([^)]+\))?: .*(BREAKING|breaking)"; then | |
| bump_type="major" | |
| echo "🔥 Found breaking feature → major bump" | |
| elif echo "$commits" | grep -qE "^feat(\([^)]+\))?: .*\b(new|add|introduce)\b.*\b(API|api|method|function|interface|endpoint)\b"; then | |
| bump_type="minor" | |
| echo "✨ Found new API features → minor bump" | |
| elif echo "$commits" | grep -q "^fix\|^chore\|^docs\|^style\|^refactor\|^perf\|^test\|^feat"; then | |
| bump_type="patch" | |
| echo "🐛 Found fixes/improvements/features → patch bump (conservative)" | |
| else | |
| bump_type="patch" | |
| echo "📦 No conventional commits found → patch bump" | |
| fi | |
| # Override with manual input if provided | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ github.event.inputs.release-type }}" != "auto" ]; then | |
| bump_type="${{ github.event.inputs.release-type }}" | |
| echo "🔧 Manual override: using $bump_type" | |
| fi | |
| echo "bump-type=$bump_type" >> $GITHUB_OUTPUT | |
| echo "📈 Final bump type: $bump_type" | |
| - name: Bump version automatically | |
| id: bump-version | |
| run: | | |
| # Configure git | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Get current version | |
| current_version=$(node -p "require('./package.json').version") | |
| echo "📦 Current version: $current_version" | |
| # Bump version | |
| bump_type="${{ steps.version-bump.outputs.bump-type }}" | |
| npm version $bump_type --no-git-tag-version | |
| # Get new version | |
| new_version=$(node -p "require('./package.json').version") | |
| echo "📦 New version: $new_version" | |
| echo "new-version=$new_version" >> $GITHUB_OUTPUT | |
| # Commit version change | |
| git add package.json package-lock.json | |
| git commit -m "chore: bump version to $new_version [skip ci]" | |
| git push origin main | |
| echo "✅ Version bumped from $current_version to $new_version" | |
| - name: Update CHANGELOG.md | |
| run: | | |
| version="${{ steps.bump-version.outputs.new-version }}" | |
| # Get commits since last tag for changelog | |
| last_tag=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "") | |
| if [ -n "$last_tag" ]; then | |
| commits=$(git log $last_tag..HEAD~1 --pretty=format:"%s (%h)" --no-merges) | |
| else | |
| commits=$(git log HEAD~1 --pretty=format:"%s (%h)" --no-merges) | |
| fi | |
| # Generate changelog entry | |
| changelog_entry="## [$version](https://github.com/btwld/docling-sdk/compare/$last_tag...v$version) ($(date +%Y-%m-%d))" | |
| # Categorize commits for changelog | |
| features="" | |
| fixes="" | |
| chores="" | |
| others="" | |
| echo "$commits" | while IFS= read -r commit; do | |
| [ -z "$commit" ] && continue | |
| if echo "$commit" | grep -qE "^feat(\([^)]+\))?:"; then | |
| echo "- $commit" >> /tmp/cl_features.txt | |
| elif echo "$commit" | grep -qE "^fix(\([^)]+\))?:"; then | |
| echo "- $commit" >> /tmp/cl_fixes.txt | |
| elif echo "$commit" | grep -qE "^(chore|docs|style|refactor|perf|test)(\([^)]+\))?:"; then | |
| echo "- $commit" >> /tmp/cl_chores.txt | |
| else | |
| echo "- $commit" >> /tmp/cl_others.txt | |
| fi | |
| done | |
| # Build changelog entry | |
| echo "$changelog_entry" > /tmp/new_changelog.md | |
| echo "" >> /tmp/new_changelog.md | |
| if [ -f /tmp/cl_features.txt ]; then | |
| echo "### ✨ Features" >> /tmp/new_changelog.md | |
| echo "" >> /tmp/new_changelog.md | |
| cat /tmp/cl_features.txt >> /tmp/new_changelog.md | |
| echo "" >> /tmp/new_changelog.md | |
| fi | |
| if [ -f /tmp/cl_fixes.txt ]; then | |
| echo "### 🐛 Bug Fixes" >> /tmp/new_changelog.md | |
| echo "" >> /tmp/new_changelog.md | |
| cat /tmp/cl_fixes.txt >> /tmp/new_changelog.md | |
| echo "" >> /tmp/new_changelog.md | |
| fi | |
| if [ -f /tmp/cl_chores.txt ]; then | |
| echo "### 🔧 Maintenance" >> /tmp/new_changelog.md | |
| echo "" >> /tmp/new_changelog.md | |
| cat /tmp/cl_chores.txt >> /tmp/new_changelog.md | |
| echo "" >> /tmp/new_changelog.md | |
| fi | |
| if [ -f /tmp/cl_others.txt ]; then | |
| echo "### 📦 Other Changes" >> /tmp/new_changelog.md | |
| echo "" >> /tmp/new_changelog.md | |
| cat /tmp/cl_others.txt >> /tmp/new_changelog.md | |
| echo "" >> /tmp/new_changelog.md | |
| fi | |
| # Insert new entry at the top of CHANGELOG.md (after header) | |
| head -n 4 CHANGELOG.md > /tmp/changelog_header.md | |
| echo "" >> /tmp/changelog_header.md | |
| cat /tmp/new_changelog.md >> /tmp/changelog_header.md | |
| tail -n +5 CHANGELOG.md >> /tmp/changelog_header.md | |
| mv /tmp/changelog_header.md CHANGELOG.md | |
| # Clean up | |
| rm -f /tmp/cl_*.txt /tmp/new_changelog.md | |
| # Commit changelog update | |
| git add CHANGELOG.md | |
| git commit -m "chore: update CHANGELOG.md for v$version [skip ci]" | |
| git push origin main | |
| echo "✅ Updated CHANGELOG.md for version $version" | |
| - name: Get current version | |
| id: get-version | |
| run: | | |
| version="${{ steps.bump-version.outputs.new-version }}" | |
| echo "version=$version" >> $GITHUB_OUTPUT | |
| echo "📦 Using version: $version" | |
| - name: Create git tag | |
| run: | | |
| version="${{ steps.get-version.outputs.version }}" | |
| tag_name="v$version" | |
| # Check if tag already exists | |
| if git tag -l | grep -q "^${tag_name}$"; then | |
| echo "ℹ️ Tag $tag_name already exists, skipping tag creation" | |
| else | |
| git tag "$tag_name" | |
| git push origin "$tag_name" | |
| echo "✅ Created and pushed tag $tag_name" | |
| fi | |
| - name: Publish to npm | |
| id: publish | |
| run: | | |
| # Check if this version is already published | |
| if npm view docling-sdk@${{ steps.get-version.outputs.version }} version >/dev/null 2>&1; then | |
| echo "ℹ️ Version ${{ steps.get-version.outputs.version }} already published to npm" | |
| echo "published=false" >> $GITHUB_OUTPUT | |
| else | |
| npm publish | |
| echo "✅ Published docling-sdk@${{ steps.get-version.outputs.version }} to npm" | |
| echo "published=true" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| - name: Generate Release Notes | |
| id: release-notes | |
| run: | | |
| version="${{ steps.get-version.outputs.version }}" | |
| # Get commits since last tag (excluding the version bump commit we just made) | |
| last_tag=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "") | |
| if [ -n "$last_tag" ]; then | |
| commits=$(git log $last_tag..HEAD~1 --pretty=format:"%s (%h)" --no-merges) | |
| echo "📝 Changes since $last_tag:" | |
| else | |
| commits=$(git log HEAD~1 --pretty=format:"%s (%h)" --no-merges) | |
| echo "📝 All changes:" | |
| fi | |
| # Skip if no commits (shouldn't happen, but safety check) | |
| if [ -z "$commits" ]; then | |
| echo "## $version ($(date +%Y-%m-%d))" > release_notes.md | |
| echo "release-notes-file=release_notes.md" >> $GITHUB_OUTPUT | |
| echo "⚠️ No commits found for release notes" | |
| exit 0 | |
| fi | |
| # Categorize commits | |
| features="" | |
| fixes="" | |
| chores="" | |
| others="" | |
| echo "$commits" | while IFS= read -r commit; do | |
| # Skip empty lines | |
| [ -z "$commit" ] && continue | |
| if echo "$commit" | grep -qE "^feat(\([^)]+\))?:"; then | |
| echo "FEATURE: $commit" >> /tmp/features.txt | |
| elif echo "$commit" | grep -qE "^fix(\([^)]+\))?:"; then | |
| echo "FIX: $commit" >> /tmp/fixes.txt | |
| elif echo "$commit" | grep -qE "^(chore|docs|style|refactor|perf|test)(\([^)]+\))?:"; then | |
| echo "CHORE: $commit" >> /tmp/chores.txt | |
| else | |
| echo "OTHER: $commit" >> /tmp/others.txt | |
| fi | |
| done | |
| # Read categorized commits | |
| features=$(cat /tmp/features.txt 2>/dev/null | sed 's/^FEATURE: /- /' || echo "") | |
| fixes=$(cat /tmp/fixes.txt 2>/dev/null | sed 's/^FIX: /- /' || echo "") | |
| chores=$(cat /tmp/chores.txt 2>/dev/null | sed 's/^CHORE: /- /' || echo "") | |
| others=$(cat /tmp/others.txt 2>/dev/null | sed 's/^OTHER: /- /' || echo "") | |
| # Build release notes | |
| echo "## $version ($(date +%Y-%m-%d))" > release_notes.md | |
| if [ -n "$features" ]; then | |
| echo "" >> release_notes.md | |
| echo "### ✨ Features" >> release_notes.md | |
| echo "$features" >> release_notes.md | |
| fi | |
| if [ -n "$fixes" ]; then | |
| echo "" >> release_notes.md | |
| echo "### 🐛 Bug Fixes" >> release_notes.md | |
| echo "$fixes" >> release_notes.md | |
| fi | |
| if [ -n "$chores" ]; then | |
| echo "" >> release_notes.md | |
| echo "### 🔧 Maintenance" >> release_notes.md | |
| echo "$chores" >> release_notes.md | |
| fi | |
| if [ -n "$others" ]; then | |
| echo "" >> release_notes.md | |
| echo "### 📦 Other Changes" >> release_notes.md | |
| echo "$others" >> release_notes.md | |
| fi | |
| # Clean up temp files | |
| rm -f /tmp/features.txt /tmp/fixes.txt /tmp/chores.txt /tmp/others.txt | |
| echo "release-notes-file=release_notes.md" >> $GITHUB_OUTPUT | |
| echo "✅ Generated release notes:" | |
| cat release_notes.md | |
| - name: Create GitHub Release | |
| if: steps.publish.outputs.published == 'true' | |
| run: | | |
| version="${{ steps.get-version.outputs.version }}" | |
| tag_name="v$version" | |
| # Create GitHub release with generated notes | |
| gh release create "$tag_name" \ | |
| --title "Release $version" \ | |
| --notes-file "${{ steps.release-notes.outputs.release-notes-file }}" \ | |
| --latest | |
| echo "✅ Created GitHub release for $tag_name" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Publish to GitHub Package Registry | |
| github-packages: | |
| name: GitHub Packages | |
| needs: release | |
| runs-on: ubuntu-latest | |
| if: needs.release.outputs.published == 'true' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: "npm" | |
| registry-url: "https://npm.pkg.github.com" | |
| scope: "@btwld" | |
| - run: npm ci | |
| - run: npm run build | |
| # Create scoped package.json for GitHub Packages | |
| - name: Setup GitHub Package | |
| run: | | |
| cp package.json package.json.backup | |
| npm pkg set name="@btwld/docling-sdk" | |
| npm pkg set publishConfig.registry="https://npm.pkg.github.com" | |
| - name: Publish to GitHub Packages | |
| run: npm publish | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Restore original package.json | |
| - name: Restore package.json | |
| run: mv package.json.backup package.json | |
| # Notify on successful release | |
| notify: | |
| name: Notify Success | |
| needs: [release, github-packages] | |
| runs-on: ubuntu-latest | |
| if: needs.release.outputs.published == 'true' | |
| steps: | |
| - name: Notify Release | |
| run: | | |
| echo "🎉 Successfully released docling-sdk@${{ needs.release.outputs.version }}" | |
| echo "📦 npm: https://www.npmjs.com/package/docling-sdk" | |
| echo "📦 GitHub Packages: https://github.com/btwld/docling-sdk/packages" | |
| echo "🏷️ GitHub Release: https://github.com/btwld/docling-sdk/releases/tag/v${{ needs.release.outputs.version }}" |