Next 16 #129
Workflow file for this run
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: Preview | |
| on: | |
| pull_request: | |
| types: [opened, reopened, synchronize, ready_for_review] | |
| concurrency: | |
| group: preview-pr-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| jobs: | |
| preview: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Abort for draft PRs | |
| if: github.event.pull_request.draft == true | |
| run: | | |
| echo "Draft PRs do not get previews. Skipping preview deployment." | |
| exit 0 | |
| - name: Resolve workers.dev subdomain | |
| id: workers_dev | |
| env: | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${CLOUDFLARE_ACCOUNT_ID:-}" ] || [ -z "${CLOUDFLARE_API_TOKEN:-}" ]; then | |
| echo "Missing Cloudflare credentials; cannot resolve workers.dev subdomain." >&2 | |
| exit 1 | |
| fi | |
| response=$(curl -sSL \ | |
| -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/subdomain") | |
| success=$(echo "$response" | jq -r '.success // false') | |
| if [ "$success" != "true" ]; then | |
| echo "workers.dev subdomain lookup failed" >&2 | |
| exit 1 | |
| fi | |
| subdomain=$(echo "$response" | jq -r '.result.subdomain // empty') | |
| if [ -z "$subdomain" ]; then | |
| echo "workers.dev subdomain missing in response" >&2 | |
| exit 1 | |
| fi | |
| echo "subdomain=$subdomain" >> "$GITHUB_OUTPUT" | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # https://github.com/actions/checkout/releases/tag/v6.0.2 | |
| with: | |
| repository: ${{ github.event.pull_request.head.repo.full_name }} | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # https://github.com/pnpm/action-setup/releases/tag/v4.2.0 | |
| with: | |
| version: 10 | |
| run_install: false | |
| - name: Install Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # https://github.com/actions/setup-node/releases/tag/v6.2.0 | |
| with: | |
| node-version: 24 | |
| cache: "pnpm" | |
| - name: Cache Next.js | |
| uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # https://github.com/actions/cache/releases/tag/v5.0.3 | |
| with: | |
| path: ${{ github.workspace }}/.next/cache | |
| key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- | |
| - name: Cache Compiled MDX | |
| uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # https://github.com/actions/cache/releases/tag/v5.0.3 | |
| with: | |
| path: ${{ github.workspace }}/.compiled-mdx | |
| key: ${{ runner.os }}-compiled-mdx-${{ hashFiles('src/app/content/**/*.mdx', 'src/lib/shiki/*.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-compiled-mdx- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Lint | |
| run: pnpm lint | |
| - name: Check formatting | |
| run: pnpm exec oxfmt --check . | |
| - name: Precompile MDX | |
| run: pnpm precompile-mdx | |
| - name: Tune AWS CLI transfers | |
| run: | | |
| aws configure set default.s3.max_concurrent_requests 100 | |
| aws configure set default.s3.max_queue_size 1000 | |
| aws configure set default.s3.multipart_threshold 100MB | |
| aws configure set default.s3.multipart_chunksize 100MB | |
| - name: Derive preview identifiers | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| WORKERS_DEV_SUBDOMAIN: ${{ steps.workers_dev.outputs.subdomain }} | |
| run: | | |
| PR_NUMBER="${PR_NUMBER}" | |
| echo "WORKER_NAME=blueshift-preview-pr-${PR_NUMBER}" >> "$GITHUB_ENV" | |
| echo "CONTENT_PREFIX=compiled-mdx-preview/pr-${PR_NUMBER}" >> "$GITHUB_ENV" | |
| if [ -n "${WORKERS_DEV_SUBDOMAIN:-}" ]; then | |
| echo "PREVIEW_URL=https://blueshift-preview-pr-${PR_NUMBER}.${WORKERS_DEV_SUBDOMAIN}.workers.dev" >> "$GITHUB_ENV" | |
| fi | |
| - name: Seed preview content from production | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} | |
| ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com | |
| PROD_CONTENT_PREFIX: compiled-mdx | |
| run: | | |
| MANIFEST_NAME=".upload-manifest.json" | |
| SOURCE_PREFIX="s3://${{ secrets.R2_BUCKET }}/$PROD_CONTENT_PREFIX" | |
| DEST_PREFIX="s3://${{ secrets.R2_BUCKET }}/$CONTENT_PREFIX" | |
| aws s3 sync "$SOURCE_PREFIX" "$DEST_PREFIX" \ | |
| --endpoint-url "$ENDPOINT" \ | |
| --quiet \ | |
| --size-only \ | |
| --exclude "$MANIFEST_NAME" \ | |
| --delete | |
| - name: Upload Compiled MDX to R2 (preview) | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} | |
| ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com | |
| COMPILED_DIR: .compiled-mdx | |
| PROD_CONTENT_PREFIX: compiled-mdx | |
| run: | | |
| S3_PREFIX="s3://${{ secrets.R2_BUCKET }}/$CONTENT_PREFIX" | |
| PROD_S3_PREFIX="s3://${{ secrets.R2_BUCKET }}/$PROD_CONTENT_PREFIX" | |
| MANIFEST_NAME=".upload-manifest.json" | |
| MANIFEST_FILE="$COMPILED_DIR/$MANIFEST_NAME" | |
| REMOTE_MANIFEST_FILE="$RUNNER_TEMP/$MANIFEST_NAME" | |
| node ./scripts/mdx-upload-manifest.mjs write "$COMPILED_DIR" "$MANIFEST_FILE" | |
| if ! aws s3 cp "$S3_PREFIX/$MANIFEST_NAME" "$REMOTE_MANIFEST_FILE" \ | |
| --endpoint-url "$ENDPOINT" \ | |
| --no-progress >/dev/null 2>&1; then | |
| aws s3 cp "$PROD_S3_PREFIX/$MANIFEST_NAME" "$REMOTE_MANIFEST_FILE" \ | |
| --endpoint-url "$ENDPOINT" \ | |
| --no-progress >/dev/null 2>&1 || printf '{}\n' > "$REMOTE_MANIFEST_FILE" | |
| fi | |
| aws s3 sync "$COMPILED_DIR/" "$S3_PREFIX" \ | |
| --endpoint-url "$ENDPOINT" \ | |
| --quiet \ | |
| --size-only \ | |
| --delete \ | |
| --content-type "application/json" | |
| # Compare against the existing preview manifest first; fall back to production only to bootstrap a new preview. | |
| changed_files=$(node ./scripts/mdx-upload-manifest.mjs diff "$MANIFEST_FILE" "$REMOTE_MANIFEST_FILE") | |
| if [ -n "$changed_files" ]; then | |
| printf '%s\n' "$changed_files" | while IFS= read -r json_path; do | |
| [ -n "$json_path" ] || continue | |
| aws s3 cp "$COMPILED_DIR/$json_path" "$S3_PREFIX/$json_path" \ | |
| --endpoint-url "$ENDPOINT" \ | |
| --quiet \ | |
| --content-type "application/json" | |
| done | |
| fi | |
| aws s3 cp "$MANIFEST_FILE" "$S3_PREFIX/$MANIFEST_NAME" \ | |
| --endpoint-url "$ENDPOINT" \ | |
| --quiet \ | |
| --content-type "application/json" | |
| - name: Build | |
| env: | |
| NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} | |
| NEXT_PUBLIC_CHALLENGE_SECRET: ${{ secrets.NEXT_PUBLIC_CHALLENGE_SECRET }} | |
| NEXT_PUBLIC_CHALLENGE_RPC_ENDPOINT: ${{ secrets.NEXT_PUBLIC_CHALLENGE_RPC_ENDPOINT }} | |
| NEXT_PUBLIC_MAINNET_RPC_ENDPOINT: ${{ secrets.NEXT_PUBLIC_MAINNET_RPC_ENDPOINT }} | |
| NEXT_PUBLIC_DEVNET_RPC_ENDPOINT: ${{ secrets.NEXT_PUBLIC_DEVNET_RPC_ENDPOINT }} | |
| run: pnpm exec opennextjs-cloudflare build | |
| - name: Deploy Preview (workers.dev) | |
| uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # https://github.com/cloudflare/wrangler-action/releases/tag/v3.14.1 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| command: > | |
| deploy --env preview | |
| --name ${{ env.WORKER_NAME }} | |
| --var CONTENT_PREFIX:${{ env.CONTENT_PREFIX }} | |
| - name: Comment preview URL | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| PREVIEW_URL: ${{ env.PREVIEW_URL }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${PREVIEW_URL:-}" ]; then | |
| echo "No preview URL available. Skipping comment." | |
| exit 0 | |
| fi | |
| marker='<!-- preview-link -->' | |
| body=$(printf '%s\n✅ Preview deployed: %s' "$marker" "$PREVIEW_URL") | |
| payload=$(jq -n --arg body "$body" '{body: $body}') | |
| comments_url="https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" | |
| comments=$(curl -sSL \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "$comments_url") | |
| comment_id=$(echo "$comments" | jq -r --arg marker "$marker" 'map(select(.body | contains($marker))) | .[0].id // empty') | |
| if [ -n "$comment_id" ]; then | |
| curl -sSL -X PATCH \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$payload" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/comments/${comment_id}" > /dev/null | |
| exit 0 | |
| fi | |
| curl -sSL -X POST \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$payload" \ | |
| "$comments_url" > /dev/null |