From 0d665e540f424a2d94857ff7d91e94485804e3f8 Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:38:31 +0900 Subject: [PATCH 1/8] refactor(core): replace custom error page with Astro's built-in error overlay Use Astro's built-in ErrorOverlay Web Component instead of the custom 7-file ErrorPage implementation. Errors are now sent via WebSocket and rendered by the patched Vite client, providing source highlighting, hints, and editor links out of the box. - Remove `hmr: { overlay: false }` from Vite config - Inline `renderComponentByPath` into the render success path - Replace catch block with minimal HTML + WebSocket error payload - Remove `MightyDevOptions` type (use `MightyServerOptions` everywhere) - Delete `packages/core/src/dev/components/error-page/` (7 files) - Update tests to match new error handling behavior --- .../components/error-page/DarkModeIcon.astro | 32 --- .../components/error-page/ErrorDisplay.astro | 193 ------------------ .../dev/components/error-page/ErrorPage.astro | 94 --------- .../dev/components/error-page/Header.astro | 87 -------- .../components/error-page/LightModeIcon.astro | 36 ---- .../components/error-page/MightyLogo.astro | 71 ------- .../src/dev/components/error-page/types.ts | 21 -- packages/core/src/dev/index.ts | 4 +- packages/core/src/dev/setup.ts | 147 ++++++------- packages/core/src/types.ts | 8 - packages/core/tests/dev/error.test.ts | 30 +-- packages/core/tests/fixture.ts | 8 +- packages/hono/src/dev.ts | 6 +- 13 files changed, 79 insertions(+), 658 deletions(-) delete mode 100644 packages/core/src/dev/components/error-page/DarkModeIcon.astro delete mode 100644 packages/core/src/dev/components/error-page/ErrorDisplay.astro delete mode 100644 packages/core/src/dev/components/error-page/ErrorPage.astro delete mode 100644 packages/core/src/dev/components/error-page/Header.astro delete mode 100644 packages/core/src/dev/components/error-page/LightModeIcon.astro delete mode 100644 packages/core/src/dev/components/error-page/MightyLogo.astro delete mode 100644 packages/core/src/dev/components/error-page/types.ts diff --git a/packages/core/src/dev/components/error-page/DarkModeIcon.astro b/packages/core/src/dev/components/error-page/DarkModeIcon.astro deleted file mode 100644 index 4edad01..0000000 --- a/packages/core/src/dev/components/error-page/DarkModeIcon.astro +++ /dev/null @@ -1,32 +0,0 @@ ---- -interface Props { - class?: string; -} - -const { class: className } = Astro.props; ---- - - - - - - - - \ No newline at end of file diff --git a/packages/core/src/dev/components/error-page/ErrorDisplay.astro b/packages/core/src/dev/components/error-page/ErrorDisplay.astro deleted file mode 100644 index 915ee72..0000000 --- a/packages/core/src/dev/components/error-page/ErrorDisplay.astro +++ /dev/null @@ -1,193 +0,0 @@ ---- -import { Code } from "astro:components"; -import { readFile } from "node:fs/promises"; -import { transformerMetaHighlight } from "@shikijs/transformers"; -import { escapeHTML } from "astro/runtime/server/escape.js"; -import { createCssVariablesTheme } from "shiki"; -import type { ErrorWithMetadata } from "./types"; - -interface Props { - error: ErrorWithMetadata; -} - -const { error } = Astro.props; - -const fullCode = error.loc?.file - ? await readFile(error.loc.file, "utf-8") - : undefined; - -const separator = error.loc?.file?.includes("/") ? "/" : "\\"; -const fileLocation = [ - error.loc?.file?.split(separator).slice(-2).join("/"), - error.loc?.line, - error.loc?.column, -] - .filter(Boolean) - .join(":"); - -const theme = createCssVariablesTheme({ - variablePrefix: "--astro-code-", -}); - -const errorHintText = escapeHTML(error.hint ?? "") - .split(" ") - .map((v) => { - if (!v.startsWith("https://")) return v; - if (v.endsWith(".")) - return `${v.slice(0, -1)}.`; - return `${v}`; - }) - .join(" "); ---- - -
-

{error.name}

-

- {error.message} -

