@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,415 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { resolve, relative, join } from 'node:path';
3
+ import { resolveComponentName } from '@vesk/compiler/src/server-codegen';
4
+ function escapeSource(src) {
5
+ return src
6
+ .replace(/\\/g, '\\\\')
7
+ .replace(/`/g, '\\`')
8
+ .replace(/\$/g, '\\$');
9
+ }
10
+ // Finds the nearest error.vsk walking up from the route's own directory to the
11
+ // app root, mirroring the router's findErrorComponent chain semantics.
12
+ export function resolveErrorFile(sourceDir, appDir) {
13
+ const rel = relative(appDir, sourceDir).split('/').filter(Boolean);
14
+ for (let depth = rel.length; depth >= 0; depth--) {
15
+ const dir = depth === 0 ? appDir : join(appDir, ...rel.slice(0, depth));
16
+ const p = join(dir, 'error.vsk');
17
+ if (existsSync(p))
18
+ return p;
19
+ }
20
+ return null;
21
+ }
22
+ function routeName(segments) {
23
+ const parts = segments.filter(Boolean).map(s => {
24
+ if (s.startsWith(':'))
25
+ return s.slice(1) || 'param';
26
+ return s;
27
+ });
28
+ return parts.join('_') || 'index';
29
+ }
30
+ function extractCompName(src) {
31
+ return resolveComponentName(src);
32
+ }
33
+ function buildParamExtraction(node, urlParts) {
34
+ const parts = [];
35
+ let partIdx = Math.max(0, urlParts.length - 1);
36
+ function walk(n) {
37
+ if (n.fullPath === '/') {
38
+ for (const child of (n.children || []))
39
+ walk(child);
40
+ return;
41
+ }
42
+ if (n.isGroup) {
43
+ for (const child of (n.children || []))
44
+ walk(child);
45
+ return;
46
+ }
47
+ if (partIdx >= urlParts.length)
48
+ return;
49
+ if (n.isCatchAll) {
50
+ const paramName = n.path.startsWith(':') ? n.path.slice(1) : 'slug';
51
+ parts.push(`${JSON.stringify(paramName)}: urlParts.slice(${partIdx}).join('/')`);
52
+ partIdx = urlParts.length;
53
+ return;
54
+ }
55
+ if (n.isDynamic) {
56
+ const paramName = n.path.startsWith(':') ? n.path.slice(1) : 'param';
57
+ parts.push(`${JSON.stringify(paramName)}: urlParts[${partIdx}]`);
58
+ partIdx++;
59
+ for (const child of (n.children || []))
60
+ walk(child);
61
+ return;
62
+ }
63
+ if (n.path === urlParts[partIdx]) {
64
+ partIdx++;
65
+ for (const child of (n.children || []))
66
+ walk(child);
67
+ }
68
+ }
69
+ walk(node);
70
+ return parts;
71
+ }
72
+ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, options) {
73
+ const ancestorLayouts = options?.ancestorLayouts || [];
74
+ const middlewareCode = options?.middlewareCode || null;
75
+ const pagePath = resolve(appDir, routeNode.sourceDir, 'page.vsk');
76
+ const layoutPath = resolve(appDir, routeNode.sourceDir, 'layout.vsk');
77
+ const parts = routeNode.fullPath.split('/').filter(Boolean);
78
+ const name = routeName(parts);
79
+ const funcDir = resolve(outDir, 'server', 'functions');
80
+ const funcPath = resolve(funcDir, `${name}.js`);
81
+ const tailwindPath = resolve(outDir, 'static', '_tailwind.css');
82
+ const globalCssPath = resolve(appDir, '..', 'src', 'global.css');
83
+ const altCssPath = resolve(appDir, '..', 'src', 'app.css');
84
+ const hasGlobalCss = existsSync(globalCssPath) || existsSync(altCssPath);
85
+ const hasTailwind = existsSync(tailwindPath) && readFileSync(tailwindPath, 'utf-8').trim().length > 0;
86
+ const cssUrls = [];
87
+ if (hasTailwind)
88
+ cssUrls.push('/_vesk/static/_tailwind.css');
89
+ if (hasGlobalCss)
90
+ cssUrls.push('/_vesk/static/global.css');
91
+ const cssOption = cssUrls.length > 0 ? `, cssUrls: ${JSON.stringify(cssUrls)}` : '';
92
+ const hasLayout = !!routeNode.layout;
93
+ const hasAncestorLayout = ancestorLayouts.length > 0;
94
+ const pageSrc = readFileSync(pagePath, 'utf-8');
95
+ const pageComp = extractCompName(pageSrc) || 'Page';
96
+ const errorPath = resolveErrorFile(routeNode.sourceDir, appDir);
97
+ const errorSrc = errorPath ? readFileSync(errorPath, 'utf-8') : null;
98
+ const errorComp = errorPath ? (extractCompName(errorSrc) || 'Error') : null;
99
+ const errorVars = errorPath
100
+ ? `const _errorSrc = \`${escapeSource(errorSrc)}\`;\nconst _errorComp = ${JSON.stringify(errorComp)};\nconst _errorPath = ${JSON.stringify(errorPath)};\nconst _errorCompiled = (() => { try { setVskHydrate(true); return compileFile(_errorSrc, { sourcePath: _errorPath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`
101
+ : 'const _errorSrc = null;\nconst _errorComp = null;\nconst _errorPath = null;\nconst _errorCompiled = null;\n';
102
+ let src = '';
103
+ if (hasLayout) {
104
+ const layoutSrc = readFileSync(layoutPath, 'utf-8');
105
+ const layoutComp = extractCompName(layoutSrc) || 'Layout';
106
+ src = `const _layoutSrc = \`${escapeSource(layoutSrc)}\`;\nconst _pageSrc = \`${escapeSource(pageSrc)}\`;\n`;
107
+ src += `const _layoutComp = ${JSON.stringify(layoutComp)};\nconst _pageComp = ${JSON.stringify(pageComp)};\n`;
108
+ src += `const _layoutPath = ${JSON.stringify(layoutPath)};\nconst _pagePath = ${JSON.stringify(pagePath)};\n`;
109
+ src += `const _layoutCompiled = (() => { try { setVskHydrate(true); return compileFile(_layoutSrc, { sourcePath: _layoutPath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
110
+ src += `const _pageCompiled = (() => { try { setVskHydrate(true); return compileFile(_pageSrc, { sourcePath: _pagePath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
111
+ src += errorVars;
112
+ }
113
+ else if (hasAncestorLayout) {
114
+ const outerLayout = ancestorLayouts[0];
115
+ const outerLayoutPath = resolve(appDir, outerLayout.sourceDir, 'layout.vsk');
116
+ const outerLayoutSrc = readFileSync(outerLayoutPath, 'utf-8');
117
+ const outerLayoutComp = extractCompName(outerLayoutSrc) || 'Layout';
118
+ src = `const _pageSrc = \`${escapeSource(pageSrc)}\`;\n`;
119
+ src += `const _pageComp = ${JSON.stringify(pageComp)};\n`;
120
+ src += `const _layoutSrc = \`${escapeSource(outerLayoutSrc)}\`;\n`;
121
+ src += `const _layoutComp = ${JSON.stringify(outerLayoutComp)};\n`;
122
+ src += `const _layoutPath = ${JSON.stringify(outerLayoutPath)};\nconst _pagePath = ${JSON.stringify(pagePath)};\n`;
123
+ src += `const _layoutCompiled = (() => { try { setVskHydrate(true); return compileFile(_layoutSrc, { sourcePath: _layoutPath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
124
+ src += `const _pageCompiled = (() => { try { setVskHydrate(true); return compileFile(_pageSrc, { sourcePath: _pagePath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
125
+ src += errorVars;
126
+ }
127
+ else {
128
+ src = `const _src = \`${escapeSource(pageSrc)}\`;\nconst _comp = ${JSON.stringify(pageComp)};\n`;
129
+ src += `const _srcPath = ${JSON.stringify(pagePath)};\n`;
130
+ src += `const _srcCompiled = (() => { try { setVskHydrate(true); return compileFile(_src, { sourcePath: _srcPath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
131
+ src += errorVars;
132
+ }
133
+ const urlParts = routeNode.fullPath.split('/').filter(Boolean);
134
+ const paramExprs = buildParamExtraction(routeNode, urlParts);
135
+ const paramsCode = `function __paramsFor(pathname) {\n const urlParts = pathname.split('/').filter(Boolean);\n return { ${paramExprs.join(', ')} };\n}\n`;
136
+ const clientScriptOption = ', clientScriptUrl: "/_vesk/static/client.js"';
137
+ const dataScriptOption = ', externalDataScript: storeDataScriptGlobal';
138
+ let registryCode = '';
139
+ const compRegEntries = [];
140
+ const compMap = componentMap || new Map();
141
+ for (const [compName, compPath] of compMap) {
142
+ const compSrc = readFileSync(compPath, 'utf-8');
143
+ const escapedSrc = escapeSource(compSrc);
144
+ compRegEntries.push(` registry.set(${JSON.stringify(compName)}, async (props, __registry, __vesk) => {\n const _src = \`${escapedSrc}\`;\n const _comp = ${JSON.stringify(compName)};\n const _compiled = (() => { try { setVskHydrate(true); return compileFile(_src, { sourcePath: ${JSON.stringify(compPath)} }); } catch { return null; } finally { setVskHydrate(false); } })();\n const result = await renderPage(_src, _comp, props, __registry, { hydrate: true, cached: _compiled, sourcePath: ${JSON.stringify(compPath)} });\n return result.body;\n })`);
145
+ }
146
+ if (compRegEntries.length > 0) {
147
+ registryCode = `const __componentRegistry = new Map();\n{\n${compRegEntries.join('\n')}\n}\n`;
148
+ }
149
+ else {
150
+ registryCode = 'const __componentRegistry = new Map();\n';
151
+ }
152
+ let htmlFnCode;
153
+ if (hasLayout || hasAncestorLayout) {
154
+ htmlFnCode = [
155
+ 'async function __renderErrorBody(props) {',
156
+ ' if (!_errorSrc) throw props.error || new Error("Internal Server Error");',
157
+ ' try {',
158
+ ' const result = await renderPage(_errorSrc, _errorComp, props, __componentRegistry, { hydrate: true, cached: _errorCompiled, sourcePath: _errorPath });',
159
+ ' return result.body;',
160
+ ' } catch {',
161
+ ' return \'<h1>500 \\u2014 Internal Server Error</h1>\';',
162
+ ' }',
163
+ '}',
164
+ '',
165
+ 'async function __renderHtml(params, requestUrl) {',
166
+ ' return withSsrStore(async () => {',
167
+ ' let page;',
168
+ ' let caughtError = null;',
169
+ ' try {',
170
+ ' page = await renderPage(_pageSrc, _pageComp, { params }, __componentRegistry, { hydrate: true, cached: _pageCompiled, sourcePath: _pagePath });',
171
+ ' } catch (err) {',
172
+ ' if (err && (err.name === \'NotFoundError\' || err.name === \'Redirect\')) throw err;',
173
+ ' caughtError = err;',
174
+ ' const message = err && typeof err === \'object\' && \'message\' in err ? String(err.message) : String(err);',
175
+ ' const stack = err && typeof err === \'object\' && \'stack\' in err ? String(err.stack) : \'\';',
176
+ ' page = { body: await __renderErrorBody({ params, statusCode: 500, error: message, stack, url: requestUrl || \'\' }), head: \'\' };',
177
+ ' }',
178
+ ' const html = await renderFullPage(_layoutSrc, _layoutComp, { params, children: (caughtError ? \'<!--vesk-ssr-error:\' + (caughtError && typeof caughtError === \'object\' && \'message\' in caughtError ? encodeURIComponent(String(caughtError.message)) : \'\') + \'-->\' : \'\') + page.body }, __componentRegistry, { hydrate: true, cached: _layoutCompiled' + cssOption + clientScriptOption + dataScriptOption + ', pageHead: page.head, sourcePath: _layoutPath });',
179
+ " return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: caughtError ? 500 : 200 });",
180
+ ' });',
181
+ '}',
182
+ '',
183
+ ].join('\n');
184
+ }
185
+ else {
186
+ htmlFnCode = [
187
+ 'async function __renderErrorFullPage(params, requestUrl, err) {',
188
+ ' if (!_errorSrc) throw err;',
189
+ ' const message = err && typeof err === \'object\' && \'message\' in err ? String(err.message) : String(err);',
190
+ ' const stack = err && typeof err === \'object\' && \'stack\' in err ? String(err.stack) : \'\';',
191
+ ' const props = { params, statusCode: 500, error: message, stack, url: requestUrl || \'\' };',
192
+ ' return renderFullPage(_errorSrc, _errorComp, props, __componentRegistry, { hydrate: true, cached: _errorCompiled' + cssOption + clientScriptOption + dataScriptOption + ', sourcePath: _errorPath });',
193
+ '}',
194
+ '',
195
+ 'async function __renderHtml(params, requestUrl) {',
196
+ ' return withSsrStore(async () => {',
197
+ ' let stream;',
198
+ ' try {',
199
+ ' stream = renderPageStream(_src, _comp, { params }, __componentRegistry, { hydrate: true, cached: _srcCompiled' + cssOption + clientScriptOption + dataScriptOption + ', sourcePath: _srcPath });',
200
+ ' } catch (err) {',
201
+ ' if (err && (err.name === \'NotFoundError\' || err.name === \'Redirect\')) throw err;',
202
+ ' const html = await __renderErrorFullPage(params, requestUrl, err);',
203
+ " return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: 500 });",
204
+ ' }',
205
+ ' return new Response(new ReadableStream({',
206
+ ' async start(controller) {',
207
+ ' const enc = new TextEncoder();',
208
+ ' try {',
209
+ ' for await (const chunk of stream) {',
210
+ ' controller.enqueue(enc.encode(chunk));',
211
+ ' }',
212
+ ' } catch (err) {',
213
+ ' if (err && (err.name === \'NotFoundError\' || err.name === \'Redirect\')) throw err;',
214
+ ' try {',
215
+ ' const html = await __renderErrorFullPage(params, requestUrl, err);',
216
+ ' controller.enqueue(enc.encode(html));',
217
+ ' } catch {}',
218
+ ' }',
219
+ ' controller.close();',
220
+ ' },',
221
+ " }), { headers: { 'Content-Type': 'text/html' }, status: 200 });",
222
+ ' });',
223
+ '}',
224
+ '',
225
+ ].join('\n');
226
+ }
227
+ let dataCode;
228
+ if (hasLayout || hasAncestorLayout) {
229
+ dataCode = [
230
+ " if (request.headers.get('x-vesk-data') === '1') {",
231
+ ' let dataPage;',
232
+ ' try {',
233
+ ' dataPage = await renderPage(_pageSrc, _pageComp, { params }, __componentRegistry, { hydrate: true, cached: _pageCompiled, sourcePath: _pagePath });',
234
+ ' } catch (err) {',
235
+ ' if (err && (err.name === \'NotFoundError\' || err.name === \'Redirect\')) throw err;',
236
+ ' const message = err && typeof err === \'object\' && \'message\' in err ? String(err.message) : String(err);',
237
+ " return new Response(JSON.stringify({ error: message }), { status: 500, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', Vary: 'x-vesk-data' } });",
238
+ ' }',
239
+ ' const dataLayout = await renderPage(_layoutSrc, _layoutComp, { params, children: \'\' }, __componentRegistry, { hydrate: true, cached: _layoutCompiled, sourcePath: _layoutPath });',
240
+ " return new Response(JSON.stringify({ path: url.pathname, params, props: dataPage.props || { params }, head: (dataLayout.head || '') + (dataPage.head || '') }), {",
241
+ " headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', Vary: 'x-vesk-data' },",
242
+ ' });',
243
+ ' }',
244
+ ' return __renderHtml(params, url.href);',
245
+ ].join('\n');
246
+ }
247
+ else {
248
+ dataCode = [
249
+ " if (request.headers.get('x-vesk-data') === '1') {",
250
+ ' let dataPage;',
251
+ ' try {',
252
+ ' dataPage = await renderPage(_src, _comp, { params }, __componentRegistry, { hydrate: true, cached: _srcCompiled, sourcePath: _srcPath });',
253
+ ' } catch (err) {',
254
+ ' if (err && (err.name === \'NotFoundError\' || err.name === \'Redirect\')) throw err;',
255
+ ' const message = err && typeof err === \'object\' && \'message\' in err ? String(err.message) : String(err);',
256
+ " return new Response(JSON.stringify({ error: message }), { status: 500, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', Vary: 'x-vesk-data' } });",
257
+ ' }',
258
+ " return new Response(JSON.stringify({ path: url.pathname, params, props: dataPage.props || { params }, head: dataPage.head || '' }), {",
259
+ " headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', Vary: 'x-vesk-data' },",
260
+ ' });',
261
+ ' }',
262
+ ' return __renderHtml(params, url.href);',
263
+ ].join('\n');
264
+ }
265
+ const hasMiddleware = !!middlewareCode;
266
+ let bodyCode;
267
+ if (hasMiddleware) {
268
+ const indentedRender = dataCode.split('\n').map(l => l ? ` ${l}` : '').join('\n');
269
+ bodyCode = [
270
+ ' // ── Middleware context ──',
271
+ ' const __ctx = {',
272
+ ' request,',
273
+ ' params,',
274
+ ' url,',
275
+ " locals: {},",
276
+ " cookies: parseCookies(request.headers.get('cookie') || ''),",
277
+ ' set(key, value) { this.locals[key] = value; },',
278
+ ' get(key) { return this.locals[key]; },',
279
+ ' };',
280
+ ' const __mwResult = await __executeMw(__ctx);',
281
+ ' if (__mwResult.response) return __mwResult.response;',
282
+ " if (__mwResult.rewriteUrl) url.pathname = __mwResult.rewriteUrl;",
283
+ ' const prev = globalThis.__vesk_request;',
284
+ ' globalThis.__vesk_request = __ctx;',
285
+ ' try {',
286
+ indentedRender,
287
+ ' } finally {',
288
+ ' globalThis.__vesk_request = prev;',
289
+ ' }',
290
+ ].join('\n');
291
+ }
292
+ else {
293
+ bodyCode = dataCode;
294
+ }
295
+ let registerActionsCode;
296
+ if (hasLayout || hasAncestorLayout) {
297
+ registerActionsCode = [
298
+ 'async function __registerActions() {',
299
+ ' if (__actionsRegistered) return;',
300
+ ' __actionsRegistered = true;',
301
+ ' compileFile(_layoutSrc, { sourcePath: _layoutPath });',
302
+ ' compileFile(_pageSrc, { sourcePath: _pagePath });',
303
+ '}',
304
+ '',
305
+ ].join('\n');
306
+ }
307
+ else {
308
+ registerActionsCode = [
309
+ 'async function __registerActions() {',
310
+ ' if (__actionsRegistered) return;',
311
+ ' __actionsRegistered = true;',
312
+ ' compileFile(_src, { sourcePath: _srcPath });',
313
+ '}',
314
+ '',
315
+ ].join('\n');
316
+ }
317
+ const actionCode = [
318
+ 'export async function handleAction(request, id) {',
319
+ ' await __registerActions();',
320
+ ' const action = getAction(id);',
321
+ ' if (!action) {',
322
+ " return new Response(JSON.stringify({ ok: false, error: 'Action not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
323
+ ' }',
324
+ ' let input = {};',
325
+ " const ct = request.headers.get('content-type') || '';",
326
+ " if (ct.includes('json')) {",
327
+ ' input = await request.json().catch(() => ({}));',
328
+ " } else if (ct.includes('multipart/form-data') || ct.includes('x-www-form-urlencoded')) {",
329
+ ' const fd = await request.formData().catch(() => null);',
330
+ ' if (fd) input = Object.fromEntries(fd.entries());',
331
+ ' } else {',
332
+ " const text = await request.text().catch(() => '');",
333
+ ' if (text) { try { input = JSON.parse(text); } catch {} }',
334
+ ' }',
335
+ ' const issues = validateActionInput(action, input);',
336
+ " const referer = request.headers.get('referer') || '';",
337
+ " const isFetch = !(request.headers.get('accept') || '').includes('text/html');",
338
+ ' const base = referer || request.url;',
339
+ ' const pageUrl = new URL(base);',
340
+ ' const params = __paramsFor(pageUrl.pathname);',
341
+ ' if (issues.length > 0) {',
342
+ ' if (isFetch) {',
343
+ " return new Response(JSON.stringify({ ok: false, issues }), { status: 200, headers: { 'Content-Type': 'application/json' } });",
344
+ ' }',
345
+ ' const prevReq = globalThis.__vesk_request;',
346
+ ' globalThis.__vesk_action_errors = issuesToFieldMap(issues);',
347
+ ' try {',
348
+ ' return await __renderHtml(params, pageUrl.href);',
349
+ ' } finally {',
350
+ ' globalThis.__vesk_action_errors = undefined;',
351
+ ' globalThis.__vesk_request = prevReq;',
352
+ ' }',
353
+ ' }',
354
+ ' const prevReq = globalThis.__vesk_request;',
355
+ ' globalThis.__vesk_request = {',
356
+ ' request,',
357
+ ' params,',
358
+ ' url: pageUrl,',
359
+ ' locals: {},',
360
+ " cookies: parseCookies(request.headers.get('cookie') || ''),",
361
+ ' };',
362
+ ' try {',
363
+ ' const result = await action.execute(input, {',
364
+ ' request,',
365
+ ' params,',
366
+ ' url: pageUrl.href,',
367
+ ' headers: () => { const m = new Map(); for (const [k, v] of request.headers.entries()) m.set(k.toLowerCase(), String(v)); return m; },',
368
+ " cookies: () => parseCookies(request.headers.get('cookie') || ''),",
369
+ " locals: () => (globalThis.__vesk_request ? globalThis.__vesk_request.locals : {}),",
370
+ " redirect: (u, status) => new Response(null, { status: status || 303, headers: { Location: u } }),",
371
+ ' });',
372
+ ' if (isFetch) {',
373
+ " return new Response(JSON.stringify({ ok: true, data: result ?? null }), { status: 200, headers: { 'Content-Type': 'application/json' } });",
374
+ ' }',
375
+ " const location = referer ? new URL(referer).pathname + new URL(referer).search : '/';",
376
+ " return new Response(null, { status: 303, headers: { Location: location } });",
377
+ ' } catch (err) {',
378
+ " const message = err && typeof err === 'object' && 'message' in err ? String(err.message) : 'Action failed';",
379
+ ' if (isFetch) {',
380
+ " return new Response(JSON.stringify({ ok: false, error: message }), { status: 500, headers: { 'Content-Type': 'application/json' } });",
381
+ ' }',
382
+ " return new Response(message, { status: 500, headers: { 'Content-Type': 'text/plain' } });",
383
+ ' } finally {',
384
+ ' globalThis.__vesk_request = prevReq;',
385
+ ' }',
386
+ '}',
387
+ '',
388
+ ].join('\n');
389
+ const funcCode = [
390
+ "import { renderFullPage, renderPageStream, renderPage, compileFile, setVskHydrate, parseCookies, getAction, validateActionInput, issuesToFieldMap, storeDataScriptGlobal, withSsrStore } from '../runtime.js';",
391
+ '',
392
+ middlewareCode || '',
393
+ registryCode,
394
+ src,
395
+ '',
396
+ paramsCode,
397
+ htmlFnCode,
398
+ '',
399
+ 'export async function handle(request) {',
400
+ ' const url = new URL(request.url);',
401
+ ' const params = __paramsFor(url.pathname);',
402
+ " Object.defineProperty(request, 'query', {",
403
+ ' get: () => Object.fromEntries(url.searchParams.entries()),',
404
+ ' enumerable: true,',
405
+ ' });',
406
+ bodyCode,
407
+ '}',
408
+ '',
409
+ 'let __actionsRegistered = false;',
410
+ '',
411
+ registerActionsCode,
412
+ actionCode,
413
+ ].filter(Boolean).join('\n');
414
+ return { funcPath, funcCode, name };
415
+ }
@@ -0,0 +1,8 @@
1
+ import type { RouteNode, SsgRouteResult } from '@vesk/adapter/src/types';
2
+ export declare function copyStaticAssets(publicDir: string, outDir: string): void;
3
+ export declare function generateSsgRoutes(routeTree: RouteNode[], appDir: string, outDir: string): Promise<SsgRouteResult[]>;
4
+ export declare function generateSitemap(_routeTree: RouteNode[], ssrRoutes: RouteNode[], prerenderedRoutes: SsgRouteResult[], { siteUrl }?: {
5
+ siteUrl?: string;
6
+ }): string;
7
+ export declare function generateRobotsTxt(siteUrl?: string): string;
8
+ //# sourceMappingURL=static.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../src/static.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAsBxE;AAED,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAiEzH;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,SAAS,EAAE,EACvB,SAAS,EAAE,SAAS,EAAE,EACtB,iBAAiB,EAAE,cAAc,EAAE,EACnC,EAAE,OAAiC,EAAE,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,MAAM,CAoCR;AAED,wBAAgB,iBAAiB,CAAC,OAAO,SAA0B,GAAG,MAAM,CAK3E"}
package/dist/static.js ADDED
@@ -0,0 +1,130 @@
1
+ import { mkdirSync, copyFileSync, readdirSync, statSync, existsSync, writeFileSync, readFileSync } from 'node:fs';
2
+ import { resolve, join } from 'node:path';
3
+ export function copyStaticAssets(publicDir, outDir) {
4
+ const targetDir = resolve(outDir, 'static', 'public');
5
+ mkdirSync(targetDir, { recursive: true });
6
+ if (!existsSync(publicDir))
7
+ return;
8
+ function copyDir(src, dest) {
9
+ mkdirSync(dest, { recursive: true });
10
+ const entries = readdirSync(src);
11
+ for (const entry of entries) {
12
+ const srcPath = join(src, entry);
13
+ const destPath = join(dest, entry);
14
+ const st = statSync(srcPath);
15
+ if (st.isDirectory()) {
16
+ copyDir(srcPath, destPath);
17
+ }
18
+ else {
19
+ copyFileSync(srcPath, destPath);
20
+ }
21
+ }
22
+ }
23
+ copyDir(publicDir, targetDir);
24
+ }
25
+ export async function generateSsgRoutes(routeTree, appDir, outDir) {
26
+ const { ssg } = await import('@vesk/compiler/src/server-render');
27
+ const prerenderDir = resolve(outDir, 'prerendered');
28
+ mkdirSync(prerenderDir, { recursive: true });
29
+ const results = [];
30
+ async function evaluateExport(src, exportName) {
31
+ try {
32
+ const match = src.match(new RegExp(`export\\s+async\\s+function\\s+${exportName}\\s*\\(([^)]*)\\)\\s*{([\\s\\S]*?\\n})`));
33
+ if (!match)
34
+ return null;
35
+ const params = match[1].split(',').map(p => p.trim()).filter(Boolean);
36
+ const body = match[2];
37
+ const fn = new Function(...params, `return (async () => { ${body} })()`);
38
+ return await fn();
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ async function walk(nodes) {
45
+ for (const node of nodes) {
46
+ if (node.page) {
47
+ const pagePath = resolve(appDir, node.sourceDir, 'page.vsk');
48
+ const src = readFileSync(pagePath, 'utf-8');
49
+ const hasStaticProps = src.includes('getStaticProps');
50
+ const hasStaticPaths = src.includes('getStaticPaths');
51
+ if (hasStaticPaths) {
52
+ const paths = await evaluateExport(src, 'getStaticPaths');
53
+ if (paths && Array.isArray(paths)) {
54
+ for (const entry of paths) {
55
+ try {
56
+ const params = entry.params || {};
57
+ const result = await ssg(src, null, params, {});
58
+ const urlPath = entry.path || node.fullPath;
59
+ const htmlPath = resolve(prerenderDir, urlPath === '/' ? 'index.html' : `${urlPath.replace(/^\//, '')}.html`);
60
+ mkdirSync(resolve(htmlPath, '..'), { recursive: true });
61
+ writeFileSync(htmlPath, result.html);
62
+ results.push({ path: urlPath, html: htmlPath, static: result.static, params });
63
+ }
64
+ catch (e) {
65
+ const message = e instanceof Error ? e.message : String(e);
66
+ console.error(`vesk: SSG failed for ${pagePath} (path: ${entry.path || node.fullPath}): ${message}`);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ else if (hasStaticProps) {
72
+ try {
73
+ const result = await ssg(src, null, undefined, {});
74
+ const htmlPath = resolve(prerenderDir, node.fullPath === '/' ? 'index.html' : `${node.fullPath.slice(1)}.html`);
75
+ mkdirSync(resolve(htmlPath, '..'), { recursive: true });
76
+ writeFileSync(htmlPath, result.html);
77
+ results.push({ path: node.fullPath, html: htmlPath, static: result.static });
78
+ }
79
+ catch (e) {
80
+ const message = e instanceof Error ? e.message : String(e);
81
+ console.error(`vesk: SSG failed for ${pagePath}: ${message}`);
82
+ }
83
+ }
84
+ }
85
+ await walk(node.children || []);
86
+ }
87
+ }
88
+ await walk(routeTree);
89
+ return results;
90
+ }
91
+ export function generateSitemap(_routeTree, ssrRoutes, prerenderedRoutes, { siteUrl = 'http://localhost:3000' } = {}) {
92
+ const urls = [];
93
+ const seen = new Set();
94
+ function addUrl(path, priority, changefreq) {
95
+ if (seen.has(path))
96
+ return;
97
+ seen.add(path);
98
+ const cleanPath = path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;
99
+ urls.push({ loc: `${siteUrl}${cleanPath}`, priority, changefreq });
100
+ }
101
+ for (const r of prerenderedRoutes) {
102
+ addUrl(r.path, '0.80', 'weekly');
103
+ }
104
+ function walk(nodes) {
105
+ for (const node of nodes) {
106
+ if (node.page && !node.fullPath.includes(':')) {
107
+ addUrl(node.fullPath, '0.64', 'daily');
108
+ }
109
+ walk(node.children || []);
110
+ }
111
+ }
112
+ walk(ssrRoutes);
113
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
114
+ xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
115
+ for (const u of urls) {
116
+ xml += ' <url>\n';
117
+ xml += ` <loc>${u.loc}</loc>\n`;
118
+ xml += ` <changefreq>${u.changefreq}</changefreq>\n`;
119
+ xml += ` <priority>${u.priority}</priority>\n`;
120
+ xml += ' </url>\n';
121
+ }
122
+ xml += '</urlset>\n';
123
+ return xml;
124
+ }
125
+ export function generateRobotsTxt(siteUrl = 'http://localhost:3000') {
126
+ return `User-agent: *
127
+ Allow: /
128
+ Sitemap: ${siteUrl}/sitemap.xml
129
+ `;
130
+ }