@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,116 +1,5 @@
1
- import { extractDeclaredIdentifiers, normalizeTypeScriptExpression, renderObjectKey, rewriteIdentifiersWithinExpression } from './typescript-expression-utils.js';
2
- /**
3
- * @param {string} value
4
- * @returns {string | null}
5
- */
6
- function deriveScopedIdentifierAlias(value) {
7
- const ident = String(value || '').trim();
8
- if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(ident)) {
9
- return null;
10
- }
11
- const parts = ident.split('_').filter(Boolean);
12
- const candidate = parts.length > 1 ? parts[parts.length - 1] : ident;
13
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(candidate) ? candidate : ident;
14
- }
15
- /**
16
- * @param {Map<string, string>} map
17
- * @param {Set<string>} ambiguous
18
- * @param {string | null} raw
19
- * @param {string | null} rewritten
20
- */
21
- function recordScopedIdentifierRewrite(map, ambiguous, raw, rewritten) {
22
- if (typeof raw !== 'string' || raw.length === 0 || typeof rewritten !== 'string' || rewritten.length === 0) {
23
- return;
24
- }
25
- const existing = map.get(raw);
26
- if (existing && existing !== rewritten) {
27
- map.delete(raw);
28
- ambiguous.add(raw);
29
- return;
30
- }
31
- if (!ambiguous.has(raw)) {
32
- map.set(raw, rewritten);
33
- }
34
- }
35
- /**
36
- * @param {object | null | undefined} ir
37
- * @returns {{ map: Map<string, string>, ambiguous: Set<string> }}
38
- */
39
- export function buildScopedIdentifierRewrite(ir) {
40
- const out = { map: new Map(), ambiguous: new Set() };
41
- if (!ir || typeof ir !== 'object') {
42
- return out;
43
- }
44
- const stateBindings = Array.isArray(ir?.hoisted?.state) ? ir.hoisted.state : [];
45
- for (const stateEntry of stateBindings) {
46
- const key = typeof stateEntry?.key === 'string' ? stateEntry.key : null;
47
- recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(key), key);
48
- }
49
- const functionBindings = Array.isArray(ir?.hoisted?.functions) ? ir.hoisted.functions : [];
50
- for (const fnName of functionBindings) {
51
- if (typeof fnName !== 'string') {
52
- continue;
53
- }
54
- recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(fnName), fnName);
55
- }
56
- const declarations = Array.isArray(ir?.hoisted?.declarations) ? ir.hoisted.declarations : [];
57
- for (const declaration of declarations) {
58
- if (typeof declaration !== 'string') {
59
- continue;
60
- }
61
- for (const identifier of extractDeclaredIdentifiers(declaration)) {
62
- recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(identifier), identifier);
63
- }
64
- }
65
- return out;
66
- }
67
- /**
68
- * @param {string} expr
69
- * @param {{
70
- * expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
71
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
72
- * } | null} rewriteContext
73
- * @returns {string}
74
- */
75
- export function rewritePropsExpression(expr, rewriteContext = null) {
76
- const trimmed = String(expr || '').trim();
77
- if (!trimmed) {
78
- return trimmed;
79
- }
80
- const expressionMap = rewriteContext?.expressionRewrite?.map;
81
- const expressionAmbiguous = rewriteContext?.expressionRewrite?.ambiguous;
82
- if (expressionMap instanceof Map &&
83
- !(expressionAmbiguous instanceof Set && expressionAmbiguous.has(trimmed))) {
84
- const exact = expressionMap.get(trimmed);
85
- if (typeof exact === 'string' && exact.length > 0) {
86
- return normalizeTypeScriptExpression(exact);
87
- }
88
- }
89
- const scopeMap = rewriteContext?.scopeRewrite?.map;
90
- const scopeAmbiguous = rewriteContext?.scopeRewrite?.ambiguous;
91
- const rootMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)([\s\S]*)$/);
92
- if (!(scopeMap instanceof Map)) {
93
- return normalizeTypeScriptExpression(trimmed);
94
- }
95
- if (!rootMatch) {
96
- return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
97
- }
98
- const root = rootMatch[1];
99
- if (scopeAmbiguous instanceof Set && scopeAmbiguous.has(root)) {
100
- return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
101
- }
102
- const rewrittenRoot = scopeMap.get(root);
103
- if (typeof rewrittenRoot !== 'string' || rewrittenRoot.length === 0 || rewrittenRoot === root) {
104
- return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
105
- }
106
- if (rootMatch[2].trim().length === 0) {
107
- return normalizeTypeScriptExpression(rewrittenRoot);
108
- }
109
- const rewrittenExpr = rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
110
- return typeof rewrittenExpr === 'string' && rewrittenExpr.length > 0
111
- ? rewrittenExpr
112
- : normalizeTypeScriptExpression(`${rewrittenRoot}${rootMatch[2]}`);
113
- }
1
+ import { normalizeTypeScriptExpression, renderObjectKey } from './typescript-expression-utils.js';
2
+ import { rewriteCompilerSignalMapReferences } from './compiler-signal-expression.js';
114
3
  /**
115
4
  * @param {string | null | undefined} compiledExpr
116
5
  * @param {{
@@ -126,22 +15,24 @@ export function resolveCompiledPropsExpression(compiledExpr, expressionRewrite =
126
15
  }
127
16
  const signals = Array.isArray(expressionRewrite?.signals) ? expressionRewrite.signals : [];
128
17
  const stateBindings = Array.isArray(expressionRewrite?.stateBindings) ? expressionRewrite.stateBindings : [];
129
- const resolved = source.replace(/signalMap\.get\((\d+)\)(?:\.get\(\))?/g, (full, rawIndex) => {
130
- const signalIndex = Number.parseInt(rawIndex, 10);
131
- if (!Number.isInteger(signalIndex)) {
132
- return full;
133
- }
18
+ return rewriteCompilerSignalMapReferences(source, ({ ts, signalIndex, valueRead }) => {
134
19
  const signal = signals[signalIndex];
135
20
  const stateIndex = signal?.state_index;
136
21
  const stateKey = Number.isInteger(stateIndex) ? stateBindings[stateIndex]?.key : null;
137
22
  if (typeof stateKey !== 'string' || stateKey.length === 0) {
138
- return full;
23
+ return null;
24
+ }
25
+ if (valueRead) {
26
+ return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(stateKey), 'get'), undefined, []);
139
27
  }
140
- return full.endsWith('.get()') ? `${stateKey}.get()` : stateKey;
28
+ return ts.factory.createIdentifier(stateKey);
141
29
  });
142
- return normalizeTypeScriptExpression(resolved);
143
30
  }
144
31
  /**
32
+ * The only allowed downstream props rewrite boundary is compiler-owned exact lookup.
33
+ * If the compiler did not emit a mapping for the attr expression, CLI keeps the
34
+ * original expression text without attempting identifier reinterpretation.
35
+ *
145
36
  * @param {string} expr
146
37
  * @param {{
147
38
  * expressionRewrite?: {
@@ -150,8 +41,7 @@ export function resolveCompiledPropsExpression(compiledExpr, expressionRewrite =
150
41
  * ambiguous?: Set<string>,
151
42
  * signals?: Array<{ state_index?: number }>,
152
43
  * stateBindings?: Array<{ key?: string }>
153
- * } | null,
154
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
44
+ * } | null
155
45
  * } | null} rewriteContext
156
46
  * @returns {string}
157
47
  */
