Skip to content

bunny sites#125

Open
jamie-at-bunny wants to merge 19 commits into
mainfrom
claude/bunny-sites-plan-15thnb
Open

bunny sites#125
jamie-at-bunny wants to merge 19 commits into
mainfrom
claude/bunny-sites-plan-15thnb

Conversation

@jamie-at-bunny

Copy link
Copy Markdown
Member

Summary

Add a comprehensive implementation plan for the bunny sites feature — static-site hosting with atomic deploys, instant rollback, and per-deploy preview URLs. This document outlines the phased approach, platform assumptions to validate, command surface, and cross-cutting conventions.

Changes

  • New file: docs/plans/sites.md — a detailed 5-phase implementation plan covering:
    • Phase 0 (Spike): Validate six platform assumptions (attach mechanism, per-request rewrite, env-var propagation, wildcard hostnames + certs, cache isolation, upload throughput)
    • Phase 1 (Scaffolding): Site lifecycle commands (create, list, show, link, unlink, delete) with local manifests and remote state
    • Phase 2 (Deploy loop): Deploy and deployments commands with deploy ID computation, file upload, and state management
    • Phase 3 (Domains): Domain management via createHostnamesCommands() factory with wildcard preview subdomains
    • Phase 4 (Env vars + builds): Environment variables, build integration, and bunny.jsonc support
    • Phase 5 (Polish): Prune, router upgrades, promotion to stable, and documentation

Notable details

  • Decisions locked: One storage zone + pull zone + middleware router per site; preview scheme dpl-{id}.preview.{domain}; deploy IDs via git short-sha or content hash; site identity keyed by storage zone ID
  • Existing machinery inventory: Documents what the repo already provides (command framework, script creation, env vars, storage files API, pull zone creation, hostname orchestration, local manifests, output formatting, cache purge, clients)
  • Router script: ~30-line middleware that serves /deploys/{CURRENT_DEPLOY}{path} on apex, per-deploy prefixes on preview hosts, and a friendly "no deploys yet" page
  • Conventions checklist: Covers defineCommand()/defineNamespace(), flag equivalents for prompts, --output json handling, UserError usage, client wiring, testing patterns, and documentation updates
  • Sequencing: Phase 0 is a 1-day spike; Phases 1–2 are ~1 week and ship a usable feature; Phase 3 is 1–2 days; Phases 4–5 are independent and can trail

This plan is the foundation for all subsequent sites feature PRs and ensures alignment on architecture, API usage, and CLI conventions before implementation begins.

https://claude.ai/code/session_01J6CzqnCzQrV6exqx4PrF13

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: a067ab7

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/app-config Patch
@bunny.net/cli Patch
@bunny.net/cli-linux-x64 Patch
@bunny.net/cli-linux-arm64 Patch
@bunny.net/cli-darwin-x64 Patch
@bunny.net/cli-darwin-arm64 Patch
@bunny.net/cli-windows-x64 Patch

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 delivers the full bunny sites feature: static-site hosting with atomic deploys, instant rollback, and per-deploy preview URLs. It introduces ~6 000 lines of new code spanning site lifecycle commands (create, deploy, list, show, delete, link, unlink), a deployments sub-namespace (list, publish, prune), domain management, a CI scaffold for GitHub Actions, and an edge router script.

  • Core data path: A storage zone holds files under deploys/{id}/; a pull zone with a BunnySDK middleware router routes production traffic via a CURRENT_DEPLOY env var and preview traffic via dpl-{id}.preview.{domain} hostname or /deploys/{id}/ path prefix.
  • Optimistic concurrency: writeRemoteState re-reads on ETag mismatch, merges deploy records, and applies a "last promote wins" policy — addressing the race conditions flagged in the previous review cycle.
  • Previous issues addressed: header-poisoning via x-bunny-preview, directory-request 404s on path previews, sequential hashing, 8-char deploy-ID collision risk, dotenv parsing, prune path-traversal, and CI scaffold API-key leakage have all been fixed in this revision.

Confidence Score: 5/5

The change is safe to merge. All core deploy paths are implemented with proper error handling and the optimistic-lock merge logic handles concurrent CI deploys correctly.

The PR is a large, well-structured addition with no identified defects in the changed code paths. All issues from the previous review cycle are addressed. The one remaining note on deploy record ID validation in parseRemoteState is a defense-in-depth suggestion that does not affect any reachable production path today.

No files require special attention. The api.ts merge logic and router/source.ts routing rules are the most complex paths and both read cleanly.

Important Files Changed

