@zenithbuild/cli 0.7.10 → 0.7.12

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 (111) hide show
  1. package/README.md +14 -2
  2. package/dist/adapters/adapter-netlify-static.d.ts +2 -5
  3. package/dist/adapters/adapter-netlify.d.ts +2 -5
  4. package/dist/adapters/adapter-netlify.js +22 -5
  5. package/dist/adapters/adapter-types.d.ts +32 -13
  6. package/dist/adapters/adapter-types.js +0 -59
  7. package/dist/adapters/adapter-vercel-static.d.ts +2 -5
  8. package/dist/adapters/adapter-vercel.d.ts +2 -5
  9. package/dist/adapters/adapter-vercel.js +21 -6
  10. package/dist/adapters/copy-hosted-page-runtime.d.ts +2 -1
  11. package/dist/adapters/copy-hosted-page-runtime.js +68 -3
  12. package/dist/adapters/resolve-adapter.d.ts +6 -4
  13. package/dist/build/compiler-runtime.js +3 -0
  14. package/dist/build/expression-rewrites.d.ts +3 -1
  15. package/dist/build/expression-rewrites.js +14 -2
  16. package/dist/build/page-component-loop.d.ts +1 -0
  17. package/dist/build/page-component-loop.js +66 -6
  18. package/dist/build/page-ir-normalization.js +7 -0
  19. package/dist/build/page-loop-state.d.ts +2 -4
  20. package/dist/build/page-loop-state.js +17 -9
  21. package/dist/build/page-loop.js +18 -8
  22. package/dist/build/scoped-expression-context.d.ts +5 -0
  23. package/dist/build/scoped-expression-context.js +133 -0
  24. package/dist/build/server-script.js +13 -36
  25. package/dist/build/type-declarations.d.ts +2 -1
  26. package/dist/build/type-declarations.js +29 -52
  27. package/dist/build-output-manifest.d.ts +10 -6
  28. package/dist/build-output-manifest.js +4 -1
  29. package/dist/build.js +11 -2
  30. package/dist/component-instance-ir.js +1 -0
  31. package/dist/component-occurrences.d.ts +9 -0
  32. package/dist/component-occurrences.js +18 -0
  33. package/dist/config-plugins.d.ts +12 -0
  34. package/dist/config-plugins.js +100 -0
  35. package/dist/config.d.ts +1 -0
  36. package/dist/config.js +56 -5
  37. package/dist/dev-build-session/helpers.js +27 -7
  38. package/dist/dev-build-session/session.js +19 -10
  39. package/dist/dev-server/build-error-response.d.ts +21 -0
  40. package/dist/dev-server/build-error-response.js +48 -0
  41. package/dist/dev-server/port-fallback.d.ts +15 -0
  42. package/dist/dev-server/port-fallback.js +61 -0
  43. package/dist/dev-server/request-handler.js +58 -5
  44. package/dist/dev-server/watcher.js +15 -0
  45. package/dist/dev-server.d.ts +5 -2
  46. package/dist/dev-server.js +129 -49
  47. package/dist/global-middleware-runtime-source.d.ts +15 -0
  48. package/dist/global-middleware-runtime-source.js +62 -0
  49. package/dist/global-middleware.d.ts +13 -0
  50. package/dist/global-middleware.js +252 -0
  51. package/dist/images/remote-fetch.d.ts +12 -0
  52. package/dist/images/remote-fetch.js +257 -0
  53. package/dist/images/service.d.ts +10 -0
  54. package/dist/images/service.js +9 -46
  55. package/dist/index.js +12 -2
  56. package/dist/manifest.d.ts +9 -1
  57. package/dist/manifest.js +70 -25
  58. package/dist/preview/request-handler.js +78 -5
  59. package/dist/preview/server-runner.d.ts +7 -2
  60. package/dist/preview/server-runner.js +19 -6
  61. package/dist/preview/server-script-runner-template.js +97 -29
  62. package/dist/resource-response.js +25 -8
  63. package/dist/resource-route-module.js +5 -22
  64. package/dist/route-classification.d.ts +11 -0
  65. package/dist/route-classification.js +21 -0
  66. package/dist/route-handler-export-analysis.d.ts +22 -0
  67. package/dist/route-handler-export-analysis.js +41 -0
  68. package/dist/scoped-server-data/analyze-owner-file.d.ts +3 -0
  69. package/dist/scoped-server-data/analyze-owner-file.js +149 -0
  70. package/dist/scoped-server-data/diagnostics.d.ts +18 -0
  71. package/dist/scoped-server-data/diagnostics.js +32 -0
  72. package/dist/scoped-server-data/lowering.d.ts +27 -0
  73. package/dist/scoped-server-data/lowering.js +242 -0
  74. package/dist/scoped-server-data/manifest-integration.d.ts +4 -0
  75. package/dist/scoped-server-data/manifest-integration.js +125 -0
  76. package/dist/scoped-server-data/owner-scanner.d.ts +6 -0
  77. package/dist/scoped-server-data/owner-scanner.js +55 -0
  78. package/dist/scoped-server-data/parse-owner-server-block.d.ts +12 -0
  79. package/dist/scoped-server-data/parse-owner-server-block.js +35 -0
  80. package/dist/scoped-server-data/runtime.d.ts +24 -0
  81. package/dist/scoped-server-data/runtime.js +121 -0
  82. package/dist/scoped-server-data/serialization-set.d.ts +2 -0
  83. package/dist/scoped-server-data/serialization-set.js +52 -0
  84. package/dist/scoped-server-data/static-props.d.ts +12 -0
  85. package/dist/scoped-server-data/static-props.js +307 -0
  86. package/dist/scoped-server-data/type-declarations.d.ts +10 -0
  87. package/dist/scoped-server-data/type-declarations.js +368 -0
  88. package/dist/scoped-server-data/types.d.ts +74 -0
  89. package/dist/scoped-server-data/types.js +1 -0
  90. package/dist/server-contract/auth-control-flow.d.ts +1 -0
  91. package/dist/server-contract/auth-control-flow.js +10 -0
  92. package/dist/server-contract/resolve.d.ts +19 -0
  93. package/dist/server-contract/resolve.js +85 -13
  94. package/dist/server-contract/resolved-envelope.d.ts +9 -0
  95. package/dist/server-contract/resolved-envelope.js +14 -0
  96. package/dist/server-contract/stage.js +1 -10
  97. package/dist/server-module-output.d.ts +9 -0
  98. package/dist/server-module-output.js +250 -0
  99. package/dist/server-output.d.ts +7 -1
  100. package/dist/server-output.js +144 -195
  101. package/dist/server-route-names.d.ts +2 -0
  102. package/dist/server-route-names.js +38 -0
  103. package/dist/server-runtime/matched-route-pipeline.d.ts +1 -0
  104. package/dist/server-runtime/matched-route-pipeline.js +1 -0
  105. package/dist/server-runtime/node-server.js +26 -3
  106. package/dist/server-runtime/route-render.d.ts +12 -3
  107. package/dist/server-runtime/route-render.js +67 -13
  108. package/dist/types/generate-env-dts.js +2 -44
  109. package/dist/types/zenith-env-dts.d.ts +4 -0
  110. package/dist/types/zenith-env-dts.js +96 -0
  111. package/package.json +3 -6
