@zenithbuild/cli 0.7.3 → 0.7.5

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 (84) hide show
  1. package/README.md +18 -13
  2. package/dist/adapters/adapter-netlify.d.ts +1 -1
  3. package/dist/adapters/adapter-netlify.js +56 -13
  4. package/dist/adapters/adapter-node.js +8 -0
  5. package/dist/adapters/adapter-static-export.d.ts +5 -0
  6. package/dist/adapters/adapter-static-export.js +115 -0
  7. package/dist/adapters/adapter-types.d.ts +3 -1
  8. package/dist/adapters/adapter-types.js +5 -2
  9. package/dist/adapters/adapter-vercel.d.ts +1 -1
  10. package/dist/adapters/adapter-vercel.js +70 -13
  11. package/dist/adapters/copy-hosted-page-runtime.d.ts +1 -0
  12. package/dist/adapters/copy-hosted-page-runtime.js +49 -0
  13. package/dist/adapters/resolve-adapter.js +4 -0
  14. package/dist/adapters/route-rules.d.ts +5 -0
  15. package/dist/adapters/route-rules.js +9 -0
  16. package/dist/adapters/validate-hosted-resource-routes.d.ts +1 -0
  17. package/dist/adapters/validate-hosted-resource-routes.js +13 -0
  18. package/dist/auth/route-auth.d.ts +6 -0
  19. package/dist/auth/route-auth.js +236 -0
  20. package/dist/build/compiler-runtime.d.ts +10 -9
  21. package/dist/build/compiler-runtime.js +58 -2
  22. package/dist/build/compiler-signal-expression.d.ts +1 -0
  23. package/dist/build/compiler-signal-expression.js +155 -0
  24. package/dist/build/expression-rewrites.d.ts +1 -6
  25. package/dist/build/expression-rewrites.js +61 -65
  26. package/dist/build/page-component-loop.d.ts +3 -13
  27. package/dist/build/page-component-loop.js +21 -46
  28. package/dist/build/page-ir-normalization.d.ts +0 -8
  29. package/dist/build/page-ir-normalization.js +13 -234
  30. package/dist/build/page-loop-state.d.ts +6 -9
  31. package/dist/build/page-loop-state.js +9 -8
  32. package/dist/build/page-loop.js +27 -22
  33. package/dist/build/scoped-identifier-rewrite.d.ts +37 -44
  34. package/dist/build/scoped-identifier-rewrite.js +28 -128
  35. package/dist/build/server-script.d.ts +3 -1
  36. package/dist/build/server-script.js +35 -5
  37. package/dist/build-output-manifest.d.ts +3 -2
  38. package/dist/build-output-manifest.js +3 -0
  39. package/dist/build.js +32 -18
  40. package/dist/component-instance-ir.js +158 -52
  41. package/dist/dev-build-session.js +20 -6
  42. package/dist/dev-server.js +152 -55
  43. package/dist/download-result.d.ts +14 -0
  44. package/dist/download-result.js +148 -0
  45. package/dist/framework-components/Image.zen +1 -1
  46. package/dist/images/materialization-plan.d.ts +1 -0
  47. package/dist/images/materialization-plan.js +6 -0
  48. package/dist/images/materialize.d.ts +5 -3
  49. package/dist/images/materialize.js +24 -109
  50. package/dist/images/router-manifest.d.ts +1 -0
  51. package/dist/images/router-manifest.js +49 -0
  52. package/dist/images/service.d.ts +13 -1
  53. package/dist/images/service.js +45 -15
  54. package/dist/index.js +8 -2
  55. package/dist/manifest.d.ts +15 -1
  56. package/dist/manifest.js +27 -7
  57. package/dist/preview.d.ts +13 -4
  58. package/dist/preview.js +261 -101
  59. package/dist/request-body.d.ts +1 -0
  60. package/dist/request-body.js +7 -0
  61. package/dist/request-origin.d.ts +2 -0
  62. package/dist/request-origin.js +45 -0
  63. package/dist/resource-manifest.d.ts +16 -0
  64. package/dist/resource-manifest.js +53 -0
  65. package/dist/resource-response.d.ts +34 -0
  66. package/dist/resource-response.js +71 -0
  67. package/dist/resource-route-module.d.ts +15 -0
  68. package/dist/resource-route-module.js +129 -0
  69. package/dist/route-check-support.d.ts +1 -0
  70. package/dist/route-check-support.js +4 -0
  71. package/dist/server-contract.d.ts +29 -6
  72. package/dist/server-contract.js +304 -42
  73. package/dist/server-error.d.ts +4 -0
  74. package/dist/server-error.js +36 -0
  75. package/dist/server-output.d.ts +4 -1
  76. package/dist/server-output.js +71 -10
  77. package/dist/server-runtime/node-server.js +67 -31
  78. package/dist/server-runtime/route-render.d.ts +27 -3
  79. package/dist/server-runtime/route-render.js +94 -53
  80. package/dist/server-script-composition.d.ts +13 -5
  81. package/dist/server-script-composition.js +29 -11
  82. package/dist/static-export-paths.d.ts +3 -0
  83. package/dist/static-export-paths.js +160 -0
  84. package/package.json +6 -3
