@zenithbuild/cli 0.6.5 → 0.6.7

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 (43) hide show
  1. package/dist/build.d.ts +32 -0
  2. package/dist/build.js +193 -548
  3. package/dist/compiler-bridge-runner.d.ts +5 -0
  4. package/dist/compiler-bridge-runner.js +70 -0
  5. package/dist/component-instance-ir.d.ts +6 -0
  6. package/dist/component-instance-ir.js +0 -20
  7. package/dist/component-occurrences.d.ts +6 -0
  8. package/dist/component-occurrences.js +6 -28
  9. package/dist/dev-server.d.ts +18 -0
  10. package/dist/dev-server.js +76 -116
  11. package/dist/dev-watch.d.ts +1 -0
  12. package/dist/dev-watch.js +19 -0
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.js +6 -28
  15. package/dist/manifest.d.ts +23 -0
  16. package/dist/manifest.js +22 -48
  17. package/dist/preview.d.ts +100 -0
  18. package/dist/preview.js +418 -488
  19. package/dist/resolve-components.d.ts +39 -0
  20. package/dist/resolve-components.js +30 -104
  21. package/dist/server/resolve-request-route.d.ts +39 -0
  22. package/dist/server/resolve-request-route.js +104 -113
  23. package/dist/server-contract.d.ts +39 -0
  24. package/dist/server-contract.js +15 -67
  25. package/dist/toolchain-paths.d.ts +23 -0
  26. package/dist/toolchain-paths.js +111 -39
  27. package/dist/toolchain-runner.d.ts +33 -0
  28. package/dist/toolchain-runner.js +170 -0
  29. package/dist/types/generate-env-dts.d.ts +5 -0
  30. package/dist/types/generate-env-dts.js +4 -2
  31. package/dist/types/generate-routes-dts.d.ts +8 -0
  32. package/dist/types/generate-routes-dts.js +7 -5
  33. package/dist/types/index.d.ts +14 -0
  34. package/dist/types/index.js +16 -7
  35. package/dist/ui/env.d.ts +18 -0
  36. package/dist/ui/env.js +0 -12
  37. package/dist/ui/format.d.ts +33 -0
  38. package/dist/ui/format.js +7 -45
  39. package/dist/ui/logger.d.ts +59 -0
  40. package/dist/ui/logger.js +3 -32
  41. package/dist/version-check.d.ts +54 -0
  42. package/dist/version-check.js +41 -98
  43. package/package.json +17 -5
package/dist/build.js CHANGED
@@ -10,7 +10,6 @@
10
10
  // The CLI does not inspect IR fields and does not write output files.
11
11
  // The bundler owns all asset and HTML emission.
12
12
  // ---------------------------------------------------------------------------
13
-
14
13
  import { spawn, spawnSync } from 'node:child_process';
15
14
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
16
15
  import { mkdir, readdir, rm, stat } from 'node:fs/promises';
