Skip to content

Commit 3bdec6b

Browse files
authored
chore(miniflare): upgrade youch version (#9876)
* feat(miniflare): upgrade youch to v4.1.0-beta.10 * fix e2e test * add changeset
1 parent 8e3ce64 commit 3bdec6b

File tree

6 files changed

+126
-17
lines changed

6 files changed

+126
-17
lines changed

.changeset/warm-shirts-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"miniflare": patch
3+
---
4+
5+
chore: update youch version

packages/miniflare/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"undici": "catalog:default",
5454
"workerd": "1.20250708.0",
5555
"ws": "catalog:default",
56-
"youch": "3.3.4",
56+
"youch": "4.1.0-beta.10",
5757
"zod": "3.22.3"
5858
},
5959
"devDependencies": {

packages/miniflare/src/plugins/core/errors/index.ts

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,23 @@ function maybeGetFile(
120120
// Otherwise, something's gone wrong, so don't do any source mapping.
121121
}
122122

123+
// Like Object.fromEntries(), but preserves all values per header (string or array)
124+
function getHeaders(request: Request) {
125+
const headers: Record<string, string | string[]> = {};
126+
127+
for (const [key, value] of request.headers.entries()) {
128+
if (headers[key] === undefined) {
129+
headers[key] = value;
130+
} else if (typeof headers[key] === "string") {
131+
headers[key] = [headers[key], value];
132+
} else {
133+
headers[key].push(value);
134+
}
135+
}
136+
137+
return headers;
138+
}
139+
123140
function getSourceMappedStack(
124141
workerSrcOpts: NameSourceOptions[],
125142
error: Error
@@ -255,21 +272,40 @@ export async function handlePrettyErrorRequest(
255272
}
256273

257274
// Lazily import `youch` when required
258-
const Youch: typeof import("youch").default = require("youch");
275+
const { Youch }: typeof import("youch") = require("youch");
259276
// `cause` is usually more useful than the error itself, display that instead
260277
// TODO(someday): would be nice if we could display both
261-
const youch = new Youch(error.cause ?? error, {
262-
url: request.cf?.prettyErrorOriginalUrl ?? request.url,
263-
method: request.method,
264-
headers: Object.fromEntries(request.headers),
265-
});
266-
youch.addLink(() => {
267-
return [
268-
'<a href="https://developers.cloudflare.com/workers/" target="_blank" style="text-decoration:none">📚 Workers Docs</a>',
269-
'<a href="https://discord.cloudflare.com" target="_blank" style="text-decoration:none">💬 Workers Discord</a>',
278+
const youch = new Youch();
279+
280+
youch.useTransformer((error) => {
281+
error.frames = error.frames
282+
.filter(
283+
(frame) =>
284+
!frame.fileName?.includes(".wrangler/tmp") &&
285+
!frame.fileName?.includes("wrangler/templates/middleware")
286+
)
287+
.map((frame) => {
288+
// To avoid Youch throwing an error if the frame has no fileName
289+
// This happens in tests which hides some parts of the stack trace
290+
frame.fileName ??= "";
291+
292+
return frame;
293+
});
294+
error.hint = [
295+
'<a href="https://developers.cloudflare.com/workers/" target="_blank" style="text-decoration:none;font-style:normal;padding:5px">📚 Workers Docs</a>',
296+
'<a href="https://discord.cloudflare.com" target="_blank" style="text-decoration:none;font-style: normal;padding:5px">💬 Workers Discord</a>',
270297
].join("");
271298
});
272-
return new Response(await youch.toHTML(), {
299+
300+
const html = await youch.toHTML(error, {
301+
request: {
302+
url: `${request.cf?.prettyErrorOriginalUrl ?? request.url}`,
303+
method: request.method,
304+
headers: getHeaders(request),
305+
},
306+
});
307+
308+
return new Response(html, {
273309
status: 500,
274310
headers: { "Content-Type": "text/html;charset=utf-8" },
275311
});

packages/miniflare/test/plugins/core/errors/index.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,8 @@ test("responds with pretty error page", async (t) => {
319319
const text = await res.text();
320320
// ...including error, request method, URL and headers
321321
t.regex(text, /Unusual oops!/);
322-
t.regex(text, /Method.+POST/s);
323-
t.regex(text, /URI.+some-unusual-path/s);
322+
t.regex(text, /Method.+POST/is);
323+
t.regex(text, /URL.+some-unusual-path/is);
324324
t.regex(text, /X-Unusual-Key.+some-unusual-value/is);
325325

326326
// Check `fetch()` accepting HTML returns pretty-error page

packages/wrangler/e2e/startWorker.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => {
325325
headers: { Accept: "text/html" },
326326
});
327327
await expect(undiciRes.text()).resolves.toEqual(
328-
expect.stringContaining(`<h2 class="error-message"> Boom 3! </h2>`) // pretty error page html snippet
328+
expect.stringContaining(`<span>Boom 3!</span>`) // pretty error page html snippet
329329
);
330330

331331
// test further changes that fix the code

pnpm-lock.yaml

Lines changed: 70 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)