diff --git a/.github/workflows/docs-sync-to-example.yml b/.github/workflows/docs-sync-to-example.yml index 368852df..bb3f084d 100644 --- a/.github/workflows/docs-sync-to-example.yml +++ b/.github/workflows/docs-sync-to-example.yml @@ -11,6 +11,19 @@ on: - main paths: - "sdk/next/tutorials/example/**" + # Manual trigger. The push trigger only fires on tutorial .mdx changes, so a + # workflow-only fix cannot re-run the sync, and a sync PR left stale by a + # failed run has no way to catch up until the next docs edit. + workflow_dispatch: + +# Serialize runs. Two overlapping runs would each check out the same revision of +# the open sync branch, commit divergently, and the loser's push would be +# rejected as non-fast-forward, silently dropping its update. Queue instead of +# cancelling: the transform is deterministic from the source docs, so the last +# run to finish always produces the correct final state. +concurrency: + group: docs-sync-to-example + cancel-in-progress: false jobs: sync: @@ -37,6 +50,41 @@ jobs: path: cosmos-example persist-credentials: false + # Select the target branch BEFORE transforming. If an open sync PR exists we + # check its branch out while the tree is still clean, so the transform writes + # directly onto that branch. Switching branches after the transform would + # either abort ("local changes would be overwritten") or, via stash, conflict + # against changes the branch already carries and exit 1 under bash -e. + - name: Select target branch + id: target + env: + GH_TOKEN: ${{ secrets.EXAMPLE_REPO_TOKEN }} + run: | + cd cosmos-example + + EXISTING=$(gh pr list \ + --repo cosmos/example \ + --label "docs-sync" \ + --state open \ + --json number,headRefName \ + --jq '.[0]') + + if [ -n "$EXISTING" ]; then + PR_NUMBER=$(echo "$EXISTING" | jq -r '.number') + BRANCH=$(echo "$EXISTING" | jq -r '.headRefName') + git fetch origin "refs/heads/${BRANCH}:refs/remotes/origin/${BRANCH}" + git checkout -B "$BRANCH" "origin/${BRANCH}" + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + echo "Reusing open sync PR #$PR_NUMBER on branch $BRANCH" + else + BRANCH="docs-sync/from-docs-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$BRANCH" + echo "pr_number=" >> "$GITHUB_OUTPUT" + echo "No open sync PR, will create branch $BRANCH" + fi + + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + - name: Transform Mintlify → example repo format run: | python3 scripts/docs-sync/transform.py \ @@ -44,18 +92,11 @@ jobs: --input sdk/next/tutorials/example/ \ --output-dir cosmos-example/docs/ - - name: Check for changes - id: diff - run: | - cd cosmos-example - [ -n "$(git status --porcelain docs/)" ] \ - && echo "changed=true" >> "$GITHUB_OUTPUT" \ - || echo "changed=false" >> "$GITHUB_OUTPUT" - - - name: Open or update PR on cosmos/example - if: steps.diff.outputs.changed == 'true' + - name: Commit, then open or update the PR on cosmos/example env: GH_TOKEN: ${{ secrets.EXAMPLE_REPO_TOKEN }} + BRANCH: ${{ steps.target.outputs.branch }} + PR_NUMBER: ${{ steps.target.outputs.pr_number }} run: | cd cosmos-example @@ -63,50 +104,28 @@ jobs: git config user.email "docs-sync[bot]@users.noreply.github.com" git remote set-url origin "https://x-access-token:${{ secrets.EXAMPLE_REPO_TOKEN }}@github.com/cosmos/example.git" - # Check for an existing open sync PR - EXISTING=$(gh pr list \ - --repo cosmos/example \ - --label "docs-sync" \ - --state open \ - --json number,headRefName \ - --jq '.[0]') + git add docs/ - if [ -n "$EXISTING" ]; then - PR_NUMBER=$(echo "$EXISTING" | jq -r '.number') - BRANCH=$(echo "$EXISTING" | jq -r '.headRefName') + if git diff --cached --quiet; then + echo "No doc changes to sync, nothing to commit." + exit 0 + fi - # Stash the transform output before switching branches, - # then restore it on top of the existing sync branch. - git stash - git fetch origin "$BRANCH" - git checkout "$BRANCH" - git stash pop - git add docs/ - - if git diff --cached --quiet; then - echo "Existing sync branch is already up to date — nothing to commit." - else - git commit -m "docs: sync example tutorials from cosmos/docs [docs-sync]" - git push origin "$BRANCH" - - gh pr comment "$PR_NUMBER" \ - --repo cosmos/example \ - --body "Sync updated: cosmos/docs was updated before this PR merged. Branch has been refreshed — please re-review." - echo "Updated existing PR #$PR_NUMBER" - fi + git commit -m "docs: sync example tutorials from cosmos/docs [docs-sync]" + git push origin "HEAD:refs/heads/${BRANCH}" + if [ -n "$PR_NUMBER" ]; then + gh pr comment "$PR_NUMBER" \ + --repo cosmos/example \ + --body "Sync updated: cosmos/docs was updated before this PR merged. Branch has been refreshed, please re-review." + echo "Updated existing PR #$PR_NUMBER" else - BRANCH="docs-sync/from-docs-$(date +%Y%m%d-%H%M%S)" - git checkout -b "$BRANCH" - git add docs/ - git commit -m "docs: sync example tutorials from cosmos/docs [docs-sync]" - git push origin "$BRANCH" - gh pr create \ --repo cosmos/example \ --head "$BRANCH" \ --base main \ --title "docs: sync example tutorials from cosmos/docs" \ --label "docs-sync" \ - --body "Auto-synced from cosmos/docs. Transforms sdk/next/tutorials/example/*.mdx to docs/*.md. Do not edit docs/ files directly in cosmos/example — edit the source and let the sync bot update them." + --body "Auto-synced from cosmos/docs. Transforms sdk/next/tutorials/example/*.mdx to docs/*.md. Do not edit docs/ files directly in cosmos/example, edit the source and let the sync bot update them." + echo "Opened a new sync PR on branch $BRANCH" fi