@zenithbuild/cli 0.7.5 → 0.7.7

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 (68) hide show
  1. package/dist/adapters/adapter-netlify.js +0 -8
  2. package/dist/adapters/adapter-vercel.js +6 -14
  3. package/dist/adapters/copy-hosted-page-runtime.js +2 -1
  4. package/dist/build/hoisted-code-transforms.d.ts +4 -1
  5. package/dist/build/hoisted-code-transforms.js +5 -3
  6. package/dist/build/page-ir-normalization.d.ts +1 -1
  7. package/dist/build/page-ir-normalization.js +33 -3
  8. package/dist/build/page-loop.js +46 -2
  9. package/dist/dev-build-session/helpers.d.ts +29 -0
  10. package/dist/dev-build-session/helpers.js +223 -0
  11. package/dist/dev-build-session/session.d.ts +24 -0
  12. package/dist/dev-build-session/session.js +204 -0
  13. package/dist/dev-build-session/state.d.ts +37 -0
  14. package/dist/dev-build-session/state.js +17 -0
  15. package/dist/dev-build-session.d.ts +1 -24
  16. package/dist/dev-build-session.js +1 -434
  17. package/dist/dev-server/css-state.d.ts +7 -0
  18. package/dist/dev-server/css-state.js +92 -0
  19. package/dist/dev-server/not-found.d.ts +23 -0
  20. package/dist/dev-server/not-found.js +129 -0
  21. package/dist/dev-server/request-handler.d.ts +1 -0
  22. package/dist/dev-server/request-handler.js +376 -0
  23. package/dist/dev-server/route-check.d.ts +9 -0
  24. package/dist/dev-server/route-check.js +100 -0
  25. package/dist/dev-server/watcher.d.ts +5 -0
  26. package/dist/dev-server/watcher.js +216 -0
  27. package/dist/dev-server.js +123 -924
  28. package/dist/images/payload.js +4 -0
  29. package/dist/manifest.js +46 -1
  30. package/dist/preview/create-preview-server.d.ts +18 -0
  31. package/dist/preview/create-preview-server.js +71 -0
  32. package/dist/preview/manifest.d.ts +42 -0
  33. package/dist/preview/manifest.js +57 -0
  34. package/dist/preview/paths.d.ts +3 -0
  35. package/dist/preview/paths.js +38 -0
  36. package/dist/preview/payload.d.ts +6 -0
  37. package/dist/preview/payload.js +34 -0
  38. package/dist/preview/request-handler.d.ts +1 -0
  39. package/dist/preview/request-handler.js +300 -0
  40. package/dist/preview/server-runner.d.ts +49 -0
  41. package/dist/preview/server-runner.js +220 -0
  42. package/dist/preview/server-script-runner-template.d.ts +1 -0
  43. package/dist/preview/server-script-runner-template.js +425 -0
  44. package/dist/preview.d.ts +5 -112
  45. package/dist/preview.js +7 -1119
  46. package/dist/resource-response.d.ts +15 -0
  47. package/dist/resource-response.js +91 -2
  48. package/dist/server-contract/constants.d.ts +5 -0
  49. package/dist/server-contract/constants.js +5 -0
  50. package/dist/server-contract/export-validation.d.ts +5 -0
  51. package/dist/server-contract/export-validation.js +59 -0
  52. package/dist/server-contract/json-serializable.d.ts +1 -0
  53. package/dist/server-contract/json-serializable.js +52 -0
  54. package/dist/server-contract/resolve.d.ts +15 -0
  55. package/dist/server-contract/resolve.js +271 -0
  56. package/dist/server-contract/result-helpers.d.ts +51 -0
  57. package/dist/server-contract/result-helpers.js +59 -0
  58. package/dist/server-contract/route-result-validation.d.ts +2 -0
  59. package/dist/server-contract/route-result-validation.js +73 -0
  60. package/dist/server-contract/stage.d.ts +6 -0
  61. package/dist/server-contract/stage.js +22 -0
  62. package/dist/server-contract.d.ts +6 -62
  63. package/dist/server-contract.js +9 -493
  64. package/dist/server-middleware.d.ts +10 -0
  65. package/dist/server-middleware.js +30 -0
  66. package/dist/server-output.js +13 -1
  67. package/dist/server-runtime/node-server.js +25 -3
  68. package/package.json +3 -3
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @param {{ source: string, sourcePath: string, params: Record<string, string>, requestUrl?: string, requestMethod?: string, requestHeaders?: Record<string, string | string[] | undefined>, requestBodyBuffer?: Buffer | null, routePattern?: string, routeFile?: string, routeId?: string, routeKind?: 'page' | 'resource' }} input
3
+ * @returns {Promise<{ result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
4
+ */
5
+ export function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, requestBodyBuffer, routePattern, routeFile, routeId, routeKind, guardOnly }: {
6
+ source: string;
7
+ sourcePath: string;
8
+ params: Record<string, string>;
9
+ requestUrl?: string;
10
+ requestMethod?: string;
11
+ requestHeaders?: Record<string, string | string[] | undefined>;
12
+ requestBodyBuffer?: Buffer | null;
13
+ routePattern?: string;
14
+ routeFile?: string;
15
+ routeId?: string;
16
+ routeKind?: "page" | "resource";
17
+ }): Promise<{
18
+ result: {
19
+ kind: string;
20
+ [key: string]: unknown;
21
+ };
22
+ trace: {
23
+ guard: string;
24
+ action: string;
25
+ load: string;
26
+ };
27
+ status?: number;
28
+ setCookies?: string[];
29
+ }>;
30
+ /**
31
+ * @param {{ source: string, sourcePath: string, params: Record<string, string>, requestUrl?: string, requestMethod?: string, requestHeaders?: Record<string, string | string[] | undefined>, routePattern?: string, routeFile?: string, routeId?: string }} input
32
+ * @returns {Promise<Record<string, unknown> | null>}
33
+ */
34
+ export function executeServerScript(input: {
35
+ source: string;
36
+ sourcePath: string;
37
+ params: Record<string, string>;
38
+ requestUrl?: string;
39
+ requestMethod?: string;
40
+ requestHeaders?: Record<string, string | string[] | undefined>;
41
+ routePattern?: string;
42
+ routeFile?: string;
43
+ routeId?: string;
44
+ }): Promise<Record<string, unknown> | null>;
45
+ /**
46
+ * @param {string} sourcePath
47
+ * @returns {string}
48
+ */
49
+ export function routeIdFromSourcePath(sourcePath: string): string;
@@ -0,0 +1,220 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { clientFacingRouteMessage, defaultRouteDenyMessage } from '../server-error.js';
5
+ import { SERVER_SCRIPT_RUNNER } from './server-script-runner-template.js';
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ /**
8
+ * @param {{ source: string, sourcePath: string, params: Record<string, string>, requestUrl?: string, requestMethod?: string, requestHeaders?: Record<string, string | string[] | undefined>, requestBodyBuffer?: Buffer | null, routePattern?: string, routeFile?: string, routeId?: string, routeKind?: 'page' | 'resource' }} input
9
+ * @returns {Promise<{ result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
10
+ */
11
+ export async function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, requestBodyBuffer, routePattern, routeFile, routeId, routeKind = 'page', guardOnly = false }) {
12
+ if (!source || !String(source).trim()) {
13
+ return {
14
+ result: { kind: 'data', data: {} },
15
+ trace: { guard: 'none', action: 'none', load: 'none' }
16
+ };
17
+ }
18
+ const payload = await spawnNodeServerRunner({
19
+ source,
20
+ sourcePath,
21
+ params,
22
+ requestUrl: requestUrl || 'http://localhost/',
23
+ requestMethod: requestMethod || 'GET',
24
+ requestHeaders: sanitizeRequestHeaders(requestHeaders || {}),
25
+ requestBodyBuffer: Buffer.isBuffer(requestBodyBuffer) ? requestBodyBuffer : null,
26
+ routePattern: routePattern || '',
27
+ routeFile: routeFile || sourcePath || '',
28
+ routeId: routeId || routeIdFromSourcePath(sourcePath || ''),
29
+ routeKind,
30
+ guardOnly
31
+ });
32
+ if (payload === null || payload === undefined) {
33
+ return {
34
+ result: { kind: 'data', data: {} },
35
+ trace: { guard: 'none', action: 'none', load: 'none' }
36
+ };
37
+ }
38
+ if (typeof payload !== 'object' || Array.isArray(payload)) {
39
+ throw new Error('[zenith-preview] server script payload must be an object');
40
+ }
41
+ const errorEnvelope = payload.__zenith_error;
42
+ if (errorEnvelope && typeof errorEnvelope === 'object') {
43
+ return {
44
+ result: {
45
+ kind: 'deny',
46
+ status: 500,
47
+ message: defaultRouteDenyMessage(500)
48
+ },
49
+ trace: { guard: 'none', action: 'none', load: 'deny' }
50
+ };
51
+ }
52
+ const result = payload.result;
53
+ const trace = payload.trace;
54
+ if (result && typeof result === 'object' && !Array.isArray(result) && typeof result.kind === 'string') {
55
+ return {
56
+ result,
57
+ trace: trace && typeof trace === 'object'
58
+ ? {
59
+ guard: String(trace.guard || 'none'),
60
+ action: String(trace.action || 'none'),
61
+ load: String(trace.load || 'none')
62
+ }
63
+ : { guard: 'none', action: 'none', load: 'none' },
64
+ status: Number.isInteger(payload.status) ? payload.status : undefined,
65
+ setCookies: Array.isArray(payload.setCookies)
66
+ ? payload.setCookies.filter((value) => typeof value === 'string' && value.length > 0)
67
+ : []
68
+ };
69
+ }
70
+ return {
71
+ result: {
72
+ kind: 'data',
73
+ data: payload
74
+ },
75
+ trace: { guard: 'none', action: 'none', load: 'data' }
76
+ };
77
+ }
78
+ /**
79
+ * @param {{ source: string, sourcePath: string, params: Record<string, string>, requestUrl?: string, requestMethod?: string, requestHeaders?: Record<string, string | string[] | undefined>, routePattern?: string, routeFile?: string, routeId?: string }} input
80
+ * @returns {Promise<Record<string, unknown> | null>}
81
+ */
82
+ export async function executeServerScript(input) {
83
+ const execution = await executeServerRoute(input);
84
+ const result = execution?.result;
85
+ if (!result || typeof result !== 'object') {
86
+ return null;
87
+ }
88
+ if (result.kind === 'data' && result.data && typeof result.data === 'object' && !Array.isArray(result.data)) {
89
+ return result.data;
90
+ }
91
+ if (result.kind === 'redirect') {
92
+ return {
93
+ __zenith_error: {
94
+ status: Number.isInteger(result.status) ? result.status : 302,
95
+ code: 'REDIRECT',
96
+ message: `Redirect to ${String(result.location || '')}`
97
+ }
98
+ };
99
+ }
100
+ if (result.kind === 'deny') {
101
+ const status = Number.isInteger(result.status) ? result.status : 403;
102
+ return {
103
+ __zenith_error: {
104
+ status,
105
+ code: status >= 500 ? 'LOAD_FAILED' : (status === 404 ? 'NOT_FOUND' : 'ACCESS_DENIED'),
106
+ message: clientFacingRouteMessage(status, result.message)
107
+ }
108
+ };
109
+ }
110
+ return {};
111
+ }
112
+ /**
113
+ * @param {{ source: string, sourcePath: string, params: Record<string, string>, requestUrl: string, requestMethod: string, requestHeaders: Record<string, string>, requestBodyBuffer?: Buffer | null, routePattern: string, routeFile: string, routeId: string, routeKind?: 'page' | 'resource' }} input
114
+ * @returns {Promise<unknown>}
115
+ */
116
+ function spawnNodeServerRunner(input) {
117
+ return new Promise((resolvePromise, rejectPromise) => {
118
+ const child = spawn(process.execPath, ['--experimental-vm-modules', '--input-type=module', '-e', SERVER_SCRIPT_RUNNER], {
119
+ env: {
120
+ ...process.env,
121
+ ZENITH_SERVER_SOURCE: input.source,
122
+ ZENITH_SERVER_SOURCE_PATH: input.sourcePath || '',
123
+ ZENITH_SERVER_PARAMS: JSON.stringify(input.params || {}),
124
+ ZENITH_SERVER_REQUEST_URL: input.requestUrl || 'http://localhost/',
125
+ ZENITH_SERVER_REQUEST_METHOD: input.requestMethod || 'GET',
126
+ ZENITH_SERVER_REQUEST_HEADERS: JSON.stringify(input.requestHeaders || {}),
127
+ ZENITH_SERVER_ROUTE_PATTERN: input.routePattern || '',
128
+ ZENITH_SERVER_ROUTE_FILE: input.routeFile || input.sourcePath || '',
129
+ ZENITH_SERVER_ROUTE_ID: input.routeId || '',
130
+ ZENITH_SERVER_ROUTE_KIND: input.routeKind || 'page',
131
+ ZENITH_SERVER_GUARD_ONLY: input.guardOnly ? '1' : '',
132
+ ZENITH_SERVER_CONTRACT_PATH: join(__dirname, '..', 'server-contract.js'),
133
+ ZENITH_SERVER_ROUTE_AUTH_PATH: join(__dirname, '..', 'auth', 'route-auth.js')
134
+ },
135
+ stdio: ['pipe', 'pipe', 'pipe']
136
+ });
137
+ const runnerRequestBody = Buffer.isBuffer(input.requestBodyBuffer) ? input.requestBodyBuffer : null;
138
+ child.stdin.on('error', () => {
139
+ // ignore broken pipes when the runner exits before consuming stdin
140
+ });
141
+ child.stdin.end(runnerRequestBody && runnerRequestBody.length > 0 ? runnerRequestBody : undefined);
142
+ let stdout = '';
143
+ let stderr = '';
144
+ child.stdout.on('data', (chunk) => {
145
+ stdout += String(chunk);
146
+ });
147
+ child.stderr.on('data', (chunk) => {
148
+ stderr += String(chunk);
149
+ });
150
+ child.on('error', (error) => {
151
+ rejectPromise(error);
152
+ });
153
+ child.on('close', (code) => {
154
+ if (code !== 0) {
155
+ rejectPromise(new Error(`[zenith-preview] server script execution failed (${code}): ${stderr.trim() || stdout.trim()}`));
156
+ return;
157
+ }
158
+ const stderrOutput = stderr.trim();
159
+ const internalErrorIndex = stderrOutput.indexOf('[Zenith:Server]');
160
+ if (internalErrorIndex >= 0) {
161
+ console.error(stderrOutput.slice(internalErrorIndex).trim());
162
+ }
163
+ const raw = stdout.trim();
164
+ if (!raw || raw === 'null') {
165
+ resolvePromise(null);
166
+ return;
167
+ }
168
+ try {
169
+ resolvePromise(JSON.parse(raw));
170
+ }
171
+ catch (error) {
172
+ rejectPromise(new Error(`[zenith-preview] invalid server payload JSON: ${error instanceof Error ? error.message : String(error)}`));
173
+ }
174
+ });
175
+ });
176
+ }
177
+ /**
178
+ * @param {Record<string, string | string[] | undefined>} headers
179
+ * @returns {Record<string, string>}
180
+ */
181
+ function sanitizeRequestHeaders(headers) {
182
+ const out = Object.create(null);
183
+ const denyExact = new Set(['proxy-authorization', 'set-cookie']);
184
+ const denyPrefixes = ['x-forwarded-', 'cf-'];
185
+ for (const [rawKey, rawValue] of Object.entries(headers || {})) {
186
+ const key = String(rawKey || '').toLowerCase();
187
+ if (!key)
188
+ continue;
189
+ if (denyExact.has(key))
190
+ continue;
191
+ if (denyPrefixes.some((prefix) => key.startsWith(prefix)))
192
+ continue;
193
+ let value = '';
194
+ if (Array.isArray(rawValue)) {
195
+ value = rawValue.filter((entry) => entry !== undefined).map(String).join(', ');
196
+ }
197
+ else if (rawValue !== undefined) {
198
+ value = String(rawValue);
199
+ }
200
+ out[key] = value;
201
+ }
202
+ return out;
203
+ }
204
+ /**
205
+ * @param {string} sourcePath
206
+ * @returns {string}
207
+ */
208
+ export function routeIdFromSourcePath(sourcePath) {
209
+ const normalized = String(sourcePath || '').replaceAll('\\', '/');
210
+ const marker = '/pages/';
211
+ const markerIndex = normalized.lastIndexOf(marker);
212
+ let routeId = markerIndex >= 0
213
+ ? normalized.slice(markerIndex + marker.length)
214
+ : normalized.split('/').pop() || normalized;
215
+ routeId = routeId.replace(/\.zen$/i, '');
216
+ if (routeId.endsWith('/index')) {
217
+ routeId = routeId.slice(0, -('/index'.length));
218
+ }
219
+ return routeId || 'index';
220
+ }
@@ -0,0 +1 @@
1
+ export const SERVER_SCRIPT_RUNNER: string;
@@ -0,0 +1,425 @@
1
+ export const SERVER_SCRIPT_RUNNER = String.raw `
2
+ import vm from 'node:vm';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { pathToFileURL, fileURLToPath } from 'node:url';
6
+
7
+ const source = process.env.ZENITH_SERVER_SOURCE || '';
8
+ const sourcePath = process.env.ZENITH_SERVER_SOURCE_PATH || '';
9
+ const params = JSON.parse(process.env.ZENITH_SERVER_PARAMS || '{}');
10
+ const requestUrl = process.env.ZENITH_SERVER_REQUEST_URL || 'http://localhost/';
11
+ const requestMethod = String(process.env.ZENITH_SERVER_REQUEST_METHOD || 'GET').toUpperCase();
12
+ const requestHeaders = JSON.parse(process.env.ZENITH_SERVER_REQUEST_HEADERS || '{}');
13
+ const routePattern = process.env.ZENITH_SERVER_ROUTE_PATTERN || '';
14
+ const routeFile = process.env.ZENITH_SERVER_ROUTE_FILE || sourcePath || '';
15
+ const routeId = process.env.ZENITH_SERVER_ROUTE_ID || routePattern || '';
16
+ const routeKind = process.env.ZENITH_SERVER_ROUTE_KIND || 'page';
17
+ const guardOnly = process.env.ZENITH_SERVER_GUARD_ONLY === '1';
18
+
19
+ if (!source.trim()) {
20
+ process.stdout.write('null');
21
+ process.exit(0);
22
+ }
23
+
24
+ let cachedTypeScript = undefined;
25
+ async function loadTypeScript() {
26
+ if (cachedTypeScript !== undefined) {
27
+ return cachedTypeScript;
28
+ }
29
+ try {
30
+ const mod = await import('typescript');
31
+ cachedTypeScript = mod.default || mod;
32
+ } catch {
33
+ cachedTypeScript = null;
34
+ }
35
+ return cachedTypeScript;
36
+ }
37
+
38
+ async function transpileIfNeeded(filename, code) {
39
+ const lower = String(filename || '').toLowerCase();
40
+ const isTs =
41
+ lower.endsWith('.ts') ||
42
+ lower.endsWith('.tsx') ||
43
+ lower.endsWith('.mts') ||
44
+ lower.endsWith('.cts');
45
+ if (!isTs) {
46
+ return code;
47
+ }
48
+ const ts = await loadTypeScript();
49
+ if (!ts || typeof ts.transpileModule !== 'function') {
50
+ throw new Error('[zenith-preview] TypeScript is required to execute server modules that import .ts files');
51
+ }
52
+ const output = ts.transpileModule(code, {
53
+ fileName: filename || 'server-script.ts',
54
+ compilerOptions: {
55
+ target: ts.ScriptTarget.ES2022,
56
+ module: ts.ModuleKind.ESNext,
57
+ moduleResolution: ts.ModuleResolutionKind.NodeNext
58
+ },
59
+ reportDiagnostics: false
60
+ });
61
+ return output.outputText;
62
+ }
63
+
64
+ async function exists(filePath) {
65
+ try {
66
+ await fs.access(filePath);
67
+ return true;
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ async function resolveRelativeSpecifier(specifier, parentIdentifier) {
74
+ let basePath = sourcePath;
75
+ if (parentIdentifier && parentIdentifier.startsWith('file:')) {
76
+ basePath = fileURLToPath(parentIdentifier);
77
+ }
78
+
79
+ const baseDir = basePath ? path.dirname(basePath) : process.cwd();
80
+ const candidateBase = specifier.startsWith('file:')
81
+ ? fileURLToPath(specifier)
82
+ : path.resolve(baseDir, specifier);
83
+
84
+ const candidates = [];
85
+ if (path.extname(candidateBase)) {
86
+ candidates.push(candidateBase);
87
+ } else {
88
+ candidates.push(candidateBase);
89
+ candidates.push(candidateBase + '.ts');
90
+ candidates.push(candidateBase + '.tsx');
91
+ candidates.push(candidateBase + '.mts');
92
+ candidates.push(candidateBase + '.cts');
93
+ candidates.push(candidateBase + '.js');
94
+ candidates.push(candidateBase + '.mjs');
95
+ candidates.push(candidateBase + '.cjs');
96
+ candidates.push(path.join(candidateBase, 'index.ts'));
97
+ candidates.push(path.join(candidateBase, 'index.tsx'));
98
+ candidates.push(path.join(candidateBase, 'index.mts'));
99
+ candidates.push(path.join(candidateBase, 'index.cts'));
100
+ candidates.push(path.join(candidateBase, 'index.js'));
101
+ candidates.push(path.join(candidateBase, 'index.mjs'));
102
+ candidates.push(path.join(candidateBase, 'index.cjs'));
103
+ }
104
+
105
+ for (const candidate of candidates) {
106
+ if (await exists(candidate)) {
107
+ return pathToFileURL(candidate).href;
108
+ }
109
+ }
110
+
111
+ throw new Error(
112
+ '[zenith-preview] Cannot resolve server import "' + specifier + '" from "' + (basePath || '<inline>') + '"'
113
+ );
114
+ }
115
+
116
+ function isRelativeSpecifier(specifier) {
117
+ return (
118
+ specifier.startsWith('./') ||
119
+ specifier.startsWith('../') ||
120
+ specifier.startsWith('/') ||
121
+ specifier.startsWith('file:')
122
+ );
123
+ }
124
+
125
+ const safeRequestHeaders =
126
+ requestHeaders && typeof requestHeaders === 'object'
127
+ ? { ...requestHeaders }
128
+ : {};
129
+ function parseCookies(rawCookieHeader) {
130
+ const out = Object.create(null);
131
+ const raw = String(rawCookieHeader || '');
132
+ if (!raw) return out;
133
+ const pairs = raw.split(';');
134
+ for (let i = 0; i < pairs.length; i++) {
135
+ const part = pairs[i];
136
+ const eq = part.indexOf('=');
137
+ if (eq <= 0) continue;
138
+ const key = part.slice(0, eq).trim();
139
+ if (!key) continue;
140
+ const value = part.slice(eq + 1).trim();
141
+ try {
142
+ out[key] = decodeURIComponent(value);
143
+ } catch {
144
+ out[key] = value;
145
+ }
146
+ }
147
+ return out;
148
+ }
149
+ const cookieHeader = typeof safeRequestHeaders.cookie === 'string'
150
+ ? safeRequestHeaders.cookie
151
+ : '';
152
+ const requestCookies = parseCookies(cookieHeader);
153
+
154
+ function ctxAllow() {
155
+ return { kind: 'allow' };
156
+ }
157
+ function ctxRedirect(location, status = 302) {
158
+ return {
159
+ kind: 'redirect',
160
+ location: String(location || ''),
161
+ status: Number.isInteger(status) ? status : 302
162
+ };
163
+ }
164
+ function ctxDeny(status = 403, message = undefined) {
165
+ return {
166
+ kind: 'deny',
167
+ status: Number.isInteger(status) ? status : 403,
168
+ message: typeof message === 'string' ? message : undefined
169
+ };
170
+ }
171
+ function ctxInvalid(payload, status = 400) {
172
+ return {
173
+ kind: 'invalid',
174
+ data: payload,
175
+ status: Number.isInteger(status) ? status : 400
176
+ };
177
+ }
178
+ function ctxData(payload) {
179
+ return {
180
+ kind: 'data',
181
+ data: payload
182
+ };
183
+ }
184
+ function ctxJson(payload, status = 200) {
185
+ return {
186
+ kind: 'json',
187
+ data: payload,
188
+ status: Number.isInteger(status) ? status : 200
189
+ };
190
+ }
191
+ function ctxText(body, status = 200) {
192
+ return {
193
+ kind: 'text',
194
+ body: typeof body === 'string' ? body : String(body ?? ''),
195
+ status: Number.isInteger(status) ? status : 200
196
+ };
197
+ }
198
+
199
+ async function readStdinBuffer() {
200
+ const chunks = [];
201
+ for await (const chunk of process.stdin) {
202
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
203
+ }
204
+ return Buffer.concat(chunks);
205
+ }
206
+
207
+ const requestInit = {
208
+ method: requestMethod,
209
+ headers: new Headers(safeRequestHeaders)
210
+ };
211
+ const requestBodyBuffer =
212
+ requestMethod !== 'GET' && requestMethod !== 'HEAD'
213
+ ? await readStdinBuffer()
214
+ : Buffer.alloc(0);
215
+ if (requestMethod !== 'GET' && requestMethod !== 'HEAD' && requestBodyBuffer.length > 0) {
216
+ requestInit.body = requestBodyBuffer;
217
+ requestInit.duplex = 'half';
218
+ }
219
+ const requestSnapshot = new Request(requestUrl, requestInit);
220
+ const routeParams = { ...params };
221
+ const routeMeta = {
222
+ id: routeId,
223
+ pattern: routePattern,
224
+ file: routeFile ? path.relative(process.cwd(), routeFile) : ''
225
+ };
226
+ const routeContext = {
227
+ params: routeParams,
228
+ url: new URL(requestUrl),
229
+ headers: { ...safeRequestHeaders },
230
+ cookies: requestCookies,
231
+ request: requestSnapshot,
232
+ method: requestMethod,
233
+ route: routeMeta,
234
+ env: {},
235
+ action: null,
236
+ allow: ctxAllow,
237
+ redirect: ctxRedirect,
238
+ deny: ctxDeny,
239
+ invalid: ctxInvalid,
240
+ data: ctxData,
241
+ json: ctxJson,
242
+ text: ctxText
243
+ };
244
+
245
+ const context = vm.createContext({
246
+ params: routeParams,
247
+ ctx: routeContext,
248
+ fetch: globalThis.fetch,
249
+ Blob: globalThis.Blob,
250
+ File: globalThis.File,
251
+ FormData: globalThis.FormData,
252
+ Headers: globalThis.Headers,
253
+ Request: globalThis.Request,
254
+ Response: globalThis.Response,
255
+ TextEncoder: globalThis.TextEncoder,
256
+ TextDecoder: globalThis.TextDecoder,
257
+ URL,
258
+ URLSearchParams,
259
+ Buffer,
260
+ console,
261
+ process,
262
+ setTimeout,
263
+ clearTimeout,
264
+ setInterval,
265
+ clearInterval
266
+ });
267
+
268
+ const moduleCache = new Map();
269
+ const syntheticModuleCache = new Map();
270
+
271
+ async function createSyntheticModule(specifier) {
272
+ if (syntheticModuleCache.has(specifier)) {
273
+ return syntheticModuleCache.get(specifier);
274
+ }
275
+
276
+ const ns = await import(specifier);
277
+ const exportNames = Object.keys(ns);
278
+ const module = new vm.SyntheticModule(
279
+ exportNames,
280
+ function() {
281
+ for (const key of exportNames) {
282
+ this.setExport(key, ns[key]);
283
+ }
284
+ },
285
+ { context }
286
+ );
287
+ await module.link(() => {
288
+ throw new Error(
289
+ '[zenith-preview] synthetic modules cannot contain nested imports: ' + specifier
290
+ );
291
+ });
292
+ syntheticModuleCache.set(specifier, module);
293
+ return module;
294
+ }
295
+
296
+ async function loadFileModule(moduleUrl) {
297
+ if (moduleCache.has(moduleUrl)) {
298
+ return moduleCache.get(moduleUrl);
299
+ }
300
+ const modulePromise = (async () => {
301
+ const filename = fileURLToPath(moduleUrl);
302
+ let code = await fs.readFile(filename, 'utf8');
303
+ code = await transpileIfNeeded(filename, code);
304
+ const module = new vm.SourceTextModule(code, {
305
+ context,
306
+ identifier: moduleUrl,
307
+ initializeImportMeta(meta) {
308
+ meta.url = moduleUrl;
309
+ }
310
+ });
311
+
312
+ await module.link((specifier, referencingModule) => {
313
+ return linkModule(specifier, referencingModule.identifier);
314
+ });
315
+ return module;
316
+ })();
317
+
318
+ moduleCache.set(moduleUrl, modulePromise);
319
+ try {
320
+ return await modulePromise;
321
+ } catch (error) {
322
+ moduleCache.delete(moduleUrl);
323
+ throw error;
324
+ }
325
+ }
326
+
327
+ async function linkModule(specifier, parentIdentifier) {
328
+ if (!isRelativeSpecifier(specifier)) {
329
+ return createSyntheticModule(specifier);
330
+ }
331
+ const resolvedUrl = await resolveRelativeSpecifier(specifier, parentIdentifier);
332
+ return loadFileModule(resolvedUrl);
333
+ }
334
+
335
+ const allowed = new Set(['data', 'load', 'guard', 'action', 'ssr_data', 'props', 'ssr', 'prerender', 'exportPaths']);
336
+ const prelude = "const params = globalThis.params;\n" +
337
+ "const ctx = globalThis.ctx;\n" +
338
+ "import { download, resolveRouteResult } from 'zenith:server-contract';\n" +
339
+ "import { attachRouteAuth } from 'zenith:route-auth';\n" +
340
+ "ctx.download = download;\n" +
341
+ "globalThis.resolveRouteResult = resolveRouteResult;\n" +
342
+ "globalThis.attachRouteAuth = attachRouteAuth;\n";
343
+ const entryIdentifier = sourcePath
344
+ ? pathToFileURL(sourcePath).href
345
+ : 'zenith:server-script';
346
+ const entryTranspileFilename = sourcePath && sourcePath.toLowerCase().endsWith('.zen')
347
+ ? sourcePath.replace(/\.zen$/i, '.ts')
348
+ : (sourcePath || 'server-script.ts');
349
+
350
+ const entryCode = await transpileIfNeeded(entryTranspileFilename, prelude + source);
351
+ const entryModule = new vm.SourceTextModule(entryCode, {
352
+ context,
353
+ identifier: entryIdentifier,
354
+ initializeImportMeta(meta) {
355
+ meta.url = entryIdentifier;
356
+ }
357
+ });
358
+
359
+ moduleCache.set(entryIdentifier, entryModule);
360
+ await entryModule.link((specifier, referencingModule) => {
361
+ if (specifier === 'zenith:server-contract') {
362
+ const defaultPath = path.join(process.cwd(), 'node_modules', '@zenithbuild', 'cli', 'src', 'server-contract.js');
363
+ const configuredPath = process.env.ZENITH_SERVER_CONTRACT_PATH || '';
364
+ const contractUrl = pathToFileURL(configuredPath || defaultPath).href;
365
+ if (configuredPath) {
366
+ return loadFileModule(contractUrl);
367
+ }
368
+ return loadFileModule(contractUrl).catch(() =>
369
+ loadFileModule(pathToFileURL(defaultPath).href)
370
+ );
371
+ }
372
+ if (specifier === 'zenith:route-auth') {
373
+ const defaultPath = path.join(process.cwd(), 'node_modules', '@zenithbuild', 'cli', 'src', 'auth', 'route-auth.js');
374
+ const configuredPath = process.env.ZENITH_SERVER_ROUTE_AUTH_PATH || '';
375
+ const authUrl = pathToFileURL(configuredPath || defaultPath).href;
376
+ if (configuredPath) {
377
+ return loadFileModule(authUrl);
378
+ }
379
+ return loadFileModule(authUrl).catch(() =>
380
+ loadFileModule(pathToFileURL(defaultPath).href)
381
+ );
382
+ }
383
+ return linkModule(specifier, referencingModule.identifier);
384
+ });
385
+ await entryModule.evaluate();
386
+ context.attachRouteAuth(routeContext, {
387
+ requestUrl: routeContext.url,
388
+ guardOnly,
389
+ redirect: ctxRedirect,
390
+ deny: ctxDeny
391
+ });
392
+
393
+ const namespaceKeys = Object.keys(entryModule.namespace);
394
+ for (const key of namespaceKeys) {
395
+ if (!allowed.has(key)) {
396
+ throw new Error('[zenith-preview] unsupported server export "' + key + '"');
397
+ }
398
+ }
399
+
400
+ const exported = entryModule.namespace;
401
+ try {
402
+ const resolved = await context.resolveRouteResult({
403
+ exports: exported,
404
+ ctx: context.ctx,
405
+ filePath: sourcePath || 'server_script',
406
+ guardOnly: guardOnly,
407
+ routeKind: routeKind
408
+ });
409
+
410
+ process.stdout.write(JSON.stringify(resolved || null));
411
+ } catch (error) {
412
+ const message = error instanceof Error
413
+ ? (typeof error.stack === 'string' && error.stack.length > 0 ? error.stack : error.message)
414
+ : String(error);
415
+ process.stderr.write('[Zenith:Server] preview route execution failed\\n' + message + '\\n');
416
+ process.stdout.write(
417
+ JSON.stringify({
418
+ __zenith_error: {
419
+ status: 500,
420
+ code: 'LOAD_FAILED'
421
+ }
422
+ })
423
+ );
424
+ }
425
+ `;