@@ -1,29 +1,9 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
- const RUNTIME_EXPORTS = {
4
- hydrate: () => () => { },
5
- signal: (value) => value,
6
- state: (value) => value,
7
- ref: () => ({ current: null }),
8
- zeneffect: () => () => { },
9
- zenEffect: () => () => { },
10
- zenMount: () => { },
11
- zenWindow: () => undefined,
12
- zenDocument: () => undefined,
13
- zenOn: () => () => { },
14
- zenResize: () => () => { },
15
- collectRefs: (...refs) => refs.filter(Boolean)
16
- };
3
+ import { renderImageHtmlWithPayload, replaceImageMarkers, serializeImageProps } from './runtime.js';
17
4
  function escapeRegex(value) {
18
5
  return value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
19
6
  }
20
- function escapeHtml(value) {
21
- return String(value ?? '')
22
- .replace(/&/g, '&')
23
- .replace(/"/g, '"')
24
- .replace(/</g, '&lt;')
25
- .replace(/>/g, '&gt;');
26
- }
27
7
  function parseMarkerSelector(selector) {
28
8
  const match = selector.match(/^\[([^\]=]+)=["']([^"']+)["']\]$/);
29
9
  if (!match)
@@ -41,7 +21,7 @@ function upsertAttributeMarkup(attributes, attrName, value) {
41
21
  if (value === null || value === undefined || value === false || value === '') {
42
22
  return attributes.replace(attrPattern, '');
43
23
  }
44
- const serialized = ` ${trimmedName}="${escapeHtml(value)}"`;
24
+ const serialized = ` ${trimmedName}="${String(value)}"`;
45
25
  if (attrPattern.test(attributes)) {
46
26
  return attributes.replace(attrPattern, serialized);
47
27
  }
@@ -52,7 +32,7 @@ function applyAttributeMarker(html, selector, attrName, value) {
52
32
  if (!parsed)
53
33
  return html;
54
34
  const markerRe = new RegExp(`<([A-Za-z][\\w:-]*)([^>]*\\s${escapeRegex(parsed.attrName)}=(["'])${escapeRegex(parsed.attrValue)}\\3[^>]*)>`, 'g');
55
- return html.replace(markerRe, (match, tagName, attrs) => {
35
+ return html.replace(markerRe, (_match, tagName, attrs) => {
56
36
  const nextAttrs = upsertAttributeMarkup(String(attrs || ''), attrName, value);
57
37
  return `<${tagName}${nextAttrs}>`;
58
38
  });
@@ -65,95 +45,33 @@ function applyInnerHtmlMarker(html, selector, value) {
65
45
  const replacement = value === null || value === undefined || value === false ? '' : String(value);
66
46
  return html.replace(markerRe, (_match, tagName, attrs) => `<${tagName}${attrs}>${replacement}</${tagName}>`);
67
47
  }
68
- function stripModuleSyntax(source) {
69
- let next = source.replace(/^import\s+[^;]+;\s*$/gm, '');
70
- if (/(^|\n)\s*import\s+/m.test(next)) {
71
- throw new Error('[Zenith:Image] Cannot materialize page asset with unresolved imports');
72
- }
73
- next = next.replace(/^export\s+default\s+function\s+/gm, 'function ');
74
- next = next.replace(/^export\s+function\s+/gm, 'function ');
75
- next = next.replace(/^export\s+const\s+/gm, 'const ');
76
- next = next.replace(/^export\s+let\s+/gm, 'let ');
77
- next = next.replace(/^export\s+var\s+/gm, 'var ');
78
- next = next.replace(/\bexport\s*\{[^}]*\};?/g, '');
79
- return next;
80
- }
81
- async function evaluatePageModule(assetPath, payload, ssrData, routePathname) {
82
- const source = stripModuleSyntax(await readFile(assetPath, 'utf8'));
83
- const runtimeNames = Object.keys(RUNTIME_EXPORTS);
84
- const evaluator = new Function('runtime', 'payload', 'ssrData', 'routePathname', [
85
- '"use strict";',
86
- `const { ${runtimeNames.join(', ')} } = runtime;`,
87
- 'const document = {};',
88
- 'const location = { pathname: routePathname || "/" };',
89
- 'const Document = class ZenithServerDocument {};',
90
- 'const globalThis = {',
91
- ' __zenith_image_runtime: payload,',
92
- ' document,',
93
- ' location,',
94
- ' Document',
95
- '};',
96
- 'if (ssrData && typeof ssrData === "object" && !Array.isArray(ssrData)) {',
97
- ' globalThis.__zenith_ssr_data = ssrData;',
98
- '}',
99
- 'globalThis.globalThis = globalThis;',
100
- 'globalThis.window = globalThis;',
101
- 'globalThis.self = globalThis;',
102
- source,
103
- 'return {',
104
- ' __zenith_markers: typeof __zenith_markers !== "undefined" ? __zenith_markers : [],',
105
- ' __zenith_expression_bindings: typeof __zenith_expression_bindings !== "undefined" ? __zenith_expression_bindings : [],',
106
- ' __zenith_expr_fns: typeof __zenith_expr_fns !== "undefined" ? __zenith_expr_fns : []',
107
- '};'
108
- ].join('\n'));
109
- return evaluator(RUNTIME_EXPORTS, payload, ssrData, routePathname);
48
+ function isPlainObject(value) {
49
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
110
50
  }
111
- function buildExpressionContext(ssrData) {
112
- return {
113
- signalMap: new Map(),
114
- params: {},
115
- props: {},
116
- ssrData: ssrData || {},
117
- componentBindings: {},
118
- zenhtml: null,
119
- fragment: null
120
- };
51
+ function hasUnmaterializedImageMarkers(html) {
52
+ const matches = html.match(/<span\b[^>]*\bdata-zx-(?:data-zenith-image|unsafeHTML)=(["'])[^"']+\1[^>]*>/gi) || [];
53
+ return matches.some((tag) => /\sdata-zenith-image=/.test(tag) === false);
121
54
  }
122
55
  export async function materializeImageMarkup(options) {
123
- const { html, pageAssetPath, payload, ssrData = null, routePathname = '/' } = options;
124
- if (!pageAssetPath || !html.includes('data-zx-data-zenith-image')) {
125
- return html;
126
- }
127
- const namespace = await evaluatePageModule(pageAssetPath, payload, ssrData, routePathname);
128
- if (!namespace) {
129
- return html;
130
- }
131
- const markers = Array.isArray(namespace.__zenith_markers) ? namespace.__zenith_markers : [];
132
- const bindings = Array.isArray(namespace.__zenith_expression_bindings) ? namespace.__zenith_expression_bindings : [];
133
- const exprFns = Array.isArray(namespace.__zenith_expr_fns) ? namespace.__zenith_expr_fns : [];
134
- if (markers.length === 0 || bindings.length === 0 || exprFns.length === 0) {
56
+ const { html, payload, imageMaterialization = [] } = options;
57
+ const entries = Array.isArray(imageMaterialization) ? imageMaterialization : [];
58
+ if (typeof html !== 'string' || html.length === 0) {
135
59
  return html;
136
60
  }
137
- const markerByIndex = new Map(markers.map((marker) => [marker.index, marker]));
138
61
  let nextHtml = html;
139
- const context = buildExpressionContext(ssrData);
140
- for (const binding of bindings) {
141
- const marker = markerByIndex.get(Number(binding.marker_index));
142
- const exprFn = Number.isInteger(binding.fn_index) ? exprFns[binding.fn_index] : null;
143
- if (!marker ||
144
- typeof exprFn !== 'function' ||
145
- marker.kind !== 'attr' ||
146
- typeof marker.selector !== 'string' ||
147
- marker.selector.includes('data-zx-data-zenith-image') === false &&
148
- marker.selector.includes('data-zx-innerHTML') === false) {
62
+ for (const entry of entries) {
63
+ if (!entry || typeof entry.selector !== 'string' || !isPlainObject(entry.props)) {
149
64
  continue;
150
65
  }
151
- const value = exprFn(context);
152
- if (marker.attr === 'innerHTML') {
153
- nextHtml = applyInnerHtmlMarker(nextHtml, marker.selector, value);
154
- continue;
155
- }
156
- nextHtml = applyAttributeMarker(nextHtml, marker.selector, marker.attr || '', value);
66
+ const encodedProps = serializeImageProps(entry.props);
67
+ const renderedHtml = renderImageHtmlWithPayload(entry.props, payload);
68
+ nextHtml = applyAttributeMarker(nextHtml, entry.selector, 'data-zenith-image', encodedProps);
69
+ nextHtml = applyInnerHtmlMarker(nextHtml, entry.selector, renderedHtml);
70
+ }
71
+ nextHtml = replaceImageMarkers(nextHtml, payload);
72
+ if (hasUnmaterializedImageMarkers(nextHtml)) {
73
+ throw new Error('[Zenith:Image] Unresolved Image markers require a compiler-owned image materialization artifact. ' +
74
+ 'Dynamic image props are currently unsupported.');
157
75
  }
158
76
  return nextHtml;
159
77
  }
@@ -175,11 +93,9 @@ export async function materializeImageMarkupInHtmlFiles(options) {
175
93
  continue;
176
94
  }
177
95
  const outputPath = typeof route.output === 'string' ? route.output.replace(/^\//, '') : '';
178
- const assetPath = typeof route.page_asset === 'string' ? route.page_asset.replace(/^\//, '') : '';
179
- if (!outputPath || !assetPath)
96
+ if (!outputPath)
180
97
  continue;
181
98
  const fullHtmlPath = join(distDir, outputPath);
182
- const fullAssetPath = join(distDir, assetPath);
183
99
  let html = '';
184
100
  try {
185
101
  html = await readFile(fullHtmlPath, 'utf8');
@@ -189,9 +105,8 @@ export async function materializeImageMarkupInHtmlFiles(options) {
189
105
  }
190
106
  const nextHtml = await materializeImageMarkup({
191
107
  html,
192
- pageAssetPath: fullAssetPath,
193
108
  payload,
194
- routePathname: typeof route.path === 'string' ? route.path : '/'
109
+ imageMaterialization: Array.isArray(route.image_materialization) ? route.image_materialization : []
195
110
  });
196
111
  if (nextHtml !== html) {
197
112
  await writeFile(fullHtmlPath, nextHtml, 'utf8');
@@ -0,0 +1 @@
1
+ export function injectImageMaterializationIntoRouterManifest(distDir: any, envelopes: any): Promise<void>;
@@ -0,0 +1,49 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ export async function injectImageMaterializationIntoRouterManifest(distDir, envelopes) {
4
+ const manifestPath = join(distDir, 'assets', 'router-manifest.json');
5
+ let parsed;
6
+ try {
7
+ parsed = JSON.parse(await readFile(manifestPath, 'utf8'));
8
+ }
9
+ catch {
10
+ return;
11
+ }
12
+ const routes = Array.isArray(parsed?.routes) ? parsed.routes : null;
13
+ if (!routes) {
14
+ return;
15
+ }
16
+ const serverMetadataByRoute = new Map();
17
+ for (const envelope of Array.isArray(envelopes) ? envelopes : []) {
18
+ const route = typeof envelope?.route === 'string' ? envelope.route : '';
19
+ if (!route) {
20
+ continue;
21
+ }
22
+ const routeIr = envelope?.ir && typeof envelope.ir === 'object' ? envelope.ir : {};
23
+ serverMetadataByRoute.set(route, {
24
+ guard_module_ref: routeIr.guard_module_ref || null,
25
+ load_module_ref: routeIr.load_module_ref || null,
26
+ action_module_ref: routeIr.action_module_ref || null,
27
+ has_guard: routeIr.has_guard === true,
28
+ has_load: routeIr.has_load === true,
29
+ has_action: routeIr.has_action === true
30
+ });
31
+ }
32
+ for (const route of routes) {
33
+ const routePath = typeof route?.path === 'string' ? route.path : '';
34
+ if (!routePath) {
35
+ continue;
36
+ }
37
+ const serverMetadata = serverMetadataByRoute.get(routePath);
38
+ if (!serverMetadata) {
39
+ continue;
40
+ }
41
+ route.guard_module_ref = serverMetadata.guard_module_ref;
42
+ route.load_module_ref = serverMetadata.load_module_ref;
43
+ route.action_module_ref = serverMetadata.action_module_ref;
44
+ route.has_guard = serverMetadata.has_guard;
45
+ route.has_load = serverMetadata.has_load;
46
+ route.has_action = serverMetadata.has_action;
47
+ }
48
+ await writeFile(manifestPath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf8');
49
+ }
@@ -1,4 +1,16 @@
1
1
  export function buildImageArtifacts(options: any): Promise<{
2
2
  manifest: {};
3
3
  }>;
4
- export function handleImageRequest(req: any, res: any, options: any): Promise<boolean>;
4
+ /**
5
+ * @param {Request | { url?: string } | null | undefined} request
6
+ * @param {{ requestUrl?: URL | string, projectRoot: string, config?: Record<string, unknown> }} options
7
+ * @returns {Promise<Response>}
8
+ */
9
+ export function handleImageFetchRequest(request: Request | {
10
+ url?: string;
11
+ } | null | undefined, options: {
12
+ requestUrl?: URL | string;
13
+ projectRoot: string;
14
+ config?: Record<string, unknown>;
15
+ }): Promise<Response>;
16
+ export function handleImageRequest(_req: any, res: any, options: any): Promise<boolean>;
@@ -220,7 +220,29 @@ function remoteCachePaths(cacheDir, cacheKey) {
220
220
  metaPath: join(cacheDir, `${cacheKey}.json`)
221
221
  };
222
222
  }
223
- export async function handleImageRequest(req, res, options) {
223
+ function createJsonResponse(status, payload) {
224
+ return new Response(JSON.stringify(payload), {
225
+ status,
226
+ headers: {
227
+ 'Content-Type': 'application/json'
228
+ }
229
+ });
230
+ }
231
+ function createBufferResponse(status, contentType, buffer, cacheSeconds) {
232
+ return new Response(buffer, {
233
+ status,
234
+ headers: {
235
+ 'Content-Type': contentType,
236
+ 'Cache-Control': `public, max-age=${cacheSeconds}`
237
+ }
238
+ });
239
+ }
240
+ async function sendResponse(res, response) {
241
+ res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
242
+ const body = await response.arrayBuffer();
243
+ res.end(Buffer.from(body));
244
+ }
245
+ async function createImageResponse(options) {
224
246
  const { requestUrl, projectRoot, config: rawConfig } = options;
225
247
  const config = normalizeImageConfig(rawConfig);
226
248
  const url = requestUrl instanceof URL ? requestUrl : new URL(String(requestUrl));
@@ -230,14 +252,10 @@ export async function handleImageRequest(req, res, options) {
230
252
  const format = normalizeImageFormat(url.searchParams.get('f') || '');
231
253
  const quality = Number.isInteger(requestedQuality) && requestedQuality > 0 ? requestedQuality : config.quality;
232
254
  if (!remoteUrl) {
233
- res.writeHead(400, { 'Content-Type': 'application/json' });
234
- res.end(JSON.stringify({ error: 'missing_url' }));
235
- return true;
255
+ return createJsonResponse(400, { error: 'missing_url' });
236
256
  }
237
257
  if (!Number.isInteger(width) || width <= 0) {
238
- res.writeHead(400, { 'Content-Type': 'application/json' });
239
- res.end(JSON.stringify({ error: 'invalid_width' }));
240
- return true;
258
+ return createJsonResponse(400, { error: 'invalid_width' });
241
259
  }
242
260
  try {
243
261
  const remote = await validateRemoteTarget(remoteUrl, config);
@@ -253,8 +271,7 @@ export async function handleImageRequest(req, res, options) {
253
271
  const contentType = typeof parsedMeta?.contentType === 'string'
254
272
  ? parsedMeta.contentType
255
273
  : mimeTypeForFormat(format || 'jpg');
256
- sendBuffer(res, 200, contentType, cached, config.minimumCacheTTL);
257
- return true;
274
+ return createBufferResponse(200, contentType, cached, config.minimumCacheTTL);
258
275
  }
259
276
  const response = await fetch(remote, {
260
277
  headers: {
@@ -288,15 +305,28 @@ export async function handleImageRequest(req, res, options) {
288
305
  format: targetFormat
289
306
  }, null, 2)}\n`, 'utf8')
290
307
  ]);
291
- sendBuffer(res, 200, mimeTypeForFormat(targetFormat), output, config.minimumCacheTTL);
292
- return true;
308
+ return createBufferResponse(200, mimeTypeForFormat(targetFormat), output, config.minimumCacheTTL);
293
309
  }
294
310
  catch (error) {
295
- res.writeHead(400, { 'Content-Type': 'application/json' });
296
- res.end(JSON.stringify({
311
+ return createJsonResponse(400, {
297
312
  error: 'image_request_failed',
298
313
  message: error instanceof Error ? error.message : String(error)
299
- }));
300
- return true;
314
+ });
301
315
  }
302
316
  }
317
+ /**
318
+ * @param {Request | { url?: string } | null | undefined} request
319
+ * @param {{ requestUrl?: URL | string, projectRoot: string, config?: Record<string, unknown> }} options
320
+ * @returns {Promise<Response>}
321
+ */
322
+ export async function handleImageFetchRequest(request, options) {
323
+ return createImageResponse({
324
+ ...options,
325
+ requestUrl: request?.url || options?.requestUrl
326
+ });
327
+ }
328
+ export async function handleImageRequest(_req, res, options) {
329
+ const response = await createImageResponse(options);
330
+ await sendResponse(res, response);
331
+ return true;
332
+ }
package/dist/index.js CHANGED
@@ -88,9 +88,15 @@ export async function cli(args, cwd) {
88
88
  printUsage(logger);
89
89
  process.exit(0);
90
90
  }
91
- if (!command || !COMMANDS.includes(command)) {
91
+ if (!command) {
92
92
  printUsage(logger);
93
- process.exit(command ? 1 : 0);
93
+ process.exit(0);
94
+ }
95
+ if (!COMMANDS.includes(command)) {
96
+ logger.print(`Unknown command: ${command}`);
97
+ logger.print('');
98
+ printUsage(logger);
99
+ process.exit(1);
94
100
  }
95
101
  const projectRoot = resolve(cwd || process.cwd());
96
102
  const config = await loadConfig(projectRoot);
@@ -2,9 +2,16 @@
2
2
  * @typedef {{
3
3
  * path: string,
4
4
  * file: string,
5
+ * route_kind?: 'page' | 'resource',
5
6
  * path_kind: 'static' | 'dynamic',
6
7
  * render_mode: 'prerender' | 'server',
7
- * params: string[]
8
+ * params: string[],
9
+ * server_script?: string,
10
+ * server_script_path?: string,
11
+ * has_guard?: boolean,
12
+ * has_load?: boolean,
13
+ * has_action?: boolean,
14
+ * export_paths?: string[]
8
15
  * }} ManifestEntry
9
16
  */
10
17
  /**
@@ -29,7 +36,14 @@ export function serializeManifest(entries: ManifestEntry[]): string;
29
36
  export type ManifestEntry = {
30
37
  path: string;
31
38
  file: string;
39
+ route_kind?: "page" | "resource";
32
40
  path_kind: "static" | "dynamic";
33
41
  render_mode: "prerender" | "server";
34
42
  params: string[];
43
+ server_script?: string;
44
+ server_script_path?: string;
45
+ has_guard?: boolean;
46
+ has_load?: boolean;
47
+ has_action?: boolean;
48
+ export_paths?: string[];
35
49
  };
package/dist/manifest.js CHANGED
@@ -18,14 +18,23 @@ import { readFileSync } from 'node:fs';
18
18
  import { readdir, stat } from 'node:fs/promises';
19
19
  import { join, relative, sep, basename, extname, dirname } from 'node:path';
20
20
  import { extractServerScript } from './build/server-script.js';
21
+ import { analyzeResourceRouteModule, isResourceRouteFile } from './resource-route-module.js';
21
22
  import { composeServerScriptEnvelope, resolveAdjacentServerModules } from './server-script-composition.js';
23
+ import { validateStaticExportPaths } from './static-export-paths.js';
22
24
  /**
23
25
  * @typedef {{
24
26
  * path: string,
25
27
  * file: string,
28
+ * route_kind?: 'page' | 'resource',
26
29
  * path_kind: 'static' | 'dynamic',
27
30
  * render_mode: 'prerender' | 'server',
28
- * params: string[]
31
+ * params: string[],
32
+ * server_script?: string,
33
+ * server_script_path?: string,
34
+ * has_guard?: boolean,
35
+ * has_load?: boolean,
36
+ * has_action?: boolean,
37
+ * export_paths?: string[]
29
38
  * }} ManifestEntry
30
39
  */
31
40
  /**
@@ -75,32 +84,41 @@ async function _scanDir(dir, root, ext, compilerOpts) {
75
84
  }
76
85
  else if (item.endsWith(ext)) {
77
86
  const routePath = _fileToRoute(fullPath, root, ext);
78
- entries.push(buildManifestEntry({
87
+ entries.push(buildPageManifestEntry({
79
88
  fullPath,
80
89
  root,
81
90
  routePath,
82
91
  compilerOpts
83
92
  }));
84
93
  }
94
+ else if (isResourceRouteFile(item)) {
95
+ entries.push(analyzeResourceRouteModule(fullPath, root));
96
+ }
85
97
  }
86
98
  return entries;
87
99
  }
88
- function buildManifestEntry({ fullPath, root, routePath, compilerOpts }) {
100
+ function buildPageManifestEntry({ fullPath, root, routePath, compilerOpts }) {
89
101
  const rawSource = readFileSync(fullPath, 'utf8');
90
102
  const inlineServerScript = extractServerScript(rawSource, fullPath, compilerOpts).serverScript;
91
- const { guardPath, loadPath } = resolveAdjacentServerModules(fullPath);
103
+ const { guardPath, loadPath, actionPath } = resolveAdjacentServerModules(fullPath);
92
104
  const composed = composeServerScriptEnvelope({
93
105
  sourceFile: fullPath,
94
106
  inlineServerScript,
95
107
  adjacentGuardPath: guardPath,
96
- adjacentLoadPath: loadPath
108
+ adjacentLoadPath: loadPath,
109
+ adjacentActionPath: actionPath
97
110
  });
111
+ const exportPaths = Array.isArray(composed.serverScript?.export_paths)
112
+ ? validateStaticExportPaths(routePath, composed.serverScript.export_paths, fullPath)
113
+ : [];
98
114
  return {
99
115
  path: routePath,
100
116
  file: relative(root, fullPath),
117
+ route_kind: 'page',
101
118
  path_kind: _isDynamic(routePath) ? 'dynamic' : 'static',
102
119
  render_mode: composed.serverScript && composed.serverScript.prerender !== true ? 'server' : 'prerender',
103
- params: extractRouteParams(routePath)
120
+ params: extractRouteParams(routePath),
121
+ ...(exportPaths.length > 0 ? { export_paths: exportPaths } : {})
104
122
  };
105
123
  }
106
124
  function extractRouteParams(routePath) {
@@ -324,7 +342,9 @@ function segmentWeight(segment) {
324
342
  * @returns {string}
325
343
  */
326
344
  export function serializeManifest(entries) {
327
- const lines = entries.map((e) => {
345
+ const lines = entries
346
+ .filter((entry) => entry?.route_kind !== 'resource')
347
+ .map((e) => {
328
348
  const hasParams = _isDynamic(e.path);
329
349
  const loader = hasParams
330
350
  ? `(params) => import('./pages/${e.file}')`
package/dist/preview.d.ts CHANGED
@@ -37,20 +37,27 @@ export function createPreviewServer(options: {
37
37
  * @returns {Promise<PreviewRoute[]>}
38
38
  */
39
39
  export function loadRouteManifest(distDir: string): Promise<PreviewRoute[]>;
40
+ export function loadRouteSurfaceState(distDir: any, fallbackBasePath?: string): Promise<{
41
+ basePath: string;
42
+ pageRoutes: any;
43
+ resourceRoutes: any[];
44
+ }>;
40
45
  /**
41
- * @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
42
- * @returns {Promise<{ result: { kind: string, [key: string]: unknown }, trace: { guard: string, load: string } }>}
46
+ * @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
47
+ * @returns {Promise<{ result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
43
48
  */
44
- export function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, routePattern, routeFile, routeId, guardOnly }: {
49
+ export function executeServerRoute({ source, sourcePath, params, requestUrl, requestMethod, requestHeaders, requestBodyBuffer, routePattern, routeFile, routeId, routeKind, guardOnly }: {
45
50
  source: string;
46
51
  sourcePath: string;
47
52
  params: Record<string, string>;
48
53
  requestUrl?: string;
49
54
  requestMethod?: string;
50
55
  requestHeaders?: Record<string, string | string[] | undefined>;
56
+ requestBodyBuffer?: Buffer | null;
51
57
  routePattern?: string;
52
58
  routeFile?: string;
53
59
  routeId?: string;
60
+ routeKind?: "page" | "resource";
54
61
  }): Promise<{
55
62
  result: {
56
63
  kind: string;
@@ -58,10 +65,12 @@ export function executeServerRoute({ source, sourcePath, params, requestUrl, req
58
65
  };
59
66
  trace: {
60
67
  guard: string;
68
+ action: string;
61
69
  load: string;
62
70
  };
71
+ status?: number;
72
+ setCookies?: string[];
63
73
  }>;
64
- export function defaultRouteDenyMessage(status: any): "Unauthorized" | "Forbidden" | "Not Found" | "Internal Server Error";
65
74
  /**
66
75
  * @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
67
76
  * @returns {Promise<Record<string, unknown> | null>}