feat(sites): one command deploys a framework project or a directory - #172
Draft
bogdan-at-bunny wants to merge 4 commits into
Draft
feat(sites): one command deploys a framework project or a directory#172bogdan-at-bunny wants to merge 4 commits into
bogdan-at-bunny wants to merge 4 commits into
Conversation
|
@codex review |
🦋 Changeset detectedLatest commit: be46e3f The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
bogdan-at-bunny
marked this pull request as draft
August 19, 2026 15:45
bogdan-at-bunny
force-pushed
the
feat/framework-deploys
branch
2 times, most recently
from
August 25, 2026 10:59
f07936e to
d0444bd
Compare
`BuildManifestSchema` describes `.bunny/build.json`. It is the contract between an adapter and the CLI, and it holds everything the CLI needs to deploy a build it did not make: the server entry, the client directory, the pull zone settings the build asks for, the variables the script reads, and whether the build renders pages per request. The CLI knows no framework. It reads the manifest, so a new adapter needs no new CLI. The specification lives beside the adapters, in BunnyWay/bunny-adapters. `manifestVersion` moves only for a change an older CLI cannot read. A new optional field is not one, because the CLI ignores what it does not know. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124kDtA5hbacU1qEJ5pQo5M
Router v6 reads three file names out of the deploy it serves: `404.html`, `_redirects`, and `_headers`. Cloudflare Pages and Netlify read the same three, so nothing in the router knows about a framework, and every preset gets it. - `404.html` answers a path the deploy does not hold, at status 404. Without it the pull zone answers with bunny.net's error page, whatever the site built. That shipped: a documentation site went up, and every wrong URL showed bunny.net's page. - `_redirects` sends a real redirect. One rule per line, `/from /to [status]`, `#` comments, a trailing `*` captured as `:splat`, and `!` to beat a file at the same path. A rewrite (`200`) is not read: it would have the router fetch another path of its own site, which can be made to loop. - `_headers` carries the headers Bunny Storage cannot hold. The router reads the three through its own reserved `/_bunny/router/` path, which is the whole permission. Nothing else under `_bunny/` becomes reachable. The rules are held in memory, never written into the script, so a publish stays an environment variable change. The router now sets `Cache-Control` on every response, and a site's pull zone stops overriding it (`CacheControlMaxAgeOverride: -1`). The zone default of 30 days replaced every answer the script gave, so an HTML page could sit a month stale in a browser that no purge reaches. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124kDtA5hbacU1qEJ5pQo5M
A published Edge Script that will not start makes the edge answer 400 with an empty body, and the deploy said nothing: a green line, a URL, and a site that served nothing. `withastro/astro.build` deployed exactly like that. The check probes the production URL up to three times, each with its own query so the CDN cache cannot hold the answer. A redirect or a 404 counts as a working script; only 400 and 5xx are faults, and a site that cannot be reached at all is not called one. It also asks for a path the deploy cannot hold, because a 404 page that never reaches a visitor looks deployed and is not. Four smaller things around the same command: - `--name <name>` is honoured when the deploy creates the site. Without it an unattended run stopped with "No site specified and no linked site found." - `--region <code>` chooses the storage region for a site the deploy creates. Only `sites create` could name one before. - The domain prompt after a first deploy refuses a value that is not a hostname, and says so. It used to send it, and the API's answer is `An error has occurred.` - The upload counts bytes as well as files. `withastro/astro.build` sends 1.4 GB in 8828 files, and ten minutes of `4210/8828 files` says nothing about how much is left. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124kDtA5hbacU1qEJ5pQo5M
`bunny lab deploy astro` deploys an Astro project that renders pages per
request. Two commands, and no more:
bunny lab deploy astro
bunny lab undeploy astro
Astro's server becomes a standalone Edge Script. The client build goes
into a storage zone the script reads. The pull zone's origin is the
script, so nothing sits between a request and the code. The command
provisions the three resources on its first run, uploads the build, sets
every variable from what it already knows, applies the pull zone
settings the adapter asks for, and publishes. No password passes through
the terminal.
`bunny sites deploy` keeps deploying a directory of files. The share was
the problem: a project that renders per request cannot use
`CURRENT_DEPLOY`, because one script serves one release, and a directory
of files has no build manifest. Each flow carried checks for the shape it
was not. Nothing under `lab/` imports from `sites/`.
`lab` says the interface is still being shaped. The namespace is hidden
from help and from the landing page.
Measured against `withastro/astro/examples/ssr` and
`render-examples/astro-ssr`, deployed to a real account. What that
changed:
- Astro 7 is checked before the install, because npm answers an older
project with an ERESOLVE a developer cannot act on.
- The adapter it replaces is uninstalled, not only unimported.
`@astrojs/node@9` peers on `astro@^5`, so after an upgrade it makes
every later install fail.
- The pull zone's cache override goes off, or the edge rewrites every
`Cache-Control` the adapter sets.
- The state file belongs to the project, not to the working directory.
- The name prefix is not added twice.
`BuildManifestSchema` in `@bunny.net/config` is the contract with the
adapter. The CLI knows no framework: it reads the manifest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124kDtA5hbacU1qEJ5pQo5M
bogdan-at-bunny
force-pushed
the
feat/framework-deploys
branch
from
August 25, 2026 11:00
d0444bd to
be46e3f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
bunny sites deploydeploys this project, whichever shape it is: a build thatrenders each page per request on Edge Scripting, or a directory of static files.
The build decides which, and the command is the one that has always deployed a
directory.
Putting an Astro site on Edge Scripting took five steps, two passwords, three
environment variables, and two raw
bunny api POSTcalls. It now takes the firstline above.
An earlier revision of this branch added a top-level
bunny deployandbunny rollbackbesidebunny sites deploy. Neither has shipped, and both aregone: the project already says which shape it is, so choosing a command was never
the developer's job.
How
The CLI learns nothing about Astro. It reads
.bunny/build.json, the buildmanifest a framework adapter writes (the contract), and that file says
what to deploy: the one script file, the folder of client files, and the pull zone
settings and variables the site needs. The schema is
BuildManifestSchemain@bunny.net/config, besidebunny.jsonc, because two repositories share it. Anew adapter therefore needs no CLI release.
A framework site is a
sitessite whose script comes from the build. So itre-uses almost all of
sites: the storage zone, thedeploys/{id}/layout, thestate file, promote and rollback, domains, ssl, delete.
state.kind(
static|framework) tells them apart. The router commands refuse a frameworksite, and
sites deployments publishdelegates to the framework path.Two things a framework site needs that a static one does not:
_bunny/deploys/{id}/server.js, soa promote or rollback restores the code and the files it names together. An
Astro server bundle contains the hashed asset names it renders, so old files
with a new renderer is a broken page.
(
globalThis.__BUNNY_DEPLOY__). A published release carries the name of its ownasset folder, and cannot read another deploy's files.
Only a project that asks for a server is offered an adapter. This is the part
worth reviewing. An Astro project that prerenders every page is a directory of
files, and
bunny sites deployhas always deployed one, so it must not be pushedtowards a server it does not want.
sites/framework/detect.tsnames four signals,any one of which means the developer already asked:
src/pages/setsprerender = falseoutput: "server"@bunny.net/astro-adapteris a dependencyThe first matters most: since Astro 5 a project prerenders every page unless a
page opts out, and
astro buildstops with its own error when a page opts out andno adapter is installed. Reading the routes puts the offer before that failure.
The scan reads
src/pages/only, because that is whereprerenderapplies, andit stops at the first match.
patchAstroConfigrefuses any config it cannot editsafely, and prints the lines to paste.
A build and a site must be the same kind. A script's type is fixed when the
API creates it, so a static site cannot run a build's server and a framework site
cannot serve a directory. The deploy names the mismatch before it uploads
anything.
The build now runs before the site is resolved. Only the framework path did
that. So a failing build can no longer leave an empty site behind, and neither can
a script the platform refuses: the 10 MB check reads the bundle first, in
readServerBundle.Layout
commands/deploy/moves intocommands/sites/.build-manifest.tsandhealth.tssit directly undersites/, because both deploy paths read them;everything a framework site alone needs is under
sites/framework/.Verified against a real account
The showcase from
BunnyWay/bunny-adapters(a server build) and itsstatic-outputfixture (every route prerendered), deployed with this branch'scompiled binary on 2026-08-21:
.bunny/site.jsonprivate, no-store, a live request id and client IPmax-age=60/immutablesrc/pages/api/Two earlier findings, both fixed here:
StorageZoneId: -1, notnull, so sitediscovery has to treat anything below 1 as absent. Without it,
bunny sites listnever finds a framework site.purges again; a probe cannot tell the outgoing release from the incoming one,
since both answer 200 with a page.
One defect this does not fix
A static site's HTML answers with
cache-control: max-age=25600000, about 296days in a visitor's browser. Router v4 sets
Cache-Controlonly on a responsethat carries none, and Bunny Storage sends one after all: it arrives with
cdn-requestpullcode: 206from the file server, on a hit and on a miss. So thestatic layer's 60 second page lifetime never reaches a page, and turning the
zone's
CacheControlMaxAgeOverrideoff did not achieve what it was meant to.That is the static layer's own ground, and the fix changes caching for every
static site, so it needs its own change and its own measurement.
Not in this change
There is no
--previewfor a build that renders per request, and the commandsays so rather than publishing production in silence. Such a preview cannot be a
per-deploy snapshot: one script publishes one release at a time, and the page and
its assets are one unit. The design (an environment per preview, sharing the
storage zone) is in bunny-adapters,
plans/one-command-deploys.md, withbunny sites ci initfor framework sites and a top-levelbunny env.Tests
bun test962 pass,bun run typecheckclean,bun run lintclean, and thecompiled binary ran every live check above. New unit tests cover the four
detection signals and the project that shows none of them, the manifest reader
(bad JSON, unknown version, version floor, missing build), the Astro config
patch, the pull-zone settings diff, the env writer (a secret is written once, so
a rotated password survives a deploy), framework-site discovery, and deleting a
deploy's server bundle with its files.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NpdDkzyH7pPq5FZqPCRhzE