version 3.1.0 #109
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: Auto Docker Build | |
| on: | |
| pull_request: | |
| branches: | |
| - release | |
| types: [opened, synchronize, reopened] | |
| jobs: | |
| check-source-branch: | |
| name: Validate PR Source Branch | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v3 | |
| - name: Verify PR is from main branch | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| console.log("Checking PR source branch..."); | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| if (pr.head.ref !== 'main') { | |
| core.setFailed(`PR must be from the main branch, but was from: ${pr.head.ref}`); | |
| process.exit(1); | |
| } | |
| console.log("PR is from main branch. Proceeding with release."); | |
| release-docker-image: | |
| name: Release Docker Image | |
| needs: check-source-branch | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| packages: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v3 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22.14.0" | |
| cache: "npm" | |
| cache-dependency-path: "frontend/package-lock.json" | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1.24" | |
| cache: true | |
| cache-dependency-path: backend/go.sum | |
| - name: Read version from VERSION file | |
| id: read_version | |
| run: | | |
| version=$(cat VERSION) | |
| echo "Version: $version" | |
| echo "version=$version" >> $GITHUB_OUTPUT | |
| - name: Check if version tag exists | |
| id: check_tag | |
| run: | | |
| TAG_EXISTS=$(git ls-remote --tags origin refs/tags/v${{ steps.read_version.outputs.version }} | wc -l) | |
| echo "tag_exists=$TAG_EXISTS" >> $GITHUB_OUTPUT | |
| - name: Exit if tag already exists | |
| if: steps.check_tag.outputs.tag_exists != '0' | |
| run: | | |
| echo "Version v${{ steps.read_version.outputs.version }} already released. Skipping." | |
| exit 0 | |
| - name: Get PR Info via GitHub API | |
| id: pr_data | |
| uses: actions/github-script@v6 | |
| with: | |
| script: | | |
| console.log("Fetching PR data..."); | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| if (!pr) { | |
| core.setFailed("No matching PR found."); | |
| return; | |
| } | |
| core.setOutput("title", pr.title); | |
| core.setOutput("body", pr.body || "No description"); | |
| const commits = await github.rest.pulls.listCommits({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| // Clean commit messages to prevent shell interpretation issues | |
| const messages = commits.data.map(c => { | |
| // Sanitize commit message to remove or escape characters that could be interpreted as commands | |
| const cleanMessage = c.commit.message | |
| // .replace(/`/g, "'") // Replace backticks with single quotes | |
| // .replace(/\(/g, "'") // Replace parentheses with single quotes | |
| // .replace(/\)/g, "'") // Replace parentheses with single quotes | |
| // .replace(/\$/g, "") // Remove dollar signs | |
| // .replace(/\\/g, "") // Remove backslashes | |
| // .replace(/;/g, ","); // Replace semicolons with commas | |
| return `- ${cleanMessage} (${c.commit.author.name})`; | |
| }); | |
| core.setOutput("commit_log", messages.join('\\n')); | |
| - name: Call Gemini to generate release summary | |
| id: ai_summary | |
| env: | |
| PR_TITLE: ${{ steps.pr_data.outputs.title }} | |
| PR_BODY: ${{ steps.pr_data.outputs.body }} | |
| COMMIT_LOG: ${{ steps.pr_data.outputs.commit_log }} | |
| run: | | |
| VERSION=v${{ steps.read_version.outputs.version }} | |
| if [ -z "${{ vars.GEMINI_MODEL }}" ] || [ -z "${{ secrets.GEMINI_API_KEY }}" ]; then | |
| echo "::error::GEMINI_MODEL or GEMINI_API_KEY secret is missing. Check repository secrets/permissions." | |
| exit 1 | |
| fi | |
| # Write commit logs to file to avoid shell interpretation issues | |
| printf "%s\n" "$COMMIT_LOG" > commit_log.txt | |
| COMMITS=$(cat commit_log.txt) | |
| PROMPT=$(printf "Release version: %s\n\nPull Request: %s\n\nDescription:\n%s\n\nCommits:\n%s\n\nYou are an agent responsible for generating GitHub release summaries from pull request commits. Write the summary using only a numbered list under the relevant categories: New Features, Improvements, or Bug Fixes. Only include categories that are present based on the commit messages. Do not add any introductory sentences like \"This release includes...\" or \"In this version...\". Go straight to the list, and describe each item as specifically as possible based on the available commit details. Maintain a clear, professional tone appropriate for public changelogs." "$VERSION" "$PR_TITLE" "$PR_BODY" "$COMMITS") | |
| echo "Prompt: $PROMPT" | |
| RESPONSE=$(curl -sS "https://generativelanguage.googleapis.com/v1beta/models/${{ vars.GEMINI_MODEL }}:generateContent?key=${{ secrets.GEMINI_API_KEY }}" \ | |
| -H 'Content-Type: application/json' \ | |
| -X POST \ | |
| -d '{ | |
| "contents": [{ | |
| "parts": [{"text": '"$(jq -Rs <<< "$PROMPT")"'}] | |
| }] | |
| }' 2>&1) || true | |
| SUMMARY=$(echo "$RESPONSE" | jq -r '.candidates[0].content.parts[0].text // empty' 2>/dev/null || true) | |
| if [ -z "$SUMMARY" ] || [ "$SUMMARY" = "null" ]; then | |
| echo "::error::Failed to generate summary from Gemini or parse the response." | |
| echo "Raw Response:" | |
| echo "$RESPONSE" | |
| exit 1 | |
| fi | |
| # Generate Docker Images section using helper script | |
| chmod +x .github/scripts/generate-docker-section.sh | |
| DOCKER_SECTION=$(.github/scripts/generate-docker-section.sh "${{ github.repository }}" "${{ steps.read_version.outputs.version }}") | |
| echo "title=$VERSION" >> $GITHUB_OUTPUT | |
| # Use a tightly bound EOF to prevent string evaluation | |
| echo "summary<<EOF_SUMMARY" >> $GITHUB_OUTPUT | |
| echo "$SUMMARY" >> $GITHUB_OUTPUT | |
| echo "$DOCKER_SECTION" >> $GITHUB_OUTPUT | |
| echo "" >> $GITHUB_OUTPUT | |
| echo "---" >> $GITHUB_OUTPUT | |
| echo "*This summary was automatically generated by Gemini AI*" >> $GITHUB_OUTPUT | |
| echo "EOF_SUMMARY" >> $GITHUB_OUTPUT | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # - name: Set up QEMU (for ARM emulation) | |
| # uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| with: | |
| buildkitd-flags: --debug | |
| driver-opts: | | |
| image=moby/buildkit:latest | |
| network=host | |
| - name: Build and Push Multi-Arch Docker image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| push: true | |
| platforms: linux/amd64 | |
| # platforms: linux/amd64,linux/arm64 | |
| cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache | |
| cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache,mode=max | |
| build-args: | | |
| NODE_VERSION=22.14.0 | |
| BUILDKIT_INLINE_CACHE=1 | |
| tags: | | |
| ghcr.io/${{ github.repository }}:${{ steps.read_version.outputs.version }} | |
| ghcr.io/${{ github.repository }}:latest | |
| - name: Create Git Tag | |
| run: | | |
| git config user.name "${{ github.actor }}" | |
| git config user.email "${{ github.actor }}@users.noreply.github.com" | |
| git tag v${{ steps.read_version.outputs.version }} | |
| git push origin v${{ steps.read_version.outputs.version }} | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 | |
| with: | |
| tag_name: v${{ steps.read_version.outputs.version }} | |
| name: ${{ steps.ai_summary.outputs.title }} | |
| body: ${{ steps.ai_summary.outputs.summary }} | |
| - name: Update CHANGELOG.md | |
| env: | |
| SUMMARY: ${{ steps.ai_summary.outputs.summary }} | |
| run: | | |
| tempfile=$(mktemp) | |
| VERSION=v${{ steps.read_version.outputs.version }} | |
| # Write new release section | |
| { | |
| echo "## $VERSION" | |
| echo "_Released on $(date +'%Y-%m-%d')_" | |
| echo "" | |
| printf '%s\n' "$SUMMARY" | |
| echo "" | |
| } >> "$tempfile" | |
| # Append existing CHANGELOG if exists | |
| [ -f CHANGELOG.md ] && cat CHANGELOG.md >> "$tempfile" | |
| mv "$tempfile" CHANGELOG.md | |
| echo "=== CHANGELOG.md Preview ===" | |
| head -n 50 CHANGELOG.md | |
| echo "===========================" | |
| rm -f commit_log.txt | |
| - name: Create Pull Request to update CHANGELOG.md | |
| uses: peter-evans/create-pull-request@4e1beaa7521e8b457b572c090b25bd3db56bf1c5 # v5.0.3 | |
| with: | |
| commit-message: "chore: update CHANGELOG.md for v${{ steps.read_version.outputs.version }}" | |
| title: "Update CHANGELOG.md for v${{ steps.read_version.outputs.version }}" | |
| body: "This PR updates the changelog after release." | |
| branch: changelog/update-${{ steps.read_version.outputs.version }} | |
| base: main | |
| auto-merge-pr: | |
| name: Auto Merge PR | |
| needs: release-docker-image | |
| runs-on: ubuntu-latest | |
| if: success() | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v3 | |
| - name: Set PR number from context | |
| id: pr_number | |
| run: | | |
| echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT | |
| echo "Found PR #${{ github.event.pull_request.number }}" | |
| - name: Auto Merge PR Release | |
| uses: actions/github-script@v6 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const pr_number = ${{ steps.pr_number.outputs.pr_number }}; | |
| if (!pr_number) { | |
| core.setFailed("No PR number found"); | |
| return; | |
| } | |
| console.log(`Attempting to merge PR #${pr_number}...`); | |
| try { | |
| await github.rest.pulls.merge({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr_number, | |
| merge_method: 'merge', | |
| commit_title: `[Automated] Merge PR #${pr_number} to release branch`, | |
| commit_message: `Auto-merged after successful Docker image build` | |
| }); | |
| console.log(`Successfully merged PR #${pr_number}`); | |
| } catch (error) { | |
| core.setFailed(`Failed to merge PR: ${error.message}`); | |
| } | |
| create-public-release: | |
| name: Create Public GitHub Release | |
| needs: auto-merge-pr | |
| runs-on: ubuntu-latest | |
| if: success() | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v3 | |
| - name: Read version from VERSION file | |
| id: read_version | |
| run: | | |
| version=$(cat VERSION) | |
| echo "Version: $version" | |
| echo "version=$version" >> $GITHUB_OUTPUT | |
| - name: Checkout target public repository | |
| uses: actions/checkout@v3 | |
| with: | |
| repository: yogasw/beo-echo-release | |
| token: ${{ secrets.PUBLIC_REPO_PAT }} | |
| path: public-repo | |
| - name: Copy release notes to public repo | |
| run: | | |
| cp CHANGELOG.md public-repo/ | |
| cd public-repo | |
| if [[ -z $(git status --porcelain) ]]; then | |
| echo "No changes to commit" | |
| else | |
| git config user.name "${{ github.actor }}" | |
| git config user.email "${{ github.actor }}@users.noreply.github.com" | |
| git add CHANGELOG.md | |
| git commit -m "Update changelog for v${{ steps.read_version.outputs.version }}" | |
| git push | |
| fi | |
| - name: Get Release Info | |
| id: release_info | |
| run: | | |
| VERSION=v${{ steps.read_version.outputs.version }} | |
| # Extract release notes from first occurrence of version to next version or end of file | |
| SUMMARY=$(awk "/^## $VERSION/,/^## [^$VERSION]/" CHANGELOG.md | sed '/^## '"$VERSION"'/d' | sed '/^## /,$d') | |
| echo "summary<<EOF" >> $GITHUB_OUTPUT | |
| echo "$SUMMARY" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| - name: Create Public GitHub Release | |
| uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 | |
| with: | |
| token: ${{ secrets.PUBLIC_REPO_PAT }} | |
| repository: yogasw/beo-echo-release | |
| tag_name: v${{ steps.read_version.outputs.version }} | |
| name: v${{ steps.read_version.outputs.version }} | |
| body: ${{ steps.release_info.outputs.summary }} | |
| draft: false | |
| prerelease: false | |
| trigger-deployment: | |
| needs: create-public-release | |
| runs-on: ubuntu-latest | |
| if: success() | |
| steps: | |
| - name: Trigger Deployment | |
| run: | | |
| curl -X POST -d {} ${{ secrets.BUILD_HOOK }} |