diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml new file mode 100644 index 00000000..029bfdf2 --- /dev/null +++ b/.github/workflows/deploy-website.yml @@ -0,0 +1,85 @@ +name: Deploy website + +on: + push: + branches: [main] + paths: + - "apps/website/**" + - "install.sh" + - "install.ps1" + - ".github/workflows/deploy-website.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + environment: production + permissions: + contents: read + defaults: + run: + working-directory: apps/website + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 11.21.0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: apps/website/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Deploy to Cloudflare + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} + run: | + set -euo pipefail + if [ -z "${CLOUDFLARE_API_TOKEN}" ]; then + echo "::error::Missing secret CLOUDFLARE_API_TOKEN on Environment production" + exit 1 + fi + if [ -z "${CLOUDFLARE_ACCOUNT_ID}" ]; then + echo "::error::Missing variable CLOUDFLARE_ACCOUNT_ID on Environment production" + exit 1 + fi + if ! pnpm run deploy; then + echo "::error::wrangler deploy failed; Worker and static assets were not published" + exit 1 + fi + + # wrangler 4.x takes {bucket}/{key}; --bucket is not a valid flag on r2 object put. + # Quote content-type so the charset parameter is not split by the shell. + - name: Upload install scripts to R2 + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} + run: | + set -euo pipefail + if ! npx wrangler r2 object put amber-dist/install.sh \ + --file=../../install.sh \ + --remote \ + --content-type "text/plain; charset=utf-8"; then + echo "::error::Failed to upload install.sh to R2 bucket amber-dist" + exit 1 + fi + if ! npx wrangler r2 object put amber-dist/install.ps1 \ + --file=../../install.ps1 \ + --remote \ + --content-type "text/plain; charset=utf-8"; then + echo "::error::Failed to upload install.ps1 to R2 bucket amber-dist" + exit 1 + fi diff --git a/apps/website/README.md b/apps/website/README.md index c732db24..1cd7ae5a 100644 --- a/apps/website/README.md +++ b/apps/website/README.md @@ -19,7 +19,13 @@ The production bundle is written to `dist/`. The `prebuild` step copies repo-roo ## Cloudflare Deploy -`get.amberjs.com` is a custom domain on the same Worker as `amberjs.com` (`apps/website/wrangler.toml`). There is no GitHub Actions deploy job; publishing is a local Wrangler deploy (needs a Cloudflare account with access to this Worker): +`get.amberjs.com` is a custom domain on the same Worker as `amberjs.com` (`apps/website/wrangler.toml`). + +Production deploys run through GitHub Actions on the **`production`** Environment (secret `CLOUDFLARE_API_TOKEN`, variable `CLOUDFLARE_ACCOUNT_ID`). The workflow publishes on push to `main` when `apps/website/**`, `install.sh`, `install.ps1`, or the workflow file change. Tokens are not stored in the YAML. + +To publish manually: **Actions → Deploy website → Run workflow**. + +Local Wrangler is still available for dry-run and development (needs a Cloudflare account with access to this Worker): ```bash pnpm run deploy:dry-run @@ -28,9 +34,9 @@ pnpm run deploy `wrangler.toml` serves `dist/` as static assets and uses `single-page-application` fallback so direct links such as `/docs/installation` work on Cloudflare. -`src/worker.ts` serves `/install.sh` and `/install.ps1` from the R2 bucket `amber-dist` **when that object exists**, otherwise from the Worker static assets. Live `get.amberjs.com/install.sh` currently matches the asset-hosted copy (`content-type: application/x-sh`, `max-age=0`), so a Wrangler deploy updates the installer. If R2 later has `install.sh` / `install.ps1`, it shadows the deploy — also upload: +`src/worker.ts` serves `/install.sh` and `/install.ps1` from the R2 bucket `amber-dist` **when that object exists**, otherwise from the Worker static assets. If R2 has `install.sh` / `install.ps1`, it shadows the Worker assets. The production workflow also uploads both scripts to `amber-dist` after deploy so `get.amberjs.com` stays in sync. Local equivalent (wrangler 4.x uses `{bucket}/{key}`): ```bash -npx wrangler r2 object put install.sh --file=../../install.sh --bucket=amber-dist --remote --content-type text/plain -npx wrangler r2 object put install.ps1 --file=../../install.ps1 --bucket=amber-dist --remote --content-type text/plain +npx wrangler r2 object put amber-dist/install.sh --file=../../install.sh --remote --content-type "text/plain; charset=utf-8" +npx wrangler r2 object put amber-dist/install.ps1 --file=../../install.ps1 --remote --content-type "text/plain; charset=utf-8" ```