package/dist/manifest.js CHANGED
@@ -1,26 +1,41 @@
1
- // ---------------------------------------------------------------------------
2
- // manifest.js Zenith CLI V0
3
- // ---------------------------------------------------------------------------
4
- // File-based manifest engine.
5
- //
6
- // Scans a /pages directory and produces a deterministic RouteManifest.
7
- //
8
- // Rules:
9
- // - index.zen → parent directory path
10
- // - [param].zen → :param dynamic segment
11
- // - [...slug].zen → *slug catch-all segment (must be terminal, 1+ segments;
12
- // root '/*slug' may match '/' in router matcher)
13
- // - [[...slug]].zen → *slug? optional catch-all segment (must be terminal, 0+ segments)
14
- // - Deterministic precedence: static > :param > *catchall
15
- // - Tie-breaker: lexicographic route path
16
- // ---------------------------------------------------------------------------
17
- import { readFileSync } from 'node:fs';
1
+ // File-based manifest engine. Scans /pages and produces deterministic RouteManifest entries.
2
+ // Rules: static > :param > *catchall, then lexicographic tie-breaker.
3
+ import { readFileSync, existsSync } from 'node:fs';
18
4
  import { readdir, stat } from 'node:fs/promises';
19
5
  import { join, relative, sep, basename, extname, dirname, resolve } from 'node:path';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
20
7
  import { extractServerScript } from './build/server-script.js';
21
8
  import { analyzeResourceRouteModule, isResourceRouteFile } from './resource-route-module.js';
22
9
  import { composeServerScriptEnvelope, resolveAdjacentServerModules } from './server-script-composition.js';
23
10
  import { validateStaticExportPaths } from './static-export-paths.js';
11
+ import { classifyPageRoute } from './route-classification.js';
12
+ import { buildComponentRegistry } from './resolve-components.js';
13
+ const SCOPED_SERVER_DATA_HELPER_UNAVAILABLE = '[Zenith:ScopedServerData] Manifest integration helper is unavailable. Run the CLI build step before using scoped server data manifest integration.';
14
+ function resolveManifestIntegrationPath() {
15
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
16
+ return [
17
+ join(moduleDir, 'scoped-server-data', 'manifest-integration.js'),
18
+ join(moduleDir, '..', 'dist', 'scoped-server-data', 'manifest-integration.js')
19
+ ].find((candidate) => existsSync(candidate)) || null;
20
+ }
21
+ const manifestIntegrationPath = resolveManifestIntegrationPath();
22
+ const manifestIntegration = manifestIntegrationPath
23
+ ? await import(pathToFileURL(manifestIntegrationPath).href)
24
+ : null;
25
+ function getManifestIntegration() {
26
+ if (!manifestIntegration) {
27
+ throw new Error(SCOPED_SERVER_DATA_HELPER_UNAVAILABLE);
28
+ }
29
+ return manifestIntegration;
30
+ }
31
+ /** @type {typeof import('./scoped-server-data/manifest-integration.js').analyzeRouteScopedServerMetadata} */
32
+ export function analyzeRouteScopedServerMetadata(options) {
33
+ return getManifestIntegration().analyzeRouteScopedServerMetadata(options);
34
+ }
35
+ /** @type {typeof import('./scoped-server-data/manifest-integration.js').assertNoScopedServerBuildErrors} */
36
+ export function assertNoScopedServerBuildErrors(diagnostics, contextFile) {
37
+ return getManifestIntegration().assertNoScopedServerBuildErrors(diagnostics, contextFile);
38
+ }
24
39
  /**
25
40
  * @typedef {{
26
41
  * path: string,
@@ -34,6 +49,8 @@ import { validateStaticExportPaths } from './static-export-paths.js';
34
49
  * has_guard?: boolean,
35
50
  * has_load?: boolean,
36
51
  * has_action?: boolean,
52
+ * has_scoped_server_data?: boolean,
53
+ * scoped_server_data?: import('./scoped-server-data/types.js').ManifestScopedServerDataEntry[],
37
54
  * export_paths?: string[]
38
55
  * }} ManifestEntry
39
56
  */
@@ -42,11 +59,15 @@ import { validateStaticExportPaths } from './static-export-paths.js';
42
59
  *
43
60
  * @param {string} pagesDir - Absolute path to /pages directory
44
61
  * @param {string} [extension='.zen'] - File extension to scan for
