@zenithbuild/cli 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +14 -11
  2. package/dist/adapters/adapter-netlify.js +1 -0
  3. package/dist/adapters/adapter-node.js +8 -0
  4. package/dist/adapters/adapter-vercel.js +1 -0
  5. package/dist/build/compiler-runtime.d.ts +10 -9
  6. package/dist/build/compiler-runtime.js +51 -1
  7. package/dist/build/compiler-signal-expression.d.ts +1 -0
  8. package/dist/build/compiler-signal-expression.js +155 -0
  9. package/dist/build/expression-rewrites.d.ts +1 -6
  10. package/dist/build/expression-rewrites.js +61 -65
  11. package/dist/build/page-component-loop.d.ts +3 -13
  12. package/dist/build/page-component-loop.js +21 -46
  13. package/dist/build/page-ir-normalization.d.ts +0 -8
  14. package/dist/build/page-ir-normalization.js +13 -234
  15. package/dist/build/page-loop-state.d.ts +6 -9
  16. package/dist/build/page-loop-state.js +9 -8
  17. package/dist/build/page-loop.js +27 -22
  18. package/dist/build/scoped-identifier-rewrite.d.ts +37 -44
  19. package/dist/build/scoped-identifier-rewrite.js +28 -128
  20. package/dist/build/server-script.d.ts +2 -1
  21. package/dist/build/server-script.js +29 -3
  22. package/dist/build.js +5 -3
  23. package/dist/component-instance-ir.js +158 -52
  24. package/dist/dev-build-session.js +20 -6
  25. package/dist/dev-server.js +82 -39
  26. package/dist/framework-components/Image.zen +1 -1
  27. package/dist/images/materialization-plan.d.ts +1 -0
  28. package/dist/images/materialization-plan.js +6 -0
  29. package/dist/images/materialize.d.ts +5 -3
  30. package/dist/images/materialize.js +24 -109
  31. package/dist/images/router-manifest.d.ts +1 -0
  32. package/dist/images/router-manifest.js +49 -0
  33. package/dist/index.js +8 -2
  34. package/dist/manifest.js +3 -2
  35. package/dist/preview.d.ts +4 -3
  36. package/dist/preview.js +87 -53
  37. package/dist/request-body.d.ts +2 -0
  38. package/dist/request-body.js +13 -0
  39. package/dist/request-origin.d.ts +2 -0
  40. package/dist/request-origin.js +45 -0
  41. package/dist/route-check-support.d.ts +1 -0
  42. package/dist/route-check-support.js +4 -0
  43. package/dist/server-contract.d.ts +15 -0
  44. package/dist/server-contract.js +102 -32
  45. package/dist/server-error.d.ts +4 -0
  46. package/dist/server-error.js +34 -0
  47. package/dist/server-output.d.ts +2 -0
  48. package/dist/server-output.js +13 -0
  49. package/dist/server-runtime/node-server.js +33 -27
  50. package/dist/server-runtime/route-render.d.ts +3 -3
  51. package/dist/server-runtime/route-render.js +20 -31
  52. package/dist/server-script-composition.d.ts +11 -5
  53. package/dist/server-script-composition.js +25 -10
  54. package/package.json +6 -3
package/README.md CHANGED
@@ -2,26 +2,30 @@
2
2
 
3
3
  > **⚠️ Internal API:** This package is an internal implementation detail of the Zenith framework. It is not intended for public use and its API may break without warning. Please use `@zenithbuild/core` instead.
4
4
 
5
-
6
5
  The command-line interface for developing and building Zenith applications.
7
6
 
8
7
  ## Canonical Docs
9
8
 
10
- - CLI contract: `../zenith-docs/documentation/cli-contract.md`
11
- - Deployment targets guide: `/Users/judahsullivan/Personal/zenithbuild-monorepo/docs/documentation/guides/deployment-targets.md`
12
- - Script server/data contract: `../zenith-docs/documentation/contracts/server-data.md`
9
+ - CLI contract: `../../docs/documentation/cli-contract.md`
10
+ - Deployment targets guide: `../../docs/documentation/guides/deployment-targets.md`
11
+ - Route protection: `../../docs/documentation/routing/route-protection.md`
13
12
  - Server output contract: `./SERVER_OUTPUT_CONTRACT.md`
14
13
 
15
14
  ## Overview
16
15
 
17
- `@zenithbuild/cli` provides the toolchain needed to manage Zenith projects. While `create-zenith` is for scaffolding, this CLI is for the daily development loop: serving apps, building for production, and managing plugins.
16
+ `@zenithbuild/cli` is Zenith's deterministic project orchestrator. It owns the daily development loop:
17
+
18
+ - `zenith dev`
19
+ - `zenith build`
20
+ - `zenith preview`
21
+
22
+ It does not ship a public plugin-management surface.
18
23
 
