Skip to content

Commit e0829f5

Browse files
fixups after PR review
The comments for the PR review were taking from the previous PR (#12055)
1 parent 385f661 commit e0829f5

File tree

4 files changed

+76
-65
lines changed

4 files changed

+76
-65
lines changed

packages/wrangler/src/core/CommandHandledError.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
/**
2-
* A wrapper that indicates the original error was thrown from a command handler
3-
* that has already sent telemetry (started + errored events).
4-
*
5-
* When this error is caught in index.ts, the outer error handler should:
6-
* 1. NOT send fallback telemetry (it's already been sent)
7-
* 2. Unwrap and rethrow the original error for proper error handling/display
2+
* A wrapper Error that indicates the original error was thrown during Command handler execution.
83
*
94
* This is used to distinguish between:
10-
* - Errors from command handlers (telemetry sent by handler)
11-
* - Yargs validation errors (telemetry needs to be sent by fallback handler)
5+
* - Errors from within command handlers: telemetry and error reporting already sent by handler.
6+
* - Yargs validation errors: telemetry and error reporting needs to be sent in the yargs middleware.
127
*/
138
export class CommandHandledError {
149
constructor(public readonly originalError: unknown) {}

packages/wrangler/src/core/handle-errors.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,6 @@ function isNetworkFetchFailedError(e: unknown): boolean {
249249

250250
/**
251251
* Determines the error type for telemetry purposes.
252-
* This is a pure function that doesn't log or have side effects.
253252
*/
254253
export function getErrorType(e: unknown): string | undefined {
255254
if (isCloudflareAPIDNSError(e)) {
@@ -264,12 +263,7 @@ export function getErrorType(e: unknown): string | undefined {
264263
if (isCloudflareAPIConnectionTimeoutError(e)) {
265264
return "ConnectionTimeout";
266265
}
267-
if (
268-
isAuthenticationError(e) ||
269-
(e instanceof UserError &&
270-
e.cause instanceof ApiError &&
271-
e.cause.status === 403)
272-
) {
266+
if (isAuthenticationError(e) || isContainersAuthenticationError(e)) {
273267
return "AuthenticationError";
274268
}
275269
if (isBuildFailure(e) || isBuildFailureFromCause(e)) {
@@ -475,14 +469,7 @@ export async function handleError(
475469
}
476470

477471
await wrangler.parse();
478-
} else if (
479-
isAuthenticationError(e) ||
480-
// Is this a Containers/Cloudchamber-based auth error?
481-
// This is different because it uses a custom OpenAPI-based generated client
482-
(e instanceof UserError &&
483-
e.cause instanceof ApiError &&
484-
e.cause.status === 403)
485-
) {
472+
} else if (isAuthenticationError(e) || isContainersAuthenticationError(e)) {
486473
mayReport = false;
487474
if (e.cause instanceof ApiError) {
488475
logger.error(e.cause);
@@ -600,3 +587,16 @@ export async function handleError(
600587
await captureGlobalException(loggableException);
601588
}
602589
}
590+
591+
/**
592+
* Is this a Containers/Cloudchamber-based auth error?
593+
*
594+
* This is different because it uses a custom OpenAPI-based generated client
595+
*/
596+
function isContainersAuthenticationError(e: unknown): e is UserError {
597+
return (
598+
e instanceof UserError &&
599+
e.cause instanceof ApiError &&
600+
e.cause.status === 403
601+
);
602+
}

packages/wrangler/src/core/register-yargs-command.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { dedent } from "../utils/dedent";
1818
import { isLocal, printResourceLocation } from "../utils/is-local";
1919
import { printWranglerBanner } from "../wrangler-banner";
2020
import { CommandHandledError } from "./CommandHandledError";
21-
import { getErrorType } from "./handle-errors";
21+
import { getErrorType, handleError } from "./handle-errors";
2222
import { demandSingleValue } from "./helpers";
2323
import type { CommonYargsArgv, SubHelp } from "../yargs-types";
2424
import type {
@@ -186,7 +186,6 @@ function createHandler(
186186
})
187187
: defaultWranglerConfig;
188188

189-
// Create metrics dispatcher with config info
190189
const dispatcher = getMetricsDispatcher({
191190
sendMetrics: config.send_metrics,
192191
hasAssets: !!config.assets?.directory,
@@ -215,7 +214,6 @@ function createHandler(
215214
}
216215
}
217216

218-
// Send "started" event
219217
dispatcher.sendCommandEvent("wrangler command started", {
220218
command: commandName,
221219
args,
@@ -230,7 +228,6 @@ function createHandler(
230228
fetchResult,
231229
});
232230

233-
// Send "completed" event
234231
const durationMs = Date.now() - startTime;
235232
dispatcher.sendCommandEvent("wrangler command completed", {
236233
command: commandName,
@@ -243,12 +240,11 @@ function createHandler(
243240
return result;
244241
} catch (err) {
245242
// If the error is already a CommandHandledError (e.g., from a nested wrangler.parse() call),
246-
// don't wrap it again - just rethrow it
243+
// don't wrap it again; just rethrow.
247244
if (err instanceof CommandHandledError) {
248245
throw err;
249246
}
250247

251-
// Send "errored" event
252248
const durationMs = Date.now() - startTime;
253249
dispatcher.sendCommandEvent("wrangler command errored", {
254250
command: commandName,
@@ -262,7 +258,10 @@ function createHandler(
262258
? err.telemetryMessage
263259
: undefined,
264260
});
265-
// Wrap the error to signal that telemetry has been sent
261+
262+
await handleError(err, args, argv);
263+
264+
// Wrap the error to signal that the telemetry has already been sent and the error reporting handled.
266265
throw new CommandHandledError(err);
267266
}
268267
});
@@ -274,13 +273,11 @@ function createHandler(
274273
if (outputErr instanceof Error) {
275274
const code =
276275
"code" in outputErr ? (outputErr.code as number) : undefined;
277-
const message =
278-
"message" in outputErr ? (outputErr.message as string) : undefined;
279276
writeOutput({
280277
type: "command-failed",
281278
version: 1,
282279
code,
283-
message,
280+
message: outputErr.message,
284281
});
285282
}
286283
throw err;

packages/wrangler/src/index.ts

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,41 +1814,27 @@ export async function main(argv: string[]): Promise<void> {
18141814
} catch (e) {
18151815
cliHandlerThrew = true;
18161816

1817-
// Check if this is a CommandHandledError (telemetry already sent by handler)
18181817
if (e instanceof CommandHandledError) {
1819-
// Unwrap and handle the original error
1820-
await handleError(e.originalError, configArgs, argv);
1818+
// This error occurred during Command handler execution,
1819+
// and has already sent metrics and reported to the user.
1820+
// So we can just re-throw the original error.
18211821
throw e.originalError;
1822+
} else {
1823+
// The error occurred before Command handler ran
1824+
// (e.g., yargs validation errors like unknown commands or invalid arguments).
1825+
// So we need to handle telemetry and error reporting here.
1826+
if (dispatcher && command) {
1827+
dispatchGenericCommandErrorEvent(
1828+
dispatcher,
1829+
command,
1830+
configArgs,
1831+
startTime,
1832+
e
1833+
);
1834+
}
1835+
await handleError(e, configArgs, argv);
1836+
throw e;
18221837
}
1823-
1824-
// Fallback telemetry for errors that occurred before handler ran
1825-
// (e.g., yargs validation errors like unknown commands or invalid arguments)
1826-
if (dispatcher && command) {
1827-
const durationMs = Date.now() - startTime;
1828-
1829-
// Send "started" event (since handler never got to send it)
1830-
dispatcher.sendCommandEvent("wrangler command started", {
1831-
command,
1832-
args: configArgs,
1833-
});
1834-
1835-
// Send "errored" event
1836-
dispatcher.sendCommandEvent("wrangler command errored", {
1837-
command,
1838-
args: configArgs,
1839-
durationMs,
1840-
durationSeconds: durationMs / 1000,
1841-
durationMinutes: durationMs / 1000 / 60,
1842-
errorType: getErrorType(e),
1843-
errorMessage:
1844-
e instanceof UserError || e instanceof ContainersUserError
1845-
? e.telemetryMessage
1846-
: undefined,
1847-
});
1848-
}
1849-
1850-
await handleError(e, configArgs, argv);
1851-
throw e;
18521838
} finally {
18531839
try {
18541840
// In the bootstrapper script `bin/wrangler.js`, we open an IPC channel,
@@ -1881,3 +1867,36 @@ export async function main(argv: string[]): Promise<void> {
18811867
}
18821868
}
18831869
}
1870+
1871+
/**
1872+
* Dispatches generic metrics events to indicate that a wrangler command errored
1873+
* when we don't know the CommandDefinition and cannot be sure what is safe to send.
1874+
*/
1875+
function dispatchGenericCommandErrorEvent(
1876+
dispatcher: ReturnType<typeof getMetricsDispatcher>,
1877+
command: string,
1878+
configArgs: ReadConfigCommandArgs,
1879+
startTime: number,
1880+
error: unknown
1881+
) {
1882+
const durationMs = Date.now() - startTime;
1883+
1884+
// Send "started" event since handler never got to send it.
1885+
dispatcher.sendCommandEvent("wrangler command started", {
1886+
command,
1887+
args: configArgs,
1888+
});
1889+
1890+
dispatcher.sendCommandEvent("wrangler command errored", {
1891+
command,
1892+
args: configArgs,
1893+
durationMs,
1894+
durationSeconds: durationMs / 1000,
1895+
durationMinutes: durationMs / 1000 / 60,
1896+
errorType: getErrorType(error),
1897+
errorMessage:
1898+
error instanceof UserError || error instanceof ContainersUserError
1899+
? error.telemetryMessage
1900+
: undefined,
1901+
});
1902+
}

0 commit comments

Comments
 (0)