@zenithbuild/cli 0.7.2 → 0.7.4

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 (54) hide show
  1. package/README.md +14 -11
  2. package/dist/adapters/adapter-netlify.js +1 -0
  3. package/dist/adapters/adapter-node.js +8 -0
  4. package/dist/adapters/adapter-vercel.js +1 -0
  5. package/dist/build/compiler-runtime.d.ts +10 -9
  6. package/dist/build/compiler-runtime.js +51 -1
  7. package/dist/build/compiler-signal-expression.d.ts +1 -0
  8. package/dist/build/compiler-signal-expression.js +155 -0
  9. package/dist/build/expression-rewrites.d.ts +1 -6
  10. package/dist/build/expression-rewrites.js +61 -65
  11. package/dist/build/page-component-loop.d.ts +3 -13
  12. package/dist/build/page-component-loop.js +21 -46
  13. package/dist/build/page-ir-normalization.d.ts +0 -8
  14. package/dist/build/page-ir-normalization.js +13 -234
  15. package/dist/build/page-loop-state.d.ts +6 -9
  16. package/dist/build/page-loop-state.js +9 -8
  17. package/dist/build/page-loop.js +27 -22
  18. package/dist/build/scoped-identifier-rewrite.d.ts +37 -44
  19. package/dist/build/scoped-identifier-rewrite.js +28 -128
  20. package/dist/build/server-script.d.ts +2 -1
  21. package/dist/build/server-script.js +29 -3
  22. package/dist/build.js +5 -3
  23. package/dist/component-instance-ir.js +158 -52
  24. package/dist/dev-build-session.js +20 -6
  25. package/dist/dev-server.js +82 -39
  26. package/dist/framework-components/Image.zen +1 -1
  27. package/dist/images/materialization-plan.d.ts +1 -0
  28. package/dist/images/materialization-plan.js +6 -0
  29. package/dist/images/materialize.d.ts +5 -3
  30. package/dist/images/materialize.js +24 -109
  31. package/dist/images/router-manifest.d.ts +1 -0
  32. package/dist/images/router-manifest.js +49 -0
  33. package/dist/index.js +8 -2
  34. package/dist/manifest.js +3 -2
  35. package/dist/preview.d.ts +4 -3
  36. package/dist/preview.js +87 -53
  37. package/dist/request-body.d.ts +2 -0
  38. package/dist/request-body.js +13 -0
  39. package/dist/request-origin.d.ts +2 -0
  40. package/dist/request-origin.js +45 -0
  41. package/dist/route-check-support.d.ts +1 -0
  42. package/dist/route-check-support.js +4 -0
  43. package/dist/server-contract.d.ts +15 -0
  44. package/dist/server-contract.js +102 -32
  45. package/dist/server-error.d.ts +4 -0
  46. package/dist/server-error.js +34 -0
  47. package/dist/server-output.d.ts +2 -0
  48. package/dist/server-output.js +13 -0
  49. package/dist/server-runtime/node-server.js +33 -27
  50. package/dist/server-runtime/route-render.d.ts +3 -3
  51. package/dist/server-runtime/route-render.js +20 -31
  52. package/dist/server-script-composition.d.ts +11 -5
  53. package/dist/server-script-composition.js +25 -10
  54. package/package.json +6 -2
package/dist/preview.js CHANGED
@@ -14,10 +14,15 @@ import { access, readFile } from 'node:fs/promises';
14
14
  import { extname, join, normalize, resolve, sep, dirname } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
16
  import { appLocalRedirectLocation, imageEndpointPath, normalizeBasePath, routeCheckPath, stripBasePath } from './base-path.js';
17
- import { isConfigKeyExplicit, loadConfig, validateConfig } from './config.js';
17
+ import { resolveBuildAdapter } from './adapters/resolve-adapter.js';
18
+ import { isConfigKeyExplicit, isLoadedConfig, loadConfig, validateConfig } from './config.js';
18
19
  import { materializeImageMarkup } from './images/materialize.js';
