@vesk/adapter 0.2.26 → 0.2.28
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/dist/client-bundle.d.ts.map +1 -1
- package/dist/client-bundle.js +12 -18
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +22 -2
- package/dist/error-codeframe.d.ts +2 -0
- package/dist/error-codeframe.d.ts.map +1 -1
- package/dist/error-codeframe.js +4 -0
- package/dist/hmr.d.ts +2 -0
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +9 -0
- package/dist/ssr-error.d.ts +92 -0
- package/dist/ssr-error.d.ts.map +1 -0
- package/dist/ssr-error.js +482 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAmF,MAAM,yBAAyB,CAAC;AAoCnL;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA8BhE;AAqVD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBxD;AAmCD,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,SAAS,EAAE,EACtB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAma7B;AAMD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA+B3D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAUlE;AAYD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuCrG"}
|
package/dist/client-bundle.js
CHANGED
|
@@ -4,10 +4,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { build } from './esbuild-fallback.js';
|
|
5
5
|
import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
|
|
6
6
|
import { parse } from '@vesk/compiler/src/parser';
|
|
7
|
-
import { compileClient, compileClientBoth } from '@vesk/compiler/src/client-codegen';
|
|
7
|
+
import { compileClient, compileClientBoth, nameAllocFor } from '@vesk/compiler/src/client-codegen';
|
|
8
8
|
import { resolveComponentName } from '@vesk/compiler/src/server-codegen';
|
|
9
9
|
import { collectVskImportPaths, vskImportLines } from '@vesk/compiler/src/vsk-imports';
|
|
10
10
|
import { inlineMdContentAttrs, guessProjectRoots } from '@vesk/compiler/src/md-inline';
|
|
11
|
+
import { resolveImportPath, findTsconfigPath } from '@vesk/compiler/src/module-imports';
|
|
11
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
function fileUnchanged(filePath, cached) {
|
|
13
14
|
try {
|
|
@@ -207,20 +208,6 @@ function demoteExports(code) {
|
|
|
207
208
|
function chunkHasImports(headLen, mergeAborted) {
|
|
208
209
|
return headLen > 0 || mergeAborted;
|
|
209
210
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Rewrites a compiled file's source specifier so esbuild can resolve it from
|
|
212
|
-
* the temp bundle entry. Bare package specifiers (e.g. `lucide-vesk`) stay
|
|
213
|
-
* as-is; relative specifiers are resolved to absolute filesystem paths with a
|
|
214
|
-
* `.ts` extension appended so esbuild loads the module as TypeScript and
|
|
215
|
-
* inlines its exported values. Offset-based on the parser AST — never a text
|
|
216
|
-
* scan.
|
|
217
|
-
*/
|
|
218
|
-
function resolveImportSource(src, filePath) {
|
|
219
|
-
if (src.startsWith('./') || src.startsWith('../')) {
|
|
220
|
-
return resolve(dirname(filePath), src) + '.ts';
|
|
221
|
-
}
|
|
222
|
-
return src;
|
|
223
|
-
}
|
|
224
211
|
/**
|
|
225
212
|
* Accumulates every import a code-split chunk needs and rebuilds it as a
|
|
226
213
|
* single deduped import block.
|
|
@@ -273,7 +260,7 @@ class ChunkImports {
|
|
|
273
260
|
const src = node.source?.value;
|
|
274
261
|
if (typeof src !== 'string')
|
|
275
262
|
continue;
|
|
276
|
-
const resolved =
|
|
263
|
+
const resolved = resolveImportPath(src, dirname(filePath));
|
|
277
264
|
for (const spec of node.specifiers ?? []) {
|
|
278
265
|
const s = spec;
|
|
279
266
|
const local = s.local?.name;
|
|
@@ -793,6 +780,9 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
|
|
|
793
780
|
const toBundle = `const __components = globalThis.__components || (globalThis.__components = {});\nconst __hydrators = globalThis.__hydrators || (globalThis.__hydrators = {});\n${entry.code}\n`;
|
|
794
781
|
writeFileSync(tmpFile, toBundle);
|
|
795
782
|
try {
|
|
783
|
+
// Pass the app tsconfig so esbuild resolves tsconfig `paths` aliases
|
|
784
|
+
// (`@/x`, `@app/x`) inside transitively bundled modules too.
|
|
785
|
+
const appTsconfig = findTsconfigPath(appDir);
|
|
796
786
|
const result = await build({
|
|
797
787
|
entryPoints: [tmpFile],
|
|
798
788
|
bundle: true,
|
|
@@ -801,6 +791,7 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
|
|
|
801
791
|
write: false,
|
|
802
792
|
logLevel: 'silent',
|
|
803
793
|
loader: { '.js': 'tsx' },
|
|
794
|
+
...(appTsconfig ? { tsconfig: appTsconfig } : {}),
|
|
804
795
|
});
|
|
805
796
|
finalCode = result.outputFiles[0].text;
|
|
806
797
|
}
|
|
@@ -863,8 +854,11 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
|
|
|
863
854
|
seen.add(filePath);
|
|
864
855
|
const src = readFileSync(filePath, 'utf-8');
|
|
865
856
|
resolveVskImports(filePath, (p, n) => compileFileMono(p, n || ''));
|
|
866
|
-
|
|
867
|
-
|
|
857
|
+
// Share one name allocator so the comp and hyd contributions (which are
|
|
858
|
+
// joined into a single scoped block below) never reuse `$n` names.
|
|
859
|
+
const monoAlloc = nameAllocFor(filePath);
|
|
860
|
+
const compCode = compileClient(src, null, { forceClient: true, sourcePath: filePath, nameAllocator: monoAlloc });
|
|
861
|
+
const hydCode = compileClient(src, null, { hydrate: true, forceClient: true, includeTopLevel: false, sourcePath: filePath, nameAllocator: monoAlloc });
|
|
868
862
|
if (compCode)
|
|
869
863
|
collectRuntimeImports(compCode);
|
|
870
864
|
if (hydCode)
|
package/dist/dev-server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAOjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAOjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAK7C,OAAO,KAAK,EAAa,gBAAgB,EAAwB,MAAM,yBAAyB,CAAC;AAgBjG;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,IAAI,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;IAC9C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxD,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB;+EAC2E;IAC3E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,IAAI,WAAW,CAEhD;AAuCD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AAkBD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,mHAAmH;IACnH,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B;;8BAE0B;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,WAAW,CAAC;IAChC,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,GAAG;IACvE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CAChH,CAUA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAa3C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrD;AA8CD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA2oBvG"}
|
package/dist/dev-server.js
CHANGED
|
@@ -8,6 +8,7 @@ import { build } from '@vesk/adapter/src/index';
|
|
|
8
8
|
import { buildErrorPayload, createHmrServer } from './hmr';
|
|
9
9
|
import * as hmrApi from './hmr';
|
|
10
10
|
import { createDevApiRouter } from './dev-api';
|
|
11
|
+
import { resolveRuntimeErrorPayload, renderDevErrorPage } from './ssr-error';
|
|
11
12
|
import { buildTreeShakenRuntime, runtimeExportNames } from '@vesk/adapter/src/client-bundle';
|
|
12
13
|
import { resolveWithin, installMdReadHook } from '@vesk/adapter/src/paths';
|
|
13
14
|
import { getPluginRecords } from './plugins';
|
|
@@ -359,6 +360,25 @@ export async function startDevServer(appDir, options) {
|
|
|
359
360
|
let routeTree = [];
|
|
360
361
|
let runtimeBundle = '';
|
|
361
362
|
let pendingInitialError = null;
|
|
363
|
+
/**
|
|
364
|
+
* Render an SSR failure as the unified dev error page (same payload shape
|
|
365
|
+
* the HMR overlay shows): the `.vsk`/`.ts` source site, codeframe, tips,
|
|
366
|
+
* and the `VeskError` code when present — never a bare `500` page. Best
|
|
367
|
+
* effort: on any failure falls back to the old generic 500 HTML.
|
|
368
|
+
*/
|
|
369
|
+
function renderSsrErrorPage(e, pathname) {
|
|
370
|
+
try {
|
|
371
|
+
const payload = resolveRuntimeErrorPayload(e, {
|
|
372
|
+
appDir,
|
|
373
|
+
projectDir: resolve(appDir, '..'),
|
|
374
|
+
componentMap,
|
|
375
|
+
});
|
|
376
|
+
return injectDevScripts(renderDevErrorPage(payload, { status: 500, url: pathname }));
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return '<!DOCTYPE html><html><body><h1>500</h1><pre>Internal Server Error</pre></body></html>';
|
|
380
|
+
}
|
|
381
|
+
}
|
|
362
382
|
let eventsMod = null;
|
|
363
383
|
async function reloadEventsMod() {
|
|
364
384
|
const eventsPath = resolve(devDir, 'server', 'events.js');
|
|
@@ -671,7 +691,7 @@ export async function startDevServer(appDir, options) {
|
|
|
671
691
|
}
|
|
672
692
|
catch (e) {
|
|
673
693
|
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
674
|
-
res.end(
|
|
694
|
+
res.end(renderSsrErrorPage(e, url.pathname));
|
|
675
695
|
}
|
|
676
696
|
return;
|
|
677
697
|
}
|
|
@@ -698,7 +718,7 @@ export async function startDevServer(appDir, options) {
|
|
|
698
718
|
}
|
|
699
719
|
catch (e) {
|
|
700
720
|
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
701
|
-
res.end(
|
|
721
|
+
res.end(renderSsrErrorPage(e, url.pathname));
|
|
702
722
|
}
|
|
703
723
|
return;
|
|
704
724
|
}
|
|
@@ -18,6 +18,8 @@ export interface ErroredLocation {
|
|
|
18
18
|
message: string;
|
|
19
19
|
stack: string | null;
|
|
20
20
|
codeframe: Codeframe | null;
|
|
21
|
+
/** VeskError-style `.code` (e.g. `V0412`) when the thrown error carries one. */
|
|
22
|
+
code?: string;
|
|
21
23
|
}
|
|
22
24
|
export declare function parseCompilerError(err: unknown, file: string, src?: string): ErroredLocation | null;
|
|
23
25
|
//# sourceMappingURL=error-codeframe.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-codeframe.d.ts","sourceRoot":"","sources":["../src/error-codeframe.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,EAAE,CAAC;CAClB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,SAAS,GAAG,IAAI,CAcxG;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"error-codeframe.d.ts","sourceRoot":"","sources":["../src/error-codeframe.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,EAAE,CAAC;CAClB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,SAAS,GAAG,IAAI,CAcxG;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAuBD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CA4FnG"}
|
package/dist/error-codeframe.js
CHANGED
|
@@ -116,6 +116,9 @@ export function parseCompilerError(err, file, src) {
|
|
|
116
116
|
if (codeframe)
|
|
117
117
|
codeframe.file = fileResolved;
|
|
118
118
|
}
|
|
119
|
+
let code;
|
|
120
|
+
if (record && typeof record.code === 'string' && record.code)
|
|
121
|
+
code = record.code;
|
|
119
122
|
return {
|
|
120
123
|
file: fileResolved,
|
|
121
124
|
line,
|
|
@@ -123,5 +126,6 @@ export function parseCompilerError(err, file, src) {
|
|
|
123
126
|
message,
|
|
124
127
|
stack,
|
|
125
128
|
codeframe,
|
|
129
|
+
code,
|
|
126
130
|
};
|
|
127
131
|
}
|
package/dist/hmr.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface HmrErrorPayload {
|
|
|
17
17
|
suggestions?: string[];
|
|
18
18
|
nextSteps?: string[];
|
|
19
19
|
stack?: string;
|
|
20
|
+
/** Optional VeskError-style code (e.g. `V0412`) carried on the thrown error. */
|
|
21
|
+
code?: string;
|
|
20
22
|
}
|
|
21
23
|
export declare function getHmrState(): {
|
|
22
24
|
status: 'up' | 'closed';
|
package/dist/hmr.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAUxC,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEvG,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,yBAAyB,CAAC;AAIzE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../src/hmr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAUxC,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEvG,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,yBAAyB,CAAC;AAIzE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAaD,wBAAgB,WAAW,IAAI;IAC7B,MAAM,EAAE,IAAI,GAAG,QAAQ,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAQA;AAED,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,wBAA6B,GAClC,eAAe,CAsGjB;AAwRD,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC;IACD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAClE,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvH,WAAW,EAAE,MAAM,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;CACnD,CA6KA"}
|
package/dist/hmr.js
CHANGED
|
@@ -103,6 +103,13 @@ export function buildErrorPayload(error, filename, opts = {}) {
|
|
|
103
103
|
const filePath = resolvedAbsPath ?? (typeof opts.appDir === 'string'
|
|
104
104
|
? resolve(opts.appDir, file)
|
|
105
105
|
: undefined);
|
|
106
|
+
// VeskError throws carry a stable `.code` (e.g. `V0412`) — surface it in the
|
|
107
|
+
// payload so the HMR overlay, SSR error page, and diagnostics can show it.
|
|
108
|
+
const errorCode = parsed && typeof parsed.code === 'string' && parsed.code
|
|
109
|
+
? parsed.code
|
|
110
|
+
: error?.code
|
|
111
|
+
? String(error.code)
|
|
112
|
+
: undefined;
|
|
106
113
|
// The file header already shows the location; strip the redundant
|
|
107
114
|
// " in <file>" suffix compilers append to messages (e.g. acorn's
|
|
108
115
|
// "Unexpected token in /abs/app/page.vsk").
|
|
@@ -117,6 +124,8 @@ export function buildErrorPayload(error, filename, opts = {}) {
|
|
|
117
124
|
column,
|
|
118
125
|
message: cleanMessage,
|
|
119
126
|
};
|
|
127
|
+
if (errorCode)
|
|
128
|
+
payload.code = errorCode;
|
|
120
129
|
if (filePath)
|
|
121
130
|
payload.filePath = filePath;
|
|
122
131
|
if (codeframe)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type HmrErrorPayload } from './hmr';
|
|
2
|
+
export interface RuntimeErrorContext {
|
|
3
|
+
/** Absolute path of the app dir (`.../app`). Used to resolve sources for codeframes. */
|
|
4
|
+
appDir: string;
|
|
5
|
+
/** Absolute path of the project root (`.../`, parent of `app/`). Defaults to `resolve(appDir, '..')`. */
|
|
6
|
+
projectDir?: string;
|
|
7
|
+
/** Absolute `.vsk` files for the failing route (page first). Scanned for the failing identifier. */
|
|
8
|
+
routeFiles?: string[];
|
|
9
|
+
/** `componentName -> absolute .vsk path` for shared `components/` (from `scanComponents`). */
|
|
10
|
+
componentMap?: Map<string, string>;
|
|
11
|
+
/** `componentName -> absolute .vsk path` for route files (from `collectSources`). */
|
|
12
|
+
routeSources?: Map<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export interface ClientErrorReport {
|
|
15
|
+
message?: unknown;
|
|
16
|
+
stack?: unknown;
|
|
17
|
+
/** Compiled-bundle URL/path from `window.onerror` — display fallback only, never the payload file. */
|
|
18
|
+
filename?: unknown;
|
|
19
|
+
line?: unknown;
|
|
20
|
+
column?: unknown;
|
|
21
|
+
/** VeskError-style code (e.g. `V0412`) when the client error carried one. */
|
|
22
|
+
code?: unknown;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extract the undeclared identifier from `ReferenceError`-style messages:
|
|
26
|
+
* `Menu is not defined`, `ReferenceError: Menu is not defined`,
|
|
27
|
+
* `Cannot find name 'Menu'`, `Can't find variable: Menu`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function extractUndefinedName(message: string): string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Component names from a compiled render stack, innermost frame first:
|
|
32
|
+
* `__components.SiteHeader`, `__components["SiteHeader"]`, `__components['Home']`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractComponentNames(stack: string): string[];
|
|
35
|
+
export interface StackFrame {
|
|
36
|
+
/** Path or URL as it appears in the stack frame (may be a bundle URL). */
|
|
37
|
+
path: string;
|
|
38
|
+
line: number;
|
|
39
|
+
column: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse V8-style stack lines into `path:line:col` frames (1-based column,
|
|
43
|
+
* matching the codeframe caret). Supports both parenthesized frames
|
|
44
|
+
* (`at fn (/abs/file.ts:12:5)`) and bare path frames (`at file.ts:12:5`).
|
|
45
|
+
*/
|
|
46
|
+
export declare function extractSourceFrames(stack: string | undefined | null): StackFrame[];
|
|
47
|
+
/**
|
|
48
|
+
* First stack frame that names a real source file inside the project
|
|
49
|
+
* (`.ts`/`.tsx`/`.js`/`.vsk`, existing on disk, not node_modules/dist/.vesk
|
|
50
|
+
* or the dev bundle). Innermost (topmost) real frame is the throw site —
|
|
51
|
+
* e.g. `ReferenceError` inside `lib/helper.ts` imported by a `.vsk` page.
|
|
52
|
+
*/
|
|
53
|
+
export declare function firstSourceFileFrame(stack: string | undefined | null, projectRoot: string): StackFrame | null;
|
|
54
|
+
/**
|
|
55
|
+
* First use of an identifier in a `.vsk` source file (1-based line/column).
|
|
56
|
+
* Import lines are skipped — an undeclared name's *use* site is the error site.
|
|
57
|
+
*/
|
|
58
|
+
export declare function findIdentifierUsage(src: string, name: string): {
|
|
59
|
+
line: number;
|
|
60
|
+
column: number;
|
|
61
|
+
} | null;
|
|
62
|
+
/** Display path for a payload: app-relative (`page.vsk`), else project-relative (`components/X.vsk`). */
|
|
63
|
+
export declare function displayPath(absPath: string, ctx: RuntimeErrorContext): string;
|
|
64
|
+
/** Ordered absolute candidate files: stack-named components first, then route files. */
|
|
65
|
+
export declare function candidateFiles(message: string, stack: string | undefined, ctx: RuntimeErrorContext): string[];
|
|
66
|
+
/**
|
|
67
|
+
* Resolve a server-side throw to the HMR payload shape. Never throws — on any
|
|
68
|
+
* failure returns the message + stack + generic tips (the old behavior, but in
|
|
69
|
+
* the overlay-renderable shape).
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveRuntimeErrorPayload(err: unknown, ctx: RuntimeErrorContext): HmrErrorPayload;
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a client-side `{ message, stack }` report (compiled-bundle
|
|
74
|
+
* coordinates) to the HMR payload shape. Same core as the server path, but
|
|
75
|
+
* stack-named components outrank route files — the throw site is named by the
|
|
76
|
+
* `__components.Foo` frames, as in `ReferenceError: Menu is not defined` from
|
|
77
|
+
* `__components.SiteHeader` inside `__components.Home`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveClientErrorReport(report: ClientErrorReport, ctx: RuntimeErrorContext): HmrErrorPayload;
|
|
80
|
+
/** `file:line:col` (or `file`, or `''`) for one-line terminal logs. */
|
|
81
|
+
export declare function formatErrorLocation(payload: HmrErrorPayload): string;
|
|
82
|
+
/**
|
|
83
|
+
* Standalone dev 500 page in the overlay's visual language (file header,
|
|
84
|
+
* message, codeframe with caret, TIPS/SUGGESTIONS/NEXT STEPS, collapsible
|
|
85
|
+
* stack). The HTML keeps a `</body>` so callers can run `injectDevScripts`
|
|
86
|
+
* over it — the HMR client then pops the same payload as an overlay on load.
|
|
87
|
+
*/
|
|
88
|
+
export declare function renderDevErrorPage(payload: HmrErrorPayload, opts?: {
|
|
89
|
+
status?: number;
|
|
90
|
+
url?: string;
|
|
91
|
+
}): string;
|
|
92
|
+
//# sourceMappingURL=ssr-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr-error.d.ts","sourceRoot":"","sources":["../src/ssr-error.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,OAAO,CAAC;AAEhE,MAAM,WAAW,mBAAmB;IAClC,wFAAwF;IACxF,MAAM,EAAE,MAAM,CAAC;IACf,yGAAyG;IACzG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oGAAoG;IACpG,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,qFAAqF;IACrF,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sGAAsG;IACtG,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAkBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqBnE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA4B7D;AAED,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AA8BD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,UAAU,EAAE,CAiBlF;AAoBD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAChC,WAAW,EAAE,MAAM,GAClB,UAAU,GAAG,IAAI,CAiBnB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAmBtG;AAMD,yGAAyG;AACzG,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAM7E;AA0BD,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAe7G;AAgBD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,mBAAmB,GAAG,eAAe,CAsDlG;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,EAAE,mBAAmB,GAAG,eAAe,CAM7G;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAMpE;AAUD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,eAAe,EACxB,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC,MAAM,CAuER"}
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only runtime-error → HMR-payload resolver.
|
|
3
|
+
*
|
|
4
|
+
* HMR compile errors already report through the canonical `HmrErrorPayload`
|
|
5
|
+
* (file + line/column + codeframe + tips/suggestions/nextSteps + stack,
|
|
6
|
+
* rendered by the client's error overlay). SSR render failures and client
|
|
7
|
+
* runtime failures historically did NOT: the dev server swallowed them into a
|
|
8
|
+
* generic `<h1>500</h1>` page (or a raw `<pre>` stack dump), and the client
|
|
9
|
+
* overlay showed compiled-bundle coordinates (`page-index.js:833`) that point
|
|
10
|
+
* nowhere in user source.
|
|
11
|
+
*
|
|
12
|
+
* This module closes that gap. Given a thrown value (or a client-side
|
|
13
|
+
* `{ message, stack }` report) plus the dev server's knowledge of the app
|
|
14
|
+
* (route files, shared `components/`, route/component name maps), it resolves
|
|
15
|
+
* the failure back to a `.vsk` file/line and returns the SAME payload shape
|
|
16
|
+
* the HMR channel broadcasts — so SSR 500s and client `ReferenceError`s render
|
|
17
|
+
* in the same overlay with the same codeframe + tips.
|
|
18
|
+
*
|
|
19
|
+
* Resolution strategy (best-effort, never throws):
|
|
20
|
+
* 1. Errors that already carry a location (VeskError/acorn `.loc`,
|
|
21
|
+
* `.position`, `.line`) go straight through `buildErrorPayload`.
|
|
22
|
+
* 2. `X is not defined` (and the `Cannot find name 'X'` / Safari
|
|
23
|
+
* `Can't find variable: X` variants): the identifier plus the
|
|
24
|
+
* `__components.Foo` frames in the stack name the exact `.vsk` files to
|
|
25
|
+
* scan; the first use of the identifier in the best candidate yields the
|
|
26
|
+
* line/column for the codeframe.
|
|
27
|
+
* 3. Other errors with `__components.Foo` frames: the payload at least names
|
|
28
|
+
* the innermost `.vsk` file instead of the compiled bundle.
|
|
29
|
+
* 4. Anything else: message + stack + generic tips, same as before — but in
|
|
30
|
+
* the HMR payload shape so the overlay still renders it.
|
|
31
|
+
*/
|
|
32
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
33
|
+
import { relative, resolve } from 'node:path';
|
|
34
|
+
import { buildCodeframe } from './error-codeframe';
|
|
35
|
+
import { buildErrorPayload } from './hmr';
|
|
36
|
+
function isIdentStart(ch) {
|
|
37
|
+
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_' || ch === '$';
|
|
38
|
+
}
|
|
39
|
+
function isIdentPart(ch) {
|
|
40
|
+
return isIdentStart(ch) || (ch >= '0' && ch <= '9');
|
|
41
|
+
}
|
|
42
|
+
function takeIdentifier(s) {
|
|
43
|
+
if (!s)
|
|
44
|
+
return null;
|
|
45
|
+
if (!isIdentStart(s.charAt(0)))
|
|
46
|
+
return null;
|
|
47
|
+
let i = 1;
|
|
48
|
+
while (i < s.length && isIdentPart(s.charAt(i)))
|
|
49
|
+
i++;
|
|
50
|
+
return s.slice(0, i);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract the undeclared identifier from `ReferenceError`-style messages:
|
|
54
|
+
* `Menu is not defined`, `ReferenceError: Menu is not defined`,
|
|
55
|
+
* `Cannot find name 'Menu'`, `Can't find variable: Menu`.
|
|
56
|
+
*/
|
|
57
|
+
export function extractUndefinedName(message) {
|
|
58
|
+
if (typeof message !== 'string' || !message)
|
|
59
|
+
return null;
|
|
60
|
+
const notDefined = message.indexOf(' is not defined');
|
|
61
|
+
if (notDefined > 0) {
|
|
62
|
+
const head = message.slice(0, notDefined);
|
|
63
|
+
let i = head.length - 1;
|
|
64
|
+
while (i >= 0 && isIdentPart(head.charAt(i)))
|
|
65
|
+
i--;
|
|
66
|
+
return takeIdentifier(head.slice(i + 1));
|
|
67
|
+
}
|
|
68
|
+
const findName = message.indexOf("Cannot find name '");
|
|
69
|
+
if (findName >= 0) {
|
|
70
|
+
const rest = message.slice(findName + "Cannot find name '".length);
|
|
71
|
+
const end = rest.indexOf("'");
|
|
72
|
+
if (end > 0)
|
|
73
|
+
return takeIdentifier(rest.slice(0, end));
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const findVar = message.indexOf("Can't find variable:");
|
|
77
|
+
if (findVar >= 0) {
|
|
78
|
+
return takeIdentifier(message.slice(findVar + "Can't find variable:".length).trim());
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Component names from a compiled render stack, innermost frame first:
|
|
84
|
+
* `__components.SiteHeader`, `__components["SiteHeader"]`, `__components['Home']`.
|
|
85
|
+
*/
|
|
86
|
+
export function extractComponentNames(stack) {
|
|
87
|
+
const names = [];
|
|
88
|
+
if (typeof stack !== 'string' || !stack)
|
|
89
|
+
return names;
|
|
90
|
+
const marker = '__components';
|
|
91
|
+
let i = 0;
|
|
92
|
+
while (i < stack.length) {
|
|
93
|
+
const at = stack.indexOf(marker, i);
|
|
94
|
+
if (at < 0)
|
|
95
|
+
break;
|
|
96
|
+
let j = at + marker.length;
|
|
97
|
+
let name = null;
|
|
98
|
+
if (stack.charAt(j) === '.') {
|
|
99
|
+
name = takeIdentifier(stack.slice(j + 1));
|
|
100
|
+
j += (name ? name.length : 0) + 1;
|
|
101
|
+
}
|
|
102
|
+
else if (stack.charAt(j) === '[') {
|
|
103
|
+
const quote = stack.charAt(j + 1);
|
|
104
|
+
if (quote === '"' || quote === "'") {
|
|
105
|
+
const end = stack.indexOf(quote, j + 2);
|
|
106
|
+
if (end > j + 2) {
|
|
107
|
+
const raw = stack.slice(j + 2, end);
|
|
108
|
+
name = takeIdentifier(raw) !== null && takeIdentifier(raw) === raw ? raw : null;
|
|
109
|
+
j = end + 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (name && !names.includes(name))
|
|
114
|
+
names.push(name);
|
|
115
|
+
i = j > at ? j : at + 1;
|
|
116
|
+
}
|
|
117
|
+
return names;
|
|
118
|
+
}
|
|
119
|
+
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs', '.cjs', '.vsk']);
|
|
120
|
+
function scanFrameLocation(seg) {
|
|
121
|
+
if (!seg)
|
|
122
|
+
return null;
|
|
123
|
+
let i = seg.length;
|
|
124
|
+
let column = 0;
|
|
125
|
+
let digits = 0;
|
|
126
|
+
while (i > 0 && seg.charCodeAt(i - 1) >= 48 && seg.charCodeAt(i - 1) <= 57) {
|
|
127
|
+
column = (seg.charCodeAt(i - 1) - 48) * Math.pow(10, digits) + column;
|
|
128
|
+
digits++;
|
|
129
|
+
i--;
|
|
130
|
+
}
|
|
131
|
+
if (digits === 0 || i === 0 || seg.charAt(i - 1) !== ':')
|
|
132
|
+
return null;
|
|
133
|
+
i--;
|
|
134
|
+
let line = 0;
|
|
135
|
+
digits = 0;
|
|
136
|
+
while (i > 0 && seg.charCodeAt(i - 1) >= 48 && seg.charCodeAt(i - 1) <= 57) {
|
|
137
|
+
line = (seg.charCodeAt(i - 1) - 48) * Math.pow(10, digits) + line;
|
|
138
|
+
digits++;
|
|
139
|
+
i--;
|
|
140
|
+
}
|
|
141
|
+
if (digits === 0 || i === 0 || seg.charAt(i - 1) !== ':')
|
|
142
|
+
return null;
|
|
143
|
+
i--;
|
|
144
|
+
const path = seg.slice(0, i);
|
|
145
|
+
if (!path)
|
|
146
|
+
return null;
|
|
147
|
+
return { path, line, column: column + 1 };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Parse V8-style stack lines into `path:line:col` frames (1-based column,
|
|
151
|
+
* matching the codeframe caret). Supports both parenthesized frames
|
|
152
|
+
* (`at fn (/abs/file.ts:12:5)`) and bare path frames (`at file.ts:12:5`).
|
|
153
|
+
*/
|
|
154
|
+
export function extractSourceFrames(stack) {
|
|
155
|
+
const frames = [];
|
|
156
|
+
if (typeof stack !== 'string' || !stack)
|
|
157
|
+
return frames;
|
|
158
|
+
for (const raw of stack.split('\n')) {
|
|
159
|
+
const line = raw.trim();
|
|
160
|
+
if (!line.startsWith('at '))
|
|
161
|
+
continue;
|
|
162
|
+
let seg = line.slice(3).trim();
|
|
163
|
+
if (seg.startsWith('async '))
|
|
164
|
+
seg = seg.slice(6).trim();
|
|
165
|
+
const close = seg.lastIndexOf(')');
|
|
166
|
+
if (close >= 0) {
|
|
167
|
+
const open = seg.lastIndexOf('(', close);
|
|
168
|
+
if (open >= 0)
|
|
169
|
+
seg = seg.slice(open + 1, close);
|
|
170
|
+
}
|
|
171
|
+
const loc = scanFrameLocation(seg);
|
|
172
|
+
if (loc)
|
|
173
|
+
frames.push(loc);
|
|
174
|
+
}
|
|
175
|
+
return frames;
|
|
176
|
+
}
|
|
177
|
+
function isInsideRoot(absPath, root) {
|
|
178
|
+
if (!root)
|
|
179
|
+
return false;
|
|
180
|
+
const base = root.endsWith('/') ? root : root + '/';
|
|
181
|
+
if (absPath === root)
|
|
182
|
+
return true;
|
|
183
|
+
return absPath.startsWith(base);
|
|
184
|
+
}
|
|
185
|
+
const GENERATED_SEGMENTS = ['node_modules', '.vesk'];
|
|
186
|
+
function isGeneratedPath(absPath) {
|
|
187
|
+
for (const seg of GENERATED_SEGMENTS) {
|
|
188
|
+
if (absPath.split('/').includes(seg))
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
if (absPath.includes('/_vesk/static/'))
|
|
192
|
+
return true;
|
|
193
|
+
if (absPath.includes('/dist/'))
|
|
194
|
+
return true;
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* First stack frame that names a real source file inside the project
|
|
199
|
+
* (`.ts`/`.tsx`/`.js`/`.vsk`, existing on disk, not node_modules/dist/.vesk
|
|
200
|
+
* or the dev bundle). Innermost (topmost) real frame is the throw site —
|
|
201
|
+
* e.g. `ReferenceError` inside `lib/helper.ts` imported by a `.vsk` page.
|
|
202
|
+
*/
|
|
203
|
+
export function firstSourceFileFrame(stack, projectRoot) {
|
|
204
|
+
for (const frame of extractSourceFrames(stack)) {
|
|
205
|
+
let p = frame.path;
|
|
206
|
+
if (p.startsWith('file://'))
|
|
207
|
+
p = p.slice('file://'.length);
|
|
208
|
+
if (!p.startsWith('/'))
|
|
209
|
+
continue; // bundle URLs (http://) and relative frames
|
|
210
|
+
const ext = p.slice(p.lastIndexOf('.')).split(':')[0].toLowerCase();
|
|
211
|
+
if (!SOURCE_EXTENSIONS.has(ext))
|
|
212
|
+
continue;
|
|
213
|
+
if (isGeneratedPath(p))
|
|
214
|
+
continue;
|
|
215
|
+
if (!isInsideRoot(p, projectRoot))
|
|
216
|
+
continue;
|
|
217
|
+
try {
|
|
218
|
+
if (!existsSync(p))
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
return { ...frame, path: p };
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* First use of an identifier in a `.vsk` source file (1-based line/column).
|
|
230
|
+
* Import lines are skipped — an undeclared name's *use* site is the error site.
|
|
231
|
+
*/
|
|
232
|
+
export function findIdentifierUsage(src, name) {
|
|
233
|
+
if (typeof src !== 'string' || !src || typeof name !== 'string' || !takeIdentifier(name) || takeIdentifier(name) !== name) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const lines = src.split('\n');
|
|
237
|
+
for (let ln = 0; ln < lines.length; ln++) {
|
|
238
|
+
const text = lines[ln];
|
|
239
|
+
if (/^\s*import[\s('"]/.test(text))
|
|
240
|
+
continue;
|
|
241
|
+
let idx = text.indexOf(name);
|
|
242
|
+
while (idx >= 0) {
|
|
243
|
+
const before = idx > 0 ? text.charAt(idx - 1) : '';
|
|
244
|
+
const after = idx + name.length < text.length ? text.charAt(idx + name.length) : '';
|
|
245
|
+
if (!isIdentPart(before) && !isIdentPart(after)) {
|
|
246
|
+
return { line: ln + 1, column: idx + 1 };
|
|
247
|
+
}
|
|
248
|
+
idx = text.indexOf(name, idx + 1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
function projectOf(ctx) {
|
|
254
|
+
return ctx.projectDir || resolve(ctx.appDir, '..');
|
|
255
|
+
}
|
|
256
|
+
/** Display path for a payload: app-relative (`page.vsk`), else project-relative (`components/X.vsk`). */
|
|
257
|
+
export function displayPath(absPath, ctx) {
|
|
258
|
+
const appRel = relative(ctx.appDir, absPath);
|
|
259
|
+
if (appRel && !appRel.startsWith('..'))
|
|
260
|
+
return appRel || absPath;
|
|
261
|
+
const projRel = relative(projectOf(ctx), absPath);
|
|
262
|
+
if (projRel && !projRel.startsWith('..'))
|
|
263
|
+
return projRel;
|
|
264
|
+
return absPath;
|
|
265
|
+
}
|
|
266
|
+
function readIfFile(absPath) {
|
|
267
|
+
try {
|
|
268
|
+
if (!existsSync(absPath))
|
|
269
|
+
return null;
|
|
270
|
+
return readFileSync(absPath, 'utf-8');
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function resolveComponentFile(name, ctx) {
|
|
277
|
+
const fromRoutes = ctx.routeSources?.get(name);
|
|
278
|
+
if (fromRoutes) {
|
|
279
|
+
const abs = resolve(ctx.appDir, fromRoutes);
|
|
280
|
+
if (readIfFile(abs) !== null)
|
|
281
|
+
return abs;
|
|
282
|
+
}
|
|
283
|
+
const fromShared = ctx.componentMap?.get(name);
|
|
284
|
+
if (fromShared) {
|
|
285
|
+
const abs = resolve(projectOf(ctx), fromShared);
|
|
286
|
+
if (readIfFile(abs) !== null)
|
|
287
|
+
return abs;
|
|
288
|
+
if (readIfFile(fromShared) !== null)
|
|
289
|
+
return fromShared;
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
/** Ordered absolute candidate files: stack-named components first, then route files. */
|
|
294
|
+
export function candidateFiles(message, stack, ctx) {
|
|
295
|
+
const out = [];
|
|
296
|
+
const push = (abs) => {
|
|
297
|
+
if (!abs || out.includes(abs))
|
|
298
|
+
return;
|
|
299
|
+
if (readIfFile(abs) === null)
|
|
300
|
+
return;
|
|
301
|
+
out.push(abs);
|
|
302
|
+
};
|
|
303
|
+
if (stack) {
|
|
304
|
+
for (const name of extractComponentNames(stack)) {
|
|
305
|
+
const f = resolveComponentFile(name, ctx);
|
|
306
|
+
if (f)
|
|
307
|
+
push(f);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
for (const f of ctx.routeFiles || [])
|
|
311
|
+
push(f);
|
|
312
|
+
return out.slice(0, 12);
|
|
313
|
+
}
|
|
314
|
+
function withCodeframe(payload, absPath, line, column) {
|
|
315
|
+
if (!payload.codeframe) {
|
|
316
|
+
const src = readIfFile(absPath);
|
|
317
|
+
if (src) {
|
|
318
|
+
const cf = buildCodeframe(src, line, column);
|
|
319
|
+
if (cf) {
|
|
320
|
+
cf.file = payload.file || cf.file;
|
|
321
|
+
payload.codeframe = cf;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return payload;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Resolve a server-side throw to the HMR payload shape. Never throws — on any
|
|
329
|
+
* failure returns the message + stack + generic tips (the old behavior, but in
|
|
330
|
+
* the overlay-renderable shape).
|
|
331
|
+
*/
|
|
332
|
+
export function resolveRuntimeErrorPayload(err, ctx) {
|
|
333
|
+
const rec = (typeof err === 'object' && err !== null ? err : null);
|
|
334
|
+
const message = rec && typeof rec.message === 'string' && rec.message
|
|
335
|
+
? rec.message
|
|
336
|
+
: err instanceof Error ? err.message : String(err ?? 'Unknown error');
|
|
337
|
+
const stack = rec && typeof rec.stack === 'string' && rec.stack
|
|
338
|
+
? rec.stack
|
|
339
|
+
: err instanceof Error && err.stack ? err.stack : undefined;
|
|
340
|
+
const code = rec && typeof rec.code === 'string' && rec.code ? rec.code
|
|
341
|
+
: err instanceof Error && typeof err.code === 'string'
|
|
342
|
+
? err.code
|
|
343
|
+
: undefined;
|
|
344
|
+
const candidates = candidateFiles(message, stack, ctx);
|
|
345
|
+
const firstFile = candidates.length > 0 ? displayPath(candidates[0], ctx) : 'app';
|
|
346
|
+
// Errors that already carry a location keep full fidelity.
|
|
347
|
+
const direct = buildErrorPayload(err, firstFile, { appDir: ctx.appDir });
|
|
348
|
+
if (direct.line !== null)
|
|
349
|
+
return direct;
|
|
350
|
+
// A stack frame naming a real source file (`.ts` imported into a `.vsk`,
|
|
351
|
+
// a `.ts` api/middleware module, etc.) is the exact throw site — point the
|
|
352
|
+
// payload there instead of scanning `__components` bundle coords.
|
|
353
|
+
const frame = firstSourceFileFrame(stack, projectOf(ctx));
|
|
354
|
+
if (frame) {
|
|
355
|
+
const payload = buildErrorPayload({ message: direct.message || message, stack, line: frame.line, column: frame.column, ...(code ? { code } : {}) }, displayPath(frame.path, ctx), { appDir: ctx.appDir });
|
|
356
|
+
return withCodeframe(payload, frame.path, frame.line, frame.column);
|
|
357
|
+
}
|
|
358
|
+
const name = extractUndefinedName(direct.message || message);
|
|
359
|
+
if (name) {
|
|
360
|
+
for (const abs of candidates) {
|
|
361
|
+
const src = readIfFile(abs);
|
|
362
|
+
if (!src)
|
|
363
|
+
continue;
|
|
364
|
+
const use = findIdentifierUsage(src, name);
|
|
365
|
+
if (!use)
|
|
366
|
+
continue;
|
|
367
|
+
const payload = buildErrorPayload({ message: direct.message || message, stack, line: use.line, column: use.column, ...(code ? { code } : {}) }, displayPath(abs, ctx), { appDir: ctx.appDir });
|
|
368
|
+
return withCodeframe(payload, abs, use.line, use.column);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// No pinpoint: still name the innermost .vsk file instead of the bundle.
|
|
372
|
+
if (candidates.length > 0) {
|
|
373
|
+
return buildErrorPayload(err, displayPath(candidates[0], ctx), { appDir: ctx.appDir });
|
|
374
|
+
}
|
|
375
|
+
return direct;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Resolve a client-side `{ message, stack }` report (compiled-bundle
|
|
379
|
+
* coordinates) to the HMR payload shape. Same core as the server path, but
|
|
380
|
+
* stack-named components outrank route files — the throw site is named by the
|
|
381
|
+
* `__components.Foo` frames, as in `ReferenceError: Menu is not defined` from
|
|
382
|
+
* `__components.SiteHeader` inside `__components.Home`.
|
|
383
|
+
*/
|
|
384
|
+
export function resolveClientErrorReport(report, ctx) {
|
|
385
|
+
const raw = (report || {});
|
|
386
|
+
const message = typeof raw.message === 'string' && raw.message ? raw.message.slice(0, 5000) : 'Unknown error';
|
|
387
|
+
const stack = typeof raw.stack === 'string' ? raw.stack.slice(0, 20000) : undefined;
|
|
388
|
+
const code = typeof raw.code === 'string' && raw.code ? raw.code : undefined;
|
|
389
|
+
return resolveRuntimeErrorPayload({ message, stack, ...(code ? { code } : {}) }, ctx);
|
|
390
|
+
}
|
|
391
|
+
/** `file:line:col` (or `file`, or `''`) for one-line terminal logs. */
|
|
392
|
+
export function formatErrorLocation(payload) {
|
|
393
|
+
const file = payload.file || '';
|
|
394
|
+
if (payload.line != null) {
|
|
395
|
+
return `${file}:${payload.line}${payload.column != null ? `:${payload.column}` : ''}`;
|
|
396
|
+
}
|
|
397
|
+
return file;
|
|
398
|
+
}
|
|
399
|
+
function escapeHtml(s) {
|
|
400
|
+
return String(s ?? '')
|
|
401
|
+
.replace(/&/g, '&')
|
|
402
|
+
.replace(/</g, '<')
|
|
403
|
+
.replace(/>/g, '>')
|
|
404
|
+
.replace(/"/g, '"');
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Standalone dev 500 page in the overlay's visual language (file header,
|
|
408
|
+
* message, codeframe with caret, TIPS/SUGGESTIONS/NEXT STEPS, collapsible
|
|
409
|
+
* stack). The HTML keeps a `</body>` so callers can run `injectDevScripts`
|
|
410
|
+
* over it — the HMR client then pops the same payload as an overlay on load.
|
|
411
|
+
*/
|
|
412
|
+
export function renderDevErrorPage(payload, opts) {
|
|
413
|
+
const status = opts?.status ?? 500;
|
|
414
|
+
const url = opts?.url ? escapeHtml(opts.url) : '';
|
|
415
|
+
const file = payload.file ? escapeHtml(payload.file) : 'unknown file';
|
|
416
|
+
const code = payload.code ? escapeHtml(payload.code) : '';
|
|
417
|
+
const loc = payload.line != null
|
|
418
|
+
? `:${payload.line}${payload.column != null ? `:${payload.column}` : ''}`
|
|
419
|
+
: '';
|
|
420
|
+
const message = escapeHtml(payload.message || 'Unknown error');
|
|
421
|
+
let codeHtml = '';
|
|
422
|
+
const frame = payload.codeframe;
|
|
423
|
+
if (frame && Array.isArray(frame.code) && frame.code.length > 0) {
|
|
424
|
+
let maxNo = 0;
|
|
425
|
+
for (const l of frame.code)
|
|
426
|
+
if (l.no > maxNo)
|
|
427
|
+
maxNo = l.no;
|
|
428
|
+
const gutterW = String(maxNo).length;
|
|
429
|
+
const pad = (n) => {
|
|
430
|
+
let s = String(n);
|
|
431
|
+
while (s.length < gutterW)
|
|
432
|
+
s = ' ' + s;
|
|
433
|
+
return s;
|
|
434
|
+
};
|
|
435
|
+
const rows = [];
|
|
436
|
+
for (const l of frame.code) {
|
|
437
|
+
const marker = l.isError ? '>' : ' ';
|
|
438
|
+
rows.push(`<div${l.isError ? ' class="err"' : ''}>${marker} <span class="ln">${pad(l.no)}</span> ${escapeHtml(l.text)}</div>`);
|
|
439
|
+
if (l.isError && frame.column != null && frame.column > 0) {
|
|
440
|
+
rows.push(`<div class="caret">${' '.repeat(gutterW + 3 + Math.max(0, frame.column - 1)).replace(/ /g, ' ')}^</div>`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
codeHtml = `<div class="code">${rows.join('\n')}</div>`;
|
|
444
|
+
}
|
|
445
|
+
const list = (title, items) => {
|
|
446
|
+
if (!items || items.length === 0)
|
|
447
|
+
return '';
|
|
448
|
+
return `<div class="sec"><div class="sec-t">> ${escapeHtml(title)}</div>` +
|
|
449
|
+
items.slice(0, 6).map((t) => `<div class="item">${escapeHtml(t)}</div>`).join('') + '</div>';
|
|
450
|
+
};
|
|
451
|
+
const lists = list('TIPS', payload.tips) + list('SUGGESTIONS', payload.suggestions) + list('NEXT STEPS', payload.nextSteps);
|
|
452
|
+
const stack = payload.stack
|
|
453
|
+
? `<details class="stack"><summary>Stack trace</summary><pre>${escapeHtml(payload.stack)}</pre></details>`
|
|
454
|
+
: '';
|
|
455
|
+
return '<!DOCTYPE html><html><head><meta charset="utf-8"><title>' + status + ' — Vesk dev</title>' +
|
|
456
|
+
'<style>' +
|
|
457
|
+
'body{background:#000;color:#e8e8e8;font-family:ui-monospace,Menlo,Consolas,monospace;margin:0;padding:32px;}' +
|
|
458
|
+
'.panel{max-width:900px;margin:0 auto;border:1px solid #e8e8e8;}' +
|
|
459
|
+
'.head{border-bottom:1px solid #e8e8e8;padding:12px 16px;font-weight:700;letter-spacing:.08em;}' +
|
|
460
|
+
'.body{padding:16px;}' +
|
|
461
|
+
'.file{font-weight:700;margin-bottom:8px;}' +
|
|
462
|
+
'.msg{margin:8px 0 16px;white-space:pre-wrap;}' +
|
|
463
|
+
'.code{background:#0a0a0a;border:1px solid #333;padding:12px;white-space:pre;overflow-x:auto;margin-bottom:16px;}' +
|
|
464
|
+
'.code .ln{color:#888;}' +
|
|
465
|
+
'.code .err{background:#3d0a0a;}' +
|
|
466
|
+
'.code .caret{color:#ff5555;}' +
|
|
467
|
+
'.sec{margin:12px 0;}' +
|
|
468
|
+
'.sec-t{font-weight:700;margin-bottom:4px;}' +
|
|
469
|
+
'.item{padding-left:16px;color:#ccc;}' +
|
|
470
|
+
'.stack{margin-top:16px;color:#999;}' +
|
|
471
|
+
'.stack pre{white-space:pre-wrap;font-size:12px;}' +
|
|
472
|
+
'.foot{border-top:1px solid #e8e8e8;padding:8px 16px;color:#888;font-size:12px;letter-spacing:.08em;}' +
|
|
473
|
+
'</style></head><body>' +
|
|
474
|
+
'<div class="panel"><div class="head">> SSR ERROR — ' + status + '</div>' +
|
|
475
|
+
'<div class="body">' +
|
|
476
|
+
`<div class="file">${code ? code + ' — ' : ''}${file}${loc}</div>` +
|
|
477
|
+
(url ? `<div class="file" style="font-weight:400;color:#888;">${url}</div>` : '') +
|
|
478
|
+
`<div class="msg">${message}</div>` +
|
|
479
|
+
codeHtml + lists + stack +
|
|
480
|
+
'</div><div class="foot">VESK DEV — FIX THE FILE AND SAVE TO RELOAD</div></div>' +
|
|
481
|
+
'</body></html>';
|
|
482
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vesk/adapter",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vesk adapter — builds deployable .vesk/ output for Deno and Node serverless",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"sharp": "^0.35.3"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@vesk/compiler": "^0.2.
|
|
27
|
-
"@vesk/runtime": "^0.2.
|
|
28
|
-
"@vesk/types": "^0.2.
|
|
26
|
+
"@vesk/compiler": "^0.2.28",
|
|
27
|
+
"@vesk/runtime": "^0.2.28",
|
|
28
|
+
"@vesk/types": "^0.2.28"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/ws": "^8.18.1",
|