@@ -21,11 +20,10 @@ import { buildComponentRegistry, expandComponents, extractTemplate, isDocumentMo
21
20
  import { collectExpandedComponentOccurrences } from './component-occurrences.js';
22
21
  import { applyOccurrenceRewritePlans, cloneComponentIrForInstance } from './component-instance-ir.js';
23
22
  import { resolveBundlerBin, resolveCompilerBin } from './toolchain-paths.js';
23
+ import { createBundlerToolchain, createCompilerToolchain, ensureToolchainCompatibility, getActiveToolchainCandidate, runToolchainSync } from './toolchain-runner.js';
24
24
  import { maybeWarnAboutZenithVersionMismatch } from './version-check.js';
25
-
26
25
  const require = createRequire(import.meta.url);
27
26
  let cachedTypeScript = undefined;
28
-
29
27
  /**
30
28
  * @returns {import('typescript') | null}
31
29
  */
@@ -33,13 +31,13 @@ function loadTypeScriptApi() {
33
31
  if (cachedTypeScript === undefined) {
34
32
  try {
35
33
  cachedTypeScript = require('typescript');
36
- } catch {
34
+ }
35
+ catch {
37
36
  cachedTypeScript = null;
38
37
  }
39
38
  }
40
39
  return cachedTypeScript;
41
40
  }
42
-
43
41
  /**
44
42
  * Build a per-build warning emitter that deduplicates repeated compiler lines.
45
43
  *
@@ -57,7 +55,6 @@ export function createCompilerWarningEmitter(sink = (line) => console.warn(line)
57
55
  sink(text);
58
56
  };
59
57
  }
60
-
61
58
  /**
62
59
  * Forward child-process output line-by-line through the structured logger.
63
60
  *
@@ -86,7 +83,6 @@ function forwardStreamLines(stream, onLine) {
86
83
  }
87
84
  });
88
85
  }
89
-
90
86
  /**
91
87
  * Run the compiler process and parse its JSON stdout.
92
88
  *
@@ -99,11 +95,18 @@ function forwardStreamLines(stream, onLine) {
99
95
  * @param {object} compilerRunOptions
100
96
  * @param {(warning: string) => void} [compilerRunOptions.onWarning]
101
97
  * @param {boolean} [compilerRunOptions.suppressWarnings]
102
- * @param {string} [compilerRunOptions.compilerBin]
98
+ * @param {string|object} [compilerRunOptions.compilerBin]
99
+ * @param {object} [compilerRunOptions.compilerToolchain]
103
100
  * @returns {object}
104
101
  */
105
102
  function runCompiler(filePath, stdinSource, compilerOpts = {}, compilerRunOptions = {}) {
106
- const compilerBin = compilerRunOptions.compilerBin || resolveCompilerBin();
103
+ const compilerToolchain = compilerRunOptions.compilerToolchain
104
+ || (compilerRunOptions.compilerBin && typeof compilerRunOptions.compilerBin === 'object'
105
+ ? compilerRunOptions.compilerBin
106
+ : null);
107
+ const compilerBin = !compilerToolchain && typeof compilerRunOptions.compilerBin === 'string'
108
+ ? compilerRunOptions.compilerBin
109
+ : resolveCompilerBin();
107
110
  const args = stdinSource !== undefined
108
111
  ? ['--stdin', filePath]
109
112
  : [filePath];
@@ -117,18 +120,19 @@ function runCompiler(filePath, stdinSource, compilerOpts = {}, compilerRunOption
117
120
  if (stdinSource !== undefined) {
118
121
  opts.input = stdinSource;
119
122
  }
120
-
121
- const result = spawnSync(compilerBin, args, opts);
122
-
123
+ const result = compilerToolchain
124
+ ? runToolchainSync(compilerToolchain, args, opts).result
125
+ : (compilerBin
126
+ ? spawnSync(compilerBin, args, opts)
127
+ : runToolchainSync(createCompilerToolchain({
128
+ logger: compilerRunOptions.logger || null
129
+ }), args, opts).result);
123
130
  if (result.error) {
124
131
  throw new Error(`Compiler spawn failed for ${filePath}: ${result.error.message}`);
125
132
  }
126
133
  if (result.status !== 0) {
127
- throw new Error(
128
- `Compiler failed for ${filePath} with exit code ${result.status}\n${result.stderr || ''}`
129
- );
134
+ throw new Error(`Compiler failed for ${filePath} with exit code ${result.status}\n${result.stderr || ''}`);
130
135
  }
131
-
132
136
  if (result.stderr && result.stderr.trim().length > 0 && compilerRunOptions.suppressWarnings !== true) {
133
137
  const lines = String(result.stderr)
134
138
  .split('\n')
@@ -137,19 +141,19 @@ function runCompiler(filePath, stdinSource, compilerOpts = {}, compilerRunOption
137
141
  for (const line of lines) {
138
142
  if (typeof compilerRunOptions.onWarning === 'function') {
139
143
  compilerRunOptions.onWarning(line);
140
- } else {
144
+ }
145
+ else {
141
146
  console.warn(line);
142
147
  }
143
148
  }
144
149
  }
145
-
146
150
  try {
147
151
  return JSON.parse(result.stdout);
148
- } catch (err) {
152
+ }
153
+ catch (err) {
149
154
  throw new Error(`Compiler emitted invalid JSON: ${err.message}`);
150
155
  }
151
156
  }
152
-
153
157
  /**
154
158
  * Strip component <style> blocks before script-only component IR compilation.
155
159
  * Component style emission is handled by page compilation/bundler paths.
@@ -160,7 +164,6 @@ function runCompiler(filePath, stdinSource, compilerOpts = {}, compilerRunOption
160
164
  function stripStyleBlocks(source) {
161
165
  return String(source || '').replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '');
162
166
  }
163
-
164
167
  /**
165
168
  * Build a deterministic raw->rewritten expression map for a component by
166
169
  * comparing template-only expressions with script-aware expressions.
@@ -169,7 +172,7 @@ function stripStyleBlocks(source) {
169
172
  * @param {string} componentSource
170
173
  * @param {object} compIr
171
174
  * @param {object} compilerOpts
172
- * @param {string} compilerBin
175
+ * @param {string|object} compilerBin
173
176
  * @returns {{
174
177
  * map: Map<string, string>,
175
178
  * bindings: Map<string, {
@@ -199,22 +202,20 @@ function buildComponentExpressionRewrite(compPath, componentSource, compIr, comp
199
202
  if (rewrittenExpressions.length === 0) {
200
203
  return out;
201
204
  }
202
-
203
205
  const templateOnly = extractTemplate(componentSource);
204
206
  if (!templateOnly.trim()) {
205
207
  return out;
206
208
  }
207
-
208
209
  let templateIr;
209
210
  try {
210
211
  templateIr = runCompiler(compPath, templateOnly, compilerOpts, {
211
212
  suppressWarnings: true,
212
- compilerBin
213
+ compilerToolchain: compilerBin
213
214
  });
214
- } catch {
215
+ }
216
+ catch {
215
217
  return out;
216
218
  }
217
-
218
219
  const rawExpressions = Array.isArray(templateIr?.expressions) ? templateIr.expressions : [];
219
220
  const count = Math.min(rawExpressions.length, rewrittenExpressions.length);
220
221
  for (let i = 0; i < count; i++) {
@@ -223,7 +224,6 @@ function buildComponentExpressionRewrite(compPath, componentSource, compIr, comp
223
224
  if (typeof raw !== 'string' || typeof rewritten !== 'string') {
224
225
  continue;
225
226
  }
226
-
227
227
  const binding = rewrittenBindings[i];
228
228
  const normalizedBinding = binding && typeof binding === 'object'
229
229
  ? {
@@ -237,13 +237,11 @@ function buildComponentExpressionRewrite(compPath, componentSource, compIr, comp
237
237
  component_binding: typeof binding.component_binding === 'string' ? binding.component_binding : null
238
238
  }
239
239
  : null;
240
-
241
240
  out.sequence.push({
242
241
  raw,
243
242
  rewritten,
244
243
  binding: normalizedBinding
245
244
  });
246
-
247
245
  if (!out.ambiguous.has(raw) && normalizedBinding) {
248
246
  const existingBinding = out.bindings.get(raw);
249
247
  if (existingBinding) {
@@ -253,11 +251,11 @@ function buildComponentExpressionRewrite(compPath, componentSource, compIr, comp
253
251
  out.ambiguous.add(raw);
254
252
  continue;
255
253
  }
256
- } else {
254
+ }
255
+ else {
257
256
  out.bindings.set(raw, normalizedBinding);
258
257
  }
259
258
  }
260
-
261
259
  if (raw !== rewritten) {
262
260
  const existing = out.map.get(raw);
263
261
  if (existing && existing !== rewritten) {
@@ -271,15 +269,12 @@ function buildComponentExpressionRewrite(compPath, componentSource, compIr, comp
271
269
  }
272
270
  }
273
271
  }
274
-
275
272
  return out;
276
273
  }
277
-
278
274
  function remapCompiledExpressionSignals(compiledExpr, componentSignals, componentStateBindings, pageSignalIndexByStateKey) {
279
275
  if (typeof compiledExpr !== 'string' || compiledExpr.length === 0) {
280
276
  return null;
281
277
  }
282
-
283
278
  return compiledExpr.replace(/signalMap\.get\((\d+)\)/g, (full, rawIndex) => {
284
279
  const localIndex = Number.parseInt(rawIndex, 10);
285
280
  if (!Number.isInteger(localIndex)) {
@@ -300,24 +295,20 @@ function remapCompiledExpressionSignals(compiledExpr, componentSignals, componen
300
295
  return `signalMap.get(${pageIndex})`;
301
296
  });
302
297
  }
303
-
304
298
  function resolveRewrittenBindingMetadata(pageIr, componentRewrite, binding) {
305
299
  if (!binding || typeof binding !== 'object') {
306
300
  return null;
307
301
  }
308
-
309
302
  const pageStateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
310
303
  const pageSignals = Array.isArray(pageIr?.signals) ? pageIr.signals : [];
311
304
  const pageStateIndexByKey = new Map();
312
305
  const pageSignalIndexByStateKey = new Map();
313
-
314
306
  for (let index = 0; index < pageStateBindings.length; index++) {
315
307
  const key = pageStateBindings[index]?.key;
316
308
  if (typeof key === 'string' && key.length > 0 && !pageStateIndexByKey.has(key)) {
317
309
  pageStateIndexByKey.set(key, index);
318
310
  }
319
311
  }
320
-
321
312
  for (let index = 0; index < pageSignals.length; index++) {
322
313
  const stateIndex = pageSignals[index]?.state_index;
323
314
  if (!Number.isInteger(stateIndex)) {
@@ -328,32 +319,27 @@ function resolveRewrittenBindingMetadata(pageIr, componentRewrite, binding) {
328
319
  pageSignalIndexByStateKey.set(stateKey, index);
329
320
  }
330
321
  }
331
-
332
322
  const componentSignals = Array.isArray(componentRewrite?.signals) ? componentRewrite.signals : [];
333
323
  const componentStateBindings = Array.isArray(componentRewrite?.stateBindings) ? componentRewrite.stateBindings : [];
334
-
335
324
  let signalIndices = Array.isArray(binding.signal_indices)
336
- ? [...new Set(
337
- binding.signal_indices
325
+ ? [...new Set(binding.signal_indices
338
326
  .map((signalIndex) => {
339
- if (!Number.isInteger(signalIndex)) {
340
- return null;
341
- }
342
- const signal = componentSignals[signalIndex];
343
- if (!signal || !Number.isInteger(signal.state_index)) {
344
- return null;
345
- }
346
- const stateKey = componentStateBindings[signal.state_index]?.key;
347
- if (typeof stateKey !== 'string' || stateKey.length === 0) {
348
- return null;
349
- }
350
- const pageIndex = pageSignalIndexByStateKey.get(stateKey);
351
- return Number.isInteger(pageIndex) ? pageIndex : null;
352
- })
353
- .filter((value) => Number.isInteger(value))
354
- )].sort((a, b) => a - b)
327
+ if (!Number.isInteger(signalIndex)) {
328
+ return null;
329
+ }
330
+ const signal = componentSignals[signalIndex];
331
+ if (!signal || !Number.isInteger(signal.state_index)) {
332
+ return null;
333
+ }
334
+ const stateKey = componentStateBindings[signal.state_index]?.key;
335
+ if (typeof stateKey !== 'string' || stateKey.length === 0) {
336
+ return null;
337
+ }
338
+ const pageIndex = pageSignalIndexByStateKey.get(stateKey);
339
+ return Number.isInteger(pageIndex) ? pageIndex : null;
340
+ })
341
+ .filter((value) => Number.isInteger(value)))].sort((a, b) => a - b)
355
342
  : [];
356
-
357
343
  let signalIndex = null;
358
344
  if (Number.isInteger(binding.signal_index)) {
359
345
  const signal = componentSignals[binding.signal_index];
@@ -366,47 +352,32 @@ function resolveRewrittenBindingMetadata(pageIr, componentRewrite, binding) {
366
352
  if (signalIndex === null && signalIndices.length === 1) {
367
353
  signalIndex = signalIndices[0];
368
354
  }
369
-
370
355
  let stateIndex = null;
371
356
  if (Number.isInteger(binding.state_index)) {
372
357
  const stateKey = componentStateBindings[binding.state_index]?.key;
373
358
  const pageIndex = typeof stateKey === 'string' ? pageStateIndexByKey.get(stateKey) : null;
374
359
  stateIndex = Number.isInteger(pageIndex) ? pageIndex : null;
375
360
  }
376
-
377
361
  if (Number.isInteger(stateIndex)) {
378
362
  const fallbackSignalIndices = pageSignals
379
363
  .map((signal, index) => signal?.state_index === stateIndex ? index : null)
380
364
  .filter((value) => Number.isInteger(value));
381
- const signalIndicesMatchState = signalIndices.every(
382
- (index) => pageSignals[index]?.state_index === stateIndex
383
- );
365
+ const signalIndicesMatchState = signalIndices.every((index) => pageSignals[index]?.state_index === stateIndex);
384
366
  if ((!signalIndicesMatchState || signalIndices.length === 0) && fallbackSignalIndices.length > 0) {
385
367
  signalIndices = fallbackSignalIndices;
386
368
  }
387
- if (
388
- (signalIndex === null || pageSignals[signalIndex]?.state_index !== stateIndex) &&
389
- fallbackSignalIndices.length === 1
390
- ) {
369
+ if ((signalIndex === null || pageSignals[signalIndex]?.state_index !== stateIndex) &&
370
+ fallbackSignalIndices.length === 1) {
391
371
  signalIndex = fallbackSignalIndices[0];
392
372
  }
393
373
  }
394
-
395
- let compiledExpr = remapCompiledExpressionSignals(
396
- binding.compiled_expr,
397
- componentSignals,
398
- componentStateBindings,
399
- pageSignalIndexByStateKey
400
- );
401
- if (
402
- typeof compiledExpr === 'string' &&
374
+ let compiledExpr = remapCompiledExpressionSignals(binding.compiled_expr, componentSignals, componentStateBindings, pageSignalIndexByStateKey);
375
+ if (typeof compiledExpr === 'string' &&
403
376
  signalIndices.length === 1 &&
404
377
  Array.isArray(binding.signal_indices) &&
405
- binding.signal_indices.length <= 1
406
- ) {
378
+ binding.signal_indices.length <= 1) {
407
379
  compiledExpr = compiledExpr.replace(/signalMap\.get\(\d+\)/g, `signalMap.get(${signalIndices[0]})`);
408
380
  }
409
-
410
381
  return {
411
382
  compiled_expr: compiledExpr,
412
383
  signal_index: signalIndex,
@@ -416,7 +387,6 @@ function resolveRewrittenBindingMetadata(pageIr, componentRewrite, binding) {
416
387
  component_binding: typeof binding.component_binding === 'string' ? binding.component_binding : null
417
388
  };
418
389
  }
419
-
420
390
  /**
421
391
  * Merge a per-component rewrite table into the page-level rewrite table.
422
392
  *
@@ -452,7 +422,6 @@ function mergeExpressionRewriteMaps(pageMap, pageBindingMap, pageAmbiguous, comp
452
422
  pageMap.delete(raw);
453
423
  pageBindingMap.delete(raw);
454
424
  }
455
-
456
425
  for (const [raw, binding] of componentRewrite.bindings.entries()) {
457
426
  if (pageAmbiguous.has(raw)) {
458
427
  continue;
@@ -467,7 +436,6 @@ function mergeExpressionRewriteMaps(pageMap, pageBindingMap, pageAmbiguous, comp
467
436
  }
468
437
  pageBindingMap.set(raw, resolved);
469
438
  }
470
-
471
439
  for (const [raw, rewritten] of componentRewrite.map.entries()) {
472
440
  if (pageAmbiguous.has(raw)) {
473
441
  continue;
@@ -482,47 +450,38 @@ function mergeExpressionRewriteMaps(pageMap, pageBindingMap, pageAmbiguous, comp
482
450
  pageMap.set(raw, rewritten);
483
451
  }
484
452
  }
485
-
486
453
  function resolveStateKeyFromBindings(identifier, stateBindings, preferredKeys = null) {
487
454
  const ident = String(identifier || '').trim();
488
455
  if (!ident) {
489
456
  return null;
490
457
  }
491
-
492
458
  const exact = stateBindings.find((entry) => String(entry?.key || '') === ident);
493
459
  if (exact && typeof exact.key === 'string') {
494
460
  return exact.key;
495
461
  }
496
-
497
462
  const suffix = `_${ident}`;
498
463
  const matches = stateBindings
499
464
  .map((entry) => String(entry?.key || ''))
500
465
  .filter((key) => key.endsWith(suffix));
501
-
502
466
  if (preferredKeys instanceof Set && preferredKeys.size > 0) {
503
467
  const preferredMatches = matches.filter((key) => preferredKeys.has(key));
504
468
  if (preferredMatches.length === 1) {
505
469
  return preferredMatches[0];
506
470
  }
507
471
  }
508
-
509
472
  if (matches.length === 1) {
510
473
  return matches[0];
511
474
  }
512
-
513
475
  return null;
514
476
  }
515
-
516
477
  function rewriteRefBindingIdentifiers(pageIr, preferredKeys = null) {
517
478
  if (!Array.isArray(pageIr?.ref_bindings) || pageIr.ref_bindings.length === 0) {
518
479
  return;
519
480
  }
520
-
521
481
  const stateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
522
482
  if (stateBindings.length === 0) {
523
483
  return;
524
484
  }
525
-
526
485
  for (const binding of pageIr.ref_bindings) {
527
486
  if (!binding || typeof binding !== 'object' || typeof binding.identifier !== 'string') {
528
487
  continue;
@@ -533,7 +492,6 @@ function rewriteRefBindingIdentifiers(pageIr, preferredKeys = null) {
533
492
  }
534
493
  }
535
494
  }
536
-
537
495
  /**
538
496
  * Rewrite unresolved page expressions using component script-aware mappings.
539
497
  *
@@ -554,7 +512,6 @@ function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambiguous) {
554
512
  return;
555
513
  }
556
514
  const bindings = Array.isArray(pageIr.expression_bindings) ? pageIr.expression_bindings : [];
557
-
558
515
  for (let index = 0; index < pageIr.expressions.length; index++) {
559
516
  const current = pageIr.expressions[index];
560
517
  if (typeof current !== 'string') {
@@ -563,21 +520,17 @@ function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambiguous) {
563
520
  if (ambiguous.has(current)) {
564
521
  continue;
565
522
  }
566
-
567
523
  const rewritten = expressionMap.get(current);
568
524
  const rewrittenBinding = bindingMap.get(current);
569
525
  if (rewritten && rewritten !== current) {
570
526
  pageIr.expressions[index] = rewritten;
571
527
  }
572
-
573
528
  if (!bindings[index] || typeof bindings[index] !== 'object') {
574
529
  continue;
575
530
  }
576
-
577
531
  if (rewritten && rewritten !== current && bindings[index].literal === current) {
578
532
  bindings[index].literal = rewritten;
579
533
  }
580
-
581
534
  if (rewrittenBinding) {
582
535
  bindings[index].compiled_expr = rewrittenBinding.compiled_expr;
583
536
  bindings[index].signal_index = rewrittenBinding.signal_index;
@@ -585,21 +538,18 @@ function applyExpressionRewrites(pageIr, expressionMap, bindingMap, ambiguous) {
585
538
  bindings[index].state_index = rewrittenBinding.state_index;
586
539
  bindings[index].component_instance = rewrittenBinding.component_instance;
587
540
  bindings[index].component_binding = rewrittenBinding.component_binding;
588
- } else if (rewritten && rewritten !== current && bindings[index].compiled_expr === current) {
541
+ }
542
+ else if (rewritten && rewritten !== current && bindings[index].compiled_expr === current) {
589
543
  bindings[index].compiled_expr = rewritten;
590
544
  }
591
-
592
- if (
593
- !rewrittenBinding &&
545
+ if (!rewrittenBinding &&
594
546
  (!rewritten || rewritten === current) &&
595
547
  bindings[index].literal === current &&
596
- bindings[index].compiled_expr === current
597
- ) {
548
+ bindings[index].compiled_expr === current) {
598
549
  bindings[index].compiled_expr = current;
599
550
  }
600
551
  }
601
552
  }
602
-
603
553
  function applyScopedIdentifierRewrites(pageIr, scopeRewrite) {
604
554
  if (!Array.isArray(pageIr?.expressions) || pageIr.expressions.length === 0) {
605
555
  return;
@@ -608,17 +558,14 @@ function applyScopedIdentifierRewrites(pageIr, scopeRewrite) {
608
558
  const rewriteContext = {
609
559
  scopeRewrite
610
560
  };
611
-
612
561
  for (let index = 0; index < pageIr.expressions.length; index++) {
613
562
  const current = pageIr.expressions[index];
614
563
  if (typeof current === 'string') {
615
564
  pageIr.expressions[index] = rewritePropsExpression(current, rewriteContext);
616
565
  }
617
-
618
566
  if (!bindings[index] || typeof bindings[index] !== 'object') {
619
567
  continue;
620
568
  }
621
-
622
569
  if (typeof bindings[index].literal === 'string') {
623
570
  bindings[index].literal = rewritePropsExpression(bindings[index].literal, rewriteContext);
624
571
  }
@@ -627,18 +574,15 @@ function applyScopedIdentifierRewrites(pageIr, scopeRewrite) {
627
574
  }
628
575
  }
629
576
  }
630
-
631
577
  function synthesizeSignalBackedCompiledExpressions(pageIr) {
632
578
  if (!Array.isArray(pageIr?.expression_bindings) || pageIr.expression_bindings.length === 0) {
633
579
  return;
634
580
  }
635
-
636
581
  const stateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
637
582
  const signals = Array.isArray(pageIr?.signals) ? pageIr.signals : [];
638
583
  if (stateBindings.length === 0 || signals.length === 0) {
639
584
  return;
640
585
  }
641
-
642
586
  const signalIndexByStateKey = new Map();
643
587
  for (let index = 0; index < signals.length; index++) {
644
588
  const stateIndex = signals[index]?.state_index;
@@ -650,7 +594,6 @@ function synthesizeSignalBackedCompiledExpressions(pageIr) {
650
594
  if (signalIndexByStateKey.size === 0) {
651
595
  return;
652
596
  }
653
-
654
597
  for (let index = 0; index < pageIr.expression_bindings.length; index++) {
655
598
  const binding = pageIr.expression_bindings[index];
656
599
  if (!binding || typeof binding !== 'object') {
@@ -659,7 +602,6 @@ function synthesizeSignalBackedCompiledExpressions(pageIr) {
659
602
  if (typeof binding.compiled_expr === 'string' && binding.compiled_expr.includes('signalMap.get(')) {
660
603
  continue;
661
604
  }
662
-
663
605
  const candidate = typeof binding.literal === 'string' && binding.literal.trim().length > 0
664
606
  ? binding.literal
665
607
  : typeof pageIr.expressions?.[index] === 'string'
@@ -668,7 +610,6 @@ function synthesizeSignalBackedCompiledExpressions(pageIr) {
668
610
  if (typeof candidate !== 'string' || candidate.trim().length === 0) {
669
611
  continue;
670
612
  }
671
-
672
613
  let rewritten = candidate;
673
614
  const signalIndices = [];
674
615
  for (const [stateKey, signalIndex] of signalIndexByStateKey.entries()) {
@@ -683,11 +624,9 @@ function synthesizeSignalBackedCompiledExpressions(pageIr) {
683
624
  rewritten = rewritten.replace(pattern, `signalMap.get(${signalIndex}).get()`);
684
625
  signalIndices.push(signalIndex);
685
626
  }
686
-
687
627
  if (rewritten === candidate || signalIndices.length === 0) {
688
628
  continue;
689
629
  }
690
-
691
630
  const uniqueSignalIndices = [...new Set(signalIndices)].sort((a, b) => a - b);
692
631
  binding.compiled_expr = rewritten;
693
632
  binding.signal_indices = uniqueSignalIndices;
@@ -700,20 +639,16 @@ function synthesizeSignalBackedCompiledExpressions(pageIr) {
700
639
  }
701
640
  }
702
641
  }
703
-
704
642
  function normalizeExpressionBindingDependencies(pageIr) {
705
643
  if (!Array.isArray(pageIr?.expression_bindings) || pageIr.expression_bindings.length === 0) {
706
644
  return;
707
645
  }
708
-
709
646
  const signals = Array.isArray(pageIr.signals) ? pageIr.signals : [];
710
647
  const dependencyRe = /signalMap\.get\((\d+)\)/g;
711
-
712
648
  for (const binding of pageIr.expression_bindings) {
713
649
  if (!binding || typeof binding !== 'object' || typeof binding.compiled_expr !== 'string') {
714
650
  continue;
715
651
  }
716
-
717
652
  const indices = [];
718
653
  dependencyRe.lastIndex = 0;
719
654
  let match;
@@ -723,36 +658,28 @@ function normalizeExpressionBindingDependencies(pageIr) {
723
658
  indices.push(index);
724
659
  }
725
660
  }
726
-
727
661
  if (indices.length === 0) {
728
662
  continue;
729
663
  }
730
-
731
664
  let signalIndices = [...new Set(indices)].sort((a, b) => a - b);
732
665
  if (Number.isInteger(binding.state_index)) {
733
666
  const owningSignalIndices = signals
734
667
  .map((signal, index) => signal?.state_index === binding.state_index ? index : null)
735
668
  .filter((value) => Number.isInteger(value));
736
- const extractedMatchState =
737
- signalIndices.length > 0 &&
669
+ const extractedMatchState = signalIndices.length > 0 &&
738
670
  signalIndices.every((index) => signals[index]?.state_index === binding.state_index);
739
671
  if (owningSignalIndices.length > 0 && !extractedMatchState) {
740
672
  signalIndices = owningSignalIndices;
741
673
  }
742
674
  }
743
-
744
- if (
745
- !Array.isArray(binding.signal_indices) ||
675
+ if (!Array.isArray(binding.signal_indices) ||
746
676
  binding.signal_indices.length === 0 ||
747
- binding.signal_indices.some((index) => signals[index]?.state_index !== binding.state_index)
748
- ) {
677
+ binding.signal_indices.some((index) => signals[index]?.state_index !== binding.state_index)) {
749
678
  binding.signal_indices = signalIndices;
750
679
  }
751
- if (
752
- (!Number.isInteger(binding.signal_index) ||
753
- signals[binding.signal_index]?.state_index !== binding.state_index) &&
754
- signalIndices.length === 1
755
- ) {
680
+ if ((!Number.isInteger(binding.signal_index) ||
681
+ signals[binding.signal_index]?.state_index !== binding.state_index) &&
682
+ signalIndices.length === 1) {
756
683
  binding.signal_index = signalIndices[0];
757
684
  }
758
685
  if (!Number.isInteger(binding.state_index) && Number.isInteger(binding.signal_index)) {
@@ -762,14 +689,10 @@ function normalizeExpressionBindingDependencies(pageIr) {
762
689
  }
763
690
  }
764
691
  if (signalIndices.length === 1) {
765
- binding.compiled_expr = binding.compiled_expr.replace(
766
- /signalMap\.get\(\d+\)/g,
767
- `signalMap.get(${signalIndices[0]})`
768
- );
692
+ binding.compiled_expr = binding.compiled_expr.replace(/signalMap\.get\(\d+\)/g, `signalMap.get(${signalIndices[0]})`);
769
693
  }
770
694
  }
771
695
  }
772
-
773
696
  /**
774
697
  * Rewrite legacy markup-literal identifiers in expression literals to the
775
698
  * internal `__ZENITH_INTERNAL_ZENHTML` binding used by the runtime.
@@ -784,39 +707,32 @@ function normalizeExpressionBindingDependencies(pageIr) {
784
707
  // Stored as concatenation so the drift gate scanner does not flag build.js itself.
785
708
  const _LEGACY_MARKUP_IDENT = 'zen' + 'html';
786
709
  const _LEGACY_MARKUP_RE = new RegExp(`\\b${_LEGACY_MARKUP_IDENT}\\b`, 'g');
787
-
788
710
  function rewriteLegacyMarkupIdentifiers(pageIr) {
789
711
  if (!Array.isArray(pageIr?.expressions) || pageIr.expressions.length === 0) {
790
712
  return;
791
713
  }
792
714
  const bindings = Array.isArray(pageIr.expression_bindings) ? pageIr.expression_bindings : [];
793
-
794
715
  for (let i = 0; i < pageIr.expressions.length; i++) {
795
716
  if (typeof pageIr.expressions[i] === 'string' && pageIr.expressions[i].includes(_LEGACY_MARKUP_IDENT)) {
796
717
  _LEGACY_MARKUP_RE.lastIndex = 0;
797
718
  pageIr.expressions[i] = pageIr.expressions[i].replace(_LEGACY_MARKUP_RE, '__ZENITH_INTERNAL_ZENHTML');
798
719
  }
799
- if (
800
- bindings[i] &&
720
+ if (bindings[i] &&
801
721
  typeof bindings[i] === 'object' &&
802
722
  typeof bindings[i].literal === 'string' &&
803
- bindings[i].literal.includes(_LEGACY_MARKUP_IDENT)
804
- ) {
723
+ bindings[i].literal.includes(_LEGACY_MARKUP_IDENT)) {
805
724
  _LEGACY_MARKUP_RE.lastIndex = 0;
806
725
  bindings[i].literal = bindings[i].literal.replace(_LEGACY_MARKUP_RE, '__ZENITH_INTERNAL_ZENHTML');
807
726
  }
808
- if (
809
- bindings[i] &&
727
+ if (bindings[i] &&
810
728
  typeof bindings[i] === 'object' &&
811
729
  typeof bindings[i].compiled_expr === 'string' &&
812
- bindings[i].compiled_expr.includes(_LEGACY_MARKUP_IDENT)
813
- ) {
730
+ bindings[i].compiled_expr.includes(_LEGACY_MARKUP_IDENT)) {
814
731
  _LEGACY_MARKUP_RE.lastIndex = 0;
815
732
  bindings[i].compiled_expr = bindings[i].compiled_expr.replace(_LEGACY_MARKUP_RE, '__ZENITH_INTERNAL_ZENHTML');
816
733
  }
817
734
  }
818
735
  }
819
-
820
736
  /**
821
737
  * @param {string} targetPath
822
738
  * @param {string} next
@@ -828,7 +744,6 @@ function writeIfChanged(targetPath, next) {
828
744
  }
829
745
  writeFileSync(targetPath, next, 'utf8');
830
746
  }
831
-
832
747
  /**
833
748
  * @param {string} routePath
834
749
  * @returns {string}
@@ -852,7 +767,6 @@ function routeParamsType(routePath) {
852
767
  }
853
768
  return `{ ${fields.join(', ')} }`;
854
769
  }
855
-
856
770
  /**
857
771
  * @param {Array<{ path: string, file: string }>} manifest
858
772
  * @returns {string}
@@ -866,13 +780,10 @@ function renderZenithRouteDts(manifest) {
866
780
  ' namespace Zenith {',
867
781
  ' interface RouteParamsMap {'
868
782
  ];
869
-
870
783
  const sortedManifest = [...manifest].sort((a, b) => a.path.localeCompare(b.path));
871
-
872
784
  for (const entry of sortedManifest) {
873
785
  lines.push(` ${JSON.stringify(entry.path)}: ${routeParamsType(entry.path)};`);
874
786
  }
875
-
876
787
  lines.push(' }');
877
788
  lines.push('');
878
789
  lines.push(' type ParamsFor<P extends keyof RouteParamsMap> = RouteParamsMap[P];');
@@ -881,7 +792,6 @@ function renderZenithRouteDts(manifest) {
881
792
  lines.push('');
882
793
  return `${lines.join('\n')}\n`;
883
794
  }
884
-
885
795
  /**
886
796
  * @returns {string}
887
797
  */
@@ -936,7 +846,6 @@ function renderZenithEnvDts() {
936
846
  ''
937
847
  ].join('\n');
938
848
  }
939
-
940
849
  /**
941
850
  * @param {string} pagesDir
942
851
  * @returns {string}
@@ -949,7 +858,6 @@ function deriveProjectRootFromPagesDir(pagesDir) {
949
858
  }
950
859
  return parent;
951
860
  }
952
-
953
861
  /**
954
862
  * @param {{ manifest: Array<{ path: string, file: string }>, pagesDir: string }} input
955
863
  * @returns {Promise<void>}
@@ -958,12 +866,10 @@ async function ensureZenithTypeDeclarations(input) {
958
866
  const projectRoot = deriveProjectRootFromPagesDir(input.pagesDir);
959
867
  const zenithDir = resolve(projectRoot, '.zenith');
960
868
  await mkdir(zenithDir, { recursive: true });
961
-
962
869
  const envPath = join(zenithDir, 'zenith-env.d.ts');
963
870
  const routesPath = join(zenithDir, 'zenith-routes.d.ts');
964
871
  writeIfChanged(envPath, renderZenithEnvDts());
965
872
  writeIfChanged(routesPath, renderZenithRouteDts(input.manifest));
966
-
967
873
  const tsconfigPath = resolve(projectRoot, 'tsconfig.json');
968
874
  if (!existsSync(tsconfigPath)) {
969
875
  return;
@@ -977,11 +883,11 @@ async function ensureZenithTypeDeclarations(input) {
977
883
  parsed.include = include;
978
884
  writeIfChanged(tsconfigPath, `${JSON.stringify(parsed, null, 2)}\n`);
979
885
  }
980
- } catch {
886
+ }
887
+ catch {
981
888
  // Non-JSON tsconfig variants are left untouched.
982
889
  }
983
890
  }
984
-
985
891
  /**
986
892
  * Extract one optional `<script server>` block from a page source.
987
893
  * Returns source with the block removed plus normalized server metadata.
@@ -994,131 +900,94 @@ async function ensureZenithTypeDeclarations(input) {
994
900
  function extractServerScript(source, sourceFile, compilerOpts = {}) {
995
901
  const scriptRe = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
996
902
  const serverMatches = [];
997
- const reservedServerExportRe =
998
- /\bexport\s+const\s+(?:data|prerender|guard|load)\b|\bexport\s+(?:async\s+)?function\s+(?:load|guard)\s*\(|\bexport\s+const\s+(?:load|guard)\s*=/;
999
-
903
+ const reservedServerExportRe = /\bexport\s+const\s+(?:data|prerender|guard|load)\b|\bexport\s+(?:async\s+)?function\s+(?:load|guard)\s*\(|\bexport\s+const\s+(?:load|guard)\s*=/;
1000
904
  for (const match of source.matchAll(scriptRe)) {
1001
905
  const attrs = String(match[1] || '');
1002
906
  const body = String(match[2] || '');
1003
907
  const isServer = /\bserver\b/i.test(attrs);
1004
-
1005
908
  if (!isServer && reservedServerExportRe.test(body)) {
1006
- throw new Error(
1007
- `Zenith server script contract violation:\n` +
909
+ throw new Error(`Zenith server script contract violation:\n` +
1008
910
  ` File: ${sourceFile}\n` +
1009
911
  ` Reason: guard/load/data exports are only allowed in <script server lang="ts"> or adjacent .guard.ts / .load.ts files\n` +
1010
- ` Example: move the export into <script server lang="ts">`
1011
- );
912
+ ` Example: move the export into <script server lang="ts">`);
1012
913
  }
1013
-
1014
914
  if (isServer) {
1015
915
  serverMatches.push(match);
1016
916
  }
1017
917
  }
1018
-
1019
918
  if (serverMatches.length === 0) {
1020
919
  return { source, serverScript: null };
1021
920
  }
1022
-
1023
921
  if (serverMatches.length > 1) {
1024
- throw new Error(
1025
- `Zenith server script contract violation:\n` +
922
+ throw new Error(`Zenith server script contract violation:\n` +
1026
923
  ` File: ${sourceFile}\n` +
1027
924
  ` Reason: multiple <script server> blocks are not supported\n` +
1028
- ` Example: keep exactly one <script server>...</script> block`
1029
- );
925
+ ` Example: keep exactly one <script server>...</script> block`);
1030
926
  }
1031
-
1032
927
  const match = serverMatches[0];
1033
928
  const full = match[0] || '';
1034
929
  const attrs = String(match[1] || '');
1035
-
1036
930
  const hasLangTs = /\blang\s*=\s*["']ts["']/i.test(attrs);
1037
931
  const hasLangJs = /\blang\s*=\s*["'](?:js|javascript)["']/i.test(attrs);
1038
932
  const hasAnyLang = /\blang\s*=/i.test(attrs);
1039
933
  const isTypescriptDefault = compilerOpts && compilerOpts.typescriptDefault === true;
1040
-
1041
934
  if (!hasLangTs) {
1042
935
  if (!isTypescriptDefault || hasLangJs || hasAnyLang) {
1043
- throw new Error(
1044
- `Zenith server script contract violation:\n` +
936
+ throw new Error(`Zenith server script contract violation:\n` +
1045
937
  ` File: ${sourceFile}\n` +
1046
938
  ` Reason: Zenith requires TypeScript server scripts. Add lang="ts" (or enable typescriptDefault).\n` +
1047
- ` Example: <script server lang="ts">`
1048
- );
939
+ ` Example: <script server lang="ts">`);
1049
940
  }
1050
941
  }
1051
-
1052
942
  const serverSource = String(match[2] || '').trim();
1053
943
  if (!serverSource) {
1054
- throw new Error(
1055
- `Zenith server script contract violation:\n` +
944
+ throw new Error(`Zenith server script contract violation:\n` +
1056
945
  ` File: ${sourceFile}\n` +
1057
946
  ` Reason: <script server> block is empty\n` +
1058
- ` Example: export const data = { ... }`
1059
- );
947
+ ` Example: export const data = { ... }`);
1060
948
  }
1061
-
1062
949
  const loadFnMatch = serverSource.match(/\bexport\s+(?:async\s+)?function\s+load\s*\(([^)]*)\)/);
1063
950
  const loadConstParenMatch = serverSource.match(/\bexport\s+const\s+load\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>/);
1064
- const loadConstSingleArgMatch = serverSource.match(
1065
- /\bexport\s+const\s+load\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/
1066
- );
951
+ const loadConstSingleArgMatch = serverSource.match(/\bexport\s+const\s+load\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
1067
952
  const hasLoad = Boolean(loadFnMatch || loadConstParenMatch || loadConstSingleArgMatch);
1068
- const loadMatchCount =
1069
- Number(Boolean(loadFnMatch)) +
953
+ const loadMatchCount = Number(Boolean(loadFnMatch)) +
1070
954
  Number(Boolean(loadConstParenMatch)) +
1071
955
  Number(Boolean(loadConstSingleArgMatch));
1072
956
  if (loadMatchCount > 1) {
1073
- throw new Error(
1074
- `Zenith server script contract violation:\n` +
957
+ throw new Error(`Zenith server script contract violation:\n` +
1075
958
  ` File: ${sourceFile}\n` +
1076
959
  ` Reason: multiple load exports detected\n` +
1077
- ` Example: keep exactly one export const load = async (ctx) => ({ ... })`
1078
- );
960
+ ` Example: keep exactly one export const load = async (ctx) => ({ ... })`);
1079
961
  }
1080
-
1081
962
  const guardFnMatch = serverSource.match(/\bexport\s+(?:async\s+)?function\s+guard\s*\(([^)]*)\)/);
1082
963
  const guardConstParenMatch = serverSource.match(/\bexport\s+const\s+guard\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>/);
1083
- const guardConstSingleArgMatch = serverSource.match(
1084
- /\bexport\s+const\s+guard\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/
1085
- );
964
+ const guardConstSingleArgMatch = serverSource.match(/\bexport\s+const\s+guard\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
1086
965
  const hasGuard = Boolean(guardFnMatch || guardConstParenMatch || guardConstSingleArgMatch);
1087
- const guardMatchCount =
1088
- Number(Boolean(guardFnMatch)) +
966
+ const guardMatchCount = Number(Boolean(guardFnMatch)) +
1089
967
  Number(Boolean(guardConstParenMatch)) +
1090
968
  Number(Boolean(guardConstSingleArgMatch));
1091
969
  if (guardMatchCount > 1) {
1092
- throw new Error(
1093
- `Zenith server script contract violation:\n` +
970
+ throw new Error(`Zenith server script contract violation:\n` +
1094
971
  ` File: ${sourceFile}\n` +
1095
972
  ` Reason: multiple guard exports detected\n` +
1096
- ` Example: keep exactly one export const guard = async (ctx) => ({ ... })`
1097
- );
973
+ ` Example: keep exactly one export const guard = async (ctx) => ({ ... })`);
1098
974
  }
1099
-
1100
975
  const hasData = /\bexport\s+const\s+data\b/.test(serverSource);
1101
976
  const hasSsrData = /\bexport\s+const\s+ssr_data\b/.test(serverSource);
1102
977
  const hasSsr = /\bexport\s+const\s+ssr\b/.test(serverSource);
1103
978
  const hasProps = /\bexport\s+const\s+props\b/.test(serverSource);
1104
-
1105
979
  if (hasData && hasLoad) {
1106
- throw new Error(
1107
- `Zenith server script contract violation:\n` +
980
+ throw new Error(`Zenith server script contract violation:\n` +
1108
981
  ` File: ${sourceFile}\n` +
1109
982
  ` Reason: export either data or load(ctx), not both\n` +
1110
- ` Example: remove data and return payload from load(ctx)`
1111
- );
983
+ ` Example: remove data and return payload from load(ctx)`);
1112
984
  }
1113
985
  if ((hasData || hasLoad) && (hasSsrData || hasSsr || hasProps)) {
1114
- throw new Error(
1115
- `Zenith server script contract violation:\n` +
986
+ throw new Error(`Zenith server script contract violation:\n` +
1116
987
  ` File: ${sourceFile}\n` +
1117
988
  ` Reason: data/load cannot be combined with legacy ssr_data/ssr/props exports\n` +
1118
- ` Example: use only export const data or export const load`
1119
- );
989
+ ` Example: use only export const data or export const load`);
1120
990
  }
1121
-
1122
991
  if (hasLoad) {
1123
992
  const singleArg = String(loadConstSingleArgMatch?.[1] || '').trim();
1124
993
  const paramsText = String((loadFnMatch || loadConstParenMatch)?.[1] || '').trim();
@@ -1128,15 +997,12 @@ function extractServerScript(source, sourceFile, compilerOpts = {}) {
1128
997
  ? 0
1129
998
  : paramsText.split(',').length;
1130
999
  if (arity !== 1) {
1131
- throw new Error(
1132
- `Zenith server script contract violation:\n` +
1000
+ throw new Error(`Zenith server script contract violation:\n` +
1133
1001
  ` File: ${sourceFile}\n` +
1134
1002
  ` Reason: load(ctx) must accept exactly one argument\n` +
1135
- ` Example: export const load = async (ctx) => ({ ... })`
1136
- );
1003
+ ` Example: export const load = async (ctx) => ({ ... })`);
1137
1004
  }
1138
1005
  }
1139
-
1140
1006
  if (hasGuard) {
1141
1007
  const singleArg = String(guardConstSingleArgMatch?.[1] || '').trim();
1142
1008
  const paramsText = String((guardFnMatch || guardConstParenMatch)?.[1] || '').trim();
@@ -1146,26 +1012,21 @@ function extractServerScript(source, sourceFile, compilerOpts = {}) {
1146
1012
  ? 0
1147
1013
  : paramsText.split(',').length;
1148
1014
  if (arity !== 1) {
1149
- throw new Error(
1150
- `Zenith server script contract violation:\n` +
1015
+ throw new Error(`Zenith server script contract violation:\n` +
1151
1016
  ` File: ${sourceFile}\n` +
1152
1017
  ` Reason: guard(ctx) must accept exactly one argument\n` +
1153
- ` Example: export const guard = async (ctx) => ({ ... })`
1154
- );
1018
+ ` Example: export const guard = async (ctx) => ({ ... })`);
1155
1019
  }
1156
1020
  }
1157
-
1158
1021
  const prerenderMatch = serverSource.match(/\bexport\s+const\s+prerender\s*=\s*([^\n;]+)/);
1159
1022
  let prerender = false;
1160
1023
  if (prerenderMatch) {
1161
1024
  const rawValue = String(prerenderMatch[1] || '').trim();
1162
1025
  if (!/^(true|false)\b/.test(rawValue)) {
1163
- throw new Error(
1164
- `Zenith server script contract violation:\n` +
1026
+ throw new Error(`Zenith server script contract violation:\n` +
1165
1027
  ` File: ${sourceFile}\n` +
1166
1028
  ` Reason: prerender must be a boolean literal\n` +
1167
- ` Example: export const prerender = true`
1168
- );
1029
+ ` Example: export const prerender = true`);
1169
1030
  }
1170
1031
  prerender = rawValue.startsWith('true');
1171
1032
  }
@@ -1182,10 +1043,8 @@ function extractServerScript(source, sourceFile, compilerOpts = {}) {
1182
1043
  }
1183
1044
  };
1184
1045
  }
1185
-
1186
1046
  const end = start + full.length;
1187
1047
  const stripped = `${source.slice(0, start)}${source.slice(end)}`;
1188
-
1189
1048
  return {
1190
1049
  source: stripped,
1191
1050
  serverScript: {
@@ -1197,9 +1056,7 @@ function extractServerScript(source, sourceFile, compilerOpts = {}) {
1197
1056
  }
1198
1057
  };
1199
1058
  }
1200
-
1201
1059
  const OPEN_COMPONENT_TAG_RE = /<([A-Z][a-zA-Z0-9]*)(\s[^<>]*?)?\s*(\/?)>/g;
1202
-
1203
1060
  /**
1204
1061
  * Collect original attribute strings for component usages in a page source.
1205
1062
  *
@@ -1225,7 +1082,6 @@ function collectComponentUsageAttrs(source, registry, ownerPath = null) {
1225
1082
  }
1226
1083
  return out;
1227
1084
  }
1228
-
1229
1085
  /**
1230
1086
  * Collect component usage attrs recursively so nested component callsites
1231
1087
  * receive deterministic props preludes during page-hoist merging.
@@ -1248,7 +1104,6 @@ function collectRecursiveComponentUsageAttrs(source, registry, ownerPath = null,
1248
1104
  }
1249
1105
  out.get(name).push(...attrsList);
1250
1106
  }
1251
-
1252
1107
  for (const name of local.keys()) {
1253
1108
  const compPath = registry.get(name);
1254
1109
  if (!compPath || visitedFiles.has(compPath)) {
@@ -1258,10 +1113,8 @@ function collectRecursiveComponentUsageAttrs(source, registry, ownerPath = null,
1258
1113
  const componentSource = readFileSync(compPath, 'utf8');
1259
1114
  collectRecursiveComponentUsageAttrs(componentSource, registry, compPath, visitedFiles, out);
1260
1115
  }
1261
-
1262
1116
  return out;
1263
1117
  }
1264
-
1265
1118
  /**
1266
1119
  * Merge a component's IR into the page IR.
1267
1120
  *
@@ -1293,12 +1146,10 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
1293
1146
  }
1294
1147
  }
1295
1148
  }
1296
-
1297
1149
  // Merge component_instances
1298
1150
  if (compIr.component_instances?.length) {
1299
1151
  pageIr.component_instances.push(...compIr.component_instances);
1300
1152
  }
1301
-
1302
1153
  if (knownRefKeys instanceof Set && Array.isArray(compIr.ref_bindings)) {
1303
1154
  const componentStateBindings = Array.isArray(compIr?.hoisted?.state) ? compIr.hoisted.state : [];
1304
1155
  for (const binding of compIr.ref_bindings) {
@@ -1309,7 +1160,6 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
1309
1160
  knownRefKeys.add(resolved || binding.identifier);
1310
1161
  }
1311
1162
  }
1312
-
1313
1163
  // Merge hoisted imports (deduplicated, rebased to the page file path)
1314
1164
  if (compIr.hoisted?.imports?.length) {
1315
1165
  for (const imp of compIr.hoisted.imports) {
@@ -1325,7 +1175,6 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
1325
1175
  }
1326
1176
  }
1327
1177
  }
1328
-
1329
1178
  // Merge hoisted symbol/state tables for runtime literal evaluation.
1330
1179
  // Component-expanded expressions can reference rewritten component symbols,
1331
1180
  // so state keys/values must be present in the page envelope.
@@ -1352,11 +1201,9 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
1352
1201
  }
1353
1202
  }
1354
1203
  if (Array.isArray(compIr.hoisted.state)) {
1355
- const existingKeys = new Set(
1356
- (pageIr.hoisted.state || [])
1357
- .map((entry) => entry && typeof entry === 'object' ? entry.key : null)
1358
- .filter(Boolean)
1359
- );
1204
+ const existingKeys = new Set((pageIr.hoisted.state || [])
1205
+ .map((entry) => entry && typeof entry === 'object' ? entry.key : null)
1206
+ .filter(Boolean));
1360
1207
  for (const stateEntry of compIr.hoisted.state) {
1361
1208
  if (!stateEntry || typeof stateEntry !== 'object') {
1362
1209
  continue;
@@ -1372,18 +1219,14 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
1372
1219
  }
1373
1220
  }
1374
1221
  }
1375
-
1376
1222
  if (options.includeCode && Array.isArray(compIr.signals)) {
1377
1223
  pageIr.signals = Array.isArray(pageIr.signals) ? pageIr.signals : [];
1378
- const existingSignalStateKeys = new Set(
1379
- pageIr.signals
1380
- .map((signal) => {
1381
- const stateIndex = signal?.state_index;
1382
- return Number.isInteger(stateIndex) ? pageIr.hoisted.state?.[stateIndex]?.key : null;
1383
- })
1384
- .filter(Boolean)
1385
- );
1386
-
1224
+ const existingSignalStateKeys = new Set(pageIr.signals
1225
+ .map((signal) => {
1226
+ const stateIndex = signal?.state_index;
1227
+ return Number.isInteger(stateIndex) ? pageIr.hoisted.state?.[stateIndex]?.key : null;
1228
+ })
1229
+ .filter(Boolean));
1387
1230
  for (const signal of compIr.signals) {
1388
1231
  if (!signal || !Number.isInteger(signal.state_index)) {
1389
1232
  continue;
@@ -1407,7 +1250,6 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
1407
1250
  });
1408
1251
  }
1409
1252
  }
1410
-
1411
1253
  // Merge hoisted code blocks (rebased to the page file path)
1412
1254
  if (options.includeCode && compIr.hoisted?.code?.length) {
1413
1255
  for (const block of compIr.hoisted.code) {
@@ -1415,11 +1257,7 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
1415
1257
  const filteredImports = options.cssImportsOnly
1416
1258
  ? stripNonCssStaticImportsInSource(rebased)
1417
1259
  : rebased;
1418
- const withPropsPrelude = injectPropsPrelude(
1419
- filteredImports,
1420
- options.componentAttrs || '',
1421
- options.componentAttrsRewrite || null
1422
- );
1260
+ const withPropsPrelude = injectPropsPrelude(filteredImports, options.componentAttrs || '', options.componentAttrsRewrite || null);
1423
1261
  const transpiled = transpileTypeScriptToJs(withPropsPrelude, compPath);
1424
1262
  const deduped = dedupeStaticImportsInSource(transpiled, seenStaticImports);
1425
1263
  const deferred = deferComponentRuntimeBlock(deduped);
@@ -1429,7 +1267,6 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
1429
1267
  }
1430
1268
  }
1431
1269
  }
1432
-
1433
1270
  /**
1434
1271
  * @param {string} spec
1435
1272
  * @returns {boolean}
@@ -1437,7 +1274,6 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
1437
1274
  function isRelativeSpecifier(spec) {
1438
1275
  return spec.startsWith('./') || spec.startsWith('../');
1439
1276
  }
1440
-
1441
1277
  /**
1442
1278
  * @param {string} spec
1443
1279
  * @param {string} fromFile
@@ -1448,7 +1284,6 @@ function rebaseRelativeSpecifier(spec, fromFile, toFile) {
1448
1284
  if (!isRelativeSpecifier(spec)) {
1449
1285
  return spec;
1450
1286
  }
1451
-
1452
1287
  const absoluteTarget = resolve(dirname(fromFile), spec);
1453
1288
  let rebased = relative(dirname(toFile), absoluteTarget).replaceAll('\\', '/');
1454
1289
  if (!rebased.startsWith('.')) {
@@ -1456,7 +1291,6 @@ function rebaseRelativeSpecifier(spec, fromFile, toFile) {
1456
1291
  }
1457
1292
  return rebased;
1458
1293
  }
1459
-
1460
1294
  /**
1461
1295
  * @param {string} line
1462
1296
  * @param {string} fromFile
@@ -1468,16 +1302,13 @@ function rewriteStaticImportLine(line, fromFile, toFile) {
1468
1302
  if (!match) {
1469
1303
  return line;
1470
1304
  }
1471
-
1472
1305
  const spec = match[1];
1473
1306
  if (!isRelativeSpecifier(spec)) {
1474
1307
  return line;
1475
1308
  }
1476
-
1477
1309
  const rebased = rebaseRelativeSpecifier(spec, fromFile, toFile);
1478
1310
  return replaceImportSpecifierLiteral(line, spec, rebased);
1479
1311
  }
1480
-
1481
1312
  /**
1482
1313
  * @param {string} line
1483
1314
  * @returns {string | null}
@@ -1486,7 +1317,6 @@ function extractStaticImportSpecifier(line) {
1486
1317
  const match = line.match(/^\s*import(?:\s+[^'"]+?\s+from)?\s*['"]([^'"]+)['"]\s*;?\s*$/);
1487
1318
  return match ? match[1] : null;
1488
1319
  }
1489
-
1490
1320
  /**
1491
1321
  * @param {string} spec
1492
1322
  * @returns {boolean}
@@ -1494,7 +1324,6 @@ function extractStaticImportSpecifier(line) {
1494
1324
  function isCssSpecifier(spec) {
1495
1325
  return /\.css(?:[?#].*)?$/i.test(spec);
1496
1326
  }
1497
-
1498
1327
  /**
1499
1328
  * @param {string} source
1500
1329
  * @param {string} fromFile
@@ -1502,12 +1331,8 @@ function isCssSpecifier(spec) {
1502
1331
  * @returns {string}
1503
1332
  */
1504
1333
  function rewriteStaticImportsInSource(source, fromFile, toFile) {
1505
- return source.replace(
1506
- /(^\s*import(?:\s+[^'"]+?\s+from)?\s*['"])([^'"]+)(['"]\s*;?\s*$)/gm,
1507
- (_full, prefix, spec, suffix) => `${prefix}${rebaseRelativeSpecifier(spec, fromFile, toFile)}${suffix}`
1508
- );
1334
+ return source.replace(/(^\s*import(?:\s+[^'"]+?\s+from)?\s*['"])([^'"]+)(['"]\s*;?\s*$)/gm, (_full, prefix, spec, suffix) => `${prefix}${rebaseRelativeSpecifier(spec, fromFile, toFile)}${suffix}`);
1509
1335
  }
1510
-
1511
1336
  /**
1512
1337
  * @param {string} line
1513
1338
  * @param {string} oldSpec
@@ -1519,15 +1344,12 @@ function replaceImportSpecifierLiteral(line, oldSpec, newSpec) {
1519
1344
  if (line.includes(single)) {
1520
1345
  return line.replace(single, `'${newSpec}'`);
1521
1346
  }
1522
-
1523
1347
  const dbl = `"${oldSpec}"`;
1524
1348
  if (line.includes(dbl)) {
1525
1349
  return line.replace(dbl, `"${newSpec}"`);
1526
1350
  }
1527
-
1528
1351
  return line;
1529
1352
  }
1530
-
1531
1353
  /**
1532
1354
  * @param {string} source
1533
1355
  * @param {string} sourceFile
@@ -1538,7 +1360,6 @@ function transpileTypeScriptToJs(source, sourceFile) {
1538
1360
  if (!ts) {
1539
1361
  return source;
1540
1362
  }
1541
-
1542
1363
  try {
1543
1364
  const output = ts.transpileModule(source, {
1544
1365
  fileName: sourceFile,
@@ -1552,13 +1373,12 @@ function transpileTypeScriptToJs(source, sourceFile) {
1552
1373
  reportDiagnostics: false,
1553
1374
  });
1554
1375
  return output.outputText;
1555
- } catch {
1376
+ }
1377
+ catch {
1556
1378
  return source;
1557
1379
  }
1558
1380
  }
1559
-
1560
1381
  const DEFERRED_RUNTIME_CALLS = new Set(['zenMount', 'zenEffect', 'zeneffect']);
1561
-
1562
1382
  /**
1563
1383
  * Split top-level runtime side-effect calls from hoistable declarations.
1564
1384
  *
@@ -1574,27 +1394,18 @@ function splitDeferredRuntimeCalls(body) {
1574
1394
  if (!ts || typeof body !== 'string' || body.trim().length === 0) {
1575
1395
  return { hoisted: body, deferred: '' };
1576
1396
  }
1577
-
1578
1397
  let sourceFile;
1579
1398
  try {
1580
- sourceFile = ts.createSourceFile(
1581
- 'zenith-component-runtime.ts',
1582
- body,
1583
- ts.ScriptTarget.Latest,
1584
- true,
1585
- ts.ScriptKind.TS
1586
- );
1587
- } catch {
1399
+ sourceFile = ts.createSourceFile('zenith-component-runtime.ts', body, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1400
+ }
1401
+ catch {
1588
1402
  return { hoisted: body, deferred: '' };
1589
1403
  }
1590
-
1591
1404
  if (!sourceFile || !Array.isArray(sourceFile.statements) || sourceFile.statements.length === 0) {
1592
1405
  return { hoisted: body, deferred: '' };
1593
1406
  }
1594
-
1595
1407
  /** @type {Array<{ start: number, end: number }>} */
1596
1408
  const ranges = [];
1597
-
1598
1409
  for (const statement of sourceFile.statements) {
1599
1410
  if (!ts.isExpressionStatement(statement)) {
1600
1411
  continue;
@@ -1602,16 +1413,13 @@ function splitDeferredRuntimeCalls(body) {
1602
1413
  if (!ts.isCallExpression(statement.expression)) {
1603
1414
  continue;
1604
1415
  }
1605
-
1606
1416
  let callee = statement.expression.expression;
1607
1417
  while (ts.isParenthesizedExpression(callee)) {
1608
1418
  callee = callee.expression;
1609
1419
  }
1610
-
1611
1420
  if (!ts.isIdentifier(callee) || !DEFERRED_RUNTIME_CALLS.has(callee.text)) {
1612
1421
  continue;
1613
1422
  }
1614
-
1615
1423
  const start = typeof statement.getFullStart === 'function'
1616
1424
  ? statement.getFullStart()
1617
1425
  : statement.pos;
@@ -1621,11 +1429,9 @@ function splitDeferredRuntimeCalls(body) {
1621
1429
  }
1622
1430
  ranges.push({ start, end });
1623
1431
  }
1624
-
1625
1432
  if (ranges.length === 0) {
1626
1433
  return { hoisted: body, deferred: '' };
1627
1434
  }
1628
-
1629
1435
  ranges.sort((a, b) => a.start - b.start);
1630
1436
  /** @type {Array<{ start: number, end: number }>} */
1631
1437
  const merged = [];
@@ -1639,11 +1445,9 @@ function splitDeferredRuntimeCalls(body) {
1639
1445
  last.end = range.end;
1640
1446
  }
1641
1447
  }
1642
-
1643
1448
  let cursor = 0;
1644
1449
  let hoisted = '';
1645
1450
  let deferred = '';
1646
-
1647
1451
  for (const range of merged) {
1648
1452
  if (range.start > cursor) {
1649
1453
  hoisted += body.slice(cursor, range.start);
@@ -1654,14 +1458,11 @@ function splitDeferredRuntimeCalls(body) {
1654
1458
  }
1655
1459
  cursor = range.end;
1656
1460
  }
1657
-
1658
1461
  if (cursor < body.length) {
1659
1462
  hoisted += body.slice(cursor);
1660
1463
  }
1661
-
1662
1464
  return { hoisted, deferred };
1663
1465
  }
1664
-
1665
1466
  /**
1666
1467
  * @param {string} source
1667
1468
  * @param {Set<string>} seenStaticImports
@@ -1670,14 +1471,12 @@ function splitDeferredRuntimeCalls(body) {
1670
1471
  function dedupeStaticImportsInSource(source, seenStaticImports) {
1671
1472
  const lines = source.split('\n');
1672
1473
  const kept = [];
1673
-
1674
1474
  for (const line of lines) {
1675
1475
  const spec = extractStaticImportSpecifier(line);
1676
1476
  if (!spec) {
1677
1477
  kept.push(line);
1678
1478
  continue;
1679
1479
  }
1680
-
1681
1480
  const key = line.trim();
1682
1481
  if (seenStaticImports.has(key)) {
1683
1482
  continue;
@@ -1685,10 +1484,8 @@ function dedupeStaticImportsInSource(source, seenStaticImports) {
1685
1484
  seenStaticImports.add(key);
1686
1485
  kept.push(line);
1687
1486
  }
1688
-
1689
1487
  return kept.join('\n');
1690
1488
  }
1691
-
1692
1489
  /**
1693
1490
  * @param {string} source
1694
1491
  * @returns {string}
@@ -1708,7 +1505,6 @@ function stripNonCssStaticImportsInSource(source) {
1708
1505
  }
1709
1506
  return kept.join('\n');
1710
1507
  }
1711
-
1712
1508
  /**
1713
1509
  * @param {string} key
1714
1510
  * @returns {string}
@@ -1719,7 +1515,6 @@ function renderObjectKey(key) {
1719
1515
  }
1720
1516
  return JSON.stringify(key);
1721
1517
  }
1722
-
1723
1518
  /**
1724
1519
  * @param {string} value
1725
1520
  * @returns {string | null}
@@ -1733,7 +1528,6 @@ function deriveScopedIdentifierAlias(value) {
1733
1528
  const candidate = parts.length > 1 ? parts[parts.length - 1] : ident;
1734
1529
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(candidate) ? candidate : ident;
1735
1530
  }
1736
-
1737
1531
  /**
1738
1532
  * @param {string} source
1739
1533
  * @returns {string[]}
@@ -1743,7 +1537,6 @@ function extractDeclaredIdentifiers(source) {
1743
1537
  if (!text) {
1744
1538
  return [];
1745
1539
  }
1746
-
1747
1540
  const ts = loadTypeScriptApi();
1748
1541
  if (ts) {
1749
1542
  const sourceFile = ts.createSourceFile('zenith-hoisted-declaration.ts', text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
@@ -1761,7 +1554,6 @@ function extractDeclaredIdentifiers(source) {
1761
1554
  }
1762
1555
  }
1763
1556
  };
1764
-
1765
1557
  for (const statement of sourceFile.statements) {
1766
1558
  if (!ts.isVariableStatement(statement)) {
1767
1559
  continue;
@@ -1770,12 +1562,10 @@ function extractDeclaredIdentifiers(source) {
1770
1562
  collectBindingNames(declaration.name);
1771
1563
  }
1772
1564
  }
1773
-
1774
1565
  if (identifiers.length > 0) {
1775
1566
  return identifiers;
1776
1567
  }
1777
1568
  }
1778
-
1779
1569
  const fallback = [];
1780
1570
  const match = text.match(/^\s*(?:const|let|var)\s+([\s\S]+?);?\s*$/);
1781
1571
  if (!match) {
@@ -1789,7 +1579,6 @@ function extractDeclaredIdentifiers(source) {
1789
1579
  }
1790
1580
  return fallback;
1791
1581
  }
1792
-
1793
1582
  /**
1794
1583
  * @param {Map<string, string>} map
1795
1584
  * @param {Set<string>} ambiguous
@@ -1810,7 +1599,6 @@ function recordScopedIdentifierRewrite(map, ambiguous, raw, rewritten) {
1810
1599
  map.set(raw, rewritten);
1811
1600
  }
1812
1601
  }
1813
-
1814
1602
  /**
1815
1603
  * @param {object | null | undefined} ir
1816
1604
  * @returns {{ map: Map<string, string>, ambiguous: Set<string> }}
@@ -1820,13 +1608,11 @@ function buildScopedIdentifierRewrite(ir) {
1820
1608
  if (!ir || typeof ir !== 'object') {
1821
1609
  return out;
1822
1610
  }
1823
-
1824
1611
  const stateBindings = Array.isArray(ir?.hoisted?.state) ? ir.hoisted.state : [];
1825
1612
  for (const stateEntry of stateBindings) {
1826
1613
  const key = typeof stateEntry?.key === 'string' ? stateEntry.key : null;
1827
1614
  recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(key), key);
1828
1615
  }
1829
-
1830
1616
  const functionBindings = Array.isArray(ir?.hoisted?.functions) ? ir.hoisted.functions : [];
1831
1617
  for (const fnName of functionBindings) {
1832
1618
  if (typeof fnName !== 'string') {
@@ -1834,7 +1620,6 @@ function buildScopedIdentifierRewrite(ir) {
1834
1620
  }
1835
1621
  recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(fnName), fnName);
1836
1622
  }
1837
-
1838
1623
  const declarations = Array.isArray(ir?.hoisted?.declarations) ? ir.hoisted.declarations : [];
1839
1624
  for (const declaration of declarations) {
1840
1625
  if (typeof declaration !== 'string') {
@@ -1844,24 +1629,21 @@ function buildScopedIdentifierRewrite(ir) {
1844
1629
  recordScopedIdentifierRewrite(out.map, out.ambiguous, deriveScopedIdentifierAlias(identifier), identifier);
1845
1630
  }
1846
1631
  }
1847
-
1848
1632
  return out;
1849
1633
  }
1850
-
1851
1634
  function rewriteIdentifiersWithinExpression(expr, scopeMap, scopeAmbiguous) {
1852
1635
  const ts = loadTypeScriptApi();
1853
1636
  if (!(scopeMap instanceof Map) || !ts) {
1854
1637
  return expr;
1855
1638
  }
1856
-
1857
1639
  const wrapped = `const __zenith_expr__ = (${expr});`;
1858
1640
  let sourceFile;
1859
1641
  try {
1860
1642
  sourceFile = ts.createSourceFile('zenith-expression.ts', wrapped, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1861
- } catch {
1643
+ }
1644
+ catch {
1862
1645
  return expr;
1863
1646
  }
1864
-
1865
1647
  const statement = sourceFile.statements[0];
1866
1648
  if (!statement || !ts.isVariableStatement(statement)) {
1867
1649
  return expr;
@@ -1871,7 +1653,6 @@ function rewriteIdentifiersWithinExpression(expr, scopeMap, scopeAmbiguous) {
1871
1653
  if (!root) {
1872
1654
  return expr;
1873
1655
  }
1874
-
1875
1656
  const replacements = [];
1876
1657
  const collectBoundNames = (name, target) => {
1877
1658
  if (ts.isIdentifier(name)) {
@@ -1922,15 +1703,12 @@ function rewriteIdentifiersWithinExpression(expr, scopeMap, scopeAmbiguous) {
1922
1703
  collectBoundNames(param.name, nextBindings);
1923
1704
  }
1924
1705
  }
1925
-
1926
1706
  if (ts.isIdentifier(node) && !shouldSkipIdentifier(node, nextBindings)) {
1927
1707
  const rewritten = scopeMap.get(node.text);
1928
- if (
1929
- typeof rewritten === 'string' &&
1708
+ if (typeof rewritten === 'string' &&
1930
1709
  rewritten.length > 0 &&
1931
1710
  rewritten !== node.text &&
1932
- !(scopeAmbiguous instanceof Set && scopeAmbiguous.has(node.text))
1933
- ) {
1711
+ !(scopeAmbiguous instanceof Set && scopeAmbiguous.has(node.text))) {
1934
1712
  replacements.push({
1935
1713
  start: node.getStart(sourceFile),
1936
1714
  end: node.getEnd(),
@@ -1938,20 +1716,16 @@ function rewriteIdentifiersWithinExpression(expr, scopeMap, scopeAmbiguous) {
1938
1716
  });
1939
1717
  }
1940
1718
  }
1941
-
1942
1719
  ts.forEachChild(node, (child) => visit(child, nextBindings));
1943
1720
  };
1944
-
1945
1721
  visit(root, new Set());
1946
1722
  if (replacements.length === 0) {
1947
1723
  return expr;
1948
1724
  }
1949
-
1950
1725
  let rewritten = wrapped;
1951
1726
  for (const replacement of replacements.sort((a, b) => b.start - a.start)) {
1952
1727
  rewritten = `${rewritten.slice(0, replacement.start)}${replacement.text}${rewritten.slice(replacement.end)}`;
1953
1728
  }
1954
-
1955
1729
  const prefix = 'const __zenith_expr__ = (';
1956
1730
  const suffix = ');';
1957
1731
  if (!rewritten.startsWith(prefix) || !rewritten.endsWith(suffix)) {
@@ -1959,7 +1733,6 @@ function rewriteIdentifiersWithinExpression(expr, scopeMap, scopeAmbiguous) {
1959
1733
  }
1960
1734
  return rewritten.slice(prefix.length, rewritten.length - suffix.length);
1961
1735
  }
1962
-
1963
1736
  /**
1964
1737
  * @param {string} expr
1965
1738
  * @param {{
@@ -1973,19 +1746,15 @@ function rewritePropsExpression(expr, rewriteContext = null) {
1973
1746
  if (!trimmed) {
1974
1747
  return trimmed;
1975
1748
  }
1976
-
1977
1749
  const expressionMap = rewriteContext?.expressionRewrite?.map;
1978
1750
  const expressionAmbiguous = rewriteContext?.expressionRewrite?.ambiguous;
1979
- if (
1980
- expressionMap instanceof Map &&
1981
- !(expressionAmbiguous instanceof Set && expressionAmbiguous.has(trimmed))
1982
- ) {
1751
+ if (expressionMap instanceof Map &&
1752
+ !(expressionAmbiguous instanceof Set && expressionAmbiguous.has(trimmed))) {
1983
1753
  const exact = expressionMap.get(trimmed);
1984
1754
  if (typeof exact === 'string' && exact.length > 0) {
1985
1755
  return exact;
1986
1756
  }
1987
1757
  }
1988
-
1989
1758
  const scopeMap = rewriteContext?.scopeRewrite?.map;
1990
1759
  const scopeAmbiguous = rewriteContext?.scopeRewrite?.ambiguous;
1991
1760
  const rootMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)([\s\S]*)$/);
@@ -1995,20 +1764,16 @@ function rewritePropsExpression(expr, rewriteContext = null) {
1995
1764
  if (!rootMatch) {
1996
1765
  return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
1997
1766
  }
1998
-
1999
1767
  const root = rootMatch[1];
2000
1768
  if (scopeAmbiguous instanceof Set && scopeAmbiguous.has(root)) {
2001
1769
  return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
2002
1770
  }
2003
-
2004
1771
  const rewrittenRoot = scopeMap.get(root);
2005
1772
  if (typeof rewrittenRoot !== 'string' || rewrittenRoot.length === 0 || rewrittenRoot === root) {
2006
1773
  return rewriteIdentifiersWithinExpression(trimmed, scopeMap, scopeAmbiguous);
2007
1774
  }
2008
-
2009
1775
  return `${rewrittenRoot}${rootMatch[2]}`;
2010
1776
  }
2011
-
2012
1777
  /**
2013
1778
  * @param {string} attrs
2014
1779
  * @param {{
@@ -2022,7 +1787,6 @@ function renderPropsLiteralFromAttrs(attrs, rewriteContext = null) {
2022
1787
  if (!src) {
2023
1788
  return '{}';
2024
1789
  }
2025
-
2026
1790
  const entries = [];
2027
1791
  const attrRe = /([A-Za-z_$][A-Za-z0-9_$-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|\{([\s\S]*?)\}))?/g;
2028
1792
  let match;
@@ -2031,30 +1795,27 @@ function renderPropsLiteralFromAttrs(attrs, rewriteContext = null) {
2031
1795
  if (!rawName || rawName.startsWith('on:')) {
2032
1796
  continue;
2033
1797
  }
2034
-
2035
1798
  const doubleQuoted = match[2];
2036
1799
  const singleQuoted = match[3];
2037
1800
  const expressionValue = match[4];
2038
1801
  let valueCode = 'true';
2039
1802
  if (doubleQuoted !== undefined) {
2040
1803
  valueCode = JSON.stringify(doubleQuoted);
2041
- } else if (singleQuoted !== undefined) {
1804
+ }
1805
+ else if (singleQuoted !== undefined) {
2042
1806
  valueCode = JSON.stringify(singleQuoted);
2043
- } else if (expressionValue !== undefined) {
1807
+ }
1808
+ else if (expressionValue !== undefined) {
2044
1809
  const trimmed = String(expressionValue).trim();
2045
1810
  valueCode = trimmed.length > 0 ? rewritePropsExpression(trimmed, rewriteContext) : 'undefined';
2046
1811
  }
2047
-
2048
1812
  entries.push(`${renderObjectKey(rawName)}: ${valueCode}`);
2049
1813
  }
2050
-
2051
1814
  if (entries.length === 0) {
2052
1815
  return '{}';
2053
1816
  }
2054
-
2055
1817
  return `{ ${entries.join(', ')} }`;
2056
1818
  }
2057
-
2058
1819
  /**
2059
1820
  * @param {string} source
2060
1821
  * @param {string} attrs
@@ -2074,11 +1835,9 @@ function injectPropsPrelude(source, attrs, rewriteContext = null) {
2074
1835
  if (/\b(?:const|let|var)\s+props\b/.test(source)) {
2075
1836
  return source;
2076
1837
  }
2077
-
2078
1838
  const propsLiteral = renderPropsLiteralFromAttrs(attrs, rewriteContext);
2079
1839
  return `var props = ${propsLiteral};\n${source}`;
2080
1840
  }
2081
-
2082
1841
  /**
2083
1842
  * @param {string} source
2084
1843
  * @returns {string}
@@ -2088,7 +1847,6 @@ function deferComponentRuntimeBlock(source) {
2088
1847
  const importLines = [];
2089
1848
  const bodyLines = [];
2090
1849
  let inImportPrefix = true;
2091
-
2092
1850
  for (const line of lines) {
2093
1851
  if (inImportPrefix && extractStaticImportSpecifier(line)) {
2094
1852
  importLines.push(line);
@@ -2097,19 +1855,16 @@ function deferComponentRuntimeBlock(source) {
2097
1855
  inImportPrefix = false;
2098
1856
  bodyLines.push(line);
2099
1857
  }
2100
-
2101
1858
  const body = bodyLines.join('\n');
2102
1859
  if (body.trim().length === 0) {
2103
1860
  return importLines.join('\n');
2104
1861
  }
2105
-
2106
1862
  const { hoisted, deferred } = splitDeferredRuntimeCalls(body);
2107
1863
  if (deferred.trim().length === 0) {
2108
1864
  return [importLines.join('\n').trim(), hoisted.trim()]
2109
1865
  .filter((segment) => segment.length > 0)
2110
1866
  .join('\n');
2111
1867
  }
2112
-
2113
1868
  const indentedBody = deferred
2114
1869
  .trim()
2115
1870
  .split('\n')
@@ -2124,10 +1879,8 @@ function deferComponentRuntimeBlock(source) {
2124
1879
  ]
2125
1880
  .filter((segment) => segment.length > 0)
2126
1881
  .join('\n');
2127
-
2128
1882
  return wrapped;
2129
1883
  }
2130
-
2131
1884
  /**
2132
1885
  * Run bundler process for one page envelope.
2133
1886
  *
@@ -2136,28 +1889,28 @@ function deferComponentRuntimeBlock(source) {
2136
1889
  * @param {string} projectRoot
2137
1890
  * @param {object | null} [logger]
2138
1891
  * @param {boolean} [showInfo]
2139
- * @param {string} [bundlerBin]
1892
+ * @param {string|object} [bundlerBin]
2140
1893
  * @returns {Promise<void>}
2141
1894
  */
2142
- function runBundler(
2143
- envelope,
2144
- outDir,
2145
- projectRoot,
2146
- logger = null,
2147
- showInfo = true,
2148
- bundlerBin = resolveBundlerBin(projectRoot)
2149
- ) {
1895
+ function runBundler(envelope, outDir, projectRoot, logger = null, showInfo = true, bundlerBin = resolveBundlerBin(projectRoot)) {
2150
1896
  return new Promise((resolvePromise, rejectPromise) => {
2151
1897
  const useStructuredLogger = Boolean(logger && typeof logger.childLine === 'function');
2152
- const child = spawn(
2153
- bundlerBin,
2154
- ['--out-dir', outDir],
2155
- {
2156
- cwd: projectRoot,
2157
- stdio: useStructuredLogger ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'inherit', 'inherit']
2158
- }
2159
- );
2160
-
1898
+ const bundlerToolchain = bundlerBin && typeof bundlerBin === 'object'
1899
+ ? bundlerBin
1900
+ : null;
1901
+ const bundlerCandidate = bundlerToolchain
1902
+ ? getActiveToolchainCandidate(bundlerToolchain)
1903
+ : null;
1904
+ const bundlerPath = bundlerCandidate?.command || bundlerBin;
1905
+ const bundlerArgs = [
1906
+ ...(Array.isArray(bundlerCandidate?.argsPrefix) ? bundlerCandidate.argsPrefix : []),
1907
+ '--out-dir',
1908
+ outDir
1909
+ ];
1910
+ const child = spawn(bundlerPath, bundlerArgs, {
1911
+ cwd: projectRoot,
1912
+ stdio: useStructuredLogger ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'inherit', 'inherit']
1913
+ });
2161
1914
  if (useStructuredLogger) {
2162
1915
  forwardStreamLines(child.stdout, (line) => {
2163
1916
  logger.childLine('bundler', line, { stream: 'stdout', showInfo });
@@ -2166,11 +1919,9 @@ function runBundler(
2166
1919
  logger.childLine('bundler', line, { stream: 'stderr', showInfo: true });
2167
1920
  });
2168
1921
  }
2169
-
2170
1922
  child.on('error', (err) => {
2171
1923
  rejectPromise(new Error(`Bundler spawn failed: ${err.message}`));
2172
1924
  });
2173
-
2174
1925
  child.on('close', (code) => {
2175
1926
  if (code === 0) {
2176
1927
  resolvePromise();
@@ -2178,12 +1929,10 @@ function runBundler(
2178
1929
  }
2179
1930
  rejectPromise(new Error(`Bundler failed with exit code ${code}`));
2180
1931
  });
2181
-
2182
1932
  child.stdin.write(JSON.stringify(envelope));
2183
1933
  child.stdin.end();
2184
1934
  });
2185
1935
  }
2186
-
2187
1936
  /**
2188
1937
  * Collect generated assets for reporting.
2189
1938
  *
@@ -2192,15 +1941,14 @@ function runBundler(
2192
1941
  */
2193
1942
  async function collectAssets(rootDir) {
2194
1943
  const files = [];
2195
-
2196
1944
  async function walk(dir) {
2197
1945
  let entries = [];
2198
1946
  try {
2199
1947
  entries = await readdir(dir);
2200
- } catch {
1948
+ }
1949
+ catch {
2201
1950
  return;
2202
1951
  }
2203
-
2204
1952
  entries.sort((a, b) => a.localeCompare(b));
2205
1953
  for (const name of entries) {
2206
1954
  const fullPath = join(dir, name);
@@ -2214,12 +1962,10 @@ async function collectAssets(rootDir) {
2214
1962
  }
2215
1963
  }
2216
1964
  }
2217
-
2218
1965
  await walk(rootDir);
2219
1966
  files.sort((a, b) => a.localeCompare(b));
2220
1967
  return files;
2221
1968
  }
2222
-
2223
1969
  /**
2224
1970
  * Build all pages by orchestrating compiler and bundler binaries.
2225
1971
  *
@@ -2238,30 +1984,28 @@ async function collectAssets(rootDir) {
2238
1984
  export async function build(options) {
2239
1985
  const { pagesDir, outDir, config = {}, logger = null, showBundlerInfo = true } = options;
2240
1986
  const projectRoot = deriveProjectRootFromPagesDir(pagesDir);
2241
- const compilerBin = resolveCompilerBin(projectRoot);
2242
- const bundlerBin = resolveBundlerBin(projectRoot);
1987
+ const compilerBin = createCompilerToolchain({ projectRoot, logger });
1988
+ const bundlerBin = createBundlerToolchain({ projectRoot, logger });
2243
1989
  const softNavigationEnabled = config.softNavigation === true || config.router === true;
2244
1990
  const compilerOpts = {
2245
1991
  typescriptDefault: config.typescriptDefault === true,
2246
1992
  experimentalEmbeddedMarkup: config.embeddedMarkupExpressions === true || config.experimental?.embeddedMarkupExpressions === true,
2247
1993
  strictDomLints: config.strictDomLints === true
2248
1994
  };
2249
-
2250
1995
  await rm(outDir, { recursive: true, force: true });
2251
1996
  await mkdir(outDir, { recursive: true });
2252
-
1997
+ ensureToolchainCompatibility(bundlerBin);
1998
+ const resolvedBundlerCandidate = getActiveToolchainCandidate(bundlerBin);
2253
1999
  if (logger) {
2254
2000
  await maybeWarnAboutZenithVersionMismatch({
2255
2001
  projectRoot,
2256
2002
  logger,
2257
2003
  command: 'build',
2258
- bundlerBinPath: bundlerBin
2004
+ bundlerBinPath: resolvedBundlerCandidate?.path || resolveBundlerBin(projectRoot)
2259
2005
  });
2260
2006
  }
2261
-
2262
2007
  // Derive src/ directory from pages/ directory
2263
2008
  const srcDir = resolve(pagesDir, '..');
2264
-
2265
2009
  // 1. Build component registry
2266
2010
  const registry = buildComponentRegistry(srcDir);
2267
2011
  if (registry.size > 0) {
@@ -2269,14 +2013,13 @@ export async function build(options) {
2269
2013
  logger.build(`registry=${registry.size} components`, {
2270
2014
  onceKey: `component-registry:${registry.size}`
2271
2015
  });
2272
- } else {
2016
+ }
2017
+ else {
2273
2018
  console.log(`[zenith] Component registry: ${registry.size} components`);
2274
2019
  }
2275
2020
  }
2276
-
2277
2021
  const manifest = await generateManifest(pagesDir);
2278
2022
  await ensureZenithTypeDeclarations({ manifest, pagesDir });
2279
-
2280
2023
  // Cache: avoid re-compiling the same component for multiple pages
2281
2024
  /** @type {Map<string, object>} */
2282
2025
  const componentIrCache = new Map();
@@ -2291,42 +2034,31 @@ export async function build(options) {
2291
2034
  }
2292
2035
  console.warn(line);
2293
2036
  });
2294
-
2295
2037
  const envelopes = [];
2296
2038
  for (const entry of manifest) {
2297
2039
  const sourceFile = join(pagesDir, entry.file);
2298
2040
  const rawSource = readFileSync(sourceFile, 'utf8');
2299
2041
  const componentOccurrences = collectExpandedComponentOccurrences(rawSource, registry, sourceFile);
2300
-
2301
2042
  const baseName = sourceFile.slice(0, -extname(sourceFile).length);
2302
2043
  let adjacentGuard = null;
2303
2044
  let adjacentLoad = null;
2304
2045
  for (const ext of ['.ts', '.js']) {
2305
- if (!adjacentGuard && existsSync(`${baseName}.guard${ext}`)) adjacentGuard = `${baseName}.guard${ext}`;
2306
- if (!adjacentLoad && existsSync(`${baseName}.load${ext}`)) adjacentLoad = `${baseName}.load${ext}`;
2046
+ if (!adjacentGuard && existsSync(`${baseName}.guard${ext}`))
2047
+ adjacentGuard = `${baseName}.guard${ext}`;
2048
+ if (!adjacentLoad && existsSync(`${baseName}.load${ext}`))
2049
+ adjacentLoad = `${baseName}.load${ext}`;
2307
2050
  }
2308
-
2309
2051
  // 2a. Expand PascalCase component tags
2310
- const { expandedSource } = expandComponents(
2311
- rawSource, registry, sourceFile
2312
- );
2052
+ const { expandedSource } = expandComponents(rawSource, registry, sourceFile);
2313
2053
  const extractedServer = extractServerScript(expandedSource, sourceFile, compilerOpts);
2314
2054
  const compileSource = extractedServer.source;
2315
-
2316
2055
  // 2b. Compile expanded page source via --stdin
2317
- const pageIr = runCompiler(
2318
- sourceFile,
2319
- compileSource,
2320
- compilerOpts,
2321
- {
2322
- compilerBin,
2323
- onWarning: emitCompilerWarning
2324
- }
2325
- );
2326
-
2056
+ const pageIr = runCompiler(sourceFile, compileSource, compilerOpts, {
2057
+ compilerToolchain: compilerBin,
2058
+ onWarning: emitCompilerWarning
2059
+ });
2327
2060
  const hasGuard = (extractedServer.serverScript && extractedServer.serverScript.has_guard) || adjacentGuard !== null;
2328
2061
  const hasLoad = (extractedServer.serverScript && extractedServer.serverScript.has_load) || adjacentLoad !== null;
2329
-
2330
2062
  if (extractedServer.serverScript) {
2331
2063
  pageIr.server_script = extractedServer.serverScript;
2332
2064
  pageIr.prerender = extractedServer.serverScript.prerender === true;
@@ -2334,21 +2066,16 @@ export async function build(options) {
2334
2066
  pageIr.ssr_data = null;
2335
2067
  }
2336
2068
  }
2337
-
2338
2069
  // Static Build Route Protection Policy
2339
2070
  if (pageIr.prerender === true && (hasGuard || hasLoad)) {
2340
- throw new Error(
2341
- `[zenith] Build failed for ${entry.file}: protected routes require SSR/runtime. ` +
2342
- `Cannot prerender a static route with a \`guard\` or \`load\` function.`
2343
- );
2071
+ throw new Error(`[zenith] Build failed for ${entry.file}: protected routes require SSR/runtime. ` +
2072
+ `Cannot prerender a static route with a \`guard\` or \`load\` function.`);
2344
2073
  }
2345
-
2346
2074
  // Apply metadata to IR
2347
2075
  pageIr.has_guard = hasGuard;
2348
2076
  pageIr.has_load = hasLoad;
2349
2077
  pageIr.guard_module_ref = adjacentGuard ? relative(srcDir, adjacentGuard).replaceAll('\\', '/') : null;
2350
2078
  pageIr.load_module_ref = adjacentLoad ? relative(srcDir, adjacentLoad).replaceAll('\\', '/') : null;
2351
-
2352
2079
  // Ensure IR has required array fields for merging
2353
2080
  pageIr.components_scripts = pageIr.components_scripts || {};
2354
2081
  pageIr.component_instances = pageIr.component_instances || [];
@@ -2366,189 +2093,109 @@ export async function build(options) {
2366
2093
  const key = occurrence.componentPath || occurrence.name;
2367
2094
  occurrenceCountByPath.set(key, (occurrenceCountByPath.get(key) || 0) + 1);
2368
2095
  }
2369
-
2370
2096
  const pageExpressionRewriteMap = new Map();
2371
2097
  const pageExpressionBindingMap = new Map();
2372
2098
  const pageAmbiguousExpressionMap = new Set();
2373
2099
  const knownRefKeys = new Set();
2374
2100
  const componentOccurrencePlans = [];
2375
2101
  const pageScopeRewrite = buildScopedIdentifierRewrite(pageIr);
2376
- const pageSelfExpressionRewrite = buildComponentExpressionRewrite(
2377
- sourceFile,
2378
- compileSource,
2379
- pageIr,
2380
- compilerOpts,
2381
- compilerBin
2382
- );
2383
- mergeExpressionRewriteMaps(
2384
- pageExpressionRewriteMap,
2385
- pageExpressionBindingMap,
2386
- pageAmbiguousExpressionMap,
2387
- pageSelfExpressionRewrite,
2388
- pageIr
2389
- );
2102
+ const pageSelfExpressionRewrite = buildComponentExpressionRewrite(sourceFile, compileSource, pageIr, compilerOpts, compilerBin);
2103
+ mergeExpressionRewriteMaps(pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, pageSelfExpressionRewrite, pageIr);
2390
2104
  const componentScopeRewriteCache = new Map();
2391
2105
  let componentInstanceCounter = 0;
2392
-
2393
2106
  // 2c. Compile each used component separately for its script IR
2394
2107
  for (const occurrence of componentOccurrences) {
2395
2108
  const compName = occurrence.name;
2396
2109
  const compPath = occurrence.componentPath || registry.get(compName);
2397
- if (!compPath) continue;
2110
+ if (!compPath)
2111
+ continue;
2398
2112
  const componentSource = readFileSync(compPath, 'utf8');
2399
2113
  const occurrenceCount = occurrenceCountByPath.get(compPath) || 0;
2400
-
2401
2114
  let compIr;
2402
2115
  if (componentIrCache.has(compPath)) {
2403
2116
  compIr = componentIrCache.get(compPath);
2404
- } else {
2117
+ }
2118
+ else {
2405
2119
  const componentCompileSource = stripStyleBlocks(componentSource);
2406
- compIr = runCompiler(
2407
- compPath,
2408
- componentCompileSource,
2409
- compilerOpts,
2410
- {
2411
- compilerBin,
2412
- onWarning: emitCompilerWarning
2413
- }
2414
- );
2120
+ compIr = runCompiler(compPath, componentCompileSource, compilerOpts, {
2121
+ compilerToolchain: compilerBin,
2122
+ onWarning: emitCompilerWarning
2123
+ });
2415
2124
  componentIrCache.set(compPath, compIr);
2416
2125
  }
2417
-
2418
2126
  let isDocMode = componentDocumentModeCache.get(compPath);
2419
2127
  if (isDocMode === undefined) {
2420
2128
  isDocMode = isDocumentMode(extractTemplate(componentSource));
2421
2129
  componentDocumentModeCache.set(compPath, isDocMode);
2422
2130
  }
2423
-
2424
2131
  let expressionRewrite = componentExpressionRewriteCache.get(compPath);
2425
2132
  if (!expressionRewrite) {
2426
- expressionRewrite = buildComponentExpressionRewrite(
2427
- compPath,
2428
- componentSource,
2429
- compIr,
2430
- compilerOpts,
2431
- compilerBin
2432
- );
2133
+ expressionRewrite = buildComponentExpressionRewrite(compPath, componentSource, compIr, compilerOpts, compilerBin);
2433
2134
  componentExpressionRewriteCache.set(compPath, expressionRewrite);
2434
2135
  }
2435
-
2436
2136
  let attrExpressionRewrite = pageSelfExpressionRewrite;
2437
2137
  let attrScopeRewrite = pageScopeRewrite;
2438
2138
  const ownerPath = typeof occurrence.ownerPath === 'string' && occurrence.ownerPath.length > 0
2439
2139
  ? occurrence.ownerPath
2440
2140
  : sourceFile;
2441
-
2442
2141
  if (ownerPath !== sourceFile) {
2443
2142
  let ownerIr = componentIrCache.get(ownerPath);
2444
2143
  if (!ownerIr) {
2445
2144
  const ownerSource = readFileSync(ownerPath, 'utf8');
2446
- ownerIr = runCompiler(
2447
- ownerPath,
2448
- stripStyleBlocks(ownerSource),
2449
- compilerOpts,
2450
- {
2451
- compilerBin,
2452
- onWarning: emitCompilerWarning
2453
- }
2454
- );
2145
+ ownerIr = runCompiler(ownerPath, stripStyleBlocks(ownerSource), compilerOpts, {
2146
+ compilerToolchain: compilerBin,
2147
+ onWarning: emitCompilerWarning
2148
+ });
2455
2149
  componentIrCache.set(ownerPath, ownerIr);
2456
2150
  }
2457
-
2458
2151
  attrExpressionRewrite = componentExpressionRewriteCache.get(ownerPath);
2459
2152
  if (!attrExpressionRewrite) {
2460
2153
  const ownerSource = readFileSync(ownerPath, 'utf8');
2461
- attrExpressionRewrite = buildComponentExpressionRewrite(
2462
- ownerPath,
2463
- ownerSource,
2464
- ownerIr,
2465
- compilerOpts,
2466
- compilerBin
2467
- );
2154
+ attrExpressionRewrite = buildComponentExpressionRewrite(ownerPath, ownerSource, ownerIr, compilerOpts, compilerBin);
2468
2155
  componentExpressionRewriteCache.set(ownerPath, attrExpressionRewrite);
2469
2156
  }
2470
-
2471
2157
  attrScopeRewrite = componentScopeRewriteCache.get(ownerPath);
2472
2158
  if (!attrScopeRewrite) {
2473
2159
  attrScopeRewrite = buildScopedIdentifierRewrite(ownerIr);
2474
2160
  componentScopeRewriteCache.set(ownerPath, attrScopeRewrite);
2475
2161
  }
2476
2162
  }
2477
-
2478
2163
  const useIsolatedInstance = occurrenceCount > 1;
2479
2164
  const { ir: instanceIr, refIdentifierPairs } = useIsolatedInstance
2480
- ? cloneComponentIrForInstance(
2481
- compIr,
2482
- componentInstanceCounter++,
2483
- extractDeclaredIdentifiers,
2484
- resolveStateKeyFromBindings
2485
- )
2165
+ ? cloneComponentIrForInstance(compIr, componentInstanceCounter++, extractDeclaredIdentifiers, resolveStateKeyFromBindings)
2486
2166
  : { ir: compIr, refIdentifierPairs: [] };
2487
2167
  const instanceRewrite = useIsolatedInstance
2488
- ? buildComponentExpressionRewrite(
2489
- compPath,
2490
- componentSource,
2491
- instanceIr,
2492
- compilerOpts,
2493
- compilerBin
2494
- )
2168
+ ? buildComponentExpressionRewrite(compPath, componentSource, instanceIr, compilerOpts, compilerBin)
2495
2169
  : expressionRewrite;
2496
-
2497
2170
  // 2d. Merge component IR into page IR
2498
- mergeComponentIr(
2499
- pageIr,
2500
- instanceIr,
2501
- compPath,
2502
- sourceFile,
2503
- {
2504
- includeCode: true,
2505
- cssImportsOnly: isDocMode,
2506
- documentMode: isDocMode,
2507
- componentAttrs: typeof occurrence.attrs === 'string' ? occurrence.attrs : '',
2508
- componentAttrsRewrite: {
2509
- expressionRewrite: attrExpressionRewrite,
2510
- scopeRewrite: attrScopeRewrite
2511
- }
2512
- },
2513
- seenStaticImports,
2514
- knownRefKeys
2515
- );
2516
-
2171
+ mergeComponentIr(pageIr, instanceIr, compPath, sourceFile, {
2172
+ includeCode: true,
2173
+ cssImportsOnly: isDocMode,
2174
+ documentMode: isDocMode,
2175
+ componentAttrs: typeof occurrence.attrs === 'string' ? occurrence.attrs : '',
2176
+ componentAttrsRewrite: {
2177
+ expressionRewrite: attrExpressionRewrite,
2178
+ scopeRewrite: attrScopeRewrite
2179
+ }
2180
+ }, seenStaticImports, knownRefKeys);
2517
2181
  if (useIsolatedInstance) {
2518
2182
  componentOccurrencePlans.push({
2519
2183
  rewrite: instanceRewrite,
2520
2184
  expressionSequence: instanceRewrite.sequence,
2521
2185
  refSequence: refIdentifierPairs
2522
2186
  });
2523
- } else {
2524
- mergeExpressionRewriteMaps(
2525
- pageExpressionRewriteMap,
2526
- pageExpressionBindingMap,
2527
- pageAmbiguousExpressionMap,
2528
- expressionRewrite,
2529
- pageIr
2530
- );
2187
+ }
2188
+ else {
2189
+ mergeExpressionRewriteMaps(pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap, expressionRewrite, pageIr);
2531
2190
  }
2532
2191
  }
2533
-
2534
- applyOccurrenceRewritePlans(
2535
- pageIr,
2536
- componentOccurrencePlans,
2537
- (rewrite, binding) => resolveRewrittenBindingMetadata(pageIr, rewrite, binding)
2538
- );
2539
- applyExpressionRewrites(
2540
- pageIr,
2541
- pageExpressionRewriteMap,
2542
- pageExpressionBindingMap,
2543
- pageAmbiguousExpressionMap
2544
- );
2192
+ applyOccurrenceRewritePlans(pageIr, componentOccurrencePlans, (rewrite, binding) => resolveRewrittenBindingMetadata(pageIr, rewrite, binding));
2193
+ applyExpressionRewrites(pageIr, pageExpressionRewriteMap, pageExpressionBindingMap, pageAmbiguousExpressionMap);
2545
2194
  applyScopedIdentifierRewrites(pageIr, buildScopedIdentifierRewrite(pageIr));
2546
2195
  synthesizeSignalBackedCompiledExpressions(pageIr);
2547
2196
  normalizeExpressionBindingDependencies(pageIr);
2548
-
2549
2197
  rewriteLegacyMarkupIdentifiers(pageIr);
2550
2198
  rewriteRefBindingIdentifiers(pageIr, knownRefKeys);
2551
-
2552
2199
  envelopes.push({
2553
2200
  route: entry.path,
2554
2201
  file: sourceFile,
@@ -2556,11 +2203,9 @@ export async function build(options) {
2556
2203
  router: softNavigationEnabled
2557
2204
  });
2558
2205
  }
2559
-
2560
2206
  if (envelopes.length > 0) {
2561
2207
  await runBundler(envelopes, outDir, projectRoot, logger, showBundlerInfo, bundlerBin);
2562
2208
  }
2563
-
2564
2209
  const assets = await collectAssets(outDir);
2565
2210
  return { pages: manifest.length, assets };
2566
2211
  }