Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .github/workflows/deploy-edge-script.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Deploy edge script (staging)

# Deploys the bunny.net middleware edge script (edge-script/) that rewrites the
# displayed price based on the visitor's country (CDN-RequestCountryCode).
#
# Manual only — run from the Actions tab. Use this to test the edge script on
# a staging pull zone before wiring it to production.
#
# Required repo secrets (see edge-script/README.md for how to get them):
# BUNNY_STAGING_SCRIPT_ID - the edge script id (from bunny dashboard)
# BUNNY_STAGING_DEPLOY_KEY - the script's deploy key
#
# Optional inputs:
# script-id - override BUNNY_STAGING_SCRIPT_ID for a one-off deploy target
# file - path to the bundled script (default edge-script/dist/index.ts)

on:
workflow_dispatch:
inputs:
script-id:
description: "Edge script id (overrides BUNNY_STAGING_SCRIPT_ID secret)"
required: false
type: string
file:
description: "Bundled script path to deploy"
required: false
type: string
default: "edge-script/dist/index.ts"

# Prevent two manual runs from racing to upload the same script.
concurrency:
group: deploy-edge-script-staging
cancel-in-progress: true

permissions: {}

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5
with:
deno-version: v2.x

- name: Type-check
working-directory: edge-script
run: deno check src/main.ts

- name: Bundle
working-directory: edge-script
run: deno task build

- name: Deploy script to Bunny Edge Scripting
uses: BunnyWay/actions/deploy-script@cfed0ba7842c85686d0dc3a83d8d971991a752f6 # deploy-script@0.5.0
with:
script_id: ${{ inputs.script-id || secrets.BUNNY_STAGING_SCRIPT_ID }}
deploy_key: ${{ secrets.BUNNY_STAGING_DEPLOY_KEY }}
file: ${{ inputs.file }}
3 changes: 3 additions & 0 deletions edge-script/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Bundled output (regenerated by `deno task build`, deployed by the
# deploy-edge-script workflow). Keep out of git.
dist/
115 changes: 115 additions & 0 deletions edge-script/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Edge script: geo-localized pricing

A bunny.net **middleware** edge script that rewrites the displayed price on the
pricing page based on the visitor's country, using bunny's `CDN-RequestCountryCode`
header and `HTMLRewriter` (streaming, no buffering).

| Visitor | Price shown |
| --------------- | ------------- |
| UK (GB, GG, JE, IM) | £25/year |
| Everywhere else | $35/year |

## How it works

The static site (`/pricing/`) ships with the **UK price as the default**
(`£25/year`). The price is marked in HTML with
`data-price=""` attributes (on the hero heading and the price table cell), and
the Pricing Vue component is rendered with **no `client:load`** — so the price
is plain static HTML with no hydration that could snap it back.

On every request, bunny injects `CDN-RequestCountryCode` (ISO-3166-1 alpha-2).
The middleware:

1. Reads the country on `onOriginResponse`.
2. For **non-UK** visitors, runs the response through `HTMLRewriter`, replacing
the inner content of every `[data-price]` element with `$35/year`.
3. For **UK** visitors (or when the header is missing), passes the response
through unchanged — they already see £25/year in the static HTML.

This means the script degrades safely: if the script is disabled or the header
is absent, visitors see the UK default.

## Files

- `src/main.ts` — entry point (imports `pricing.ts`).
- `src/pricing.ts` — the middleware.
- `src/bunny-globals.d.ts` — ambient types for bunny runtime globals
(`HTMLRewriter`) not shipped with the SDK.
- `build.mjs` — esbuild + `@luca/esbuild-deno-loader` bundler, inlines the
`https://esm.sh/...` SDK import into a single `dist/index.ts`.
- `deno.json` — Deno tasks (`build`, `check`, `dev`).

## Local development

```bash
cd edge-script

# Type-check
deno check src/main.ts

# Bundle to dist/index.ts
deno task build

# Run locally (proxies to https://shroud.email/ as the origin)
deno task dev
```

Then test with curl (simulating a non-UK visitor — the default origin HTML
already has £25, so the script rewrites to $35):

```bash
curl http://127.0.0.1:8080/pricing/ | grep data-price
```

## Deploy (staging)

Deployment is via a **manual** GitHub workflow:
`.github/workflows/deploy-edge-script.yml` (run it from the Actions tab).

### One-time setup in bunny

1. In the bunny dashboard, create an **Edge Script** of type **Middleware**.
2. Attach it to your **staging Pull Zone** (the one fronting the static site).
3. Under **Script → Deployments → Settings**, copy the **Script ID** and
**Deploy Key**.

### One-time setup in GitHub

Add two repository secrets (Settings → Secrets and variables → Actions):

| Secret name | Value |
| ---------------------------- | ------------------------------ |
| `BUNNY_STAGING_SCRIPT_ID` | The edge script id |
| `BUNNY_STAGING_DEPLOY_KEY` | The script's deploy key |

### Deploy

Run the **"Deploy edge script (staging)"** workflow from the Actions tab. It
type-checks, bundles, and uploads `edge-script/dist/index.ts` to bunny.

### Verify

With the script attached to the staging pull zone, check the rewritten price:

```bash
# Non-UK (e.g. US) — bunny routes through a non-UK PoP, expect $35/year
curl -s https://staging.shroud.email/pricing/ | grep -o 'data-price="">[^<]*'

# Force a UK egress isn't trivial from curl; verify from a UK network/VPN,
# or check the bunny dashboard → Script → Logs.
```

## Production

Once validated on staging:

1. Create a production Edge Script + attach to the production Pull Zone.
2. Add `BUNNY_SCRIPT_ID` / `BUNNY_DEPLOY_KEY` secrets and copy this workflow
to `deploy-edge-script-prod.yml` (or extend the existing one with an
environment selector).

## Changing the prices

Edit `WORLDWIDE_PRICE` and `UK_COUNTRY_CODES` in `src/pricing.ts`, **and** the
default price baked into `src/components/organisms/Pricing.vue` (the UK price
must stay the static default so the no-script fallback stays correct).
16 changes: 16 additions & 0 deletions edge-script/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Bundles the edge script into a single file (dist/index.ts) that the bunny.net
// deploy action uploads. Mirrors the official BunnyWay/es-empty-script setup:
// esbuild + @luca/esbuild-deno-loader resolves the `https://esm.sh/...` import
// at build time and inlines it.
import * as esbuild from "npm:esbuild";
import { denoPlugins } from "jsr:@luca/esbuild-deno-loader";

await esbuild.build({
plugins: [...denoPlugins()],
entryPoints: ["./src/main.ts"],
outfile: "./dist/index.ts",
bundle: true,
format: "esm",
});

esbuild.stop();
13 changes: 13 additions & 0 deletions edge-script/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
// Used by `deno task build` to bundle the edge script.
"tasks": {
"build": "mkdir -p dist && deno run -A build.mjs",
"check": "deno check src/main.ts",
"dev": "deno run --allow-net --allow-read src/main.ts"
},
"lint": {
"rules": {
"tags": ["recommended"]
}
}
}
Loading