@@ -177,13 +67,18 @@ export function resolvePropsValueCode(expr, rewriteContext = null) {
177
67
  return normalizeTypeScriptExpression(exact);
178
68
  }
179
69
  }
180
- return rewritePropsExpression(trimmed, rewriteContext);
70
+ return normalizeTypeScriptExpression(trimmed);
181
71
  }
182
72
  /**
183
73
  * @param {string} attrs
184
74
  * @param {{
185
- * expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
186
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
75
+ * expressionRewrite?: {
76
+ * map?: Map<string, string>,
77
+ * bindings?: Map<string, { compiled_expr?: string | null }>,
78
+ * ambiguous?: Set<string>,
79
+ * signals?: Array<{ state_index?: number }>,
80
+ * stateBindings?: Array<{ key?: string }>
81
+ * } | null
187
82
  * } | null} rewriteContext
188
83
  * @returns {string}
189
84
  */
@@ -225,8 +120,13 @@ export function renderPropsLiteralFromAttrs(attrs, rewriteContext = null) {
225
120
  * @param {string} source
226
121
  * @param {string} attrs
227
122
  * @param {{
228
- * expressionRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null,
229
- * scopeRewrite?: { map?: Map<string, string>, ambiguous?: Set<string> } | null
123
+ * expressionRewrite?: {
124
+ * map?: Map<string, string>,
125
+ * bindings?: Map<string, { compiled_expr?: string | null }>,
126
+ * ambiguous?: Set<string>,
127
+ * signals?: Array<{ state_index?: number }>,
128
+ * stateBindings?: Array<{ key?: string }>
129
+ * } | null
230
130
  * } | null} rewriteContext
231
131
  * @returns {string}
232
132
  */
@@ -2,7 +2,7 @@
2
2
  * @param {string} source
3
3
  * @param {string} sourceFile
4
4
  * @param {object} [compilerOpts]
5
- * @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, source_path: string } | null }}
5
+ * @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string, export_paths?: string[] } | null }}
6
6
  */
