@vesk/adapter 0.0.1

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.
Files changed (60) hide show
  1. package/README.md +21 -0
  2. package/dist/api-function.d.ts +7 -0
  3. package/dist/api-function.d.ts.map +1 -0
  4. package/dist/api-function.js +187 -0
  5. package/dist/client-bundle.d.ts +19 -0
  6. package/dist/client-bundle.d.ts.map +1 -0
  7. package/dist/client-bundle.js +491 -0
  8. package/dist/dev-server.d.ts +4 -0
  9. package/dist/dev-server.d.ts.map +1 -0
  10. package/dist/dev-server.js +358 -0
  11. package/dist/esbuild-fallback.d.ts +3 -0
  12. package/dist/esbuild-fallback.d.ts.map +1 -0
  13. package/dist/esbuild-fallback.js +64 -0
  14. package/dist/hmr.d.ts +7 -0
  15. package/dist/hmr.d.ts.map +1 -0
  16. package/dist/hmr.js +411 -0
  17. package/dist/image-pipeline.d.ts +3 -0
  18. package/dist/image-pipeline.d.ts.map +1 -0
  19. package/dist/image-pipeline.js +120 -0
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +291 -0
  23. package/dist/manifest.d.ts +3 -0
  24. package/dist/manifest.d.ts.map +1 -0
  25. package/dist/manifest.js +47 -0
  26. package/dist/middleware.d.ts +4 -0
  27. package/dist/middleware.d.ts.map +1 -0
  28. package/dist/middleware.js +96 -0
  29. package/dist/package.json +15 -0
  30. package/dist/platform-deploy.d.ts +18 -0
  31. package/dist/platform-deploy.d.ts.map +1 -0
  32. package/dist/platform-deploy.js +354 -0
  33. package/dist/platform-handler.d.ts +32 -0
  34. package/dist/platform-handler.d.ts.map +1 -0
  35. package/dist/platform-handler.js +211 -0
  36. package/dist/platform-output.d.ts +30 -0
  37. package/dist/platform-output.d.ts.map +1 -0
  38. package/dist/platform-output.js +119 -0
  39. package/dist/platform.d.ts +17 -0
  40. package/dist/platform.d.ts.map +1 -0
  41. package/dist/platform.js +35 -0
  42. package/dist/prod-server.d.ts +5 -0
  43. package/dist/prod-server.d.ts.map +1 -0
  44. package/dist/prod-server.js +429 -0
  45. package/dist/runtime-bundle.d.ts +2 -0
  46. package/dist/runtime-bundle.d.ts.map +1 -0
  47. package/dist/runtime-bundle.js +140 -0
  48. package/dist/seo-audit.d.ts +3 -0
  49. package/dist/seo-audit.d.ts.map +1 -0
  50. package/dist/seo-audit.js +169 -0
  51. package/dist/ssr-function.d.ts +8 -0
  52. package/dist/ssr-function.d.ts.map +1 -0
  53. package/dist/ssr-function.js +415 -0
  54. package/dist/static.d.ts +8 -0
  55. package/dist/static.d.ts.map +1 -0
  56. package/dist/static.js +130 -0
  57. package/dist/types.d.ts +182 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +1 -0
  60. package/package.json +54 -0
