Skip to content

Commit

Permalink
[D1] Add user friendly D1 validation error messages for `dev --remote…
Browse files Browse the repository at this point in the history
…` and `deploy` (#4914)

* chore: refactor user friendly errors and add 10063 code handling

* chore: add user friendly error message to deployments

* chore: lint

* chore: cleanup directory

* chore: fix remote error handling

* chore: add patch change set

* chore: move ABORT_ERR logic out of the user friendly error handler

* chore: fix syntax

* Update .changeset/fair-shoes-melt.md

Co-authored-by: Pete Bacon Darwin <pete@bacondarwin.com>

---------

Co-authored-by: Pete Bacon Darwin <pete@bacondarwin.com>
  • Loading branch information
nora-soderlund and petebacondarwin authored Feb 5, 2024
1 parent a343cdd commit e61dba5
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-shoes-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

fix: ensure d1 validation errors render user friendly messages
9 changes: 9 additions & 0 deletions packages/wrangler/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
98 changes: 68 additions & 30 deletions packages/wrangler/src/dev/remote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
}
}

0 comments on commit e61dba5

Please sign in to comment.