Merge pull request #91 from SecureToolsProject/hotfix/v2.1.0-ocr-cate… #9
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 Cloudflare bridge | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| deployments: write | |
| concurrency: | |
| group: web-utilities-cloudflare-bridge | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| name: Deploy Web Utilities bridge | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Check out repository | |
| # actions/checkout v6.0.2 | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| with: | |
| persist-credentials: false | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Run static tool validation | |
| run: node tests/run-all.mjs | |
| - name: Validate bridge prerequisites | |
| shell: bash | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| run: | | |
| set -euo pipefail | |
| missing=() | |
| [[ -n "$CLOUDFLARE_API_TOKEN" ]] || missing+=(CLOUDFLARE_API_TOKEN) | |
| [[ -n "$CLOUDFLARE_ACCOUNT_ID" ]] || missing+=(CLOUDFLARE_ACCOUNT_ID) | |
| if (( ${#missing[@]} > 0 )); then | |
| printf '::error::Missing required GitHub Actions secret: %s\n' "${missing[@]}" | |
| exit 1 | |
| fi | |
| [[ "$(<CNAME)" == "securetools.app" ]] || { | |
| echo "::error file=CNAME::Existing GitHub Pages custom domain changed" | |
| exit 1 | |
| } | |
| - name: Prepare isolated bridge artifact | |
| shell: bash | |
| env: | |
| BRIDGE_DIRECTORY: ${{ runner.temp }}/secure-tools-web-bridge | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$BRIDGE_DIRECTORY" | |
| cp -R \ | |
| 404.html \ | |
| index.html \ | |
| about \ | |
| assets \ | |
| css \ | |
| js \ | |
| privacy \ | |
| robots.txt \ | |
| sitemap.xml \ | |
| tools \ | |
| "$BRIDGE_DIRECTORY/" | |
| printf '%s\n' \ | |
| 'https://secure-tools-web-bridge.pages.dev/*' \ | |
| ' X-Robots-Tag: noindex, nofollow' \ | |
| '' \ | |
| 'https://:version.secure-tools-web-bridge.pages.dev/*' \ | |
| ' X-Robots-Tag: noindex, nofollow' \ | |
| > "$BRIDGE_DIRECTORY/_headers" | |
| [[ ! -e "$BRIDGE_DIRECTORY/CNAME" ]] | |
| [[ ! -e "$BRIDGE_DIRECTORY/_redirects" ]] | |
| [[ ! -e "$BRIDGE_DIRECTORY/_worker.js" ]] | |
| [[ ! -d "$BRIDGE_DIRECTORY/functions" ]] | |
| [[ "$(find "$BRIDGE_DIRECTORY" -name index.html -type f | wc -l)" -eq 20 ]] | |
| - name: Validate Cloudflare Pages project isolation | |
| shell: bash | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| run: | | |
| set -euo pipefail | |
| project="$(curl --fail --silent --show-error \ | |
| "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/secure-tools-web-bridge" \ | |
| --header "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}")" | |
| jq --raw-output ' | |
| .result as $project | | |
| "Pages project state: name=\($project.name), production_branch=\($project.production_branch), subdomain=\($project.subdomain), custom_domains=\($project.domains // [] | map(select(. != $project.subdomain)) | sort | join(",")), source=\(if $project.source == null then "direct-upload" else $project.source.type end), web_analytics=\(if ($project.build_config.web_analytics_tag // "") == "" and ($project.build_config.web_analytics_token // "") == "" then "disabled" else "enabled" end)" | |
| ' <<< "$project" | |
| jq --exit-status ' | |
| .result as $project | | |
| .success == true and | |
| $project.name == "secure-tools-web-bridge" and | |
| $project.production_branch == "main" and | |
| $project.subdomain == "secure-tools-web-bridge.pages.dev" and | |
| ($project.domains // [] | map(select(. != $project.subdomain)) | sort) == ["tools.securetools.app"] and | |
| $project.source == null and | |
| ($project.build_config.web_analytics_tag // "") == "" and | |
| ($project.build_config.web_analytics_token // "") == "" | |
| ' <<< "$project" > /dev/null || { | |
| echo "::error::Cloudflare Pages project is missing or violates the H3.3 parallel-domain contract" | |
| exit 1 | |
| } | |
| echo "Validated Direct Upload project secure-tools-web-bridge: production branch main, stable pages.dev subdomain plus tools.securetools.app, no Git integration, no Web Analytics." | |
| - name: Deploy to Cloudflare Pages | |
| id: deploy | |
| # cloudflare/wrangler-action v4.0.0 | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| gitHubToken: ${{ secrets.GITHUB_TOKEN }} | |
| command: pages deploy "${{ runner.temp }}/secure-tools-web-bridge" --project-name=secure-tools-web-bridge --branch=main --commit-hash=${{ github.sha }} | |
| - name: Validate deployed bridge | |
| env: | |
| DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }} | |
| run: node tests/deployment-smoke.mjs "$DEPLOYMENT_URL" noindex | |
| - name: Validate stable deployment aliases | |
| shell: bash | |
| run: | | |
| smoke_with_retry() { | |
| local url="$1" | |
| local indexing="$2" | |
| for attempt in 1 2 3 4 5 6; do | |
| if node tests/deployment-smoke.mjs "$url" "$indexing"; then | |
| return 0 | |
| fi | |
| [[ "$attempt" -lt 6 ]] || return 1 | |
| echo "Waiting for Cloudflare alias propagation (attempt $attempt of 6)." | |
| sleep 10 | |
| done | |
| } | |
| smoke_with_retry https://secure-tools-web-bridge.pages.dev noindex | |
| smoke_with_retry https://tools.securetools.app indexable |