diff --git a/.changeset/secret-bulk-undeployed-version.md b/.changeset/secret-bulk-undeployed-version.md new file mode 100644 index 00000000000..3ea085e2d69 --- /dev/null +++ b/.changeset/secret-bulk-undeployed-version.md @@ -0,0 +1,9 @@ +--- +"wrangler": patch +--- + +Show a useful error when `wrangler secret bulk` hits an undeployed latest version + +`wrangler secret put` already explained this case (API error 10215). `secret bulk` just dumped the raw API response, which for 10214 talks about logpush and tail_consumers even though you were only uploading secrets. + +Both commands now point at `wrangler versions secret …` instead. diff --git a/packages/vite-plugin-cloudflare/playground/vitest-setup.ts b/packages/vite-plugin-cloudflare/playground/vitest-setup.ts index 03bb2c61123..e717d20edc3 100644 --- a/packages/vite-plugin-cloudflare/playground/vitest-setup.ts +++ b/packages/vite-plugin-cloudflare/playground/vitest-setup.ts @@ -88,6 +88,7 @@ export function resetServerLogs() { } // eslint-disable-next-line no-empty-pattern -- Vitest requires the 1st argument to use object destructuring +// 120s: macOS vite-8 has been timing out the default 50s beforeAll on the headers/redirects SPA. beforeAll(async ({}, s) => { let server: ViteDevServer | PreviewServer | undefined; let postServe: (() => Promise) | undefined; @@ -213,7 +214,7 @@ beforeAll(async ({}, s) => { await postServe(); } }; -}); +}, 120_000); export async function loadConfig(configEnv: ConfigEnv) { let config: UserConfig | null = null; diff --git a/packages/wrangler/src/__tests__/secret.test.ts b/packages/wrangler/src/__tests__/secret.test.ts index 312e3b41e26..333f25c90c3 100644 --- a/packages/wrangler/src/__tests__/secret.test.ts +++ b/packages/wrangler/src/__tests__/secret.test.ts @@ -8,7 +8,10 @@ import { import { http, HttpResponse } from "msw"; import * as TOML from "smol-toml"; import { afterEach, beforeEach, describe, it, vi } from "vitest"; -import { VERSION_NOT_DEPLOYED_ERR_CODE } from "../secret"; +import { + VERSION_NOT_DEPLOYED_ERR_CODE, + VERSION_SETTINGS_NOT_DEPLOYED_ERR_CODE, +} from "../secret"; import { WORKER_NOT_FOUND_ERR_CODE, workerNotFoundErrorMessage, @@ -1239,6 +1242,80 @@ describe("wrangler secret", () => { expect(std.warn).toMatchInlineSnapshot(`""`); }); + it("should error if the latest version is not deployed", async ({ + expect, + }) => { + writeFileSync( + "secret.json", + JSON.stringify({ + "secret-name-1": "secret_text", + }) + ); + + msw.use( + http.patch( + `*/accounts/:accountId/workers/scripts/:scriptName/secrets-bulk`, + async ({ params }) => { + expect(params.accountId).toEqual("some-account-id"); + expect(params.scriptName).toEqual("script-name"); + return HttpResponse.json( + createFetchResult(null, false, [ + { + code: VERSION_NOT_DEPLOYED_ERR_CODE, + message: "latest is not deployed", + }, + ]) + ); + }, + { once: true } + ) + ); + + await expect(runWrangler("secret bulk ./secret.json --name script-name")) + .rejects.toThrowErrorMatchingInlineSnapshot(` + [Error: Secret edit failed. You attempted to modify a secret, but the latest version of your Worker isn't currently deployed. + This limitation exists to prevent accidental deployment when using Worker versions and secrets together. + To resolve this, you have two options: + (1) use the \`wrangler versions secret bulk\` instead, which allows you to update secrets without deploying; or + (2) deploy the latest version first, then modify secrets. + Alternatively, you can use the Cloudflare dashboard to modify secrets and deploy the version.] + `); + }); + + it("should rewrite the logpush/tail_consumers settings error when the latest version is not deployed", async ({ + expect, + }) => { + writeFileSync( + "secret.json", + JSON.stringify({ + "secret-name-1": "secret_text", + }) + ); + + msw.use( + http.patch( + `*/accounts/:accountId/workers/scripts/:scriptName/secrets-bulk`, + async ({ params }) => { + expect(params.accountId).toEqual("some-account-id"); + return HttpResponse.json( + createFetchResult(null, false, [ + { + code: VERSION_SETTINGS_NOT_DEPLOYED_ERR_CODE, + message: + "Script edit failed. You attempted to deploy the latest version with modified settings, but the latest version isn't currently deployed.", + }, + ]) + ); + }, + { once: true } + ) + ); + + await expect( + runWrangler("secret bulk ./secret.json --name script-name") + ).rejects.toThrow(/wrangler versions secret bulk/); + }); + it("throws a meaningful error", async ({ expect }) => { writeFileSync( "secret.json", diff --git a/packages/wrangler/src/secret/index.ts b/packages/wrangler/src/secret/index.ts index 8ef1875c49b..5107e622909 100644 --- a/packages/wrangler/src/secret/index.ts +++ b/packages/wrangler/src/secret/index.ts @@ -16,6 +16,46 @@ import { readFromStdin, trimTrailingWhitespace } from "../utils/std"; import type { Config } from "@cloudflare/workers-utils"; export const VERSION_NOT_DEPLOYED_ERR_CODE = 10215; +/** Script-settings edit on an undeployed latest version (logpush / tail_consumers). */ +export const VERSION_SETTINGS_NOT_DEPLOYED_ERR_CODE = 10214; + +const VERSION_NOT_DEPLOYED_ERR_CODES = [ + VERSION_NOT_DEPLOYED_ERR_CODE, + VERSION_SETTINGS_NOT_DEPLOYED_ERR_CODE, +]; + +/** + * The `wrangler secret` commands edit the currently deployed Worker. If the + * latest version isn't deployed, the API rejects the change with 10214/10215 + * and a note about logpush/tail_consumers that doesn't mention secrets. + * + * @param e - Error thrown by the secrets API. + * @param versionsCommand - The `wrangler versions secret …` command that + * updates secrets without deploying. + * @param telemetryMessage - Telemetry identifier for the rewritten error. + * @throws {UserError} When `e` is a version-not-deployed API error. + */ +function throwIfLatestVersionNotDeployed( + e: unknown, + versionsCommand: string, + telemetryMessage: string +): void { + if ( + e instanceof APIError && + e.code !== undefined && + VERSION_NOT_DEPLOYED_ERR_CODES.includes(e.code) + ) { + throw new UserError( + "Secret edit failed. You attempted to modify a secret, but the latest version of your Worker isn't currently deployed.\n" + + "This limitation exists to prevent accidental deployment when using Worker versions and secrets together.\n" + + "To resolve this, you have two options:\n" + + `(1) use the \`${versionsCommand}\` instead, which allows you to update secrets without deploying; or\n` + + "(2) deploy the latest version first, then modify secrets.\n" + + "Alternatively, you can use the Cloudflare dashboard to modify secrets and deploy the version.", + { telemetryMessage } + ); + } +} type SecretBindingUpload = { type: "secret_text"; @@ -154,19 +194,12 @@ export const secretPutCommand = createCommand({ }), }); } catch (e) { - if (e instanceof APIError && e.code === VERSION_NOT_DEPLOYED_ERR_CODE) { - throw new UserError( - "Secret edit failed. You attempted to modify a secret, but the latest version of your Worker isn't currently deployed.\n" + - "This limitation exists to prevent accidental deployment when using Worker versions and secrets together.\n" + - "To resolve this, you have two options:\n" + - "(1) use the `wrangler versions secret put` instead, which allows you to update secrets without deploying; or\n" + - "(2) deploy the latest version first, then modify secrets.\n" + - "Alternatively, you can use the Cloudflare dashboard to modify secrets and deploy the version.", - { telemetryMessage: "secret put version not deployed" } - ); - } else { - throw e; - } + throwIfLatestVersionNotDeployed( + e, + "wrangler versions secret put", + "secret put version not deployed" + ); + throw e; } } @@ -373,12 +406,21 @@ async function putBulkSecrets( secrets[key] = null; } } - const resp = await fetchResult(config, url, { - method: "PATCH", - headers: { "Content-Type": "application/merge-patch+json" }, - body: JSON.stringify({ secrets }), - }); - return [resp, toCreate, toDelete]; + try { + const resp = await fetchResult(config, url, { + method: "PATCH", + headers: { "Content-Type": "application/merge-patch+json" }, + body: JSON.stringify({ secrets }), + }); + return [resp, toCreate, toDelete]; + } catch (e) { + throwIfLatestVersionNotDeployed( + e, + "wrangler versions secret bulk", + "secret bulk version not deployed" + ); + throw e; + } } export const secretBulkCommand = createCommand({