Filename Overview
packages/cli/src/commands/sites/api.ts Core site API: state reads/writes with ETag-based optimistic locking, concurrent-write merge, site provisioning and teardown. Concurrency merge logic was substantially improved from the previous review.
packages/cli/src/commands/sites/router/source.ts BunnySDK edge router: routes production and preview traffic, strips x-bunny-preview from external requests, expands directory paths before routing. All previously flagged issues are addressed.
packages/cli/src/commands/sites/deploy.ts Deploy command: hashing, skip-on-unchanged, upload, promote, state write. The reused-deploy ID from remote state passes to promoteDeploy without isValidDeployId validation.
packages/cli/src/commands/sites/deployments/publish.ts Publish/rollback command: resolves deploy ID, confirms, promotes, updates state. Does not validate deploy ID format before passing to promoteDeploy.
packages/cli/src/commands/sites/deployments/prune.ts Prune command: validates deploy IDs with isValidDeployId before path interpolation (fix from previous review cycle).
packages/cli/src/commands/sites/ci/scaffold.ts CI scaffold: passes BUNNY_API_KEY via stdin, never as a process argument. API key exposure concern from previous review is fixed.
packages/cli/src/commands/sites/uploader.ts Parallel file upload with bounded concurrency, per-file SHA-256 checksums, retry with backoff. hashFiles now uses mapWithConcurrency.
packages/cli/src/commands/sites/constants.ts Remote state schema and validation helpers. parseRemoteState validates top-level shape and site name but not individual deploy record IDs.
packages/cli/src/core/env.ts New dotenv parser handling inline comments, double-quoted values with backslash escapes, single-quoted verbatim values, and export prefix.
packages/cli/src/commands/sites/deploy-id.ts Deploy identity resolution: git short-SHA on clean tree, 12-hex-char content hash (48-bit) otherwise. Hash length increased from the previously-flagged 8 chars.
packages/cli/src/commands/sites/ci/workflow.ts GitHub Actions workflow renderer: site name and directory are JSON.stringify-quoted to prevent YAML injection.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI as bunny CLI
    participant StorageAPI as Storage API
    participant ComputeAPI as Compute API
    participant PullZoneAPI as Pull Zone API
    participant CDNEdge as CDN Edge

    note over CLI: bunny sites deploy ./dist --production

    CLI->>StorageAPI: GET _bunny/site.json (read state + ETag)
    StorageAPI-->>CLI: RemoteSiteState + ETag

    CLI->>CLI: hashFiles + resolveDeployIdentity

    alt files unchanged
        CLI-->>CLI: skip upload, reuse existing deployId
    else new content
        CLI->>StorageAPI: "PUT deploys/id/** (concurrent, SHA-256)"
        StorageAPI-->>CLI: ok
        CLI->>StorageAPI: PUT _bunny/site.json (ETag + merge)
        StorageAPI-->>CLI: new ETag
    end

    CLI->>ComputeAPI: "PUT script variables CURRENT_DEPLOY=id"
    ComputeAPI-->>CLI: ok
    CLI->>PullZoneAPI: POST purgeCache
    PullZoneAPI-->>CLI: ok
    CLI->>CDNEdge: probe GET (await non-404)
    CDNEdge-->>CLI: 200
    CLI->>PullZoneAPI: POST purgeCache (second)
    PullZoneAPI-->>CLI: ok
    CLI->>StorageAPI: PUT _bunny/site.json (mark current)
    StorageAPI-->>CLI: ok
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 StorageAPI as Storage API
    participant ComputeAPI as Compute API
    participant PullZoneAPI as Pull Zone API
    participant CDNEdge as CDN Edge

    note over CLI: bunny sites deploy ./dist --production

    CLI->>StorageAPI: GET _bunny/site.json (read state + ETag)
    StorageAPI-->>CLI: RemoteSiteState + ETag

    CLI->>CLI: hashFiles + resolveDeployIdentity

    alt files unchanged
        CLI-->>CLI: skip upload, reuse existing deployId
    else new content
        CLI->>StorageAPI: "PUT deploys/id/** (concurrent, SHA-256)"
        StorageAPI-->>CLI: ok
        CLI->>StorageAPI: PUT _bunny/site.json (ETag + merge)
        StorageAPI-->>CLI: new ETag
    end

    CLI->>ComputeAPI: "PUT script variables CURRENT_DEPLOY=id"
    ComputeAPI-->>CLI: ok
    CLI->>PullZoneAPI: POST purgeCache
    PullZoneAPI-->>CLI: ok
    CLI->>CDNEdge: probe GET (await non-404)
    CDNEdge-->>CLI: 200
    CLI->>PullZoneAPI: POST purgeCache (second)
    PullZoneAPI-->>CLI: ok
    CLI->>StorageAPI: PUT _bunny/site.json (mark current)
    StorageAPI-->>CLI: ok
Loading

Reviews (15): Last reviewed commit: "fix(sites): infer the framework output d..." | Re-trigger Greptile

Comment thread packages/cli/src/commands/sites/api.ts
Comment thread packages/cli/src/commands/sites/api.ts
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 Outdated
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
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.

3 participants