@gracile-labs/better-errors 0.1.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +15 -0
- package/ambient.d.ts +0 -0
- package/dist/dev/custom-overlay/assets.d.ts +7 -0
- package/dist/dev/custom-overlay/assets.d.ts.map +1 -0
- package/dist/dev/custom-overlay/assets.js +115 -0
- package/dist/dev/custom-overlay/vite-custom-overlay.d.ts +18 -0
- package/dist/dev/custom-overlay/vite-custom-overlay.d.ts.map +1 -0
- package/dist/dev/custom-overlay/vite-custom-overlay.js +208 -0
- package/dist/dev/custom-overlay/vite-custom-overlay.styles.d.ts +2 -0
- package/dist/dev/custom-overlay/vite-custom-overlay.styles.d.ts.map +1 -0
- package/dist/dev/custom-overlay/vite-custom-overlay.styles.js +549 -0
- package/dist/dev/logger.d.ts +5 -0
- package/dist/dev/logger.d.ts.map +1 -0
- package/dist/dev/logger.js +82 -0
- package/dist/dev/printer.d.ts +5 -0
- package/dist/dev/printer.d.ts.map +1 -0
- package/dist/dev/printer.js +38 -0
- package/dist/dev/utils.d.ts +16 -0
- package/dist/dev/utils.d.ts.map +1 -0
- package/dist/dev/utils.js +271 -0
- package/dist/dev/vite.d.ts +33 -0
- package/dist/dev/vite.d.ts.map +1 -0
- package/dist/dev/vite.js +191 -0
- package/dist/errors-data.d.ts +16 -0
- package/dist/errors-data.d.ts.map +1 -0
- package/dist/errors-data.js +10 -0
- package/dist/errors.d.ts +66 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +51 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +84 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import c from 'picocolors';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import { isAbsolute, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { normalizePath } from 'vite';
|
|
7
|
+
import { codeFrame } from './printer.js';
|
|
8
|
+
import { removeLeadingForwardSlashWindows } from '@gracile/internal-utils/paths';
|
|
9
|
+
import { AggregateError, } from '../errors.js';
|
|
10
|
+
const ansiPattern = [
|
|
11
|
+
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
12
|
+
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))',
|
|
13
|
+
].join('|');
|
|
14
|
+
const stripAnsi = (input, { onlyFirst = false } = {}) => {
|
|
15
|
+
return input.replace(new RegExp(ansiPattern, onlyFirst ? undefined : 'g'), '');
|
|
16
|
+
};
|
|
17
|
+
/** Coalesce any throw variable to an Error instance. */
|
|
18
|
+
export function createSafeError(err) {
|
|
19
|
+
if (err instanceof Error || (err?.name && err.message)) {
|
|
20
|
+
return err;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const error = new Error(JSON.stringify(err));
|
|
24
|
+
error['hint'] =
|
|
25
|
+
`To get as much information as possible from your errors, make sure to throw Error objects instead of \`${typeof err}\`. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error for more information.`;
|
|
26
|
+
return error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function normalizeLF(code) {
|
|
30
|
+
return code.replace(/\r\n|\r(?!\n)|\n/g, '\n');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Takes any error-like object and returns a standardized Error + metadata object.
|
|
34
|
+
* Useful for consistent reporting regardless of where the error surfaced from.
|
|
35
|
+
*/
|
|
36
|
+
export function collectErrorMetadata(e, rootFolder) {
|
|
37
|
+
const err = AggregateError.is(e) || Array.isArray(e.errors)
|
|
38
|
+
? e.errors
|
|
39
|
+
: [e];
|
|
40
|
+
err.forEach((error) => {
|
|
41
|
+
if (e.stack) {
|
|
42
|
+
const stackInfo = collectInfoFromStacktrace(e);
|
|
43
|
+
try {
|
|
44
|
+
error.stack = stripAnsi(stackInfo.stack);
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
if (stackInfo.loc)
|
|
48
|
+
error.loc = stackInfo.loc;
|
|
49
|
+
if (stackInfo.plugin)
|
|
50
|
+
error.plugin = stackInfo.plugin;
|
|
51
|
+
if (stackInfo.pluginCode)
|
|
52
|
+
error.pluginCode = stackInfo.pluginCode;
|
|
53
|
+
}
|
|
54
|
+
// Make sure the file location is absolute, otherwise:
|
|
55
|
+
// - It won't be clickable in the terminal
|
|
56
|
+
// - We'll fail to show the file's content in the browser
|
|
57
|
+
// - We'll fail to show the code frame in the terminal
|
|
58
|
+
// - The "Open in Editor" button won't work
|
|
59
|
+
// Normalize the paths so that we can correctly detect if it's absolute on any platform
|
|
60
|
+
const normalizedFile = normalizePath(error.loc?.file || '');
|
|
61
|
+
const normalizedRootFolder = removeLeadingForwardSlashWindows(rootFolder?.pathname || '');
|
|
62
|
+
if (error.loc?.file &&
|
|
63
|
+
rootFolder &&
|
|
64
|
+
(!normalizedFile?.startsWith(normalizedRootFolder) ||
|
|
65
|
+
!isAbsolute(normalizedFile))) {
|
|
66
|
+
error.loc.file = join(fileURLToPath(rootFolder), error.loc.file);
|
|
67
|
+
}
|
|
68
|
+
// If we don't have a frame, but we have a location let's try making up a frame for it
|
|
69
|
+
if (error.loc && (!error.frame || !error['fullCode'])) {
|
|
70
|
+
try {
|
|
71
|
+
const fileContents = fs.readFileSync(error.loc.file, 'utf8');
|
|
72
|
+
if (!error.frame) {
|
|
73
|
+
const frame = codeFrame(fileContents, error.loc);
|
|
74
|
+
error.frame = stripAnsi(frame);
|
|
75
|
+
}
|
|
76
|
+
if (!error['fullCode']) {
|
|
77
|
+
error['fullCode'] = fileContents;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
}
|
|
82
|
+
// Generic error (probably from Vite, and already formatted)
|
|
83
|
+
error['hint'] = generateHint(e);
|
|
84
|
+
// Strip ANSI for `message` property. Note that ESBuild errors may not have the property,
|
|
85
|
+
// but it will be handled and added below, which is already ANSI-free
|
|
86
|
+
if (error.message) {
|
|
87
|
+
try {
|
|
88
|
+
error.message = stripAnsi(error.message);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Setting `error.message` can fail here if the message is read-only, which for the vast majority of cases will never happen, however some somewhat obscure cases can cause this to happen.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// If we received an array of errors and it's not from us, it's most likely from ESBuild, try to extract info for Vite to display
|
|
96
|
+
// NOTE: We still need to be defensive here, because it might not necessarily be from ESBuild, it's just fairly likely.
|
|
97
|
+
if (!AggregateError.is(e) && Array.isArray(e.errors)) {
|
|
98
|
+
e.errors.forEach((buildError, i) => {
|
|
99
|
+
const { location, pluginName, text } = buildError;
|
|
100
|
+
if (!err[i])
|
|
101
|
+
return;
|
|
102
|
+
// ESBuild can give us a slightly better error message than the one in the error, so let's use it
|
|
103
|
+
if (text) {
|
|
104
|
+
try {
|
|
105
|
+
err[i].message = text;
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
}
|
|
109
|
+
if (location) {
|
|
110
|
+
err[i].loc = {
|
|
111
|
+
file: location.file,
|
|
112
|
+
line: location.line,
|
|
113
|
+
column: location.column,
|
|
114
|
+
};
|
|
115
|
+
err[i].id = err[0]?.id || location?.file;
|
|
116
|
+
}
|
|
117
|
+
// Vite adds the error message to the frame for ESBuild errors, we don't want that
|
|
118
|
+
if (err[i].frame) {
|
|
119
|
+
const errorLines = err[i].frame?.trim().split('\n');
|
|
120
|
+
if (errorLines && errorLines[0]) {
|
|
121
|
+
err[i].frame = !/^\d/.test(errorLines[0])
|
|
122
|
+
? errorLines?.slice(1).join('\n')
|
|
123
|
+
: err[i].frame;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const possibleFilePath = location?.file ?? err[i].id;
|
|
127
|
+
if (possibleFilePath &&
|
|
128
|
+
err[i].loc &&
|
|
129
|
+
(!err[i].frame || !err[i]['fullCode'])) {
|
|
130
|
+
try {
|
|
131
|
+
const fileContents = fs.readFileSync(possibleFilePath, 'utf8');
|
|
132
|
+
if (!err[i].frame) {
|
|
133
|
+
err[i].frame = codeFrame(fileContents, {
|
|
134
|
+
...err[i].loc,
|
|
135
|
+
file: possibleFilePath,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
err[i]['fullCode'] = fileContents;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
err[i]['fullCode'] = err[i].pluginCode;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (pluginName) {
|
|
145
|
+
err[i].plugin = pluginName;
|
|
146
|
+
}
|
|
147
|
+
if (err[0])
|
|
148
|
+
err[i]['hint'] = generateHint(err[0]);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// TODO: Handle returning multiple errors
|
|
152
|
+
// if (err[0]) return err[0];
|
|
153
|
+
return err[0];
|
|
154
|
+
}
|
|
155
|
+
function generateHint(err) {
|
|
156
|
+
const commonBrowserAPIs = ['document', 'window'];
|
|
157
|
+
// if (
|
|
158
|
+
// /Unknown file extension "\.(?:jsx|vue|svelte|astro|css)" for /.test(
|
|
159
|
+
// err.message,
|
|
160
|
+
// )
|
|
161
|
+
// ) {
|
|
162
|
+
// return 'You likely need to add this package to `vite.ssr.noExternal` in your astro config file.';
|
|
163
|
+
// } else if (commonBrowserAPIs.some((api) => err.toString().includes(api))) {
|
|
164
|
+
// const hint = `Browser APIs are not available on the server.
|
|
165
|
+
// ${
|
|
166
|
+
// err.loc?.file?.endsWith('.astro')
|
|
167
|
+
// ? 'Move your code to a <script> tag outside of the frontmatter, so the code runs on the client.'
|
|
168
|
+
// : 'If the code is in a framework component, try to access these objects after rendering using lifecycle methods or use a `client:only` directive to make the component exclusively run on the client.'
|
|
169
|
+
// }
|
|
170
|
+
// See https://docs.astro.build/en/guides/troubleshooting/#document-or-window-is-not-defined for more information.
|
|
171
|
+
// `;
|
|
172
|
+
// return hint;
|
|
173
|
+
// }
|
|
174
|
+
return err.hint;
|
|
175
|
+
}
|
|
176
|
+
function collectInfoFromStacktrace(error) {
|
|
177
|
+
// @ts-expect-error exact optional
|
|
178
|
+
let stackInfo = {
|
|
179
|
+
stack: error.stack,
|
|
180
|
+
plugin: error.plugin,
|
|
181
|
+
pluginCode: error.pluginCode,
|
|
182
|
+
loc: error.loc,
|
|
183
|
+
};
|
|
184
|
+
// normalize error stack line-endings to \n
|
|
185
|
+
stackInfo.stack = normalizeLF(error.stack);
|
|
186
|
+
const stackText = stripAnsi(error.stack);
|
|
187
|
+
// Try to find possible location from stack if we don't have one
|
|
188
|
+
if (!stackInfo.loc || (!stackInfo.loc.column && !stackInfo.loc.line)) {
|
|
189
|
+
const possibleFilePath = error.loc?.file ||
|
|
190
|
+
error.pluginCode ||
|
|
191
|
+
error.id ||
|
|
192
|
+
// TODO: this could be better, `src` might be something else
|
|
193
|
+
stackText
|
|
194
|
+
.split('\n')
|
|
195
|
+
.find((ln) => ln.includes('src') || ln.includes('node_modules'));
|
|
196
|
+
// Disable eslint as we're not sure how to improve this regex yet
|
|
197
|
+
// eslint-disable-next-line regexp/no-super-linear-backtracking
|
|
198
|
+
const source = possibleFilePath
|
|
199
|
+
?.replace?.(/^[^(]+\(([^)]+).*$/, '$1')
|
|
200
|
+
.replace(/^\s+at\s+/, '');
|
|
201
|
+
let file = source?.replace(/:\d+/g, '');
|
|
202
|
+
const location = /:(\d+):(\d+)/.exec(source) ?? [];
|
|
203
|
+
const line = location[1];
|
|
204
|
+
const column = location[2];
|
|
205
|
+
if (file && line && column) {
|
|
206
|
+
try {
|
|
207
|
+
file = fileURLToPath(file);
|
|
208
|
+
}
|
|
209
|
+
catch { }
|
|
210
|
+
stackInfo.loc = {
|
|
211
|
+
file,
|
|
212
|
+
line: Number.parseInt(line),
|
|
213
|
+
column: Number.parseInt(column),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// // Derive plugin from stack (if possible)
|
|
218
|
+
// if (!stackInfo.plugin) {
|
|
219
|
+
// const stack =
|
|
220
|
+
// /withastro\/astro\/packages\/integrations\/([\w-]+)/i
|
|
221
|
+
// .exec(stackText)
|
|
222
|
+
// ?.at(1) ||
|
|
223
|
+
// /(@astrojs\/[\w-]+)\/(server|client|index)/i.exec(stackText)?.at(1);
|
|
224
|
+
// if (stack) stackInfo.plugin = stack;
|
|
225
|
+
// }
|
|
226
|
+
// Normalize stack (remove `/@fs/` urls, etc)
|
|
227
|
+
stackInfo.stack = cleanErrorStack(error.stack);
|
|
228
|
+
return stackInfo;
|
|
229
|
+
}
|
|
230
|
+
function cleanErrorStack(stack) {
|
|
231
|
+
return stack
|
|
232
|
+
.split(/\n/)
|
|
233
|
+
.map((l) => l.replace(/\/@fs\//g, '/'))
|
|
234
|
+
.join('\n');
|
|
235
|
+
}
|
|
236
|
+
export function getDocsForError(err, errorsData, docsBaseUrl) {
|
|
237
|
+
if (err.name !== 'UnknownError' && err.name in errorsData) {
|
|
238
|
+
return `${docsBaseUrl}/${getKebabErrorName(err.name)}/`;
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
/**
|
|
242
|
+
* The docs has kebab-case urls for errors, so we need to convert the error name
|
|
243
|
+
* @param errorName
|
|
244
|
+
*/
|
|
245
|
+
function getKebabErrorName(errorName) {
|
|
246
|
+
return errorName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const linkRegex = /\[([^[]+)\]\((.*)\)/g;
|
|
250
|
+
const boldRegex = /\*\*(.+)\*\*/g;
|
|
251
|
+
const urlRegex = / ((?:https?|ftp):\/\/[-\w+&@#\\/%?=~|!:,.;]*[-\w+&@#\\/%=~|])/gi;
|
|
252
|
+
const codeRegex = /`([^`]+)`/g;
|
|
253
|
+
/**
|
|
254
|
+
* Render a subset of Markdown to HTML or a CLI output
|
|
255
|
+
*/
|
|
256
|
+
export function renderErrorMarkdown(markdown, target) {
|
|
257
|
+
if (target === 'html') {
|
|
258
|
+
// escape()
|
|
259
|
+
return markdown
|
|
260
|
+
.replace(linkRegex, `<a href="$2" target="_blank">$1</a>`)
|
|
261
|
+
.replace(boldRegex, '<b>$1</b>')
|
|
262
|
+
.replace(urlRegex, ' <a href="$1" target="_blank">$1</a>')
|
|
263
|
+
.replace(codeRegex, '<code>$1</code>');
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
return markdown
|
|
267
|
+
.replace(linkRegex, (_, m1, m2) => `${c.bold(m1)} ${c.underline(m2)}`)
|
|
268
|
+
.replace(urlRegex, (fullMatch) => ` ${c.underline(fullMatch.trim())}`)
|
|
269
|
+
.replace(boldRegex, (_, m1) => `${c.bold(m1)}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { BetterErrorData } from './../errors-data.js';
|
|
2
|
+
import type { ErrorPayload, ViteDevServer } from 'vite';
|
|
3
|
+
import type { ErrorWithMetadata } from '../errors.js';
|
|
4
|
+
export declare function enhanceViteSSRError({ error, filePath, vite, }: {
|
|
5
|
+
error: unknown;
|
|
6
|
+
filePath?: URL | undefined;
|
|
7
|
+
vite?: ViteDevServer | undefined;
|
|
8
|
+
}): Error;
|
|
9
|
+
export interface BetterErrorPayload {
|
|
10
|
+
type: ErrorPayload['type'];
|
|
11
|
+
err: Omit<ErrorPayload['err'], 'loc'> & {
|
|
12
|
+
name?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
hint?: string;
|
|
15
|
+
docslink?: string;
|
|
16
|
+
highlightedCode?: string;
|
|
17
|
+
loc?: {
|
|
18
|
+
file?: string;
|
|
19
|
+
line?: number;
|
|
20
|
+
column?: number;
|
|
21
|
+
};
|
|
22
|
+
cause?: unknown;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generate a payload for Vite's error overlay
|
|
27
|
+
*/
|
|
28
|
+
export declare function getViteErrorPayload({ err, errorsData, docsBaseUrl, }: {
|
|
29
|
+
err: ErrorWithMetadata;
|
|
30
|
+
errorsData?: Record<string, BetterErrorData>;
|
|
31
|
+
docsBaseUrl: string;
|
|
32
|
+
}): Promise<BetterErrorPayload>;
|
|
33
|
+
//# sourceMappingURL=vite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../src/dev/vite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AASxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGtD,wBAAgB,mBAAmB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,IAAI,GAEJ,EAAE;IACF,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,GAAG,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAEjC,GAAG,KAAK,CAwFR;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC3B,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG;QACvC,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,GAAG,CAAC,EAAE;YACL,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,MAAM,CAAC,EAAE,MAAM,CAAC;SAChB,CAAC;QACF,KAAK,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACF;AAcD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,EACzC,GAAG,EACH,UAAU,EACV,WAAW,GACX,EAAE;IACF,GAAG,EAAE,iBAAiB,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,WAAW,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAkF9B"}
|
package/dist/dev/vite.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { getDocsForError, createSafeError, renderErrorMarkdown, } from './utils.js';
|
|
5
|
+
import { html, render } from '@lit-labs/ssr';
|
|
6
|
+
import { collectResultSync } from '@lit-labs/ssr/lib/render-result.js';
|
|
7
|
+
import { FailedToLoadModuleSSR } from '../errors-data.js';
|
|
8
|
+
export function enhanceViteSSRError({ error, filePath, vite,
|
|
9
|
+
// renderers,
|
|
10
|
+
}) {
|
|
11
|
+
// NOTE: We don't know where the error that's coming here comes from, so we need to be defensive regarding what we do
|
|
12
|
+
// to it to make sure we keep as much information as possible. It's very possible that we receive an error that does not
|
|
13
|
+
// follow any kind of standard formats (ex: a number, a string etc)
|
|
14
|
+
// let safeError = error as ErrorWithMetadata;
|
|
15
|
+
let safeError = createSafeError(error);
|
|
16
|
+
// Vite will give you better stacktraces, using sourcemaps.
|
|
17
|
+
if (vite) {
|
|
18
|
+
try {
|
|
19
|
+
vite.ssrFixStacktrace(safeError);
|
|
20
|
+
}
|
|
21
|
+
catch { }
|
|
22
|
+
}
|
|
23
|
+
if (filePath) {
|
|
24
|
+
const path = fileURLToPath(filePath);
|
|
25
|
+
const content = fs.readFileSync(path).toString();
|
|
26
|
+
const lns = content.split('\n');
|
|
27
|
+
// Vite has a fairly generic error message when it fails to load a module, let's try to enhance it a bit
|
|
28
|
+
// https://github.com/vitejs/vite/blob/ee7c28a46a6563d54b828af42570c55f16b15d2c/packages/vite/src/node/ssr/ssrModuleLoader.ts#L91
|
|
29
|
+
let importName;
|
|
30
|
+
if ((importName = /Failed to load url (.*?) \(resolved id:/.exec(safeError.message)?.[1])) {
|
|
31
|
+
safeError.title = FailedToLoadModuleSSR.title;
|
|
32
|
+
safeError.name = 'FailedToLoadModuleSSR';
|
|
33
|
+
safeError.message = FailedToLoadModuleSSR.message(importName);
|
|
34
|
+
safeError.hint = FailedToLoadModuleSSR.hint;
|
|
35
|
+
const line = lns.findIndex((ln) => ln.includes(importName));
|
|
36
|
+
console.log({ line });
|
|
37
|
+
if (line !== -1) {
|
|
38
|
+
const column = lns[line]?.indexOf(importName);
|
|
39
|
+
safeError.loc = {
|
|
40
|
+
file: path,
|
|
41
|
+
line: line + 1,
|
|
42
|
+
};
|
|
43
|
+
if (column)
|
|
44
|
+
safeError.loc.column = column;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// const fileId = safeError.id ?? safeError.loc?.file;
|
|
48
|
+
// // Vite throws a syntax error trying to parse MDX without a plugin.
|
|
49
|
+
// // Suggest installing the MDX integration if none is found.
|
|
50
|
+
// if (
|
|
51
|
+
// fileId &&
|
|
52
|
+
// !renderers?.find((r) => r.name === '@astrojs/mdx') &&
|
|
53
|
+
// safeError.message.includes('Syntax error') &&
|
|
54
|
+
// /.mdx$/.test(fileId)
|
|
55
|
+
// ) {
|
|
56
|
+
// safeError = new BetterError({
|
|
57
|
+
// ...MdxIntegrationMissingError,
|
|
58
|
+
// message: MdxIntegrationMissingError.message(JSON.stringify(fileId)),
|
|
59
|
+
// location: safeError.loc,
|
|
60
|
+
// stack: safeError.stack,
|
|
61
|
+
// }) as ErrorWithMetadata;
|
|
62
|
+
// }
|
|
63
|
+
// // Since Astro.glob is a wrapper around Vite's import.meta.glob, errors don't show accurate information, let's fix that
|
|
64
|
+
// if (safeError.message.includes('Invalid glob')) {
|
|
65
|
+
// const globPattern = /glob: "(.+)" \(/.exec(safeError.message)?.[1];
|
|
66
|
+
// if (globPattern) {
|
|
67
|
+
// safeError.message = InvalidGlob.message(globPattern);
|
|
68
|
+
// safeError.name = 'InvalidGlob';
|
|
69
|
+
// safeError.title = InvalidGlob.title;
|
|
70
|
+
// const line = lns.findIndex((ln) => ln.includes(globPattern));
|
|
71
|
+
// if (line !== -1) {
|
|
72
|
+
// const column = lns[line]?.indexOf(globPattern);
|
|
73
|
+
// safeError.loc = {
|
|
74
|
+
// file: path,
|
|
75
|
+
// line: line + 1,
|
|
76
|
+
// column,
|
|
77
|
+
// };
|
|
78
|
+
// }
|
|
79
|
+
// }
|
|
80
|
+
// }
|
|
81
|
+
}
|
|
82
|
+
return safeError;
|
|
83
|
+
}
|
|
84
|
+
// Shiki does not support `mjs` or `cjs` aliases by default.
|
|
85
|
+
// Map these to `.js` during error highlighting.
|
|
86
|
+
const ALTERNATIVE_JS_EXTS = ['cjs', 'mjs'];
|
|
87
|
+
const ALTERNATIVE_MD_EXTS = ['mdoc'];
|
|
88
|
+
// let _cssVariablesTheme: ReturnType<typeof createCssVariablesTheme>;
|
|
89
|
+
// const cssVariablesTheme = () =>
|
|
90
|
+
// _cssVariablesTheme ??
|
|
91
|
+
// (_cssVariablesTheme = createCssVariablesTheme({
|
|
92
|
+
// variablePrefix: '--astro-code-',
|
|
93
|
+
// }));
|
|
94
|
+
/**
|
|
95
|
+
* Generate a payload for Vite's error overlay
|
|
96
|
+
*/
|
|
97
|
+
export async function getViteErrorPayload({ err, errorsData, docsBaseUrl, }) {
|
|
98
|
+
let plugin = err.plugin;
|
|
99
|
+
if (!plugin && err.hint) {
|
|
100
|
+
plugin = 'astro';
|
|
101
|
+
}
|
|
102
|
+
const message = renderErrorMarkdown(err.message.trim(), 'html');
|
|
103
|
+
const hint = err.hint
|
|
104
|
+
? renderErrorMarkdown(err.hint.trim(), 'html')
|
|
105
|
+
: undefined;
|
|
106
|
+
const docslink = errorsData
|
|
107
|
+
? getDocsForError(err, errorsData, docsBaseUrl)
|
|
108
|
+
: undefined;
|
|
109
|
+
// let highlighterLang = err.loc?.file?.split('.').pop();
|
|
110
|
+
// if (ALTERNATIVE_JS_EXTS.includes(highlighterLang ?? '')) {
|
|
111
|
+
// highlighterLang = 'js';
|
|
112
|
+
// } else if (ALTERNATIVE_MD_EXTS.includes(highlighterLang ?? '')) {
|
|
113
|
+
// highlighterLang = 'md';
|
|
114
|
+
// }
|
|
115
|
+
// const highlightedCode = err.fullCode
|
|
116
|
+
// ? await codeToHtml(err.fullCode, {
|
|
117
|
+
// lang: highlighterLang ?? 'text',
|
|
118
|
+
// theme: cssVariablesTheme(),
|
|
119
|
+
// transformers: [
|
|
120
|
+
// transformerCompactLineOptions(
|
|
121
|
+
// err.loc?.line
|
|
122
|
+
// ? [{ line: err.loc.line, classes: ['error-line'] }]
|
|
123
|
+
// : undefined,
|
|
124
|
+
// ),
|
|
125
|
+
// ],
|
|
126
|
+
// })
|
|
127
|
+
// : undefined;
|
|
128
|
+
const highlightedCode = err.fullCode
|
|
129
|
+
? collectResultSync(render(html `
|
|
130
|
+
<div id="code-content">
|
|
131
|
+
<pre
|
|
132
|
+
class="shiki css-variables"
|
|
133
|
+
style="background-color:var(--betterr-code-background);color:var(--betterr-code-foreground)"
|
|
134
|
+
tabindex="0"
|
|
135
|
+
><code>${err.fullCode
|
|
136
|
+
.split('\n')
|
|
137
|
+
.map((l, i) => html `<span
|
|
138
|
+
class="line ${err.loc?.line === i + 1
|
|
139
|
+
? 'error-line'
|
|
140
|
+
: ''} "
|
|
141
|
+
>${l}</span
|
|
142
|
+
>${'\n'}`)}</code></pre>
|
|
143
|
+
</div>
|
|
144
|
+
`))
|
|
145
|
+
: undefined;
|
|
146
|
+
return {
|
|
147
|
+
type: 'error',
|
|
148
|
+
err: {
|
|
149
|
+
...err,
|
|
150
|
+
name: err.name,
|
|
151
|
+
type: err.type,
|
|
152
|
+
message,
|
|
153
|
+
hint,
|
|
154
|
+
frame: err.frame,
|
|
155
|
+
highlightedCode,
|
|
156
|
+
docslink,
|
|
157
|
+
// @ts-expect-error Exact optional props
|
|
158
|
+
loc: {
|
|
159
|
+
file: err.loc?.file,
|
|
160
|
+
line: err.loc?.line,
|
|
161
|
+
column: err.loc?.column,
|
|
162
|
+
},
|
|
163
|
+
plugin,
|
|
164
|
+
stack: err.stack,
|
|
165
|
+
cause: err.cause,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// /**
|
|
170
|
+
// * Transformer for `shiki`'s legacy `lineOptions`, allows to add classes to specific lines
|
|
171
|
+
// * FROM: https://github.com/shikijs/shiki/blob/4a58472070a9a359a4deafec23bb576a73e24c6a/packages/transformers/src/transformers/compact-line-options.ts
|
|
172
|
+
// * LICENSE: https://github.com/shikijs/shiki/blob/4a58472070a9a359a4deafec23bb576a73e24c6a/LICENSE
|
|
173
|
+
// */
|
|
174
|
+
// function transformerCompactLineOptions(
|
|
175
|
+
// lineOptions: {
|
|
176
|
+
// /**
|
|
177
|
+
// * 1-based line number.
|
|
178
|
+
// */
|
|
179
|
+
// line: number;
|
|
180
|
+
// classes?: string[];
|
|
181
|
+
// }[] = [],
|
|
182
|
+
// ): ShikiTransformer {
|
|
183
|
+
// return {
|
|
184
|
+
// name: '@shikijs/transformers:compact-line-options',
|
|
185
|
+
// line(node, line) {
|
|
186
|
+
// const lineOption = lineOptions.find((o) => o.line === line);
|
|
187
|
+
// if (lineOption?.classes) this.addClassToHast(node, lineOption.classes);
|
|
188
|
+
// return node;
|
|
189
|
+
// },
|
|
190
|
+
// };
|
|
191
|
+
// }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface BetterErrorData {
|
|
2
|
+
name: string;
|
|
3
|
+
title: string;
|
|
4
|
+
message?: string | ((...params: any) => string) | undefined;
|
|
5
|
+
hint?: string | ((...params: any) => string) | undefined;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
export declare const FailedToLoadModuleSSR: {
|
|
11
|
+
name: string;
|
|
12
|
+
title: string;
|
|
13
|
+
message: (importName: string) => string;
|
|
14
|
+
hint: string;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=errors-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-data.d.ts","sourceRoot":"","sources":["../src/errors-data.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;IAC5D,IAAI,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;CACzD;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;0BAGX,MAAM;;CAEF,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
*/
|
|
5
|
+
export const FailedToLoadModuleSSR = {
|
|
6
|
+
name: 'FailedToLoadModuleSSR',
|
|
7
|
+
title: 'Could not import file.',
|
|
8
|
+
message: (importName) => `Could not import \`${importName}\`.`,
|
|
9
|
+
hint: 'This is often caused by a typo in the import path. Please make sure the file exists.',
|
|
10
|
+
};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ErrorPayload } from 'vite';
|
|
2
|
+
export type BuiltinErrorTypes = 'BetterError' | 'InternalError' | 'AggregateError';
|
|
3
|
+
/**
|
|
4
|
+
* Generic object representing an error with all possible data
|
|
5
|
+
* Compatible with Gracile, Astro's and Vite's errors
|
|
6
|
+
*/
|
|
7
|
+
export interface ErrorWithMetadata<T extends string = string> {
|
|
8
|
+
[name: string]: unknown;
|
|
9
|
+
name: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
type?: BuiltinErrorTypes & T;
|
|
12
|
+
message: string;
|
|
13
|
+
stack: string;
|
|
14
|
+
hint?: string;
|
|
15
|
+
id?: string;
|
|
16
|
+
frame?: string;
|
|
17
|
+
plugin?: string;
|
|
18
|
+
pluginCode?: string;
|
|
19
|
+
fullCode?: string;
|
|
20
|
+
loc?: {
|
|
21
|
+
file?: string;
|
|
22
|
+
line?: number;
|
|
23
|
+
column?: number;
|
|
24
|
+
};
|
|
25
|
+
cause?: unknown;
|
|
26
|
+
}
|
|
27
|
+
interface ErrorProperties {
|
|
28
|
+
title?: string | undefined;
|
|
29
|
+
name: string;
|
|
30
|
+
message?: string | undefined;
|
|
31
|
+
location?: ErrorLocation | undefined;
|
|
32
|
+
hint?: string | undefined;
|
|
33
|
+
stack?: string | undefined;
|
|
34
|
+
frame?: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
export interface ErrorLocation {
|
|
37
|
+
file?: string | undefined;
|
|
38
|
+
line?: number | undefined;
|
|
39
|
+
column?: number | undefined;
|
|
40
|
+
}
|
|
41
|
+
export declare class BetterError<ConsumerErrorTypes extends string = string> extends Error {
|
|
42
|
+
loc: ErrorLocation | undefined;
|
|
43
|
+
title: string | undefined;
|
|
44
|
+
hint: string | undefined;
|
|
45
|
+
frame: string | undefined;
|
|
46
|
+
type: BuiltinErrorTypes | ConsumerErrorTypes;
|
|
47
|
+
constructor(props: ErrorProperties, options?: ErrorOptions);
|
|
48
|
+
setLocation(location: ErrorLocation): void;
|
|
49
|
+
setName(name: string): void;
|
|
50
|
+
setMessage(message: string): void;
|
|
51
|
+
setHint(hint: string): void;
|
|
52
|
+
setFrame(source: string, location: ErrorLocation): void;
|
|
53
|
+
static is(err: unknown): err is BetterError;
|
|
54
|
+
}
|
|
55
|
+
export declare class AggregateError extends BetterError {
|
|
56
|
+
type: BuiltinErrorTypes;
|
|
57
|
+
errors: BetterError[];
|
|
58
|
+
constructor(props: ErrorProperties & {
|
|
59
|
+
errors: BetterError[];
|
|
60
|
+
}, options?: ErrorOptions);
|
|
61
|
+
static is(err: unknown): err is AggregateError;
|
|
62
|
+
}
|
|
63
|
+
export declare function isBetterError(e: unknown): e is BetterError;
|
|
64
|
+
export type SSRError = Error & ErrorPayload['err'];
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { codeFrame } from './dev/printer.js';
|
|
2
|
+
export class BetterError extends Error {
|
|
3
|
+
constructor(props, options) {
|
|
4
|
+
const { name, title, message, stack, location, hint, frame } = props;
|
|
5
|
+
super(message, options);
|
|
6
|
+
this.type = 'BetterError';
|
|
7
|
+
this.title = title;
|
|
8
|
+
this.name = name;
|
|
9
|
+
if (message)
|
|
10
|
+
this.message = message;
|
|
11
|
+
// Only set this if we actually have a stack passed, otherwise uses Error's
|
|
12
|
+
if (stack)
|
|
13
|
+
this.stack = stack;
|
|
14
|
+
this.loc = location;
|
|
15
|
+
this.hint = hint;
|
|
16
|
+
this.frame = frame;
|
|
17
|
+
}
|
|
18
|
+
setLocation(location) {
|
|
19
|
+
this.loc = location;
|
|
20
|
+
}
|
|
21
|
+
setName(name) {
|
|
22
|
+
this.name = name;
|
|
23
|
+
}
|
|
24
|
+
setMessage(message) {
|
|
25
|
+
this.message = message;
|
|
26
|
+
}
|
|
27
|
+
setHint(hint) {
|
|
28
|
+
this.hint = hint;
|
|
29
|
+
}
|
|
30
|
+
setFrame(source, location) {
|
|
31
|
+
this.frame = codeFrame(source, location);
|
|
32
|
+
}
|
|
33
|
+
static is(err) {
|
|
34
|
+
return err.type === 'BetterError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class AggregateError extends BetterError {
|
|
38
|
+
// Despite being a collection of errors, AggregateError still needs to have a main error attached to it
|
|
39
|
+
// This is because Vite expects every thrown errors handled during HMR to be, well, Error and have a message
|
|
40
|
+
constructor(props, options) {
|
|
41
|
+
super(props, options);
|
|
42
|
+
this.type = 'AggregateError';
|
|
43
|
+
this.errors = props.errors;
|
|
44
|
+
}
|
|
45
|
+
static is(err) {
|
|
46
|
+
return err.type === 'AggregateError';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function isBetterError(e) {
|
|
50
|
+
return e instanceof BetterError;
|
|
51
|
+
}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAeA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,UA6B7D;AAED,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE;IACtC,iBAAiB,EAAE,MAAM,CAAC;CAK1B,GAAG,GAAG,EAAE,CAgER"}
|