Skip to content

Commit 660991c

Browse files
Fix build error location reporting for MDX and aggregate plugin errors (#17757)
Co-authored-by: factory[bot] <factory[bot]@users.noreply.github.com>
1 parent b872d6c commit 660991c

4 files changed

Lines changed: 89 additions & 5 deletions

File tree

.changeset/gentle-regions-shave.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'astro': patch
3+
'@astrojs/mdx': patch
4+
---
5+
6+
Fixes build errors showing wrong file location, missing line:col, and misleading hints when a plugin error (e.g. from MDX) is wrapped by Vite's build error

packages/astro/src/core/errors/dev/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ export function collectErrorMetadata(e: any, rootFolder?: URL): ErrorWithMetadat
2323
AggregateError.is(e) || Array.isArray(e.errors) ? (e.errors as SSRError[]) : [e as SSRError];
2424

2525
err.forEach((error) => {
26-
if (e.stack) {
27-
const stackInfo = collectInfoFromStacktrace(e);
26+
if (error.stack) {
27+
const stackInfo = collectInfoFromStacktrace(error);
2828
try {
2929
error.stack = stripVTControlCharacters(stackInfo.stack);
3030
} catch {}
@@ -68,7 +68,7 @@ export function collectErrorMetadata(e: any, rootFolder?: URL): ErrorWithMetadat
6868
}
6969

7070
// Generic error (probably from Vite, and already formatted)
71-
error.hint = generateHint(e);
71+
error.hint = generateHint(error);
7272

7373
// Strip ANSI for `message` property. Note that ESBuild errors may not have the property,
7474
// but it will be handled and added below, which is already ANSI-free

packages/astro/test/units/errors/dev-utils.test.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as assert from 'node:assert/strict';
22
import { describe, it } from 'node:test';
3-
import { renderErrorMarkdown } from '../../../dist/core/errors/dev/utils.js';
3+
import { collectErrorMetadata, renderErrorMarkdown } from '../../../dist/core/errors/dev/utils.js';
44

55
describe('renderErrorMarkdown', () => {
66
describe('html target', () => {
@@ -222,3 +222,70 @@ describe('renderErrorMarkdown', () => {
222222
});
223223
});
224224
});
225+
226+
describe('collectErrorMetadata', () => {
227+
it('uses sub-error properties when the parent error has an errors array', () => {
228+
// Simulate how rolldown/Vite wraps a plugin error: the parent error has
229+
// its own stack but no loc/plugin, while the sub-error carries the real info.
230+
const subError = new Error('Something went wrong in component.astro');
231+
subError.stack = `Error: Something went wrong in component.astro
232+
at renderComponent (file:///project/src/components/Foo.astro:10:5)`;
233+
234+
const parentError = new Error('Build failed with 1 error');
235+
parentError.stack = `Error: Build failed with 1 error
236+
at buildEnvironment (file:///node_modules/vite/dist/node/chunks/node.js:33011:66)`;
237+
// @ts-ignore - adding errors array like rolldown does
238+
parentError.errors = [subError];
239+
240+
const result = collectErrorMetadata(parentError);
241+
242+
// The sub-error's own stack should be used, not the parent's
243+
assert.ok(result.stack?.includes('renderComponent'));
244+
assert.ok(!result.stack?.includes('buildEnvironment'));
245+
});
246+
247+
it('preserves sub-error loc when parent error has no loc', () => {
248+
const subError = new Error('Parse error');
249+
// @ts-ignore
250+
subError.loc = { file: '/project/src/pages/test.mdx', line: 10, column: 5 };
251+
// @ts-ignore
252+
subError.plugin = 'astro:mdx';
253+
subError.stack = `Error: Parse error
254+
at transform (file:///project/node_modules/@astrojs/mdx/dist/index.js:42:7)`;
255+
256+
const parentError = new Error('Build failed');
257+
parentError.stack = `Error: Build failed
258+
at buildEnvironment (file:///node_modules/vite/dist/node.js:100:20)`;
259+
// @ts-ignore
260+
parentError.errors = [subError];
261+
262+
const result = collectErrorMetadata(parentError);
263+
264+
assert.equal(result.loc?.file, '/project/src/pages/test.mdx');
265+
assert.equal(result.loc?.line, 10);
266+
assert.equal(result.loc?.column, 5);
267+
assert.equal(result.plugin, 'astro:mdx');
268+
});
269+
270+
it('does not generate misleading hints from parent error message', () => {
271+
// The sub-error message has no browser API references, but the parent's
272+
// stack/message might mention "window" or "document" incidentally.
273+
const subError = new Error('Could not parse expression with oxc');
274+
subError.stack = `Error: Could not parse expression with oxc
275+
at transform (file:///project/node_modules/@astrojs/mdx/dist/index.js:42:7)`;
276+
277+
const parentError = new Error('Build failed - check document for details');
278+
parentError.stack = `Error: Build failed
279+
at build (file:///node_modules/vite/dist/node.js:100:20)`;
280+
// @ts-ignore
281+
parentError.errors = [subError];
282+
283+
const result = collectErrorMetadata(parentError);
284+
285+
// Should not get a "Browser APIs are not available" hint from the parent message
286+
assert.ok(
287+
!result.hint?.includes('Browser APIs'),
288+
'Should not generate browser API hint from parent error',
289+
);
290+
});
291+
});

packages/integrations/mdx/src/vite-plugin-mdx.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,18 @@ export function vitePluginMdx(opts: VitePluginMdxOptions): Plugin {
7272
// Surface compile failures as a dedicated MDX error with a source
7373
// location so the dev overlay can point at the offending file.
7474
err.name = 'MDXError';
75-
err.loc = { file: id, line: e.line, column: e.column };
75+
// Some parser errors (e.g. from oxc) embed line:col only in the
76+
// message as a "line:col: ..." prefix instead of setting properties.
77+
let line = e.line;
78+
let column = e.column;
79+
if (line == null || column == null) {
80+
const match = /^(\d+):(\d+):/.exec(e.message);
81+
if (match) {
82+
line ??= Number(match[1]);
83+
column ??= Number(match[2]);
84+
}
85+
}
86+
err.loc = { file: id, line, column };
7687
// Compiler errors may arrive without a JS stack; capture one here.
7788
Error.captureStackTrace(err);
7889
throw err;

0 commit comments

Comments
 (0)