@@ -0,0 +1,354 @@
1
+ import { writeFileSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
2
+ import { resolve, dirname, relative } from 'node:path';
3
+ import { generatePlatformHandlerSource, bundlePlatformHandler } from '@vesk/adapter/src/platform-handler';
4
+ import { ensureCleanDir, writePlatformStatic, writePrerenderedStatic, listStaticDir, mimeFor } from '@vesk/adapter/src/platform-output';
5
+ /**
6
+ * Universal deployment emit. The handler, the bundle and the static layout are
7
+ * shared across every platform — only the ~10-line shell each runtime mandates
8
+ * differs (artifact directory, bootstrap, function config). Deno-based platforms
9
+ * (Coxmos, Deno Deploy) share the exact same shell.
10
+ *
11
+ * Every artifact is written under `.vesk/<platform>/`; Vercel additionally gets
12
+ * a gitignored `.vercel/output` symlink because the Build Output API is keyed on
13
+ * that literal directory.
14
+ */
15
+ export async function emitPlatformOutput(platform, ctx) {
16
+ if (platform === 'node')
17
+ return null;
18
+ const prerenderedPaths = ctx.prerenderedRoutes.map(r => r.path);
19
+ const handler = generatePlatformHandlerSource({
20
+ ssrRoutes: ctx.ssrRoutes,
21
+ apiRoutes: ctx.apiRoutes,
22
+ prerenderedPaths,
23
+ hasMiddleware: ctx.hasMiddleware,
24
+ });
25
+ const shell = shellFor(platform);
26
+ const projectRoot = resolve(ctx.outDir, '..');
27
+ const outRoot = resolve(projectRoot, shell.root);
28
+ ensureCleanDir(outRoot);
29
+ const staticDir = shell.staticSubdir === '' ? outRoot : resolve(outRoot, shell.staticSubdir || 'static');
30
+ writePlatformStatic(resolve(ctx.outDir, 'static'), staticDir);
31
+ writePrerenderedStatic(ctx.prerenderedRoutes, staticDir);
32
+ const entry = resolve(ctx.outDir, '.platform-entry.mjs');
33
+ let source = handler;
34
+ if (shell.imports)
35
+ source = `${shell.imports}\n${source}`;
36
+ if (shell.staticMode === 'embedded') {
37
+ if (shell.staticServe === 'disk-deno')
38
+ source += `\n${denoStaticSource()}`;
39
+ else if (shell.staticServe === 'disk-node')
40
+ source += `\n${nodeStaticSource()}`;
41
+ else if (shell.staticServe === 'inline')
42
+ source += `\n${inlineStaticSource(staticDir)}`;
43
+ }
44
+ source += `\n${shell.bootstrap}\n`;
45
+ writeFileSync(entry, source, 'utf-8');
46
+ let handlerRel;
47
+ if (shell.functionFile) {
48
+ const funcDir = resolve(outRoot, shell.functionFile.dir);
49
+ mkdirSync(funcDir, { recursive: true });
50
+ if (shell.functionConfig) {
51
+ writeFileSync(resolve(funcDir, '.vc-config.json'), JSON.stringify(shell.functionConfig, null, 2), 'utf-8');
52
+ }
53
+ handlerRel = `${shell.functionFile.dir}/${shell.functionFile.file}`;
54
+ await bundlePlatformHandler({ entry, outfile: resolve(funcDir, shell.functionFile.file), nodeBuiltins: shell.nodeBuiltins });
55
+ }
56
+ else {
57
+ handlerRel = shell.outfile || 'index.js';
58
+ await bundlePlatformHandler({ entry, outfile: resolve(outRoot, handlerRel), nodeBuiltins: shell.nodeBuiltins });
59
+ }
60
+ for (const file of shell.extraFiles || []) {
61
+ writeFileSync(resolve(outRoot, file.path), file.content, 'utf-8');
62
+ }
63
+ if (platform === 'vercel') {
64
+ writeFileSync(resolve(outRoot, 'config.json'), vercelConfigJson(prerenderedPaths), 'utf-8');
65
+ }
66
+ writeFileSync(resolve(outRoot, 'manifest.json'), JSON.stringify({
67
+ platform,
68
+ runtime: shell.nodeBuiltins ? 'node' : 'edge',
69
+ static: shell.staticMode,
70
+ handler: handlerRel,
71
+ routes: ctx.ssrRoutes.map(r => r.fullPath),
72
+ apiRoutes: ctx.apiRoutes.map(r => '/api' + r.fullPath),
73
+ prerendered: prerenderedPaths,
74
+ }, null, 2), 'utf-8');
75
+ if (platform === 'vercel') {
76
+ const vercelDir = resolve(projectRoot, '.vercel');
77
+ mkdirSync(vercelDir, { recursive: true });
78
+ const linkPath = resolve(vercelDir, 'output');
79
+ rmSync(linkPath, { recursive: true, force: true });
80
+ symlinkSync(relative(dirname(linkPath), outRoot), linkPath, 'dir');
81
+ }
82
+ rmSync(entry, { force: true });
83
+ return outRoot;
84
+ }
85
+ function vercelConfigJson(prerenderedPaths) {
86
+ const escapeRegex = (p) => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
87
+ const prerenderRoutes = prerenderedPaths.map(p => {
88
+ const htmlRel = p === '/' ? '/index.html' : `${p.replace(/\/$/, '')}.html`;
89
+ return { src: `^${escapeRegex(p === '/' ? '/' : p)}/?$`, dest: `/_vesk/static/public${htmlRel}` };
90
+ });
91
+ return JSON.stringify({
92
+ version: 3,
93
+ routes: [
94
+ ...prerenderRoutes,
95
+ { handle: 'filesystem' },
96
+ { src: '/(.*)', dest: '/__index' },
97
+ ],
98
+ }, null, 2);
99
+ }
100
+ function __relsSource() {
101
+ return `function __rels(p) {
102
+ if (p === '/' || p === '') return ['index.html'];
103
+ if (p.startsWith('/_vesk/static/') || p === '/_vesk/runtime.js') return [p === '/_vesk/runtime.js' ? '_vesk/runtime.js' : p.slice(1)];
104
+ if (p.endsWith('/')) return [p.slice(1) + 'index.html', p.slice(1, -1) + '.html'];
105
+ return [p.slice(1), p.slice(1) + '.html', p.slice(1) + '/index.html'];
106
+ }`;
107
+ }
108
+ function __mimeSource() {
109
+ return `function __mime(rel) {
110
+ const i = rel.lastIndexOf('.');
111
+ const ext = i === -1 ? '' : rel.slice(i).toLowerCase();
112
+ const m = {
113
+ '.html': 'text/html; charset=utf-8', '.htm': 'text/html; charset=utf-8',
114
+ '.js': 'application/javascript; charset=utf-8', '.mjs': 'application/javascript; charset=utf-8',
115
+ '.css': 'text/css; charset=utf-8', '.json': 'application/json; charset=utf-8',
116
+ '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
117
+ '.gif': 'image/gif', '.webp': 'image/webp', '.avif': 'image/avif', '.ico': 'image/x-icon',
118
+ '.txt': 'text/plain; charset=utf-8', '.xml': 'application/xml',
119
+ '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', '.otf': 'font/otf',
120
+ '.wasm': 'application/wasm', '.map': 'application/json', '.webmanifest': 'application/manifest+json',
121
+ };
122
+ return m[ext] || 'application/octet-stream';
123
+ }`;
124
+ }
125
+ function denoStaticSource() {
126
+ return `${__relsSource()}
127
+ ${__mimeSource()}
128
+ const __staticUrl = new URL('./static/', import.meta.url);
129
+ async function serveEmbeddedStatic(request) {
130
+ const __url = new URL(request.url);
131
+ for (const rel of __rels(__url.pathname)) {
132
+ try {
133
+ const data = await Deno.readFile(new URL(rel, __staticUrl));
134
+ return new Response(data, { headers: { 'Content-Type': __mime(rel) } });
135
+ } catch {}
136
+ }
137
+ return null;
138
+ }`;
139
+ }
140
+ function nodeStaticSource() {
141
+ return `${__relsSource()}
142
+ ${__mimeSource()}
143
+ const __staticUrl = new URL('./static/', import.meta.url);
144
+ function serveEmbeddedStatic(request) {
145
+ const __url = new URL(request.url);
146
+ for (const rel of __rels(__url.pathname)) {
147
+ try {
148
+ return new Response(readFileSync(new URL(rel, __staticUrl)), { headers: { 'Content-Type': __mime(rel) } });
149
+ } catch {}
150
+ }
151
+ return null;
152
+ }`;
153
+ }
154
+ function inlineStaticSource(staticDir) {
155
+ const files = listStaticDir(staticDir);
156
+ const entries = files.map(f => {
157
+ const t = mimeFor(f.rel);
158
+ const isText = /\.(html|htm|js|mjs|css|json|svg|txt|xml|map|webmanifest)$/i.test(f.rel);
159
+ return isText
160
+ ? `${JSON.stringify(f.rel)}: { t: ${JSON.stringify(t)}, s: ${JSON.stringify(f.buffer.toString('utf8'))} }`
161
+ : `${JSON.stringify(f.rel)}: { t: ${JSON.stringify(t)}, b: ${JSON.stringify(f.buffer.toString('base64'))} }`;
162
+ });
163
+ return `${__relsSource()}
164
+ const __STATIC = { ${entries.join(', ')} };
165
+ function __decode(e) {
166
+ if (e.s !== undefined) return new TextEncoder().encode(e.s);
167
+ return new Uint8Array(atob(e.b).split('').map(function (c) { return c.charCodeAt(0); }));
168
+ }
169
+ function serveEmbeddedStatic(request) {
170
+ const __url = new URL(request.url);
171
+ for (const rel of __rels(__url.pathname)) {
172
+ const entry = __STATIC[rel];
173
+ if (entry) return new Response(__decode(entry), { headers: { 'Content-Type': entry.t } });
174
+ }
175
+ return null;
176
+ }`;
177
+ }
178
+ function shellFor(platform) {
179
+ switch (platform) {
180
+ case 'vercel':
181
+ return {
182
+ root: '.vesk/vercel',
183
+ nodeBuiltins: true,
184
+ staticMode: 'platform',
185
+ staticSubdir: 'static',
186
+ functionFile: {
187
+ dir: 'functions/__index.func',
188
+ file: 'index.js',
189
+ },
190
+ functionConfig: {
191
+ runtime: 'nodejs22.x',
192
+ handler: 'index.js',
193
+ launcherType: 'Nodejs',
194
+ shouldAddHelpers: false,
195
+ shouldAddSourcemapSupport: false,
196
+ supportsResponseStreaming: true,
197
+ },
198
+ bootstrap: [
199
+ 'export default async function veskNodeEntry(req, res) {',
200
+ " const url = new URL(req.url, 'http://' + (req.headers.host || 'localhost'));",
201
+ " const method = req.method || 'GET';",
202
+ ' let body;',
203
+ " if (method !== 'GET' && method !== 'HEAD') {",
204
+ ' body = await new Promise((resolveBody, reject) => {',
205
+ ' const chunks = [];',
206
+ " req.on('data', (c) => chunks.push(c));",
207
+ ' req.on("error", reject);',
208
+ ' req.on("end", () => resolveBody(Buffer.concat(chunks)));',
209
+ ' });',
210
+ ' }',
211
+ ' const headers = {};',
212
+ ' for (const key in req.headers) headers[key] = req.headers[key];',
213
+ ' const request = new Request(url, { method, headers, body });',
214
+ ' const response = await handleRequest(request);',
215
+ ' res.writeHead(response.status, Object.fromEntries(response.headers));',
216
+ ' res.end(await response.text());',
217
+ '}',
218
+ ].join('\n'),
219
+ };
220
+ case 'netlify':
221
+ return {
222
+ root: '.vesk/netlify',
223
+ nodeBuiltins: true,
224
+ staticMode: 'platform',
225
+ staticSubdir: '',
226
+ functionFile: {
227
+ dir: 'functions',
228
+ file: '__index.js',
229
+ },
230
+ bootstrap: [
231
+ 'export default { fetch: handleRequest };',
232
+ "export const config = { path: '/*', preferStatic: true };",
233
+ ].join('\n'),
234
+ };
235
+ case 'cloudflare':
236
+ return {
237
+ root: '.vesk/cloudflare',
238
+ nodeBuiltins: false,
239
+ staticMode: 'platform',
240
+ staticSubdir: '',
241
+ outfile: '_worker.js',
242
+ bootstrap: [
243
+ 'export default {',
244
+ ' async fetch(request, env) {',
245
+ ' const response = await handleRequest(request);',
246
+ ' if (response.status !== 404) return response;',
247
+ ' if (env && env.ASSETS) {',
248
+ ' try {',
249
+ ' const asset = await env.ASSETS.fetch(request);',
250
+ ' if (asset.status !== 404) return asset;',
251
+ ' } catch {}',
252
+ ' }',
253
+ ' return response;',
254
+ ' }',
255
+ '};',
256
+ ].join('\n'),
257
+ };
258
+ case 'deno':
259
+ case 'coxmos':
260
+ return {
261
+ root: platform === 'coxmos' ? '.vesk/coxmos' : '.vesk/deno',
262
+ nodeBuiltins: false,
263
+ staticMode: 'embedded',
264
+ staticSubdir: 'static',
265
+ staticServe: 'disk-deno',
266
+ bootstrap: [
267
+ 'if (typeof Deno !== "undefined" && typeof Deno.serve === "function") {',
268
+ ' Deno.serve(async (request) => {',
269
+ ' const staticResponse = await serveEmbeddedStatic(request);',
270
+ ' if (staticResponse) return staticResponse;',
271
+ ' return handleRequest(request);',
272
+ ' });',
273
+ '}',
274
+ 'export default handleRequest;',
275
+ ].join('\n'),
276
+ };
277
+ case 'aws':
278
+ return {
279
+ root: '.vesk/aws',
280
+ nodeBuiltins: true,
281
+ staticMode: 'embedded',
282
+ staticSubdir: 'static',
283
+ staticServe: 'disk-node',
284
+ imports: "import { readFileSync } from 'node:fs';",
285
+ outfile: 'index.mjs',
286
+ extraFiles: [
287
+ {
288
+ path: 'package.json',
289
+ content: JSON.stringify({ name: 'vesk-aws-app', type: 'module', private: true }, null, 2),
290
+ },
291
+ {
292
+ path: 'template.yaml',
293
+ content: [
294
+ "AWSTemplateFormatVersion: '2010-09-09'",
295
+ 'Transform: AWS::Serverless-2016-10-31',
296
+ 'Description: Vesk application deployed to AWS Lambda via SAM',
297
+ '',
298
+ 'Resources:',
299
+ ' VeskFunction:',
300
+ ' Type: AWS::Serverless::Function',
301
+ ' Properties:',
302
+ ' CodeUri: ./',
303
+ ' Handler: index.handler',
304
+ ' Runtime: nodejs22.x',
305
+ ' MemorySize: 512',
306
+ ' Timeout: 30',
307
+ ' Events:',
308
+ ' HttpApiEvent:',
309
+ ' Type: HttpApi',
310
+ ' Properties:',
311
+ ' Auth:',
312
+ ' Authorizer: NONE',
313
+ ' Path: $default',
314
+ ' Method: ANY',
315
+ '',
316
+ ].join('\n'),
317
+ },
318
+ ],
319
+ bootstrap: [
320
+ 'export async function handler(event) {',
321
+ " const url = new URL(event.rawPath || '/', 'http://' + (event.headers?.host || 'lambda.local'));",
322
+ " if (event.rawQueryString) url.search = event.rawQueryString;",
323
+ ' const headers = {};',
324
+ ' for (const [k, v] of Object.entries(event.headers || {})) headers[k.toLowerCase()] = String(v);',
325
+ ' let body;',
326
+ ' if (event.body) {',
327
+ " body = event.isBase64Encoded ? Buffer.from(event.body, 'base64') : event.body;",
328
+ ' }',
329
+ " const request = new Request(url, { method: event.requestContext?.http?.method || event.httpMethod || 'GET', headers, body });",
330
+ ' const response = await handleRequest(request);',
331
+ " return { statusCode: response.status, headers: Object.fromEntries(response.headers), body: Buffer.from(await response.text()).toString('base64'), isBase64Encoded: true };",
332
+ '}',
333
+ ].join('\n'),
334
+ };
335
+ case 'edge':
336
+ return {
337
+ root: '.vesk/edge',
338
+ nodeBuiltins: false,
339
+ staticMode: 'embedded',
340
+ staticSubdir: 'static',
341
+ staticServe: 'inline',
342
+ bootstrap: [
343
+ 'export async function handleEdgeRequest(request) {',
344
+ ' const staticResponse = await serveEmbeddedStatic(request);',
345
+ ' if (staticResponse) return staticResponse;',
346
+ ' return handleRequest(request);',
347
+ '}',
348
+ 'export default handleEdgeRequest;',
349
+ ].join('\n'),
350
+ };
351
+ default:
352
+ throw new Error(`Unsupported platform: ${platform}`);
353
+ }
354
+ }
@@ -0,0 +1,32 @@
1
+ import type { RouteNode, ApiRouteNode } from '@vesk/adapter/src/types';
2
+ export interface PlatformHandlerInput {
3
+ ssrRoutes: RouteNode[];
4
+ apiRoutes: ApiRouteNode[];
5
+ prerenderedPaths: string[];
6
+ hasMiddleware: boolean;
7
+ }
8
+ export declare function routeName(segments: string[]): string;
9
+ export declare function apiRouteName(fullPath: string): string;
10
+ export declare function toId(s: string): string;
11
+ /**
12
+ * Generate a self-contained platform handler: a `handleRequest(request: Request)`
13
+ * that routes SSR pages, API routes and middleware for any edge/serverless runtime.
14
+ * The output references server functions via relative imports so it can be bundled
15
+ * with esbuild for each target platform.
16
+ */
17
+ export declare function generatePlatformHandlerSource(input: PlatformHandlerInput): string;
18
+ export interface BundlePlatformOptions {
19
+ /** Keep real node builtins (node:fs, node:path) in the bundle. */
20
+ nodeBuiltins?: boolean;
21
+ outfile: string;
22
+ entry: string;
23
+ }
24
+ export declare function bundlePlatformHandler(options: BundlePlatformOptions): Promise<void>;
25
+ export interface PlatformBuildContext {
26
+ outDir: string;
27
+ ssrRoutes: RouteNode[];
28
+ apiRoutes: ApiRouteNode[];
29
+ prerenderedPaths: string[];
30
+ hasMiddleware: boolean;
31
+ }
32
+ //# sourceMappingURL=platform-handler.d.ts.map
@@ -0,0 +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,CA4HjF;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,CA8DzF;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"}
@@ -0,0 +1,211 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ export function routeName(segments) {
6
+ const parts = segments.filter(Boolean).map(s => {
7
+ if (s.startsWith(':'))
8
+ return s.slice(1) || 'param';
9
+ return s;
10
+ });
11
+ return parts.join('_') || 'index';
12
+ }
13
+ export function apiRouteName(fullPath) {
14
+ const parts = fullPath.split('/').filter(Boolean);
15
+ return parts.map(s => (s.startsWith(':') ? s.slice(1) || 'param' : s)).join('_') || 'index';
16
+ }
17
+ export function toId(s) {
18
+ return s.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^_/, '');
19
+ }
20
+ function findCompilerSrc() {
21
+ const monorepo = resolve(__dirname, '..', '..', '..', 'packages', 'compiler', 'dist');
22
+ if (existsSync(monorepo))
23
+ return monorepo;
24
+ throw new Error('@vesk/compiler/dist not found — run "npm run build" first');
25
+ }
26
+ /**
27
+ * Generate a self-contained platform handler: a `handleRequest(request: Request)`
28
+ * that routes SSR pages, API routes and middleware for any edge/serverless runtime.
29
+ * The output references server functions via relative imports so it can be bundled
30
+ * with esbuild for each target platform.
31
+ */
32
+ export function generatePlatformHandlerSource(input) {
33
+ const { ssrRoutes, apiRoutes, prerenderedPaths, hasMiddleware } = input;
34
+ let imports = '';
35
+ const routeEntries = [];
36
+ for (const r of ssrRoutes) {
37
+ const name = routeName(r.fullPath.split('/').filter(Boolean));
38
+ const id = `__ssr_${toId(name)}`;
39
+ const funcPath = `./server/functions/${name}.js`;
40
+ imports += `import { handle as ${id} } from ${JSON.stringify(funcPath)};\n`;
41
+ const revalidate = r._revalidate != null ? `revalidate: ${r._revalidate}, ` : '';
42
+ const tags = r._isrTags ? `tags: ${JSON.stringify(r._isrTags)}, ` : '';
43
+ routeEntries.push(`{ path: ${JSON.stringify(r.fullPath)}, type: 'ssr', handler: ${id}, ${revalidate}${tags}}`);
44
+ }
45
+ for (const r of apiRoutes) {
46
+ const name = apiRouteName(r.fullPath);
47
+ const id = `__api_${toId(name)}`;
48
+ const funcPath = `./server/api/${name}.js`;
49
+ imports += `import { handle as ${id} } from ${JSON.stringify(funcPath)};\n`;
50
+ routeEntries.push(`{ path: ${JSON.stringify('/api' + r.fullPath)}, type: 'api', handler: ${id} }`);
51
+ }
52
+ const mwImport = hasMiddleware ? "import { execute as __executeMw } from './server/middleware.js';" : '';
53
+ const hasMwLiteral = hasMiddleware ? 'true' : 'false';
54
+ const prerenderedList = prerenderedPaths.length > 0
55
+ ? `const __prerendered = new Set(${JSON.stringify(prerenderedPaths)});\n`
56
+ : 'const __prerendered = new Set();\n';
57
+ const isrCache = 'const __isrCache = new Map();';
58
+ const compilerSrc = findCompilerSrc();
59
+ const parseCookiesImport = hasMiddleware
60
+ ? `import { parseCookies } from ${JSON.stringify(resolve(compilerSrc, 'server-cookies.js'))};`
61
+ : '';
62
+ return `
63
+ ${imports}
64
+ ${parseCookiesImport}
65
+ ${mwImport}
66
+ ${prerenderedList}
67
+ ${isrCache}
68
+ const __routes = [${routeEntries.join(',\n')}];
69
+
70
+ function __matchPath(pattern, pathname) {
71
+ const patternParts = pattern.split('/').filter(Boolean);
72
+ const pathParts = pathname.split('/').filter(Boolean);
73
+ let pi = 0, pp = 0;
74
+ const params = {};
75
+ while (pi < pathParts.length && pp < patternParts.length) {
76
+ if (patternParts[pp].startsWith(':')) {
77
+ const name = patternParts[pp].slice(1);
78
+ params[name] = pathParts[pi];
79
+ pi++; pp++;
80
+ } else if (patternParts[pp] === pathParts[pi]) {
81
+ pi++; pp++;
82
+ } else {
83
+ return null;
84
+ }
85
+ }
86
+ if (pp === patternParts.length && pi === pathParts.length) return params;
87
+ return null;
88
+ }
89
+
90
+ export async function handleRequest(request) {
91
+ const url = new URL(request.url);
92
+ const pathname = url.pathname;
93
+ const isDataRequest = request.headers.get('x-vesk-data') === '1';
94
+
95
+ if (__prerendered.has(pathname) && !isDataRequest) {
96
+ return new Response(null, { status: 308, headers: { Location: '/_vesk/static/public' + (pathname.endsWith('/') ? pathname + 'index.html' : pathname + '.html') } });
97
+ }
98
+
99
+ if (${hasMwLiteral}) {
100
+ const mwCtx = {
101
+ request,
102
+ params: {},
103
+ url,
104
+ locals: {},
105
+ cookies: typeof parseCookies !== 'undefined' ? parseCookies(request.headers.get('cookie') || '') : {},
106
+ set(key, value) { this.locals[key] = value; },
107
+ get(key) { return this.locals[key]; },
108
+ };
109
+ const mwResult = await __executeMw(mwCtx);
110
+ if (mwResult.response) return mwResult.response;
111
+ if (mwResult.rewriteUrl) url.pathname = mwResult.rewriteUrl;
112
+ }
113
+
114
+ for (const route of __routes) {
115
+ const params = __matchPath(route.path, url.pathname);
116
+ if (!params) continue;
117
+
118
+ if (route.type === 'api') {
119
+ return await route.handler(request);
120
+ }
121
+
122
+ if (route.revalidate && route.revalidate > 0 && !isDataRequest) {
123
+ const cached = __isrCache.get(url.pathname);
124
+ if (cached && Date.now() - cached.ts < route.revalidate * 1000) {
125
+ return new Response(cached.html, {
126
+ status: 200,
127
+ headers: { 'Content-Type': 'text/html', ...cached.headers },
128
+ });
129
+ }
130
+ }
131
+
132
+ const response = await route.handler(request);
133
+
134
+ if (route.revalidate && route.revalidate > 0 && !isDataRequest) {
135
+ const html = await response.clone().text();
136
+ __isrCache.set(url.pathname, { html, headers: Object.fromEntries(response.headers), ts: Date.now() });
137
+ }
138
+
139
+ return response;
140
+ }
141
+
142
+ return new Response('<!DOCTYPE html><html><body><h1>404</h1><p>Not Found</p></body></html>', {
143
+ status: 404,
144
+ headers: { 'Content-Type': 'text/html' },
145
+ });
146
+ }
147
+ `;
148
+ }
149
+ export async function bundlePlatformHandler(options) {
150
+ const { nodeBuiltins = false, outfile, entry } = options;
151
+ const { build } = await import('./esbuild-fallback.js');
152
+ const plugins = [];
153
+ if (!nodeBuiltins) {
154
+ plugins.push({
155
+ name: 'empty-node-builtins',
156
+ setup(build) {
157
+ const builtins = /^(fs|path|node:fs|node:path|child_process|os|crypto|net|stream|buffer|events|util|url|querystring|http|https|zlib|tty)$/;
158
+ build.onResolve({ filter: builtins }, () => {
159
+ return { path: 'node-builtin-empty', namespace: 'empty-node' };
160
+ });
161
+ build.onLoad({ filter: /.*/, namespace: 'empty-node' }, () => ({
162
+ contents: `
163
+ const __m = typeof Proxy !== 'undefined' ? new Proxy({}, {
164
+ get(_, key) { return typeof key === 'string' ? () => {} : undefined; },
165
+ has() { return true; },
166
+ }) : {};
167
+ export default __m;
168
+ export const readFileSync = () => {};
169
+ export const writeFileSync = () => {};
170
+ export const existsSync = () => {};
171
+ export const statSync = () => {};
172
+ export const readdirSync = () => {};
173
+ export const mkdirSync = () => {};
174
+ export const unlinkSync = () => {};
175
+ export const rmSync = () => {};
176
+ export const copyFileSync = () => {};
177
+ export const accessSync = () => {};
178
+ export const join = (...a) => a.join('/');
179
+ export const resolve = (...a) => a.join('/');
180
+ export const dirname = () => '';
181
+ export const basename = () => '';
182
+ export const extname = () => '';
183
+ export const relative = () => '';
184
+ export const sep = '/';
185
+ export const delimiter = ':';
186
+ export const spawnSync = () => ({ status: 0 });
187
+ export const execSync = () => '';
188
+ export const randomBytes = () => ({});
189
+ export const createHash = () => ({ update: () => {}, digest: () => '' });
190
+ `,
191
+ loader: 'js',
192
+ }));
193
+ },
194
+ });
195
+ }
196
+ const result = await build({
197
+ entryPoints: [entry],
198
+ bundle: true,
199
+ platform: nodeBuiltins ? 'node' : 'neutral',
200
+ format: 'esm',
201
+ target: ['es2022'],
202
+ outfile,
203
+ minify: false,
204
+ sourcemap: false,
205
+ logOverride: { 'direct-eval': 'silent' },
206
+ plugins,
207
+ });
208
+ if (result.errors.length > 0) {
209
+ throw new Error(`Platform bundle errors: ${result.errors.map((e) => e.text).join(', ')}`);
210
+ }
211
+ }
@@ -0,0 +1,30 @@
1
+ import type { SsgRouteResult } from '@vesk/adapter/src/types';
2
+ export declare const MIME: Record<string, string>;
3
+ export declare function ensureCleanDir(dir: string): void;
4
+ export declare function copyDirContents(srcDir: string, destDir: string): void;
5
+ export declare function writeFile(path: string, content: string | Buffer): void;
6
+ /**
7
+ * Lay out `.vesk/static/**` into a platform `static/` directory using the
8
+ * URL scheme the SSR output expects:
9
+ * /_vesk/static/* <- .vesk/static/*
10
+ * /_vesk/runtime.js <- .vesk/static/client.js (alias)
11
+ * /* (public) <- .vesk/static/public/*
12
+ */
13
+ export declare function writePlatformStatic(buildStaticDir: string, platformStaticDir: string): void;
14
+ /**
15
+ * Write SSG pages into a platform static dir. They land under
16
+ * `_vesk/static/public/<path>.html` — the exact location the platform
17
+ * handler's prerendered 308 redirect points to — plus a `<path>/index.html`
18
+ * twin for trailing-slash URLs. `route.html` is the prerendered file path.
19
+ */
20
+ export declare function writePrerenderedStatic(prerenderedRoutes: SsgRouteResult[], platformStaticDir: string): void;
21
+ /**
22
+ * Recursively list the files of a static dir as `{ rel, buffer }` so a target
23
+ * runtime without a filesystem (generic edge) can inline them into the bundle.
24
+ */
25
+ export declare function listStaticDir(dir: string): Array<{
26
+ rel: string;
27
+ buffer: Buffer;
28
+ }>;
29
+ export declare function mimeFor(path: string): string;
30
+ //# sourceMappingURL=platform-output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-output.d.ts","sourceRoot":"","sources":["../src/platform-output.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwBvC,CAAC;AAEF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGhD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAarE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGtE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAiB3F;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,iBAAiB,EAAE,cAAc,EAAE,EAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAY3G;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAgBjF;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C"}