Wrangling the wrangler
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Copy to .env.pages.prod and fill in the values for production deploys.
|
# Copy to .env.worker.prod and fill in the values for production deploys.
|
||||||
PUBLIC_SITE_URL=https://swansea-airport.wales
|
PUBLIC_SITE_URL=https://swansea-airport.wales
|
||||||
PUBLIC_PPR_API_BASE=https://ppr.swansea-airport.wales/api/v1
|
PUBLIC_PPR_API_BASE=https://ppr.swansea-airport.wales/api/v1
|
||||||
PUBLIC_WEATHER_BASE=https://wx.swansea-airport.wales
|
PUBLIC_WEATHER_BASE=https://wx.swansea-airport.wales
|
||||||
@@ -9,7 +9,10 @@ DIRECTUS_PUBLIC_URL=https://cms.swansea-airport.wales
|
|||||||
DIRECTUS_ADMIN_TOKEN=replace-with-production-directus-token
|
DIRECTUS_ADMIN_TOKEN=replace-with-production-directus-token
|
||||||
DIRECTUS_HOMEPAGE_BANNER_FOLDER=homepage-banners
|
DIRECTUS_HOMEPAGE_BANNER_FOLDER=homepage-banners
|
||||||
|
|
||||||
CF_PAGES_PROJECT_NAME=swansea-airfield
|
CF_WORKER_NAME=swansea-airfield
|
||||||
CF_PAGES_BRANCH=main
|
CF_WORKER_COMPATIBILITY_DATE=2026-06-21
|
||||||
|
# Optional. Use one of these if you want the deploy command to attach a route or custom domain.
|
||||||
|
# CF_WORKER_ROUTE=swansea-airport.wales/*
|
||||||
|
# CF_WORKER_DOMAIN=swansea-airport.wales
|
||||||
CLOUDFLARE_ACCOUNT_ID=replace-with-cloudflare-account-id
|
CLOUDFLARE_ACCOUNT_ID=replace-with-cloudflare-account-id
|
||||||
CLOUDFLARE_API_TOKEN=replace-with-cloudflare-api-token
|
CLOUDFLARE_API_TOKEN=replace-with-cloudflare-api-token
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Copy to .env.pages.test and fill in the values for test/preview deploys.
|
# Copy to .env.worker.test and fill in the values for test deploys.
|
||||||
PUBLIC_SITE_URL=https://test.example.pages.dev
|
PUBLIC_SITE_URL=https://new.swansea-airport.wales
|
||||||
PUBLIC_PPR_API_BASE=https://test-ppr.example.com/api/v1
|
PUBLIC_PPR_API_BASE=https://pprdev.pattinson.org/api/v1
|
||||||
PUBLIC_WEATHER_BASE=https://wx.swansea-airport.wales
|
PUBLIC_WEATHER_BASE=https://wx.swansea-airport.wales
|
||||||
PUBLIC_WEATHER_MQTT_HOST=https://wx.swansea-airport.wales/mqtt
|
PUBLIC_WEATHER_MQTT_HOST=https://wx.swansea-airport.wales/mqtt
|
||||||
|
|
||||||
@@ -9,7 +9,10 @@ DIRECTUS_PUBLIC_URL=https://egfhcmstest.pattinson.org
|
|||||||
DIRECTUS_ADMIN_TOKEN=replace-with-test-directus-token
|
DIRECTUS_ADMIN_TOKEN=replace-with-test-directus-token
|
||||||
DIRECTUS_HOMEPAGE_BANNER_FOLDER=homepage-banners
|
DIRECTUS_HOMEPAGE_BANNER_FOLDER=homepage-banners
|
||||||
|
|
||||||
CF_PAGES_PROJECT_NAME=swansea-airfield
|
CF_WORKER_NAME=egfh
|
||||||
CF_PAGES_BRANCH=test
|
CF_WORKER_COMPATIBILITY_DATE=2026-06-21
|
||||||
|
# Optional. Use one of these if you want the deploy command to attach a route or custom domain.
|
||||||
|
# CF_WORKER_ROUTE=test.swansea-airport.wales/*
|
||||||
|
# CF_WORKER_DOMAIN=test.swansea-airport.wales
|
||||||
CLOUDFLARE_ACCOUNT_ID=replace-with-cloudflare-account-id
|
CLOUDFLARE_ACCOUNT_ID=replace-with-cloudflare-account-id
|
||||||
CLOUDFLARE_API_TOKEN=replace-with-cloudflare-api-token
|
CLOUDFLARE_API_TOKEN=replace-with-cloudflare-api-token
|
||||||
+2
-2
@@ -2,6 +2,6 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
.astro
|
.astro
|
||||||
.env
|
.env
|
||||||
.env.pages.test
|
.env.worker.test
|
||||||
.env.pages.prod
|
.env.worker.prod
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -23,29 +23,38 @@ Production-ready airfield website stack built with Astro, Directus, PostgreSQL,
|
|||||||
- The frontend service bind-mounts the project into `/app`, keeps `node_modules` and `.astro` in named volumes, and serves the site with `astro dev` on the published frontend port.
|
- The frontend service bind-mounts the project into `/app`, keeps `node_modules` and `.astro` in named volumes, and serves the site with `astro dev` on the published frontend port.
|
||||||
- Layout and page structure are controlled entirely by Astro.
|
- Layout and page structure are controlled entirely by Astro.
|
||||||
- Frontend source edits should appear without rebuilding the container image.
|
- Frontend source edits should appear without rebuilding the container image.
|
||||||
- Cloudflare Pages deployment uses Direct Upload with Wrangler. See `docs/cloudflare-pages.md` for the test/prod URL workflow.
|
- Cloudflare Worker deployment uses Wrangler static assets. See `docs/cloudflare-worker.md` for the test/prod URL workflow.
|
||||||
|
|
||||||
## Cloudflare Pages deployment
|
## Cloudflare Worker deployment
|
||||||
|
|
||||||
|
This project is normally developed through Docker Compose, so run the Worker build/deploy scripts inside the `web` service.
|
||||||
|
|
||||||
|
Copy the Worker env files first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.worker.test.example .env.worker.test
|
||||||
|
cp .env.worker.prod.example .env.worker.prod
|
||||||
|
```
|
||||||
|
|
||||||
Build static files with the test URL set:
|
Build static files with the test URL set:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build:pages:test
|
docker compose exec web npm run build:worker:test
|
||||||
```
|
```
|
||||||
|
|
||||||
Deploy the test build to the Cloudflare Pages `test` branch:
|
Deploy the test build to the test Cloudflare Worker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run deploy:pages:test
|
docker compose exec web npm run deploy:worker:test
|
||||||
```
|
```
|
||||||
|
|
||||||
Deploy production with the production URL set:
|
Deploy production with the production URL set:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run deploy:pages:prod
|
docker compose exec web npm run deploy:worker:prod
|
||||||
```
|
```
|
||||||
|
|
||||||
Copy `.env.pages.test.example` and `.env.pages.prod.example` before using these commands.
|
The deploy scripts build first, then upload `dist/` with Wrangler. The `test` command uses `.env.worker.test`; the production command uses `.env.worker.prod`.
|
||||||
|
|
||||||
## Programmatic Directus schema bootstrap
|
## Programmatic Directus schema bootstrap
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
# Cloudflare Pages Direct Upload
|
|
||||||
|
|
||||||
This project uses Astro static output, so Cloudflare Pages only needs the generated `dist/` directory.
|
|
||||||
|
|
||||||
Because the source repository is hosted in Gitea, use Cloudflare Pages Direct Upload rather than Cloudflare's GitHub/GitLab integration. The build happens in your local machine, Docker container, or Gitea CI runner, then Wrangler uploads the finished files to Cloudflare.
|
|
||||||
|
|
||||||
## URL Sets
|
|
||||||
|
|
||||||
Keep three sets of URLs separate:
|
|
||||||
|
|
||||||
- Local development: `.env`, used by Docker Compose and `astro dev`.
|
|
||||||
- Test/preview deployment: `.env.pages.test`, used by `npm run build:pages:test` and `npm run deploy:pages:test`.
|
|
||||||
- Production deployment: `.env.pages.prod`, used by `npm run build:pages:prod` and `npm run deploy:pages:prod`.
|
|
||||||
|
|
||||||
The important distinction is that Astro fetches Directus content during the build. The URLs in the env file selected for the build are baked into the generated static files.
|
|
||||||
|
|
||||||
## First-Time Setup
|
|
||||||
|
|
||||||
Copy the example files and fill in the real values:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.pages.test.example .env.pages.test
|
|
||||||
cp .env.pages.prod.example .env.pages.prod
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not commit `.env.pages.test` or `.env.pages.prod`; they contain API tokens.
|
|
||||||
|
|
||||||
Create a Cloudflare API token with permission to deploy Pages projects, then set these values in both env files:
|
|
||||||
|
|
||||||
```env
|
|
||||||
CF_PAGES_PROJECT_NAME=swansea-airfield
|
|
||||||
CLOUDFLARE_ACCOUNT_ID=...
|
|
||||||
CLOUDFLARE_API_TOKEN=...
|
|
||||||
```
|
|
||||||
|
|
||||||
For Directus, use public HTTPS URLs in Pages env files. Do not use Docker-only hostnames such as `http://directus:8055` outside Docker Compose.
|
|
||||||
|
|
||||||
## Build Locally For Test
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build:pages:test
|
|
||||||
```
|
|
||||||
|
|
||||||
This loads `.env.pages.test`, fetches content from the test Directus URL, and writes static files to `dist/`.
|
|
||||||
|
|
||||||
## Deploy To Cloudflare Test Branch
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run deploy:pages:test
|
|
||||||
```
|
|
||||||
|
|
||||||
This builds with `.env.pages.test`, then uploads `dist/` to the `test` Pages branch. Cloudflare will serve it on the matching branch preview URL.
|
|
||||||
|
|
||||||
## Deploy To Production
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run deploy:pages:prod
|
|
||||||
```
|
|
||||||
|
|
||||||
This builds with `.env.pages.prod`, then uploads `dist/` to the `main` Pages branch.
|
|
||||||
|
|
||||||
## Gitea CI Shape
|
|
||||||
|
|
||||||
In Gitea Actions, store the same values as CI secrets and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm ci
|
|
||||||
npm run deploy:pages:prod
|
|
||||||
```
|
|
||||||
|
|
||||||
The deploy script reads from the env file first, but existing CI environment variables win. That means secrets injected by Gitea can override placeholder values from a committed example or generated env file.
|
|
||||||
|
|
||||||
## Content Updates
|
|
||||||
|
|
||||||
Static deploys are snapshots. If Directus content changes after deployment, Cloudflare Pages will not update until another build and deploy runs.
|
|
||||||
|
|
||||||
For production, trigger `npm run deploy:pages:prod` from either:
|
|
||||||
|
|
||||||
- a code push to Gitea,
|
|
||||||
- a manual Gitea Actions workflow,
|
|
||||||
- a Directus webhook that calls your CI runner,
|
|
||||||
- or a scheduled CI job.
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Cloudflare Worker Static Assets
|
||||||
|
|
||||||
|
This project uses Astro static output, so Cloudflare only needs the generated `dist/` directory.
|
||||||
|
|
||||||
|
The public site is deployed as a Cloudflare Worker serving static assets. Wrangler supports deploying a directory of static assets with `wrangler deploy dist`.
|
||||||
|
|
||||||
|
## URL Sets
|
||||||
|
|
||||||
|
Keep three sets of URLs separate:
|
||||||
|
|
||||||
|
- Local development: `.env`, used by Docker Compose and `astro dev`.
|
||||||
|
- Test Worker deployment: `.env.worker.test`, used by `npm run build:worker:test` and `npm run deploy:worker:test`.
|
||||||
|
- Production Worker deployment: `.env.worker.prod`, used by `npm run build:worker:prod` and `npm run deploy:worker:prod`.
|
||||||
|
|
||||||
|
The important distinction is that Astro fetches Directus content during the build. The URLs in the env file selected for the build are baked into the generated static files.
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
|
||||||
|
Copy the example files and fill in the real values:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.worker.test.example .env.worker.test
|
||||||
|
cp .env.worker.prod.example .env.worker.prod
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not commit `.env.worker.test` or `.env.worker.prod`; they contain API tokens.
|
||||||
|
|
||||||
|
Create a Cloudflare API token that can deploy Workers, then set these values in both env files:
|
||||||
|
|
||||||
|
```env
|
||||||
|
CLOUDFLARE_ACCOUNT_ID=...
|
||||||
|
CLOUDFLARE_API_TOKEN=...
|
||||||
|
```
|
||||||
|
|
||||||
|
In practice, Wrangler's static asset upload currently works reliably with a user-owned API token scoped to the target account. Account-owned tokens may authenticate successfully with `wrangler whoami` but still fail during `workers/scripts/<name>/assets-upload-session` with `Authentication error [code: 10000]`.
|
||||||
|
|
||||||
|
For a user-owned token, start with these permissions:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Account -> Workers Scripts -> Edit
|
||||||
|
Account -> Account Settings -> Read
|
||||||
|
User -> Memberships -> Read
|
||||||
|
```
|
||||||
|
|
||||||
|
If the deploy command manages Worker routes or domains, also grant the matching zone/worker route permissions for the target domain.
|
||||||
|
|
||||||
|
Each env file also names the target Worker:
|
||||||
|
|
||||||
|
```env
|
||||||
|
CF_WORKER_NAME=swansea-airfield-test
|
||||||
|
```
|
||||||
|
|
||||||
|
For Directus, use public HTTPS URLs in Worker env files. Do not use Docker-only hostnames such as `http://directus:8055` outside Docker Compose.
|
||||||
|
|
||||||
|
## Build Locally For Test
|
||||||
|
|
||||||
|
When working through Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec web npm run build:worker:test
|
||||||
|
```
|
||||||
|
|
||||||
|
This loads `.env.worker.test`, fetches content from the test Directus URL, and writes static files to `dist/`.
|
||||||
|
|
||||||
|
## Deploy To The Test Worker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec web npm run deploy:worker:test
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds with `.env.worker.test`, then uploads `dist/` to the Worker named by `CF_WORKER_NAME`.
|
||||||
|
|
||||||
|
## Deploy To The Production Worker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec web npm run deploy:worker:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds with `.env.worker.prod`, then uploads `dist/` to the production Worker.
|
||||||
|
|
||||||
|
## Routes And Domains
|
||||||
|
|
||||||
|
The deploy wrapper can optionally attach a route or custom domain if the env file sets one of these:
|
||||||
|
|
||||||
|
```env
|
||||||
|
CF_WORKER_ROUTE=swansea-airport.wales/*
|
||||||
|
CF_WORKER_DOMAIN=swansea-airport.wales
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave both unset if routes/domains are managed in the Cloudflare dashboard.
|
||||||
|
|
||||||
|
## Gitea CI Shape
|
||||||
|
|
||||||
|
In Gitea Actions, create the selected env file from CI secrets, then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm run deploy:worker:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
The deploy script treats the selected env file as the source of truth. This is deliberate because the Docker Compose `web` service already has local development variables in its environment, and production/test deploys need to override them.
|
||||||
|
|
||||||
|
## Content Updates
|
||||||
|
|
||||||
|
Static deploys are snapshots. If Directus content changes after deployment, the Worker will not update until another build and deploy runs.
|
||||||
|
|
||||||
|
For production, trigger `npm run deploy:worker:prod` from either:
|
||||||
|
|
||||||
|
- a code push to Gitea,
|
||||||
|
- a manual Gitea Actions workflow,
|
||||||
|
- a Directus webhook that calls your CI runner,
|
||||||
|
- or a scheduled CI job.
|
||||||
+4
-4
@@ -5,10 +5,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"build:pages:test": "node scripts/cloudflare-pages.mjs build .env.pages.test",
|
"build:worker:test": "node scripts/cloudflare-worker.mjs build .env.worker.test",
|
||||||
"build:pages:prod": "node scripts/cloudflare-pages.mjs build .env.pages.prod",
|
"build:worker:prod": "node scripts/cloudflare-worker.mjs build .env.worker.prod",
|
||||||
"deploy:pages:test": "node scripts/cloudflare-pages.mjs deploy .env.pages.test --branch test",
|
"deploy:worker:test": "node scripts/cloudflare-worker.mjs deploy .env.worker.test",
|
||||||
"deploy:pages:prod": "node scripts/cloudflare-pages.mjs deploy .env.pages.prod --branch main",
|
"deploy:worker:prod": "node scripts/cloudflare-worker.mjs deploy .env.worker.prod",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"check": "astro check",
|
"check": "astro check",
|
||||||
"bootstrap:directus": "node scripts/bootstrap-directus.mjs"
|
"bootstrap:directus": "node scripts/bootstrap-directus.mjs"
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { existsSync, readFileSync } from 'node:fs';
|
|||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import { basename } from 'node:path';
|
import { basename } from 'node:path';
|
||||||
|
|
||||||
const [command, envFile, ...args] = process.argv.slice(2);
|
const [command, envFile] = process.argv.slice(2);
|
||||||
|
|
||||||
if (!['build', 'deploy'].includes(command) || !envFile) {
|
if (!['build', 'deploy'].includes(command) || !envFile) {
|
||||||
console.error('Usage: node scripts/cloudflare-pages.mjs <build|deploy> <env-file> [--branch name] [--project name]');
|
console.error('Usage: node scripts/cloudflare-worker.mjs <build|deploy> <env-file>');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,20 +14,6 @@ if (!existsSync(envFile)) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseArgs(values) {
|
|
||||||
const options = {};
|
|
||||||
|
|
||||||
for (let index = 0; index < values.length; index += 1) {
|
|
||||||
const value = values[index];
|
|
||||||
if (value === '--branch' || value === '--project') {
|
|
||||||
options[value.slice(2)] = values[index + 1];
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseEnvFile(path) {
|
function parseEnvFile(path) {
|
||||||
const entries = {};
|
const entries = {};
|
||||||
const lines = readFileSync(path, 'utf8').split(/\r?\n/);
|
const lines = readFileSync(path, 'utf8').split(/\r?\n/);
|
||||||
@@ -57,9 +43,7 @@ function parseEnvFile(path) {
|
|||||||
|
|
||||||
function applyEnv(entries) {
|
function applyEnv(entries) {
|
||||||
for (const [key, value] of Object.entries(entries)) {
|
for (const [key, value] of Object.entries(entries)) {
|
||||||
if (process.env[key] === undefined) {
|
process.env[key] = value;
|
||||||
process.env[key] = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +70,10 @@ function run(name, commandName, commandArgs) {
|
|||||||
const envEntries = parseEnvFile(envFile);
|
const envEntries = parseEnvFile(envFile);
|
||||||
applyEnv(envEntries);
|
applyEnv(envEntries);
|
||||||
|
|
||||||
const options = parseArgs(args);
|
const workerName = process.env.CF_WORKER_NAME;
|
||||||
const projectName = options.project ?? process.env.CF_PAGES_PROJECT_NAME;
|
const workerRoute = process.env.CF_WORKER_ROUTE;
|
||||||
const branch = options.branch ?? process.env.CF_PAGES_BRANCH;
|
const workerDomain = process.env.CF_WORKER_DOMAIN;
|
||||||
|
const compatibilityDate = process.env.CF_WORKER_COMPATIBILITY_DATE;
|
||||||
|
|
||||||
console.log(`Using ${basename(envFile)} for ${command}.`);
|
console.log(`Using ${basename(envFile)} for ${command}.`);
|
||||||
console.log(`PUBLIC_SITE_URL=${process.env.PUBLIC_SITE_URL ?? '<unset>'}`);
|
console.log(`PUBLIC_SITE_URL=${process.env.PUBLIC_SITE_URL ?? '<unset>'}`);
|
||||||
@@ -97,15 +82,24 @@ console.log(`DIRECTUS_URL=${process.env.DIRECTUS_URL ?? '<unset>'}`);
|
|||||||
await run('Astro build', 'npm', ['run', 'build']);
|
await run('Astro build', 'npm', ['run', 'build']);
|
||||||
|
|
||||||
if (command === 'deploy') {
|
if (command === 'deploy') {
|
||||||
if (!projectName) {
|
if (!workerName) {
|
||||||
console.error('Missing CF_PAGES_PROJECT_NAME. Set it in the env file or pass --project <name>.');
|
console.error('Missing CF_WORKER_NAME. Set it in the env file.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const wranglerArgs = ['wrangler', 'pages', 'deploy', 'dist', '--project-name', projectName];
|
const wranglerArgs = ['wrangler', 'deploy', 'dist', '--name', workerName];
|
||||||
if (branch) {
|
|
||||||
wranglerArgs.push('--branch', branch);
|
if (compatibilityDate) {
|
||||||
|
wranglerArgs.push('--compatibility-date', compatibilityDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
await run('Cloudflare Pages deploy', 'npx', wranglerArgs);
|
if (workerRoute) {
|
||||||
|
wranglerArgs.push('--route', workerRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workerDomain) {
|
||||||
|
wranglerArgs.push('--domain', workerDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
await run('Cloudflare Worker deploy', 'npx', wranglerArgs);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user