@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
@@ -1,4 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
+ import { dirname } from 'node:path';
2
3
  import { pathToFileURL } from 'node:url';
3
4
  import { attachRouteAuth } from '../auth/route-auth.js';
4
5
  import { appLocalRedirectLocation, normalizeBasePath, prependBasePath } from '../base-path.js';
@@ -6,9 +7,27 @@ import { createImageRuntimePayload, injectImageRuntimePayload } from '../images/
6
7
  import { materializeImageMarkup } from '../images/materialize.js';
7
8
  import { buildResourceResponseDescriptor } from '../resource-response.js';
8
9
  import { clientFacingRouteMessage, defaultRouteDenyMessage, logServerException } from '../server-error.js';
9
- import { allow, data, deny, download, invalid, json, redirect, resolveRouteResult, text } from '../server-contract.js';
10
+ import { allow, data, deny, download, invalid, json, redirect, text } from '../server-contract.js';
11
+ import { executeMatchedRoutePipeline } from '../server-contract/resolve.js';
10
12
  const MODULE_CACHE = new Map();
13
+ const GLOBAL_MIDDLEWARE_MODULE_CACHE = new Map();
11
14
  const INTERNAL_QUERY_PREFIX = '__zenith_param_';
15
+ const SCOPED_ROUTE_FIELD = ['scoped', 'server', 'data'].join('_');
16
+ const HAS_SCOPED_ROUTE_FIELD = ['has', SCOPED_ROUTE_FIELD].join('_');
17
+ let SCOPED_SERVER_DATA_RUNTIME_PROMISE = null;
18
+ function hasRouteScopedServerData(route) {
19
+ return route?.route_kind !== 'resource' &&
20
+ route?.prerender !== true &&
21
+ route?.[HAS_SCOPED_ROUTE_FIELD] === true &&
22
+ Array.isArray(route?.[SCOPED_ROUTE_FIELD]) &&
23
+ route[SCOPED_ROUTE_FIELD].length > 0;
24
+ }
25
+ async function loadScopedServerDataRuntime() {
26
+ if (!SCOPED_SERVER_DATA_RUNTIME_PROMISE) {
27
+ SCOPED_SERVER_DATA_RUNTIME_PROMISE = import('../scoped-server-data/runtime.js');
28
+ }
29
+ return SCOPED_SERVER_DATA_RUNTIME_PROMISE;
30
+ }
12
31
  function parseCookies(rawCookieHeader) {
13
32
  const out = Object.create(null);
14
33
  const raw = String(rawCookieHeader || '');
@@ -159,6 +178,22 @@ async function loadRouteExports(routeModulePath) {
159
178
  MODULE_CACHE.set(cacheKey, value);
160
179
  return value;
161
180
  }
181
+ async function loadGlobalMiddleware(globalMiddlewareModulePath) {
182
+ if (!globalMiddlewareModulePath) {
183
+ return null;
184
+ }
185
+ const cacheKey = globalMiddlewareModulePath;
186
+ if (GLOBAL_MIDDLEWARE_MODULE_CACHE.has(cacheKey)) {
187
+ return GLOBAL_MIDDLEWARE_MODULE_CACHE.get(cacheKey);
188
+ }
189
+ const mod = await import(pathToFileURL(globalMiddlewareModulePath).href);
190
+ const middlewareFn = mod?.default;
191
+ if (typeof middlewareFn !== 'function') {
192
+ throw new Error('[Zenith:Middleware] Compiled global middleware module must default export a function.');
193
+ }
194
+ GLOBAL_MIDDLEWARE_MODULE_CACHE.set(cacheKey, middlewareFn);
195
+ return middlewareFn;
196
+ }
162
197
  function createRouteContext({ request, route, params, publicUrl, guardOnly = false }) {
163
198
  const requestHeaders = Object.fromEntries(request.headers.entries());
164
199
  const ctx = {
@@ -198,24 +233,28 @@ function createRouteContext({ request, route, params, publicUrl, guardOnly = fal
198
233
  * route: { path: string, params?: string[], route_id?: string | null, server_script_path?: string | null, file?: string | null },
199
234
  * params: Record<string, string>,
200
235
  * routeModulePath: string,
236
+ * globalMiddlewareModulePath?: string | null,
201
237
  * guardOnly?: boolean
202
238
  * }} options
203
- * @returns {Promise<{ publicUrl: URL, result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
239
+ * @returns {Promise<{ publicUrl: URL, ctx: object, result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
204
240
  */
205
241
  export async function executeRouteRequest(options) {
206
- const { request, route, params, routeModulePath, guardOnly = false } = options;
242
+ const { request, route, params, routeModulePath, globalMiddlewareModulePath = null, guardOnly = false } = options;
207
243
  const publicUrl = buildPublicUrl(request.url, route, params);
208
244
  const ctx = createRouteContext({ request, route, params, publicUrl, guardOnly });
209
245
  const exports = await loadRouteExports(routeModulePath);
210
- const resolved = await resolveRouteResult({
246
+ const globalMiddleware = await loadGlobalMiddleware(globalMiddlewareModulePath);
247
+ const resolved = await executeMatchedRoutePipeline({
211
248
  exports,
212
249
  ctx,
213
250
  filePath: route.file || route.server_script_path || route.path,
214
251
  guardOnly,
215
- routeKind: route.route_kind === 'resource' ? 'resource' : 'page'
252
+ routeKind: route.route_kind === 'resource' ? 'resource' : 'page',
253
+ globalMiddleware
216
254
  });
217
255
  return {
218
256
  publicUrl,
257
+ ctx,
219
258
  result: resolved.result,
220
259
  trace: resolved.trace,
221
260
  status: resolved.status,
@@ -228,20 +267,23 @@ export async function executeRouteRequest(options) {
228
267
  * route: { path: string, params?: string[], route_id?: string | null, server_script_path?: string | null, file?: string | null },
229
268
  * params: Record<string, string>,
230
269
  * routeModulePath: string,
270
+ * globalMiddlewareModulePath?: string | null,
231
271
  * shellHtmlPath: string,
232
272
  * imageManifestPath?: string | null,
233
- * imageConfig?: Record<string, unknown>
273
+ * imageConfig?: Record<string, unknown>,
274
+ * scopedModuleBaseDir?: string | null
234
275
  * }} options
235
276
  * @returns {Promise<Response>}
236
277
  */
237
278
  export async function renderRouteRequest(options) {
238
- const { request, route, params, routeModulePath, shellHtmlPath, imageManifestPath = null, imageConfig = {} } = options;
279
+ const { request, route, params, routeModulePath, globalMiddlewareModulePath = null, shellHtmlPath, imageManifestPath = null, imageConfig = {}, scopedModuleBaseDir = null } = options;
239
280
  try {
240
- const { publicUrl, result, status, setCookies = [] } = await executeRouteRequest({
281
+ const { ctx, result, status, setCookies = [] } = await executeRouteRequest({
241
282
  request,
242
283
  route,
243
284
  params,
244
- routeModulePath
285
+ routeModulePath,
286
+ globalMiddlewareModulePath
245
287
  });
246
288
  if (result.kind === 'redirect') {
247
289
  const headers = new Headers({
@@ -261,6 +303,16 @@ export async function renderRouteRequest(options) {
261
303
  const ssrPayload = result.kind === 'data' && result.data && typeof result.data === 'object' && !Array.isArray(result.data)
262
304
  ? result.data
263
305
  : {};
306
+ let finalSsrPayload = ssrPayload;
307
+ if (hasRouteScopedServerData(route)) {
308
+ const scopedRuntime = await loadScopedServerDataRuntime();
309
+ const scopedPayload = await scopedRuntime.executeScopedServerData({
310
+ route,
311
+ ctx,
312
+ serverDir: scopedModuleBaseDir || dirname(dirname(dirname(dirname(routeModulePath))))
313
+ });
314
+ finalSsrPayload = scopedRuntime.mergeScopedSsrPayload(ssrPayload, scopedPayload);
315
+ }
264
316
  const localImages = await loadImageManifest(imageManifestPath);
265
317
  const imagePayload = createImageRuntimePayload(imageConfig, localImages, 'passthrough', route.base_path || '/');
266
318
  let html = await readFile(shellHtmlPath, 'utf8');
@@ -271,7 +323,7 @@ export async function renderRouteRequest(options) {
271
323
  ? route.image_materialization
272
324
  : []
273
325
  });
274
- html = injectSsrPayload(html, ssrPayload);
326
+ html = injectSsrPayload(html, finalSsrPayload);
275
327
  html = injectImageRuntimePayload(html, imagePayload);
276
328
  const headers = new Headers({
277
329
  'Content-Type': 'text/html; charset=utf-8'
@@ -292,18 +344,20 @@ export async function renderRouteRequest(options) {
292
344
  * request: Request,
293
345
  * route: { path: string, params?: string[], route_id?: string | null, server_script_path?: string | null, file?: string | null, route_kind?: string | null, base_path?: string | null },
294
346
  * params: Record<string, string>,
295
- * routeModulePath: string
347
+ * routeModulePath: string,
348
+ * globalMiddlewareModulePath?: string | null
296
349
  * }} options
297
350
  * @returns {Promise<Response>}
298
351
  */
299
352
  export async function renderResourceRouteRequest(options) {
300
- const { request, route, params, routeModulePath } = options;
353
+ const { request, route, params, routeModulePath, globalMiddlewareModulePath = null } = options;
301
354
  try {
302
355
  const { result, setCookies = [] } = await executeRouteRequest({
303
356
  request,
304
357
  route: { ...route, route_kind: 'resource' },
305
358
  params,
306
- routeModulePath
359
+ routeModulePath,
360
+ globalMiddlewareModulePath
307
361
  });
308
362
  return createResourceResponse(result, route.base_path || '/', setCookies);
309
363
  }
@@ -1,54 +1,12 @@
1
1
  import { writeFile, mkdir } from 'node:fs/promises';
2
2
  import { join, dirname } from 'node:path';
3
+ import { renderZenithEnvDts } from './zenith-env-dts.js';
3
4
  /**
4
5
  * @param {string} projectRoot
5
6
  * @returns {Promise<void>}
6
7
  */
7
8
  export async function generateEnvDts(projectRoot) {
8
- const content = `// .zenith/zenith-env.d.ts
9
- // Auto-generated by Zenith. Do not edit.
10
-
11
- export {};
12
-
13
- declare global {
14
- namespace Zenith {
15
- type Params = Record<string, string>;
16
-
17
- interface LoadContext {
18
- params: Params;
19
- url: URL;
20
- request: Request;
21
- route: {
22
- id: string;
23
- file: string;
24
- pattern: string;
25
- };
26
- }
27
-
28
- interface ErrorState {
29
- status?: number;
30
- code?: string;
31
- message: string;
32
- }
33
-
34
- type PageData = Record<string, unknown> & {
35
- __zenith_error?: ErrorState;
36
- };
37
-
38
- type Load<T extends PageData = PageData> = (ctx: LoadContext) => Promise<T> | T;
39
-
40
- type Renderable =
41
- | string
42
- | number
43
- | boolean
44
- | null
45
- | undefined
46
- | Renderable[]
47
- | { __zenith_fragment: true };
48
- }
49
- }
50
- `;
51
9
  const outPath = join(projectRoot, '.zenith', 'zenith-env.d.ts');
52
10
  await mkdir(dirname(outPath), { recursive: true });
53
- await writeFile(outPath, content, 'utf8');
11
+ await writeFile(outPath, renderZenithEnvDts(), 'utf8');
54
12
  }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @returns {string}
3
+ */
4
+ export function renderZenithEnvDts(): string;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @returns {string}
3
+ */
4
+ export function renderZenithEnvDts() {
5
+ return [
6
+ '// Auto-generated by Zenith CLI. Do not edit manually.',
7
+ 'export {};',
8
+ '',
9
+ 'declare global {',
10
+ ' namespace Zenith {',
11
+ ' type Params = Record<string, string>;',
12
+ '',
13
+ ' interface ErrorState {',
14
+ ' status?: number;',
15
+ ' code?: string;',
16
+ ' message: string;',
17
+ ' }',
18
+ '',
19
+ ' type PageData = Record<string, unknown> & { __zenith_error?: ErrorState };',
20
+ '',
21
+ ' interface RouteMeta {',
22
+ ' id: string;',
23
+ ' file: string;',
24
+ ' pattern: string;',
25
+ ' }',
26
+ '',
27
+ ' type RouteSession = Record<string, unknown>;',
28
+ '',
29
+ ' type RequireSessionOptions =',
30
+ ' | { redirectTo: string; status?: 302 | 303 | 307 }',
31
+ ' | { deny: 401 | 403 | 404; message?: string };',
32
+ '',
33
+ ' type ActionState =',
34
+ ' | null',
35
+ ' | { ok: true; status: 200; data: unknown }',
36
+ ' | { ok: false; status: 400 | 422; data: unknown };',
37
+ '',
38
+ ' type AllowResult = { kind: "allow" };',
39
+ ' type RedirectResult = { kind: "redirect"; location: string; status: number };',
40
+ ' type DenyResult = { kind: "deny"; status: 401 | 403 | 404; message?: string };',
41
+ ' type DataResult<T extends PageData = PageData> = { kind: "data"; data: T };',
42
+ ' type InvalidResult<T = unknown> = { kind: "invalid"; data: T; status: 400 | 422 };',
43
+ ' type JsonResult<T = unknown> = { kind: "json"; data: T; status: number };',
44
+ ' type TextResult = { kind: "text"; body: string; status: number };',
45
+ ' type RouteDownloadBody = string | Uint8Array | ArrayBuffer;',
46
+ ' type RouteDownloadOptions = { filename: string; contentType?: string };',
47
+ ' type DownloadResult = { kind: "download"; body: string; bodyEncoding: "utf8" | "base64"; bodySize: number; filename: string; contentType: string; status: 200 };',
48
+ '',
49
+ ' interface LoadContext {',
50
+ ' params: Params;',
51
+ ' url: URL;',
52
+ ' headers: Record<string, string>;',
53
+ ' cookies: Record<string, string>;',
54
+ ' request: Request;',
55
+ ' method: string;',
56
+ ' route: RouteMeta;',
57
+ ' env: Record<string, unknown>;',
58
+ ' action: ActionState;',
59
+ ' auth: {',
60
+ ' getSession(): Promise<RouteSession | null>;',
61
+ ' requireSession(options: RequireSessionOptions): Promise<RouteSession>;',
62
+ ' signIn(sessionObject: RouteSession): Promise<void>;',
63
+ ' signOut(): Promise<void>;',
64
+ ' };',
65
+ ' allow(): AllowResult;',
66
+ ' redirect(location: string, status?: number): RedirectResult;',
67
+ ' deny(status: 401 | 403 | 404, message?: string): DenyResult;',
68
+ ' data<T extends PageData = PageData>(payload: T): DataResult<T>;',
69
+ ' invalid<T = unknown>(payload: T, status?: 400 | 422): InvalidResult<T>;',
70
+ ' json<T = unknown>(payload: T, status?: number): JsonResult<T>;',
71
+ ' text(body: string, status?: number): TextResult;',
72
+ ' download(body: RouteDownloadBody, options: RouteDownloadOptions): DownloadResult;',
73
+ ' }',
74
+ '',
75
+ ' type PageRouteResult<T extends PageData = PageData> = T | DataResult<T> | RedirectResult | DenyResult;',
76
+ ' type Load<T extends PageData = PageData> = (ctx: LoadContext) => Promise<PageRouteResult<T>> | PageRouteResult<T>;',
77
+ '',
78
+ ' interface Fragment {',
79
+ ' __zenith_fragment: true;',
80
+ ' mount: (anchor: Node | null) => void;',
81
+ ' unmount: () => void;',
82
+ ' }',
83
+ '',
84
+ ' type Renderable =',
85
+ ' | string',
86
+ ' | number',
87
+ ' | boolean',
88
+ ' | null',
89
+ ' | undefined',
90
+ ' | Renderable[]',
91
+ ' | Fragment;',
92
+ ' }',
93
+ '}',
94
+ ''
95
+ ].join('\n');
96
+ }
package/package.json CHANGED
@@ -1,13 +1,10 @@
1
1
  {
2
2
  "name": "@zenithbuild/cli",
3
- "version": "0.7.10",
3
+ "version": "0.7.12",
4
4
  "description": "Deterministic project orchestrator for Zenith framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
- "bin": {
9
- "zenith": "./dist/index.js"
10
- },
11
8
  "exports": {
12
9
  ".": "./dist/index.js"
13
10
  },
@@ -38,8 +35,8 @@
38
35
  "prepublishOnly": "npm run build"
39
36
  },
40
37
  "dependencies": {
41
- "@zenithbuild/compiler": "0.7.10",
42
- "@zenithbuild/bundler": "0.7.10",
38
+ "@zenithbuild/compiler": "0.7.12",
39
+ "@zenithbuild/bundler": "0.7.12",
43
40
  "picocolors": "^1.1.1",
44
41
  "sharp": "^0.34.4"
45
42
  },