diff --git a/.changeset/tidy-lions-breathe.md b/.changeset/tidy-lions-breathe.md new file mode 100644 index 000000000..719363949 --- /dev/null +++ b/.changeset/tidy-lions-breathe.md @@ -0,0 +1,7 @@ +--- +"@opennextjs/cloudflare": patch +--- + +fix: disable response compression when provisioning R2 cache buckets + +Avoid truncated compressed Cloudflare API responses causing R2 cache bucket provisioning to fail. diff --git a/packages/cloudflare/src/cli/utils/ensure-r2-bucket.spec.ts b/packages/cloudflare/src/cli/utils/ensure-r2-bucket.spec.ts new file mode 100644 index 000000000..938f3e0dd --- /dev/null +++ b/packages/cloudflare/src/cli/utils/ensure-r2-bucket.spec.ts @@ -0,0 +1,81 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { runWrangler } from "../commands/utils/run-wrangler.js"; +import { ensureR2Bucket } from "./ensure-r2-bucket.js"; + +const { MockCloudflare, mockR2BucketsGet } = vi.hoisted(() => { + const mockR2BucketsGet = vi.fn(); + + class MockCloudflare { + static NotFoundError = class extends Error {}; + + accounts = { list: vi.fn(() => []) }; + + r2 = { + buckets: { + get: mockR2BucketsGet, + create: vi.fn(), + }, + }; + } + + return { MockCloudflare: vi.fn(MockCloudflare), mockR2BucketsGet }; +}); + +vi.mock("@opennextjs/aws/build/helper.js", () => ({ + findPackagerAndRoot: vi.fn(() => ({ packager: "pnpm", root: "/tmp" })), +})); + +vi.mock("../commands/utils/run-wrangler.js", () => ({ + runWrangler: vi.fn(), +})); + +vi.mock("cloudflare", () => ({ + default: MockCloudflare, +})); + +vi.mock("./ask-account-selection.js", () => ({ + askAccountSelection: vi.fn(), +})); + +describe("ensureR2Bucket", () => { + beforeEach(() => { + vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "test-account-id"); + mockR2BucketsGet.mockResolvedValue({}); + }); + + it("disables response compression when authenticating with a token", async () => { + vi.mocked(runWrangler).mockReturnValue({ + success: true, + stdout: JSON.stringify({ type: "api_token", token: "test-token" }), + stderr: "", + }); + + await expect(ensureR2Bucket("/tmp/app", "test-bucket")).resolves.toEqual({ + success: true, + bucketName: "test-bucket", + }); + expect(MockCloudflare).toHaveBeenCalledWith({ + apiToken: "test-token", + defaultHeaders: { "Accept-Encoding": "identity" }, + }); + }); + + it("disables response compression when authenticating with an API key", async () => { + vi.mocked(runWrangler).mockReturnValue({ + success: true, + stdout: JSON.stringify({ type: "api_key", key: "test-key", email: "test@example.com" }), + stderr: "", + }); + + await expect(ensureR2Bucket("/tmp/app", "test-bucket")).resolves.toEqual({ + success: true, + bucketName: "test-bucket", + }); + expect(MockCloudflare).toHaveBeenCalledWith({ + apiKey: "test-key", + apiEmail: "test@example.com", + defaultHeaders: { "Accept-Encoding": "identity" }, + }); + }); +}); diff --git a/packages/cloudflare/src/cli/utils/ensure-r2-bucket.ts b/packages/cloudflare/src/cli/utils/ensure-r2-bucket.ts index c7270a783..2911e8c67 100644 --- a/packages/cloudflare/src/cli/utils/ensure-r2-bucket.ts +++ b/packages/cloudflare/src/cli/utils/ensure-r2-bucket.ts @@ -141,8 +141,15 @@ export async function ensureR2Bucket( const client = authCredentials.type === "api_key" - ? new Cloudflare({ apiKey: authCredentials.apiKey, apiEmail: authCredentials.apiEmail }) - : new Cloudflare({ apiToken: authCredentials.token }); + ? new Cloudflare({ + apiKey: authCredentials.apiKey, + apiEmail: authCredentials.apiEmail, + defaultHeaders: { "Accept-Encoding": "identity" }, + }) + : new Cloudflare({ + apiToken: authCredentials.token, + defaultHeaders: { "Accept-Encoding": "identity" }, + }); const accountId = await getAccountId(client); if (!accountId) {