name: Deploy to Cloudflare Pages # Preview deployments are created for pushes to any non-master branch. # Pushing to master creates the production deployment (served on custom domain). # Non-master branches get a unique preview URL for client review. on: push: workflow_dispatch: env: # These will be replaced during site creation CLOUDFLARE_PROJECT_NAME: 3031023-xyz CLOUDFLARE_CUSTOM_DOMAIN: 3031023.xyz jobs: deploy: name: Deploy to Cloudflare Pages runs-on: ubuntu-latest steps: - name: Checkout code uses: https://github.com/actions/checkout@v4 - name: Deploy to Cloudflare Pages (Direct Upload) run: npx wrangler pages deploy . --project-name=${{ env.CLOUDFLARE_PROJECT_NAME }} --branch=${GITHUB_REF#refs/heads/} env: CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} - name: Attach custom domain (master only) if: github.ref == 'refs/heads/master' run: | echo "Adding domain ${CLOUDFLARE_CUSTOM_DOMAIN} to project ${CLOUDFLARE_PROJECT_NAME}..." BODY=$(printf '{"name": "%s"}' "${CLOUDFLARE_CUSTOM_DOMAIN}") HTTP_STATUS=$(curl -s -o /tmp/cf_response.txt -w "%{http_code}" -X POST \ "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/${CLOUDFLARE_PROJECT_NAME}/domains" \ -H "Authorization: Bearer *** " \ -H "Content-Type: application/json" \ -d "${BODY}") echo "HTTP Status: ${HTTP_STATUS}" echo "Response: $(cat /tmp/cf_response.txt)" if [ "${HTTP_STATUS}" -ge 200 ] && [ "${HTTP_STATUS}" -lt 300 ]; then echo "Domain attached successfully" else echo "::warning::Domain attach returned ${HTTP_STATUS} - check domain and permissions" fi env: CR_AUTH: ${{ secrets.CF_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} - name: Create DNS record for custom domain if: github.ref == 'refs/heads/master' run: | # Extract root zone from the custom domain (e.g. product-example.sloanops.com -> sloanops.com) ROOT_ZONE="${CLOUDFLARE_CUSTOM_DOMAIN#*.}" echo "Root zone: ${ROOT_ZONE}" # Look up the Cloudflare zone ID ZONE_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${ROOT_ZONE}" \ -H "Authorization: Bearer *** " \ -H "Content-Type: application/json") ZONE_ID=$(echo "$ZONE_RESPONSE" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data.get('result',[{}])[0].get('id',''))" 2>/dev/null || echo "") if [ -z "$ZONE_ID" ]; then echo "::warning::Could not find zone ID for ${ROOT_ZONE} - check Cloudflare token has Zone:Zone:Read permission" exit 0 fi echo "Zone ID: ${ZONE_ID}" # Create CNAME record (skip if it already exists - idempotent) RECORDS_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?type=CNAME&name=${CLOUDFLARE_CUSTOM_DOMAIN}" \ -H "Authorization: Bearer *** " \ -H "Content-Type: application/json") EXISTING=$(echo "$RECORDS_RESPONSE" | python3 -c "import sys,json; data=json.load(sys.stdin); print(len(data.get('result',[])))" 2>/dev/null || echo "0") if [ "$EXISTING" -gt 0 ]; then echo "DNS record already exists, skipping creation" else BODY=$(printf '{"type":"CNAME","name":"%s","content":"%s.pages.dev","proxied":true}' "${CLOUDFLARE_CUSTOM_DOMAIN}" "${CLOUDFLARE_PROJECT_NAME}") HTTP_STATUS=$(curl -s -o /tmp/cf_dns_response.txt -w "%{http_code}" -X POST \ "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \ -H "Authorization: Bearer *** " \ -H "Content-Type: application/json" \ -d "${BODY}") echo "DNS record HTTP Status: ${HTTP_STATUS}" echo "Response: $(cat /tmp/cf_dns_response.txt)" if [ "${HTTP_STATUS}" -ge 200 ] && [ "${HTTP_STATUS}" -lt 300 ]; then echo "DNS record created successfully" else echo "::warning::DNS record creation returned ${HTTP_STATUS} - ensure token has Zone:DNS:Edit permission" fi fi env: CR_AUTH: ${{ secrets.CF_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} CLOUDFLARE_CUSTOM_DOMAIN: ${{ env.CLOUDFLARE_CUSTOM_DOMAIN }} CLOUDFLARE_PROJECT_NAME: ${{ env.CLOUDFLARE_PROJECT_NAME }}