19
24
  ## Features
20
25
 
21
26
  - **Dev Server**: Instant HMR (Hot Module Replacement) powered by Bun.
22
- - **Build System**: optimized production bundling.
23
- - **Plugin Management**: Easily add and remove Zenith plugins.
24
- - **Preview**: Test your production builds locally.
27
+ - **Build System**: deterministic build output and adapter packaging.
28
+ - **Preview**: target-aware verification of built output.
25
29
 
26
30
  ## Config Baseline
27
31
 
@@ -78,6 +82,8 @@ Current limitations:
78
82
 
79
83
  - There is no separate `assetPrefix` knob. Assets intentionally follow `basePath`.
80
84
  - `vercel` and `netlify` do not yet emit a deployed `/_zenith/image` endpoint. The `node` target does.
85
+ - Image materialization is route-artifact-driven. Build, preview, and server render consume structured `image_materialization` metadata instead of executing page assets, and dynamic image props are currently unsupported until the compiler emits a dedicated image-props artifact.
86
+ - There is no shipped plugin install/remove command surface in this CLI.
81
87
 
82
88
  ## Commands
83
89
 
@@ -90,9 +96,6 @@ Compiles and bundles your application for production.
90
96
  ### `zenith preview`
91
97
  Previews the locally built target contract for verification. Static targets serve built files; `target: 'node'` boots the built Node artifact.
92
98
 
93
- ### `zenith add <plugin>`
94
- Installs and configures a Zenith plugin.
95
-
96
99
  ## Installation
97
100
 
98
101
  Typically installed as a dev dependency in your Zenith project:
