name: bake-plugin-previews-release # Release-cut full bake (spec slice 3). On a push to a release/** branch, render # every preview (hash-skip reuses unchanged), publish the immutable clips to R2, # and commit the authoritative manifest snapshot onto the release branch. The # manifest then flows to main via the normal non-squash release back-merge. # # Bumping BAKE_VERSION at release time is the explicit lever to refresh shared- # input changes (fonts / web CSS / design systems / renderer) that per-file # triggers cannot detect — a full sweep alone would reuse, not re-render, them. on: push: branches: ['release/**'] paths-ignore: - 'data/plugin-previews/**' # our own manifest commit must not re-trigger workflow_dispatch: {} permissions: contents: write # commit the authoritative manifest onto the release branch concurrency: group: bake-plugin-previews-release-${{ github.ref }} cancel-in-progress: false jobs: bake: name: Bake plugin previews (release cut) # `push` is already limited to release/**, but `workflow_dispatch` can be run # from any ref. The commit step pushes the authoritative manifest to the # triggering ref with contents:write, so a dispatch on `main` would write # straight to main and bypass the release back-merge/review path. Hard-gate # the whole job to release branches. if: ${{ startsWith(github.ref, 'refs/heads/release/') }} runs-on: ubuntu-24.04 timeout-minutes: 90 steps: - name: Checkout uses: actions/checkout@v6.0.2 with: ref: ${{ github.ref }} fetch-depth: 0 # Read-only clone with the default token, but do NOT persist it as a # github.com credential. release/** is guarded by the "Protected # branches (preview/*, release/v*)" ruleset (deletion / non_fast_forward # / pull_request), so a direct push is rejected (GH013) unless the pusher # is a bypass actor. github-actions[bot] is NOT one — so the manifest # writeback below force-authenticates as open-design-bot, which IS a # bypass actor, via an explicit token URL. A persisted github.token # extraheader would override that inline token on push (see #5357), so # drop it; nothing between here and the push needs the persisted cred. persist-credentials: false - name: Loop guard — skip the bake bot's own manifest commit id: guard # Belt-and-suspenders with paths-ignore above: also skip when the head # commit was authored by the bake bot, so the manifest writeback cannot # re-trigger this workflow. run: | AUTHOR="$(git log -1 --format='%ae')" echo "head commit author: $AUTHOR" if [ "$AUTHOR" = "bot@open-design.ai" ]; then echo "skip=true" >> "$GITHUB_OUTPUT" echo "head commit is the bake bot's manifest commit — skipping to break the loop" else echo "skip=false" >> "$GITHUB_OUTPUT" fi - name: Setup workspace if: ${{ steps.guard.outputs.skip != 'true' }} uses: ./.github/actions/setup-workspace - name: Render previews + publish clips to R2 if: ${{ steps.guard.outputs.skip != 'true' }} uses: ./.github/actions/bake-previews with: upload: 'true' r2-access-key-id: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_AK }} r2-secret-access-key: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_SK }} r2-bucket: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_BUCKET }} r2-endpoint: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_URL }} - name: Mint the open-design-bot token for the release-branch push if: ${{ steps.guard.outputs.skip != 'true' }} id: bot # open-design-bot (app id 3640364) is a bypass actor on the # "Protected branches (preview/*, release/v*)" ruleset, so its push clears # the pull_request rule that rejects github-actions[bot] (GH013). The # release bake pushes the manifest directly (it then rides the non-squash # release back-merge to main). Minted here — after the long render, not at # job start — because an App token lives ~1h and the render can run long. uses: actions/create-github-app-token@v3.2.0 with: client-id: ${{ secrets.BOT_APP_CLIENT_ID }} private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} owner: nexu-io repositories: open-design - name: Commit the authoritative manifest onto the release branch if: ${{ steps.guard.outputs.skip != 'true' }} env: BOT_TOKEN: ${{ steps.bot.outputs.token }} run: | OLD=data/plugin-previews/manifest.json NEW=.tmp/plugin-previews/manifest.json # Capture on its own line so `set -e` surfaces a helper error (exit 2) # rather than command substitution swallowing it into the test. diff_result="$(node scripts/plugin-previews-diff.mjs "$OLD" "$NEW")" case "$diff_result" in changed) ;; unchanged) echo "previews unchanged — nothing to commit"; exit 0 ;; *) echo "unexpected diff result: '$diff_result'" >&2; exit 1 ;; esac cp "$NEW" "$OLD" git config user.name "open-design-bot" git config user.email "bot@open-design.ai" git add "$OLD" git commit -m "chore(plugin-previews): refresh baked preview manifest (release cut)" # Push as open-design-bot (a ruleset bypass actor) via an explicit token # URL. A plain `git push origin` would authenticate as github-actions[bot] # and be rejected by the release/** pull_request rule (GH013). git push "https://x-access-token:${BOT_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:${GITHUB_REF#refs/heads/}"