Publishing on approval
Approving a listing writes a row; the page exists only after the site rebuilds. providers.rebuild
names what asks for that build — a Cloudflare Deploy Hook, a repository_dispatch to your own
GitHub Actions, or nothing — and providers.rebuildAuto decides whether an approval asks by itself.
Pick the route that matches Deploy on git push.
-
Create the hook: Cloudflare → Workers & Pages → your Worker → Settings → Build → Deploy hooks → Create deploy hook. The repository must be connected to Workers Builds first.
-
Store the URL. It takes no header and no body, so it is the whole credential.
Terminal pnpm cf:secrets DEPLOY_HOOK_URLThe prompt ends with
✔ <n> characters received — setting it on <worker>… done. -
Switch the provider on and deploy.
site.config.ts providers: { rebuild: 'deploy-hook', rebuildAuto: true },pnpm check:config, then push. The provider is read from the deployed build at request time.
The route when your own CI should run tests before every deploy, or the build minutes belong on GitHub’s account.
-
Turn the shipped workflow on.
deploy.ymllistens forrepository_dispatchof typerebuildand stays skipped until this variable exists; without it, approving publishes nothing. Deploy on git push lists its secrets.Terminal gh variable set USE_GITHUB_DEPLOY --body true -
Store a token and the repository: a fine-grained personal access token with Contents: Read and write on the repository that builds the site (it begins
github_pat_), and the repository asowner/repo.Terminal pnpm cf:secrets GITHUB_TOKEN GITHUB_REPO -
Switch the provider on and deploy.
site.config.ts providers: { rebuild: 'github-dispatch', rebuildAuto: true },GitHub fires
repository_dispatchonly on the default branch, which the workflow deploys from anyway.
The default. Every request is recorded as skipped, the dashboard bar counts what is not
published, and you publish when you choose:
pnpm deploy/admin/ carries a warning for as long as this is the setting.
Verify
Section titled “Verify”Approve one listing in /admin/ and follow it.
| What to check | Where | You should see |
|---|---|---|
| The request | the publishing bar on /admin/ |
Last request: requested via deploy-hook on <date> — build_uuid <id>, or repository_dispatch "rebuild" accepted by <owner/repo> |
| The row | pnpm exec wrangler d1 execute DB --remote --command "SELECT provider, status, detail FROM rebuild_requests ORDER BY created_at DESC LIMIT 1" |
requested, or coalesced when a build was already in flight |
| The build | Workers & Pages → your Worker → Builds, or the Actions run | a build shortly after the approval, with the [zerodirs-d1] line in its log |
| The page | curl -sI https://<your domain>/<listingBase>/<slug>/ | head -1 |
HTTP/2 200 once the build finishes |
Until then the submitter’s status page says “goes live with the next publish”, and the approval mail “within a few minutes”.
How it works
Section titled “How it works”What an approval triggers. Approving assigns the slug, stamps approved_at, writes the
audit row, queues the approval mail and calls requestRebuild() with reason approve:<id>.
Editing, featuring or unfeaturing an approved listing asks too, as do the hourly
expireFeatured (featured-expired) and the button (manual). Rejecting never does: nothing
on the live site changed.
The request row. Every request writes one rebuild_requests row — reason, provider,
status, attempts, detail — so the table is a complete log, not a sample. The status is
requested (the detail is the build id), coalesced, failed (the HTTP status or error text,
the hook URL scrubbed to [DEPLOY_HOOK_URL]), or skipped under none. A provider gets 10
seconds and never throws: an approve cannot fail because a build could not start.
Coalescing. A request within 60 seconds of a requested one is written as coalesced,
folded into rebuild <id>, and the provider is not called: ten approvals in a minute are one
build. Only requested rows open the window, so under none nothing coalesces and the
skipped rows stay an honest backlog. The hook itself answers already_exists with the id of a
build still queued; that is recorded as requested.
The hourly retry. retryRebuilds takes the oldest failed row with fewer than five
attempts, fires the provider once, and on success folds the other failed rows into that build.
A 429 or 5xx, a timeout or a transport error is retryable; a 404 (the hook is gone), any
other 4xx, or a GitHub 401, 403, 404 or 422 is not — but a row is rescanned until its
attempts run out either way. After five, only Publish now or pnpm deploy moves it.
Manual mode. rebuildAuto: false keeps the provider and makes every request wait:
approvals accumulate and Publish now fires one build. The button ignores rebuildAuto.
The bar. RebuildBar on /admin/ shows the newest row and the count of approvals newer
than the last requested row: “Up to date”, “N approved listings are waiting for the next
build”, “The last publish request failed”, or under none “N approved listings are not
published yet” with Record a publish — and since none never writes a requested row,
that count does not reset. Beneath: Last request: <status> via <provider> on <date> — <detail>.
When it is off or degraded
Section titled “When it is off or degraded”| State | Visitor sees | /admin/ reports |
Fix |
|---|---|---|---|
rebuild: 'none' |
approved listings stay off the site | warning; rows skipped |
pnpm deploy, or a provider |
'deploy-hook' without DEPLOY_HOOK_URL |
the same: the trigger degrades to none |
error: “Approvals will not trigger a build.” | pnpm cf:secrets DEPLOY_HOOK_URL |
'github-dispatch' missing a secret |
the same | error: the trigger falls back to none |
pnpm cf:secrets GITHUB_TOKEN GITHUB_REPO |
USE_GITHUB_DEPLOY unset |
the dispatch is accepted, the run is skipped | requested, no build |
gh variable set USE_GITHUB_DEPLOY --body true |
| The hook was deleted | nothing builds | failed — HTTP 404: the deploy hook no longer exists |
create a new hook, store it |
LISTINGS_SOURCE unset on the builder |
the build runs, the page is a 404 | requested, build green |
set the D1 variables |
Configuration
Section titled “Configuration”| Field | Default | What it changes |
|---|---|---|
providers.rebuild |
'none' |
'deploy-hook' POSTs DEPLOY_HOOK_URL; 'github-dispatch' posts to GITHUB_REPO with GITHUB_TOKEN; 'none' records and skips |
providers.rebuildAuto |
true |
whether an approval, an edit, a feature change or an expiry asks by itself |
See providers.
Directorysrc/
Directoryserver/
Directoryrebuild/
- index.ts
requestRebuild(), the window, the row - deploy-hook.ts
- github-dispatch.ts
- none.ts
- index.ts
- jobs.ts
retryRebuilds - queries.ts what approve, update and feature ask for
- components/admin/RebuildBar.astro
- loaders/index.ts how
LISTINGS_SOURCEis resolved
- .github/workflows/deploy.yml the
rebuildlistener - .dev.vars.example the dashboard path, the token instructions
- .env.example