@vesk/adapter 0.2.50-canary.2 → 0.2.50-canary.4
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/asset-hash.d.ts +26 -0
- package/dist/asset-hash.d.ts.map +1 -0
- package/dist/asset-hash.js +63 -0
- package/dist/client-bundle.d.ts.map +1 -1
- package/dist/client-bundle.js +12 -3
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +10 -2
- package/dist/error-report.d.ts +43 -0
- package/dist/error-report.d.ts.map +1 -0
- package/dist/error-report.js +52 -0
- package/dist/events.d.ts +17 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +23 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -24
- package/dist/prod-server.d.ts.map +1 -1
- package/dist/prod-server.js +33 -5
- package/dist/ssr-function.d.ts.map +1 -1
- package/dist/ssr-function.js +4 -1
- package/package.json +4 -4
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface AssetDigest {
|
|
2
|
+
/** `client.a1b2c3d4e5.js` */
|
|
3
|
+
fileName: string;
|
|
4
|
+
/** 10 hex chars of the content hash, safe in a URL and a filename. */
|
|
5
|
+
hash: string;
|
|
6
|
+
bytes: number;
|
|
7
|
+
/** `sha384-<base64>`, ready for an `integrity` attribute. */
|
|
8
|
+
integrity: string;
|
|
9
|
+
}
|
|
10
|
+
/** `client.js` + bytes -> `client.a1b2c3d4e5.js` (the extension is preserved). */
|
|
11
|
+
export declare function hashedName(fileName: string, content: string | Buffer): string;
|
|
12
|
+
export declare function hashOf(content: string | Buffer): string;
|
|
13
|
+
export declare function integrityOf(content: string | Buffer): string;
|
|
14
|
+
export declare function describeAsset(fileName: string, content: string | Buffer): AssetDigest;
|
|
15
|
+
/** Does this name carry a content hash? Used to pick the cache policy. */
|
|
16
|
+
export declare function isHashedName(fileName: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Cache-Control for a served asset.
|
|
19
|
+
*
|
|
20
|
+
* A hashed name means the bytes behind that URL can never change, so it is
|
|
21
|
+
* immutable for a year. Anything else (dev bundles, `public/` files copied
|
|
22
|
+
* verbatim, the unhashed name a prerendered page may still reference) must be
|
|
23
|
+
* revalidated, or a deploy would serve stale code forever.
|
|
24
|
+
*/
|
|
25
|
+
export declare function cacheControlFor(fileName: string, isDev: boolean): string;
|
|
26
|
+
//# sourceMappingURL=asset-hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-hash.d.ts","sourceRoot":"","sources":["../src/asset-hash.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAK7E;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAOrF;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQtD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAKxE"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-addressed asset names and Subresource Integrity digests.
|
|
3
|
+
*
|
|
4
|
+
* Production assets are emitted under a content hash — `client.a1b2c3d4.js` —
|
|
5
|
+
* so they can be served `immutable` and never go stale in a CDN, and so a
|
|
6
|
+
* deploy that changes the runtime cannot be served the previous one. Dev keeps
|
|
7
|
+
* the plain name: the dev server rewrites it on every edit and HMR needs a
|
|
8
|
+
* stable URL.
|
|
9
|
+
*
|
|
10
|
+
* The digest is also the SRI value. Both come from the same bytes, so a tag
|
|
11
|
+
* carrying `integrity` can only execute the file that was actually built.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
const EXT_RE = /\.([a-z0-9]+)$/i;
|
|
15
|
+
/** `client.js` + bytes -> `client.a1b2c3d4e5.js` (the extension is preserved). */
|
|
16
|
+
export function hashedName(fileName, content) {
|
|
17
|
+
const m = EXT_RE.exec(fileName);
|
|
18
|
+
if (!m)
|
|
19
|
+
return fileName;
|
|
20
|
+
const base = fileName.slice(0, m.index);
|
|
21
|
+
return `${base}.${hashOf(content)}${m[0]}`;
|
|
22
|
+
}
|
|
23
|
+
export function hashOf(content) {
|
|
24
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 10);
|
|
25
|
+
}
|
|
26
|
+
export function integrityOf(content) {
|
|
27
|
+
return `sha384-${createHash('sha384').update(content).digest('base64')}`;
|
|
28
|
+
}
|
|
29
|
+
export function describeAsset(fileName, content) {
|
|
30
|
+
return {
|
|
31
|
+
fileName: hashedName(fileName, content),
|
|
32
|
+
hash: hashOf(content),
|
|
33
|
+
bytes: typeof content === 'string' ? Buffer.byteLength(content) : content.length,
|
|
34
|
+
integrity: integrityOf(content),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Does this name carry a content hash? Used to pick the cache policy. */
|
|
38
|
+
export function isHashedName(fileName) {
|
|
39
|
+
const m = EXT_RE.exec(fileName);
|
|
40
|
+
if (!m)
|
|
41
|
+
return false;
|
|
42
|
+
const base = fileName.slice(0, m.index);
|
|
43
|
+
const dot = base.lastIndexOf('.');
|
|
44
|
+
if (dot === -1)
|
|
45
|
+
return false;
|
|
46
|
+
const suffix = base.slice(dot + 1);
|
|
47
|
+
return suffix.length === 10 && /^[0-9a-f]{10}$/.test(suffix);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Cache-Control for a served asset.
|
|
51
|
+
*
|
|
52
|
+
* A hashed name means the bytes behind that URL can never change, so it is
|
|
53
|
+
* immutable for a year. Anything else (dev bundles, `public/` files copied
|
|
54
|
+
* verbatim, the unhashed name a prerendered page may still reference) must be
|
|
55
|
+
* revalidated, or a deploy would serve stale code forever.
|
|
56
|
+
*/
|
|
57
|
+
export function cacheControlFor(fileName, isDev) {
|
|
58
|
+
if (isDev)
|
|
59
|
+
return 'no-cache';
|
|
60
|
+
return isHashedName(fileName)
|
|
61
|
+
? 'public, max-age=31536000, immutable'
|
|
62
|
+
: 'public, max-age=0, must-revalidate';
|
|
63
|
+
}
|
|
@@ -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":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAmF,MAAM,yBAAyB,CAAC;AAoCnL;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA4BhE;AAED;;;;;GAKG;AACH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7B,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;IACtH,WAAW,CAAC,EAAE;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1C,YAAY,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;gBAAC,MAAM,CAAC,EAAE;oBAAE,IAAI,CAAC,EAAE,MAAM,CAAC;oBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAA;SAAE,CAAC,CAAC;KAC/F,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAmDD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAKzD;AAkCD;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA+B7D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyBlD;AA4MD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA+B5D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASxD;AA4CD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,GAAG,MAAM,CAoBnG;AAED,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,CA6c7B;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;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAI/F"}
|
package/dist/client-bundle.js
CHANGED
|
@@ -3,6 +3,7 @@ import { resolve, join, dirname, relative, sep } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { build } from './esbuild-fallback.js';
|
|
6
|
+
import { hashedName } from '@vesk/adapter/src/asset-hash';
|
|
6
7
|
import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
|
|
7
8
|
import { parse, parseGeneratedJs } from '@vesk/compiler/src/parser';
|
|
8
9
|
import { compileClient, compileClientBoth, nameAllocFor, vskComponentNames } from '@vesk/compiler/src/client-codegen';
|
|
@@ -982,9 +983,17 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
|
|
|
982
983
|
function annotate(nodes) {
|
|
983
984
|
for (const node of nodes) {
|
|
984
985
|
const chunkName = `page-${buildChunkName(node)}.js`;
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
986
|
+
// Production chunk names are content-hashed, and the ROUTER asks for
|
|
987
|
+
// chunks by the URL baked here — so the hash has to be applied to this
|
|
988
|
+
// reference, not just to the file on disk. Getting it wrong is silent
|
|
989
|
+
// and total: every chunk 404s, the server answers those requests with
|
|
990
|
+
// the SPA fallback (text/html), the browser refuses the script, and the
|
|
991
|
+
// page never hydrates at all.
|
|
992
|
+
const emitted = chunks.find((c) => c.name === chunkName && c.code.trim());
|
|
993
|
+
if (emitted) {
|
|
994
|
+
const fileName = options?.hmr ? chunkName : hashedName(chunkName, emitted.code);
|
|
995
|
+
node.chunk = `/_vesk/static/${fileName}`;
|
|
996
|
+
}
|
|
988
997
|
annotate(node.children || []);
|
|
989
998
|
}
|
|
990
999
|
}
|
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;
|
|
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;AASjG,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,CAwqBvG"}
|
package/dist/dev-server.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { readFileSync, existsSync, watch, statSync } from 'node:fs';
|
|
2
|
-
import { resolve, extname, dirname } from 'node:path';
|
|
2
|
+
import { resolve, extname, dirname, basename } from 'node:path';
|
|
3
3
|
import { createServer } from 'node:http';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
|
|
6
6
|
import { DEFAULT_MAX_BODY_BYTES, safeJsonForScript } from '@vesk/compiler/src/server-codegen';
|
|
7
7
|
import { build } from '@vesk/adapter/src/index';
|
|
8
|
+
import { cacheControlFor } from '@vesk/adapter/src/asset-hash';
|
|
9
|
+
import { reportServerError, registerErrorHooks } from '@vesk/adapter/src/error-report';
|
|
8
10
|
import { buildErrorPayload, createHmrServer } from './hmr';
|
|
9
11
|
import * as hmrApi from './hmr';
|
|
10
12
|
import { createDevApiRouter } from './dev-api';
|
|
@@ -205,6 +207,9 @@ export async function startDevServer(appDir, options) {
|
|
|
205
207
|
const devDir = resolve(appDir, '..', '.vesk', 'dev');
|
|
206
208
|
const publicDir = options?.publicDir || resolve(appDir, '..', 'public');
|
|
207
209
|
installMdReadHook([publicDir, resolve(devDir, 'static', 'public')]);
|
|
210
|
+
// Plugin `onError` hooks only exist here (dev): in production there are no
|
|
211
|
+
// plugin objects, and `_events.ts` is the seam that ships.
|
|
212
|
+
registerErrorHooks(options?.plugins);
|
|
208
213
|
// Dev-panel plugin list: plugin / module names declared in the dev server's own config.
|
|
209
214
|
const configPluginNames = (options?.plugins || []).map((p) => {
|
|
210
215
|
if (typeof p === 'string')
|
|
@@ -572,7 +577,9 @@ export async function startDevServer(appDir, options) {
|
|
|
572
577
|
}
|
|
573
578
|
if (existsSync(staticPath) && statSync(staticPath).isFile()) {
|
|
574
579
|
const ext = extname(staticPath);
|
|
575
|
-
|
|
580
|
+
// Dev names are stable so HMR can rewrite them, which means they must
|
|
581
|
+
// never be cached: the same URL has to serve the newest bytes.
|
|
582
|
+
res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream', 'Cache-Control': cacheControlFor(basename(staticPath), true) });
|
|
576
583
|
res.end(readFileSync(staticPath));
|
|
577
584
|
return;
|
|
578
585
|
}
|
|
@@ -697,6 +704,7 @@ export async function startDevServer(appDir, options) {
|
|
|
697
704
|
}
|
|
698
705
|
catch (e) {
|
|
699
706
|
console.error('[vesk dev] SSR render failed:', e instanceof Error ? (e.stack || e.message) : String(e));
|
|
707
|
+
await reportServerError(e, { url: url.href, target: 'dev', status: 500 });
|
|
700
708
|
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
701
709
|
res.end(renderSsrErrorPage(e, url.pathname));
|
|
702
710
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The production error-reporting seam.
|
|
3
|
+
*
|
|
4
|
+
* Production never leaks a stack trace to the client (that is the right
|
|
5
|
+
* default), which used to mean an adopter had two bad options: show the stack,
|
|
6
|
+
* or replace the adapter's error handling. This is the supported middle: a
|
|
7
|
+
* plugin's `onError` hook receives the error, the route and the build id.
|
|
8
|
+
*
|
|
9
|
+
* Design rules:
|
|
10
|
+
* - a hook that throws is caught and logged, never turned into a second
|
|
11
|
+
* failure (a reporting outage must not take down the page that was already
|
|
12
|
+
* failing);
|
|
13
|
+
* - one failing hook does not stop the others;
|
|
14
|
+
* - nothing is reported twice for one request.
|
|
15
|
+
*/
|
|
16
|
+
import type { ServerErrorContext, VeskPlugin } from '@vesk/types';
|
|
17
|
+
export interface BuildConfigLike {
|
|
18
|
+
version?: number;
|
|
19
|
+
buildId?: string;
|
|
20
|
+
veskVersion?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
export declare function registerErrorReporter(fn: (err: unknown, ctx: ServerErrorContext) => void | Promise<void>): void;
|
|
24
|
+
export declare function clearErrorReporters(): void;
|
|
25
|
+
export declare function errorReporterCount(): number;
|
|
26
|
+
/** Register every plugin that declares `onError`. */
|
|
27
|
+
export declare function registerErrorHooks(plugins: VeskPlugin[] | null | undefined): void;
|
|
28
|
+
export interface ReportOptions {
|
|
29
|
+
url?: string;
|
|
30
|
+
routePath?: string;
|
|
31
|
+
target: ServerErrorContext['target'];
|
|
32
|
+
status?: number;
|
|
33
|
+
durationMs?: number;
|
|
34
|
+
buildConfig?: BuildConfigLike | null;
|
|
35
|
+
/** Suppress a duplicate report for the same error object. */
|
|
36
|
+
dedupe?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Report a production failure. Never throws: a failure to report is logged and
|
|
40
|
+
* swallowed, because the caller is already handling a worse problem.
|
|
41
|
+
*/
|
|
42
|
+
export declare function reportServerError(err: unknown, options: ReportOptions): Promise<void>;
|
|
43
|
+
//# sourceMappingURL=error-report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-report.d.ts","sourceRoot":"","sources":["../src/error-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAMD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAE/G;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,qDAAqD;AACrD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAIjF;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3F"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Process-wide hook list; the config's plugins are registered at boot. */
|
|
2
|
+
const hooks = [];
|
|
3
|
+
const reported = new WeakSet();
|
|
4
|
+
export function registerErrorReporter(fn) {
|
|
5
|
+
if (typeof fn === 'function')
|
|
6
|
+
hooks.push(fn);
|
|
7
|
+
}
|
|
8
|
+
export function clearErrorReporters() {
|
|
9
|
+
hooks.length = 0;
|
|
10
|
+
}
|
|
11
|
+
export function errorReporterCount() {
|
|
12
|
+
return hooks.length;
|
|
13
|
+
}
|
|
14
|
+
/** Register every plugin that declares `onError`. */
|
|
15
|
+
export function registerErrorHooks(plugins) {
|
|
16
|
+
for (const plugin of plugins || []) {
|
|
17
|
+
if (plugin && typeof plugin.onError === 'function')
|
|
18
|
+
registerErrorReporter(plugin.onError);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Report a production failure. Never throws: a failure to report is logged and
|
|
23
|
+
* swallowed, because the caller is already handling a worse problem.
|
|
24
|
+
*/
|
|
25
|
+
export async function reportServerError(err, options) {
|
|
26
|
+
if (options.dedupe !== false && err && typeof err === 'object') {
|
|
27
|
+
if (reported.has(err))
|
|
28
|
+
return;
|
|
29
|
+
reported.add(err);
|
|
30
|
+
}
|
|
31
|
+
const cfg = options.buildConfig || null;
|
|
32
|
+
const ctx = {
|
|
33
|
+
url: options.url,
|
|
34
|
+
routePath: options.routePath,
|
|
35
|
+
target: options.target,
|
|
36
|
+
status: options.status ?? 500,
|
|
37
|
+
durationMs: options.durationMs,
|
|
38
|
+
version: typeof cfg?.veskVersion === 'string' ? cfg.veskVersion : undefined,
|
|
39
|
+
buildId: typeof cfg?.buildId === 'string' ? cfg.buildId : undefined,
|
|
40
|
+
errorName: err instanceof Error ? err.name : typeof err,
|
|
41
|
+
message: err instanceof Error ? err.message : String(err),
|
|
42
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
43
|
+
};
|
|
44
|
+
for (const hook of hooks) {
|
|
45
|
+
try {
|
|
46
|
+
await hook(err, ctx);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
console.error('vesk: an onError hook threw and was ignored:', e?.message ?? e);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
package/dist/events.d.ts
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper module bundled into `server/events.js`. It lifts the user's
|
|
3
|
+
* `_events.ts` exports into `executeStart` / `executeRequest` / `executeStop`
|
|
4
|
+
* entry points with a context that routes `set`/`get` into the process-wide
|
|
5
|
+
* server context (`globalThis.__vesk_server_ctx`).
|
|
6
|
+
*
|
|
7
|
+
* The `__vesk_events_started` guard is global (not module-local) so lazy edge
|
|
8
|
+
* isolates and the Node prod server share one lifetime; `onStop` clears it so
|
|
9
|
+
* dev HMR reloads can boot a fresh module.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildEventsEntry(sourcePath: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Report a production failure to `_events.ts`'s `onError`, if it exports one.
|
|
14
|
+
* Never throws: a reporting outage must not add a second failure to a request
|
|
15
|
+
* that already failed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function executeError(err: unknown, base: Record<string, unknown>, mod: unknown): Promise<void>;
|
|
1
18
|
/**
|
|
2
19
|
* Compile the app's `_events.ts` (if present) into a self-contained
|
|
3
20
|
* `server/events.js` next to the server runtime. Returns true when an events
|
package/dist/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAOA;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAuD3D;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiCpF"}
|
package/dist/events.js
CHANGED
|
@@ -13,14 +13,17 @@ let buildId = 0;
|
|
|
13
13
|
* isolates and the Node prod server share one lifetime; `onStop` clears it so
|
|
14
14
|
* dev HMR reloads can boot a fresh module.
|
|
15
15
|
*/
|
|
16
|
-
function
|
|
16
|
+
export function buildEventsEntry(sourcePath) {
|
|
17
17
|
return [
|
|
18
18
|
`import * as __events from ${JSON.stringify(sourcePath)};`,
|
|
19
19
|
'',
|
|
20
20
|
'const __store = () => (globalThis.__vesk_server_ctx ||= {});',
|
|
21
21
|
'',
|
|
22
22
|
'const __handlers = {};',
|
|
23
|
-
|
|
23
|
+
// `onError` joins the server events: production has no plugin objects, so
|
|
24
|
+
// this is the seam an error reporter actually uses there (plugins carry
|
|
25
|
+
// their own `onError` in dev, where plugin objects do exist).
|
|
26
|
+
"for (const __name of ['onStart', 'onRequest', 'onStop', 'onError']) {",
|
|
24
27
|
' const __fn = __events[__name];',
|
|
25
28
|
" if (typeof __fn === 'function') __handlers[__name] = __fn;",
|
|
26
29
|
'}',
|
|
@@ -61,9 +64,26 @@ function buildEntry(sourcePath) {
|
|
|
61
64
|
'export const onStart = __handlers.onStart;',
|
|
62
65
|
'export const onRequest = __handlers.onRequest;',
|
|
63
66
|
'export const onStop = __handlers.onStop;',
|
|
67
|
+
'export const onError = __handlers.onError;',
|
|
64
68
|
'',
|
|
65
69
|
].join('\n');
|
|
66
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Report a production failure to `_events.ts`'s `onError`, if it exports one.
|
|
73
|
+
* Never throws: a reporting outage must not add a second failure to a request
|
|
74
|
+
* that already failed.
|
|
75
|
+
*/
|
|
76
|
+
export async function executeError(err, base, mod) {
|
|
77
|
+
const fn = mod?.onError;
|
|
78
|
+
if (typeof fn !== 'function')
|
|
79
|
+
return;
|
|
80
|
+
try {
|
|
81
|
+
await fn(err, base);
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
console.error('vesk: _events onError threw and was ignored:', e instanceof Error ? e.message : e);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
67
87
|
/**
|
|
68
88
|
* Compile the app's `_events.ts` (if present) into a self-contained
|
|
69
89
|
* `server/events.js` next to the server runtime. Returns true when an events
|
|
@@ -78,7 +98,7 @@ export async function compileEvents(appDir, outDir) {
|
|
|
78
98
|
const serverDir = resolve(outDir, 'server');
|
|
79
99
|
mkdirSync(serverDir, { recursive: true });
|
|
80
100
|
const entryFile = resolve(serverDir, `.events-entry-${buildId++}.mjs`);
|
|
81
|
-
writeFileSync(entryFile,
|
|
101
|
+
writeFileSync(entryFile, buildEventsEntry(sourcePath), 'utf-8');
|
|
82
102
|
try {
|
|
83
103
|
const result = await esbuild.build({
|
|
84
104
|
entryPoints: [entryFile],
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACe,YAAY,EAAE,WAAW,EAC7B,UAAU,EAChC,MAAM,yBAAyB,CAAC;AAiBjC;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAC9C,UAAU,EAAE,CAQd;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,UAAU,EAAE,EACrB,KAAK,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,GACxC,UAAU,EAAE,CAGd;AAkDD,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAqWpG;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAIhE,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EACzB,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,aAAa,GACd,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { resolve, dirname, relative, basename } from 'node:path';
|
|
2
|
+
import { resolve, dirname, relative, basename, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { bundleRuntime } from '@vesk/adapter/src/runtime-bundle';
|
|
5
5
|
import { generateSsrFunction } from '@vesk/adapter/src/ssr-function';
|
|
6
6
|
import { resolveUserCssPath, isTailwindPlugin, resolveCssUrls, hasBuiltGlobalCss } from '@vesk/adapter/src/css';
|
|
7
7
|
import { collectActionIds } from '@vesk/compiler/src/actions';
|
|
8
8
|
import { generateApiFunction } from '@vesk/adapter/src/api-function';
|
|
9
|
+
import { describeAsset } from '@vesk/adapter/src/asset-hash';
|
|
10
|
+
// The adapter's own package.json, for the version stamped into the manifest.
|
|
11
|
+
function PACKAGES_DIR_SELF() {
|
|
12
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
}
|
|
9
14
|
import { compileMiddleware, compileMiddlewareCode } from '@vesk/adapter/src/middleware';
|
|
10
15
|
import { compileEvents } from '@vesk/adapter/src/events';
|
|
11
16
|
import { generateClientBundle } from '@vesk/adapter/src/client-bundle';
|
|
@@ -219,6 +224,39 @@ export async function build(appDir, options) {
|
|
|
219
224
|
const apiTree = existsSync(apiDir) ? scanApiRoutes(apiDir) : [];
|
|
220
225
|
console.error(`vesk build: ${routeTree.length} root routes, ${apiTree.length} API routes`);
|
|
221
226
|
markPhase('scan routes');
|
|
227
|
+
console.error('vesk build: bundling client runtime...');
|
|
228
|
+
const bundleOpts = {};
|
|
229
|
+
if (options?.codeSplit)
|
|
230
|
+
bundleOpts.codeSplit = true;
|
|
231
|
+
if (options?.hmr)
|
|
232
|
+
bundleOpts.hmr = true;
|
|
233
|
+
if (options?.routeDataCache !== undefined)
|
|
234
|
+
bundleOpts.routeDataCache = options.routeDataCache;
|
|
235
|
+
if (pluginsPipelines.length > 0)
|
|
236
|
+
bundleOpts.plugins = pluginsPipelines;
|
|
237
|
+
const { main, chunks } = await generateClientBundle(routeTree, appDir, componentMap, bundleOpts);
|
|
238
|
+
markPhase('client runtime + chunks');
|
|
239
|
+
// Content-hash every emitted browser asset. A hashed name can be served
|
|
240
|
+
// immutable and can never be stale in a CDN; the digest rides along as the
|
|
241
|
+
// SRI attribute, so a tag can only execute the bytes this build produced.
|
|
242
|
+
// Dev keeps the plain name — the dev server rewrites it on every edit and HMR
|
|
243
|
+
// needs a stable URL.
|
|
244
|
+
const staticDir = resolve(outDir, 'static');
|
|
245
|
+
const hashed = !options?.hmr;
|
|
246
|
+
const clientAsset = describeAsset('client.js', main);
|
|
247
|
+
const clientFileName = hashed ? clientAsset.fileName : 'client.js';
|
|
248
|
+
writeFileSync(resolve(staticDir, clientFileName), main, 'utf-8');
|
|
249
|
+
const clientUrl = `/_vesk/static/${clientFileName}`;
|
|
250
|
+
const mode = chunks.length > 0 ? 'code-split' : 'monolithic';
|
|
251
|
+
console.error(`vesk build: client → static/${clientFileName} (${main.length} bytes, ${mode}${hashed ? `, ${clientAsset.hash}` : ''})`);
|
|
252
|
+
const emittedAssets = [clientAsset];
|
|
253
|
+
for (const chunk of chunks) {
|
|
254
|
+
const asset = describeAsset(chunk.name, chunk.code);
|
|
255
|
+
emittedAssets.push(asset);
|
|
256
|
+
writeFileSync(resolve(staticDir, hashed ? asset.fileName : chunk.name), chunk.code, 'utf-8');
|
|
257
|
+
console.error(`vesk build: chunk → static/${hashed ? asset.fileName : chunk.name} (${chunk.code.length} bytes${hashed ? `, ${asset.hash}` : ''})`);
|
|
258
|
+
}
|
|
259
|
+
const clientIntegrity = hashed ? clientAsset.integrity : undefined;
|
|
222
260
|
console.error('vesk build: bundling server runtime...');
|
|
223
261
|
await bundleRuntime(appDir, outDir);
|
|
224
262
|
const ssrRoutes = [];
|
|
@@ -242,7 +280,7 @@ export async function build(appDir, options) {
|
|
|
242
280
|
// For standalone routes the layout chain is cleared — only the
|
|
243
281
|
// standalone node's own layout (if any) applies, no root ancestors.
|
|
244
282
|
const ssrAncestorLayouts = isStandalone ? [] : ancestorLayouts;
|
|
245
|
-
const { funcPath, funcCode, name } = generateSsrFunction(node, appDir, outDir, componentMap, { ancestorLayouts: ssrAncestorLayouts, middlewareCode: mwCode, headExtra: pluginHeadExtra });
|
|
283
|
+
const { funcPath, funcCode, name } = generateSsrFunction(node, appDir, outDir, componentMap, { ancestorLayouts: ssrAncestorLayouts, middlewareCode: mwCode, headExtra: pluginHeadExtra, clientScriptUrl: clientUrl, clientScriptIntegrity: clientIntegrity });
|
|
246
284
|
writeFileSync(funcPath, funcCode, 'utf-8');
|
|
247
285
|
const pagePath = resolve(appDir, node.sourceDir, 'page.vsk');
|
|
248
286
|
if (existsSync(pagePath)) {
|
|
@@ -302,28 +340,6 @@ export async function build(appDir, options) {
|
|
|
302
340
|
const hasEvents = await compileEvents(appDir, outDir);
|
|
303
341
|
if (hasEvents)
|
|
304
342
|
console.error('vesk build: evt → server/events.js (onStart/onRequest/onStop)');
|
|
305
|
-
markPhase('api functions');
|
|
306
|
-
console.error('vesk build: bundling client runtime...');
|
|
307
|
-
const bundleOpts = {};
|
|
308
|
-
if (options?.codeSplit)
|
|
309
|
-
bundleOpts.codeSplit = true;
|
|
310
|
-
if (options?.hmr)
|
|
311
|
-
bundleOpts.hmr = true;
|
|
312
|
-
if (options?.routeDataCache !== undefined)
|
|
313
|
-
bundleOpts.routeDataCache = options.routeDataCache;
|
|
314
|
-
if (pluginsPipelines.length > 0)
|
|
315
|
-
bundleOpts.plugins = pluginsPipelines;
|
|
316
|
-
const { main, chunks } = await generateClientBundle(routeTree, appDir, componentMap, bundleOpts);
|
|
317
|
-
writeFileSync(resolve(outDir, 'static', 'client.js'), main, 'utf-8');
|
|
318
|
-
const mode = chunks.length > 0 ? 'code-split' : 'monolithic';
|
|
319
|
-
console.error(`vesk build: client → static/client.js (${main.length} bytes, ${mode})`);
|
|
320
|
-
if (chunks.length > 0) {
|
|
321
|
-
const staticDir = resolve(outDir, 'static');
|
|
322
|
-
for (const chunk of chunks) {
|
|
323
|
-
writeFileSync(resolve(staticDir, chunk.name), chunk.code, 'utf-8');
|
|
324
|
-
console.error(`vesk build: chunk → static/${chunk.name} (${chunk.code.length} bytes)`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
343
|
copyStaticAssets(publicDir, outDir);
|
|
328
344
|
markPhase('client runtime + chunks');
|
|
329
345
|
console.error('vesk build: static → static/public/');
|
|
@@ -366,6 +382,17 @@ export async function build(appDir, options) {
|
|
|
366
382
|
}
|
|
367
383
|
markPhase('ssg+images+seo');
|
|
368
384
|
const manifest = generateManifest(routeTree, ssrRoutes, apiRoutes, prerenderedRoutes, middlewareEnabled, actionMap, pluginHeadExtra, hasEvents);
|
|
385
|
+
// The asset map is what makes a deploy auditable: sizes, content hashes and
|
|
386
|
+
// the SRI digests a page can carry.
|
|
387
|
+
// The build id is the asset map's own fingerprint: same assets, same id.
|
|
388
|
+
const buildId = emittedAssets.map((a) => a.hash).join('-');
|
|
389
|
+
manifest.buildId = buildId.slice(0, 32);
|
|
390
|
+
manifest.veskVersion = JSON.parse(readFileSync(join(PACKAGES_DIR_SELF(), 'package.json'), 'utf-8')).version;
|
|
391
|
+
manifest.assets = {
|
|
392
|
+
hashed,
|
|
393
|
+
client: { file: clientFileName, url: clientUrl, hash: clientAsset.hash, bytes: clientAsset.bytes, integrity: clientAsset.integrity },
|
|
394
|
+
chunks: emittedAssets.slice(1).map((a) => ({ file: a.fileName, hash: a.hash, bytes: a.bytes, integrity: a.integrity })),
|
|
395
|
+
};
|
|
369
396
|
writeFileSync(resolve(outDir, 'config.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
370
397
|
console.error('vesk build: config → config.json');
|
|
371
398
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAiDjG,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AAkGD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAwdxI"}
|
package/dist/prod-server.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
-
import { resolve, extname, dirname } from 'node:path';
|
|
2
|
+
import { resolve, extname, dirname, basename } from 'node:path';
|
|
3
3
|
import { createServer } from 'node:http';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { createRateLimiter, resolveComponentName, safeJsonForScript, getClientProtocol, DEFAULT_MAX_BODY_BYTES } from '@vesk/compiler/src/server-codegen';
|
|
6
6
|
import { securityHeaders } from '@vesk/compiler/src/server-utils';
|
|
7
|
+
import { cacheControlFor } from '@vesk/adapter/src/asset-hash';
|
|
8
|
+
import { reportServerError } from '@vesk/adapter/src/error-report';
|
|
9
|
+
import { executeError } from '@vesk/adapter/src/events';
|
|
7
10
|
import { resolveWithin, installMdReadHook } from '@vesk/adapter/src/paths';
|
|
8
11
|
import { resolveCssUrls, hasBuiltGlobalCss } from '@vesk/adapter/src/css';
|
|
9
12
|
import { loadVeskConfig } from '@vesk/adapter/src/load-config';
|
|
@@ -287,10 +290,16 @@ export async function startProdServer(outDir, options) {
|
|
|
287
290
|
res.end(lines.join('\n') || '// no ssr data');
|
|
288
291
|
return;
|
|
289
292
|
}
|
|
293
|
+
// Stable alias for the client runtime: the file on disk is content-hashed in
|
|
294
|
+
// production, so the name is resolved from the manifest rather than assumed.
|
|
290
295
|
if (url.pathname === '/_vesk/runtime.js') {
|
|
291
|
-
const
|
|
296
|
+
const clientFile = buildConfig?.assets?.client?.file || 'client.js';
|
|
297
|
+
const clientPath = resolve(staticDir, clientFile);
|
|
292
298
|
if (existsSync(clientPath)) {
|
|
293
|
-
res.writeHead(200, {
|
|
299
|
+
res.writeHead(200, {
|
|
300
|
+
'Content-Type': 'application/javascript',
|
|
301
|
+
'Cache-Control': cacheControlFor(clientFile, false),
|
|
302
|
+
});
|
|
294
303
|
res.end(readFileSync(clientPath));
|
|
295
304
|
return;
|
|
296
305
|
}
|
|
@@ -305,7 +314,13 @@ export async function startProdServer(outDir, options) {
|
|
|
305
314
|
}
|
|
306
315
|
if (existsSync(staticPath) && statSync(staticPath).isFile()) {
|
|
307
316
|
const ext = extname(staticPath);
|
|
308
|
-
res.writeHead(200, {
|
|
317
|
+
res.writeHead(200, {
|
|
318
|
+
'Content-Type': MIME[ext] || 'application/octet-stream',
|
|
319
|
+
// A content-hashed name can never change meaning, so it is immutable
|
|
320
|
+
// for a year; anything unhashed must be revalidated or a deploy would
|
|
321
|
+
// serve stale JS forever.
|
|
322
|
+
'Cache-Control': cacheControlFor(basename(staticPath), false),
|
|
323
|
+
});
|
|
309
324
|
res.end(readFileSync(staticPath));
|
|
310
325
|
return;
|
|
311
326
|
}
|
|
@@ -498,6 +513,19 @@ export async function startProdServer(outDir, options) {
|
|
|
498
513
|
return;
|
|
499
514
|
}
|
|
500
515
|
console.error('vesk ssr error:', err.message);
|
|
516
|
+
// Two seams, both best-effort: a plugin's `onError` (dev-style
|
|
517
|
+
// plugin objects don't exist here) and `_events.ts`'s, which IS
|
|
518
|
+
// baked into the production build.
|
|
519
|
+
await reportServerError(err, {
|
|
520
|
+
url: url.pathname + url.search,
|
|
521
|
+
routePath: route.path,
|
|
522
|
+
target: 'node',
|
|
523
|
+
status: 500,
|
|
524
|
+
buildConfig,
|
|
525
|
+
});
|
|
526
|
+
if (eventsMod) {
|
|
527
|
+
await executeError(err, { url: url.href, routePath: route.path, target: 'node', status: 500 }, eventsMod);
|
|
528
|
+
}
|
|
501
529
|
const errPath = resolve(appDir, 'error.vsk');
|
|
502
530
|
let errorHtml = null;
|
|
503
531
|
if (existsSync(errPath)) {
|
|
@@ -508,7 +536,7 @@ export async function startProdServer(outDir, options) {
|
|
|
508
536
|
const compName = resolveComponentName(src) || 'Error';
|
|
509
537
|
// never leak stack traces / internal messages to prod error pages
|
|
510
538
|
const expose = process.env.NODE_ENV !== 'production';
|
|
511
|
-
errorHtml = await renderFullPage(src, compName, { error: expose ? err.message : 'Internal Server Error', stack: expose ? err.stack : '', statusCode: 500, url: url.pathname }, new Map(), { hydrate: true, cssUrls: pageCssUrls, clientScriptUrl: '/_vesk/static/client.js', security: securityConfig?.security || {}, externalDataScript: storeDataScriptGlobal, sourcePath: errPath, headExtra: bakedHeadExtra });
|
|
539
|
+
errorHtml = await renderFullPage(src, compName, { error: expose ? err.message : 'Internal Server Error', stack: expose ? err.stack : '', statusCode: 500, url: url.pathname }, new Map(), { hydrate: true, cssUrls: pageCssUrls, clientScriptUrl: buildConfig?.assets?.client?.url || '/_vesk/static/client.js', clientScriptIntegrity: buildConfig?.assets?.client?.integrity, security: securityConfig?.security || {}, externalDataScript: storeDataScriptGlobal, sourcePath: errPath, headExtra: bakedHeadExtra });
|
|
512
540
|
}
|
|
513
541
|
catch { }
|
|
514
542
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAI7F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjF;AAgED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,kBAAkB,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAI7F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjF;AAgED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,kBAAkB,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAwZtD"}
|
package/dist/ssr-function.js
CHANGED
|
@@ -134,7 +134,10 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
|
|
|
134
134
|
const urlParts = routeNode.fullPath.split('/').filter(Boolean);
|
|
135
135
|
const paramExprs = buildParamExtraction(routeNode, urlParts);
|
|
136
136
|
const paramsCode = `function __paramsFor(pathname) {\n const urlParts = pathname.split('/').filter(Boolean);\n return { ${paramExprs.join(', ')} };\n}\n`;
|
|
137
|
-
|
|
137
|
+
// Content-hashed in production (the build computes it before generating the
|
|
138
|
+
// functions), the stable name in dev.
|
|
139
|
+
const clientScriptOption = `, clientScriptUrl: ${JSON.stringify(options?.clientScriptUrl || '/_vesk/static/client.js')}` +
|
|
140
|
+
(options?.clientScriptIntegrity ? `, clientScriptIntegrity: ${JSON.stringify(options.clientScriptIntegrity)}` : '');
|
|
138
141
|
const dataScriptOption = ', externalDataScript: storeDataScriptGlobal';
|
|
139
142
|
let registryCode = '';
|
|
140
143
|
const compRegEntries = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vesk/adapter",
|
|
3
|
-
"version": "0.2.50-canary.
|
|
3
|
+
"version": "0.2.50-canary.4",
|
|
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.50-canary.
|
|
27
|
-
"@vesk/runtime": "^0.2.50-canary.
|
|
28
|
-
"@vesk/types": "^0.2.50-canary.
|
|
26
|
+
"@vesk/compiler": "^0.2.50-canary.4",
|
|
27
|
+
"@vesk/runtime": "^0.2.50-canary.4",
|
|
28
|
+
"@vesk/types": "^0.2.50-canary.4"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/ws": "^8.18.1",
|