Previously, push to master triggered 6 separate deploy workflows (CF +
5 new v2.5.0 targets). If the fork hadn't configured, say, AWS or Azure
secrets, those workflows would run just to fail on "Check required
secrets" — producing 5 red X's in Actions tab per push, 5 failure
notifications, 5 wasted runner allocations.
Collapse to one top-level `deploy.yml` dispatcher that:
1. Runs a lightweight `detect` job (~5s) probing which platform secret
bundles are fully present — without invoking secrets.* in job-level
`if:` (which GH disallows).
2. Invokes the corresponding reusable child workflow via `uses:` +
`secrets: inherit` only when that platform's flag is true.
Each child workflow (`deploy-<target>.yml`) is now a reusable workflow:
- `push: [master]` trigger → removed (dispatcher owns push)
- `workflow_call:` trigger → added (invoked by dispatcher)
- `workflow_dispatch:` trigger → kept (manual runs via Actions UI)
- `if: github.repository != 'saltbo/zpan'` job guard → removed
(dispatcher enforces this once)
The old `deploy.yml` (CF Workers flow) is renamed to
`deploy-cloudflare.yml` for consistency with the other 5. Content of
the CF flow is unchanged.
For a fork with only CF configured: 1 dispatcher run + 1 cloudflare run.
For a fork with nothing configured: 1 dispatcher run with all 6 child
jobs shown as "Skipped" (not failed), and a ::notice:: pointing at the
README secrets table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
\`wrangler r2 bucket list\` has no \`--json\` flag (confirmed via
\`--help\`), so piping its text output to jq would fail silently. Switch
the \"Ensure R2 public-images bucket exists\" step to the CF REST API
which returns proper JSON and also lets us check-then-create in two
clean HTTP calls. Both endpoints use the same \`CLOUDFLARE_API_TOKEN\`
with R2 Storage: Edit scope.
Idempotency unchanged: check-exists-before-create.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new workflow steps turn the CF deploy into fully zero-touch for
the avatar/logo feature:
1. Ensure R2 bucket exists — `wrangler r2 bucket list --json` → create if
missing. Idempotent; prior deploys skip creation.
2. Enable managed public URL — `PUT /r2/buckets/.../domains/managed`
with {enabled:true}. CF returns the same pub-<hash>.r2.dev on every
call once enabled, so this is idempotent too. Then GET the domain
from the same endpoint and capture into step output.
3. Upsert `PUBLIC_IMAGES_URL` as a Worker secret via
`wrangler secret put`. Always overwritten to keep in sync with the
managed domain (which is stable but this is defensive).
Order: bucket/URL steps run BEFORE `wrangler deploy` so the binding
declared in wrangler.toml (new `[[r2_buckets]] binding = "PUBLIC_IMAGES"`)
references an existing bucket. The secret step runs AFTER deploy —
wrangler secret put requires the Worker to exist.
`wrangler.toml`: new `[[r2_buckets]]` for production and
`[[env.staging.r2_buckets]]` for the staging environment. Staging uses
a distinct bucket (`zpan-public-images-staging`) so preview deploys
don't mix objects into production.
README updated: `CLOUDFLARE_API_TOKEN` now requires
**R2 Storage: Edit** in addition to Workers Scripts + D1. The workflow
surfaces an actionable error message when this scope is missing.
User experience on a fresh fork: add the 2 GitHub secrets (ACCOUNT_ID
+ API_TOKEN with the 3 scopes), push. Workflow creates D1, creates R2,
enables public URL, migrates, builds, deploys, sets both BETTER_AUTH_SECRET
(random) and PUBLIC_IMAGES_URL (from R2). Avatars + org logos work
immediately — no trip to Admin → Storages required.
Non-CF deployments continue to require a user-added mode='public'
storage (no change to those paths).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Running `wrangler deploy --assets dist` from the repo root used the
source wrangler.toml (no assets.directory) and treated the whole dist/
tree as static assets, so the built worker under dist/zpan/ was served
as a file and SPA routes 404'd. It also forced wrangler to re-bundle
the TS entry, diverging from what @cloudflare/vite-plugin produced.
The vite plugin emits dist/zpan/wrangler.json with the right asset dir
(../client), the prebuilt worker, and all bindings. Deploy from there
so the GitHub Actions workflow matches Cloudflare Workers Builds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wrangler d1 create has no --json flag, so the first-time deploy path
errored with "Unknown argument: json" on fresh forks. d1 list --json
masked the bug for anyone whose CF account already had zpan-db.
Parse the TOML snippet wrangler prints instead, and fail fast if the
UUID can't be extracted so downstream sed/wrangler steps don't run with
an empty database_id.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add deploy.yml: auto-deploys latest upstream release tag on fork sync
- Support manual trigger with optional version override
- Auto-create D1 database, apply migrations, and set BETTER_AUTH_SECRET
- Only runs on forks (skipped on saltbo/zpan)
- Validate required secrets with clear error message
- Update README with fork + GitHub Actions deploy instructions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>