Repository navigation
chore: fix Keystatic app slug and update GitHub Actions versions #163
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 static website to Cloudflare Pages | |
| on: | |
| push: | |
| branches: ["main"] | |
| paths-ignore: | |
| - "apps/admin/**" | |
| - ".github/workflows/deploy-admin.yml" | |
| - "README.md" | |
| - "docs/**" | |
| workflow_dispatch: | |
| inputs: | |
| site_ref: | |
| description: Website branch, tag, or commit to deploy | |
| required: true | |
| default: main | |
| type: string | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: production-pages | |
| cancel-in-progress: true | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| site_sha: ${{ steps.revision.outputs.sha }} | |
| steps: | |
| - name: Check out website revision | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ inputs.site_ref || github.sha }} | |
| persist-credentials: false | |
| - name: Record source revision | |
| id: revision | |
| run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | |
| - name: Set up pnpm | |
| uses: pnpm/action-setup@v6 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| cache: pnpm | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Restore generated-image cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: | | |
| public/optimized | |
| .cache/image-optimizer.json | |
| key: ${{ runner.os }}-images-v4-${{ hashFiles('public/uploads/**', 'public/team/members/**', 'public/team/activities/**', 'scripts/optimize-all-images.ts', 'src/lib/image-widths.ts') }} | |
| restore-keys: | | |
| ${{ runner.os }}-images-v4- | |
| - name: Validate website and committed editorial content | |
| run: pnpm check | |
| - name: Audit dependencies | |
| run: pnpm audit:dependencies | |
| - name: Build static export | |
| run: pnpm build:prepared | |
| - name: Upload static export | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: static-export | |
| path: out | |
| if-no-files-found: error | |
| retention-days: 3 | |
| deploy: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: website-production | |
| url: https://preferred.ai | |
| steps: | |
| - name: Download static export | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: static-export | |
| path: out | |
| - name: Reassert static Pages boundaries | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| test ! -e out/_worker.js | |
| test ! -e out/_routes.json | |
| test ! -d out/functions | |
| test -z "$(find out -type l -print -quit)" | |
| file_count="$(find out -type f | wc -l | tr -d ' ')" | |
| test "$file_count" -le 20000 | |
| while IFS= read -r file; do | |
| size="$(stat -c '%s' "$file")" | |
| test "$size" -le 26214400 | |
| done < <(find out -type f) | |
| - name: Reject a superseded automatic deployment | |
| if: github.event_name == 'push' | |
| id: freshness | |
| env: | |
| EXPECTED_SHA: ${{ needs.build.outputs.site_sha }} | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| current_sha="$(gh api "repos/$GITHUB_REPOSITORY/commits/main" --jq .sha)" | |
| if [[ "$current_sha" == "$EXPECTED_SHA" ]]; then | |
| echo "current=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "current=false" >> "$GITHUB_OUTPUT" | |
| echo "Skipping superseded revision $EXPECTED_SHA; main is $current_sha." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Deploy to Cloudflare Pages production | |
| if: github.event_name == 'workflow_dispatch' || steps.freshness.outputs.current == 'true' | |
| uses: cloudflare/wrangler-action@v4 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_PAGES_API_TOKEN || secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| packageManager: npm | |
| wranglerVersion: 4.124.0 | |
| command: pages deploy ./out --project-name=preferredai --branch=main --commit-hash=${{ needs.build.outputs.site_sha }} | |
| - name: Smoke-test production | |
| if: github.event_name == 'workflow_dispatch' || steps.freshness.outputs.current == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| index_file="$RUNNER_TEMP/preferredai-index.html" | |
| curl --fail --silent --show-error --location --retry 10 --retry-all-errors https://preferred.ai/ > "$index_file" | |
| for path in /blog /blog/page/2 /people /feed.xml /sitemap.xml /robots.txt /Brand.png; do | |
| curl --fail --silent --show-error --location --retry 10 --retry-all-errors "https://preferred.ai$path" > /dev/null | |
| done | |
| static_asset="$(grep -o '/_next/static/[^" ]*' "$index_file" | sed -n '1p')" | |
| test -n "$static_asset" | |
| curl --fail --silent --show-error --location "https://preferred.ai$static_asset" > /dev/null | |
| media_asset="$(grep -o '/optimized/[^" ]*' "$index_file" | sed -n '1p')" | |
| test -n "$media_asset" | |
| curl --fail --silent --show-error --location "https://preferred.ai$media_asset" > /dev/null | |
| missing_status="$(curl --silent --output /dev/null --write-out '%{http_code}' https://preferred.ai/__deployment-smoke-missing__)" | |
| test "$missing_status" = "404" | |
| - name: Record deployment provenance | |
| if: github.event_name == 'workflow_dispatch' || steps.freshness.outputs.current == 'true' | |
| env: | |
| SITE_SHA: ${{ needs.build.outputs.site_sha }} | |
| run: | | |
| { | |
| echo "### Preferred.AI production deployment" | |
| echo "- Website and content: \`$SITE_SHA\`" | |
| } >> "$GITHUB_STEP_SUMMARY" |