diff --git a/.changeset/fair-shoes-melt.md b/.changeset/fair-shoes-melt.md new file mode 100644 index 000000000000..4ee839db8c0d --- /dev/null +++ b/.changeset/fair-shoes-melt.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +fix: ensure d1 validation errors render user friendly messages diff --git a/packages/wrangler/src/deploy/deploy.ts b/packages/wrangler/src/deploy/deploy.ts index 41588459fdde..5eab7f4b4cf3 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -719,6 +719,15 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m ) { err.preventReport(); + if ( + err.notes[0].text === + "binding DB of type d1 must have a valid `id` specified [code: 10021]" + ) { + throw new UserError( + "You must use a real database in the database_id configuration. You can find your databases using 'wrangler d1 list', or read how to develop locally with D1 here: https://developers.cloudflare.com/d1/configuration/local-development" + ); + } + const maybeNameToFilePath = (moduleName: string) => { // If this is a service worker, always return the entrypoint path. // Service workers can't have additional JavaScript modules. diff --git a/packages/wrangler/src/dev/remote.tsx b/packages/wrangler/src/dev/remote.tsx index ac3d36d55f1c..7cfa0dc0b7a0 100644 --- a/packages/wrangler/src/dev/remote.tsx +++ b/packages/wrangler/src/dev/remote.tsx @@ -29,6 +29,7 @@ import type { CfWorkerContext, CfWorkerInit, } from "../deployment-bundle/worker"; +import type { ParseError } from "../parse"; import type { AssetPaths } from "../sites"; import type { ChooseAccountItem } from "../user"; import type { @@ -336,27 +337,18 @@ export function useWorker( start().catch((err) => { // we want to log the error, but not end the process // since it could recover after the developer fixes whatever's wrong - if ((err as { code: string }).code !== "ABORT_ERR") { - // instead of logging the raw API error to the user, - // give them friendly instructions - // for error 10063 (workers.dev subdomain required) - if (err.code === 10063) { - const errorMessage = - "Error: You need to register a workers.dev subdomain before running the dev command in remote mode"; - const solutionMessage = - "You can either enable local mode by pressing l, or register a workers.dev subdomain here:"; - const onboardingLink = `https://dash.cloudflare.com/${props.accountId}/workers/onboarding`; - logger.error( - `${errorMessage}\n${solutionMessage}\n${onboardingLink}` - ); - } else if (err.code === 10049) { + // instead of logging the raw API error to the user, + // give them friendly instructions + if ((err as unknown as { code: string }).code !== "ABORT_ERR") { + // code 10049 happens when the preview token expires + if (err.code === 10049) { logger.log("Preview token expired, fetching a new one"); - // code 10049 happens when the preview token expires + // since we want a new preview token when this happens, // lets increment the counter, and trigger a rerun of // the useEffect above setRestartCounter((prevCount) => prevCount + 1); - } else { + } else if (!handleUserFriendlyError(err, props.accountId)) { logger.error("Error on remote worker:", err); } } @@ -525,21 +517,15 @@ export async function getRemotePreviewToken(props: RemoteProps) { return workerPreviewToken; } return start().catch((err) => { - if ((err as { code?: string })?.code !== "ABORT_ERR") { - // instead of logging the raw API error to the user, - // give them friendly instructions - // for error 10063 (workers.dev subdomain required) - if (err?.code === 10063) { - const errorMessage = - "Error: You need to register a workers.dev subdomain before running the dev command in remote mode"; - const solutionMessage = - "You can either enable local mode by pressing l, or register a workers.dev subdomain here:"; - const onboardingLink = `https://dash.cloudflare.com/${props.accountId}/workers/onboarding`; - logger.error(`${errorMessage}\n${solutionMessage}\n${onboardingLink}`); - } else if (err?.code === 10049) { - // code 10049 happens when the preview token expires + // we want to log the error, but not end the process + // since it could recover after the developer fixes whatever's wrong + // instead of logging the raw API error to the user, + // give them friendly instructions + if ((err as unknown as { code: string })?.code !== "ABORT_ERR") { + // code 10049 happens when the preview token expires + if (err.code === 10049) { logger.log("Preview token expired, restart server to fetch a new one"); - } else { + } else if (!handleUserFriendlyError(err, props.accountId)) { helpIfErrorIsSizeOrScriptStartup(err, props.bundle?.dependencies || {}); logger.error("Error on remote worker:", err); } @@ -684,3 +670,55 @@ function ChooseAccount(props: { ); } + +/** + * A switch for handling thrown error mappings to user friendly + * messages, does not perform any logic other than logging errors. + * @returns if the error was handled or not + */ +function handleUserFriendlyError(error: ParseError, accountId?: string) { + switch ((error as unknown as { code: number }).code) { + // code 10021 is a validation error + case 10021: { + // if it is the following message, give a more user friendly + // error, otherwise do not handle this error in this function + if ( + error.notes[0].text === + "binding DB of type d1 must have a valid `id` specified [code: 10021]" + ) { + const errorMessage = + "Error: You must use a real database in the preview_database_id configuration."; + const solutionMessage = + "You can find your databases using 'wrangler d1 list', or read how to develop locally with D1 here:"; + const documentationLink = `https://developers.cloudflare.com/d1/configuration/local-development`; + + logger.error( + `${errorMessage}\n${solutionMessage}\n${documentationLink}` + ); + + return true; + } + + return false; + } + + // for error 10063 (workers.dev subdomain required) + case 10063: { + const errorMessage = + "Error: You need to register a workers.dev subdomain before running the dev command in remote mode"; + const solutionMessage = + "You can either enable local mode by pressing l, or register a workers.dev subdomain here:"; + const onboardingLink = accountId + ? `https://dash.cloudflare.com/${accountId}/workers/onboarding` + : "https://dash.cloudflare.com/?to=/:account/workers/onboarding"; + + logger.error(`${errorMessage}\n${solutionMessage}\n${onboardingLink}`); + + return true; + } + + default: { + return false; + } + } +}