Skip to content

bunny sites#125

Merged
jamie-at-bunny merged 26 commits into
mainfrom
claude/bunny-sites-plan-15thnb
Jul 23, 2026
Merged

bunny sites#125
jamie-at-bunny merged 26 commits into
mainfrom
claude/bunny-sites-plan-15thnb

Conversation

@jamie-at-bunny

@jamie-at-bunny jamie-at-bunny commented Jul 13, 2026

Copy link
Copy Markdown
Member

bunny sites

New sites namespace: create, list, show, open, deploy, deployments list/publish/prune, domains, ssl, ci init, link, unlink, upgrade-router, delete.

Provisioning

  • sites create provisions a storage zone + pull zone + middleware router per site. Underlying zones are named sites-<name>-<random id> so globally-taken zone names can't block a create; commands always take the clean site name.
  • Interactive create prompts for the site name (directory-name suggestion) and optionally a custom domain: Bunny DNS record, nameserver guidance, DNS propagation wait, then SSL issuance. On GitHub repos it also offers to scaffold CI.

Deploys

  • sites deploy uploads immutable deploys with git-sha or content-hash IDs (no-op when nothing changed) to per-deploy preview URLs; --production/--prod publishes by flipping the router's CURRENT_DEPLOY env var and purging the cache.
  • --build runs the configured build command and infers the framework's output dir when no dir is given.
  • Concurrent deploys are safe: remote state records are merged (with current/previous pointer reconciliation) instead of overwritten, and pruning filters against remote state.
  • Site state lives at _bunny/site.json inside the storage zone, 403-blocked by the router.

Domains & CI

  • sites domains attaches custom domains plus a *.preview.<domain> wildcard for per-deploy preview URLs.
  • sites ci init scaffolds 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/prune cover rollback and old-deploy cleanup.

Supporting changes

  • core/bunny-config.ts: shared bunny.jsonc locator/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.
  • The shared hostnames factory gains optional onAdded/onRemoved hooks (used by sites for the preview wildcard).
  • New 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

  • Package renamed to @bunny.net/config; it's now the shared schema package for the whole bunny.jsonc file, not just apps. Marked private (removed from the npm release workflow).
  • Root BunnyConfigSchema makes both blocks optional, so app-only, sites-only, and combined files all validate against the generated JSON Schema.
  • New top-level sites block (SiteConfigSchema): name (links the directory to a site), dir (deploy root), build (command run by sites deploy --build).
  • BunnyAppConfigSchema is the same shape narrowed to require app; the apps commands and API conversion functions use it, so their behavior is unchanged.
  • Regenerated generated/schema.json; all app templates and examples updated to the new $schema path.

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
@bogdan-at-bunny

Copy link
Copy Markdown

@codex review

@changeset-bot

changeset-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3230dc7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@bunny.net/config Patch
@bunny.net/cli Minor
@bunny.net/cli-linux-x64 Minor
@bunny.net/cli-linux-arm64 Minor
@bunny.net/cli-darwin-x64 Minor
@bunny.net/cli-darwin-arm64 Minor
@bunny.net/cli-windows-x64 Minor

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread docs/plans/sites.md Outdated
Comment thread docs/plans/sites.md Outdated
Comment thread docs/plans/sites.md Outdated
Comment thread docs/plans/sites.md Outdated
Comment thread docs/plans/sites.md Outdated
Comment thread docs/plans/sites.md Outdated
Comment thread docs/plans/sites.md Outdated
Comment thread docs/plans/sites.md Outdated
Comment thread docs/plans/sites.md Outdated
Comment thread docs/plans/sites.md Outdated
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-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces the complete bunny sites namespace for deploying and managing static sites on Bunny CDN. It provisions a storage zone + pull zone + middleware router per site, implements immutable deploys with git-SHA or content-hash IDs, and adds preview URLs, custom domains, SSL, and CI scaffolding.

  • Sites provisioning (create, link, delete, upgrade-router): zones are named sites-<name>-<random-suffix> to avoid global namespace collisions; a middleware Edge Script routes hostname- and path-based previews; site state is stored in _bunny/site.json inside the storage zone, 403-blocked by the router.
  • Deploy pipeline (deploy, deployments list/publish/prune): uploads files to deploys/{id}/, flips CURRENT_DEPLOY for production promotes, and merges remote state on concurrent-write conflicts using ETags, promotedTo promotion semantics, and a removedIds exclusion list that prevents prune from resurrecting pruned records.
  • Supporting infrastructure: a shared bunny.jsonc locator/parser, JSONC-preserving surgical edit helpers, framework detection + CI workflow scaffolding, and @bunny.net/config renamed from @bunny.net/app-config with a root BunnyConfigSchema making both app and sites blocks optional.