- { - errorHintText && ( -
- 🚀 -

-
- ) - } - {fileLocation &&

{fileLocation}

} - { - fullCode && ( - - ) - } -
- - - - - - diff --git a/packages/core/src/dev/components/error-page/ErrorPage.astro b/packages/core/src/dev/components/error-page/ErrorPage.astro deleted file mode 100644 index 55cd305..0000000 --- a/packages/core/src/dev/components/error-page/ErrorPage.astro +++ /dev/null @@ -1,94 +0,0 @@ ---- -import ErrorDisplay from "./ErrorDisplay.astro"; -import Header from "./Header.astro"; -import type { ErrorWithMetadata } from "./types"; - -interface Props { - error: ErrorWithMetadata; -} - -const { error } = Astro.props; ---- - - - - - Error - - - -
-
- -
- - - - diff --git a/packages/core/src/dev/components/error-page/Header.astro b/packages/core/src/dev/components/error-page/Header.astro deleted file mode 100644 index 4def227..0000000 --- a/packages/core/src/dev/components/error-page/Header.astro +++ /dev/null @@ -1,87 +0,0 @@ ---- -import DarkModeIcon from "./DarkModeIcon.astro"; -import LightModeIcon from "./LightModeIcon.astro"; -import MightyLogo from "./MightyLogo.astro"; ---- - -
-
-
- -
- - - - diff --git a/packages/core/src/dev/components/error-page/LightModeIcon.astro b/packages/core/src/dev/components/error-page/LightModeIcon.astro deleted file mode 100644 index faec933..0000000 --- a/packages/core/src/dev/components/error-page/LightModeIcon.astro +++ /dev/null @@ -1,36 +0,0 @@ ---- -interface Props { - class?: string; -} - -const { class: className } = Astro.props; ---- - - - - - - - - - - - \ No newline at end of file diff --git a/packages/core/src/dev/components/error-page/MightyLogo.astro b/packages/core/src/dev/components/error-page/MightyLogo.astro deleted file mode 100644 index 102ea80..0000000 --- a/packages/core/src/dev/components/error-page/MightyLogo.astro +++ /dev/null @@ -1,71 +0,0 @@ ---- -interface Props { - class?: string; -} - -const { class: className } = Astro.props; ---- - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/core/src/dev/components/error-page/types.ts b/packages/core/src/dev/components/error-page/types.ts deleted file mode 100644 index 39617c9..0000000 --- a/packages/core/src/dev/components/error-page/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -type ErrorTypes = - | "AstroError" - | "AstroUserError" - | "CompilerError" - | "CSSError" - | "MarkdownError" - | "InternalError" - | "AggregateError"; - -export interface ErrorWithMetadata { - name: string; - type?: ErrorTypes; - message: string; - stack: string; - hint?: string; - loc?: { - file?: string; - line?: number; - column?: number; - }; -} diff --git a/packages/core/src/dev/index.ts b/packages/core/src/dev/index.ts index 81f657d..9699ff8 100644 --- a/packages/core/src/dev/index.ts +++ b/packages/core/src/dev/index.ts @@ -1,11 +1,11 @@ -import type { MightyDevMiddleware, MightyDevOptions } from "@/types"; +import type { MightyDevMiddleware, MightyServerOptions } from "@/types"; import { setupDev } from "./setup"; /** * Starts the Mighty development server. */ export async function dev( - options: MightyDevOptions, + options: MightyServerOptions, ): Promise { return setupDev(options); } diff --git a/packages/core/src/dev/setup.ts b/packages/core/src/dev/setup.ts index 5bb1e05..77cd7bb 100644 --- a/packages/core/src/dev/setup.ts +++ b/packages/core/src/dev/setup.ts @@ -12,8 +12,8 @@ import type { ViteDevServer } from "vite"; import { getStylesForURL } from "@/dev/css"; import type { MightyDevMiddleware, - MightyDevOptions, MightyRenderDevRequest, + MightyServerOptions, } from "@/types"; import { dotStringToPath } from "@/utils/dotStringToPath"; import { injectTagsIntoHead } from "@/utils/injectTagsIntoHead"; @@ -31,7 +31,7 @@ const require = createRequire(import.meta.url); const devDir = path.join(path.dirname(require.resolve("@gomighty/core/dev"))); export async function setupDev( - options: MightyDevOptions, + options: MightyServerOptions, ): Promise { let finalConfig: AstroConfig; let viteServer: ViteDevServer; @@ -42,9 +42,6 @@ export async function setupDev( server: { middlewareMode: true, cors: false, - hmr: { - overlay: false, - }, }, plugins: [ { @@ -141,67 +138,12 @@ export async function setupDev( ] : () => []; - const renderComponentByPath = async ( - data: Omit & { - componentPath: `${string}.astro`; - }, - ) => { - const { componentPath, props, context, partial = true, address } = data; - - const [rawRenderedComponent, styleTags] = await Promise.all([ - renderComponent({ - componentPath, - props, - context, - partial, - }), - getStylesForURL(componentPath, viteServer).then((styles): Element[] => - styles.styles.map((style) => ({ - type: "element", - tagName: "style", - properties: { - type: "text/css", - "data-vite-dev-id": style.id, - }, - children: [{ type: "text", value: style.content }], - })), - ), - ]); - - // Rewrite image URLs to include the dev address - const renderedComponent = rawRenderedComponent.replace( - /(["'(])\/@fs\//g, - `$1${MIGHTY_DEV_PLACEHOLDER_ADDRESS}/@fs/`, - ); - - const viteClientScript: Element = { - type: "element", - tagName: "script", - properties: { - type: "module", - src: `${MIGHTY_DEV_PLACEHOLDER_ADDRESS}/@vite/client`, - }, - children: [], - }; - - return injectTagsIntoHead( - renderedComponent, - [ - ...styleTags, - viteClientScript, - ...getPageScripts(), - ...headInlineScriptTags, - ], - partial, - ).replaceAll(MIGHTY_DEV_PLACEHOLDER_ADDRESS, address); - }; - return { viteMiddleware: viteServer.middlewares, stop: () => viteServer.close(), render: async (request: MightyRenderDevRequest) => { try { - const { component, props, context, partial, address } = request; + const { component, props, context, partial = true, address } = request; const componentPath: `${string}.astro` = `${path.join( finalConfig.srcDir.pathname, @@ -216,39 +158,84 @@ export async function setupDev( return { status: 404, content: `Component ${component} not found` }; } - return { - status: 200, - content: await renderComponentByPath({ + const [rawRenderedComponent, styleTags] = await Promise.all([ + renderComponent({ componentPath, props, context, partial, - address, }), + getStylesForURL(componentPath, viteServer).then((styles): Element[] => + styles.styles.map((style) => ({ + type: "element", + tagName: "style", + properties: { + type: "text/css", + "data-vite-dev-id": style.id, + }, + children: [{ type: "text", value: style.content }], + })), + ), + ]); + + // Rewrite image URLs to include the dev address + const renderedComponent = rawRenderedComponent.replace( + /(["'(])\/@fs\//g, + `$1${MIGHTY_DEV_PLACEHOLDER_ADDRESS}/@fs/`, + ); + + const viteClientScript: Element = { + type: "element", + tagName: "script", + properties: { + type: "module", + src: `${MIGHTY_DEV_PLACEHOLDER_ADDRESS}/@vite/client`, + }, + children: [], }; + + const content = injectTagsIntoHead( + renderedComponent, + [ + ...styleTags, + viteClientScript, + ...getPageScripts(), + ...headInlineScriptTags, + ], + partial, + ).replaceAll(MIGHTY_DEV_PLACEHOLDER_ADDRESS, address); + + return { status: 200, content }; } catch (error) { viteServer.ssrFixStacktrace(error as Error); - if (!(options.showErrorPage ?? true)) { + // Check hmr.overlay from the resolved Astro config (defaults to true) + const hmr = finalConfig.vite?.server?.hmr; + const overlayEnabled = + typeof hmr === "object" ? hmr.overlay !== false : hmr !== false; + + if (!overlayEnabled) { throw error; } + const err = error as Error; + + // Send error to Astro's error overlay via WebSocket + // 200ms delay matches Astro's pattern — gives the browser time to load /@vite/client and connect + setTimeout(() => { + viteServer.environments.client.hot.send({ + type: "error", + err: { + message: err.message, + stack: err.stack ?? "", + }, + }); + }, 200); + + // Return minimal HTML that loads Vite client (which renders the error overlay) return { status: 500, - content: await renderComponentByPath({ - componentPath: path.join( - devDir, - "components", - "error-page", - "ErrorPage.astro", - ) as `${string}.astro`, - props: { - error: error as Error, - }, - context: {}, - partial: false, - address: request.address, - }), + content: `${err.name}`, }; } }, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index afa69a2..ba9170f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -12,14 +12,6 @@ export type MightyServerOptions = { config?: AstroInlineConfig; }; -export type MightyDevOptions = MightyServerOptions & { - /** - * Whether to show the Mighty error page when an error occurs. - * @default true - */ - showErrorPage?: boolean; -}; - export type MightyStartOptions = MightyServerOptions; export type MightyRenderRequest = { diff --git a/packages/core/tests/dev/error.test.ts b/packages/core/tests/dev/error.test.ts index 008ed51..34f18a3 100644 --- a/packages/core/tests/dev/error.test.ts +++ b/packages/core/tests/dev/error.test.ts @@ -26,37 +26,17 @@ describe("dev error fixture", () => { partial: false, }); expect(response.status).toBe(500); - expect(response.content).toContain("Error"); expect(response.content).toContain( '', ); - expect(response.content).toContain("pages/error.astro:2:1"); }); - it("renders an error page when showErrorPage is true", async () => { + it("throws an error when hmr.overlay is false", async () => { const { render } = await fixture.startDevServer({ - showErrorPage: true, - config: { logLevel: "silent" }, - }); - - const response = await render({ - component: "error", - props: {}, - context: {}, - partial: false, - }); - expect(response.status).toBe(500); - expect(response.content).toContain("Error"); - expect(response.content).toContain( - '', - ); - expect(response.content).toContain("pages/error.astro:2:1"); - }); - - it("throws an error when showErrorPage is false", async () => { - const { render } = await fixture.startDevServer({ - showErrorPage: false, - config: { logLevel: "silent" }, + config: { + logLevel: "silent", + vite: { server: { hmr: { overlay: false } } }, + }, }); await expect( diff --git a/packages/core/tests/fixture.ts b/packages/core/tests/fixture.ts index 30beba2..482cd14 100644 --- a/packages/core/tests/fixture.ts +++ b/packages/core/tests/fixture.ts @@ -6,11 +6,7 @@ import { toFetchResponse, toReqRes } from "fetch-to-node"; import { build } from "@/build"; import { setupDev } from "@/dev/setup"; import { setupStart } from "@/start/setup"; -import type { - MightyDevOptions, - MightyRenderRequest, - MightyServerOptions, -} from "@/types"; +import type { MightyRenderRequest, MightyServerOptions } from "@/types"; import { dotStringToPath } from "@/utils/dotStringToPath"; export type DevRenderFunction = ( @@ -27,7 +23,7 @@ export type GetFromViteMiddlewareFunction = ( export function getFixture(fixtureName: string): { fixtureRoot: string; outDir: string; - startDevServer: (params?: MightyDevOptions) => Promise<{ + startDevServer: (params?: MightyServerOptions) => Promise<{ render: DevRenderFunction; getFromViteMiddleware: GetFromViteMiddlewareFunction; stop: () => Promise; diff --git a/packages/hono/src/dev.ts b/packages/hono/src/dev.ts index 1f1ebc3..d02c1e4 100644 --- a/packages/hono/src/dev.ts +++ b/packages/hono/src/dev.ts @@ -1,4 +1,4 @@ -import { dev, type MightyDevOptions } from "@gomighty/core"; +import { dev, type MightyServerOptions } from "@gomighty/core"; import { mergeConfig } from "astro/config"; import { createMiddleware } from "hono/factory"; import type { UnofficialStatusCode } from "hono/utils/http-status"; @@ -8,9 +8,9 @@ import { runConnectMiddleware } from "./utils/runConnectMiddleware.ts"; const MIGHTY_DEV_ROOT = "/__MIGHTY_DEV_ADDRESS__"; export function devMiddleware( - options?: MightyDevOptions, + options?: MightyServerOptions, ): StartMightyServerMiddlewareHandler { - const mightyConfig: MightyDevOptions = { + const mightyConfig: MightyServerOptions = { ...options, config: mergeConfig( { root: "./astro", vite: { base: MIGHTY_DEV_ROOT } }, From 8dbd6db38f955b640f98c24a8ff93d484263594d Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:44:35 +0900 Subject: [PATCH 2/8] tweaks --- packages/core/src/dev/setup.ts | 12 +++--------- packages/core/tests/dev/error.test.ts | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/core/src/dev/setup.ts b/packages/core/src/dev/setup.ts index 77cd7bb..336432b 100644 --- a/packages/core/src/dev/setup.ts +++ b/packages/core/src/dev/setup.ts @@ -209,7 +209,6 @@ export async function setupDev( } catch (error) { viteServer.ssrFixStacktrace(error as Error); - // Check hmr.overlay from the resolved Astro config (defaults to true) const hmr = finalConfig.vite?.server?.hmr; const overlayEnabled = typeof hmr === "object" ? hmr.overlay !== false : hmr !== false; @@ -218,24 +217,19 @@ export async function setupDev( throw error; } - const err = error as Error; - - // Send error to Astro's error overlay via WebSocket - // 200ms delay matches Astro's pattern — gives the browser time to load /@vite/client and connect setTimeout(() => { viteServer.environments.client.hot.send({ type: "error", err: { - message: err.message, - stack: err.stack ?? "", + message: (error as Error).message, + stack: (error as Error).stack ?? "", }, }); }, 200); - // Return minimal HTML that loads Vite client (which renders the error overlay) return { status: 500, - content: `${err.name}`, + content: `${(error as Error).name}`, }; } }, diff --git a/packages/core/tests/dev/error.test.ts b/packages/core/tests/dev/error.test.ts index 34f18a3..c163f1e 100644 --- a/packages/core/tests/dev/error.test.ts +++ b/packages/core/tests/dev/error.test.ts @@ -14,7 +14,7 @@ describe("dev error fixture", () => { await fixture.clean(); }); - it("can render an error page", async () => { + it("renders the Astro error overlay", async () => { const { render } = await fixture.startDevServer({ config: { logLevel: "silent" }, }); From ef5d94cd1a58ff8bda1d5d76d705610641f7672b Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Mon, 16 Mar 2026 23:06:43 +0900 Subject: [PATCH 3/8] fix(core): preserve error properties for Astro's error overlay enrichment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass through loc, hint, frame, fullCode, plugin, pluginCode, id, and cause from the original error to the WebSocket payload. Astro's module loader intercepts hot.send() and calls collectErrorMetadata() + getViteErrorPayload() to enrich the overlay — but only if these properties are present. Without them, the overlay was missing source code highlighting, error carets, and hints. --- packages/core/src/dev/setup.ts | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/core/src/dev/setup.ts b/packages/core/src/dev/setup.ts index 336432b..4e31b94 100644 --- a/packages/core/src/dev/setup.ts +++ b/packages/core/src/dev/setup.ts @@ -8,7 +8,7 @@ import { } from "astro"; import { mergeConfig } from "astro/config"; import type { Element } from "hast"; -import type { ViteDevServer } from "vite"; +import type { ErrorPayload, ViteDevServer } from "vite"; import { getStylesForURL } from "@/dev/css"; import type { MightyDevMiddleware, @@ -217,19 +217,38 @@ export async function setupDev( throw error; } + const err = error as Error & Record; + + // Send error to Astro's error overlay via WebSocket. + // Astro's module loader intercepts hot.send() and enriches the payload + // with source code highlighting, hints, and docs links via + // collectErrorMetadata() + getViteErrorPayload(). + // We preserve all error properties (loc, hint, frame, etc.) so the + // enrichment has full context — especially for AstroError subclasses. + const errPayload: ErrorPayload = { + type: "error", + err: { + name: err.name, + message: err.message, + stack: err.stack ?? "", + loc: err.loc, + hint: err.hint, + frame: err.frame, + fullCode: err.fullCode, + plugin: err.plugin, + pluginCode: err.pluginCode, + id: err.id, + cause: err.cause, + } as ErrorPayload["err"], + }; + setTimeout(() => { - viteServer.environments.client.hot.send({ - type: "error", - err: { - message: (error as Error).message, - stack: (error as Error).stack ?? "", - }, - }); + viteServer.environments.client.hot.send(errPayload); }, 200); return { status: 500, - content: `${(error as Error).name}`, + content: `${err.name}`, }; } }, From 470daa6d30351434e6050e90da941d646a4ef535 Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Wed, 18 Mar 2026 07:04:01 +0900 Subject: [PATCH 4/8] fix(core): extract loc from stack frames to fix error overlay source location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Astro's collectInfoFromStacktrace heuristic searches for a line containing "src" or "node_modules" — but when the error message itself contains a component path (e.g., "astro/src/pages/index.astro"), it matches the message line instead of an actual stack frame, causing loc extraction to fail. Add extractLocFromStack() that only parses proper "at ..." stack frames, and use it as a fallback when the error doesn't already have loc set. This ensures the overlay always shows the "Open in editor" link. --- packages/core/src/dev/setup.ts | 36 +++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/core/src/dev/setup.ts b/packages/core/src/dev/setup.ts index 4e31b94..090a078 100644 --- a/packages/core/src/dev/setup.ts +++ b/packages/core/src/dev/setup.ts @@ -1,6 +1,7 @@ import { access } from "node:fs/promises"; import { createRequire } from "node:module"; import path from "node:path"; +import { fileURLToPath } from "node:url"; import { type AstroConfig, type AstroInlineConfig, @@ -30,6 +31,39 @@ import { getViteLogger } from "./viteLogger"; const require = createRequire(import.meta.url); const devDir = path.join(path.dirname(require.resolve("@gomighty/core/dev"))); +/** + * Extract error location from the first actual stack frame. + * + * Astro's `collectInfoFromStacktrace` searches for a line containing "src" or + * "node_modules" — but if the error *message* contains a component path like + * "astro/src/pages/index.astro", it matches the message line instead of a + * stack frame. By extracting `loc` ourselves and passing it in the payload, + * `collectInfoFromStacktrace` skips its fallback heuristic entirely. + */ +function extractLocFromStack( + stack: string, +): { file: string; line: number; column: number } | undefined { + for (const line of stack.split("\n")) { + if (!line.trimStart().startsWith("at ")) continue; + const match = + /\((.+):(\d+):(\d+)\)/.exec(line) ?? /at\s+(.+):(\d+):(\d+)/.exec(line); + if (match?.[1] && match[2] && match[3]) { + let file = match[1]; + try { + file = fileURLToPath(file); + } catch { + // Not a file:// URL, use as-is + } + return { + file, + line: Number.parseInt(match[2]), + column: Number.parseInt(match[3]), + }; + } + } + return undefined; +} + export async function setupDev( options: MightyServerOptions, ): Promise { @@ -231,7 +265,7 @@ export async function setupDev( name: err.name, message: err.message, stack: err.stack ?? "", - loc: err.loc, + loc: err.loc ?? extractLocFromStack(err.stack ?? ""), hint: err.hint, frame: err.frame, fullCode: err.fullCode, From 4da587a0c732ef09b6662064e5e5b3515039a487 Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Wed, 18 Mar 2026 07:14:09 +0900 Subject: [PATCH 5/8] fix(core): pass title through to error overlay, remove extractLocFromStack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `title` to the WebSocket error payload — AstroError instances carry a `title` property that the overlay uses for the main heading (falls back to "An error occurred." when missing). Remove `extractLocFromStack` — for errors like PrerenderClientAddressNotAvailable, the extracted loc would point to an internal Astro file (render-context.js), which isn't useful to the user. Let the interceptor's collectErrorMetadata handle loc extraction naturally. --- packages/core/src/dev/setup.ts | 37 ++-------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/packages/core/src/dev/setup.ts b/packages/core/src/dev/setup.ts index 090a078..07d2fb3 100644 --- a/packages/core/src/dev/setup.ts +++ b/packages/core/src/dev/setup.ts @@ -1,7 +1,6 @@ import { access } from "node:fs/promises"; import { createRequire } from "node:module"; import path from "node:path"; -import { fileURLToPath } from "node:url"; import { type AstroConfig, type AstroInlineConfig, @@ -31,39 +30,6 @@ import { getViteLogger } from "./viteLogger"; const require = createRequire(import.meta.url); const devDir = path.join(path.dirname(require.resolve("@gomighty/core/dev"))); -/** - * Extract error location from the first actual stack frame. - * - * Astro's `collectInfoFromStacktrace` searches for a line containing "src" or - * "node_modules" — but if the error *message* contains a component path like - * "astro/src/pages/index.astro", it matches the message line instead of a - * stack frame. By extracting `loc` ourselves and passing it in the payload, - * `collectInfoFromStacktrace` skips its fallback heuristic entirely. - */ -function extractLocFromStack( - stack: string, -): { file: string; line: number; column: number } | undefined { - for (const line of stack.split("\n")) { - if (!line.trimStart().startsWith("at ")) continue; - const match = - /\((.+):(\d+):(\d+)\)/.exec(line) ?? /at\s+(.+):(\d+):(\d+)/.exec(line); - if (match?.[1] && match[2] && match[3]) { - let file = match[1]; - try { - file = fileURLToPath(file); - } catch { - // Not a file:// URL, use as-is - } - return { - file, - line: Number.parseInt(match[2]), - column: Number.parseInt(match[3]), - }; - } - } - return undefined; -} - export async function setupDev( options: MightyServerOptions, ): Promise { @@ -263,9 +229,10 @@ export async function setupDev( type: "error", err: { name: err.name, + title: err.title, message: err.message, stack: err.stack ?? "", - loc: err.loc ?? extractLocFromStack(err.stack ?? ""), + loc: err.loc, hint: err.hint, frame: err.frame, fullCode: err.fullCode, From 5d5a9988fb4115f5a0e6ca628e6f9eacfe529c23 Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:00:28 +0900 Subject: [PATCH 6/8] Fix Astro-specific error overlay (do not show external files) --- packages/core/src/dev/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/dev/setup.ts b/packages/core/src/dev/setup.ts index 07d2fb3..00dcaa7 100644 --- a/packages/core/src/dev/setup.ts +++ b/packages/core/src/dev/setup.ts @@ -232,7 +232,7 @@ export async function setupDev( title: err.title, message: err.message, stack: err.stack ?? "", - loc: err.loc, + loc: err.loc ?? { line: 1, column: 1 }, hint: err.hint, frame: err.frame, fullCode: err.fullCode, From 0944faa3807d14ed16348efe51dd9ef0a867c060 Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:34:37 +0900 Subject: [PATCH 7/8] Simplify error sending --- packages/core/src/dev/setup.ts | 35 ++++++---------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/packages/core/src/dev/setup.ts b/packages/core/src/dev/setup.ts index 00dcaa7..b0102b5 100644 --- a/packages/core/src/dev/setup.ts +++ b/packages/core/src/dev/setup.ts @@ -8,7 +8,7 @@ import { } from "astro"; import { mergeConfig } from "astro/config"; import type { Element } from "hast"; -import type { ErrorPayload, ViteDevServer } from "vite"; +import type { ViteDevServer } from "vite"; import { getStylesForURL } from "@/dev/css"; import type { MightyDevMiddleware, @@ -217,39 +217,16 @@ export async function setupDev( throw error; } - const err = error as Error & Record; - - // Send error to Astro's error overlay via WebSocket. - // Astro's module loader intercepts hot.send() and enriches the payload - // with source code highlighting, hints, and docs links via - // collectErrorMetadata() + getViteErrorPayload(). - // We preserve all error properties (loc, hint, frame, etc.) so the - // enrichment has full context — especially for AstroError subclasses. - const errPayload: ErrorPayload = { - type: "error", - err: { - name: err.name, - title: err.title, - message: err.message, - stack: err.stack ?? "", - loc: err.loc ?? { line: 1, column: 1 }, - hint: err.hint, - frame: err.frame, - fullCode: err.fullCode, - plugin: err.plugin, - pluginCode: err.pluginCode, - id: err.id, - cause: err.cause, - } as ErrorPayload["err"], - }; - setTimeout(() => { - viteServer.environments.client.hot.send(errPayload); + viteServer.environments.client.hot.send({ + type: "error", + err: error as Error & { stack: string }, + }); }, 200); return { status: 500, - content: `${err.name}`, + content: `${(error as Error).name}`, }; } }, From 677fdd172f1e9d8ca243a80e65c28825b6d2dc1e Mon Sep 17 00:00:00 2001 From: Amine Ilidrissi <38422328+aminevg@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:51:41 +0900 Subject: [PATCH 8/8] fix(hono): forward /__open-in-editor requests to Vite middleware --- packages/hono/src/dev.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/hono/src/dev.ts b/packages/hono/src/dev.ts index d02c1e4..ea285d2 100644 --- a/packages/hono/src/dev.ts +++ b/packages/hono/src/dev.ts @@ -23,7 +23,11 @@ export function devMiddleware( return createMiddleware(async (c, next) => { const { render, viteMiddleware } = await setupDevPromise; - if (c.req.method === "GET" && c.req.path.includes(MIGHTY_DEV_ROOT)) { + if ( + c.req.method === "GET" && + (c.req.path.includes(MIGHTY_DEV_ROOT) || + c.req.path === "/__open-in-editor") + ) { return runConnectMiddleware(viteMiddleware, c); }