diff --git a/.changeset/happy-streets-leave.md b/.changeset/happy-streets-leave.md new file mode 100644 index 000000000000..2973d331266a --- /dev/null +++ b/.changeset/happy-streets-leave.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Add --json flag to r2 bucket info command for machine-readable output. diff --git a/packages/wrangler/e2e/unenv-preset/preset.test.ts b/packages/wrangler/e2e/unenv-preset/preset.test.ts index 420bf2c8d7df..622c1e045509 100644 --- a/packages/wrangler/e2e/unenv-preset/preset.test.ts +++ b/packages/wrangler/e2e/unenv-preset/preset.test.ts @@ -1,6 +1,7 @@ import { join } from "node:path"; import { fetch } from "undici"; import { afterAll, beforeAll, describe, expect, test, vi } from "vitest"; +import { CLOUDFLARE_ACCOUNT_ID } from "../helpers/account-id"; import { WranglerE2ETestHelper } from "../helpers/e2e-wrangler-test"; import { generateResourceName } from "../helpers/generate-resource-name"; import { retry } from "../helpers/retry"; @@ -85,6 +86,12 @@ describe.each(testConfigs)( // The "local" and "remote" runtimes do not necessarily use the exact same version // of workerd and we want to make sure the preset works for both. describe.for(["local", "remote"])("%s tests", (localOrRemote) => { + // Skip the remote tests if the user is not logged in (e.g. a PR from a forked repo) + if (localOrRemote === "remote" && !CLOUDFLARE_ACCOUNT_ID) { + test.skip("Remote tests are not supported"); + return; + } + let url: string; let wrangler: WranglerLongLivedCommand; beforeAll(async () => { diff --git a/packages/wrangler/src/__tests__/r2/bucket.test.ts b/packages/wrangler/src/__tests__/r2/bucket.test.ts new file mode 100644 index 000000000000..eebb3546fa07 --- /dev/null +++ b/packages/wrangler/src/__tests__/r2/bucket.test.ts @@ -0,0 +1,40 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { getR2Bucket, getR2BucketMetrics } from "../../r2/helpers"; +import { requireAuth } from "../../user"; +import { mockConsoleMethods } from "../helpers/mock-console"; +import { runWrangler } from "../helpers/run-wrangler"; + +vi.unmock("../../wrangler-banner"); + +vi.mock("../../r2/helpers"); +vi.mock("../../user"); + +const logs = mockConsoleMethods(); + +const mockRequireAuth = vi.mocked(requireAuth); +const mockGetR2Bucket = vi.mocked(getR2Bucket); +const mockGetR2BucketMetrics = vi.mocked(getR2BucketMetrics); + +describe("r2 bucket info", () => { + beforeEach(() => { + vi.resetAllMocks(); + + mockRequireAuth.mockResolvedValue("test-account-id"); + mockGetR2Bucket.mockResolvedValue({ + name: "my-bucket-name", + creation_date: "2025-06-07T15:55:22.222Z", + location: "APAC", + storage_class: "Standard", + }); + mockGetR2BucketMetrics.mockResolvedValue({ + objectCount: 0, + totalSize: "0 B", + }); + }); + + it("should output valid JSON format when --json flag is used", async () => { + await runWrangler("r2 bucket info my-bucket-name --json"); + const json = JSON.parse(logs.out); + expect(json.name).toBe("my-bucket-name"); + }); +}); diff --git a/packages/wrangler/src/r2/bucket.ts b/packages/wrangler/src/r2/bucket.ts index 2a6659df80fc..ddc6fb8744b9 100644 --- a/packages/wrangler/src/r2/bucket.ts +++ b/packages/wrangler/src/r2/bucket.ts @@ -198,7 +198,7 @@ export const r2BucketInfoCommand = createCommand({ positionalArgs: ["bucket"], args: { bucket: { - describe: "The name of the bucket to delete", + describe: "The name of the bucket to retrieve info for", type: "string", demandOption: true, }, @@ -208,11 +208,22 @@ export const r2BucketInfoCommand = createCommand({ requiresArg: true, type: "string", }, + json: { + describe: "Return the bucket information as JSON", + type: "boolean", + default: false, + }, }, + behaviour: { + printBanner: (args) => !args.json, + }, + async handler(args, { config }) { const accountId = await requireAuth(config); - logger.log(`Getting info for '${args.bucket}'...`); + if (!args.json) { + logger.log(`Getting info for '${args.bucket}'...`); + } const bucketInfo = await getR2Bucket( config, @@ -220,6 +231,7 @@ export const r2BucketInfoCommand = createCommand({ args.bucket, args.jurisdiction ); + const bucketMetrics = await getR2BucketMetrics( config, accountId, @@ -236,7 +248,11 @@ export const r2BucketInfoCommand = createCommand({ bucket_size: bucketMetrics.totalSize, }; - logger.log(formatLabelledValues(output)); + if (args.json) { + logger.json(output); + } else { + logger.log(formatLabelledValues(output)); + } }, });