diff --git a/.github/workflows/version-and-publish.yml b/.github/workflows/version-and-publish.yml index a8bd4cd..33c2250 100644 --- a/.github/workflows/version-and-publish.yml +++ b/.github/workflows/version-and-publish.yml @@ -1,356 +1,16 @@ -name: Version and Publish +name: Publish to NPM on: push: - branches: - - 'major/**' - - 'minor/**' - - 'patch/**' - pull_request: branches: - main - types: [opened, synchronize, reopened, closed] jobs: - prepare-release: - if: github.event_name == 'push' && (startsWith(github.ref_name, 'major/') || startsWith(github.ref_name, 'minor/') || startsWith(github.ref_name, 'patch/')) + publish: runs-on: ubuntu-latest steps: - name: Checkout code 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' - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Get pnpm store directory - shell: bash - run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Run tests - run: pnpm test - - - name: Run build - run: pnpm build - - - name: Determine version bump from branch comparison - id: version-type - run: | - # Get version from source branch (current) - SOURCE_VERSION=$(node -p "require('./package.json').version") - - # Get version from target branch (main) - git fetch origin main - TARGET_VERSION=$(git show origin/main:package.json | node -p "JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8')).version") - - echo "📊 Source branch version: $SOURCE_VERSION" - echo "📊 Target branch version: $TARGET_VERSION" - - # Parse versions into components - IFS='.' read -r -a SOURCE_PARTS <<< "$SOURCE_VERSION" - IFS='.' read -r -a TARGET_PARTS <<< "$TARGET_VERSION" - - SOURCE_MAJOR=${SOURCE_PARTS[0]} - SOURCE_MINOR=${SOURCE_PARTS[1]} - SOURCE_PATCH=${SOURCE_PARTS[2]} - - TARGET_MAJOR=${TARGET_PARTS[0]} - TARGET_MINOR=${TARGET_PARTS[1]} - TARGET_PATCH=${TARGET_PARTS[2]} - - # Determine version bump type based on what changed - if [ "$SOURCE_MAJOR" -gt "$TARGET_MAJOR" ]; then - echo "type=major" >> $GITHUB_OUTPUT - echo "🚀 Major version increase detected ($TARGET_MAJOR → $SOURCE_MAJOR)" - elif [ "$SOURCE_MINOR" -gt "$TARGET_MINOR" ]; then - echo "type=minor" >> $GITHUB_OUTPUT - echo "✨ Minor version increase detected ($TARGET_MINOR → $SOURCE_MINOR)" - elif [ "$SOURCE_PATCH" -gt "$TARGET_PATCH" ]; then - echo "type=patch" >> $GITHUB_OUTPUT - echo "🐛 Patch version increase detected ($TARGET_PATCH → $SOURCE_PATCH)" - else - echo "type=none" >> $GITHUB_OUTPUT - echo "⚠️ No version increase detected. Source version must be higher than target." - echo "Target: $TARGET_VERSION, Source: $SOURCE_VERSION" - exit 1 - fi - - # Store versions for later use - echo "source-version=$SOURCE_VERSION" >> $GITHUB_OUTPUT - echo "target-version=$TARGET_VERSION" >> $GITHUB_OUTPUT - - - name: Generate changelog with Claude - uses: anthropics/claude-code-action@v1 - with: - prompt: | - Please analyze the recent git commits and update the CHANGELOG.md file with a new release entry. - - Add a new section at the top of CHANGELOG.md with today's date in this format: - - ## [Unreleased] - $(date +%Y-%m-%d) - - Organize the commits into these categories based on their content and conventional commit patterns: - - ### 🚀 Features - - List any new features, major additions, or enhancements - - ### 🐛 Bug Fixes - - List any bug fixes, error corrections, or issue resolutions - - ### 🔧 Improvements - - List any refactoring, code improvements, or optimizations - - ### 📚 Documentation - - List any documentation updates, README changes, or comment improvements - - ### ⚡ Performance - - List any performance improvements or optimizations - - Only include sections that have actual changes. Keep descriptions concise and user-focused. - - The recent commits to analyze are visible in the git history. Please examine them and create a comprehensive changelog entry. - - If CHANGELOG.md doesn't exist, create it with a proper header and the new release section. - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - - name: Read generated changelog - id: changelog - run: | - if [ -f CHANGELOG.md ]; then - # Extract the latest changelog entry (from top until next ## or end of file) - CHANGELOG=$(sed -n '/^## \[Unreleased\]/,/^## /p' CHANGELOG.md | sed '$d' | head -n -1) - - # If that doesn't work, try a different approach - if [ -z "$CHANGELOG" ]; then - # Get first section after any header - CHANGELOG=$(sed -n '/^## /,/^## /p' CHANGELOG.md | head -n -1) - fi - - # Final fallback - just get the first meaningful section - if [ -z "$CHANGELOG" ]; then - CHANGELOG=$(head -20 CHANGELOG.md | grep -A 20 "^## ") - fi - - echo "📖 Extracted changelog from CHANGELOG.md:" - echo "$CHANGELOG" - else - echo "⚠️ CHANGELOG.md not found, creating fallback" - CHANGELOG="## Changes - - ### 🔧 Improvements - - Version update and maintenance changes" - fi - - # Save changelog to output - echo "changelog<> $GITHUB_OUTPUT - echo "$CHANGELOG" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Configure git - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - - name: Create version calculation script - run: | - # Create Node.js script to calculate proper version increment - cat > calculate-version.js << 'VERSION_SCRIPT_EOF' - const fs = require('fs'); - const path = require('path'); - - const versionType = process.argv[2]; - const targetVersion = process.argv[3]; - const sourceVersion = process.argv[4]; - const packagePath = path.join(process.cwd(), 'package.json'); - - try { - // Parse versions - const [targetMajor, targetMinor, targetPatch] = targetVersion.split('.').map(Number); - const [sourceMajor, sourceMinor, sourcePatch] = sourceVersion.split('.').map(Number); - - let newVersion; - let increment; - - switch (versionType) { - case 'major': - // Calculate major increment and reset minor/patch - increment = sourceMajor - targetMajor; - newVersion = `${targetMajor + increment}.0.0`; - break; - case 'minor': - // Keep major from target, calculate minor increment, reset patch - increment = sourceMinor - targetMinor; - newVersion = `${targetMajor}.${targetMinor + increment}.0`; - break; - case 'patch': - // Keep major/minor from target, calculate patch increment - increment = sourcePatch - targetPatch; - newVersion = `${targetMajor}.${targetMinor}.${targetPatch + increment}`; - break; - default: - throw new Error(`Invalid version type: ${versionType}`); - } - - // Ensure increment is positive - if (increment <= 0) { - throw new Error(`Invalid increment ${increment} for ${versionType} version`); - } - - // Update package.json - const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); - packageJson.version = newVersion; - fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + '\n'); - - console.log(`${targetVersion}:${newVersion}:${increment}`); - } catch (error) { - console.error('Error calculating version:', error.message); - process.exit(1); - } - VERSION_SCRIPT_EOF - - - name: Calculate new version and create merge branch - id: prepare - run: | - VERSION_TYPE="${{ steps.version-type.outputs.type }}" - TARGET_VERSION="${{ steps.version-type.outputs.target-version }}" - SOURCE_VERSION="${{ steps.version-type.outputs.source-version }}" - - # Calculate new version using target + calculated increment - VERSION_OUTPUT=$(node calculate-version.js $VERSION_TYPE $TARGET_VERSION $SOURCE_VERSION) - CURRENT_VERSION=$(echo $VERSION_OUTPUT | cut -d: -f1) - NEW_VERSION=$(echo $VERSION_OUTPUT | cut -d: -f2) - INCREMENT=$(echo $VERSION_OUTPUT | cut -d: -f3) - - echo "📊 Version calculation:" - echo " Target (main): $CURRENT_VERSION" - echo " Source (branch): $SOURCE_VERSION" - echo " New version: $NEW_VERSION" - echo " Increment: +$INCREMENT ($VERSION_TYPE)" - - echo "current-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT - echo "new-version=$NEW_VERSION" >> $GITHUB_OUTPUT - - # Create merge branch name - MERGE_BRANCH="merge/${{ github.ref_name }}" - echo "merge-branch=$MERGE_BRANCH" >> $GITHUB_OUTPUT - - # Create or update merge branch - if git ls-remote --exit-code --heads origin $MERGE_BRANCH; then - echo "Merge branch $MERGE_BRANCH exists, checking out and updating" - git fetch origin $MERGE_BRANCH - git checkout -B $MERGE_BRANCH origin/$MERGE_BRANCH - git merge --no-ff ${{ github.ref_name }} -m "Update merge branch with latest changes from ${{ github.ref_name }}" - else - echo "Creating new merge branch $MERGE_BRANCH" - git checkout -b $MERGE_BRANCH - fi - - # Synchronize pnpm lockfile if it exists - if [ -f pnpm-lock.yaml ]; then - echo "Synchronizing pnpm lockfile..." - pnpm install --frozen-lockfile --ignore-scripts || echo "Lockfile sync completed with warnings" - fi - - # Clean up temporary files - rm -f calculate-version.js - - # Commit version bump and changes - git add -A - - # Create commit message title - if [ "$VERSION_TYPE" = "major" ]; then - COMMIT_TITLE="🚀 Major Release v$NEW_VERSION" - elif [ "$VERSION_TYPE" = "minor" ]; then - COMMIT_TITLE="✨ Minor Release v$NEW_VERSION" - else - COMMIT_TITLE="🐛 Patch Release v$NEW_VERSION" - fi - - COMMIT_MSG="$COMMIT_TITLE - - ${{ steps.changelog.outputs.changelog }}" - - git commit -m "$COMMIT_MSG" || echo "No changes to commit" - - # Push merge branch - git push origin $MERGE_BRANCH - - - name: Create or update pull request - run: | - MERGE_BRANCH="${{ steps.prepare.outputs.merge-branch }}" - NEW_VERSION="${{ steps.prepare.outputs.new-version }}" - VERSION_TYPE="${{ steps.version-type.outputs.type }}" - - # Create PR title and body - if [ "$VERSION_TYPE" = "major" ]; then - PR_TITLE="🚀 Release v$NEW_VERSION (Major)" - elif [ "$VERSION_TYPE" = "minor" ]; then - PR_TITLE="✨ Release v$NEW_VERSION (Minor)" - else - PR_TITLE="🐛 Release v$NEW_VERSION (Patch)" - fi - - PR_BODY="## Release v$NEW_VERSION - - This PR contains the prepared release for version **v$NEW_VERSION** ($VERSION_TYPE release). - - ${{ steps.changelog.outputs.changelog }} - - ### Pre-Release Checklist - - [x] Version bumped in package.json - - [x] Changelog generated automatically - - [x] Tests passing - - [x] Build successful - - **⚠️ Merging this PR will automatically publish to NPM and create a GitHub release.** - - --- - 🤖 This PR was created automatically from branch \`${{ github.ref_name }}\`" - - # Check if PR already exists - EXISTING_PR=$(gh pr list --head $MERGE_BRANCH --json number --jq '.[0].number' 2>/dev/null || echo "") - - if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then - echo "Updating existing PR #$EXISTING_PR" - gh pr edit $EXISTING_PR --title "$PR_TITLE" --body "$PR_BODY" - else - echo "Creating new PR" - gh pr create --title "$PR_TITLE" --body "$PR_BODY" --head $MERGE_BRANCH --base main - fi - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - version-and-publish: - if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'merge/') - runs-on: ubuntu-latest - outputs: - new-version: ${{ steps.extract-version.outputs.new-version }} - package-name: ${{ steps.extract-version.outputs.package-name }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Node.js uses: actions/setup-node@v4 @@ -361,97 +21,16 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v4 - - name: Get pnpm store directory - shell: bash - run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Extract version and package info - id: extract-version - run: | - # Extract package info from the merged commit - PACKAGE_NAME=$(node -p "require('./package.json').name") - NEW_VERSION=$(node -p "require('./package.json').version") - - echo "package-name=$PACKAGE_NAME" >> $GITHUB_OUTPUT - echo "new-version=$NEW_VERSION" >> $GITHUB_OUTPUT - - echo "📦 Package: $PACKAGE_NAME" - echo "🚀 New Version: v$NEW_VERSION" - - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Run final tests + - name: Run tests run: pnpm test - - name: Run final build + - name: Run build run: pnpm build - - name: Create git tag - run: | - NEW_VERSION="${{ steps.extract-version.outputs.new-version }}" - - # Get the commit message (which contains the changelog) - COMMIT_MSG=$(git log -1 --pretty=%B) - - # Configure git - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - # Create git tag - git tag -a "v$NEW_VERSION" -m "Version $NEW_VERSION - - $COMMIT_MSG" - - # Push tags - git push origin --tags - - name: Publish to NPM run: pnpm publish --access public --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Create GitHub Release - run: | - NEW_VERSION="${{ steps.extract-version.outputs.new-version }}" - - # Get the commit message (which contains the changelog) - COMMIT_MSG=$(git log -1 --pretty=%B) - - gh release create "v$NEW_VERSION" \ - --title "Release v$NEW_VERSION" \ - --notes "$COMMIT_MSG - - --- - - ### Installation - - \`\`\`bash - npm install ${{ steps.extract-version.outputs.package-name }}@$NEW_VERSION - \`\`\` - - ### Documentation - - See the [README](https://github.com/${{ github.repository }}#readme) for usage instructions and full documentation." - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - notify-success: - if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'merge/') - needs: version-and-publish - runs-on: ubuntu-latest - steps: - - name: Success notification - if: needs.version-and-publish.outputs.new-version != '' - run: | - echo "🎉 Successfully published version ${{ needs.version-and-publish.outputs.new-version }} to NPM!" - echo "📦 Package: https://www.npmjs.com/package/${{ needs.version-and-publish.outputs.package-name }}" - echo "🏷️ GitHub Release: https://github.com/${{ github.repository }}/releases/tag/v${{ needs.version-and-publish.outputs.new-version }}"