7
7
  export function extractServerScript(source: string, sourceFile: string, compilerOpts?: object): {
8
8
  source: string;
@@ -11,7 +11,9 @@ export function extractServerScript(source: string, sourceFile: string, compiler
11
11
  prerender: boolean;
12
12
  has_guard: boolean;
13
13
  has_load: boolean;
14
+ has_action: boolean;
14
15
  source_path: string;
16
+ export_paths?: string[];
15
17
  } | null;
16
18
  };
17
19
  /**
@@ -1,15 +1,16 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { findNextKnownComponentTag } from '../component-tag-parser.js';
3
+ import { extractStaticExportPaths } from '../static-export-paths.js';
3
4
  /**
4
5
  * @param {string} source
5
6
  * @param {string} sourceFile
6
7
  * @param {object} [compilerOpts]
7
- * @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, source_path: string } | null }}
8
+ * @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string, export_paths?: string[] } | null }}
8
9
  */
9
10
  export function extractServerScript(source, sourceFile, compilerOpts = {}) {
10
11
  const scriptRe = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
11
12
  const serverMatches = [];
12
- const reservedServerExportRe = /\bexport\s+const\s+(?:data|prerender|guard|load|ssr_data|props|ssr)\b|\bexport\s+(?:async\s+)?function\s+(?:load|guard)\s*\(|\bexport\s+const\s+(?:load|guard)\s*=/;
13
+ const reservedServerExportRe = /\bexport\s+const\s+(?:data|prerender|guard|load|action|ssr_data|props|ssr)\b|\bexport\s+(?:async\s+)?function\s+(?:load|guard|action)\s*\(|\bexport\s+const\s+(?:load|guard|action)\s*=/;
13
14
  for (const match of source.matchAll(scriptRe)) {
14
15
  const attrs = String(match[1] || '');
15
16
  const body = String(match[2] || '');
@@ -17,7 +18,7 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
17
18
  if (!isServer && reservedServerExportRe.test(body)) {
18
19
  throw new Error(`Zenith server script contract violation:\n` +
19
20
  ` File: ${sourceFile}\n` +
20
- ` Reason: guard/load/data exports are only allowed in <script server lang="ts"> or adjacent .guard.ts / .load.ts files\n` +
21
+ ` Reason: guard/load/action/data exports are only allowed in <script server lang="ts"> or adjacent .guard.ts / .load.ts / .action.ts files\n` +
21
22
  ` Example: move the export into <script server lang="ts">`);
22
23
  }
23
24
  if (isServer) {
@@ -81,6 +82,19 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
81
82
  ` Reason: multiple guard exports detected\n` +
82
83
  ` Example: keep exactly one export const guard = async (ctx) => ({ ... })`);
83
84
  }
85
+ const actionFnMatch = serverSource.match(/\bexport\s+(?:async\s+)?function\s+action\s*\(([^)]*)\)/);
86
+ const actionConstParenMatch = serverSource.match(/\bexport\s+const\s+action\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>/);
87
+ const actionConstSingleArgMatch = serverSource.match(/\bexport\s+const\s+action\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
88
+ const hasAction = Boolean(actionFnMatch || actionConstParenMatch || actionConstSingleArgMatch);
89
+ const actionMatchCount = Number(Boolean(actionFnMatch)) +
90
+ Number(Boolean(actionConstParenMatch)) +
91
+ Number(Boolean(actionConstSingleArgMatch));
92
+ if (actionMatchCount > 1) {
93
+ throw new Error(`Zenith server script contract violation:\n` +
94
+ ` File: ${sourceFile}\n` +
95
+ ` Reason: multiple action exports detected\n` +
96
+ ` Example: keep exactly one export const action = async (ctx) => ({ ... })`);
97
+ }
84
98
  const hasData = /\bexport\s+const\s+data\b/.test(serverSource);
85
99
  const hasSsrData = /\bexport\s+const\s+ssr_data\b/.test(serverSource);
86
100
  const hasSsr = /\bexport\s+const\s+ssr\b/.test(serverSource);
@@ -119,6 +133,17 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
119
133
  ` Example: export const guard = async (ctx) => ({ ... })`);
120
134
  }
121
135
  }
