diff --git a/.tangled/workflows/preview-web-cleanup.yml b/.tangled/workflows/preview-web-cleanup.yml new file mode 100644 index 000000000..f274b1d6e --- /dev/null +++ b/.tangled/workflows/preview-web-cleanup.yml @@ -0,0 +1,19 @@ +when: + - event: pull_request + branch: sv-fe + types: + - closed + - merged + +engine: microvm +image: alpine + +steps: + - name: install nodejs + command: apk add nodejs npm + + - name: delete preview worker + command: | + name=$(sh web/scripts/preview-worker-name.sh) + CLOUDFLARE_API_TOKEN=$CLOUDFLARE_WORKER_DEPLOY_TOKEN \ + npx --yes wrangler delete --name "svfe-preview-$name" --force diff --git a/.tangled/workflows/preview-web.yml b/.tangled/workflows/preview-web.yml new file mode 100644 index 000000000..b6a93c57a --- /dev/null +++ b/.tangled/workflows/preview-web.yml @@ -0,0 +1,27 @@ +when: + - event: pull_request + branch: sv-fe + +engine: microvm +image: alpine + +steps: + - name: install nodejs, pnpm, jq + command: apk add nodejs pnpm jq + + - name: install web dependencies + command: | + cd web + pnpm install --frozen-lockfile + + - name: sync web static assets + command: | + out=$(nix build .#web-static-files --no-link --print-out-paths) + mkdir -p web/static + rm -rf web/static/fonts web/static/logos + cp -fr "$out"/* web/static + + - name: build and deploy preview worker + command: | + cd web + CLOUDFLARE_API_TOKEN=$CLOUDFLARE_WORKER_DEPLOY_TOKEN pnpm run deploy:cloudflare-preview diff --git a/web/.gitignore b/web/.gitignore index c7a5fd598..01029f374 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -35,3 +35,5 @@ storybook-static .eslintcache .browser-artifacts + +wrangler.preview.jsonc diff --git a/web/package.json b/web/package.json index 706657f9c..af5da5bf2 100644 --- a/web/package.json +++ b/web/package.json @@ -9,6 +9,7 @@ "build": "vite build && node scripts/asset-headers.ts", "preview": "vite preview --host 0.0.0.0", "deploy:cloudflare-dev": "./scripts/deploy-cloudflare-dev.sh", + "deploy:cloudflare-preview": "./scripts/deploy-cloudflare-preview.sh", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "lint": "prettier --check . && eslint .", "lint:cached": "prettier --check --cache . && eslint . --cache", diff --git a/web/scripts/deploy-cloudflare-preview.sh b/web/scripts/deploy-cloudflare-preview.sh new file mode 100755 index 000000000..3806c22c0 --- /dev/null +++ b/web/scripts/deploy-cloudflare-preview.sh @@ -0,0 +1,36 @@ +#!/bin/sh +set -eu + +cd "$(dirname "$0")/.." + +name=$(sh scripts/preview-worker-name.sh) +worker="svfe-preview-$name" +host="$name.preview.tangled.org" +config="wrangler.preview.jsonc" + +jq --arg worker "$worker" --arg host "$host" ' + .name = $worker + | .workers_dev = false + | del(.routes) + | .routes = [{ pattern: $host, custom_domain: true }] +' wrangler.dev.jsonc >"$config" + +jq -e --arg worker "$worker" --arg host "$host" ' + .name == $worker + and .workers_dev == false + and .routes == [{ pattern: $host, custom_domain: true }] +' "$config" >/dev/null || { + echo "deploy-cloudflare-preview: refusing to deploy, $config did not come out clean" >&2 + cat "$config" >&2 + exit 1 +} + +export SVELTE_ADAPTER=cloudflare +export WRANGLER_CONFIG="$config" +export VITE_OAUTH_CLIENT_ID="https://$host/oauth-client-metadata.json" +export VITE_OAUTH_REDIRECT_URI="https://$host/oauth/callback" + +pnpm run build +pnpm exec wrangler deploy --config "$config" + +echo "preview deployed: https://$host" diff --git a/web/scripts/preview-worker-name.sh b/web/scripts/preview-worker-name.sh new file mode 100755 index 000000000..533367de5 --- /dev/null +++ b/web/scripts/preview-worker-name.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -eu + +branch="${1:-${TANGLED_PR_SOURCE_BRANCH:-}}" +if [ -z "$branch" ]; then + echo "preview-worker-name: no branch given and TANGLED_PR_SOURCE_BRANCH is unset" >&2 + exit 1 +fi + +name=$(printf '%s' "$branch" | + tr '[:upper:]' '[:lower:]' | + tr '/_' '--' | + tr -cd 'a-z0-9-' | + cut -c1-40 | + sed 's/^-*//; s/-*$//') + +if [ -z "$name" ]; then + echo "preview-worker-name: branch '$branch' sanitizes to an empty name" >&2 + exit 1 +fi + +printf '%s\n' "$name" diff --git a/web/svelte.config.js b/web/svelte.config.js index a0354638a..95276358d 100644 --- a/web/svelte.config.js +++ b/web/svelte.config.js @@ -3,6 +3,7 @@ import adapter from "@sveltejs/adapter-node"; import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; const useCloudflareAdapter = process.env.SVELTE_ADAPTER === "cloudflare"; +const wranglerConfig = process.env.WRANGLER_CONFIG ?? "wrangler.dev.jsonc"; const config = { preprocess: vitePreprocess(), @@ -15,11 +16,11 @@ const config = { kit: { adapter: useCloudflareAdapter ? cloudflareAdapter({ - config: "wrangler.dev.jsonc", + config: wranglerConfig, // dev-only: serve the worker bindings from the local .wrangler/state // store. `wrangler kv key put --local --binding EMAIL_DID` seeds what // platform.env.EMAIL_DID answers; remoteBindings would need tokens. - platformProxy: { configPath: "wrangler.dev.jsonc", persist: true, remoteBindings: false } + platformProxy: { configPath: wranglerConfig, persist: true, remoteBindings: false } }) : adapter(), // storybook input, not app code. eslint keeps the app from importing it