bunny sites#125
Conversation
Grounds the bunny sites plan in verified repo machinery: defineCommand/ defineNamespace registration in cli.ts, core/manifest.ts local-link pattern, storage files-api uploader (caller-side SHA-256 checksums), scripts env-var promote lever, createHostnamesCommands factory for the domains phase, and the changeset/testing/docs conventions each PR must follow. Adds a Phase 0 spike question for how a middleware script attaches to a storage-backed pull zone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J6CzqnCzQrV6exqx4PrF13
|
@codex review |
🦋 Changeset detectedLatest commit: 3230dc7 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 |
…c deploys
Implements the docs/plans/sites.md plan (phases 1-5):
- sites create provisions storage zone + pull zone + middleware router
(attached via the pull zone's MiddlewareScriptId); idempotent re-runs
converge on half-finished creates
- sites deploy uploads immutable deploys/{id}/ directories (git short-sha
when clean, content hash otherwise; unchanged deploys are no-ops) and
promotes by flipping the router's CURRENT_DEPLOY env var + purging the
cache; --build runs the configured build with remote env merged
- sites deployments list/publish/prune: instant rollback via --previous,
prune never drops current/previous
- sites domains mounts core/hostnames createHostnamesCommands (new
onAdded/onRemoved hooks) and pairs every apex with a *.preview.<domain>
wildcard for per-deploy preview URLs
- sites env manages build-time vars at _bunny/env.json (403-blocked by
the router, masked list, dotenv pull)
- site state lives at _bunny/site.json in the storage zone with a
sha256-etag optimistic lock; .bunny/site.json is the local pointer
- @bunny.net/app-config gains an optional sites { name, dir, build }
block consumed for deploy defaults
- co-located bun tests (36) via a swappable siteFiles IO seam and
path-branching fake clients; README/AGENTS/skills docs + changesets
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J6CzqnCzQrV6exqx4PrF13
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 215874a663
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The live API returns GET /pullzone as { Items, CurrentPage, HasMoreItems }
for some queries (e.g. ?search=) even though the OpenAPI spec types the
response as a plain array, so createSite crashed on .find during pull
zone lookup. Route all pull zone listing through fetchPullZones, which
accepts both shapes and follows HasMoreItems across pages (with an
empty-page guard). /storagezone?search= genuinely returns a plain array,
so the storage lookup is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J6CzqnCzQrV6exqx4PrF13
Greptile SummaryThis PR introduces the complete
Confidence Score: 5/5Safe to merge; all critical issues raised in prior rounds have been addressed in this revision. All previously raised issues were resolved: the optimistic-lock conflict now correctly reconciles current/previous pointers via the promotedTo option; fail-open ownership checks re-throw non-404 errors; the API key goes via stdin rather than argv; prune validates deploy IDs with isValidDeployId and passes removedIds to prevent resurrection; path-preview directory requests are handled correctly by the global trailing-slash expansion; and the router strips client-sent x-bunny-preview before routing. The remaining findings are minor consistency gaps in deploy-record ID validation at the parse boundary. packages/cli/src/commands/sites/constants.ts (parseRemoteState deploy-record ID validation) and packages/cli/src/commands/sites/deploy.ts (alreadyUploaded.id usage) are worth a second look for defensive hardening, but neither blocks a merge. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant CLI as bunny CLI
participant Store as Storage Zone
participant Router as Edge Script
participant CDN as Pull Zone / CDN
participant API as Bunny API
note over CLI,API: sites create
CLI->>API: POST /storagezone
CLI->>API: POST /compute/script
CLI->>API: POST /pullzone
CLI->>Store: PUT _bunny/site.json
note over CLI,CDN: sites deploy
CLI->>Store: GET _bunny/site.json (etag read)
CLI->>CLI: hashFiles + resolveDeployIdentity
alt content unchanged
CLI-->>CLI: skip upload
else new content
CLI->>Store: PUT deploys/id/...
end
CLI->>Store: PUT _bunny/site.json (ETag merge)
alt --production
CLI->>API: "PUT script variables CURRENT_DEPLOY=id"
CLI->>API: purgeCache x2
CLI->>Store: "PUT _bunny/site.json promotedTo=id"
end
note over Router,CDN: Request routing
CDN->>Router: onOriginRequest
Router->>Router: strip x-bunny-preview header
alt path preview
Router->>Store: forward with preview header
else hostname preview
Router->>Store: GET /deploys/id/path
else production
Router->>Store: GET /deploys/CURRENT_DEPLOY/path
end
Router->>CDN: onOriginResponse HTML rewrite + noindex
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant CLI as bunny CLI
participant Store as Storage Zone
participant Router as Edge Script
participant CDN as Pull Zone / CDN
participant API as Bunny API
note over CLI,API: sites create
CLI->>API: POST /storagezone
CLI->>API: POST /compute/script
CLI->>API: POST /pullzone
CLI->>Store: PUT _bunny/site.json
note over CLI,CDN: sites deploy
CLI->>Store: GET _bunny/site.json (etag read)
CLI->>CLI: hashFiles + resolveDeployIdentity
alt content unchanged
CLI-->>CLI: skip upload
else new content
CLI->>Store: PUT deploys/id/...
end
CLI->>Store: PUT _bunny/site.json (ETag merge)
alt --production
CLI->>API: "PUT script variables CURRENT_DEPLOY=id"
CLI->>API: purgeCache x2
CLI->>Store: "PUT _bunny/site.json promotedTo=id"
end
note over Router,CDN: Request routing
CDN->>Router: onOriginRequest
Router->>Router: strip x-bunny-preview header
alt path preview
Router->>Store: forward with preview header
else hostname preview
Router->>Store: GET /deploys/id/path
else production
Router->>Store: GET /deploys/CURRENT_DEPLOY/path
end
Router->>CDN: onOriginResponse HTML rewrite + noindex
Reviews (19): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile |
bunny sites feature…an-15thnb # Conflicts: # AGENTS.md
…ader; sites changesets to patch
|
@greptile-apps review |
|
@greptile-apps review |
…an-15thnb # Conflicts: # packages/cli/src/cli.ts # skills/bunny-cli/SKILL.md
|
@greptile-apps review |
bunny sitesNew
sitesnamespace:create,list,show,open,deploy,deployments list/publish/prune,domains,ssl,ci init,link,unlink,upgrade-router,delete.Provisioning
sites createprovisions a storage zone + pull zone + middleware router per site. Underlying zones are namedsites-<name>-<random id>so globally-taken zone names can't block a create; commands always take the clean site name.Deploys
sites deployuploads immutable deploys with git-sha or content-hash IDs (no-op when nothing changed) to per-deploy preview URLs;--production/--prodpublishes by flipping the router'sCURRENT_DEPLOYenv var and purging the cache.--buildruns the configured build command and infers the framework's output dir when no dir is given._bunny/site.jsoninside the storage zone, 403-blocked by the router.Domains & CI
sites domainsattaches custom domains plus a*.preview.<domain>wildcard for per-deploy preview URLs.sites ci initscaffolds a GitHub Actions workflow with framework detection (static-output presets): previews on PRs, production on merge to main. The GitHub secret is passed via stdin.Rollback & cleanup
sites deployments list/publish/prunecover rollback and old-deploy cleanup.Supporting changes
core/bunny-config.ts: sharedbunny.jsonclocator/parser (walks up from cwd, honors--config); apps config now reads through it too.core/jsonc.ts: surgical JSONC edit helpers so writes preserve comments/formatting.onAdded/onRemovedhooks (used by sites for the preview wildcard).examples/sites/(static HTML, Vite, Astro, Hugo, Next.js static, app+site combined) and a sites reference in the bunny-cli skill; README/AGENTS updated.@bunny.net/config: renamed from@bunny.net/app-config@bunny.net/config; it's now the shared schema package for the wholebunny.jsoncfile, not just apps. Marked private (removed from the npm release workflow).BunnyConfigSchemamakes both blocks optional, so app-only, sites-only, and combined files all validate against the generated JSON Schema.sitesblock (SiteConfigSchema):name(links the directory to a site),dir(deploy root),build(command run bysites deploy --build).BunnyAppConfigSchemais the same shape narrowed to requireapp; the apps commands and API conversion functions use it, so their behavior is unchanged.generated/schema.json; all app templates and examples updated to the new$schemapath.