Sync SDK repos #1735
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: Sync SDK repos | |
| # Keeps the staging and production trunks in sync and the config repo's | |
| # tracking files fresh. Each job self-routes by repo + event, so this one file | |
| # can live in both repos and only the right job runs. The dispatch jobs are | |
| # eager-only — the polls cover them if a dispatch fails. | |
| on: | |
| schedule: | |
| # back-sync poll: a cheap pure-git check, twice hourly so an unsynced production | |
| # change (e.g. a community PR between releases) can't hold codegen for long. | |
| - cron: '7,37 * * * *' | |
| workflow_dispatch: {} | |
| # repository_dispatch: | |
| # types: [prod-released] | |
| # release: | |
| # types: [published] | |
| push: | |
| # main only. stlc preview/integrated/codegen branches never push to main. | |
| branches: [main] | |
| jobs: | |
| back-sync: | |
| # Fast-forward production main back onto staging so the trunks stay identical. | |
| runs-on: ubuntu-latest | |
| if: >- | |
| github.repository == 'togethercomputer/together-typescript-staging' && | |
| (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch') | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: stlc-back-sync | |
| cancel-in-progress: true | |
| env: | |
| PRODUCTION_REPO: togethercomputer/together-typescript | |
| steps: | |
| - name: Check out staging | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Mint STLC app token (scoped to SDK staging repos) | |
| id: app-token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| client-id: ${{ secrets.STLC_WORKFLOW_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.STLC_WORKFLOW_APP_PRIVATE_KEY }} | |
| owner: togethercomputer | |
| repositories: | | |
| together-typescript-staging | |
| together-typescript | |
| - name: Configure auth for github | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| git config --global user.name "stlc-workflow-app[bot]" | |
| git config --global user.email "287504455+stlc-workflow-app[bot]@users.noreply.github.com" | |
| gh auth setup-git | |
| - name: Fetch production main | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| git remote add production "https://x-access-token:${GH_TOKEN}@github.com/${PRODUCTION_REPO}.git" | |
| git fetch production main | |
| - name: Check whether production is ahead of staging | |
| id: diff | |
| run: | | |
| # Content compare: would merging production into staging change its tree? | |
| # If not, staging already has production's content (release-please commits). | |
| MERGED=$(git merge-tree --write-tree origin/main production/main) || MERGED=conflict | |
| STAGING_TREE=$(git rev-parse 'origin/main^{tree}') | |
| if [ "$MERGED" = "$STAGING_TREE" ]; then | |
| echo "Staging already has production's content. Nothing to pull back." | |
| echo "behind=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "behind=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Sync production to staging (fast-forward) | |
| if: steps.diff.outputs.behind == 'true' | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| set -euo pipefail | |
| # Prefer a SHA-preserving fast-forward. If the trunks have forked | |
| # (staging is not an ancestor of production), open a heal PR instead. | |
| if git -c credential.helper= merge-base --is-ancestor origin/main production/main; then | |
| git -c credential.helper= push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" production/main:refs/heads/main | |
| echo "Fast-forwarded staging/main to production/main." | |
| exit 0 | |
| fi | |
| echo "::warning title=Back-sync blocked::staging main is not an ancestor of production/main — opening a heal PR." | |
| branch="stlc/heal-promote-ancestry" | |
| staging_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" | |
| git checkout -B "$branch" origin/main | |
| if ! git -c credential.helper= merge production/main -m "Merge production into staging (heal promote ancestry)"; then | |
| echo "::error title=Heal merge conflicted::Could not auto-merge production into staging. Resolve ancestry manually." | |
| git merge --abort || true | |
| exit 1 | |
| fi | |
| git -c credential.helper= push --force "$staging_url" "HEAD:refs/heads/$branch" | |
| EXISTING_PR=$(gh pr list \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --head "$branch" \ | |
| --state open \ | |
| --json number \ | |
| --jq '.[0].number // empty') | |
| if [ -z "$EXISTING_PR" ]; then | |
| gh pr create \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --base main \ | |
| --head "$branch" \ | |
| --title "Merge production into staging (heal promote ancestry)" \ | |
| --reviewer blainekasten \ | |
| --body "$(cat <<'EOF' | |
| Staging and production have forked, so a fast-forward back-sync is unsafe. | |
| This PR merges `production/main` into staging to restore ancestry so promote/back-sync can fast-forward again. Review carefully — both trunks had unique commits. | |
| EOF | |
| )" | |
| echo "Opened heal PR and requested review from blainekasten." | |
| else | |
| echo "Heal PR #${EXISTING_PR} already open; branch force-pushed." | |
| gh pr edit "$EXISTING_PR" --repo "${GITHUB_REPOSITORY}" --add-reviewer blainekasten || true | |
| fi | |
| # Optional alerting workflow. | |
| # If we want to add this back in, we need an incoming webhook (Slack/Teams/etc) that the workflow POSTs to when a run fails, so a stalled build or tracking sync pages a human instead of sitting red in the Actions tab. | |
| # | |
| # - name: Alert on failure | |
| # if: failure() | |
| # env: | |
| # ALERT_WEBHOOK_URL: ${{ secrets.STLC_ALERT_WEBHOOK_URL }} | |
| # run: | | |
| # run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| # msg="stlc back-sync (sync from production) failed in ${{ github.repository }}. A stalled back-sync lets custom-code tracking drift, which later builds refuse on — investigate before the next build. Run: $run_url" | |
| # echo "::error title=stlc workflow failed::$msg" | |
| # { echo "### ⚠️ stlc workflow failed"; echo ""; echo "$msg"; } >> "$GITHUB_STEP_SUMMARY" | |
| # if [ -n "${ALERT_WEBHOOK_URL:-}" ]; then | |
| # curl -sS -X POST -H 'Content-Type: application/json' \ | |
| # -d "$(jq -n --arg text "$msg" '{text:$text}')" "$ALERT_WEBHOOK_URL" \ | |
| # || echo "::warning::Alert webhook POST failed" | |
| # fi | |
| # notify-back-sync: | |
| # # On a published release, tell staging to back-sync now instead of waiting for | |
| # # the poll. Dispatch-only: it cannot write production or staging contents. | |
| # runs-on: ubuntu-latest | |
| # if: >- | |
| # github.repository == 'togethercomputer/together-typescript' && | |
| # (github.event_name == 'release' || github.event_name == 'workflow_dispatch') | |
| # permissions: | |
| # contents: read | |
| # env: | |
| # STAGING_REPO: togethercomputer/together-typescript-staging | |
| # steps: | |
| # - name: Mint STLC app token (scoped to SDK staging repos) | |
| # id: app-token | |
| # uses: actions/create-github-app-token@v3 | |
| # with: | |
| # client-id: ${{ secrets.STLC_WORKFLOW_APP_CLIENT_ID }} | |
| # private-key: ${{ secrets.STLC_WORKFLOW_APP_PRIVATE_KEY }} | |
| # owner: togethercomputer | |
| # repositories: together-typescript-staging | |
| # - name: Dispatch back-sync to staging | |
| # env: | |
| # DISPATCH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| # REF_NAME: ${{ github.ref_name }} | |
| # run: | | |
| # set -euo pipefail | |
| # payload=$(jq -n --arg ref "$REF_NAME" '{event_type:"prod-released",client_payload:{ref:$ref}}') | |
| # code=$(curl -sS -o /tmp/dispatch.txt -w '%{http_code}' -X POST \ | |
| # -H "Authorization: Bearer ${DISPATCH_TOKEN}" \ | |
| # -H "Accept: application/vnd.github+json" \ | |
| # -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| # "https://api.github.com/repos/${STAGING_REPO}/dispatches" \ | |
| # -d "$payload") | |
| # if [ "$code" = "204" ]; then | |
| # echo "Back-sync dispatched to ${STAGING_REPO}." | |
| # else | |
| # echo "Dispatch failed (HTTP $code)" >&2; cat /tmp/dispatch.txt >&2; exit 1 | |
| # fi | |
| # Optional alerting workflow. | |
| # If we want to add this back in, we need an incoming webhook (Slack/Teams/etc) that the workflow POSTs to when a run fails, so a stalled build or tracking sync pages a human instead of sitting red in the Actions tab. | |
| # | |
| # - name: Alert on failure | |
| # if: failure() | |
| # env: | |
| # ALERT_WEBHOOK_URL: ${{ secrets.STLC_ALERT_WEBHOOK_URL }} | |
| # run: | | |
| # run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| # msg="stlc release back-sync trigger failed in ${{ github.repository }} — the staging repo was NOT notified to back-sync this release. Staging catches up on its next poll, but verify the app token. Run: $run_url" | |
| # echo "::error title=stlc workflow failed::$msg" | |
| # { echo "### ⚠️ stlc workflow failed"; echo ""; echo "$msg"; } >> "$GITHUB_STEP_SUMMARY" | |
| # if [ -n "${ALERT_WEBHOOK_URL:-}" ]; then | |
| # curl -sS -X POST -H 'Content-Type: application/json' \ | |
| # -d "$(jq -n --arg text "$msg" '{text:$text}')" "$ALERT_WEBHOOK_URL" \ | |
| # || echo "::warning::Alert webhook POST failed" | |
| # fi | |
| seal-dispatch: | |
| # When out-of-band custom code lands on staging main, tell the config repo to | |
| # re-seal now instead of waiting for its scheduled sync. The loop guards skip | |
| # stlc's own pushes, so the bot's commits can't trigger a re-seal loop. | |
| runs-on: ubuntu-latest | |
| if: >- | |
| github.repository == 'togethercomputer/together-typescript-staging' && | |
| github.event_name == 'push' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: seal-dispatch-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| CONFIG_REPO: togethercomputer/openapi | |
| steps: | |
| - name: Mint STLC app token (scoped to SDK staging repos) | |
| id: app-token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| client-id: ${{ secrets.STLC_WORKFLOW_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.STLC_WORKFLOW_APP_PRIVATE_KEY }} | |
| owner: togethercomputer | |
| repositories: openapi | |
| - name: Loop-guard and send re-seal dispatch | |
| env: | |
| DISPATCH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| HEAD_MSG: ${{ github.event.head_commit.message }} | |
| HEAD_AUTHOR_NAME: ${{ github.event.head_commit.author.name }} | |
| ACTOR: ${{ github.actor }} | |
| SHA: ${{ github.sha }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| # Loop guard 1: skip pushes by the workflow app (back-sync FF, stlc regenerate). | |
| # App-token pushes re-trigger workflows; commit author/message alone miss back-sync | |
| # tips authored by release-please/humans. | |
| if [ "$ACTOR" = "stlc-workflow-app[bot]" ]; then | |
| echo "Push actor is stlc-workflow-app[bot] — skipping re-seal dispatch." | |
| exit 0 | |
| fi | |
| # Loop guard 2: skip the stlc "Build SDK" squash commit (Stainless-Generated-From trailer). | |
| if printf '%s' "$HEAD_MSG" | grep -q 'Stainless-Generated-From'; then | |
| echo "Head commit is an stlc build — skipping re-seal dispatch." | |
| exit 0 | |
| fi | |
| # Loop guard 3: skip stlc-workflow-app[bot] commits (e.g. a regeneration commit). | |
| if [ "$HEAD_AUTHOR_NAME" = "stlc-workflow-app[bot]" ]; then | |
| echo "Head commit authored by stlc-workflow-app[bot] — skipping re-seal dispatch." | |
| exit 0 | |
| fi | |
| payload=$(jq -n --arg sha "$SHA" --arg repo "$REPO" \ | |
| '{event_type:"seal-custom-code",client_payload:{target:"all",sha:$sha,repo:$repo}}') | |
| code=$(curl -sS -o /tmp/dispatch.txt -w '%{http_code}' -X POST \ | |
| -H "Authorization: Bearer ${DISPATCH_TOKEN}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "https://api.github.com/repos/${CONFIG_REPO}/dispatches" \ | |
| -d "$payload") | |
| if [ "$code" = "204" ]; then | |
| echo "Re-seal dispatched to ${CONFIG_REPO}." | |
| else | |
| echo "Dispatch failed (HTTP $code)" >&2; cat /tmp/dispatch.txt >&2; exit 1 | |
| fi | |
| # Optional alerting workflow. | |
| # If we want to add this back in, we need an incoming webhook (Slack/Teams/etc) that the workflow POSTs to when a run fails, so a stalled build or tracking sync pages a human instead of sitting red in the Actions tab. | |
| # | |
| # - name: Alert on failure | |
| # if: failure() | |
| # env: | |
| # ALERT_WEBHOOK_URL: ${{ secrets.STLC_ALERT_WEBHOOK_URL }} | |
| # run: | | |
| # run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| # msg="stlc seal-dispatch failed in ${{ github.repository }} — the config repo was NOT notified to re-seal. The config repo's scheduled sync is the backstop, but verify the app token. Run: $run_url" | |
| # echo "::error title=stlc workflow failed::$msg" | |
| # { echo "### ⚠️ stlc workflow failed"; echo ""; echo "$msg"; } >> "$GITHUB_STEP_SUMMARY" | |
| # if [ -n "${ALERT_WEBHOOK_URL:-}" ]; then | |
| # curl -sS -X POST -H 'Content-Type: application/json' \ | |
| # -d "$(jq -n --arg text "$msg" '{text:$text}')" "$ALERT_WEBHOOK_URL" \ | |
| # || echo "::warning::Alert webhook POST failed" | |
| # fi |