Skip to content

feat(storage): add storage zone and file commands with S3 credentials#98

Merged
jamie-at-bunny merged 13 commits into
mainfrom
storage-commands
Jul 1, 2026
Merged

feat(storage): add storage zone and file commands with S3 credentials#98
jamie-at-bunny merged 13 commits into
mainfrom
storage-commands

Conversation

@jamie-at-bunny

Copy link
Copy Markdown
Member

No description provided.

@changeset-bot

changeset-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 26cbbe5

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

This PR includes changesets to release 6 packages
Name Type
@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

Comment thread packages/cli/README.md Outdated
Comment thread packages/cli/README.md Outdated
Comment thread packages/cli/README.md
Comment thread packages/cli/README.md

# Files: list, upload, download, delete (paths are relative to the zone root)
bunny storage file list my-zone
bunny storage file list my-zone images/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

when dir doesn't exist it says Directory is empty. maybe a more accurate error message would be nicer. the current one implies the dir exists but empty.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't think we know either way here, unless something changed in the API recently. I discovered this while working on bunny-upload/file-manager. We could instead return No files found at "images/". The path may be empty or not exist though, wdyt?

Comment thread packages/cli/README.md
bunny storage file list my-zone images/
bunny storage file upload my-zone ./photo.png --to images/
bunny storage file upload my-zone ./photo.png --checksum --content-type image/png
bunny storage file download my-zone images/photo.png --out ./local.png

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this doesn't work for me. it gets stuck on downloading and blocks the process.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@amir-at-bunny can you check with the latest this works as you expect.

@jamie-at-bunny jamie-at-bunny marked this pull request as ready for review June 22, 2026 09:23
@bogdan-at-bunny

Copy link
Copy Markdown

@codex review

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@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: 2eb4f25d96

ℹ️ 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".

): string[] {
const primary = primaryCode?.toUpperCase();
const normalized = regions
.map((region) => region.trim().toUpperCase())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Split comma-separated replication regions

The new examples use --replication LA,SG, but the parser passes that as one array element, and this normalization only trims/uppercases each element before validating it. As a result LA,SG is checked as a single region code and rejected as unknown, so the documented creation/update form fails unless users repeat the flag; split each entry on commas before validation or change the documented syntax.

Useful? React with 👍 / 👎.

};
}

if (output === "json") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add the requested domain before returning JSON

When bunny storage zones add ... --domain <host> --output json is used, --domain already made the pull zone get created, but this JSON branch returns before the custom-domain block below runs. The command therefore reports success while silently skipping the explicit domain the user asked to attach; perform the non-interactive hostname add before this return and include its result/error in the JSON payload.

Useful? React with 👍 / 👎.

Comment thread packages/cli/src/commands/storage/file/download.ts Outdated
Comment thread packages/cli/src/commands/storage/zone/hostnames/index.ts
Comment thread packages/cli/src/commands/storage/zone/add.ts Outdated
Comment thread packages/cli/src/commands/storage/zone/show.ts Outdated
Comment thread packages/cli/src/commands/storage/s3.ts Outdated
Comment thread packages/cli/src/commands/storage/zone/add.ts Outdated

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@jamie-at-bunny jamie-at-bunny self-assigned this Jun 22, 2026

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@jamie-at-bunny

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@jamie-at-bunny

Copy link
Copy Markdown
Member Author

@greptile-apps re-review

@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds the experimental bunny storage command group: zone lifecycle management (add/list/show/update/remove), S3 credential display with tool-config formatters, and file operations (list/upload/download/delete) backed by the @bunny.net/storage-sdk. It also refactors the shared hostname-setup flow into a reusable setupHostname() helper used by both scripts create and the new storage zone add.

  • Zone commands resolve a zone by name or ID, expose replication with a permanent-add guard, and use toSafeStorageZone to strip passwords from every inspect/list/create JSON output.
  • Credentials command was updated mid-PR to mask the S3 secret by default in JSON output as well as table output, but the README that was written in the same PR still documents the old behaviour ("–output json … always emits [the secret] in full"), creating a contradictory API contract for automation consumers.
  • maskSecret fix in core/format.ts now fully masks secrets ≤ 8 characters instead of prepending bullets to the full value.