19
20
  import { createImageRuntimePayload, injectImageRuntimePayload } from './images/payload.js';
20
21
  import { handleImageRequest } from './images/service.js';
22
+ import { encodeRequestBodyBase64, readRequestBodyBuffer } from './request-body.js';
23
+ import { createTrustedOriginResolver } from './request-origin.js';
24
+ import { supportsTargetRouteCheck } from './route-check-support.js';
25
+ import { clientFacingRouteMessage, defaultRouteDenyMessage, logServerException, sanitizeRouteResult } from './server-error.js';
21
26
  import { createSilentLogger } from './ui/logger.js';
22
27
  import { compareRouteSpecificity, matchRoute as matchManifestRoute, resolveRequestRoute } from './server/resolve-request-route.js';
23
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -34,6 +39,7 @@ const MIME_TYPES = {
34
39
  '.avif': 'image/avif',
35
40
  '.gif': 'image/gif'
36
41
  };
42
+ const IMAGE_RUNTIME_TAG_RE = /<script\b[^>]*\bid=(["'])zenith-image-runtime\1[^>]*>[\s\S]*?<\/script>/i;
37
43
  const SERVER_SCRIPT_RUNNER = String.raw `
38
44
  import vm from 'node:vm';
39
45
  import fs from 'node:fs/promises';
@@ -203,6 +209,13 @@ function ctxDeny(status = 403, message = undefined) {
203
209
  message: typeof message === 'string' ? message : undefined
204
210
  };
205
211
  }
212
+ function ctxInvalid(payload, status = 400) {
213
+ return {
214
+ kind: 'invalid',
215
+ data: payload,
216
+ status: Number.isInteger(status) ? status : 400
217
+ };
218
+ }
206
219
  function ctxData(payload) {
207
220
  return {
208
221
  kind: 'data',
@@ -210,10 +223,16 @@ function ctxData(payload) {
210
223
  };
211
224
  }
212
225
 
213
- const requestSnapshot = new Request(requestUrl, {
226
+ const requestInit = {
214
227
  method: requestMethod,
215
228
  headers: new Headers(safeRequestHeaders)
216
- });
229
+ };
230
+ const requestBodyBase64 = String(process.env.ZENITH_SERVER_REQUEST_BODY_BASE64 || '');
231
+ if (requestMethod !== 'GET' && requestMethod !== 'HEAD' && requestBodyBase64.length > 0) {
232
+ requestInit.body = Buffer.from(requestBodyBase64, 'base64');
233
+ requestInit.duplex = 'half';
234
+ }
235
+ const requestSnapshot = new Request(requestUrl, requestInit);
217
236
  const routeParams = { ...params };
218
237
  const routeMeta = {
219
238
  id: routeId,
@@ -229,6 +248,7 @@ const routeContext = {
229
248
  method: requestMethod,
230
249
  route: routeMeta,
231
250
  env: {},
251
+ action: null,
232
252
  auth: {
233
253
  async getSession(_ctx) {
234
254
  return null;
@@ -240,6 +260,7 @@ const routeContext = {
240
260
  allow: ctxAllow,
241
261
  redirect: ctxRedirect,
242
262
  deny: ctxDeny,
263
+ invalid: ctxInvalid,
243
264
  data: ctxData
244
265
  };
245
266
 
@@ -320,7 +341,7 @@ async function linkModule(specifier, parentIdentifier) {
320
341
  return loadFileModule(resolvedUrl);
321
342
  }
322
343
 
323
- const allowed = new Set(['data', 'load', 'guard', 'ssr_data', 'props', 'ssr', 'prerender']);
344
+ const allowed = new Set(['data', 'load', 'guard', 'action', 'ssr_data', 'props', 'ssr', 'prerender']);
324
345
  const prelude = "const params = globalThis.params;\n" +
325
346
  "const ctx = globalThis.ctx;\n" +
326
347
  "import { resolveRouteResult } from 'zenith:server-contract';\n" +
@@ -372,13 +393,15 @@ try {
372
393
 
373
394
  process.stdout.write(JSON.stringify(resolved || null));
374
395
  } catch (error) {
375
- const message = error instanceof Error ? error.message : String(error);
396
+ const message = error instanceof Error
397
+ ? (typeof error.stack === 'string' && error.stack.length > 0 ? error.stack : error.message)
398
+ : String(error);
399
+ process.stderr.write('[Zenith:Server] preview route execution failed\\n' + message + '\\n');
376
400
  process.stdout.write(
377
401
  JSON.stringify({
378
402
  __zenith_error: {
379
403
  status: 500,
380
- code: 'LOAD_FAILED',
381
- message
404
+ code: 'LOAD_FAILED'
382
405
  }
383
406
  })
384
407
  );
@@ -395,7 +418,9 @@ export async function createPreviewServer(options) {
395
418
  const loadedConfig = await loadConfig(resolvedProjectRoot);
396
419
  const resolvedConfig = options?.config && typeof options.config === 'object'
397
420
  ? (() => {
398
- const overrideConfig = validateConfig(options.config);
421
+ const overrideConfig = isLoadedConfig(options.config)
422
+ ? options.config
423
+ : validateConfig(options.config);
399
424
  const mergedConfig = { ...loadedConfig };
400
425
  for (const key of Object.keys(overrideConfig)) {
401
426
  if (isConfigKeyExplicit(overrideConfig, key)) {
@@ -411,7 +436,13 @@ export async function createPreviewServer(options) {
411
436
  const logger = providedLogger || createSilentLogger();
412
437
  const verboseLogging = logger.mode?.logLevel === 'verbose';
413
438
  const configuredBasePath = normalizeBasePath(config.basePath || '/');
439
+ const routeCheckEnabled = supportsTargetRouteCheck(resolveBuildAdapter(config).target);
414
440
  let actualPort = port;
441
+ const resolveServerOrigin = createTrustedOriginResolver({
442
+ host,
443
+ getPort: () => actualPort,
444
+ label: 'preview server'
445
+ });
415
446
  async function loadImageManifest() {
416
447
  try {
417
448
  const manifestRaw = await readFile(join(distDir, '_zenith', 'image', 'manifest.json'), 'utf8');
@@ -422,24 +453,17 @@ export async function createPreviewServer(options) {
422
453
  return {};
423
454
  }
424
455
  }
425
- function publicHost() {
426
- if (host === '0.0.0.0' || host === '::') {
427
- return '127.0.0.1';
428
- }
429
- return host;
430
- }
431
- function serverOrigin() {
432
- return `http://${publicHost()}:${actualPort}`;
433
- }
434
456
  const server = createServer(async (req, res) => {
435
- const requestBase = typeof req.headers.host === 'string' && req.headers.host.length > 0
436
- ? `http://${req.headers.host}`
437
- : serverOrigin();
438
- const url = new URL(req.url, requestBase);
457
+ const url = new URL(req.url, resolveServerOrigin());
439
458
  const { basePath, routes } = await loadRouteManifestState(distDir, configuredBasePath);
440
459
  const canonicalPath = stripBasePath(url.pathname, basePath);
441
460
  try {
442
461
  if (url.pathname === routeCheckPath(basePath)) {
462
+ if (!routeCheckEnabled) {
463
+ res.writeHead(501, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
464
+ res.end(JSON.stringify({ error: 'route_check_unsupported' }));
465
+ return;
466
+ }
443
467
  // Security: Require explicitly designated header to prevent public oracle probing
444
468
  if (req.headers['x-zenith-route-check'] !== '1') {
445
469
  res.writeHead(403, { 'Content-Type': 'application/json' });
@@ -509,7 +533,7 @@ export async function createPreviewServer(options) {
509
533
  'Vary': 'Cookie'
510
534
  });
511
535
  res.end(JSON.stringify({
512
- result: checkResult?.result || checkResult,
536
+ result: sanitizeRouteResult(checkResult?.result || checkResult),
513
537
  routeId: resolvedCheck.route.route_id || '',
514
538
  to: targetUrl.toString()
515
539
  }));
@@ -557,33 +581,40 @@ export async function createPreviewServer(options) {
557
581
  throw new Error('not found');
558
582
  }
559
583
  let ssrPayload = null;
584
+ let routeExecution = null;
560
585
  if (resolved.matched && resolved.route?.server_script && resolved.route.prerender !== true) {
561
- let routeExecution = null;
562
586
  try {
587
+ const requestMethod = req.method || 'GET';
588
+ const requestBodyBuffer = requestMethod === 'GET' || requestMethod === 'HEAD'
589
+ ? null
590
+ : await readRequestBodyBuffer(req);
563
591
  routeExecution = await executeServerRoute({
564
592
  source: resolved.route.server_script,
565
593
  sourcePath: resolved.route.server_script_path || '',
566
594
  params: resolved.params,
567
595
  requestUrl: url.toString(),
568
- requestMethod: req.method || 'GET',
596
+ requestMethod,
569
597
  requestHeaders: req.headers,
598
+ requestBodyBase64: encodeRequestBodyBase64(requestBodyBuffer),
570
599
  routePattern: resolved.route.path,
571
600
  routeFile: resolved.route.server_script_path || '',
572
601
  routeId: resolved.route.route_id || routeIdFromSourcePath(resolved.route.server_script_path || '')
573
602
  });
574
603
  }
575
604
  catch (error) {
605
+ logServerException('preview server route execution failed', error);
576
606
  ssrPayload = {
577
607
  __zenith_error: {
608
+ status: 500,
578
609
  code: 'LOAD_FAILED',
579
- message: error instanceof Error ? error.message : String(error)
610
+ message: error instanceof Error ? error.message : String(error || '')
580
611
  }
581
612
  };
582
613
  }
583
- const trace = routeExecution?.trace || { guard: 'none', load: 'none' };
614
+ const trace = routeExecution?.trace || { guard: 'none', action: 'none', load: 'none' };
584
615
  const routeId = resolved.route.route_id || routeIdFromSourcePath(resolved.route.server_script_path || '');
585
616
  if (verboseLogging) {
586
- logger.router(`${routeId} guard=${trace.guard} load=${trace.load}`);
617
+ logger.router(`${routeId} guard=${trace.guard} action=${trace.action} load=${trace.load}`);
587
618
  }
588
619
  const result = routeExecution?.result;
589
620
  if (result && result.kind === 'redirect') {
@@ -598,7 +629,7 @@ export async function createPreviewServer(options) {
598
629
  if (result && result.kind === 'deny') {
599
630
  const status = Number.isInteger(result.status) ? result.status : 403;
600
631
  res.writeHead(status, { 'Content-Type': 'text/plain; charset=utf-8' });
601
- res.end(result.message || defaultRouteDenyMessage(status));
632
+ res.end(clientFacingRouteMessage(status, result.message));
602
633
  return;
603
634
  }
604
635
  if (result && result.kind === 'data' && result.data && typeof result.data === 'object' && !Array.isArray(result.data)) {
@@ -606,21 +637,24 @@ export async function createPreviewServer(options) {
606
637
  }
607
638
  }
608
639
  let html = await readFile(htmlPath, 'utf8');
609
- if (resolved.matched && resolved.route?.page_asset) {
610
- const pageAssetPath = resolveWithinDist(distDir, resolved.route.page_asset);
640
+ if (resolved.matched) {
611
641
  html = await materializeImageMarkup({
612
642
  html,
613
- pageAssetPath,
614
643
  payload: createImageRuntimePayload(config.images, await loadImageManifest(), 'endpoint', basePath),
615
- ssrData: ssrPayload,
616
- routePathname: url.pathname
644
+ imageMaterialization: Array.isArray(resolved.route?.image_materialization)
645
+ ? resolved.route.image_materialization
646
+ : []
617
647
  });
618
648
  }
619
649
  if (ssrPayload) {
620
650
  html = injectSsrPayload(html, ssrPayload);
621
651
  }
622
- html = injectImageRuntimePayload(html, createImageRuntimePayload(config.images, await loadImageManifest(), 'endpoint', basePath));
623
- res.writeHead(200, { 'Content-Type': 'text/html' });
652
+ if (!IMAGE_RUNTIME_TAG_RE.test(html)) {
653
+ html = injectImageRuntimePayload(html, createImageRuntimePayload(config.images, await loadImageManifest(), 'endpoint', basePath));
654
+ }
655
+ res.writeHead(Number.isInteger(routeExecution?.status) ? routeExecution.status : 200, {
656
+ 'Content-Type': 'text/html'
657
+ });
624
658
  res.end(html);
625
659
  }
626
660
  catch {
@@ -691,13 +725,13 @@ async function loadRouteManifestState(distDir, fallbackBasePath = '/') {
691
725
  export const matchRoute = matchManifestRoute;
692
726
  /**
693
727
  * @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
694
- * @returns {Promise<{ result: { kind: string, [key: string]: unknown }, trace: { guard: string, load: string } }>}
728
+ * @returns {Promise<{ result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number }>}
695
729
  */
696
- export async function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, routePattern, routeFile, routeId, guardOnly = false }) {
730
+ export async function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, requestBodyBase64, routePattern, routeFile, routeId, guardOnly = false }) {
697
731
  if (!source || !String(source).trim()) {
698
732
  return {
699
733
  result: { kind: 'data', data: {} },
700
- trace: { guard: 'none', load: 'none' }
734
+ trace: { guard: 'none', action: 'none', load: 'none' }
701
735
  };
702
736
  }
703
737
  const payload = await spawnNodeServerRunner({
@@ -707,6 +741,7 @@ export async function executeServerRoute({ source, sourcePath, params, requestUr
707
741
  requestUrl: requestUrl || 'http://localhost/',
708
742
  requestMethod: requestMethod || 'GET',
709
743
  requestHeaders: sanitizeRequestHeaders(requestHeaders || {}),
744
+ requestBodyBase64: requestBodyBase64 || '',
710
745
  routePattern: routePattern || '',
711
746
  routeFile: routeFile || sourcePath || '',
712
747
  routeId: routeId || routeIdFromSourcePath(sourcePath || ''),
@@ -715,7 +750,7 @@ export async function executeServerRoute({ source, sourcePath, params, requestUr
715
750
  if (payload === null || payload === undefined) {
716
751
  return {
717
752
  result: { kind: 'data', data: {} },
718
- trace: { guard: 'none', load: 'none' }
753
+ trace: { guard: 'none', action: 'none', load: 'none' }
719
754
  };
720
755
  }
721
756
  if (typeof payload !== 'object' || Array.isArray(payload)) {
@@ -727,9 +762,9 @@ export async function executeServerRoute({ source, sourcePath, params, requestUr
727
762
  result: {
728
763
  kind: 'deny',
729
764
  status: 500,
730
- message: String(errorEnvelope.message || 'Server route execution failed')
765
+ message: defaultRouteDenyMessage(500)
731
766
  },
732
- trace: { guard: 'none', load: 'deny' }
767
+ trace: { guard: 'none', action: 'none', load: 'deny' }
733
768
  };
734
769
  }
735
770
  const result = payload.result;
@@ -740,9 +775,11 @@ export async function executeServerRoute({ source, sourcePath, params, requestUr
740
775
  trace: trace && typeof trace === 'object'
741
776
  ? {
742
777
  guard: String(trace.guard || 'none'),
778
+ action: String(trace.action || 'none'),
743
779
  load: String(trace.load || 'none')
744
780
  }
745
- : { guard: 'none', load: 'none' }
781
+ : { guard: 'none', action: 'none', load: 'none' },
782
+ status: Number.isInteger(payload.status) ? payload.status : undefined
746
783
  };
747
784
  }
748
785
  return {
@@ -750,18 +787,9 @@ export async function executeServerRoute({ source, sourcePath, params, requestUr
750
787
  kind: 'data',
751
788
  data: payload
752
789
  },
753
- trace: { guard: 'none', load: 'data' }
790
+ trace: { guard: 'none', action: 'none', load: 'data' }
754
791
  };
755
792
  }
756
- export function defaultRouteDenyMessage(status) {
757
- if (status === 401)
758
- return 'Unauthorized';
759
- if (status === 403)
760
- return 'Forbidden';
761
- if (status === 404)
762
- return 'Not Found';
763
- return 'Internal Server Error';
764
- }
765
793
  /**
766
794
  * @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
767
795
  * @returns {Promise<Record<string, unknown> | null>}
@@ -790,7 +818,7 @@ export async function executeServerScript(input) {
790
818
  __zenith_error: {
791
819
  status,
792
820
  code: status >= 500 ? 'LOAD_FAILED' : (status === 404 ? 'NOT_FOUND' : 'ACCESS_DENIED'),
793
- message: String(result.message || defaultRouteDenyMessage(status))
821
+ message: clientFacingRouteMessage(status, result.message)
794
822
  }
795
823
  };
796
824
  }
@@ -811,6 +839,7 @@ function spawnNodeServerRunner(input) {
811
839
  ZENITH_SERVER_REQUEST_URL: input.requestUrl || 'http://localhost/',
812
840
  ZENITH_SERVER_REQUEST_METHOD: input.requestMethod || 'GET',
813
841
  ZENITH_SERVER_REQUEST_HEADERS: JSON.stringify(input.requestHeaders || {}),
842
+ ZENITH_SERVER_REQUEST_BODY_BASE64: input.requestBodyBase64 || '',
814
843
  ZENITH_SERVER_ROUTE_PATTERN: input.routePattern || '',
815
844
  ZENITH_SERVER_ROUTE_FILE: input.routeFile || input.sourcePath || '',
816
845
  ZENITH_SERVER_ROUTE_ID: input.routeId || '',
@@ -835,6 +864,11 @@ function spawnNodeServerRunner(input) {
835
864
  rejectPromise(new Error(`[zenith-preview] server script execution failed (${code}): ${stderr.trim() || stdout.trim()}`));
836
865
  return;
837
866
  }
867
+ const stderrOutput = stderr.trim();
868
+ const internalErrorIndex = stderrOutput.indexOf('[Zenith:Server]');
869
+ if (internalErrorIndex >= 0) {
870
+ console.error(stderrOutput.slice(internalErrorIndex).trim());
871
+ }
838
872
  const raw = stdout.trim();
839
873
  if (!raw || raw === 'null') {
840
874
  resolvePromise(null);
@@ -0,0 +1,2 @@
1
+ export function readRequestBodyBuffer(req: any): Promise<Buffer<ArrayBuffer>>;
2
+ export function encodeRequestBodyBase64(bodyBuffer: any): string;
@@ -0,0 +1,13 @@
1
+ export async function readRequestBodyBuffer(req) {
2
+ const chunks = [];
3
+ for await (const chunk of req) {
4
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
5
+ }
6
+ return Buffer.concat(chunks);
7
+ }
8
+ export function encodeRequestBodyBase64(bodyBuffer) {
9
+ if (!Buffer.isBuffer(bodyBuffer) || bodyBuffer.length === 0) {
10
+ return '';
11
+ }
12
+ return bodyBuffer.toString('base64');
13
+ }
@@ -0,0 +1,2 @@
1
+ export function publicHost(host: any): string;
2
+ export function createTrustedOriginResolver(options?: {}): () => string;
@@ -0,0 +1,45 @@
1
+ const DEFAULT_HOST = '127.0.0.1';
2
+ export function publicHost(host) {
3
+ const normalized = String(host || DEFAULT_HOST).trim() || DEFAULT_HOST;
4
+ if (normalized === '0.0.0.0' || normalized === '::') {
5
+ return DEFAULT_HOST;
6
+ }
7
+ return normalized;
8
+ }
9
+ function normalizePublicOrigin(value, label) {
10
+ const raw = String(value || '').trim();
11
+ if (!raw) {
12
+ throw new Error(`[Zenith:Server] ${label} must be a non-empty absolute origin`);
13
+ }
14
+ let parsed;
15
+ try {
16
+ parsed = new URL(raw);
17
+ }
18
+ catch {
19
+ throw new Error(`[Zenith:Server] ${label} must be an absolute origin`);
20
+ }
21
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
22
+ throw new Error(`[Zenith:Server] ${label} must use http or https`);
23
+ }
24
+ if (parsed.username || parsed.password) {
25
+ throw new Error(`[Zenith:Server] ${label} must not include credentials`);
26
+ }
27
+ if (parsed.pathname !== '/' || parsed.search || parsed.hash) {
28
+ throw new Error(`[Zenith:Server] ${label} must not include a path, query, or hash`);
29
+ }
30
+ return parsed.origin;
31
+ }
32
+ export function createTrustedOriginResolver(options = {}) {
33
+ const { publicOrigin = undefined, host = DEFAULT_HOST, port = undefined, getPort = undefined, label = 'server' } = options;
34
+ if (publicOrigin !== undefined && publicOrigin !== null && String(publicOrigin).trim().length > 0) {
35
+ const origin = normalizePublicOrigin(publicOrigin, `${label} publicOrigin`);
36
+ return () => origin;
37
+ }
38
+ return () => {
39
+ const resolvedPort = typeof getPort === 'function' ? getPort() : port;
40
+ if (!Number.isInteger(resolvedPort) || resolvedPort <= 0) {
41
+ throw new Error(`[Zenith:Server] ${label} requires "publicOrigin" when a trusted port is unavailable; raw Host headers are not trusted`);
42
+ }
43
+ return `http://${publicHost(host)}:${resolvedPort}`;
44
+ };
45
+ }
@@ -0,0 +1 @@
1
+ export function supportsTargetRouteCheck(target: any): boolean;
@@ -0,0 +1,4 @@
1
+ const ROUTE_CHECK_UNSUPPORTED_TARGETS = new Set(['vercel', 'netlify']);
2
+ export function supportsTargetRouteCheck(target) {
3
+ return !ROUTE_CHECK_UNSUPPORTED_TARGETS.has(String(target || '').trim());
4
+ }
@@ -15,6 +15,11 @@ export function data(payload: any): {
15
15
  kind: string;
16
16
  data: any;
17
17
  };
18
+ export function invalid(payload: any, status?: number): {
19
+ kind: string;
20
+ data: any;
21
+ status: number;
22
+ };
18
23
  export function validateServerExports({ exports, filePath }: {
19
24
  exports: any;
20
25
  filePath: any;
@@ -29,8 +34,18 @@ export function resolveRouteResult({ exports, ctx, filePath, guardOnly }: {
29
34
  result: any;
30
35
  trace: {
31
36
  guard: string;
37
+ action: string;
38
+ load: string;
39
+ };
40
+ status?: undefined;
41
+ } | {
42
+ result: any;
43
+ trace: {
44
+ guard: string;
45
+ action: string;
32
46
  load: string;
33
47
  };
48
+ status: number | undefined;
34
49
  }>;
35
50
  export function resolveServerPayload({ exports, ctx, filePath }: {
36
51
  exports: any;