chore: version packages #57
Workflow file for this run
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: Build and Release App | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| inputs: | |
| version_suffix: | |
| description: "Version suffix (leave empty to use commit SHA)" | |
| required: false | |
| default: "" | |
| permissions: | |
| contents: write | |
| jobs: | |
| ci: | |
| name: β Lint, Format & Test | |
| uses: ./.github/workflows/ci.yml | |
| secrets: inherit | |
| build: | |
| name: π¨ Build (${{ matrix.profile }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| needs: ci | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| profile: [internal, production] | |
| steps: | |
| - name: ποΈ Checkout | |
| uses: actions/checkout@v4 | |
| - name: π¦ Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24.x | |
| - name: β Setup Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: "temurin" | |
| java-version: "17" | |
| - name: π€ Setup Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: π Get yarn cache directory | |
| id: yarn-cache | |
| run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT | |
| - name: π¦ Cache yarn downloads | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.yarn-cache.outputs.dir }} | |
| key: ${{ runner.os }}-yarn-cache-${{ hashFiles('yarn.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-yarn-cache- | |
| - name: π¦ Cache node_modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: node_modules | |
| key: ${{ runner.os }}-node-modules-${{ hashFiles('yarn.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-node-modules- | |
| - name: π Cache Gradle | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| - name: π οΈ Setup EAS CLI | |
| uses: expo/expo-github-action@v8 | |
| with: | |
| eas-version: latest | |
| token: ${{ secrets.EXPO_TOKEN }} | |
| - name: π₯ Install dependencies | |
| run: yarn install --immutable | |
| - name: π Extract changelog for this version | |
| id: changelog | |
| run: | | |
| echo "CHANGELOG=" >> $GITHUB_OUTPUT | |
| - name: π§ Determine version name | |
| id: version | |
| run: | | |
| if [ "${{ github.event_name }}" == "push" ] && [ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]; then | |
| VERSION="${GITHUB_REF#refs/tags/}" | |
| echo "version_name=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Using tag version: $VERSION" | |
| elif [ -n "${{ github.event.inputs.version_suffix }}" ]; then | |
| VERSION="${{ github.event.inputs.version_suffix }}" | |
| echo "version_name=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Using custom version: $VERSION" | |
| else | |
| VERSION="${GITHUB_SHA:0:7}" | |
| echo "version_name=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Using commit SHA: $VERSION" | |
| fi | |
| - name: π Build Locally (${{ matrix.profile }}) | |
| run: | | |
| echo "Building locally on GitHub runner..." | |
| if [ "${{ matrix.profile }}" == "production" ]; then | |
| EXT="aab" | |
| else | |
| EXT="apk" | |
| fi | |
| eas build --platform android --profile ${{ matrix.profile }} --local --non-interactive --output=./expense-buddy-${{ steps.version.outputs.version_name }}-${{ matrix.profile }}.$EXT | |
| - name: π€ Upload build as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: expense-buddy-${{ steps.version.outputs.version_name }}-${{ matrix.profile }} | |
| path: expense-buddy-${{ steps.version.outputs.version_name }}-${{ matrix.profile }}.${{ matrix.profile == 'production' && 'aab' || 'apk' }} | |
| - name: π Submit to Google Play Store (Production) | |
| if: matrix.profile == 'production' | |
| continue-on-error: true | |
| run: | | |
| eas submit --platform android --profile production --path expense-buddy-${{ steps.version.outputs.version_name }}-production.aab --non-interactive | |
| - name: π¬ Comment on merged PRs | |
| if: matrix.profile == 'production' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_PAT }} | |
| script: | | |
| const version = '${{ steps.version.outputs.version_name }}'; | |
| const tag = version.startsWith('v') ? version : `v${version}`; | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| console.log(`Processing release comments for tag: ${tag}`); | |
| // 1. Get list of tags to find the previous one | |
| const { data: tags } = await github.rest.repos.listTags({ | |
| owner, | |
| repo, | |
| per_page: 10 | |
| }); | |
| // Tags are returned latest first | |
| const currentTagIndex = tags.findIndex(t => t.name === tag); | |
| // If tag not found, it might be a manual build for a future tag | |
| if (currentTagIndex === -1) { | |
| console.log(`Tag ${tag} not found in repository. Skipping comments.`); | |
| return; | |
| } | |
| const previousTag = tags[currentTagIndex + 1]?.name; | |
| if (!previousTag) { | |
| console.log('No previous tag found, likely first release. Skipping PR comments.'); | |
| return; | |
| } | |
| console.log(`Comparing releases: ${previousTag} ... ${tag}`); | |
| // 2. Compare commits | |
| const comparison = await github.rest.repos.compareCommits({ | |
| owner, | |
| repo, | |
| base: previousTag, | |
| head: tag | |
| }); | |
| // 3. Find unique PRs | |
| const prNumbers = new Set(); | |
| for (const commit of comparison.data.commits) { | |
| const { data: associatedPrs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ | |
| owner, | |
| repo, | |
| commit_sha: commit.sha | |
| }); | |
| for (const pr of associatedPrs) { | |
| if (pr.merged_at) { | |
| prNumbers.add(pr.number); | |
| } | |
| } | |
| } | |
| console.log(`Found ${prNumbers.size} PRs to notify: ${Array.from(prNumbers).join(', ')}`); | |
| // 4. Comment on each PR | |
| for (const prNumber of prNumbers) { | |
| try { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: prNumber, | |
| body: `π Released in [${tag}](https://github.com/${owner}/${repo}/releases/tag/${tag})` | |
| }); | |
| console.log(`β Commented on PR #${prNumber}`); | |
| } catch (error) { | |
| console.error(`β Failed to comment on PR #${prNumber}:`, error); | |
| } | |
| } | |
| release: | |
| name: π Create GitHub Release | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/') | |
| steps: | |
| - name: ποΈ Checkout | |
| uses: actions/checkout@v4 | |
| - name: οΏ½ Determine version name | |
| id: version | |
| run: | | |
| VERSION="${GITHUB_REF#refs/tags/}" | |
| echo "version_name=$VERSION" >> $GITHUB_OUTPUT | |
| VERSION_NO_V="${VERSION#v}" | |
| echo "version_no_v=$VERSION_NO_V" >> $GITHUB_OUTPUT | |
| - name: π Extract changelog for this version | |
| id: changelog | |
| run: | | |
| VERSION_NO_V="${{ steps.version.outputs.version_no_v }}" | |
| if [ -f "CHANGELOG.md" ]; then | |
| echo "Found CHANGELOG.md, extracting section for $VERSION_NO_V" | |
| CHANGELOG_CONTENT=$(awk -v version="$VERSION_NO_V" ' | |
| /^## / { | |
| if (found) exit | |
| if ($0 ~ version) { | |
| found=1 | |
| next | |
| } | |
| } | |
| found { print } | |
| ' CHANGELOG.md) | |
| if [ -n "$CHANGELOG_CONTENT" ]; then | |
| echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT | |
| echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| echo "β Changelog extracted successfully" | |
| else | |
| echo "CHANGELOG=_No changelog entry found for $VERSION_NO_V_" >> $GITHUB_OUTPUT | |
| echo "β οΈ No changelog entry found for $VERSION_NO_V" | |
| fi | |
| else | |
| echo "CHANGELOG=_No CHANGELOG.md found._" >> $GITHUB_OUTPUT | |
| echo "β οΈ CHANGELOG.md not found" | |
| fi | |
| - name: π₯ Download all app artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: expense-buddy-${{ steps.version.outputs.version_name }}-* | |
| merge-multiple: true | |
| - name: π Create GitHub Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: | | |
| expense-buddy-${{ steps.version.outputs.version_name }}-internal.apk | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| body: | | |
| ## π± Get it on Google Play | |
| For the best experience with automatic updates, install from the Play Store: | |
| **[Download from Google Play](https://play.google.com/store/apps/details?id=com.sudokoi.expensebuddy)** | |
| --- | |
| ## π¦ APK Download | |
| Alternatively, download directly from the Assets below: | |
| - `expense-buddy-${{ steps.version.outputs.version_name }}-internal.apk` | |
| --- | |
| ## π Changelog | |
| ${{ steps.changelog.outputs.CHANGELOG }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |