diff --git a/.github/workflows/backfill-episodes.yml b/.github/workflows/backfill-episodes.yml index e3486de..1504488 100644 --- a/.github/workflows/backfill-episodes.yml +++ b/.github/workflows/backfill-episodes.yml @@ -57,7 +57,7 @@ jobs: - name: Install dependencies run: pnpm install - name: Backfill all episodes - run: pnpm tsx scripts/publish-episodes.ts --backfill + run: pnpm tsx scripts/publish-atproto-episodes.ts --backfill env: ATPROTO_HANDLE: ${{ secrets.ATPROTO_HANDLE }} ATPROTO_APP_PASSWORD: ${{ secrets.ATPROTO_APP_PASSWORD }} diff --git a/.github/workflows/publish-episodes.yml b/.github/workflows/publish-episodes.yml index e0deea6..cbafa48 100644 --- a/.github/workflows/publish-episodes.yml +++ b/.github/workflows/publish-episodes.yml @@ -79,20 +79,53 @@ jobs: # the site build runs while the publish job is installing dependencies. # The publish script then waits for the new episode pages to be live # before publishing documents that link to them. + # + # The rebuild-hash cache tracks which feed content we already rebuilt + # for. The publish-success hash (.feed-hash) is intentionally separate: + # a persistently failing publish must keep retrying, but must not + # trigger a fresh Vercel build on every 30-minute poll. + - name: Restore last rebuild trigger hash + if: steps.config.outputs.configured == 'true' + uses: actions/cache/restore@v4 + with: + path: .rebuild-hash + key: rebuild-hash- + restore-keys: | + rebuild-hash- - name: Trigger site rebuild if: steps.config.outputs.configured == 'true' && steps.feed.outputs.changed == 'true' id: rebuild env: REBUILD_WEBHOOK: ${{ secrets.REBUILD_WEBHOOK }} + FEED_HASH: ${{ steps.feed.outputs.hash }} run: | - if [ -n "$REBUILD_WEBHOOK" ]; then - curl -sf -X POST "$REBUILD_WEBHOOK" > /dev/null - echo "triggered=true" >> "$GITHUB_OUTPUT" - echo "Triggered site rebuild." - else + if [ -z "$REBUILD_WEBHOOK" ]; then echo "triggered=false" >> "$GITHUB_OUTPUT" + echo "fired=false" >> "$GITHUB_OUTPUT" echo "::notice::REBUILD_WEBHOOK not set — publishing without waiting for a site rebuild." + exit 0 + fi + if [ -n "$FEED_HASH" ] && [ "$FEED_HASH" = "$(cat .rebuild-hash 2>/dev/null || true)" ]; then + # triggered=true so the publish script still waits for the pages + # from that earlier build before publishing. + echo "triggered=true" >> "$GITHUB_OUTPUT" + echo "fired=false" >> "$GITHUB_OUTPUT" + echo "Site already rebuilt for this feed content — not triggering again." + exit 0 fi + curl -sf -X POST "$REBUILD_WEBHOOK" > /dev/null + echo "triggered=true" >> "$GITHUB_OUTPUT" + echo "fired=true" >> "$GITHUB_OUTPUT" + echo "Triggered site rebuild." + - name: Record rebuild trigger hash + if: steps.rebuild.outputs.fired == 'true' && steps.feed.outputs.hash != '' + run: printf '%s' "${{ steps.feed.outputs.hash }}" > .rebuild-hash + - name: Save rebuild trigger hash + if: steps.rebuild.outputs.fired == 'true' && steps.feed.outputs.hash != '' + uses: actions/cache/save@v4 + with: + path: .rebuild-hash + key: rebuild-hash-${{ steps.feed.outputs.hash }}-${{ github.run_id }}-${{ github.run_attempt }} publish: needs: gate @@ -119,7 +152,7 @@ jobs: - name: Install dependencies run: pnpm install - name: Publish new episodes - run: pnpm tsx scripts/publish-episodes.ts + run: pnpm tsx scripts/publish-atproto-episodes.ts env: ATPROTO_HANDLE: ${{ secrets.ATPROTO_HANDLE }} ATPROTO_APP_PASSWORD: ${{ secrets.ATPROTO_APP_PASSWORD }} diff --git a/README.md b/README.md index 4c9416d..8c51e0d 100644 --- a/README.md +++ b/README.md @@ -179,10 +179,10 @@ You can also publish locally: ```bash # Publish only new episodes -pnpm publish:episodes +pnpm publish:atproto # Backfill all episodes -pnpm publish:episodes:backfill +pnpm publish:atproto:backfill ``` ##### Verification diff --git a/package.json b/package.json index 948d2c8..4e9de4f 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "lint:fix": "eslint . --fix", "preview": "astro preview", "start": "astro dev", - "publish:episodes": "tsx scripts/publish-episodes.ts", - "publish:episodes:backfill": "tsx scripts/publish-episodes.ts --backfill", + "publish:atproto": "tsx scripts/publish-atproto-episodes.ts", + "publish:atproto:backfill": "tsx scripts/publish-atproto-episodes.ts --backfill", "test": "concurrently \"pnpm:test:*(!fix)\" --names \"test:\"", "test:e2e": "pnpm exec playwright test", "test:unit": "vitest" diff --git a/scripts/publish-episodes.ts b/scripts/publish-atproto-episodes.ts similarity index 93% rename from scripts/publish-episodes.ts rename to scripts/publish-atproto-episodes.ts index e2a1126..4010972 100644 --- a/scripts/publish-episodes.ts +++ b/scripts/publish-atproto-episodes.ts @@ -12,8 +12,8 @@ * STANDARD_SITE_PUBLICATION_RKEY - The publication record key * * Usage: - * pnpm publish:episodes # publish new episodes only - * pnpm publish:episodes:backfill # publish all episodes (backfill) + * pnpm publish:atproto # publish new episodes only + * pnpm publish:atproto:backfill # publish all episodes (backfill) */ import { htmlToText } from 'html-to-text'; @@ -62,24 +62,17 @@ async function waitForPage(url: string) { } } +// Only validate the fields this script actually uses — requiring unused +// fields makes publishing break whenever the feed host changes something +// irrelevant (e.g. Flightcast's image enclosures have no `type`). const FeedSchema = object({ items: array( object({ - id: string(), title: string(), published: number(), description: string(), content_encoded: optional(string()), - itunes_duration: number(), - itunes_episode: optional(number()), - itunes_episodeType: optional(string()), - itunes_image: optional(object({ href: optional(string()) })), - enclosures: array( - object({ - url: string(), - type: string() - }) - ) + itunes_episodeType: optional(string()) }) ) });