You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(storage): add storage zone and file commands with S3 credentials (#98)
* feat(storage): add storage zone and file commands with S3 credentials
* updates to creating storage zones with domains
* update table header color
* apply fixes
* apply code suggestions
* add note about flags for secrets
* link and fix file listing
* apply greptile feedback
* replica add warning
* improve force cmds
* secret fix
│ │ │ ├── dns.ts # dnsPointsAt()/anyResolverPointsAt(): DNS checks (CNAME or flattened A records) via system + public (1.1.1.1/8.8.8.8) resolvers, injectable for tests
189
189
│ │ │ ├── dns.test.ts # Tests for DNS matching + multi-resolver checks with fake resolvers
190
-
│ │ │ ├── flow.ts # offerDnsWaitAndSsl(): poll DNS + opportunistically attempt SSL issuance (~30s) since bunny's resolvers decide validation; printSslHint(). dnsAlreadyLive skips the poll (Bunny DNS record already live). offerBunnyDnsThenSsl() takes an optional onBunnyDnsZone(zone) callback fired when the hostname is on Bunny DNS (lets the command layer link the directory)
190
+
│ │ │ ├── flow.ts # offerDnsWaitAndSsl(): poll DNS + opportunistically attempt SSL issuance (~30s) since bunny's resolvers decide validation; printSslHint(). dnsAlreadyLive skips the poll (Bunny DNS record already live). offerBunnyDnsThenSsl() takes an optional onBunnyDnsZone(zone) callback fired when the hostname is on Bunny DNS (lets the command layer link the directory). setupHostname(): resource-agnostic add-hostname -> DNS -> SSL orchestration (caller supplies sslHint/retryHint); used by scripts setupCustomDomain and storage zone add
191
191
│ │ │ ├── bunny-dns.ts # findBunnyDnsZone()/offerBunnyDnsRecord(): detect a hostname inside an account Bunny DNS zone, then add/repoint a PullZone record (always confirmed) so SSL can issue immediately
192
192
│ │ │ ├── bunny-dns.test.ts # Tests for longest-suffix zone matching + record-name derivation with a fake core client
193
193
│ │ │ └── commands.ts # createHostnamesCommands(): add/ssl/list/remove factory parameterized by a pull-zone resolver
@@ -346,6 +346,33 @@ bunny-cli/
346
346
│ │ │ ├── attach.ts # Bridge: add a SCRIPT record on a zone pointing at the script. `attach [domain] [name] --script <id>`; always confirms (apex gets a louder root-domain warning, lists existing records at the name), --force skips and is required to write non-interactively
347
347
│ │ │ ├── link.ts # Link this directory to an existing DNS script → .bunny/dns-script.json (positional id, else pick interactively; preserves entry)
348
348
│ │ │ └── list.ts # List DNS scripts (alias: ls)
349
+
│ │ ├── storage/ # Experimental (hidden from help and landing page)
350
+
│ │ │ ├── index.ts # defineNamespace("storage", ...): registers zone + file groups + link + regions + docs (+ hidden bucket aliases)
351
+
│ │ │ ├── api.ts # CoreClient type, fetchStorageZones/fetchStorageZone, resolveStorageZone (name-or-ID to zone, re-fetched by ID), toSafeStorageZone (strips Password/ReadOnlyPassword; used by every command that emits a raw zone as JSON: show/list/add)
352
+
│ │ │ ├── constants.ts # STORAGE_REGIONS (from SDK enum; /storagezone/regions API endpoint is not reliable) + replicationChoices/normalizeReplicationRegions (replication uses the same regions minus the primary; the SDK file ZoneSchema is the physical footprint, NOT the create input) + STORAGE_MANIFEST/StorageZoneManifest (.bunny/storage.json, written by storage link)
353
+
│ │ │ ├── interactive.ts # resolveStorageZoneInteractive: explicit name/ID arg → linked manifest (.bunny/storage.json, fetched by ID even when non-interactive) → zone picker
354
+
│ │ │ ├── link.ts # Link the current directory to a storage zone (.bunny/storage.json); bunny storage link [zone]
355
+
│ │ │ ├── files-api.ts # Adapter over @bunny.net/storage-sdk: connectStorageZone (zone → SDK connection, Region→StorageRegion enum + password), listFiles/uploadFile/downloadFile/deleteFile (deleteFile translates the SDK's boolean return into a UserError)
356
+
│ │ │ ├── files-api.test.ts # Tests for region mapping + delete error translation (NOT the SDK's URL building)
│ │ │ │ ├── list.ts # List all storage zones (alias: ls)
364
+
│ │ │ │ ├── add.ts # Create a storage zone (prompts for name + region when omitted; offers/--pull-zone creates a pull zone via core/hostnames createPullZone, then offers/--domain a custom domain via setupHostname). Under --output json it stays non-interactive: --domain is attached via addHostname (no DNS/SSL prompts) and reported in the CustomDomain field (with cnameTarget or error)
365
+
│ │ │ │ ├── show.ts # Show zone details (region, replication, hostname, usage; adds S3 endpoint rows when S3-enabled)
366
+
│ │ │ │ ├── credentials.ts # S3 credentials / tool config for the zone (alias: creds; --format, --read-only, --show-secret); table masks the secret unless --show-secret, JSON/--format always emit it in full
367
+
│ │ │ │ ├── update.ts # Update zone settings (custom 404, rewrite 404->200, replication); replication is additive (replicas can't be removed, so existing ones are kept and the prompt only offers new regions, confirming before adding); interactive pre-filled editor when no flags, non-interactive under --output json
368
+
│ │ │ │ ├── remove.ts # Delete a storage zone and its files (alias: rm)
369
+
│ │ │ │ └── hostnames/index.ts # Mounts core/hostnames createHostnamesCommands as "storage zone domains" (alias hostnames); resolver maps a storage zone (name/ID positional, --pull-zone) to its linked pull zone
370
+
│ │ │ └── file/ # `bunny storage files` (canonical: files; aliases: file); zone is the --zone/-z flag (defaults to linked zone), the positional is the file/path
-**Config logic lives in `packages/cli/src/config/`** — schema, file resolution, and profile management.
403
430
-**Error classes are split.**`UserError` and `ApiError` live in `@bunny.net/openapi-client` (the SDK needs them). `ConfigError` lives in the CLI and extends `UserError`. The CLI's `errors.ts` re-exports `UserError` and `ApiError` from `@bunny.net/openapi-client`.
404
431
-**Import API clients from `@bunny.net/openapi-client`**, not relative paths. Import generated types from `@bunny.net/openapi-client/generated/<spec>.d.ts`.
432
+
-**Mask secrets in human output; reveal only behind an explicit flag.** Any sensitive value (API keys, passwords, S3 secret keys, auth tokens) must be masked in the default table/text output and shown in full only when the user opts in with a flag (e.g. `--show-secret`). Use `maskSecret()` from `core/format.ts` for the masked form (it keeps the last 4 characters for identification). Machine-readable output is the exception because it exists to be consumed by tools: `--output json` and tool-config `--format` emit full values. Never print a secret the user did not explicitly ask to see. Reference: `storage zones credentials` masks the S3 secret access key by default and reveals it with `--show-secret`, while never leaking it from inspect/list commands (see `toSafeStorageZone`).
405
433
-**Pull-zone settings are exposed via "Hybrid D" across surfaces.** Scripts and apps are backed by a pull zone, which has a large settings surface (hostnames, caching, edge rules, origin, security, purge, CORS, optimizer, logging, …). To keep each owner's help legible:
406
434
-**Flatten only first-class groups** directly into the owner — picked by user mental model, kept to one or two. `scripts domains` is the flattened group (a custom domain is "my site's address," not a CDN setting).
407
435
-**Group the long tail** under a `pullzone` sub-namespace within the owner (e.g. `scripts pullzone <setting>`), so the owner's top-level help gains one line, not ten. Curate per owner — don't expose settings that don't apply (a script _is_ its pull zone's origin, so no origin-URL command under `scripts`).
│ └── disable [domain] [--force] Disable DNS query logging
958
+
├── storage (experimental, hidden from help and landing page)
959
+
│ │ Two resource groups: `zones` (the zone, via core API + account key) and `files` (zone contents, via @bunny.net/storage-sdk + the zone password/region host, resolved automatically). The zone is a name or numeric ID; `zones` commands take it as the `[zone]` positional, `files` commands as the `--zone`/`-z` flag (the positional is the file/path). When the zone is omitted it resolves from a linked zone (`bunny storage link`) then an interactive picker.
960
+
│ ├── zones (canonical; aliases: zone; hidden: bucket, buckets)
961
+
│ │ ├── list List all storage zones (alias: ls)
962
+
│ │ ├── add [name] [--region] [--replication] [--pull-zone] [--pull-zone-name] [--domain] [--force/-f] Create a storage zone (prompts for name + region when omitted; offers/--pull-zone creates a pull zone to serve it on the web, then offers/--domain a custom domain via setupHostname; replicas are permanent so adding any is confirmed; --force/--output json skip all prompts and use flag values only)
963
+
│ │ ├── show [zone] Show zone details (region, replication, hostname, usage)
964
+
│ │ ├── update [zone] [--custom-404-path] [--rewrite-404-to-200] [--replication] [--force/-f] Update zone settings (edits interactively pre-filled when no flags; replication is additive and adding a replica is confirmed unless --force; non-interactive under --output json)
965
+
│ │ ├── remove [zone] [--force] Delete a storage zone and its files (alias: rm)
│ │ │ S3 credentials for the zone (name = access key, password = secret); --format emits tool config, else table/--output json; table masks the secret unless --show-secret
968
+
│ │ └── domains (canonical; alias: hostnames) custom domains on the zone's pull zone; mounts core/hostnames createHostnamesCommands; resolver maps the storage zone to its linked pull zone
969
+
│ ├── files (canonical; aliases: file) [--zone|-z] defaults to the linked zone on every file command
970
+
│ │ ├── list [path] [--zone] (alias: ls) List files in a directory (trailing slash on path)
971
+
│ │ ├── upload <file> [--zone] [--to] [--checksum] [--content-type] Upload a local file
972
+
│ │ ├── download <path> [--zone] [--out] Download a file
973
+
│ │ └── remove <path> [--zone] [--force] (alias: rm) Delete a file or directory (trailing slash = recursive)
974
+
│ ├── link [zone] Link the current directory to a storage zone (.bunny/storage.json); interactive picker when omitted
975
+
│ ├── regions List available storage regions (replication uses the same set minus the primary)
0 commit comments