diff --git a/.changeset/serious-seals-sneeze.md b/.changeset/serious-seals-sneeze.md new file mode 100644 index 000000000000..0ccabc4b0558 --- /dev/null +++ b/.changeset/serious-seals-sneeze.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: throw helpful error if email validation required + +Previously, Wrangler would display the raw API error message and code if email validation was required during `wrangler deploy`. This change ensures a helpful error message is displayed instead, prompting users to check their emails or visit the dashboard for a verification link. diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index 96ea6d9599f8..6d3c68d30d7e 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -4046,6 +4046,40 @@ addEventListener('fetch', event => {});` expect(std.err).toMatchInlineSnapshot(`""`); }); + it("should fail to deploy to the workers.dev domain if email is unverified", async () => { + writeWranglerToml({ workers_dev: true }); + writeWorkerSource(); + mockUploadWorkerRequest({ available_on_subdomain: false }); + mockSubDomainRequest(); + msw.use( + rest.post( + `*/accounts/:accountId/workers/scripts/:scriptName/subdomain`, + async (req, res, ctx) => { + return res.once( + ctx.json( + createFetchResult(null, /* success */ false, [ + { + code: 10034, + message: "workers.api.error.email_verification_required", + }, + ]) + ) + ); + } + ) + ); + + await expect(runWrangler("deploy ./index")).rejects.toMatchObject({ + text: "Please verify your account's email address and try again.", + notes: [ + { + text: "Check your email for a verification link, or login to https://dash.cloudflare.com and request a new one.", + }, + {}, + ], + }); + }); + it("should offer to create a new workers.dev subdomain when publishing to workers_dev without one", async () => { writeWranglerToml({ workers_dev: true, diff --git a/packages/wrangler/src/cfetch/errors.ts b/packages/wrangler/src/cfetch/errors.ts new file mode 100644 index 000000000000..f2cc72f7432b --- /dev/null +++ b/packages/wrangler/src/cfetch/errors.ts @@ -0,0 +1,23 @@ +import { ParseError } from "../parse"; + +export interface FetchError { + code: number; + message: string; + error_chain?: FetchError[]; +} + +function buildDetailedError(message: string, ...extra: string[]) { + return new ParseError({ + text: message, + notes: extra.map((text) => ({ text })), + }); +} + +export function maybeThrowFriendlyError(error: FetchError) { + if (error.message === "workers.api.error.email_verification_required") { + throw buildDetailedError( + "Please verify your account's email address and try again.", + "Check your email for a verification link, or login to https://dash.cloudflare.com and request a new one." + ); + } +} diff --git a/packages/wrangler/src/cfetch/index.ts b/packages/wrangler/src/cfetch/index.ts index 3e6d7e63c95f..891e23eb5ef4 100644 --- a/packages/wrangler/src/cfetch/index.ts +++ b/packages/wrangler/src/cfetch/index.ts @@ -1,16 +1,14 @@ import { URLSearchParams } from "node:url"; import { logger } from "../logger"; import { ParseError } from "../parse"; +import { maybeThrowFriendlyError } from "./errors"; import { fetchInternal, performApiFetch } from "./internal"; +import type { FetchError } from "./errors"; import type { RequestInit } from "undici"; // Check out https://api.cloudflare.com/ for API docs. -export interface FetchError { - code: number; - message: string; - error_chain?: FetchError[]; -} +export type { FetchError }; export interface FetchResult { success: boolean; result: ResponseType; @@ -164,6 +162,8 @@ function throwFetchError( resource: string, response: FetchResult ): never { + for (const error of response.errors) maybeThrowFriendlyError(error); + const error = new ParseError({ text: `A request to the Cloudflare API (${resource}) failed.`, notes: [