From 72ffa37dd968390af4160d52543297a25f7449c1 Mon Sep 17 00:00:00 2001 From: bo0tzz Date: Mon, 8 Sep 2025 16:25:31 +0200 Subject: [PATCH] feat: workflow for automated translations merge (#21639) * feat: workflow for automated translations merge * feat: dismiss review on merge failure * chore: parameterize weblate URL * fix: remove unnecessary CHANGES_REQUESTED review flow * feat: leave weblate locked on failures * chore: remove unnecessary merge timeout comment The review dismissal already communicates this * chore: remove todo * feat: save api call * fix: quotes --- .github/workflows/merge-translations.yml | 87 ++++++++++++++++++++++++ .github/workflows/prepare-release.yml | 9 +++ .github/workflows/weblate-lock.yml | 20 +++--- 3 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/merge-translations.yml diff --git a/.github/workflows/merge-translations.yml b/.github/workflows/merge-translations.yml new file mode 100644 index 0000000000..9c9f6a4ce7 --- /dev/null +++ b/.github/workflows/merge-translations.yml @@ -0,0 +1,87 @@ +name: Merge translations + +on: + workflow_call: + workflow_dispatch: + +permissions: {} + +env: + WEBLATE_HOST: 'https://hosted.weblate.org' + WEBLATE_COMPONENT: 'immich/immich' + +jobs: + merge: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Find translation PR + id: find_pr + run: | + gh pr list --repo $GITHUB_REPOSITORY --author weblate --json number,mergeable | read PR + echo "$PR" | jq ' + if length == 1 then + .[0].number + else + error("Expected exactly 1 entry, got \(length)") + end + ' | read PR_NUMBER + echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "$PR" | jq -e '.[0].mergeable == "MERGEABLE"' || (echo "PR is not mergeable" && exit 1) + + - name: Generate a token + id: generate_token + uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + with: + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} + private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + + - name: Lock weblate + env: + WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} + run: | + curl -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/lock/" -d lock=true + + - name: Commit translations + env: + WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} + run: | + curl -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/repository/" -d operation=commit + curl -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/repository/" -d operation=push + + - name: Merge PR + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + PR_NUMBER: ${{ steps.find_pr.outputs.PR_NUMBER }} + run: | + gh api -X POST "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews" --field event='APPROVE' --field body='Automatically merging translations PR' \ + | jq '.id' | read REVIEW_ID + echo "REVIEW_ID=$REVIEW_ID" >> $GITHUB_OUTPUT + gh pr merge "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --auto --squash + + - name: Wait for PR to merge + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + PR_NUMBER: ${{ steps.find_pr.outputs.PR_NUMBER }} + REVIEW_ID: ${{ steps.merge_pr.outputs.REVIEW_ID }} + run: | + for i in {1..10}; do + if gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json merged | jq -e '.merged == true'; then + echo "PR merged" + exit 0 + else + echo "PR not merged yet, waiting..." + sleep 6 + fi + done + echo "PR did not merge in time" + gh api -X PUT "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews/$REVIEW_ID/dismissals" --field message='Merge attempt timed out' --field event='DISMISS' + gh pr merge "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --disable-auto + exit 1 + + - name: Unlock weblate + env: + WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} + run: | + curl -X POST -H "Authorization: Token $WEBLATE_TOKEN" "$WEBLATE_HOST/api/components/$WEBLATE_COMPONENT/lock/" -d lock=false diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 70882ea201..add0ba7ae4 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -24,6 +24,15 @@ concurrency: permissions: {} jobs: + merge_translations: + uses: ./.github/workflows/merge-translations.yml + permissions: + pull-requests: write + secrets: + PUSH_O_MATIC_APP_ID: ${{ secrets.PUSH_O_MATIC_APP_ID }} + PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }} + WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} + bump_version: runs-on: ubuntu-latest outputs: diff --git a/.github/workflows/weblate-lock.yml b/.github/workflows/weblate-lock.yml index f732fe2a49..0bbd9c1b15 100644 --- a/.github/workflows/weblate-lock.yml +++ b/.github/workflows/weblate-lock.yml @@ -1,6 +1,7 @@ name: Weblate checks on: + pull_request_review: pull_request: branches: [main] @@ -32,19 +33,14 @@ jobs: permissions: {} if: ${{ needs.pre-job.outputs.should_run == 'true' }} steps: - - name: Check weblate lock + - name: Bot review status + env: + PR_NUMBER: ${{ github.event.pull_request.number || github.event.pull_request_review.pull_request.number }} run: | - if [[ "false" = $(curl https://hosted.weblate.org/api/components/immich/immich/lock/ | jq .locked) ]]; then - exit 1 - fi - - name: Find Pull Request - uses: juliangruber/find-pull-request-action@952b3bb1ddb2dcc0aa3479e98bb1c2d1a922f096 # v1.10.0 - id: find-pr - with: - branch: chore/translations - - name: Fail if existing weblate PR - if: ${{ steps.find-pr.outputs.number }} - run: exit 1 + # Then check for APPROVED by the bot, if absent fail + gh pr view "$PR_NUMBER" --json reviews | jq -e '.reviews | map(select(.author.login == "github-actions[bot]" and .state == "APPROVED")) | length > 0' \ + || (echo "The push-o-matic bot has not approved this PR yet" && exit 1) + success-check-lock: name: Weblate Lock Check Success needs: [enforce-lock]