Confidence Score: 4/5

Safe to merge with one documentation fix: the new README section explicitly documents the opposite secret-handling behaviour from what credentials.ts now implements.

The credentials command was fixed (correctly) to mask the secret in JSON output, but the README written in the same PR tells users and automation scripts that --output json always emits the full secret. Any script following the documentation — e.g. piping JSON to jq — will silently receive a bullet-masked string rather than the real secret and need an undocumented --show-secret flag to work.

packages/cli/README.md (line ~330) needs to be updated to reflect that --output json respects --show-secret just like the table output does; AGENTS.md line ~68 has the same stale claim.

Important Files Changed

Filename Overview
packages/cli/README.md New storage section documents --output json as always emitting the full secret, which contradicts the updated credentials.ts behaviour (JSON now masks unless --show-secret). Stale claim will break automation that follows the docs.
packages/cli/src/commands/storage/zone/credentials.ts S3 credentials command — JSON output now correctly masks the secret by default (addresses previous review finding). --format path still always emits the full secret (documented intent).
packages/cli/src/commands/storage/zone/add.ts Zone creation command with pull-zone and custom-domain flows. Correctly guards pull-zone and custom-domain steps behind zoneId check; covered by previous threads regarding unchecked API error in client.POST.
packages/cli/src/commands/storage/zone/remove.ts Destructive delete with --force guard; DELETE response goes unchecked (covered in previous threads).
packages/cli/src/commands/storage/zone/update.ts Update settings with additive replication merge logic; replication-merge is correct (merges existing + new). POST response unchecked (previous threads).
packages/cli/src/commands/storage/s3.ts S3 credential derivation and tool-config rendering. Shell quoting for env format is correct (shSingleQuote). --format path still emits the full secret as intended.
packages/cli/src/core/format.ts maskSecret fixed: fully masks secrets <= 8 chars to prevent tail exposure. Tests cover all edge cases correctly.
packages/cli/src/commands/storage/files-api.ts SDK adapter for file operations. Region validation and deleteFile error translation are correct. connectStorageZone correctly lowercases region code to match SDK enum.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[bunny storage zones credentials] --> B{--format?}
    B -- yes --> C[renderS3ToolConfig\nalways full secret]
    B -- no --> D{--output json?}
    D -- yes --> E{--show-secret?}
    E -- yes --> F[JSON with full secret]
    E -- no --> G[JSON with masked secret]
    D -- no --> H{--show-secret?}
    H -- yes --> I[Table with full secret]
    H -- no --> J[Table with masked secret]

    style C fill:#90EE90
    style F fill:#90EE90
    style G fill:#FFD700,stroke:#FF8C00
    style I fill:#90EE90
    style J fill:#FFD700,stroke:#FF8C00
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"}}}%%
flowchart TD
    A[bunny storage zones credentials] --> B{--format?}
    B -- yes --> C[renderS3ToolConfig\nalways full secret]
    B -- no --> D{--output json?}
    D -- yes --> E{--show-secret?}
    E -- yes --> F[JSON with full secret]
    E -- no --> G[JSON with masked secret]
    D -- no --> H{--show-secret?}
    H -- yes --> I[Table with full secret]
    H -- no --> J[Table with masked secret]

    style C fill:#90EE90
    style F fill:#90EE90
    style G fill:#FFD700,stroke:#FF8C00
    style I fill:#90EE90
    style J fill:#FFD700,stroke:#FF8C00
Loading

Reviews (8): Last reviewed commit: "secret fix" | Re-trigger Greptile

Comment thread packages/cli/src/commands/storage/zone/add.ts
Comment thread packages/cli/src/core/format.ts
@jamie-at-bunny

Copy link
Copy Markdown
Member Author

We should also prompt before adding a replica that it cannot later be removed.

# Conflicts:
#	bun.lock
#	packages/cli/src/cli.ts
Comment thread packages/cli/src/commands/storage/zone/credentials.ts
@jamie-at-bunny jamie-at-bunny merged commit 4aa8fbe into main Jul 1, 2026
2 checks passed
@jamie-at-bunny jamie-at-bunny deleted the storage-commands branch July 1, 2026 12:45
@github-actions github-actions Bot mentioned this pull request Jul 3, 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.

3 participants