fix(sync): fold re-keyed upstream duplicates into their existing id (… #184
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: Deploy to GitHub Pages | |
| # Deployment depends on the same verify workflow CI runs, and publishes the | |
| # artifact that workflow produced. A commit whose tests, lint, catalog | |
| # validation or bundle budget fail cannot reach the live site — previously this | |
| # workflow ran only `npm run build`, so a red CI run and a green deploy could | |
| # happen from the same push. | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| # One deployment at a time, but never cancel one that is already running. | |
| # | |
| # `cancel-in-progress: true` looks right for a deploy and is not. Pages keys a | |
| # deployment by the commit SHA, and `actions/deploy-pages` cancels its | |
| # deployment when it is interrupted or times out — after which that SHA is | |
| # spent. A later run for the same commit does not retry it; it is rejected | |
| # immediately with "Deployment cancelled", and re-running the job cannot help | |
| # because the commit is the identity. Recovering needs a *new* commit. | |
| # | |
| # On 2026-08-06 two merges landed four minutes apart. The second cancelled the | |
| # first mid-deployment, which left a deployment stuck in progress; that blocked | |
| # the next one with "due to in progress deployment", and once it was cancelled | |
| # to clear the block, every retry of the same commit failed instantly. The site | |
| # served a two-day-old catalog through all of it. | |
| # | |
| # Queued runs still collapse to the newest, which is the behaviour worth having: | |
| # skip the middle, never interrupt the one in flight. | |
| concurrency: | |
| group: pages | |
| cancel-in-progress: false | |
| jobs: | |
| # Everything that gates what is about to be published, and nothing that does | |
| # not. `site_only` drops the Expo app's job — it verifies an app this deploy | |
| # never publishes, and its dependency check consults a remote version map, so | |
| # it went red and skipped this deploy on four separate days without a single | |
| # line of the site changing. See the note at the top of verify.yml. | |
| verify: | |
| uses: ./.github/workflows/verify.yml | |
| with: | |
| site_only: true | |
| package: | |
| needs: verify | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: site | |
| path: dist | |
| - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6 | |
| - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 | |
| with: | |
| path: dist | |
| deploy: | |
| needs: package | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| # Only for `public/data/pricing.json`, which the confirmation step below | |
| # compares against what the site is actually serving. | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| sparse-checkout: public/data/pricing.json | |
| sparse-checkout-cone-mode: false | |
| - id: deployment | |
| uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 | |
| with: | |
| # The action's default is ten minutes, and on 2026-08-06 every run hit | |
| # it while Pages was still reporting `deployment_queued`. The action | |
| # then cancels and fails — but Pages went on to publish afterwards, so | |
| # the site was correct and current while eight consecutive deploy runs | |
| # showed red. A job that reports failure on a deployment that | |
| # succeeded is worse than a slow one: it is the freshness alert's | |
| # third failure mode inverted, and a permanently red deploy is one | |
| # nobody reads when it finally means something. | |
| # | |
| # Ten minutes is the action's ceiling, not its default — asking for | |
| # more is accepted, warned about, and ignored: | |
| # | |
| # Warning: timeout value is greater than the allowed maximum - | |
| # timeout set to the maximum of 600000 milliseconds. | |
| # | |
| # So the wait cannot be extended, and on a slow morning this step will | |
| # fail while Pages goes on to publish. That makes its verdict | |
| # unreliable in one direction only, which the next step settles by | |
| # reading the site. | |
| timeout: 600000 | |
| continue-on-error: true | |
| # The deployment is what the visitor gets, so that is what decides whether | |
| # this job passed. The step above reports on its own timer; this one reads | |
| # `generatedAt` off the live catalog and compares it with the artifact just | |
| # built. A slow-but-successful deploy goes green, a genuinely failed one | |
| # still goes red, and neither depends on how long Pages happened to take. | |
| - name: Confirm the site is serving this build | |
| if: always() | |
| run: | | |
| set -uo pipefail | |
| expected=$(jq -r '.generatedAt' public/data/pricing.json) | |
| expected_revision="${GITHUB_SHA}" | |
| echo "expecting revision=$expected_revision and generatedAt=$expected" | |
| for attempt in $(seq 1 40); do | |
| live_revision=$(curl -sS --max-time 20 "https://promptspend.com/build.json" \ | |
| | jq -r '.revision' 2>/dev/null || echo '') | |
| live=$(curl -sS --max-time 20 "https://promptspend.com/data/pricing.json" \ | |
| | jq -r '.generatedAt' 2>/dev/null || echo '') | |
| if [ "$live_revision" = "$expected_revision" ] && [ "$live" = "$expected" ]; then | |
| echo "✓ the site is serving this build (after ${attempt} check(s))" | |
| exit 0 | |
| fi | |
| echo " serving revision=$live_revision generatedAt=$live — waiting" | |
| sleep 30 | |
| done | |
| echo "::error title=Deploy::The site is still serving revision=$live_revision generatedAt=$live, not revision=$expected_revision generatedAt=$expected, 20 minutes after publication." | |
| exit 1 |