diff --git a/.github/taskdef/dev-taskdef.yaml b/.github/taskdef/dev-taskdef.yaml deleted file mode 100644 index 01dc4a33..00000000 --- a/.github/taskdef/dev-taskdef.yaml +++ /dev/null @@ -1,11 +0,0 @@ -region: eu-west-1 -hostport: 80 -containerport: 80 -app_name: static-dev -role: frontend-apps -environment: dev -iac: terraform-workspace-aws-dev-apps-eu-west-1-apps-static-dev-polygon-technology -team_name: product-apps -memory: 1024 -cpu: 512 -# diff --git a/.github/taskdef/prod-taskdef.yaml b/.github/taskdef/prod-taskdef.yaml deleted file mode 100644 index 2d7ec9ba..00000000 --- a/.github/taskdef/prod-taskdef.yaml +++ /dev/null @@ -1,10 +0,0 @@ -region: eu-west-1 -hostport: 80 -containerport: 80 -app_name: static -role: frontend-apps -environment: prod -iac: terraform-workspace-aws-prod-applications-eu-west-1-apps-static-polygon-technology -team_name: product-apps -memory: 1024 -cpu: 512 diff --git a/.github/taskdef/staging-taskdef.yaml b/.github/taskdef/staging-taskdef.yaml deleted file mode 100644 index 717f4072..00000000 --- a/.github/taskdef/staging-taskdef.yaml +++ /dev/null @@ -1,10 +0,0 @@ -region: eu-west-1 -hostport: 80 -containerport: 80 -app_name: static-staging -role: frontend-apps -environment: staging -iac: terraform-workspace-aws-test-applications-eu-west-1-apps-static-staging-polygon-technology -team_name: product-apps -memory: 1024 -cpu: 512 diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml deleted file mode 100644 index 11648823..00000000 --- a/.github/workflows/build_and_deploy.yml +++ /dev/null @@ -1,54 +0,0 @@ -on: - workflow_call: - inputs: - environment: - required: false - type: string - default: 'dev' - core_app: - required: false - type: string - description: 'Core app name' - default: 'static' - secrets: - build_params_gh_secret_keys: - required: false - description: 'JSON-encoded build-time secrets forwarded to the ECS deploy workflow.' - -permissions: - contents: write - id-token: write - -jobs: - set-env-variable: - runs-on: ubuntu-latest - outputs: - ACCOUNT_NUMBER: ${{ steps.set-env-var.outputs.ACCOUNT_NUMBER }} - APP_NAME: ${{ steps.set-env-var.outputs.APP_NAME }} - steps: - - name: Set Environment Variable - id: set-env-var - run: | - if [ "${{ inputs.environment }}" == "dev" ]; then - echo "ACCOUNT_NUMBER=058264511034" >> $GITHUB_OUTPUT - echo "APP_NAME=${{ inputs.core_app }}-dev" >> $GITHUB_OUTPUT - elif [ "${{ inputs.environment }}" == "staging" ]; then - echo "ACCOUNT_NUMBER=070528468658" >> $GITHUB_OUTPUT - echo "APP_NAME=${{ inputs.core_app }}-staging" >> $GITHUB_OUTPUT - elif [ "${{ inputs.environment }}" == "prod" ]; then - echo "ACCOUNT_NUMBER=042947190491" >> $GITHUB_OUTPUT - echo "APP_NAME=${{ inputs.core_app }}" >> $GITHUB_OUTPUT - fi - - deploy: - uses: 0xPolygon/pipelines/.github/workflows/ecs_deploy_docker_taskdef.yaml@main - needs: set-env-variable - with: - app_name: ${{ needs.set-env-variable.outputs.APP_NAME }} - taskdef_file_vars: .github/taskdef/${{ inputs.environment }}-taskdef.yaml - aws_region: eu-west-1 - environment: ${{ inputs.environment }} - cluster_name: frontend-${{ inputs.environment }}-ecs-cluster - account_number: '${{ needs.set-env-variable.outputs.ACCOUNT_NUMBER }}' - secrets: - build_params_gh_secret_keys: ${{ secrets.build_params_gh_secret_keys }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 19b6bbb2..67f6a706 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,19 +3,23 @@ name: Deploy to Cloudflare # Trunk-based deployment. master is the only branch. # # - staging: every push to master (trunk) deploys staging -# (static-cf.polygon.technology, a custom_domain on a fresh -# hostname). Trunk is always deployable, so staging tracks trunk. -# - production: workflow_dispatch ONLY, for now. The apex cutover sequence is -# documented in wrangler.toml [env.production]: dispatch once to -# create the worker → land custom_domain → SPEC swaps DNS in the -# CF dashboard → dispatch to verify ownership. The ordering makes -# a stray production dispatch harmless at every step. Once the -# domain is wrangler-owned, re-add a release-tag trigger here so -# prod auto-deploys on each @polygonlabs/meta release. +# (static-staging.polygon.technology). Trunk is always deployable, so +# staging tracks trunk. +# - production: the @polygonlabs/meta release tag, pushed by the release bot +# when the Version Packages PR merges, deploys production +# (static.polygon.technology) in lockstep with the npm publish. +# The CDN and the npm package serve the same network/ tree, so +# gating prod on the release keeps them from drifting; merging +# the Version Packages PR is the deliberate promote-to-prod step. +# (The release commit also re-deploys staging — a harmless +# idempotent no-op.) +# - dispatch: manual escape hatch for either env (rollback / re-deploy). on: push: branches: - master + tags: + - '@polygonlabs/meta@*' workflow_dispatch: inputs: environment: @@ -50,12 +54,32 @@ jobs: - run: pnpm install --frozen-lockfile - # dispatch → chosen env; a push to master → staging. - - run: > - pnpm exec wrangler deploy --env ${{ - github.event_name == 'workflow_dispatch' && github.event.inputs.environment - || 'staging' - }} + # dispatch → chosen env; a tag push → production; a push to master → staging. + # Deliberately plain bash rather than a ${{ }} ternary chain: GitHub + # expression short-circuiting silently falls through when a middle operand + # is falsy (e.g. an empty dispatch input), and shell if/else has no such + # trap. Anything unexpected exits 1 instead of deploying somewhere. + - name: Resolve target environment + id: target + env: + DISPATCH_ENV: ${{ github.event.inputs.environment }} + run: | + if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then + case "$DISPATCH_ENV" in + staging|production) ENV="$DISPATCH_ENV" ;; + *) echo "Unexpected dispatch environment: '$DISPATCH_ENV'" >&2; exit 1 ;; + esac + elif [ "$GITHUB_REF_TYPE" = "tag" ]; then + ENV="production" + elif [ "$GITHUB_REF" = "refs/heads/master" ]; then + ENV="staging" + else + echo "Unexpected ref for deploy: $GITHUB_REF ($GITHUB_REF_TYPE)" >&2; exit 1 + fi + echo "resolved: $GITHUB_EVENT_NAME $GITHUB_REF -> $ENV" + echo "environment=$ENV" >> "$GITHUB_OUTPUT" + + - run: pnpm exec wrangler deploy --env ${{ steps.target.outputs.environment }} env: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_WORKER_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CF_WORKER_API_TOKEN }} diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml deleted file mode 100644 index a524a85d..00000000 --- a/.github/workflows/deployment.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Static Deployment -on: - push: - branches: - - master - workflow_dispatch: - inputs: - environment: - required: false - type: choice - description: 'Select the environment to deploy to (optional for pushes)' - options: - - staging - - prod - -permissions: - contents: write - id-token: write - -jobs: - deploy: - uses: ./.github/workflows/build_and_deploy.yml - with: - environment: ${{ inputs.environment || 'dev' }} - secrets: - build_params_gh_secret_keys: ${{ secrets.build_params_gh_secret_keys }} diff --git a/.github/workflows/deployment_gcp.yml b/.github/workflows/deployment_gcp.yml deleted file mode 100644 index bcfff2ed..00000000 --- a/.github/workflows/deployment_gcp.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Deploy docker image to GCP -on: - push: - branches: - - master - workflow_dispatch: - inputs: - tag: - description: 'Tag to build and push' - required: true - type: string - -permissions: - contents: read - id-token: write - -jobs: - generate-version: - uses: 0xPolygon/pipelines/.github/workflows/generate_version.yaml@main - - docker-release: - needs: - - generate-version - uses: 0xPolygon/pipelines/.github/workflows/gcp_pipeline_release_image.yaml@main - with: - image_name: 'static' - image_tag: ${{ inputs.tag || needs.generate-version.outputs.version }} - checkout_ref: ${{ github.ref_name }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..64d98e74 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,44 @@ +# Changelog + +Repo-level changelog for the `static.polygon.technology` HTTP endpoint and the +repository's tooling. The `@polygonlabs/meta` npm package has its own +changesets-managed changelog at +[`packages/meta/CHANGELOG.md`](./packages/meta/CHANGELOG.md). + +## 1.0.0 — 2026-07-08 + +First changelog entry, marking the endpoint's move to Cloudflare. + +### Changed + +- `static.polygon.technology` is now served from Cloudflare Workers static + assets. The nginx Docker image and its AWS ECS / GCP deploy pipelines are + removed (`Dockerfile`, `nginx.conf`, `deployment.yml`, `build_and_deploy.yml`, + `deployment_gcp.yml`, `.github/taskdef/`). The image was internal deploy + tooling only — it was never published to a public registry. Self-hosting the + content needs nothing more than a static file server pointed at `network/`; + the retired nginx setup remains available in git history. +- Deployment is trunk-based via `wrangler` (`.github/workflows/deploy.yml`): + pushes to `master` deploy staging (`static-staging.polygon.technology`), and + `@polygonlabs/meta` release tags deploy production, in lockstep with the npm + publish. +- Missing paths now return a real `404`. The retired nginx config served the + health-check HTML page with `200` for any unknown path. +- CORS: preflight `OPTIONS` is answered by a minimal worker script + (`worker/worker.ts`) with `204` and `Access-Control-Allow-Origin: *`, + `Allow-Methods: GET, HEAD, OPTIONS`, `Allow-Headers: Content-Type, + Authorization`, `Max-Age: 86400`; asset responses carry + `Access-Control-Allow-Origin: *` via `_headers`. `POST` is no longer + advertised (nothing accepts a write), but the `Content-Type`/`Authorization` + allow-headers are load-bearing: maticjs sends `Content-Type` on its GETs, so + browsers preflight — an initial Cloudflare revision shipped without the + worker, returned 405 to preflights, and broke browser maticjs consumers + until the worker restored them (#194). +- Responses now carry `Cache-Control: public, max-age=300`. + +### Removed + +- `/network/index.js` (a CommonJS `Network` class predating the + `@maticnetwork/meta` 2.x package split) is no longer served. Use the + [`@polygonlabs/meta`](./packages/meta/) npm package for typed access, or + fetch the JSON tree directly. diff --git a/CLAUDE.md b/CLAUDE.md index fedef6fd..cb4d6020 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,27 +32,30 @@ Two public surfaces driven from one source JSON tree at the repo-root and caching for asset hits live in `public/_headers`. `deploy.yml` is trunk-based: every push to `master` deploys - **staging** (`static-cf.polygon.technology`, a `custom_domain` on a - fresh hostname, where wrangler can create the DNS record itself). - **Production** is `workflow_dispatch`-only. The apex cannot be bound - from CI while its externally-managed record exists — `custom_domain` - fails with Cloudflare error 100117, our CI token lacks - `Zone:DNS:Edit` to override, and zone routes fail likewise (SPEC has - tried) — so the cutover is the four-step process documented in - `wrangler.toml` `[env.production]`: dispatch to create the worker, - land `custom_domain = true`, SPEC swaps the DNS record onto the - worker in the CF dashboard (zero downtime), dispatch to verify - wrangler owns the domain. The ordering makes a stray production - dispatch harmless at every step. Once ownership is verified, a - release-tag trigger can be re-added so prod auto-deploys on each - `@polygonlabs/meta` release. See the apps-team-ops - Cloudflare-migration runbook for the full rationale. - - The legacy nginx-on-ECS origin (`Dockerfile`, `nginx.conf`, - `deployment.yml`, `build_and_deploy.yml`) and the staged GCP path - (`deployment_gcp.yml`) are kept as rollback until the apex DNS is - cut over to Cloudflare, then removed in a follow-up PR. The apex DNS - cutover and the AWS/GCP teardown are manual infra steps. + **staging** (`static-staging.polygon.technology`); the `@polygonlabs/meta` + release tag (pushed by the release bot when the Version Packages PR + merges) deploys **production** (`static.polygon.technology`) in + lockstep with the npm publish — merging that PR is the deliberate + promote-to-prod step, and gating prod on the release keeps the CDN + and the npm package (two surfaces of one `network/` tree) from + drifting. `workflow_dispatch` is the manual escape hatch for either + environment. + + If this hostname (or any live hostname) ever needs to move to a + different host, follow the team's internal `service-hosting-migration` + runbook — wrangler cannot bind a hostname whose DNS record already + exists elsewhere, and the working process is documented there. + +## Changelog + +Maintain the root [`CHANGELOG.md`](./CHANGELOG.md) whenever a change +fundamentally alters the repository's capabilities — what the running +service serves or how (endpoint behaviour, status codes, headers, paths +added/removed), the API surface consumers depend on, or the deployment/ +hosting mechanism that drives the static site. It is hand-maintained and +scoped to the repo/HTTP endpoint; routine content updates under +`network/` don't need an entry, and `packages/meta` has its own +changesets-managed changelog. ## Codegen flow diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 46928f6d..00000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM nginx:alpine - -COPY nginx.conf /etc/nginx/conf.d/default.conf - -WORKDIR /usr/share/nginx/html -COPY network /usr/share/nginx/html/network -COPY index.html . -RUN rm -rf .git* - -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 4546a42f..83c4f1c8 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ repo-root `network/` directory: `as const` ABI modules, typed network metadata, and raw JSON. See [`packages/meta/README.md`](./packages/meta/README.md) for usage. 2. **HTTP endpoint** at `https://static.polygon.technology/...` — the - same JSON files served verbatim by an nginx Docker image built from - the root `Dockerfile` and deployed via the workflows under - `.github/workflows/`. + same JSON files served verbatim from Cloudflare Workers static + assets (`wrangler.toml` + `scripts/assemble-cdn.sh`), deployed via + `.github/workflows/deploy.yml`. Both consumers read from the same `network/` tree at the repo root, so adding or updating ABIs in one place keeps both surfaces in sync. The @@ -27,8 +27,8 @@ packages/meta/ # @polygonlabs/meta npm package src/generated/ # codegenned `as const` TS modules (tracked) network/ # mirror of /network/ for the ./network/* subpath export (gitignored, materialised by prepack) -Dockerfile # nginx image for static.polygon.technology — COPYs network/ -nginx.conf, index.html +wrangler.toml # Cloudflare Workers static-assets config for static.polygon.technology +scripts/assemble-cdn.sh # stages network/ + index.html + public/_headers into dist/ ``` See [`packages/meta/CONTRIBUTING.md`](./packages/meta/CONTRIBUTING.md) diff --git a/nginx.conf b/nginx.conf deleted file mode 100644 index 2a4fdfd5..00000000 --- a/nginx.conf +++ /dev/null @@ -1,20 +0,0 @@ -server { - listen 0.0.0.0:80; - root /usr/share/nginx/html; - index index.html; - error_page 404 /404.html; - - location / { - try_files $uri $uri/ /index.html =404; - add_header Access-Control-Allow-Origin *; - add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; # Update to include OPTIONS method - add_header Access-Control-Allow-Headers "Authorization, Content-Type"; # Specify allowed headers - add_header Access-Control-Max-Age 86400; # Set CORS preflight request cache duration - add_header Access-Control-Allow-Credentials false; - add_header Access-Control-Expose-Headers *; - - if ($request_method = 'OPTIONS') { - return 204; # Return 204 No Content for OPTIONS requests - } - } -} diff --git a/public/_headers b/public/_headers index 09720214..aa51d39f 100644 --- a/public/_headers +++ b/public/_headers @@ -8,9 +8,9 @@ # preflight handling broke every browser consumer of the SDK. Allow-headers # only matter on the preflight response, so they live in the worker, not here. # -# Cache-Control is new: the store is fetched uncached at every maticjs client -# init, so a short shared-cache TTL cuts repeated edge/origin load without -# risking staleness for the rare network/ABI additions. +# The short shared-cache TTL matters because consumers (maticjs and friends) +# fetch this store uncached at every client init: edge caching absorbs that +# repeat load, while 300s keeps new network/ABI additions from going stale. /* Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, HEAD, OPTIONS diff --git a/wrangler.toml b/wrangler.toml index 9807f02a..3a7020e4 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -3,9 +3,9 @@ # tree + health-check index.html + _headers) directly from its edge; the tiny # worker script exists only because the asset layer serves GET/HEAD and 405s # every other method — including the OPTIONS preflights browsers send for -# maticjs's Content-Type-bearing GETs (see worker/worker.ts). Replaces the -# previous nginx-on-ECS origin; the npm package (@polygonlabs/meta, -# packages/meta/) is a separate surface and unaffected. +# maticjs's Content-Type-bearing GETs (see worker/worker.ts). The npm package +# (@polygonlabs/meta, packages/meta/) is a separate surface with its own +# release pipeline; both are generated from the same network/ tree. name = "polygon-static" main = "worker/worker.ts" compatibility_date = "2026-06-24" @@ -16,8 +16,8 @@ send_metrics = false [assets] directory = "./dist" -# Missing paths return a real 404 (a JSON CDN, not an SPA). The old nginx -# try_files fell back to index.html, masking 404s as 200+HTML. +# Missing paths return a real 404: this is a JSON store consumed by SDKs, not an +# SPA — an index.html fallback would mask missing files as 200+HTML. not_found_handling = "none" # Lets the worker delegate non-OPTIONS requests back to the asset layer. binding = "ASSETS" @@ -31,20 +31,17 @@ run_worker_first = true [build] command = "pnpm run build:cdn" -# Validation domain — a fresh hostname, so wrangler creates its DNS record itself. -# custom_domain only works from CI when NO record pre-exists; on a hostname with an -# existing externally-managed record it fails with code 100117, our CI token cannot -# override (no Zone:DNS:Edit), and zone routes fail likewise (SPEC has tried). +# Staging tracks trunk: every push to master deploys here. static-staging's +# DNS record predates this worker (externally managed), so until SPEC swaps it +# onto the polygon-static-staging worker in the CF dashboard, a staging deploy +# fails harmlessly at the domain-registration step (code 100117) — same +# sequence as the apex cutover; see the service-hosting-migration runbook. [env.staging] -routes = [{ pattern = "static-cf.polygon.technology", custom_domain = true }] +routes = [{ pattern = "static-staging.polygon.technology", custom_domain = true }] -# Apex. The worker (polygon-static-production) is deployed and unbound; the -# static.polygon.technology record still points at AWS (externally managed), so no -# wrangler-side binding can take it over from CI (see staging note). Until SPEC -# swaps the record onto the worker in the CF dashboard, a production deploy fails -# harmlessly at the domain-registration step (code 100117) — the script deploys -# before domain registration, so nothing already deployed or bound is touched. -# After the swap, this config matches the dashboard state and deploys idempotently -# own the domain. +# Apex. Deploys on the @polygonlabs/meta release tag, in lockstep with the npm +# publish. If this hostname ever needs to move to another platform, follow the +# team's internal service-hosting-migration runbook — wrangler cannot rebind a +# hostname whose DNS record already exists elsewhere. [env.production] routes = [{ pattern = "static.polygon.technology", custom_domain = true }]