@@ -110,6 +110,7 @@ export const netlifyAdapter = {
110
110
  await cp(join(options.coreOutput, 'server', 'images'), join(functionsDir, '_zenith', 'images'), { recursive: true, force: true });
111
111
  await cp(join(options.coreOutput, 'server', 'base-path.js'), join(functionsDir, '_zenith', 'base-path.js'), { force: true });
112
112
  await cp(join(options.coreOutput, 'server', 'server-contract.js'), join(functionsDir, '_zenith', 'server-contract.js'), { force: true });
113
+ await cp(join(options.coreOutput, 'server', 'server-error.js'), join(functionsDir, '_zenith', 'server-error.js'), { force: true });
113
114
  for (const route of serverRoutes) {
114
115
  await cp(join(options.coreOutput, 'server', 'routes', route.name), join(functionsDir, '_zenith', 'routes', route.name), { recursive: true, force: true });
115
116
  await writeFile(join(functionsDir, `__zenith_${route.name}.mjs`), createFunctionSource(route), 'utf8');
@@ -12,6 +12,14 @@ const NODE_RUNTIME_FILES = [
12
12
  from: new URL('../server/resolve-request-route.js', import.meta.url),
13
13
  to: 'runtime/resolve-request-route.js'
14
14
  },
15
+ {
16
+ from: new URL('../request-origin.js', import.meta.url),
17
+ to: 'request-origin.js'
18
+ },
19
+ {
20
+ from: new URL('../server-error.js', import.meta.url),
21
+ to: 'server-error.js'
22
+ },
15
23
  {
16
24
  from: new URL('../images/service.js', import.meta.url),
17
25
  to: 'images/service.js'
@@ -84,6 +84,7 @@ export const vercelAdapter = {
84
84
  await cp(join(options.coreOutput, 'server', 'images'), join(functionDir, 'images'), { recursive: true, force: true });
85
85
  await cp(join(options.coreOutput, 'server', 'base-path.js'), join(functionDir, 'base-path.js'), { force: true });
86
86
  await cp(join(options.coreOutput, 'server', 'server-contract.js'), join(functionDir, 'server-contract.js'), { force: true });
87
+ await cp(join(options.coreOutput, 'server', 'server-error.js'), join(functionDir, 'server-error.js'), { force: true });
87
88
  await cp(join(options.coreOutput, 'server', 'routes', route.name), functionDir, { recursive: true, force: true });
88
89
  await writeFile(join(functionDir, 'package.json'), '{\n "type": "module"\n}\n', 'utf8');
89
90
  await writeFile(join(functionDir, 'index.js'), createFunctionSource(route), 'utf8');
@@ -42,17 +42,18 @@ export function createTimedCompilerRunner(startupProfile: ReturnType<typeof impo
42
42
  * @param {object | null} [logger]
43
43
  * @param {boolean} [showInfo]
44
44
  * @param {string|object} [bundlerBin]
45
- * @param {{ devStableAssets?: boolean, rebuildStrategy?: 'full'|'bundle-only'|'page-only', changedRoutes?: string[], fastPath?: boolean, globalGraphHash?: string, basePath?: string }} [bundlerOptions]
45
+ * @param {{ devStableAssets?: boolean, rebuildStrategy?: 'full'|'bundle-only'|'page-only', changedRoutes?: string[], fastPath?: boolean, globalGraphHash?: string, basePath?: string, routeCheck?: boolean }} [bundlerOptions]
46
46
  * @returns {Promise<void>}
47
47
  */
48
- export function runBundler(envelope: object | object[], outDir: string, projectRoot: string, logger?: object | null, showInfo?: boolean, bundlerBin?: string | object, bundlerOptions?: {
49
- devStableAssets?: boolean;
50
- rebuildStrategy?: "full" | "bundle-only" | "page-only";
51
- changedRoutes?: string[];
52
- fastPath?: boolean;
53
- globalGraphHash?: string;
54
- basePath?: string;
55
- }): Promise<void>;
48
+ /**
49
+ * Merge compiler-owned static image materialization rows from marker bindings + rewritten props literals.
50
+ * @param {object} pageIr
51
+ * @param {string[]} literals
52
+ * @param {object} [compilerRunOptions]
53
+ * @returns {object}
54
+ */
55
+ export function mergePageImageMaterialization(pageIr: object, literals: string[], compilerRunOptions?: object): object;
56
+ export function runBundler(envelope: any, outDir: any, projectRoot: any, logger?: null, showInfo?: boolean, bundlerBin?: string, bundlerOptions?: {}): Promise<any>;
56
57
  /**
57
58
  * @param {string} rootDir
58
59
  * @returns {Promise<string[]>}
@@ -171,9 +171,56 @@ export function createTimedCompilerRunner(startupProfile, compilerTotals) {
171
171
  * @param {object | null} [logger]
172
172
  * @param {boolean} [showInfo]
173
173
  * @param {string|object} [bundlerBin]
174
- * @param {{ devStableAssets?: boolean, rebuildStrategy?: 'full'|'bundle-only'|'page-only', changedRoutes?: string[], fastPath?: boolean, globalGraphHash?: string, basePath?: string }} [bundlerOptions]
174
+ * @param {{ devStableAssets?: boolean, rebuildStrategy?: 'full'|'bundle-only'|'page-only', changedRoutes?: string[], fastPath?: boolean, globalGraphHash?: string, basePath?: string, routeCheck?: boolean }} [bundlerOptions]
175
175
  * @returns {Promise<void>}
176
176
  */
177
+ /**
178
+ * Merge compiler-owned static image materialization rows from marker bindings + rewritten props literals.
179
+ * @param {object} pageIr
180
+ * @param {string[]} literals
181
+ * @param {object} [compilerRunOptions]
182
+ * @returns {object}
183
+ */
184
+ export function mergePageImageMaterialization(pageIr, literals, compilerRunOptions = {}) {
185
+ if (!Array.isArray(literals) || literals.length === 0) {
186
+ return pageIr;
187
+ }
188
+ const compilerToolchain = compilerRunOptions.compilerToolchain
189
+ || (compilerRunOptions.compilerBin && typeof compilerRunOptions.compilerBin === 'object'
190
+ ? compilerRunOptions.compilerBin
191
+ : null);
192
+ const compilerBin = !compilerToolchain && typeof compilerRunOptions.compilerBin === 'string'
193
+ ? compilerRunOptions.compilerBin
194
+ : null;
195
+ const payload = JSON.stringify({
196
+ marker_bindings: pageIr.marker_bindings,
197
+ literals
198
+ });
199
+ const args = ['--merge-image-materialization'];
200
+ const opts = {
201
+ encoding: 'utf8',
202
+ maxBuffer: COMPILER_SPAWN_MAX_BUFFER,
203
+ input: payload
204
+ };
205
+ const result = compilerToolchain
206
+ ? runToolchainSync(compilerToolchain, args, opts).result
207
+ : (compilerBin
208
+ ? spawnSync(compilerBin, args, opts)
209
+ : runToolchainSync(createCompilerToolchain({
210
+ logger: compilerRunOptions.logger || null
211
+ }), args, opts).result);
212
+ if (result.error) {
213
+ throw new Error(`merge image materialization spawn failed: ${result.error.message}`);
214
+ }
215
+ if (result.status !== 0) {
216
+ throw new Error(`merge image materialization failed with exit code ${result.status}\n${result.stderr || ''}`);
217
+ }
218
+ const parsed = JSON.parse(result.stdout);
219
+ return {
220
+ ...pageIr,
221
+ image_materialization: parsed.image_materialization
222
+ };
223
+ }
177
224
  export function runBundler(envelope, outDir, projectRoot, logger = null, showInfo = true, bundlerBin = resolveBundlerBin(projectRoot), bundlerOptions = {}) {
178
225
  return new Promise((resolvePromise, rejectPromise) => {
179
226
  const useStructuredLogger = Boolean(logger && typeof logger.childLine === 'function');
@@ -192,6 +239,9 @@ export function runBundler(envelope, outDir, projectRoot, logger = null, showInf
192
239
  if (typeof bundlerOptions.basePath === 'string' && bundlerOptions.basePath.length > 0) {
193
240
  bundlerArgs.push('--base-path', bundlerOptions.basePath);
194
241
  }
242
+ if (bundlerOptions.routeCheck === true) {
243
+ bundlerArgs.push('--route-check');
244
+ }
195
245
  if (bundlerOptions.devStableAssets === true) {
196
246
  bundlerArgs.push('--dev-stable-assets');
197
247
  }
@@ -0,0 +1 @@
1
+ export function rewriteCompilerSignalMapReferences(compiledExpr: any, buildReplacement: any): string | null;
@@ -0,0 +1,155 @@
1
+ import { loadTypeScriptApi } from './compiler-runtime.js';
2
+ import { normalizeTypeScriptExpression } from './typescript-expression-utils.js';
3
+ function collectBindingNames(ts, name, target) {
4
+ if (ts.isIdentifier(name)) {
5
+ target.add(name.text);
6
+ return;
7
+ }
8
+ if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
9
+ for (const element of name.elements) {
10
+ if (ts.isBindingElement(element)) {
11
+ collectBindingNames(ts, element.name, target);
12
+ }
13
+ }
14
+ }
15
+ }
16
+ function collectDirectBlockBindings(ts, block, target) {
17
+ const statements = Array.isArray(block?.statements) ? block.statements : [];
18
+ for (const statement of statements) {
19
+ if (ts.isVariableStatement(statement)) {
20
+ for (const declaration of statement.declarationList.declarations) {
21
+ collectBindingNames(ts, declaration.name, target);
22
+ }
23
+ continue;
24
+ }
25
+ if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) {
26
+ target.add(statement.name.text);
27
+ }
28
+ }
29
+ }
30
+ function isNestedBlockScope(ts, node) {
31
+ return (ts.isBlock(node) || ts.isModuleBlock(node)) && !ts.isSourceFile(node.parent);
32
+ }
33
+ function resolveCompilerSignalIndex(ts, node, localBindings) {
34
+ if (!ts.isCallExpression(node)) {
35
+ return null;
36
+ }
37
+ if (node.arguments.length !== 1) {
38
+ return null;
39
+ }
40
+ const expression = node.expression;
41
+ if (!ts.isPropertyAccessExpression(expression) || expression.name.text !== 'get') {
42
+ return null;
43
+ }
44
+ if (!ts.isIdentifier(expression.expression) || expression.expression.text !== 'signalMap') {
45
+ return null;
46
+ }
47
+ if (localBindings.has('signalMap')) {
48
+ return null;
49
+ }
50
+ const [indexArg] = node.arguments;
51
+ if (!ts.isNumericLiteral(indexArg)) {
52
+ return null;
53
+ }
54
+ const signalIndex = Number.parseInt(indexArg.text, 10);
55
+ return Number.isInteger(signalIndex) ? signalIndex : null;
56
+ }
57
+ export function rewriteCompilerSignalMapReferences(compiledExpr, buildReplacement) {
58
+ const source = typeof compiledExpr === 'string' ? compiledExpr.trim() : '';
59
+ if (!source) {
60
+ return null;
61
+ }
62
+ if (!source.includes('signalMap.get(') || typeof buildReplacement !== 'function') {
63
+ return normalizeTypeScriptExpression(source);
64
+ }
65
+ const ts = loadTypeScriptApi();
66
+ if (!ts) {
67
+ return normalizeTypeScriptExpression(source);
68
+ }
69
+ let sourceFile;
70
+ try {
71
+ sourceFile = ts.createSourceFile('zenith-compiled-expression.ts', `const __zenith_expr__ = (${source});`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
72
+ }
73
+ catch {
74
+ return normalizeTypeScriptExpression(source);
75
+ }
76
+ const nextScopeBindings = (node, localBindings) => {
77
+ if (ts.isSourceFile(node)) {
78
+ return localBindings;
79
+ }
80
+ if (ts.isFunctionLike(node)) {
81
+ const next = new Set(localBindings);
82
+ if (node.name && ts.isIdentifier(node.name) && !ts.isSourceFile(node.parent)) {
83
+ next.add(node.name.text);
84
+ }
85
+ for (const param of node.parameters) {
86
+ collectBindingNames(ts, param.name, next);
87
+ }
88
+ return next;
89
+ }
90
+ if (isNestedBlockScope(ts, node)) {
91
+ const next = new Set(localBindings);
92
+ collectDirectBlockBindings(ts, node, next);
93
+ return next;
94
+ }
95
+ if (ts.isCatchClause(node) && node.variableDeclaration) {
96
+ const next = new Set(localBindings);
97
+ collectBindingNames(ts, node.variableDeclaration.name, next);
98
+ return next;
99
+ }
100
+ if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node))
101
+ && node.initializer
102
+ && ts.isVariableDeclarationList(node.initializer)) {
103
+ const next = new Set(localBindings);
104
+ for (const declaration of node.initializer.declarations) {
105
+ collectBindingNames(ts, declaration.name, next);
106
+ }
107
+ return next;
108
+ }
109
+ return localBindings;
110
+ };
111
+ const transformer = (context) => {
112
+ const visit = (node, localBindings) => {
113
+ const scopeBindings = nextScopeBindings(node, localBindings);
114
+ if (ts.isCallExpression(node) && node.arguments.length === 0) {
115
+ const expression = node.expression;
116
+ if (ts.isPropertyAccessExpression(expression) && expression.name.text === 'get') {
117
+ const signalIndex = resolveCompilerSignalIndex(ts, expression.expression, scopeBindings);
118
+ if (Number.isInteger(signalIndex)) {
119
+ const replacement = buildReplacement({ ts, signalIndex, valueRead: true });
120
+ if (replacement) {
121
+ return replacement;
122
+ }
123
+ }
124
+ }
125
+ }
126
+ const signalIndex = resolveCompilerSignalIndex(ts, node, scopeBindings);
127
+ if (Number.isInteger(signalIndex)) {
128
+ const replacement = buildReplacement({ ts, signalIndex, valueRead: false });
129
+ if (replacement) {
130
+ return replacement;
131
+ }
132
+ }
133
+ return ts.visitEachChild(node, (child) => visit(child, scopeBindings), context);
134
+ };
135
+ return (node) => ts.visitNode(node, (child) => visit(child, new Set()));
136
+ };
137
+ const result = ts.transform(sourceFile, [transformer]);
138
+ try {
139
+ const printed = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
140
+ .printFile(result.transformed[0])
141
+ .trimEnd();
142
+ const prefix = 'const __zenith_expr__ = (';
143
+ const suffix = ');';
144
+ if (!printed.startsWith(prefix) || !printed.endsWith(suffix)) {
145
+ return normalizeTypeScriptExpression(source);
146
+ }
147
+ return normalizeTypeScriptExpression(printed.slice(prefix.length, printed.length - suffix.length));
148
+ }
149
+ catch {
150
+ return normalizeTypeScriptExpression(source);
151
+ }
152
+ finally {
153
+ result.dispose();
154
+ }
155
+ }
@@ -1,10 +1,5 @@
1
1
  /**
2
- * @param {string} compPath
3
- * @param {string} componentSource
4
2
  * @param {object} compIr
5
- * @param {object} compilerOpts
6
- * @param {string|object} compilerBin
7
- * @param {Map<string, string[]> | null} [templateExpressionCache]
8
3
  * @param {Record<string, number> | null} [rewriteMetrics]
9
4
  * @returns {{
10
5
  * map: Map<string, string>,
@@ -22,7 +17,7 @@
22
17
  * sequence: Array<{ raw: string, rewritten: string, binding: object | null }>
23
18
  * }}
24
19
  */
25
- export function buildComponentExpressionRewrite(compPath: string, componentSource: string, compIr: object, compilerOpts: object, compilerBin: string | object, templateExpressionCache?: Map<string, string[]> | null, rewriteMetrics?: Record<string, number> | null): {
20
+ export function buildComponentExpressionRewrite(compIr: object, rewriteMetrics?: Record<string, number> | null): {
26
21
  map: Map<string, string>;
27
22
  bindings: Map<string, {
28
23
  compiled_expr: string | null;
@@ -1,13 +1,7 @@
1
1
  import { performance } from 'node:perf_hooks';
2
- import { extractTemplate } from '../resolve-components.js';
3
- import { runCompiler } from './compiler-runtime.js';
2
+ import { rewriteCompilerSignalMapReferences } from './compiler-signal-expression.js';
4
3
  /**
5
- * @param {string} compPath
6
- * @param {string} componentSource
7
4
  * @param {object} compIr
8
- * @param {object} compilerOpts
9
- * @param {string|object} compilerBin
10
- * @param {Map<string, string[]> | null} [templateExpressionCache]
11
5
  * @param {Record<string, number> | null} [rewriteMetrics]
12
6
  * @returns {{
13
7
  * map: Map<string, string>,
@@ -25,7 +19,9 @@ import { runCompiler } from './compiler-runtime.js';
25
19
  * sequence: Array<{ raw: string, rewritten: string, binding: object | null }>
26
20
  * }}
27
21
  */
28
- export function buildComponentExpressionRewrite(compPath, componentSource, compIr, compilerOpts, compilerBin, templateExpressionCache = null, rewriteMetrics = null) {
22
+ export function buildComponentExpressionRewrite(compIr, rewriteMetrics = null) {
23
+ // Downstream is only allowed to read compiler-owned raw->rewritten pairs here.
24
+ // It must not synthesize new identifier meaning beyond this mapping.
29
25
  const out = {
30
26
  map: new Map(),
31
27
  bindings: new Map(),
@@ -39,60 +35,19 @@ export function buildComponentExpressionRewrite(compPath, componentSource, compI
39
35
  if (rewrittenExpressions.length === 0) {
40
36
  return out;
41
37
  }
42
- const hoistedState = Array.isArray(compIr?.hoisted?.state) ? compIr.hoisted.state : [];
43
- const hoistedFunctions = Array.isArray(compIr?.hoisted?.functions) ? compIr.hoisted.functions : [];
44
- const hoistedDeclarations = Array.isArray(compIr?.hoisted?.declarations) ? compIr.hoisted.declarations : [];
45
- const signals = Array.isArray(compIr?.signals) ? compIr.signals : [];
46
- if (hoistedState.length === 0 &&
47
- hoistedFunctions.length === 0 &&
48
- hoistedDeclarations.length === 0 &&
49
- signals.length === 0) {
50
- return out;
51
- }
52
38
  if (rewriteMetrics && typeof rewriteMetrics === 'object') {
53
39
  rewriteMetrics.calls += 1;
54
40
  }
55
- const templateOnly = extractTemplate(componentSource);
56
- if (!templateOnly.trim()) {
57
- return out;
58
- }
59
- const cacheKey = `${compPath}\u0000${templateOnly}`;
60
- let rawExpressions = null;
61
- if (templateExpressionCache instanceof Map && templateExpressionCache.has(cacheKey)) {
62
- rawExpressions = templateExpressionCache.get(cacheKey);
63
- if (rewriteMetrics && typeof rewriteMetrics === 'object') {
64
- rewriteMetrics.cacheHits += 1;
65
- }
66
- }
67
- else {
68
- let templateIr;
69
- const templateCompileStartedAt = performance.now();
70
- try {
71
- templateIr = runCompiler(compPath, templateOnly, compilerOpts, {
72
- suppressWarnings: true,
73
- compilerToolchain: compilerBin
74
- });
75
- }
76
- catch {
77
- return out;
78
- }
79
- rawExpressions = Array.isArray(templateIr?.expressions) ? templateIr.expressions : [];
80
- if (templateExpressionCache instanceof Map) {
81
- templateExpressionCache.set(cacheKey, rawExpressions);
82
- }
83
- if (rewriteMetrics && typeof rewriteMetrics === 'object') {
84
- rewriteMetrics.cacheMisses += 1;
85
- rewriteMetrics.templateCompileMs += Math.round((performance.now() - templateCompileStartedAt) * 100) / 100;
86
- }
87
- }
88
- const count = Math.min(rawExpressions.length, rewrittenExpressions.length);
89
- for (let i = 0; i < count; i++) {
90
- const raw = rawExpressions[i];
41
+ for (let i = 0; i < rewrittenExpressions.length; i++) {
91
42
  const rewritten = rewrittenExpressions[i];
92
- if (typeof raw !== 'string' || typeof rewritten !== 'string') {
43
+ if (typeof rewritten !== 'string') {
93
44
  continue;
94
45
  }
95
46
  const binding = rewrittenBindings[i];
47
+ // Compiler emits the raw source literal for exact lookup; CLI only transports it.
48
+ const raw = typeof binding?.literal === 'string' && binding.literal.length > 0
49
+ ? binding.literal
50
+ : rewritten;
96
51
  const normalizedBinding = binding && typeof binding === 'object'
97
52
  ? {
98
53
  compiled_expr: typeof binding.compiled_expr === 'string' ? binding.compiled_expr : null,
@@ -133,6 +88,10 @@ export function buildComponentExpressionRewrite(compPath, componentSource, compI
133
88
  }
134
89
  }
135
90
  }
91
+ if (rewriteMetrics && typeof rewriteMetrics === 'object') {
92
+ rewriteMetrics.compilerOwnedBindings += out.sequence.length;
93
+ rewriteMetrics.ambiguousBindings += out.ambiguous.size;
94
+ }
136
95
  return out;
137
96
  }
138
97
  /**
@@ -143,27 +102,28 @@ export function buildComponentExpressionRewrite(compPath, componentSource, compI
143
102
  * @returns {string | null}
144
103
  */
145
104
  export function remapCompiledExpressionSignals(compiledExpr, componentSignals, componentStateBindings, pageSignalIndexByStateKey) {
105
+ // This is a mechanical index remap across page merge boundaries, not a second compiler pass.
146
106
  if (typeof compiledExpr !== 'string' || compiledExpr.length === 0) {
147
107
  return null;
148
108
  }
149
- return compiledExpr.replace(/signalMap\.get\((\d+)\)/g, (full, rawIndex) => {
150
- const localIndex = Number.parseInt(rawIndex, 10);
151
- if (!Number.isInteger(localIndex)) {
152
- return full;
153
- }
154
- const signal = componentSignals[localIndex];
109
+ return rewriteCompilerSignalMapReferences(compiledExpr, ({ ts, signalIndex, valueRead }) => {
110
+ const signal = componentSignals[signalIndex];
155
111
  if (!signal || !Number.isInteger(signal.state_index)) {
156
- return full;
112
+ return null;
157
113
  }
158
114
  const stateKey = componentStateBindings[signal.state_index]?.key;
159
115
  if (typeof stateKey !== 'string' || stateKey.length === 0) {
160
- return full;
116
+ return null;
161
117
  }
162
118
  const pageIndex = pageSignalIndexByStateKey.get(stateKey);
163
119
  if (!Number.isInteger(pageIndex)) {
164
- return full;
120
+ return null;
165
121
  }
166
- return `signalMap.get(${pageIndex})`;
122
+ const signalMapRead = ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('signalMap'), 'get'), undefined, [ts.factory.createNumericLiteral(String(pageIndex))]);
123
+ if (valueRead) {
124
+ return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(signalMapRead, 'get'), undefined, []);
125
+ }
126
+ return signalMapRead;
167
127
  });
168
128
  }
169
129
  /**
@@ -319,6 +279,15 @@ export function mergeExpressionRewriteMaps(pageMap, pageBindingMap, pageAmbiguou
319
279
  const resolved = resolveRewrittenBindingMetadata(pageBindingContext, componentRewrite, binding, bindingResolutionMetrics);
320
280
  const existing = pageBindingMap.get(raw);
321
281
  if (existing && JSON.stringify(existing) !== JSON.stringify(resolved)) {
282
+ const existingWeight = measureBindingSpecificity(existing, raw);
283
+ const resolvedWeight = measureBindingSpecificity(resolved, raw);
284
+ if (resolvedWeight > existingWeight && existingWeight === 0) {
285
+ pageBindingMap.set(raw, resolved);
286
+ continue;
287
+ }
288
+ if (existingWeight > resolvedWeight && resolvedWeight === 0) {
289
+ continue;
290
+ }
322
291
  pageAmbiguous.add(raw);
323
292
  pageMap.delete(raw);
324
293
  pageBindingMap.delete(raw);
@@ -340,6 +309,33 @@ export function mergeExpressionRewriteMaps(pageMap, pageBindingMap, pageAmbiguou
340
309
  pageMap.set(raw, rewritten);
341
310
  }
342
311
  }
312
+ function measureBindingSpecificity(binding, raw) {
313
+ if (!binding || typeof binding !== 'object') {
314
+ return 0;
315
+ }
316
+ let score = 0;
317
+ if (typeof binding.compiled_expr === 'string' &&
318
+ binding.compiled_expr.length > 0 &&
319
+ binding.compiled_expr !== raw) {
320
+ score += 4;
321
+ }
322
+ if (Number.isInteger(binding.signal_index)) {
323
+ score += 2;
324
+ }
325
+ if (Array.isArray(binding.signal_indices) && binding.signal_indices.length > 0) {
326
+ score += 2;
327
+ }
328
+ if (Number.isInteger(binding.state_index)) {
329
+ score += 1;
330
+ }
331
+ if (typeof binding.component_instance === 'string' && binding.component_instance.length > 0) {
332
+ score += 1;
333
+ }
334
+ if (typeof binding.component_binding === 'string' && binding.component_binding.length > 0) {
335
+ score += 1;
336
+ }
337
+ return score;
338
+ }
343
339
  /**
344
340
  * @param {string} identifier
345
341
  * @param {Array<{ key?: string }>} stateBindings
@@ -1,4 +1,4 @@
1
- export function buildPageOwnerContext({ componentOccurrences, sourceFile, pageOwnerSource, compilerOpts, compilerBin, timedRunCompiler, cooperativeYield, templateExpressionCache, expressionRewriteMetrics, startupProfile }: {
1
+ export function buildPageOwnerContext({ componentOccurrences, sourceFile, pageOwnerSource, compilerOpts, compilerBin, timedRunCompiler, cooperativeYield, expressionRewriteMetrics, startupProfile }: {
2
2
  componentOccurrences: any;
3
3
  sourceFile: any;
4
4
  pageOwnerSource: any;
@@ -6,7 +6,6 @@ export function buildPageOwnerContext({ componentOccurrences, sourceFile, pageOw
6
6
  compilerBin: any;
7
7
  timedRunCompiler: any;
8
8
  cooperativeYield: any;
9
- templateExpressionCache: any;
10
9
  expressionRewriteMetrics: any;
11
10
  startupProfile: any;
12
11
  }): Promise<{
@@ -19,10 +18,6 @@ export function buildPageOwnerContext({ componentOccurrences, sourceFile, pageOw
19
18
  ambiguous: Set<any>;
20
19
  sequence: never[];
21
20
  };
22
- pageOwnerScopeRewrite: {
23
- map: Map<any, any>;
24
- ambiguous: Set<any>;
25
- };
26
21
  } | {
27
22
  pageOwnerCompileMs: any;
28
23
  pageOwnerExpressionRewrite: {
@@ -51,12 +46,8 @@ export function buildPageOwnerContext({ componentOccurrences, sourceFile, pageOw
51
46
  binding: object | null;
52
47
  }>;
53
48
  };
54
- pageOwnerScopeRewrite: {
55
- map: Map<string, string>;
56
- ambiguous: Set<string>;
57
- };
58
49
  }>;
59
- export function runPageComponentLoop({ componentOccurrences, occurrenceCountByPath, sourceFile, registry, compilerOpts, compilerBin, timedRunCompiler, cooperativeYield, startupProfile, compilerTotals, emitCompilerWarning, componentIrCache, componentDocumentModeCache, componentExpressionRewriteCache, templateExpressionCache, expressionRewriteMetrics, pageOwnerExpressionRewrite, pageOwnerScopeRewrite, pageIr, pageIrMergeCache, seenStaticImports, pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, knownRefKeys, componentOccurrencePlans, pagePhase, pageBindingResolutionBreakdown, pageMergeBreakdown, pageComponentLoopBreakdown, hoistedCodeTransformCache, pageStats }: {
50
+ export function runPageComponentLoop({ componentOccurrences, occurrenceCountByPath, sourceFile, registry, compilerOpts, compilerBin, timedRunCompiler, cooperativeYield, startupProfile, compilerTotals, emitCompilerWarning, componentIrCache, componentDocumentModeCache, componentExpressionRewriteCache, expressionRewriteMetrics, pageOwnerExpressionRewrite, pageIr, pageIrMergeCache, seenStaticImports, pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, knownRefKeys, componentOccurrencePlans, imagePropsLiterals, pagePhase, pageBindingResolutionBreakdown, pageMergeBreakdown, pageComponentLoopBreakdown, hoistedCodeTransformCache, pageStats }: {
60
51
  componentOccurrences: any;
61
52
  occurrenceCountByPath: any;
62
53
  sourceFile: any;
@@ -71,10 +62,8 @@ export function runPageComponentLoop({ componentOccurrences, occurrenceCountByPa
71
62
  componentIrCache: any;
72
63
  componentDocumentModeCache: any;
73
64
  componentExpressionRewriteCache: any;
74
- templateExpressionCache: any;
75
65
  expressionRewriteMetrics: any;
76
66
  pageOwnerExpressionRewrite: any;
77
- pageOwnerScopeRewrite: any;
78
67
  pageIr: any;
79
68
  pageIrMergeCache: any;
80
69
  seenStaticImports: any;
@@ -83,6 +72,7 @@ export function runPageComponentLoop({ componentOccurrences, occurrenceCountByPa
83
72
  pageAmbiguousExpressionMap: any;
84
73
  knownRefKeys: any;
85
74
  componentOccurrencePlans: any;
75
+ imagePropsLiterals: any;
86
76
  pagePhase: any;
87
77
  pageBindingResolutionBreakdown: any;
88
78
  pageMergeBreakdown: any;