@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,119 @@
1
+ import { mkdirSync, copyFileSync, readdirSync, statSync, existsSync, writeFileSync, rmSync, readFileSync, } from 'node:fs';
2
+ import { resolve, join, extname, dirname } from 'node:path';
3
+ export const MIME = {
4
+ '.html': 'text/html; charset=utf-8',
5
+ '.htm': 'text/html; charset=utf-8',
6
+ '.js': 'application/javascript; charset=utf-8',
7
+ '.mjs': 'application/javascript; charset=utf-8',
8
+ '.css': 'text/css; charset=utf-8',
9
+ '.json': 'application/json; charset=utf-8',
10
+ '.svg': 'image/svg+xml',
11
+ '.png': 'image/png',
12
+ '.jpg': 'image/jpeg',
13
+ '.jpeg': 'image/jpeg',
14
+ '.gif': 'image/gif',
15
+ '.webp': 'image/webp',
16
+ '.avif': 'image/avif',
17
+ '.ico': 'image/x-icon',
18
+ '.txt': 'text/plain; charset=utf-8',
19
+ '.xml': 'application/xml',
20
+ '.woff': 'font/woff',
21
+ '.woff2': 'font/woff2',
22
+ '.ttf': 'font/ttf',
23
+ '.otf': 'font/otf',
24
+ '.wasm': 'application/wasm',
25
+ '.map': 'application/json',
26
+ '.webmanifest': 'application/manifest+json',
27
+ };
28
+ export function ensureCleanDir(dir) {
29
+ rmSync(dir, { recursive: true, force: true });
30
+ mkdirSync(dir, { recursive: true });
31
+ }
32
+ export function copyDirContents(srcDir, destDir) {
33
+ if (!existsSync(srcDir))
34
+ return;
35
+ mkdirSync(destDir, { recursive: true });
36
+ for (const entry of readdirSync(srcDir)) {
37
+ const srcPath = join(srcDir, entry);
38
+ const destPath = join(destDir, entry);
39
+ if (statSync(srcPath).isDirectory()) {
40
+ copyDirContents(srcPath, destPath);
41
+ }
42
+ else {
43
+ mkdirSync(dirname(destPath), { recursive: true });
44
+ copyFileSync(srcPath, destPath);
45
+ }
46
+ }
47
+ }
48
+ export function writeFile(path, content) {
49
+ mkdirSync(dirname(path), { recursive: true });
50
+ writeFileSync(path, content, 'utf-8');
51
+ }
52
+ /**
53
+ * Lay out `.vesk/static/**` into a platform `static/` directory using the
54
+ * URL scheme the SSR output expects:
55
+ * /_vesk/static/* <- .vesk/static/*
56
+ * /_vesk/runtime.js <- .vesk/static/client.js (alias)
57
+ * /* (public) <- .vesk/static/public/*
58
+ */
59
+ export function writePlatformStatic(buildStaticDir, platformStaticDir) {
60
+ mkdirSync(platformStaticDir, { recursive: true });
61
+ const publicDir = resolve(buildStaticDir, 'public');
62
+ if (existsSync(publicDir)) {
63
+ copyDirContents(publicDir, platformStaticDir);
64
+ }
65
+ const assetsDir = resolve(platformStaticDir, '_vesk', 'static');
66
+ copyDirContents(buildStaticDir, assetsDir);
67
+ const runtimeAlias = resolve(platformStaticDir, '_vesk', 'runtime.js');
68
+ const clientPath = resolve(buildStaticDir, 'client.js');
69
+ if (existsSync(clientPath)) {
70
+ mkdirSync(dirname(runtimeAlias), { recursive: true });
71
+ copyFileSync(clientPath, runtimeAlias);
72
+ }
73
+ }
74
+ /**
75
+ * Write SSG pages into a platform static dir. They land under
76
+ * `_vesk/static/public/<path>.html` — the exact location the platform
77
+ * handler's prerendered 308 redirect points to — plus a `<path>/index.html`
78
+ * twin for trailing-slash URLs. `route.html` is the prerendered file path.
79
+ */
80
+ export function writePrerenderedStatic(prerenderedRoutes, platformStaticDir) {
81
+ for (const route of prerenderedRoutes) {
82
+ if (!existsSync(route.html))
83
+ continue;
84
+ const content = readFileSync(route.html);
85
+ const htmlRel = route.path === '/' ? 'index.html' : `${route.path.replace(/^\//, '')}.html`;
86
+ const target = resolve(platformStaticDir, '_vesk', 'static', 'public', htmlRel);
87
+ writeFile(target, content);
88
+ if (route.path !== '/' && route.path.endsWith('/')) {
89
+ const dirIndex = resolve(platformStaticDir, '_vesk', 'static', 'public', `${route.path.replace(/^\//, '')}index.html`);
90
+ writeFile(dirIndex, content);
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ * Recursively list the files of a static dir as `{ rel, buffer }` so a target
96
+ * runtime without a filesystem (generic edge) can inline them into the bundle.
97
+ */
98
+ export function listStaticDir(dir) {
99
+ const out = [];
100
+ if (!existsSync(dir))
101
+ return out;
102
+ function walk(d, prefix) {
103
+ for (const entry of readdirSync(d)) {
104
+ const full = join(d, entry);
105
+ const rel = prefix ? `${prefix}/${entry}` : entry;
106
+ if (statSync(full).isDirectory()) {
107
+ walk(full, rel);
108
+ }
109
+ else {
110
+ out.push({ rel, buffer: readFileSync(full) });
111
+ }
112
+ }
113
+ }
114
+ walk(dir, '');
115
+ return out;
116
+ }
117
+ export function mimeFor(path) {
118
+ return MIME[extname(path).toLowerCase()] || 'application/octet-stream';
119
+ }
@@ -0,0 +1,17 @@
1
+ export type Platform = 'node' | 'vercel' | 'netlify' | 'cloudflare' | 'deno' | 'aws' | 'edge' | 'coxmos';
2
+ export interface PlatformEnv {
3
+ [key: string]: string | undefined;
4
+ }
5
+ /**
6
+ * Detect the deployment platform from the build environment so `vesk build`
7
+ * emits the correct artifact (Vercel Build Output API, Netlify functions,
8
+ * Cloudflare _worker.js, Deno entry, etc.) without extra flags.
9
+ *
10
+ * Precedence:
11
+ * 1. Explicit `--platform <name>` CLI override
12
+ * 2. Well-known platform environment variables set by CI/build systems
13
+ * 3. Defaults to `node`
14
+ */
15
+ export declare function detectPlatform(args?: string[], env?: PlatformEnv): Platform;
16
+ export declare function platformLabel(platform: Platform): string;
17
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;AAIzG,MAAM,WAAW,WAAW;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAM,EAAO,EAAE,GAAG,GAAE,WAAwC,GAAG,QAAQ,CAoB3G;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAExD"}
@@ -0,0 +1,35 @@
1
+ const VALID = ['node', 'vercel', 'netlify', 'cloudflare', 'deno', 'aws', 'edge', 'coxmos'];
2
+ /**
3
+ * Detect the deployment platform from the build environment so `vesk build`
4
+ * emits the correct artifact (Vercel Build Output API, Netlify functions,
5
+ * Cloudflare _worker.js, Deno entry, etc.) without extra flags.
6
+ *
7
+ * Precedence:
8
+ * 1. Explicit `--platform <name>` CLI override
9
+ * 2. Well-known platform environment variables set by CI/build systems
10
+ * 3. Defaults to `node`
11
+ */
12
+ export function detectPlatform(args = [], env = process.env) {
13
+ const flagIdx = args.indexOf('--platform');
14
+ if (flagIdx !== -1) {
15
+ const explicit = (args[flagIdx + 1] || '').toLowerCase();
16
+ if (VALID.includes(explicit))
17
+ return explicit;
18
+ }
19
+ if (env.VERCEL || env.VERCEL_ENV || env.NOW_REGION || env.VERCEL_GIT_COMMIT_SHA)
20
+ return 'vercel';
21
+ if (env.NETLIFY || env.NETLIFY_BUILD_CONTEXT || env.NETLIFY_LOCAL || env.NETLIFY_EDGE)
22
+ return 'netlify';
23
+ if (env.CF_PAGES || env.CF_PAGES_BRANCH || env.CF_PAGES_URL || env.CLOUDFLARE_WORKERS || env.WORKERS_NAME)
24
+ return 'cloudflare';
25
+ if (env.DENO_DEPLOYMENT_ID || env.DENO_REGION || env.DENO_DEPLOY_URL)
26
+ return 'deno';
27
+ if (env.AWS_LAMBDA_FUNCTION_NAME || env.AWS_LAMBDA_FUNCTION_VERSION || env.LAMBDA_TASK_ROOT || env.LAMBDA_RUNTIME_DIR)
28
+ return 'aws';
29
+ if (env.COXMOS || env.COXMOS_DEPLOYMENT_ID || env.COXMOS_ENV || env.VESK_DEPLOY || env.VESK_PLATFORM === 'coxmos')
30
+ return 'coxmos';
31
+ return 'node';
32
+ }
33
+ export function platformLabel(platform) {
34
+ return platform === 'node' ? 'node (default)' : platform;
35
+ }
@@ -0,0 +1,5 @@
1
+ import { type Server } from 'node:http';
2
+ export declare function startProdServer(outDir: string, options?: {
3
+ port?: number;
4
+ }): Promise<Server>;
5
+ //# sourceMappingURL=prod-server.d.ts.map
@@ -0,0 +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;AAgGjG,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAyWlG"}
@@ -0,0 +1,429 @@
1
+ import { readFileSync, existsSync, statSync } from 'node:fs';
2
+ import { resolve, extname, dirname } from 'node:path';
3
+ import { createServer } from 'node:http';
4
+ import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createRateLimiter, resolveComponentName } from '@vesk/compiler/src/server-codegen';
7
+ import { securityHeaders, getClientProtocol } from '@vesk/compiler/src/server-utils';
8
+ const _require = createRequire(import.meta.url);
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ async function readBody(req) {
11
+ const chunks = [];
12
+ for await (const chunk of req)
13
+ chunks.push(chunk);
14
+ return Buffer.concat(chunks);
15
+ }
16
+ function makeWebRequest(nodeReq, url) {
17
+ const parsedUrl = new URL(url, `http://${nodeReq.headers.host || 'localhost'}`);
18
+ const method = nodeReq.method || 'GET';
19
+ let _bodyBuffer = null;
20
+ async function getBody() {
21
+ if (_bodyBuffer)
22
+ return _bodyBuffer;
23
+ const chunks = [];
24
+ for await (const chunk of nodeReq)
25
+ chunks.push(Buffer.from(chunk));
26
+ _bodyBuffer = Buffer.concat(chunks);
27
+ return _bodyBuffer;
28
+ }
29
+ const webRequest = new Request(parsedUrl, { method, headers: nodeReq.headers, body: null });
30
+ webRequest.json = async () => { try {
31
+ return JSON.parse((await getBody()).toString());
32
+ }
33
+ catch {
34
+ return null;
35
+ } };
36
+ webRequest.text = async () => (await getBody()).toString('utf-8');
37
+ webRequest.formData = async () => {
38
+ const body = await getBody();
39
+ const ct = String(nodeReq.headers['content-type'] || '');
40
+ if (ct.includes('multipart/form-data')) {
41
+ const temp = new Request('http://localhost', { method: 'POST', headers: nodeReq.headers, body: body });
42
+ return temp.formData();
43
+ }
44
+ const fd = new FormData();
45
+ if (ct.includes('x-www-form-urlencoded')) {
46
+ for (const [k, v] of new URLSearchParams(body.toString()).entries())
47
+ fd.append(k, v);
48
+ }
49
+ return fd;
50
+ };
51
+ webRequest.clone = () => webRequest;
52
+ const cookies = {};
53
+ for (const [k, v] of String(nodeReq.headers.cookie || '').split(';').map(s => s.trim()).filter(Boolean)) {
54
+ const eq = k.indexOf('=');
55
+ if (eq > -1)
56
+ cookies[k.slice(0, eq)] = decodeURIComponent(k.slice(eq + 1));
57
+ }
58
+ webRequest.cookies = cookies;
59
+ const query = {};
60
+ for (const [k, v] of parsedUrl.searchParams.entries())
61
+ query[k] = v;
62
+ webRequest.query = query;
63
+ return webRequest;
64
+ }
65
+ const MIME = {
66
+ '.svg': 'image/svg+xml', '.css': 'text/css', '.js': 'application/javascript',
67
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
68
+ '.ico': 'image/x-icon', '.html': 'text/html', '.json': 'application/json',
69
+ '.woff': 'font/woff', '.woff2': 'font/woff2', '.wasm': 'application/wasm',
70
+ };
71
+ export async function startProdServer(outDir, options) {
72
+ const port = options?.port || 3000;
73
+ const staticDir = resolve(outDir, 'static');
74
+ const configPath = resolve(outDir, 'config.json');
75
+ if (!existsSync(configPath)) {
76
+ console.error(`vesk start: no build found at ${outDir}`);
77
+ console.error('Run "vesk build" first');
78
+ process.exit(1);
79
+ }
80
+ const buildConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
81
+ console.error(`vesk start: serving from ${outDir}`);
82
+ const projectDir = resolve(outDir, '..');
83
+ let securityConfig = {};
84
+ try {
85
+ const veskConfigPath = resolve(projectDir, 'vesk.config.js');
86
+ const veskConfigTsPath = resolve(projectDir, 'vesk.config.ts');
87
+ let rawConfig = {};
88
+ if (existsSync(veskConfigPath)) {
89
+ rawConfig = _require(veskConfigPath);
90
+ }
91
+ else if (existsSync(veskConfigTsPath)) {
92
+ const { transpile } = _require('typescript');
93
+ const src = readFileSync(veskConfigTsPath, 'utf-8');
94
+ const result = transpile(src, { module: 99, target: 99 });
95
+ rawConfig = eval(`(${result})`);
96
+ }
97
+ if (typeof rawConfig === 'function')
98
+ rawConfig = rawConfig();
99
+ const configObj = rawConfig;
100
+ securityConfig = { security: configObj.security };
101
+ }
102
+ catch {
103
+ // ignore config load errors
104
+ }
105
+ let rateLimiter = null;
106
+ if (securityConfig?.rateLimit) {
107
+ const rlConfig = securityConfig.rateLimit;
108
+ rateLimiter = createRateLimiter({ windowMs: rlConfig.windowMs || 60000, max: rlConfig.max || 100 });
109
+ }
110
+ let securityHeadersFn = null;
111
+ try {
112
+ securityHeadersFn = securityHeaders;
113
+ }
114
+ catch {
115
+ // security headers not available
116
+ }
117
+ let middlewareMod = null;
118
+ const mwPath = resolve(outDir, 'server', 'middleware.js');
119
+ if (existsSync(mwPath)) {
120
+ try {
121
+ middlewareMod = await import(`${mwPath}?t=${Date.now()}`);
122
+ }
123
+ catch {
124
+ // ignore
125
+ }
126
+ }
127
+ const functionCache = new Map();
128
+ async function loadFunction(funcPath) {
129
+ if (functionCache.has(funcPath))
130
+ return functionCache.get(funcPath);
131
+ const fullPath = resolve(outDir, funcPath);
132
+ if (!existsSync(fullPath))
133
+ return null;
134
+ try {
135
+ const mod = await import(`${fullPath}?t=${Date.now()}`);
136
+ functionCache.set(funcPath, mod);
137
+ return mod;
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ }
143
+ function matchPath(pattern, pathname) {
144
+ const patternParts = pattern.split('/').filter(Boolean);
145
+ const pathParts = pathname.split('/').filter(Boolean);
146
+ let pi = 0, pp = 0;
147
+ const params = {};
148
+ while (pi < pathParts.length && pp < patternParts.length) {
149
+ if (patternParts[pp].startsWith(':')) {
150
+ const name = patternParts[pp].slice(1);
151
+ params[name] = pathParts[pi];
152
+ pi++;
153
+ pp++;
154
+ }
155
+ else if (patternParts[pp] === pathParts[pi]) {
156
+ pi++;
157
+ pp++;
158
+ }
159
+ else {
160
+ return null;
161
+ }
162
+ }
163
+ if (pp === patternParts.length && pi === pathParts.length)
164
+ return params;
165
+ return null;
166
+ }
167
+ function getClientIpFromReq(req) {
168
+ if (!securityConfig?.trustProxy)
169
+ return req.socket?.remoteAddress || 'unknown';
170
+ const forwarded = req.headers['x-forwarded-for'];
171
+ if (forwarded)
172
+ return (typeof forwarded === 'string' ? forwarded.split(',')[0] : forwarded[0]).trim();
173
+ return req.headers['x-real-ip'] || req.socket?.remoteAddress || 'unknown';
174
+ }
175
+ const server = createServer(async (req, res) => {
176
+ const url = new URL(req.url || '/', `http://localhost:${port}`);
177
+ const reqHost = req.headers.host || `localhost:${port}`;
178
+ const proto = req.socket.encrypted
179
+ ? 'https'
180
+ : getClientProtocol({ headers: req.headers }, !!securityConfig?.trustProxy);
181
+ globalThis.__vesk_ssr_base_url = `${proto}://${reqHost}`;
182
+ if (rateLimiter) {
183
+ const clientIp = getClientIpFromReq(req);
184
+ if (!rateLimiter.check(clientIp)) {
185
+ const retryAfter = Math.ceil((securityConfig?.rateLimit?.windowMs || 60000) / 1000);
186
+ res.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': String(retryAfter) });
187
+ res.end(JSON.stringify({ error: 'Too Many Requests' }));
188
+ return;
189
+ }
190
+ }
191
+ const origWriteHead = res.writeHead.bind(res);
192
+ let secHeadersApplied = false;
193
+ res.writeHead = ((statusCode, headers) => {
194
+ if (!secHeadersApplied && securityHeadersFn) {
195
+ secHeadersApplied = true;
196
+ const sh = securityHeadersFn({ security: securityConfig?.security || {} });
197
+ headers = { ...sh, ...headers };
198
+ }
199
+ return origWriteHead(statusCode, headers);
200
+ });
201
+ const publicDir = resolve(staticDir, 'public');
202
+ const sanitized = url.pathname.replace(/\.\./g, '');
203
+ const rootFile = resolve(publicDir, sanitized.slice(1));
204
+ if (rootFile.startsWith(publicDir) && existsSync(rootFile) && statSync(rootFile).isFile()) {
205
+ const ext = extname(rootFile);
206
+ res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
207
+ res.end(readFileSync(rootFile));
208
+ return;
209
+ }
210
+ if (url.pathname === '/ssr-data.js') {
211
+ const token = url.searchParams.get('t') || '';
212
+ const store = globalThis.__vsk_ssr_data_store;
213
+ const payload = store?.[token];
214
+ if (payload)
215
+ delete store[token];
216
+ const lines = [];
217
+ if (payload?.props)
218
+ lines.push(`globalThis.__vesk_props = ${JSON.stringify(payload.props)};`);
219
+ if (payload?.ssrData)
220
+ lines.push(`globalThis.__vsk_ssr_data = ${JSON.stringify(payload.ssrData)};`);
221
+ res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-store' });
222
+ res.end(lines.join('\n') || '// no ssr data');
223
+ return;
224
+ }
225
+ if (url.pathname === '/_vesk/runtime.js') {
226
+ const clientPath = resolve(staticDir, 'client.js');
227
+ if (existsSync(clientPath)) {
228
+ res.writeHead(200, { 'Content-Type': 'application/javascript' });
229
+ res.end(readFileSync(clientPath));
230
+ return;
231
+ }
232
+ }
233
+ if (url.pathname.startsWith('/_vesk/static/')) {
234
+ const relPath = url.pathname.replace('/_vesk/static/', '').replace(/\.\./g, '');
235
+ const staticPath = resolve(staticDir, relPath);
236
+ if (!staticPath.startsWith(staticDir)) {
237
+ res.writeHead(403);
238
+ res.end('Forbidden');
239
+ return;
240
+ }
241
+ if (existsSync(staticPath) && statSync(staticPath).isFile()) {
242
+ const ext = extname(staticPath);
243
+ res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
244
+ res.end(readFileSync(staticPath));
245
+ return;
246
+ }
247
+ }
248
+ if (buildConfig.prerendered) {
249
+ const prerendered = buildConfig.prerendered.find(r => r.path === url.pathname);
250
+ if (prerendered) {
251
+ const htmlPath = resolve(outDir, prerendered.file);
252
+ if (existsSync(htmlPath)) {
253
+ res.writeHead(200, { 'Content-Type': 'text/html' });
254
+ res.end(readFileSync(htmlPath));
255
+ return;
256
+ }
257
+ }
258
+ }
259
+ if (middlewareMod) {
260
+ const mwCtx = {
261
+ request: new Request(url.href, { headers: req.headers, method: req.method || 'GET' }),
262
+ params: {},
263
+ url,
264
+ locals: {},
265
+ cookies: {},
266
+ set(key, value) { this.locals[key] = value; },
267
+ get(key) { return this.locals[key]; },
268
+ };
269
+ const mwResult = await middlewareMod.execute(mwCtx);
270
+ if (mwResult.response) {
271
+ const body = await mwResult.response.text();
272
+ res.writeHead(mwResult.response.status, Object.fromEntries(mwResult.response.headers));
273
+ res.end(body);
274
+ return;
275
+ }
276
+ if (mwResult.rewriteUrl) {
277
+ url.pathname = mwResult.rewriteUrl;
278
+ }
279
+ }
280
+ if (url.pathname.startsWith('/_vesk/action/')) {
281
+ const actionId = url.pathname.replace('/_vesk/action/', '');
282
+ const actionEntry = buildConfig.actions && buildConfig.actions.find(a => a.id === actionId);
283
+ if (!actionEntry) {
284
+ res.writeHead(404, { 'Content-Type': 'application/json' });
285
+ res.end(JSON.stringify({ ok: false, error: 'Action not found' }));
286
+ return;
287
+ }
288
+ const mod = await loadFunction(actionEntry.function);
289
+ if (!mod || !mod.handleAction) {
290
+ res.writeHead(404, { 'Content-Type': 'application/json' });
291
+ res.end(JSON.stringify({ ok: false, error: 'Action not found' }));
292
+ return;
293
+ }
294
+ try {
295
+ const webRequest = makeWebRequest(req, url.href);
296
+ const response = await mod.handleAction(webRequest, actionId);
297
+ const body = await response.text();
298
+ res.writeHead(response.status, Object.fromEntries(response.headers));
299
+ res.end(body);
300
+ }
301
+ catch (e) {
302
+ const message = e instanceof Error ? e.message : String(e);
303
+ res.writeHead(500, { 'Content-Type': 'application/json' });
304
+ res.end(JSON.stringify({ ok: false, error: message }));
305
+ }
306
+ return;
307
+ }
308
+ if (url.pathname.startsWith('/api')) {
309
+ for (const route of buildConfig.routes) {
310
+ if (route.type === 'api') {
311
+ const params = matchPath(route.path, url.pathname);
312
+ if (params) {
313
+ const mod = await loadFunction(route.function);
314
+ if (mod) {
315
+ try {
316
+ const webRequest = makeWebRequest(req, url.href);
317
+ const response = await mod.handle(webRequest);
318
+ const body = await response.text();
319
+ res.writeHead(response.status, Object.fromEntries(response.headers));
320
+ res.end(body);
321
+ return;
322
+ }
323
+ catch (e) {
324
+ const message = e instanceof Error ? e.message : String(e);
325
+ res.writeHead(500, { 'Content-Type': 'application/json' });
326
+ res.end(JSON.stringify({ error: message }));
327
+ return;
328
+ }
329
+ }
330
+ }
331
+ }
332
+ }
333
+ }
334
+ const appDir = resolve(projectDir, 'app');
335
+ const nfPath = resolve(appDir, 'not-found.vsk');
336
+ let notFoundHtml = null;
337
+ if (existsSync(nfPath)) {
338
+ try {
339
+ const runtimePath = resolve(outDir, 'server', 'runtime.js');
340
+ const { renderFullPage } = await import(runtimePath);
341
+ const src = readFileSync(nfPath, 'utf-8');
342
+ const compName = resolveComponentName(src) || 'NotFound';
343
+ notFoundHtml = await renderFullPage(src, compName, { params: {}, url: url.pathname }, new Map(), { hydrate: true, cssUrls: ['/_vesk/static/_tailwind.css', '/_vesk/static/global.css'], security: securityConfig?.security || {}, sourcePath: nfPath });
344
+ }
345
+ catch { }
346
+ }
347
+ for (const route of buildConfig.routes) {
348
+ if (route.type === 'ssr') {
349
+ const params = matchPath(route.path, url.pathname);
350
+ if (params) {
351
+ const mod = await loadFunction(route.function);
352
+ if (mod) {
353
+ try {
354
+ const webRequest = makeWebRequest(req, url.href);
355
+ let cachedResult = null;
356
+ if (route.revalidate && route.revalidate > 0) {
357
+ const { pageIsr } = await import('@vesk/runtime/src/index-server');
358
+ cachedResult = await pageIsr(url.pathname, async () => {
359
+ const response = await mod.handle(webRequest);
360
+ return { html: await response.text(), headers: Object.fromEntries(response.headers) };
361
+ }, { revalidate: route.revalidate, tags: route.tags || [] });
362
+ }
363
+ if (cachedResult) {
364
+ res.writeHead(200, cachedResult.headers || { 'Content-Type': 'text/html' });
365
+ res.end(cachedResult.html);
366
+ return;
367
+ }
368
+ const response = await mod.handle(webRequest);
369
+ const headers = Object.fromEntries(response.headers);
370
+ if (!headers['content-type'] && !headers['Content-Type'])
371
+ headers['Content-Type'] = 'text/html';
372
+ if (response.body && typeof response.body.getReader === 'function') {
373
+ res.writeHead(response.status, headers);
374
+ const reader = response.body.getReader();
375
+ const pump = () => {
376
+ reader.read().then(({ done, value }) => {
377
+ if (done) {
378
+ res.end();
379
+ return;
380
+ }
381
+ res.write(value);
382
+ pump();
383
+ }).catch(() => res.end());
384
+ };
385
+ pump();
386
+ }
387
+ else {
388
+ const body = await response.text();
389
+ res.writeHead(response.status, headers);
390
+ res.end(body);
391
+ }
392
+ return;
393
+ }
394
+ catch (e) {
395
+ const err = e instanceof Error ? e : new Error(String(e));
396
+ if (err.name === 'NotFoundError') {
397
+ res.writeHead(404, { 'Content-Type': 'text/html' });
398
+ res.end(notFoundHtml || '<!DOCTYPE html><html><body><h1>404</h1><p>Not Found</p></body></html>');
399
+ return;
400
+ }
401
+ console.error('vesk ssr error:', err.message);
402
+ const errPath = resolve(appDir, 'error.vsk');
403
+ let errorHtml = null;
404
+ if (existsSync(errPath)) {
405
+ try {
406
+ const runtimePath = resolve(outDir, 'server', 'runtime.js');
407
+ const { renderFullPage } = await import(runtimePath);
408
+ const src = readFileSync(errPath, 'utf-8');
409
+ const compName = resolveComponentName(src) || 'Error';
410
+ errorHtml = await renderFullPage(src, compName, { error: err.message, stack: err.stack, statusCode: 500, url: url.pathname }, new Map(), { hydrate: true, cssUrls: ['/_vesk/static/_tailwind.css', '/_vesk/static/global.css'], security: securityConfig?.security || {}, sourcePath: errPath });
411
+ }
412
+ catch { }
413
+ }
414
+ res.writeHead(500, { 'Content-Type': 'text/html' });
415
+ res.end(errorHtml || '<!DOCTYPE html><html><body><h1>500</h1><pre>Internal Server Error</pre></body></html>');
416
+ return;
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ res.writeHead(404, { 'Content-Type': 'text/html' });
423
+ res.end(notFoundHtml || '<!DOCTYPE html><html><body><h1>404</h1><p>Not Found</p></body></html>');
424
+ });
425
+ server.listen(port, () => {
426
+ console.error(`vesk production server at http://localhost:${port}`);
427
+ });
428
+ return server;
429
+ }
@@ -0,0 +1,2 @@
1
+ export declare function bundleRuntime(appDir: string, outDir: string): Promise<string>;
2
+ //# sourceMappingURL=runtime-bundle.d.ts.map
@@ -0,0 +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,CAsGnF"}