136
+ if (hasAction) {
137
+ const singleArg = String(actionConstSingleArgMatch?.[1] || '').trim();
138
+ const paramsText = String((actionFnMatch || actionConstParenMatch)?.[1] || '').trim();
139
+ const arity = singleArg ? 1 : paramsText.length === 0 ? 0 : paramsText.split(',').length;
140
+ if (arity !== 1) {
141
+ throw new Error(`Zenith server script contract violation:\n` +
142
+ ` File: ${sourceFile}\n` +
143
+ ` Reason: action(ctx) must accept exactly one argument\n` +
144
+ ` Example: export const action = async (ctx) => ({ ... })`);
145
+ }
146
+ }
122
147
  const prerenderMatch = serverSource.match(/\bexport\s+const\s+prerender\s*=\s*([^\n;]+)/);
123
148
  let prerender = false;
124
149
  if (prerenderMatch) {
@@ -131,6 +156,7 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
131
156
  }
132
157
  prerender = rawValue.startsWith('true');
133
158
  }
159
+ const exportPaths = extractStaticExportPaths(serverSource, sourceFile) || [];
134
160
  const start = match.index ?? -1;
135
161
  if (start < 0) {
136
162
  return {
@@ -140,7 +166,9 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
140
166
  prerender,
141
167
  has_guard: hasGuard,
142
168
  has_load: hasLoad,
143
- source_path: sourceFile
169
+ has_action: hasAction,
170
+ source_path: sourceFile,
171
+ export_paths: exportPaths
144
172
  }
145
173
  };
146
174
  }
@@ -153,7 +181,9 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
153
181
  prerender,
154
182
  has_guard: hasGuard,
155
183
  has_load: hasLoad,
156
- source_path: sourceFile
184
+ has_action: hasAction,
185
+ source_path: sourceFile,
186
+ export_paths: exportPaths
157
187
  }
158
188
  };
159
189
  }