Confidence Score: 5/5

Safe 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

Filename Overview
packages/cli/src/commands/sites/api.ts Core site API: createSite provisioning, writeRemoteState with ETag conflict reconciliation (promotedTo + removedIds options), promoteDeploy with edge propagation probing. Concurrent-write and ownership-check issues from the prior review are all addressed.
packages/cli/src/commands/sites/deploy.ts Deploy command: hashes files, resolves deploy identity (git sha / content hash), skips upload on unchanged content, uploads with concurrency, records deploy state, and optionally promotes to production.
packages/cli/src/commands/sites/router/source.ts Edge Script router: strips x-bunny-preview from incoming client requests before routing, expands trailing slashes globally, routes hostname-preview and path-preview requests, and rewrites HTML assets only for subpath previews.
packages/cli/src/commands/sites/deployments/prune.ts Prune command: validates deploy IDs with isValidDeployId before path interpolation and passes removedIds to writeRemoteState to prevent ETag reconciliation from resurrecting pruned records.
packages/cli/src/commands/sites/ci/scaffold.ts CI scaffolding: API key correctly passed via stdin (not --body argv) to the gh CLI, avoiding process-listing exposure.
packages/cli/src/commands/sites/uploader.ts File hashing and upload: hashFiles now uses mapWithConcurrency. Upload uses per-file SHA-256 checksums with retry backoff.
packages/cli/src/commands/sites/constants.ts Site constants: parseRemoteState validates structural shape and site name format but does not validate individual deploy record ID formats; prune compensates with isValidDeployId at use-site.
packages/cli/src/core/env.ts Dotenv parser: correctly strips inline comments, handles double-quoted values with backslash escape sequences, and supports single-quoted verbatim values.
packages/cli/src/commands/sites/deploy-id.ts Deploy identity: contentHashId now uses 12 hex chars (48 bits); contentHash stored separately drives the no-op check independently of the display id.
packages/cli/src/commands/sites/deployments/publish.ts Publish/rollback command: resolves target deploy ID, validates it exists in state.deploys, calls promoteDeploy, and writes updated state with promotedTo semantics.

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
Loading
%%{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
Loading

Reviews (19): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread packages/cli/src/commands/sites/api.ts
Comment thread packages/cli/src/commands/sites/api.ts Outdated
Comment thread packages/cli/src/commands/sites/uploader.ts
Comment thread packages/cli/src/commands/sites/deploy-id.ts
Comment thread packages/cli/src/commands/sites/build.ts Outdated
@jamie-at-bunny jamie-at-bunny changed the title docs: add implementation plan for bunny sites feature bunny sites Jul 13, 2026
Comment thread packages/cli/src/commands/sites/deployments/prune.ts Outdated
Comment thread .changeset/app-config-sites-block.md Outdated
Comment thread .changeset/sites-namespace.md
Comment thread packages/cli/src/commands/sites/ci/scaffold.ts Outdated
Comment thread packages/cli/src/commands/sites/router/source.ts
Comment thread packages/cli/src/commands/sites/router/source.ts Outdated
Comment thread packages/cli/src/commands/sites/api.ts
@jamie-at-bunny

Copy link
Copy Markdown
Member Author

@greptile-apps review

@jamie-at-bunny

Copy link
Copy Markdown
Member Author

@greptile-apps review

Comment thread packages/cli/src/commands/sites/deployments/prune.ts Outdated
@jamie-at-bunny

Copy link
Copy Markdown
Member Author

@greptile-apps review

@jamie-at-bunny jamie-at-bunny self-assigned this Jul 23, 2026
@jamie-at-bunny
jamie-at-bunny merged commit 9696434 into main Jul 23, 2026
3 checks passed
@jamie-at-bunny
jamie-at-bunny deleted the claude/bunny-sites-plan-15thnb branch July 23, 2026 13:35
@github-actions github-actions Bot mentioned this pull request Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants