@vesk/adapter 0.2.13 → 0.2.16

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/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
2
2
  import { resolve, dirname, relative, basename } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { cssBlockEnd } from '@vesk/compiler/src/scan';
5
4
  import { bundleRuntime } from '@vesk/adapter/src/runtime-bundle';
6
5
  import { generateSsrFunction } from '@vesk/adapter/src/ssr-function';
6
+ import { resolveUserCssPath, stripTailwindDirectives, isTailwindPlugin, resolveCssUrls, hasUserCss, hasBuiltTailwindCss } 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
9
  import { compileMiddleware, compileMiddlewareCode } from '@vesk/adapter/src/middleware';
10
+ import { compileEvents } from '@vesk/adapter/src/events';
10
11
  import { generateClientBundle } from '@vesk/adapter/src/client-bundle';
11
12
  import { generateManifest } from '@vesk/adapter/src/manifest';
12
13
  import { copyStaticAssets } from '@vesk/adapter/src/static';
@@ -61,6 +62,28 @@ function veskDirFromOutDir(outDir) {
61
62
  return dirname(outDir);
62
63
  return outDir;
63
64
  }
65
+ /**
66
+ * Invoke every active plugin's `onHead` once with an empty head to capture its
67
+ * static head contribution. The result is baked into generated SSR functions
68
+ * and `config.json` (`headExtra`) for the prod path, where plugin objects are
69
+ * not available at request time.
70
+ */
71
+ async function collectPluginHeadExtra(pluginsPipelines) {
72
+ const parts = [];
73
+ for (const plugin of pluginsPipelines) {
74
+ if (typeof plugin.onHead !== 'function')
75
+ continue;
76
+ try {
77
+ const result = await plugin.onHead('', { sourcePath: 'build' });
78
+ if (typeof result === 'string' && result.trim())
79
+ parts.push(result);
80
+ }
81
+ catch {
82
+ // a failing onHead at bake time must not break the build
83
+ }
84
+ }
85
+ return parts.join('\n');
86
+ }
64
87
  export async function build(appDir, options) {
65
88
  appDir = resolve(appDir);
66
89
  const outDir = resolve(options?.outDir || resolve(appDir, '..', '.vesk'));
@@ -111,6 +134,13 @@ export async function build(appDir, options) {
111
134
  await plugin.onBuildStart();
112
135
  }
113
136
  }
137
+ // Bake the active `onHead` plugins' static head contribution for the prod
138
+ // SSR path (generated SSR functions + prod-server not-found/error pages have
139
+ // no in-process plugin objects, so their head snippets ship build-time).
140
+ // Live hooks still run in dev / build / SSG where plugin objects exist.
141
+ const pluginHeadExtra = await collectPluginHeadExtra(pluginsPipelines);
142
+ if (pluginHeadExtra)
143
+ console.error('vesk build: baked plugin head → config.json + SSR functions');
114
144
  console.error(`vesk build: output → ${outDir}`);
115
145
  const dirs = [
116
146
  resolve(outDir, 'server', 'functions'),
@@ -120,6 +150,48 @@ export async function build(appDir, options) {
120
150
  ];
121
151
  for (const d of dirs)
122
152
  mkdirSync(d, { recursive: true });
153
+ const cssSourcePath = resolveUserCssPath(appDir);
154
+ if (cssSourcePath !== null) {
155
+ const cssContent = readFileSync(cssSourcePath, 'utf-8');
156
+ const userCss = stripTailwindDirectives(cssContent);
157
+ const userCssTarget = resolve(outDir, 'static', 'global.css');
158
+ writeFileSync(userCssTarget, userCss, 'utf-8');
159
+ console.error(`vesk build: css → static/global.css (${userCss.length} bytes)`);
160
+ const twCssTarget = resolve(outDir, 'static', '_tailwind.css');
161
+ const tailwindActive = isTailwindPlugin(pluginsPipelines);
162
+ if (!tailwindActive) {
163
+ writeFileSync(twCssTarget, '', 'utf-8');
164
+ console.error('vesk build: css → static/_tailwind.css (empty, tailwind plugin inactive)');
165
+ }
166
+ else {
167
+ let twCss = cssContent;
168
+ for (const plugin of pluginsPipelines) {
169
+ if (typeof plugin.onCSS === 'function') {
170
+ const result = await plugin.onCSS(twCss, cssSourcePath);
171
+ if (result !== null && typeof result === 'string') {
172
+ twCss = result;
173
+ }
174
+ }
175
+ }
176
+ const hasUnresolvedTailwindImport = /@import\s+['"]tailwindcss['"]/.test(twCss);
177
+ if (hasUnresolvedTailwindImport) {
178
+ const lines = twCss.split('\n').filter(l => !/^\s*@import\s+['"]tailwindcss['"]/.test(l));
179
+ twCss = lines.join('\n').trim();
180
+ if (twCss.length === 0) {
181
+ writeFileSync(twCssTarget, '', 'utf-8');
182
+ console.error('vesk build: css → static/_tailwind.css (empty, tailwind unresolved)');
183
+ }
184
+ else {
185
+ writeFileSync(twCssTarget, twCss, 'utf-8');
186
+ console.error(`vesk build: css → static/_tailwind.css (${twCss.length} bytes, tailwind partially unresolved)`);
187
+ }
188
+ }
189
+ else {
190
+ writeFileSync(twCssTarget, twCss, 'utf-8');
191
+ console.error(`vesk build: css → static/_tailwind.css (${twCss.length} bytes)`);
192
+ }
193
+ }
194
+ }
123
195
  const { scanRoutes, scanComponents } = await resolveCompilerApi('router.js');
124
196
  const { scanApiRoutes } = await resolveCompilerApi('api-routes.js');
125
197
  const { collectMiddlewareChain } = await resolveCompilerApi('middleware.js');
@@ -153,7 +225,7 @@ export async function build(appDir, options) {
153
225
  const mwSources = mwChain.map((m) => readFileSync(m.sourcePath, 'utf-8'));
154
226
  mwCode = compileMiddlewareCode(mwSources);
155
227
  }
156
- const { funcPath, funcCode, name } = generateSsrFunction(node, appDir, outDir, componentMap, { ancestorLayouts, middlewareCode: mwCode });
228
+ const { funcPath, funcCode, name } = generateSsrFunction(node, appDir, outDir, componentMap, { ancestorLayouts, middlewareCode: mwCode, headExtra: pluginHeadExtra });
157
229
  writeFileSync(funcPath, funcCode, 'utf-8');
158
230
  const pagePath = resolve(appDir, node.sourceDir, 'page.vsk');
159
231
  if (existsSync(pagePath)) {
@@ -209,6 +281,9 @@ export async function build(appDir, options) {
209
281
  console.error(`vesk build: mw → server/middleware.js (${mwChain.length} middlewares)`);
210
282
  }
211
283
  }
284
+ const hasEvents = await compileEvents(appDir, outDir);
285
+ if (hasEvents)
286
+ console.error('vesk build: evt → server/events.js (onStart/onRequest/onStop)');
212
287
  console.error('vesk build: bundling client runtime...');
213
288
  const bundleOpts = {};
214
289
  if (options?.codeSplit)
@@ -217,6 +292,8 @@ export async function build(appDir, options) {
217
292
  bundleOpts.hmr = true;
218
293
  if (options?.routeDataCache !== undefined)
219
294
  bundleOpts.routeDataCache = options.routeDataCache;
295
+ if (pluginsPipelines.length > 0)
296
+ bundleOpts.plugins = pluginsPipelines;
220
297
  const { main, chunks } = await generateClientBundle(routeTree, appDir, componentMap, bundleOpts);
221
298
  writeFileSync(resolve(outDir, 'static', 'client.js'), main, 'utf-8');
222
299
  const mode = chunks.length > 0 ? 'code-split' : 'monolithic';
@@ -230,82 +307,14 @@ export async function build(appDir, options) {
230
307
  }
231
308
  copyStaticAssets(publicDir, outDir);
232
309
  console.error('vesk build: static → static/public/');
233
- const srcDir = resolve(appDir, '..', 'src');
234
- const cssSrc = resolve(srcDir, 'global.css');
235
- const altCssSrc = resolve(srcDir, 'app.css');
236
- let cssContent = null;
237
- let cssSourcePath = null;
238
- if (existsSync(cssSrc)) {
239
- cssContent = readFileSync(cssSrc, 'utf-8');
240
- cssSourcePath = cssSrc;
241
- }
242
- else if (existsSync(altCssSrc)) {
243
- cssContent = readFileSync(altCssSrc, 'utf-8');
244
- cssSourcePath = altCssSrc;
245
- }
246
- function stripTailwindDirectives(css) {
247
- const blockStart = /^\s*@(theme\s*\{|layer\s+(components|utilities)\s*\{|utility\s+\w+\s*\{)/;
248
- let result = css.replace(/^\s*@import\s+['"]tailwindcss['"]\s*;?\s*$/gm, '');
249
- result = result.replace(/^\s*@source\s+['"][^'"]+['"]\s*;?\s*$/gm, '');
250
- const output = [];
251
- let pos = 0;
252
- while (pos < result.length) {
253
- const lineEnd = result.indexOf('\n', pos) === -1 ? result.length : result.indexOf('\n', pos) + 1;
254
- const line = result.slice(pos, lineEnd);
255
- if (blockStart.test(line.trim())) {
256
- const end = cssBlockEnd(result, pos);
257
- pos = end;
258
- continue;
259
- }
260
- output.push(line);
261
- pos = lineEnd;
262
- }
263
- return output.join('').trim();
264
- }
265
- if (cssContent !== null) {
266
- const userCss = stripTailwindDirectives(cssContent);
267
- const userCssTarget = resolve(outDir, 'static', 'global.css');
268
- writeFileSync(userCssTarget, userCss, 'utf-8');
269
- console.error(`vesk build: css → static/global.css (${userCss.length} bytes)`);
270
- const twCssTarget = resolve(outDir, 'static', '_tailwind.css');
271
- const isTailwindActive = pluginsPipelines.some((p) => String(p.name).toLowerCase().includes('tailwind'));
272
- if (!isTailwindActive) {
273
- writeFileSync(twCssTarget, '', 'utf-8');
274
- console.error('vesk build: css → static/_tailwind.css (empty, tailwind plugin inactive)');
275
- }
276
- else {
277
- let twCss = cssContent;
278
- for (const plugin of pluginsPipelines) {
279
- if (typeof plugin.onCSS === 'function') {
280
- const result = await plugin.onCSS(twCss, cssSourcePath);
281
- if (result !== null && typeof result === 'string') {
282
- twCss = result;
283
- }
284
- }
285
- }
286
- const hasUnresolvedTailwindImport = /@import\s+['"]tailwindcss['"]/.test(twCss);
287
- if (hasUnresolvedTailwindImport) {
288
- const lines = twCss.split('\n').filter(l => !/^\s*@import\s+['"]tailwindcss['"]/.test(l));
289
- twCss = lines.join('\n').trim();
290
- if (twCss.length === 0) {
291
- writeFileSync(twCssTarget, '', 'utf-8');
292
- console.error('vesk build: css → static/_tailwind.css (empty, tailwind unresolved)');
293
- }
294
- else {
295
- writeFileSync(twCssTarget, twCss, 'utf-8');
296
- console.error(`vesk build: css → static/_tailwind.css (${twCss.length} bytes, tailwind partially unresolved)`);
297
- }
298
- }
299
- else {
300
- writeFileSync(twCssTarget, twCss, 'utf-8');
301
- console.error(`vesk build: css → static/_tailwind.css (${twCss.length} bytes)`);
302
- }
303
- }
304
- }
305
310
  let prerenderedRoutes = [];
306
311
  if (options?.ssg) {
307
312
  const { generateSsgRoutes } = await import('./static.js');
308
- prerenderedRoutes = await generateSsgRoutes(routeTree, appDir, outDir);
313
+ const cssUrls = resolveCssUrls({
314
+ tailwind: hasBuiltTailwindCss(outDir),
315
+ userCss: hasUserCss(appDir),
316
+ });
317
+ prerenderedRoutes = await generateSsgRoutes(routeTree, appDir, outDir, pluginsPipelines, pluginHeadExtra, cssUrls);
309
318
  console.error(`vesk build: ssg → prerendered/ (${prerenderedRoutes.length} pages)`);
310
319
  }
311
320
  {
@@ -340,7 +349,7 @@ export async function build(appDir, options) {
340
349
  console.error(`vesk build: seo → static/public/robots.txt (${robots.length} bytes)`);
341
350
  }
342
351
  }
343
- const manifest = generateManifest(routeTree, ssrRoutes, apiRoutes, prerenderedRoutes, middlewareEnabled, actionMap);
352
+ const manifest = generateManifest(routeTree, ssrRoutes, apiRoutes, prerenderedRoutes, middlewareEnabled, actionMap, pluginHeadExtra, hasEvents);
344
353
  writeFileSync(resolve(outDir, 'config.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
345
354
  console.error('vesk build: config → config.json');
346
355
  {
@@ -357,6 +366,7 @@ export async function build(appDir, options) {
357
366
  prerenderedPaths: prerenderedRoutes.map(r => r.path),
358
367
  prerenderedRoutes,
359
368
  hasMiddleware: middlewareEnabled,
369
+ hasEvents,
360
370
  });
361
371
  if (outRoot) {
362
372
  console.error(`vesk build: ${platform} → ${relative(projectRoot, outRoot)}`);
@@ -1,3 +1,3 @@
1
1
  import type { RouteNode, ApiRouteNode, Manifest, SsgRouteResult } from '@vesk/adapter/src/types';
2
- export declare function generateManifest(routes: RouteNode[], ssrRoutes: RouteNode[], apiRoutes: ApiRouteNode[], staticRoutes: SsgRouteResult[], middlewareEnabled: boolean, actionMap?: Record<string, string>): Manifest;
2
+ export declare function generateManifest(routes: RouteNode[], ssrRoutes: RouteNode[], apiRoutes: ApiRouteNode[], staticRoutes: SsgRouteResult[], middlewareEnabled: boolean, actionMap?: Record<string, string>, headExtra?: string, eventsEnabled?: boolean): Manifest;
3
3
  //# sourceMappingURL=manifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAqE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEpK,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,SAAS,EAAE,SAAS,EAAE,EACtB,SAAS,EAAE,YAAY,EAAE,EACzB,YAAY,EAAE,cAAc,EAAE,EAC9B,iBAAiB,EAAE,OAAO,EAC1B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,QAAQ,CAiDV"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAqE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEpK,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,SAAS,EAAE,SAAS,EAAE,EACtB,SAAS,EAAE,YAAY,EAAE,EACzB,YAAY,EAAE,cAAc,EAAE,EAC9B,iBAAiB,EAAE,OAAO,EAC1B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,SAAS,CAAC,EAAE,MAAM,EAClB,aAAa,UAAQ,GACpB,QAAQ,CAmDV"}
package/dist/manifest.js CHANGED
@@ -1,4 +1,4 @@
1
- export function generateManifest(routes, ssrRoutes, apiRoutes, staticRoutes, middlewareEnabled, actionMap) {
1
+ export function generateManifest(routes, ssrRoutes, apiRoutes, staticRoutes, middlewareEnabled, actionMap, headExtra, eventsEnabled = false) {
2
2
  const routeEntries = [];
3
3
  for (const r of ssrRoutes) {
4
4
  const urlParts = r.fullPath.split('/').filter(Boolean);
@@ -43,5 +43,7 @@ export function generateManifest(routes, ssrRoutes, apiRoutes, staticRoutes, mid
43
43
  dir: 'static',
44
44
  },
45
45
  ...(actionEntries.length > 0 ? { actions: actionEntries } : {}),
46
+ ...(headExtra ? { headExtra } : {}),
47
+ ...(eventsEnabled ? { events: true } : {}),
46
48
  };
47
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"platform-deploy.d.ts","sourceRoot":"","sources":["../src/platform-deploy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwD,KAAK,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAErI,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAE3D,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,iBAAiB,EAAE,cAAc,EAAE,CAAC;CACrC;AA0BD;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyEvG"}
1
+ {"version":3,"file":"platform-deploy.d.ts","sourceRoot":"","sources":["../src/platform-deploy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwD,KAAK,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAErI,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAE3D,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,iBAAiB,EAAE,cAAc,EAAE,CAAC;CACrC;AA0BD;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0EvG"}
@@ -21,6 +21,7 @@ export async function emitPlatformOutput(platform, ctx) {
21
21
  apiRoutes: ctx.apiRoutes,
22
22
  prerenderedPaths,
23
23
  hasMiddleware: ctx.hasMiddleware,
24
+ hasEvents: ctx.hasEvents,
24
25
  });
25
26
  const shell = shellFor(platform);
26
27
  const projectRoot = resolve(ctx.outDir, '..');
@@ -4,6 +4,7 @@ export interface PlatformHandlerInput {
4
4
  apiRoutes: ApiRouteNode[];
5
5
  prerenderedPaths: string[];
6
6
  hasMiddleware: boolean;
7
+ hasEvents?: boolean;
7
8
  }
8
9
  export declare function routeName(segments: string[]): string;
9
10
  export declare function apiRouteName(fullPath: string): string;
@@ -28,5 +29,6 @@ export interface PlatformBuildContext {
28
29
  apiRoutes: ApiRouteNode[];
29
30
  prerenderedPaths: string[];
30
31
  hasMiddleware: boolean;
32
+ hasEvents?: boolean;
31
33
  }
32
34
  //# sourceMappingURL=platform-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform-handler.d.ts","sourceRoot":"","sources":["../src/platform-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAIvE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMpD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AAQD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAiIjF;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFzF;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;CACxB"}
1
+ {"version":3,"file":"platform-handler.d.ts","sourceRoot":"","sources":["../src/platform-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAIvE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMpD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AAQD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CA8IjF;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFzF;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB"}
@@ -31,6 +31,7 @@ function findCompilerSrc() {
31
31
  */
32
32
  export function generatePlatformHandlerSource(input) {
33
33
  const { ssrRoutes, apiRoutes, prerenderedPaths, hasMiddleware } = input;
34
+ const hasEvents = !!input.hasEvents;
34
35
  let imports = '';
35
36
  const routeEntries = [];
36
37
  for (const r of ssrRoutes) {
@@ -51,10 +52,15 @@ export function generatePlatformHandlerSource(input) {
51
52
  }
52
53
  const mwImport = hasMiddleware ? "import { execute as __executeMw } from './server/middleware.js';" : '';
53
54
  const hasMwLiteral = hasMiddleware ? 'true' : 'false';
55
+ const eventsImport = hasEvents ? "import { executeStart as __eventsStart, executeRequest as __eventsRequest } from './server/events.js';" : '';
54
56
  const prerenderedList = prerenderedPaths.length > 0
55
57
  ? `const __prerendered = new Set(${JSON.stringify(prerenderedPaths)});\n`
56
58
  : 'const __prerendered = new Set();\n';
57
59
  const isrCache = 'const __isrCache = new Map();';
60
+ const serverStoreLine = hasEvents ? 'const __serverStore = globalThis.__vesk_server_ctx || {};' : '';
61
+ const eventsStartLine = hasEvents ? ' await __eventsStart({ server: null });' : '';
62
+ const eventsRequestLine = hasEvents ? ' await __eventsRequest(mwCtx);' : '';
63
+ const localsSeed = hasEvents ? 'Object.assign({}, __serverStore)' : '{}';
58
64
  const compilerSrc = findCompilerSrc();
59
65
  const parseCookiesImport = hasMiddleware
60
66
  ? `import { parseCookies } from ${JSON.stringify(resolve(compilerSrc, 'server-cookies.js'))};`
@@ -63,6 +69,7 @@ export function generatePlatformHandlerSource(input) {
63
69
  ${imports}
64
70
  ${parseCookiesImport}
65
71
  ${mwImport}
72
+ ${eventsImport}
66
73
  ${prerenderedList}
67
74
  ${isrCache}
68
75
  const __routes = [${routeEntries.join(',\n')}];
@@ -91,23 +98,28 @@ export async function handleRequest(request) {
91
98
  const url = new URL(request.url);
92
99
  const pathname = url.pathname;
93
100
  const isDataRequest = request.headers.get('x-vesk-data') === '1';
101
+ ${serverStoreLine}
94
102
 
95
103
  if (__prerendered.has(pathname) && !isDataRequest) {
96
104
  return new Response(null, { status: 308, headers: { Location: '/_vesk/static/public' + (pathname.endsWith('/') ? pathname + 'index.html' : pathname + '.html') } });
97
105
  }
98
106
 
99
- let mwCtx = { params: {}, url, locals: {}, cookies: {}, request, resolveUrl(u) { return new URL(u, request.url).href; }, set() {}, get() { return undefined; } };
107
+ ${eventsStartLine}
108
+ let mwCtx = { params: {}, url, locals: ${localsSeed}, cookies: {}, request, resolveUrl(u) { return new URL(u, request.url).href; }, set() {}, get() { return undefined; } };
100
109
  if (${hasMwLiteral}) {
101
110
  mwCtx = {
102
111
  request,
103
112
  params: {},
104
113
  url,
105
- locals: {},
114
+ locals: ${localsSeed},
106
115
  cookies: typeof parseCookies !== 'undefined' ? parseCookies(request.headers.get('cookie') || '') : {},
107
116
  resolveUrl(u) { return new URL(u, request.url).href; },
108
117
  set(key, value) { this.locals[key] = value; },
109
118
  get(key) { return this.locals[key]; },
110
119
  };
120
+ }
121
+ ${eventsRequestLine}
122
+ if (${hasMwLiteral}) {
111
123
  const mwResult = await __executeMw(mwCtx);
112
124
  if (mwResult.response) return mwResult.response;
113
125
  if (mwResult.rewriteUrl) url.pathname = mwResult.rewriteUrl;
@@ -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;AA8CjG,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AA4FD,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,CAiZxI"}
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;AA+CjG,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AA+FD,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,CAmcxI"}
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
6
6
  import { createRateLimiter, resolveComponentName, safeJsonForScript, getClientProtocol, DEFAULT_MAX_BODY_BYTES } from '@vesk/compiler/src/server-codegen';
7
7
  import { securityHeaders } from '@vesk/compiler/src/server-utils';
8
8
  import { resolveWithin, installMdReadHook } from '@vesk/adapter/src/paths';
9
+ import { resolveCssUrls, hasUserCss, hasBuiltTailwindCss } from '@vesk/adapter/src/css';
9
10
  const _require = createRequire(import.meta.url);
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
12
  async function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
@@ -111,6 +112,7 @@ const MIME = {
111
112
  '.svg': 'image/svg+xml', '.css': 'text/css', '.js': 'application/javascript',
112
113
  '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
113
114
  '.ico': 'image/x-icon', '.html': 'text/html', '.json': 'application/json',
115
+ '.webmanifest': 'application/manifest+json',
114
116
  '.woff': 'font/woff', '.woff2': 'font/woff2', '.wasm': 'application/wasm',
115
117
  };
116
118
  export async function startProdServer(outDir, options) {
@@ -129,6 +131,10 @@ export async function startProdServer(outDir, options) {
129
131
  process.exit(1);
130
132
  }
131
133
  const buildConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
134
+ // Baked onHead-plugin head snippet (config.json `headExtra`) — applied to
135
+ // pages that render directly here (not-found / error) where no SSR function
136
+ // and no in-process plugin objects exist.
137
+ const bakedHeadExtra = buildConfig.headExtra || '';
132
138
  console.error(`vesk start: serving from ${outDir}`);
133
139
  const projectDir = resolve(outDir, '..');
134
140
  let securityConfig = {};
@@ -186,6 +192,16 @@ export async function startProdServer(outDir, options) {
186
192
  // ignore
187
193
  }
188
194
  }
195
+ let eventsMod = null;
196
+ const eventsPath = resolve(outDir, 'server', 'events.js');
197
+ if (existsSync(eventsPath)) {
198
+ try {
199
+ eventsMod = await import(`${eventsPath}?t=${Date.now()}`);
200
+ }
201
+ catch {
202
+ // ignore
203
+ }
204
+ }
189
205
  const functionCache = new Map();
190
206
  async function loadFunction(funcPath) {
191
207
  if (functionCache.has(funcPath))
@@ -319,38 +335,49 @@ export async function startProdServer(outDir, options) {
319
335
  }
320
336
  }
321
337
  let mwCtx;
338
+ const serverStore = globalThis.__vesk_server_ctx;
322
339
  if (middlewareMod) {
323
340
  mwCtx = {
324
341
  request: new Request(url.href, { headers: req.headers, method: req.method || 'GET' }),
325
342
  params: {},
326
343
  url,
327
- locals: {},
344
+ locals: Object.assign({}, serverStore || {}),
328
345
  cookies: {},
329
346
  set(key, value) { this.locals[key] = value; },
330
347
  get(key) { return this.locals[key]; },
331
348
  };
332
- const mwResult = await middlewareMod.execute(mwCtx);
333
- if (mwResult.response) {
334
- const body = await mwResult.response.text();
335
- res.writeHead(mwResult.response.status, Object.fromEntries(mwResult.response.headers));
336
- res.end(body);
337
- return;
338
- }
339
- if (mwResult.rewriteUrl) {
340
- url.pathname = mwResult.rewriteUrl;
341
- }
342
349
  }
343
350
  else {
344
351
  mwCtx = {
345
352
  params: {},
346
353
  url,
347
- locals: {},
354
+ locals: Object.assign({}, serverStore || {}),
348
355
  cookies: {},
349
356
  request: null,
350
357
  set() { },
351
358
  get() { return undefined; },
352
359
  };
353
360
  }
361
+ if (eventsMod) {
362
+ try {
363
+ await eventsMod.executeRequest?.(mwCtx);
364
+ }
365
+ catch (e) {
366
+ console.error('vesk request: onRequest error:', e instanceof Error ? e.message : e);
367
+ }
368
+ }
369
+ if (middlewareMod) {
370
+ const mwResult = await middlewareMod.execute(mwCtx);
371
+ if (mwResult.response) {
372
+ const body = await mwResult.response.text();
373
+ res.writeHead(mwResult.response.status, Object.fromEntries(mwResult.response.headers));
374
+ res.end(body);
375
+ return;
376
+ }
377
+ if (mwResult.rewriteUrl) {
378
+ url.pathname = mwResult.rewriteUrl;
379
+ }
380
+ }
354
381
  // Expose root middleware locals to API/SSR handlers (which seed their own
355
382
  // ctx.locals from this before running route-level middleware).
356
383
  globalThis.__vesk_request = mwCtx;
@@ -408,6 +435,10 @@ export async function startProdServer(outDir, options) {
408
435
  }
409
436
  }
410
437
  const appDir = resolve(projectDir, 'app');
438
+ const pageCssUrls = resolveCssUrls({
439
+ tailwind: hasBuiltTailwindCss(outDir),
440
+ userCss: hasUserCss(appDir),
441
+ });
411
442
  const nfPath = resolve(appDir, 'not-found.vsk');
412
443
  let notFoundHtml = null;
413
444
  if (existsSync(nfPath)) {
@@ -416,13 +447,7 @@ export async function startProdServer(outDir, options) {
416
447
  const { renderFullPage, storeDataScriptGlobal } = await import(runtimePath);
417
448
  const src = readFileSync(nfPath, 'utf-8');
418
449
  const compName = resolveComponentName(src) || 'NotFound';
419
- const tailwindPathNF = resolve(outDir, 'static', '_tailwind.css');
420
- const hasTailwindNF = existsSync(tailwindPathNF) && readFileSync(tailwindPathNF, 'utf-8').trim().length > 0;
421
- const cssUrlsNF = [];
422
- if (hasTailwindNF)
423
- cssUrlsNF.push('/_vesk/static/_tailwind.css');
424
- cssUrlsNF.push('/_vesk/static/global.css');
425
- notFoundHtml = await renderFullPage(src, compName, { params: {}, url: url.pathname }, new Map(), { hydrate: true, cssUrls: cssUrlsNF, security: securityConfig?.security || {}, externalDataScript: storeDataScriptGlobal, sourcePath: nfPath });
450
+ notFoundHtml = await renderFullPage(src, compName, { params: {}, url: url.pathname }, new Map(), { hydrate: true, cssUrls: pageCssUrls, security: securityConfig?.security || {}, externalDataScript: storeDataScriptGlobal, sourcePath: nfPath, headExtra: bakedHeadExtra });
426
451
  }
427
452
  catch { }
428
453
  }
@@ -491,13 +516,7 @@ export async function startProdServer(outDir, options) {
491
516
  const compName = resolveComponentName(src) || 'Error';
492
517
  // never leak stack traces / internal messages to prod error pages
493
518
  const expose = process.env.NODE_ENV !== 'production';
494
- const tailwindPathErr = resolve(outDir, 'static', '_tailwind.css');
495
- const hasTailwindErr = existsSync(tailwindPathErr) && readFileSync(tailwindPathErr, 'utf-8').trim().length > 0;
496
- const cssUrlsErr = [];
497
- if (hasTailwindErr)
498
- cssUrlsErr.push('/_vesk/static/_tailwind.css');
499
- cssUrlsErr.push('/_vesk/static/global.css');
500
- 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: cssUrlsErr, clientScriptUrl: '/_vesk/static/client.js', security: securityConfig?.security || {}, externalDataScript: storeDataScriptGlobal, sourcePath: errPath });
519
+ 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 });
501
520
  }
502
521
  catch { }
503
522
  }
@@ -512,6 +531,33 @@ export async function startProdServer(outDir, options) {
512
531
  res.writeHead(404, { 'Content-Type': 'text/html' });
513
532
  res.end(notFoundHtml || '<!DOCTYPE html><html><body><h1>404</h1><p>Not Found</p></body></html>');
514
533
  });
534
+ try {
535
+ await eventsMod?.executeStart?.({ server, port, host });
536
+ }
537
+ catch (e) {
538
+ console.error('vesk start: onStart error:', e instanceof Error ? e.message : e);
539
+ }
540
+ server.on('close', () => {
541
+ void (async () => {
542
+ try {
543
+ await eventsMod?.executeStop?.({ server, port, host });
544
+ }
545
+ catch (e) {
546
+ console.error('vesk stop: onStop error:', e instanceof Error ? e.message : e);
547
+ }
548
+ })();
549
+ });
550
+ let shuttingDown = false;
551
+ function shutdown(signal) {
552
+ if (shuttingDown)
553
+ return;
554
+ shuttingDown = true;
555
+ console.error(`vesk stop: received ${signal}, closing…`);
556
+ server.close(() => process.exit(0));
557
+ setTimeout(() => process.exit(0), 3000).unref();
558
+ }
559
+ process.on('SIGINT', () => shutdown('SIGINT'));
560
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
515
561
  server.listen(port, host, () => {
516
562
  console.error(`vesk production server at http://localhost:${port} (listening on ${host})`);
517
563
  });
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-bundle.d.ts","sourceRoot":"","sources":["../src/runtime-bundle.ts"],"names":[],"mappings":"AAuCA,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2GnF"}
1
+ {"version":3,"file":"runtime-bundle.d.ts","sourceRoot":"","sources":["../src/runtime-bundle.ts"],"names":[],"mappings":"AAuCA,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqHnF"}
@@ -113,6 +113,16 @@ export async function bundleRuntime(appDir, outDir) {
113
113
  'export const clearActions = __veskRuntime.clearActions;',
114
114
  'export const validateActionInput = __veskRuntime.validateActionInput;',
115
115
  'export const issuesToFieldMap = __veskRuntime.issuesToFieldMap;',
116
+ '',
117
+ '// Server-wide context store (server events)',
118
+ 'export const serverLocals = __veskRuntime.serverLocals;',
119
+ 'export const getServerContext = __veskRuntime.getServerContext;',
120
+ 'export const setServerContext = __veskRuntime.setServerContext;',
121
+ 'export const clearServerContext = __veskRuntime.clearServerContext;',
122
+ 'export const server_locals = __veskRuntime.server_locals;',
123
+ 'export const get_server_context = __veskRuntime.get_server_context;',
124
+ 'export const set_server_context = __veskRuntime.set_server_context;',
125
+ 'export const clear_server_context = __veskRuntime.clear_server_context;',
116
126
  ].join('\n');
117
127
  writeFileSync(entryFile, entryContent, 'utf-8');
118
128
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAW7F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjF;AAiDD,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,CAqXtD"}
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;AAW7F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjF;AAiDD,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,CAsXtD"}