Deployment
The front end is a static SPA deployed to S3 and served through CloudFront using the Serverless Framework and serverless-finch.
Stages
| Stage | URL | S3 bucket | Branch / trigger |
|---|---|---|---|
dev |
play.dev.abstractplay.com | abstract-play-dev |
develop push |
prod |
play.abstractplay.com | abstract-play-prod |
main push |
Defined in serverless.yml.
Build commands
| Command | Effect |
|---|---|
npm run build-dev |
Vite build with VITE_REAL_MODE=development; copies dev robots.txt, strips build/locales |
npm run build-prod |
Vite build with VITE_REAL_MODE=production; generates sitemap; copies prod robots.txt, strips build/locales |
npm run analyze |
Bundle size report via source-map-explorer (runs a dev-mode Vite build first — prod builds omit source maps) |
npm run analyze:only |
Re-run explorer on an existing dev build in build/ |
npm run deploy |
Upload build/ to dev S3 bucket |
npm run deploy-prod |
Upload build/ to prod S3 bucket (--stage prod) |
npm run full-dev |
build-dev + deploy + publish locales to dev S3 |
npm run full-prod |
build-prod + deploy + publish locales to prod S3 |
npm run publish-locales |
Upload public/locales/ (+ gameslib) to dev bucket (locales/ prefix) |
npm run publish-locales:prod |
Same for prod bucket |
AWS setup
- Install AWS CLI.
- Configure profiles
AbstractPlayDevandAbstractPlayProdin~/.aws/credentials. - Install Serverless globally:
npm install -g serverless. - First-time stack setup:
serverless deploy(dev) andserverless --stage prod deploy(prod) to create S3 buckets and CloudFront distributions.
CI/CD
GitHub Actions workflows:
.github/workflows/deploy-dev.js.yml— push todevelop.github/workflows/deploy-prod.js.yml— push tomain
CI steps:
- Install Serverless and run
npm ci(with GitHub Packages auth). - Test job (required before deploy):
bin/install-ap-deps.mjs --for-testsinstalls@abstractplay/gameslib(full registry) plus pinned renderer, then runsnpm run test:ciand lint. - Deploy job:
bin/install-ap-deps.mjsinstalls the pinned production gameslib and renderer fromci-deps.json(or dispatch payload). - Auto-commit
ci-deps.json,package-lock.json, and syncedpackage.jsonAP version fields when they change (dispatch only). npm run build-devorbuild-prod.serverless client deploy.- Publish locale JSON files to S3 (
bin/publish-locales.mjs).
Deployments do not use CloudFront invalidations. Cache freshness is handled by upload headers (see below) and content-hashed JS/CSS bundle filenames under build/static/ (Vite rollupOptions.output, matching the former CRA layout).
Cache headers
serverless.yml sets object headers on upload:
- Fingerprinted assets (
static/**,flags/**):max-age=31536000, immutable index.html,error.html:no-cache, no-store, must-revalidate- Other objects:
max-age=3600
Locale files uploaded by publish-locales.mjs use max-age=3600.
Content Security Policy
csp-policy.mjs is the single source of truth. Production CSP is enforced by a CloudFront response header, not an HTML meta tag (duplicate policies are intersected by the browser, so an outdated CloudFront header can block features even after csp-policy.mjs changes are deployed to S3).
After editing csp-policy.mjs, deploy as usual; CI runs node bin/sync-cloudfront-csp.mjs --stage dev|prod after serverless client deploy. To sync manually:
node bin/sync-cloudfront-csp.mjs --stage dev
node bin/sync-cloudfront-csp.mjs --stage prod
Use --dry-run to print the policy without calling AWS. Local npm start has no CSP (Vite HMR).
SPA routing
The serverless-single-page-app-plugin rewrites unknown paths to index.html so client-side routing works on refresh.
Dependencies on other repos
Published gameslib and renderer packages use immutable 1.0.0-ci-{GITHUB_RUN_ID} versions. The cascade works like this:
- renderer publishes and dispatches
renderer_versionto gameslib and designer. - gameslib installs that renderer, tests, publishes, then dispatches
gameslib_version+renderer_versionto front and the backends. - Each consumer runs
npm ci, thenbin/install-ap-deps.mjs, which installs the exact versions from the dispatch payload (or falls back toci-deps.jsonon consumer-only pushes).
You do not need to manually bump AP dependency versions in package.json after a gameslib or renderer publish — CI updates ci-deps.json and the lockfile automatically.
Test vs deploy gameslib
Engine contract tests (test:engines) need the full gameslib registry (including experimental games). The CI test job installs @abstractplay/gameslib@development via install-ap-deps.mjs --for-tests without changing the pinned production version in ci-deps.json. Deploy builds use the production gameslib artifact from ci-deps.json, which matches what production users receive.
A future improvement: gameslib CI could publish a paired dev-registry version (same run ID) into ci-deps.json to avoid @development tag drift.