45
- * @param {{ compilerOpts?: object }} [options]
62
+ * @param {{ compilerOpts?: object, srcDir?: string, registry?: Map<string, string> }} [options]
46
63
  * @returns {Promise<ManifestEntry[]>}
47
64
  */
48
65
  export async function generateManifest(pagesDir, extension = '.zen', options = {}) {
49
- const entries = await _scanDir(pagesDir, pagesDir, extension, options.compilerOpts || {});
66
+ const resolvedPagesDir = resolve(pagesDir);
67
+ const srcDir = resolve(options.srcDir || resolve(resolvedPagesDir, '..'));
68
+ const registry = options.registry || buildComponentRegistry(srcDir);
69
+ const scanContext = { srcDir, registry, compilerOpts: options.compilerOpts || {} };
70
+ const entries = await _scanDir(resolvedPagesDir, resolvedPagesDir, extension, scanContext);
50
71
  const apiAliasState = _resolveSrcApiAliasState(pagesDir);
51
72
  if (apiAliasState) {
52
73
  const aliasEntries = await _scanResourceDir(apiAliasState.aliasDir, apiAliasState.srcDir);
@@ -68,7 +89,7 @@ export async function generateManifest(pagesDir, extension = '.zen', options = {
68
89
  * @param {string} ext - Extension to match
69
90
  * @returns {Promise<ManifestEntry[]>}
70
91
  */
71
- async function _scanDir(dir, root, ext, compilerOpts) {
92
+ async function _scanDir(dir, root, ext, scanContext) {
72
93
  /** @type {ManifestEntry[]} */
73
94
  const entries = [];
74
95
  let items;
@@ -84,7 +105,7 @@ async function _scanDir(dir, root, ext, compilerOpts) {
84
105
  const fullPath = join(dir, item);
85
106
  const info = await stat(fullPath);
86
107
  if (info.isDirectory()) {
87
- const nested = await _scanDir(fullPath, root, ext, compilerOpts);
108
+ const nested = await _scanDir(fullPath, root, ext, scanContext);
88
109
  entries.push(...nested);
89
110
  }
90
111
  else if (item.endsWith(ext)) {
@@ -93,7 +114,7 @@ async function _scanDir(dir, root, ext, compilerOpts) {
93
114
  fullPath,
94
115
  root,
95
116
  routePath,
96
- compilerOpts
117
+ scanContext
97
118
  }));
98
119
  }
99
120
  else if (isResourceRouteFile(item)) {
@@ -142,7 +163,8 @@ function _resolveSrcApiAliasState(pagesDir) {
142
163
  srcDir
143
164
  };
144
165
  }
145
- function buildPageManifestEntry({ fullPath, root, routePath, compilerOpts }) {
166
+ function buildPageManifestEntry({ fullPath, root, routePath, scanContext }) {
167
+ const { srcDir, registry, compilerOpts } = scanContext;
146
168
  const rawSource = readFileSync(fullPath, 'utf8');
147
169
  const inlineServerScript = extractServerScript(rawSource, fullPath, compilerOpts).serverScript;
148
170
  const { guardPath, loadPath, actionPath } = resolveAdjacentServerModules(fullPath);
@@ -153,16 +175,39 @@ function buildPageManifestEntry({ fullPath, root, routePath, compilerOpts }) {
153
175
  adjacentLoadPath: loadPath,
154
176
  adjacentActionPath: actionPath
155
177
  });
178
+ const scopedMetadata = analyzeRouteScopedServerMetadata({
179
+ pageSource: rawSource,
180
+ pageFile: fullPath,
181
+ registry,
182
+ srcDir,
183
+ compilerOpts
184
+ });
185
+ const manifestFile = relative(root, fullPath);
186
+ assertNoScopedServerBuildErrors(scopedMetadata.diagnostics, manifestFile);
156
187
  const exportPaths = Array.isArray(composed.serverScript?.export_paths)
157
188
  ? validateStaticExportPaths(routePath, composed.serverScript.export_paths, fullPath)
158
189
  : [];
190
+ const classification = classifyPageRoute({
191
+ file: manifestFile,
192
+ serverScript: composed.serverScript,
193
+ hasScopedServerData: scopedMetadata.hasScopedServerData
194
+ });
159
195
  return {
160
196
  path: routePath,
161
- file: relative(root, fullPath),
197
+ file: manifestFile,
162
198
  route_kind: 'page',
163
199
  path_kind: _isDynamic(routePath) ? 'dynamic' : 'static',
164
- render_mode: composed.serverScript && composed.serverScript.prerender !== true ? 'server' : 'prerender',
200
+ render_mode: classification.renderMode,
165
201
  params: extractRouteParams(routePath),
202
+ has_guard: classification.hasGuard,
203
+ has_load: classification.hasLoad,
204
+ has_action: classification.hasAction,
205
+ ...(scopedMetadata.hasScopedServerData
206
+ ? {
207
+ has_scoped_server_data: true,
208
+ scoped_server_data: scopedMetadata.scopedServerData
209
+ }
210
+ : {}),
166
211
  ...(exportPaths.length > 0 ? { export_paths: exportPaths } : {})
167
212
  };
168
213
  }
@@ -1,5 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { extname, join } from 'node:path';
2
+ import { dirname, extname, join } from 'node:path';
3
3
  import { appLocalRedirectLocation, imageEndpointPath, routeCheckPath, stripBasePath } from '../base-path.js';
4
4
  import { materializeImageMarkup } from '../images/materialize.js';
5
5
  import { createImageRuntimePayload, injectImageRuntimePayload } from '../images/payload.js';
@@ -8,6 +8,7 @@ import { readRequestBodyBuffer } from '../request-body.js';
8
8
  import { buildResourceResponseDescriptor } from '../resource-response.js';
9
9
  import { clientFacingRouteMessage, logServerException, sanitizeRouteResult } from '../server-error.js';
10
10
  import { resolveRequestRoute } from '../server/resolve-request-route.js';
11
+ import { loadPreviewGlobalMiddlewareSource } from '../global-middleware-runtime-source.js';
11
12
  import { loadRouteSurfaceState } from './manifest.js';
12
13
  import { injectSsrPayload } from './payload.js';
13
14
  import { fileExists, resolveWithinDist, toStaticFilePath } from './paths.js';
@@ -32,6 +33,47 @@ function appendSetCookieHeaders(headers, setCookies = []) {
32
33
  }
33
34
  return headers;
34
35
  }
36
+ function respondWithMiddlewareSourceError(res, error) {
37
+ logServerException('preview server route execution failed', error);
38
+ res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
39
+ res.end(clientFacingRouteMessage(500));
40
+ }
41
+ function hasRouteScopedServerData(route) {
42
+ return route?.has_scoped_server_data === true &&
43
+ Array.isArray(route?.scoped_server_data) &&
44
+ route.scoped_server_data.length > 0;
45
+ }
46
+ async function loadPreviewScopedServerRoutes(serverModuleBaseDir) {
47
+ try {
48
+ const parsed = JSON.parse(await readFile(join(serverModuleBaseDir, 'manifest.json'), 'utf8'));
49
+ return Array.isArray(parsed?.routes) ? parsed.routes : [];
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ }
55
+ function mergePreviewScopedServerRoutes(routeState, serverRoutes) {
56
+ const scopedByPath = new Map((Array.isArray(serverRoutes) ? serverRoutes : [])
57
+ .filter((route) => hasRouteScopedServerData(route))
58
+ .map((route) => [route.path, route]));
59
+ if (scopedByPath.size === 0) {
60
+ return routeState;
61
+ }
62
+ return {
63
+ ...routeState,
64
+ pageRoutes: (Array.isArray(routeState.pageRoutes) ? routeState.pageRoutes : []).map((route) => {
65
+ const scoped = scopedByPath.get(route.path);
66
+ if (!scoped) {
67
+ return route;
68
+ }
69
+ return {
70
+ ...route,
71
+ has_scoped_server_data: true,
72
+ scoped_server_data: scoped.scoped_server_data
73
+ };
74
+ })
75
+ };
76
+ }
35
77
  export function createPreviewRequestHandler(options) {
36
78
  const { distDir, projectRoot, config, logger, verboseLogging, configuredBasePath, routeCheckEnabled, isStaticExportTarget, serverOrigin } = options;
37
79
  async function loadImageManifest() {
@@ -44,9 +86,14 @@ export function createPreviewRequestHandler(options) {
44
86
  return {};
45
87
  }
46
88
  }
89
+ async function loadGlobalMiddlewareForRoute() {
90
+ return loadPreviewGlobalMiddlewareSource({ projectRoot, distDir });
91
+ }
47
92
  return async function previewRequestHandler(req, res) {
48
93
  const url = new URL(req.url, serverOrigin());
49
- const { basePath, pageRoutes, resourceRoutes } = await loadRouteSurfaceState(distDir, configuredBasePath);
94
+ const serverModuleBaseDir = join(dirname(distDir), 'server');
95
+ const routeState = mergePreviewScopedServerRoutes(await loadRouteSurfaceState(distDir, configuredBasePath), await loadPreviewScopedServerRoutes(serverModuleBaseDir));
96
+ const { basePath, pageRoutes, resourceRoutes } = routeState;
50
97
  const canonicalPath = stripBasePath(url.pathname, basePath);
51
98
  try {
52
99
  if (url.pathname === routeCheckPath(basePath)) {
@@ -171,6 +218,14 @@ export function createPreviewRequestHandler(options) {
171
218
  canonicalUrl.pathname = canonicalPath;
172
219
  const resolvedResource = resolveRequestRoute(canonicalUrl, resourceRoutes);
173
220
  if (resolvedResource.matched && resolvedResource.route) {
221
+ let globalMiddleware = null;
222
+ try {
223
+ globalMiddleware = await loadGlobalMiddlewareForRoute();
224
+ }
225
+ catch (error) {
226
+ respondWithMiddlewareSourceError(res, error);
227
+ return;
228
+ }
174
229
  const requestMethod = req.method || 'GET';
175
230
  const requestBodyBuffer = requestMethod === 'GET' || requestMethod === 'HEAD'
176
231
  ? null
@@ -186,7 +241,9 @@ export function createPreviewRequestHandler(options) {
186
241
  routePattern: resolvedResource.route.path,
187
242
  routeFile: resolvedResource.route.server_script_path || '',
188
243
  routeId: resolvedResource.route.route_id || routeIdFromSourcePath(resolvedResource.route.server_script_path || ''),
189
- routeKind: 'resource'
244
+ routeKind: 'resource',
245
+ globalMiddlewareSource: globalMiddleware?.source || '',
246
+ globalMiddlewareSourcePath: globalMiddleware?.sourcePath || ''
190
247
  });
191
248
  const descriptor = buildResourceResponseDescriptor(execution?.result, basePath, Array.isArray(execution?.setCookies) ? execution.setCookies : []);
192
249
  res.writeHead(descriptor.status, appendSetCookieHeaders(descriptor.headers, descriptor.setCookies));
@@ -216,7 +273,17 @@ export function createPreviewRequestHandler(options) {
216
273
  }
217
274
  let ssrPayload = null;
218
275
  let routeExecution = null;
219
- if (resolved.matched && resolved.route?.server_script && resolved.route.prerender !== true) {
276
+ if (resolved.matched &&
277
+ resolved.route?.prerender !== true &&
278
+ (resolved.route?.server_script || hasRouteScopedServerData(resolved.route))) {
279
+ let globalMiddleware = null;
280
+ try {
281
+ globalMiddleware = await loadGlobalMiddlewareForRoute();
282
+ }
283
+ catch (error) {
284
+ respondWithMiddlewareSourceError(res, error);
285
+ return;
286
+ }
220
287
  try {
221
288
  const requestMethod = req.method || 'GET';
222
289
  const requestBodyBuffer = requestMethod === 'GET' || requestMethod === 'HEAD'
@@ -232,7 +299,13 @@ export function createPreviewRequestHandler(options) {
232
299
  requestBodyBuffer,
233
300
  routePattern: resolved.route.path,
234
301
  routeFile: resolved.route.server_script_path || '',
235
- routeId: resolved.route.route_id || routeIdFromSourcePath(resolved.route.server_script_path || '')
302
+ routeId: resolved.route.route_id || routeIdFromSourcePath(resolved.route.server_script_path || ''),
303
+ globalMiddlewareSource: globalMiddleware?.source || '',
304
+ globalMiddlewareSourcePath: globalMiddleware?.sourcePath || '',
305
+ scopedServerData: Array.isArray(resolved.route.scoped_server_data)
306
+ ? resolved.route.scoped_server_data
307
+ : [],
308
+ scopedServerModuleBaseDir: serverModuleBaseDir
236
309
  });
237
310
  }
238
311
  catch (error) {
@@ -1,8 +1,8 @@
1
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
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', globalMiddlewareSource?: string, globalMiddlewareSourcePath?: string, scopedServerData?: unknown[], scopedServerModuleBaseDir?: string, scopedServerModuleSources?: unknown[] }} input
3
3
  * @returns {Promise<{ result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
4
4
  */
5
- export function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, requestBodyBuffer, routePattern, routeFile, routeId, routeKind, guardOnly }: {
5
+ export function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, requestBodyBuffer, routePattern, routeFile, routeId, routeKind, guardOnly, globalMiddlewareSource, globalMiddlewareSourcePath, scopedServerData, scopedServerModuleBaseDir, scopedServerModuleSources }: {
6
6
  source: string;
7
7
  sourcePath: string;
8
8
  params: Record<string, string>;
@@ -14,6 +14,11 @@ export function executeServerRoute({ source, sourcePath, params, requestUrl, req
14
14
  routeFile?: string;
15
15
  routeId?: string;
16
16
  routeKind?: "page" | "resource";
17
+ globalMiddlewareSource?: string;
18
+ globalMiddlewareSourcePath?: string;
19
+ scopedServerData?: unknown[];
20
+ scopedServerModuleBaseDir?: string;
21
+ scopedServerModuleSources?: unknown[];
17
22
  }): Promise<{
18
23
  result: {
19
24
  kind: string;
@@ -5,11 +5,12 @@ import { clientFacingRouteMessage, defaultRouteDenyMessage } from '../server-err
5
5
  import { SERVER_SCRIPT_RUNNER } from './server-script-runner-template.js';
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
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
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', globalMiddlewareSource?: string, globalMiddlewareSourcePath?: string, scopedServerData?: unknown[], scopedServerModuleBaseDir?: string, scopedServerModuleSources?: unknown[] }} input
9
9
  * @returns {Promise<{ result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
10
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()) {
11
+ export async function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, requestBodyBuffer, routePattern, routeFile, routeId, routeKind = 'page', guardOnly = false, globalMiddlewareSource = '', globalMiddlewareSourcePath = '', scopedServerData = [], scopedServerModuleBaseDir = '', scopedServerModuleSources = [] }) {
12
+ const hasScopedServerData = Array.isArray(scopedServerData) && scopedServerData.length > 0;
13
+ if ((!source || !String(source).trim()) && !hasScopedServerData) {
13
14
  return {
14
15
  result: { kind: 'data', data: {} },
15
16
  trace: { guard: 'none', action: 'none', load: 'none' }
@@ -27,7 +28,12 @@ export async function executeServerRoute({ source, sourcePath, params, requestUr
27
28
  routeFile: routeFile || sourcePath || '',
28
29
  routeId: routeId || routeIdFromSourcePath(sourcePath || ''),
29
30
  routeKind,
30
- guardOnly
31
+ guardOnly,
32
+ globalMiddlewareSource,
33
+ globalMiddlewareSourcePath,
34
+ scopedServerData,
35
+ scopedServerModuleBaseDir,
36
+ scopedServerModuleSources
31
37
  });
32
38
  if (payload === null || payload === undefined) {
33
39
  return {
@@ -110,7 +116,7 @@ export async function executeServerScript(input) {
110
116
  return {};
111
117
  }
112
118
  /**
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
119
+ * @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', globalMiddlewareSource?: string, globalMiddlewareSourcePath?: string, scopedServerData?: unknown[], scopedServerModuleBaseDir?: string, scopedServerModuleSources?: unknown[] }} input
114
120
  * @returns {Promise<unknown>}
115
121
  */
116
122
  function spawnNodeServerRunner(input) {
@@ -130,7 +136,14 @@ function spawnNodeServerRunner(input) {
130
136
  ZENITH_SERVER_ROUTE_KIND: input.routeKind || 'page',
131
137
  ZENITH_SERVER_GUARD_ONLY: input.guardOnly ? '1' : '',
132
138
  ZENITH_SERVER_CONTRACT_PATH: join(__dirname, '..', 'server-contract.js'),
133
- ZENITH_SERVER_ROUTE_AUTH_PATH: join(__dirname, '..', 'auth', 'route-auth.js')
139
+ ZENITH_SERVER_ROUTE_AUTH_PATH: join(__dirname, '..', 'auth', 'route-auth.js'),
140
+ ZENITH_SERVER_MATCHED_ROUTE_PIPELINE_PATH: join(__dirname, '..', 'server-runtime', 'matched-route-pipeline.js'),
141
+ ZENITH_GLOBAL_MIDDLEWARE_SOURCE: input.globalMiddlewareSource || '',
142
+ ZENITH_GLOBAL_MIDDLEWARE_SOURCE_PATH: input.globalMiddlewareSourcePath || '',
143
+ ZENITH_SCOPED_SERVER_DATA: JSON.stringify(Array.isArray(input.scopedServerData) ? input.scopedServerData : []),
144
+ ZENITH_SCOPED_SERVER_MODULE_BASE_DIR: input.scopedServerModuleBaseDir || '',
145
+ ZENITH_SCOPED_SERVER_MODULE_SOURCES: JSON.stringify(Array.isArray(input.scopedServerModuleSources) ? input.scopedServerModuleSources : []),
146
+ ZENITH_SCOPED_SERVER_RUNTIME_PATH: join(__dirname, '..', 'scoped-server-data', 'runtime.js')
134
147
  },
135
148
  stdio: ['pipe', 'pipe', 'pipe']
136
149
  });
@@ -15,11 +15,11 @@ const routeFile = process.env.ZENITH_SERVER_ROUTE_FILE || sourcePath || '';
15
15
  const routeId = process.env.ZENITH_SERVER_ROUTE_ID || routePattern || '';
16
16
  const routeKind = process.env.ZENITH_SERVER_ROUTE_KIND || 'page';
17
17
  const guardOnly = process.env.ZENITH_SERVER_GUARD_ONLY === '1';
18
+ const globalMiddlewareSource = process.env.ZENITH_GLOBAL_MIDDLEWARE_SOURCE || '';
19
+ const globalMiddlewareSourcePath = process.env.ZENITH_GLOBAL_MIDDLEWARE_SOURCE_PATH || '';
20
+ const scopedServerData = JSON.parse(process.env.ZENITH_SCOPED_SERVER_DATA || '[]'), scopedServerModuleBaseDir = process.env.ZENITH_SCOPED_SERVER_MODULE_BASE_DIR || '', scopedServerModuleSources = JSON.parse(process.env.ZENITH_SCOPED_SERVER_MODULE_SOURCES || '[]');
18
21
 
19
- if (!source.trim()) {
20
- process.stdout.write('null');
21
- process.exit(0);
22
- }
22
+ if (!source.trim() && !(Array.isArray(scopedServerData) && scopedServerData.length > 0)) { process.stdout.write('null'); process.exit(0); }
23
23
 
24
24
  let cachedTypeScript = undefined;
25
25
  async function loadTypeScript() {
@@ -332,6 +332,92 @@ async function linkModule(specifier, parentIdentifier) {
332
332
  return loadFileModule(resolvedUrl);
333
333
  }
334
334
 
335
+ function configuredFrameworkUrl(specifier) {
336
+ if (specifier === 'zenith:server-contract') {
337
+ const defaultPath = path.join(process.cwd(), 'node_modules', '@zenithbuild', 'cli', 'src', 'server-contract.js');
338
+ const configuredPath = process.env.ZENITH_SERVER_CONTRACT_PATH || '';
339
+ return {
340
+ url: pathToFileURL(configuredPath || defaultPath).href,
341
+ fallbackUrl: pathToFileURL(defaultPath).href,
342
+ hasConfiguredPath: Boolean(configuredPath)
343
+ };
344
+ }
345
+ if (specifier === 'zenith:route-auth') {
346
+ const defaultPath = path.join(process.cwd(), 'node_modules', '@zenithbuild', 'cli', 'src', 'auth', 'route-auth.js');
347
+ const configuredPath = process.env.ZENITH_SERVER_ROUTE_AUTH_PATH || '';
348
+ return {
349
+ url: pathToFileURL(configuredPath || defaultPath).href,
350
+ fallbackUrl: pathToFileURL(defaultPath).href,
351
+ hasConfiguredPath: Boolean(configuredPath)
352
+ };
353
+ }
354
+ if (specifier === 'zenith:scoped-server-data-runtime') { const defaultPath = path.join(process.cwd(), 'node_modules', '@zenithbuild', 'cli', 'src', 'scoped-server-data', 'runtime.js'); const configuredPath = process.env.ZENITH_SCOPED_SERVER_RUNTIME_PATH || ''; return { url: pathToFileURL(configuredPath || defaultPath).href, fallbackUrl: pathToFileURL(defaultPath).href, hasConfiguredPath: Boolean(configuredPath) }; }
355
+ return null;
356
+ }
357
+
358
+ async function linkEntryModule(specifier, referencingModule) {
359
+ const frameworkUrl = configuredFrameworkUrl(specifier);
360
+ if (frameworkUrl) {
361
+ if (frameworkUrl.hasConfiguredPath) {
362
+ return loadFileModule(frameworkUrl.url);
363
+ }
364
+ return loadFileModule(frameworkUrl.url).catch(() =>
365
+ loadFileModule(frameworkUrl.fallbackUrl)
366
+ );
367
+ }
368
+ return linkModule(specifier, referencingModule.identifier);
369
+ }
370
+
371
+ async function loadMatchedRoutePipeline() {
372
+ const defaultPath = path.join(process.cwd(), 'node_modules', '@zenithbuild', 'cli', 'src', 'server-runtime', 'matched-route-pipeline.js');
373
+ const configuredPath = process.env.ZENITH_SERVER_MATCHED_ROUTE_PIPELINE_PATH || '';
374
+ const pipelineUrl = pathToFileURL(configuredPath || defaultPath).href;
375
+ const module = await loadFileModule(pipelineUrl).catch((error) => {
376
+ if (configuredPath) {
377
+ throw error;
378
+ }
379
+ return loadFileModule(pathToFileURL(defaultPath).href);
380
+ });
381
+ await module.evaluate();
382
+ if (typeof module.namespace.executeMatchedRoutePipeline !== 'function') {
383
+ throw new Error('[zenith-preview] matched route pipeline unavailable');
384
+ }
385
+ return module.namespace.executeMatchedRoutePipeline;
386
+ }
387
+
388
+ async function loadGlobalMiddleware() {
389
+ if (!globalMiddlewareSource.trim()) {
390
+ return null;
391
+ }
392
+ const identifier = globalMiddlewareSourcePath
393
+ ? pathToFileURL(globalMiddlewareSourcePath).href
394
+ : 'zenith:global-middleware';
395
+ const filename = globalMiddlewareSourcePath || 'global-middleware.ts';
396
+ const code = await transpileIfNeeded(filename, globalMiddlewareSource);
397
+ const module = new vm.SourceTextModule(code, {
398
+ context,
399
+ identifier,
400
+ initializeImportMeta(meta) {
401
+ meta.url = identifier;
402
+ }
403
+ });
404
+ moduleCache.set(identifier, module);
405
+ await module.link(linkEntryModule);
406
+ await module.evaluate();
407
+ const middlewareFn = module.namespace.default;
408
+ if (typeof middlewareFn !== 'function') {
409
+ throw new Error('[Zenith:Middleware] Global middleware module must default export a function.');
410
+ }
411
+ return middlewareFn;
412
+ }
413
+
414
+ async function loadScopedServerRuntime() { const module = await linkEntryModule('zenith:scoped-server-data-runtime', null); await module.evaluate(); if (typeof module.namespace.executeScopedServerData !== 'function' || typeof module.namespace.mergeScopedSsrPayload !== 'function') throw new Error('[zenith-preview] scoped server data runtime unavailable'); return module.namespace; }
415
+ async function loadScopedSourceModule(entry) { const modulePath = String(entry && entry.module || ''); const found = Array.isArray(scopedServerModuleSources) ? scopedServerModuleSources.find((item) => item && item.module === modulePath) : null; if (!found || typeof found.source !== 'string') throw new Error('[Zenith:ScopedServerData] Missing scoped server data source for "' + modulePath + '".'); const sourcePath = typeof found.sourcePath === 'string' && found.sourcePath.length > 0 ? found.sourcePath : modulePath; const identifier = sourcePath.startsWith('file:') ? sourcePath : pathToFileURL(sourcePath).href; const filename = sourcePath.toLowerCase().endsWith('.zen') ? sourcePath.replace(/\.zen$/i, '.ts') : sourcePath; const code = await transpileIfNeeded(filename, found.source); const module = new vm.SourceTextModule(code, { context, identifier, initializeImportMeta(meta) { meta.url = identifier; } }); moduleCache.set(identifier, module); await module.link(linkEntryModule); await module.evaluate(); return module.namespace; }
416
+ function invalidScopedModulePath() { throw new Error('[Zenith:ScopedServerData] Invalid scoped server data module path.'); }
417
+ function resolveScopedPackagedModulePath(entry) { const modulePath = String(entry && entry.module || ''); if (!modulePath || path.isAbsolute(modulePath) || /^[A-Za-z]:[\\/]/.test(modulePath)) invalidScopedModulePath(); const normalized = modulePath.replace(/\\/g, '/'); if (!normalized.startsWith('scoped/') || normalized.split('/').some((part) => part === '..' || part === '.')) invalidScopedModulePath(); const scopedRoot = path.resolve(scopedServerModuleBaseDir, 'scoped'); const candidate = path.resolve(scopedServerModuleBaseDir, normalized); if (candidate !== scopedRoot && !candidate.startsWith(scopedRoot + path.sep)) invalidScopedModulePath(); return candidate; }
418
+ async function loadScopedPackagedModule(entry) { if (!scopedServerModuleBaseDir) throw new Error('[Zenith:ScopedServerData] Cannot execute scoped server data without a server module root.'); const module = await loadFileModule(pathToFileURL(resolveScopedPackagedModulePath(entry)).href); await module.evaluate(); return module.namespace; }
419
+ async function executeScopedServerDataAfterRoute(resolved) { if (guardOnly || routeKind === 'resource' || !Array.isArray(scopedServerData) || scopedServerData.length === 0 || !resolved || !resolved.result || resolved.result.kind !== 'data') return resolved; const runtime = await loadScopedServerRuntime(); const scoped = await runtime.executeScopedServerData({ route: { route_kind: routeKind, prerender: false, has_scoped_server_data: true, scoped_server_data: scopedServerData }, ctx: context.ctx, loadModule: (entry) => Array.isArray(scopedServerModuleSources) && scopedServerModuleSources.length > 0 ? loadScopedSourceModule(entry) : loadScopedPackagedModule(entry) }); return { ...resolved, result: { ...resolved.result, data: runtime.mergeScopedSsrPayload(resolved.result.data, scoped) } }; }
420
+
335
421
  const allowed = new Set(['data', 'load', 'guard', 'action', 'ssr_data', 'props', 'ssr', 'prerender', 'exportPaths']);
336
422
  const prelude = "const params = globalThis.params;\n" +
337
423
  "const ctx = globalThis.ctx;\n" +
@@ -358,29 +444,7 @@ const entryModule = new vm.SourceTextModule(entryCode, {
358
444
 
359
445
  moduleCache.set(entryIdentifier, entryModule);
360
446
  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);
447
+ return linkEntryModule(specifier, referencingModule);
384
448
  });
385
449
  await entryModule.evaluate();
386
450
  context.attachRouteAuth(routeContext, {
@@ -399,13 +463,17 @@ for (const key of namespaceKeys) {
399
463
 
400
464
  const exported = entryModule.namespace;
401
465
  try {
402
- const resolved = await context.resolveRouteResult({
466
+ const executeMatchedRoutePipeline = await loadMatchedRoutePipeline();
467
+ const globalMiddleware = await loadGlobalMiddleware();
468
+ let resolved = await executeMatchedRoutePipeline({
403
469
  exports: exported,
404
470
  ctx: context.ctx,
405
471
  filePath: sourcePath || 'server_script',
406
472
  guardOnly: guardOnly,
407
- routeKind: routeKind
473
+ routeKind: routeKind,
474
+ globalMiddleware
408
475
  });
476
+ resolved = await executeScopedServerDataAfterRoute(resolved);
409
477
 
410
478
  process.stdout.write(JSON.stringify(resolved || null));
411
479
  } catch (error) {
@@ -28,6 +28,24 @@ function createReadableStreamFromAsyncIterable(iterable) {
28
28
  }
29
29
  });
30
30
  }
31
+ const SSE_METADATA_CONTROL_RE = /[\x00-\x1F\x7F]/u;
32
+ function serializeSseMetadata(value, field) {
33
+ const serialized = String(value);
34
+ if (SSE_METADATA_CONTROL_RE.test(serialized)) {
35
+ throw new Error(`[Zenith] sse ${field} metadata must be a single line without control characters.`);
36
+ }
37
+ return serialized;
38
+ }
39
+ function serializeSseRetry(value) {
40
+ if (!Number.isSafeInteger(value) || value < 0) {
41
+ throw new Error('[Zenith] sse retry metadata must be a non-negative safe integer.');
42
+ }
43
+ return String(value);
44
+ }
45
+ function serializeSseData(value) {
46
+ const raw = typeof value === 'string' ? value : JSON.stringify(value);
47
+ return String(raw ?? '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
48
+ }
31
49
  function createSseStream(events) {
32
50
  const iterator = events[Symbol.asyncIterator]();
33
51
  const encoder = new TextEncoder();
@@ -40,14 +58,13 @@ function createSseStream(events) {
40
58
  return;
41
59
  }
42
60
  let chunk = '';
43
- if (value.event)
44
- chunk += `event: ${value.event}\n`;
45
- if (value.id)
46
- chunk += `id: ${value.id}\n`;
47
- if (value.retry)
48
- chunk += `retry: ${value.retry}\n`;
49
- const data = typeof value.data === 'string' ? value.data : JSON.stringify(value.data);
50
- const lines = data.split('\n');
61
+ if (value.event !== undefined)
62
+ chunk += `event: ${serializeSseMetadata(value.event, 'event')}\n`;
63
+ if (value.id !== undefined)
64
+ chunk += `id: ${serializeSseMetadata(value.id, 'id')}\n`;
65
+ if (value.retry !== undefined)
66
+ chunk += `retry: ${serializeSseRetry(value.retry)}\n`;
67
+ const lines = serializeSseData(value.data).split('\n');
51
68
  for (const line of lines) {
52
69
  chunk += `data: ${line}\n`;
53
70
  }
@@ -1,27 +1,10 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { relative, sep } from 'node:path';
3
+ import { readRouteHandlerExport } from './route-handler-export-analysis.js';
3
4
  const RESOURCE_EXTENSIONS = ['.resource.ts', '.resource.js', '.resource.mts', '.resource.cts', '.resource.mjs', '.resource.cjs'];
4
5
  const FORBIDDEN_RESOURCE_EXPORT_RE = /\bexport\s+const\s+(?:data|prerender|exportPaths|ssr_data|props|ssr)\b/;
5
- function readSingleExport(source, name) {
6
- const fnMatch = source.match(new RegExp(`\\bexport\\s+(?:async\\s+)?function\\s+${name}\\s*\\(([^)]*)\\)`));
7
- const constParenMatch = source.match(new RegExp(`\\bexport\\s+const\\s+${name}\\s*=\\s*(?:async\\s*)?\\(([^)]*)\\)\\s*=>`));
8
- const constSingleArgMatch = source.match(new RegExp(`\\bexport\\s+const\\s+${name}\\s*=\\s*(?:async\\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\\s*=>`));
9
- const matchCount = Number(Boolean(fnMatch)) +
10
- Number(Boolean(constParenMatch)) +
11
- Number(Boolean(constSingleArgMatch));
12
- return {
13
- fnMatch,
14
- constParenMatch,
15
- constSingleArgMatch,
16
- hasExport: matchCount > 0,
17
- matchCount
18
- };
19
- }
20
6
  function assertSingleCtxArg(sourceFile, name, exportMatch) {
21
- const singleArg = String(exportMatch.constSingleArgMatch?.[1] || '').trim();
22
- const paramsText = String((exportMatch.fnMatch || exportMatch.constParenMatch)?.[1] || '').trim();
23
- const arity = singleArg ? 1 : paramsText.length === 0 ? 0 : paramsText.split(',').length;
24
- if (arity !== 1) {
7
+ if (exportMatch.arity !== null && exportMatch.arity !== 1) {
25
8
  throw new Error(`Zenith resource route contract violation:\n` +
26
9
  ` File: ${sourceFile}\n` +
27
10
  ` Reason: ${name}(ctx) must accept exactly one argument\n` +
@@ -80,9 +63,9 @@ export function analyzeResourceRouteModule(fullPath, root) {
80
63
  ` Reason: resource routes may only export guard(ctx), load(ctx), and action(ctx)\n` +
81
64
  ` Example: remove page-only exports such as data/prerender/exportPaths`);
82
65
  }
83
- const guardExport = readSingleExport(source, 'guard');
84
- const loadExport = readSingleExport(source, 'load');
85
- const actionExport = readSingleExport(source, 'action');
66
+ const guardExport = readRouteHandlerExport(source, 'guard');
67
+ const loadExport = readRouteHandlerExport(source, 'load');
68
+ const actionExport = readRouteHandlerExport(source, 'action');
86
69
  for (const [name, exportMatch] of [
87
70
  ['guard', guardExport],
88
71
  ['load', loadExport],
@@ -0,0 +1,11 @@
1
+ export function classifyPageRoute({ file, serverScript, hasScopedServerData }: {
2
+ file: any;
3
+ serverScript: any;
4
+ hasScopedServerData?: boolean | undefined;
5
+ }): {
6
+ prerender: boolean;
7
+ renderMode: string;
8
+ hasGuard: boolean;
9
+ hasLoad: boolean;
10
+ hasAction: boolean;
11
+ };