@@ -11,14 +11,15 @@ export function writeBuildOutputManifest({ coreOutputDir, staticDir, target, rou
11
11
  base_path: string;
12
12
  content_hash: any;
13
13
  routes: {
14
+ html: any;
15
+ assets: any[];
16
+ export_paths?: any[] | undefined;
14
17
  path: any;
15
18
  file: any;
16
19
  path_kind: any;
17
20
  render_mode: any;
18
21
  requires_hydration: boolean;
19
22
  params: any[];
20
- html: any;
21
- assets: any[];
22
23
  }[];
23
24
  assets: {
24
25
  js: any[];
@@ -67,6 +67,9 @@ export async function writeBuildOutputManifest({ coreOutputDir, staticDir, targe
67
67
  render_mode: entry.render_mode,
68
68
  requires_hydration: /<script\b[^>]*type="module"/i.test(html),
69
69
  params: [...entry.params],
70
+ ...(Array.isArray(entry.export_paths) && entry.export_paths.length > 0
71
+ ? { export_paths: [...entry.export_paths] }
72
+ : {}),
70
73
  html: htmlPath,
71
74
  assets
72
75
  });
package/dist/build.js CHANGED
@@ -1,24 +1,25 @@
1
- import { mkdir, rm } from 'node:fs/promises';
1
+ import { cp, mkdir, rm } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { resolveBuildAdapter } from './adapters/resolve-adapter.js';
4
4
  import { normalizeBasePath } from './base-path.js';
5
5
  import { rewriteSoftNavigationHrefBasePathInHtmlFiles } from './base-path-html.js';
6
6
  import { writeBuildOutputManifest } from './build-output-manifest.js';
7
7
  import { generateManifest } from './manifest.js';
8
+ import { writeResourceRouteManifest } from './resource-manifest.js';
8
9
  import { buildComponentRegistry } from './resolve-components.js';
9
10
  import { collectAssets, createCompilerWarningEmitter, runBundler } from './build/compiler-runtime.js';
10
11
  import { buildPageEnvelopes } from './build/page-loop.js';
11
12
  import { deriveProjectRootFromPagesDir, ensureZenithTypeDeclarations } from './build/type-declarations.js';
12
- import { materializeImageMarkupInHtmlFiles } from './images/materialize.js';
13
13
  import { buildImageArtifacts } from './images/service.js';
14
+ import { injectImageMaterializationIntoRouterManifest } from './images/router-manifest.js';
14
15
  import { createImageRuntimePayload, injectImageRuntimePayloadIntoHtmlFiles } from './images/payload.js';
16
+ import { supportsTargetRouteCheck } from './route-check-support.js';
15
17
  import { createStartupProfiler } from './startup-profile.js';
16
18
  import { writeServerOutput } from './server-output.js';
17
19
  import { resolveBundlerBin } from './toolchain-paths.js';
18
20
  import { createBundlerToolchain, createCompilerToolchain, ensureToolchainCompatibility, getActiveToolchainCandidate } from './toolchain-runner.js';
19
21
  import { maybeWarnAboutZenithVersionMismatch } from './version-check.js';
20
22
  export { createCompilerWarningEmitter };
21
- const RUNTIME_MARKUP_BINDING = '__ZENITH_INTERNAL_ZENHTML';
22
23
  function createCompilerTotals() {
23
24
  return {
24
25
  pageMs: 0,
@@ -49,12 +50,14 @@ export async function build(options) {
49
50
  const projectRoot = deriveProjectRootFromPagesDir(pagesDir);
50
51
  const coreOutputDir = join(projectRoot, '.zenith-output');
51
52
  const staticOutputDir = join(coreOutputDir, 'static');
53
+ const imageStageDir = join(coreOutputDir, 'image-materialization-stage');
52
54
  const srcDir = resolve(pagesDir, '..');
53
55
  const compilerBin = createCompilerToolchain({ projectRoot, logger });
54
56
  const bundlerBin = createBundlerToolchain({ projectRoot, logger });
55
57
  const compilerTotals = createCompilerTotals();
56
58
  const { target, adapter, mode } = resolveBuildAdapter(config);
57
59
  const basePath = normalizeBasePath(config.basePath || '/');
60
+ const routeCheckEnabled = supportsTargetRouteCheck(target);
58
61
  const routerEnabled = config.router === true;
59
62
  const compilerOpts = {
60
63
  typescriptDefault: config.typescriptDefault === true,
@@ -72,13 +75,13 @@ export async function build(options) {
72
75
  }));
73
76
  }
74
77
  const registry = startupProfile.measureSync('build_component_registry', () => buildComponentRegistry(srcDir));
75
- void RUNTIME_MARKUP_BINDING;
76
78
  const manifest = await startupProfile.measureAsync('generate_manifest', () => generateManifest(pagesDir, '.zen', { compilerOpts }));
79
+ const pageManifest = manifest.filter((entry) => entry?.route_kind !== 'resource');
77
80
  if (mode !== 'legacy') {
78
81
  adapter.validateRoutes(manifest);
79
82
  }
80
83
  await startupProfile.measureAsync('ensure_zenith_type_declarations', () => ensureZenithTypeDeclarations({
81
- manifest,
84
+ manifest: pageManifest,
82
85
  pagesDir
83
86
  }));
84
87
  await startupProfile.measureAsync('reset_core_output', () => rm(coreOutputDir, { recursive: true, force: true }));
@@ -91,7 +94,7 @@ export async function build(options) {
91
94
  console.warn(line);
92
95
  });
93
96
  const { envelopes, expressionRewriteMetrics } = await buildPageEnvelopes({
94
- manifest,
97
+ manifest: pageManifest,
95
98
  pagesDir,
96
99
  srcDir,
97
100
  registry,
@@ -102,26 +105,37 @@ export async function build(options) {
102
105
  compilerTotals,
103
106
  emitCompilerWarning
104
107
  });
105
- if (envelopes.length > 0) {
106
- await startupProfile.measureAsync('run_bundler', () => runBundler(envelopes, staticOutputDir, projectRoot, logger, showBundlerInfo, bundlerBin, { basePath }), { envelopes: envelopes.length });
107
- }
108
- await startupProfile.measureAsync('rewrite_soft_navigation_base_path', () => rewriteSoftNavigationHrefBasePathInHtmlFiles(staticOutputDir, basePath));
109
108
  const { manifest: imageManifest } = await startupProfile.measureAsync('build_image_artifacts', () => buildImageArtifacts({
110
109
  projectRoot,
111
- outDir: staticOutputDir,
110
+ outDir: imageStageDir,
112
111
  config: config.images
113
112
  }));
114
113
  const imageRuntimePayload = createImageRuntimePayload(config.images, imageManifest, 'passthrough', basePath);
115
- await startupProfile.measureAsync('materialize_image_markup', () => materializeImageMarkupInHtmlFiles({
116
- distDir: staticOutputDir,
117
- payload: imageRuntimePayload
118
- }));
114
+ if (envelopes.length > 0) {
115
+ await startupProfile.measureAsync('run_bundler', () => runBundler(envelopes, staticOutputDir, projectRoot, logger, showBundlerInfo, bundlerBin, {
116
+ basePath,
117
+ routeCheck: routeCheckEnabled,
118
+ imageRuntimePayload
119
+ }), { envelopes: envelopes.length });
120
+ await startupProfile.measureAsync('inject_image_materialization_manifest', () => injectImageMaterializationIntoRouterManifest(staticOutputDir, envelopes), { envelopes: envelopes.length });
121
+ }
122
+ await startupProfile.measureAsync('write_resource_manifest', () => writeResourceRouteManifest(staticOutputDir, manifest, basePath));
123
+ await startupProfile.measureAsync('rewrite_soft_navigation_base_path', () => rewriteSoftNavigationHrefBasePathInHtmlFiles(staticOutputDir, basePath));
124
+ await startupProfile.measureAsync('stage_image_artifacts_into_static_output', async () => {
125
+ if (Object.keys(imageManifest).length === 0) {
126
+ return;
127
+ }
128
+ await cp(join(imageStageDir, '_zenith'), join(staticOutputDir, '_zenith'), {
129
+ recursive: true,
130
+ force: true
131
+ });
132
+ });
119
133
  await startupProfile.measureAsync('inject_image_runtime_payload', () => injectImageRuntimePayloadIntoHtmlFiles(staticOutputDir, imageRuntimePayload));
120
134
  const buildManifest = await startupProfile.measureAsync('write_core_manifest', () => writeBuildOutputManifest({
121
135
  coreOutputDir,
122
136
  staticDir: staticOutputDir,
123
137
  target,
124
- routeManifest: manifest,
138
+ routeManifest: pageManifest,
125
139
  basePath
126
140
  }));
127
141
  await startupProfile.measureAsync('write_server_output', () => writeServerOutput({
@@ -134,11 +148,11 @@ export async function build(options) {
134
148
  await startupProfile.measureAsync('adapt_output', () => adapter.adapt({ coreOutput: coreOutputDir, outDir, manifest: buildManifest, config }));
135
149
  const assets = await startupProfile.measureAsync('collect_assets', () => collectAssets(outDir));
136
150
  startupProfile.emit('build_complete', {
137
- pages: manifest.length,
151
+ pages: pageManifest.length,
138
152
  assets: assets.length,
139
153
  target,
140
154
  compilerTotals,
141
155
  expressionRewriteMetrics
142
156
  });
143
- return { pages: manifest.length, assets };
157
+ return { pages: pageManifest.length, assets };
144
158
  }