@zenithbuild/core 1.2.2 → 1.2.3

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 (83) hide show
  1. package/README.md +20 -19
  2. package/cli/commands/add.ts +2 -2
  3. package/cli/commands/build.ts +2 -3
  4. package/cli/commands/dev.ts +93 -73
  5. package/cli/commands/index.ts +1 -1
  6. package/cli/commands/preview.ts +1 -1
  7. package/cli/commands/remove.ts +2 -2
  8. package/cli/index.ts +1 -1
  9. package/cli/main.ts +1 -1
  10. package/cli/utils/logger.ts +1 -1
  11. package/cli/utils/plugin-manager.ts +1 -1
  12. package/cli/utils/project.ts +4 -4
  13. package/core/components/ErrorPage.zen +218 -0
  14. package/core/components/index.ts +15 -0
  15. package/core/config.ts +1 -0
  16. package/core/index.ts +29 -0
  17. package/dist/compiler-native-frej59m4.node +0 -0
  18. package/dist/core/compiler-native-frej59m4.node +0 -0
  19. package/dist/core/index.js +6293 -0
  20. package/dist/runtime/lifecycle/index.js +1 -0
  21. package/dist/runtime/reactivity/index.js +1 -0
  22. package/dist/zen-build.js +1 -20118
  23. package/dist/zen-dev.js +1 -20118
  24. package/dist/zen-preview.js +1 -20118
  25. package/dist/zenith.js +1 -20118
  26. package/package.json +11 -20
  27. package/compiler/README.md +0 -380
  28. package/compiler/build-analyzer.ts +0 -122
  29. package/compiler/css/index.ts +0 -317
  30. package/compiler/discovery/componentDiscovery.ts +0 -242
  31. package/compiler/discovery/layouts.ts +0 -70
  32. package/compiler/errors/compilerError.ts +0 -56
  33. package/compiler/finalize/finalizeOutput.ts +0 -192
  34. package/compiler/finalize/generateFinalBundle.ts +0 -82
  35. package/compiler/index.ts +0 -83
  36. package/compiler/ir/types.ts +0 -174
  37. package/compiler/output/types.ts +0 -48
  38. package/compiler/parse/detectMapExpressions.ts +0 -102
  39. package/compiler/parse/importTypes.ts +0 -78
  40. package/compiler/parse/parseImports.ts +0 -309
  41. package/compiler/parse/parseScript.ts +0 -46
  42. package/compiler/parse/parseTemplate.ts +0 -628
  43. package/compiler/parse/parseZenFile.ts +0 -66
  44. package/compiler/parse/scriptAnalysis.ts +0 -91
  45. package/compiler/parse/trackLoopContext.ts +0 -82
  46. package/compiler/runtime/dataExposure.ts +0 -332
  47. package/compiler/runtime/generateDOM.ts +0 -255
  48. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  49. package/compiler/runtime/hydration.ts +0 -309
  50. package/compiler/runtime/navigation.ts +0 -432
  51. package/compiler/runtime/thinRuntime.ts +0 -160
  52. package/compiler/runtime/transformIR.ts +0 -406
  53. package/compiler/runtime/wrapExpression.ts +0 -114
  54. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -97
  55. package/compiler/spa-build.ts +0 -917
  56. package/compiler/ssg-build.ts +0 -486
  57. package/compiler/test/component-stacking.test.ts +0 -365
  58. package/compiler/test/map-lowering.test.ts +0 -130
  59. package/compiler/test/validate-test.ts +0 -104
  60. package/compiler/transform/classifyExpression.ts +0 -444
  61. package/compiler/transform/componentResolver.ts +0 -350
  62. package/compiler/transform/componentScriptTransformer.ts +0 -303
  63. package/compiler/transform/expressionTransformer.ts +0 -385
  64. package/compiler/transform/fragmentLowering.ts +0 -819
  65. package/compiler/transform/generateBindings.ts +0 -68
  66. package/compiler/transform/generateHTML.ts +0 -28
  67. package/compiler/transform/layoutProcessor.ts +0 -132
  68. package/compiler/transform/slotResolver.ts +0 -292
  69. package/compiler/transform/transformNode.ts +0 -314
  70. package/compiler/transform/transformTemplate.ts +0 -38
  71. package/compiler/validate/invariants.ts +0 -292
  72. package/compiler/validate/validateExpressions.ts +0 -168
  73. package/core/config/index.ts +0 -18
  74. package/core/config/loader.ts +0 -69
  75. package/core/config/types.ts +0 -119
  76. package/core/plugins/bridge.ts +0 -193
  77. package/core/plugins/index.ts +0 -7
  78. package/core/plugins/registry.ts +0 -126
  79. package/dist/cli.js +0 -11675
  80. package/runtime/build.ts +0 -17
  81. package/runtime/bundle-generator.ts +0 -1266
  82. package/runtime/client-runtime.ts +0 -891
  83. package/runtime/serve.ts +0 -93
@@ -1,1266 +0,0 @@
1
- /**
2
- * Zenith Bundle Generator
3
- *
4
- * Generates the shared client runtime bundle that gets served as:
5
- * - /assets/bundle.js in production
6
- * - /runtime.js in development
7
- *
8
- * This is a cacheable, versioned file that contains:
9
- * - Reactivity primitives (zenSignal, zenState, zenEffect, etc.)
10
- * - Lifecycle hooks (zenOnMount, zenOnUnmount)
11
- * - Hydration functions (zenithHydrate)
12
- * - Event binding utilities
13
- */
14
-
15
- /**
16
- * Generate the complete client runtime bundle
17
- * This is served as an external JS file, not inlined
18
- */
19
- export function generateBundleJS(): string {
20
- return `/*!
21
- * Zenith Runtime v0.1.0
22
- * Shared client-side runtime for hydration and reactivity
23
- */
24
- (function(global) {
25
- 'use strict';
26
-
27
- // ============================================
28
- // Dependency Tracking System
29
- // ============================================
30
-
31
- let currentEffect = null;
32
- const effectStack = [];
33
- let batchDepth = 0;
34
- const pendingEffects = new Set();
35
-
36
- function pushContext(effect) {
37
- effectStack.push(currentEffect);
38
- currentEffect = effect;
39
- }
40
-
41
- function popContext() {
42
- currentEffect = effectStack.pop() || null;
43
- }
44
-
45
- function trackDependency(subscribers) {
46
- if (currentEffect) {
47
- subscribers.add(currentEffect);
48
- currentEffect.dependencies.add(subscribers);
49
- }
50
- }
51
-
52
- function notifySubscribers(subscribers) {
53
- const effects = [...subscribers];
54
- for (const effect of effects) {
55
- if (batchDepth > 0) {
56
- pendingEffects.add(effect);
57
- } else {
58
- effect.run();
59
- }
60
- }
61
- }
62
-
63
- function cleanupEffect(effect) {
64
- for (const deps of effect.dependencies) {
65
- deps.delete(effect);
66
- }
67
- effect.dependencies.clear();
68
- }
69
-
70
- // ============================================
71
- // zenSignal - Atomic reactive value
72
- // ============================================
73
-
74
- function zenSignal(initialValue) {
75
- let value = initialValue;
76
- const subscribers = new Set();
77
-
78
- function signal(newValue) {
79
- if (arguments.length === 0) {
80
- trackDependency(subscribers);
81
- return value;
82
- }
83
- if (newValue !== value) {
84
- value = newValue;
85
- notifySubscribers(subscribers);
86
- }
87
- return value;
88
- }
89
-
90
- return signal;
91
- }
92
-
93
- // ============================================
94
- // zenState - Deep reactive object with Proxy
95
- // ============================================
96
-
97
- function zenState(initialObj) {
98
- const subscribers = new Map();
99
-
100
- function getSubscribers(path) {
101
- if (!subscribers.has(path)) {
102
- subscribers.set(path, new Set());
103
- }
104
- return subscribers.get(path);
105
- }
106
-
107
- function createProxy(obj, path) {
108
- path = path || '';
109
- if (typeof obj !== 'object' || obj === null) return obj;
110
-
111
- return new Proxy(obj, {
112
- get: function(target, prop) {
113
- const propPath = path ? path + '.' + String(prop) : String(prop);
114
- trackDependency(getSubscribers(propPath));
115
- const value = target[prop];
116
- if (typeof value === 'object' && value !== null) {
117
- return createProxy(value, propPath);
118
- }
119
- return value;
120
- },
121
- set: function(target, prop, value) {
122
- const propPath = path ? path + '.' + String(prop) : String(prop);
123
- target[prop] = value;
124
- notifySubscribers(getSubscribers(propPath));
125
- if (path) {
126
- notifySubscribers(getSubscribers(path));
127
- }
128
- return true;
129
- }
130
- });
131
- }
132
-
133
- return createProxy(initialObj);
134
- }
135
-
136
- // ============================================
137
- // zenEffect - Auto-tracked side effect
138
- // ============================================
139
-
140
- function zenEffect(fn) {
141
- const effect = {
142
- fn: fn,
143
- dependencies: new Set(),
144
- run: function() {
145
- cleanupEffect(this);
146
- pushContext(this);
147
- try {
148
- this.fn();
149
- } finally {
150
- popContext();
151
- }
152
- },
153
- dispose: function() {
154
- cleanupEffect(this);
155
- }
156
- };
157
-
158
- effect.run();
159
- return function() { effect.dispose(); };
160
- }
161
-
162
- // ============================================
163
- // zenMemo - Cached computed value
164
- // ============================================
165
-
166
- function zenMemo(fn) {
167
- let cachedValue;
168
- let dirty = true;
169
- const subscribers = new Set();
170
-
171
- const effect = {
172
- dependencies: new Set(),
173
- run: function() {
174
- dirty = true;
175
- notifySubscribers(subscribers);
176
- }
177
- };
178
-
179
- function compute() {
180
- if (dirty) {
181
- cleanupEffect(effect);
182
- pushContext(effect);
183
- try {
184
- cachedValue = fn();
185
- dirty = false;
186
- } finally {
187
- popContext();
188
- }
189
- }
190
- trackDependency(subscribers);
191
- return cachedValue;
192
- }
193
-
194
- return compute;
195
- }
196
-
197
- // ============================================
198
- // zenRef - Non-reactive mutable container
199
- // ============================================
200
-
201
- function zenRef(initialValue) {
202
- return { current: initialValue !== undefined ? initialValue : null };
203
- }
204
-
205
- // ============================================
206
- // zenBatch - Batch updates
207
- // ============================================
208
-
209
- function zenBatch(fn) {
210
- batchDepth++;
211
- try {
212
- fn();
213
- } finally {
214
- batchDepth--;
215
- if (batchDepth === 0) {
216
- const effects = [...pendingEffects];
217
- pendingEffects.clear();
218
- for (const effect of effects) {
219
- effect.run();
220
- }
221
- }
222
- }
223
- }
224
-
225
- // ============================================
226
- // zenUntrack - Read without tracking
227
- // ============================================
228
-
229
- function zenUntrack(fn) {
230
- const prevEffect = currentEffect;
231
- currentEffect = null;
232
- try {
233
- return fn();
234
- } finally {
235
- currentEffect = prevEffect;
236
- }
237
- }
238
-
239
- // ============================================
240
- // Lifecycle Hooks
241
- // ============================================
242
-
243
- const mountCallbacks = [];
244
- const unmountCallbacks = [];
245
- let isMounted = false;
246
-
247
- function zenOnMount(fn) {
248
- if (isMounted) {
249
- const cleanup = fn();
250
- if (typeof cleanup === 'function') {
251
- unmountCallbacks.push(cleanup);
252
- }
253
- } else {
254
- mountCallbacks.push(fn);
255
- }
256
- }
257
-
258
- function zenOnUnmount(fn) {
259
- unmountCallbacks.push(fn);
260
- }
261
-
262
- function triggerMount() {
263
- isMounted = true;
264
- for (let i = 0; i < mountCallbacks.length; i++) {
265
- try {
266
- const cleanup = mountCallbacks[i]();
267
- if (typeof cleanup === 'function') {
268
- unmountCallbacks.push(cleanup);
269
- }
270
- } catch(e) {
271
- console.error('[Zenith] Mount error:', e);
272
- }
273
- }
274
- mountCallbacks.length = 0;
275
- }
276
-
277
- function triggerUnmount() {
278
- isMounted = false;
279
- for (let i = 0; i < unmountCallbacks.length; i++) {
280
- try { unmountCallbacks[i](); } catch(e) { console.error('[Zenith] Unmount error:', e); }
281
- }
282
- unmountCallbacks.length = 0;
283
- }
284
-
285
- // ============================================
286
- // Component Instance System
287
- // ============================================
288
- // Each component instance gets isolated state, effects, and lifecycles
289
- // Instances are tied to DOM elements via hydration markers
290
-
291
- const componentRegistry = {};
292
-
293
- function createComponentInstance(componentName, rootElement) {
294
- const instanceMountCallbacks = [];
295
- const instanceUnmountCallbacks = [];
296
- const instanceEffects = [];
297
- let instanceMounted = false;
298
-
299
- return {
300
- // DOM reference
301
- root: rootElement,
302
-
303
- // Lifecycle hooks (instance-scoped)
304
- onMount: function(fn) {
305
- if (instanceMounted) {
306
- const cleanup = fn();
307
- if (typeof cleanup === 'function') {
308
- instanceUnmountCallbacks.push(cleanup);
309
- }
310
- } else {
311
- instanceMountCallbacks.push(fn);
312
- }
313
- },
314
- onUnmount: function(fn) {
315
- instanceUnmountCallbacks.push(fn);
316
- },
317
-
318
- // Reactivity (uses global primitives but tracks for cleanup)
319
- signal: function(initial) {
320
- return zenSignal(initial);
321
- },
322
- state: function(initial) {
323
- return zenState(initial);
324
- },
325
- ref: function(initial) {
326
- return zenRef(initial);
327
- },
328
- effect: function(fn) {
329
- const cleanup = zenEffect(fn);
330
- instanceEffects.push(cleanup);
331
- return cleanup;
332
- },
333
- memo: function(fn) {
334
- return zenMemo(fn);
335
- },
336
- batch: function(fn) {
337
- zenBatch(fn);
338
- },
339
- untrack: function(fn) {
340
- return zenUntrack(fn);
341
- },
342
-
343
- // Lifecycle execution
344
- mount: function() {
345
- instanceMounted = true;
346
- for (let i = 0; i < instanceMountCallbacks.length; i++) {
347
- try {
348
- const cleanup = instanceMountCallbacks[i]();
349
- if (typeof cleanup === 'function') {
350
- instanceUnmountCallbacks.push(cleanup);
351
- }
352
- } catch(e) {
353
- console.error('[Zenith] Component mount error:', componentName, e);
354
- }
355
- }
356
- instanceMountCallbacks.length = 0;
357
- },
358
- unmount: function() {
359
- instanceMounted = false;
360
- // Cleanup effects
361
- for (let i = 0; i < instanceEffects.length; i++) {
362
- try {
363
- if (typeof instanceEffects[i] === 'function') instanceEffects[i]();
364
- } catch(e) {
365
- console.error('[Zenith] Effect cleanup error:', e);
366
- }
367
- }
368
- instanceEffects.length = 0;
369
- // Run unmount callbacks
370
- for (let i = 0; i < instanceUnmountCallbacks.length; i++) {
371
- try { instanceUnmountCallbacks[i](); } catch(e) { console.error('[Zenith] Unmount error:', e); }
372
- }
373
- instanceUnmountCallbacks.length = 0;
374
- }
375
- };
376
- }
377
-
378
- function defineComponent(name, factory) {
379
- componentRegistry[name] = factory;
380
- }
381
-
382
- function instantiateComponent(name, props, rootElement) {
383
- const factory = componentRegistry[name];
384
- if (!factory) {
385
- console.warn('[Zenith] Component not found:', name);
386
- return null;
387
- }
388
- return factory(props, rootElement);
389
- }
390
-
391
- /**
392
- * Hydrate components by discovering data-zen-component markers
393
- * This is the ONLY place component instantiation should happen
394
- */
395
- function hydrateComponents(container) {
396
- const componentElements = container.querySelectorAll('[data-zen-component]');
397
-
398
- for (let i = 0; i < componentElements.length; i++) {
399
- const el = componentElements[i];
400
- const componentName = el.getAttribute('data-zen-component');
401
-
402
- // Skip if already hydrated
403
- if (el.__zenith_instance) continue;
404
-
405
- // Parse props from data attribute if present
406
- const propsJson = el.getAttribute('data-zen-props') || '{}';
407
- let props = {};
408
- try {
409
- props = JSON.parse(propsJson);
410
- } catch(e) {
411
- console.warn('[Zenith] Invalid props JSON for', componentName);
412
- }
413
-
414
- // Instantiate component and bind to DOM element
415
- const instance = instantiateComponent(componentName, props, el);
416
-
417
- if (instance) {
418
- el.__zenith_instance = instance;
419
- }
420
- }
421
- }
422
-
423
- // ============================================
424
- // Expression Registry & Hydration
425
- // ============================================
426
-
427
- const expressionRegistry = new Map();
428
-
429
- function registerExpression(id, fn) {
430
- expressionRegistry.set(id, fn);
431
- }
432
-
433
- function getExpression(id) {
434
- return expressionRegistry.get(id);
435
- }
436
-
437
- function updateNode(node, exprId, pageState) {
438
- const expr = getExpression(exprId);
439
- if (!expr) return;
440
-
441
- zenEffect(function() {
442
- const result = expr(pageState);
443
-
444
- if (node.hasAttribute('data-zen-text')) {
445
- // Handle complex text/children results
446
- if (result === null || result === undefined || result === false) {
447
- node.textContent = '';
448
- } else if (typeof result === 'string') {
449
- if (result.trim().startsWith('<') && result.trim().endsWith('>')) {
450
- node.innerHTML = result;
451
- } else {
452
- node.textContent = result;
453
- }
454
- } else if (result instanceof Node) {
455
- node.innerHTML = '';
456
- node.appendChild(result);
457
- } else if (Array.isArray(result)) {
458
- node.innerHTML = '';
459
- const fragment = document.createDocumentFragment();
460
- result.flat(Infinity).forEach(item => {
461
- if (item instanceof Node) fragment.appendChild(item);
462
- else if (item != null && item !== false) fragment.appendChild(document.createTextNode(String(item)));
463
- });
464
- node.appendChild(fragment);
465
- } else {
466
- node.textContent = String(result);
467
- }
468
- } else {
469
- // Attribute update
470
- const attrNames = ['class', 'style', 'src', 'href', 'disabled', 'checked'];
471
- for (const attr of attrNames) {
472
- if (node.hasAttribute('data-zen-attr-' + attr)) {
473
- if (attr === 'class' || attr === 'className') {
474
- node.className = String(result || '');
475
- } else if (attr === 'disabled' || attr === 'checked') {
476
- if (result) node.setAttribute(attr, '');
477
- else node.removeAttribute(attr);
478
- } else {
479
- if (result != null && result !== false) node.setAttribute(attr, String(result));
480
- else node.removeAttribute(attr);
481
- }
482
- }
483
- }
484
- }
485
- });
486
- }
487
-
488
- /**
489
- * Hydrate a page with reactive bindings
490
- * Called after page HTML is in DOM
491
- */
492
- function zenithHydrate(pageState, container) {
493
- container = container || document;
494
-
495
- // Find all text expression placeholders
496
- const textNodes = container.querySelectorAll('[data-zen-text]');
497
- textNodes.forEach(el => updateNode(el, el.getAttribute('data-zen-text'), pageState));
498
-
499
- // Find all attribute expression placeholders
500
- const attrNodes = container.querySelectorAll('[data-zen-attr-class], [data-zen-attr-style], [data-zen-attr-src], [data-zen-attr-href]');
501
- attrNodes.forEach(el => {
502
- const attrMatch = Array.from(el.attributes).find(a => a.name.startsWith('data-zen-attr-'));
503
- if (attrMatch) updateNode(el, attrMatch.value, pageState);
504
- });
505
-
506
- // Wire up event handlers
507
- const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
508
- eventTypes.forEach(eventType => {
509
- const elements = container.querySelectorAll('[data-zen-' + eventType + ']');
510
- elements.forEach(el => {
511
- const handlerName = el.getAttribute('data-zen-' + eventType);
512
- if (handlerName && (global[handlerName] || getExpression(handlerName))) {
513
- el.addEventListener(eventType, function(e) {
514
- const handler = global[handlerName] || getExpression(handlerName);
515
- if (typeof handler === 'function') handler(e, el);
516
- });
517
- }
518
- });
519
- });
520
-
521
- // Trigger mount
522
- triggerMount();
523
- }
524
-
525
- // ============================================
526
- // zenith:content - Content Engine
527
- // ============================================
528
-
529
- const schemaRegistry = new Map();
530
- const builtInEnhancers = {
531
- readTime: (item) => {
532
- const wordsPerMinute = 200;
533
- const text = item.content || '';
534
- const wordCount = text.split(/\\s+/).length;
535
- const minutes = Math.ceil(wordCount / wordsPerMinute);
536
- return Object.assign({}, item, { readTime: minutes + ' min' });
537
- },
538
- wordCount: (item) => {
539
- const text = item.content || '';
540
- const wordCount = text.split(/\\s+/).length;
541
- return Object.assign({}, item, { wordCount: wordCount });
542
- }
543
- };
544
-
545
- async function applyEnhancers(item, enhancers) {
546
- let enrichedItem = Object.assign({}, item);
547
- for (const enhancer of enhancers) {
548
- if (typeof enhancer === 'string') {
549
- const fn = builtInEnhancers[enhancer];
550
- if (fn) enrichedItem = await fn(enrichedItem);
551
- } else if (typeof enhancer === 'function') {
552
- enrichedItem = await enhancer(enrichedItem);
553
- }
554
- }
555
- return enrichedItem;
556
- }
557
-
558
- class ZenCollection {
559
- constructor(items) {
560
- this.items = [...items];
561
- this.filters = [];
562
- this.sortField = null;
563
- this.sortOrder = 'desc';
564
- this.limitCount = null;
565
- this.selectedFields = null;
566
- this.enhancers = [];
567
- this._groupByFolder = false;
568
- }
569
- where(fn) { this.filters.push(fn); return this; }
570
- sortBy(field, order = 'desc') { this.sortField = field; this.sortOrder = order; return this; }
571
- limit(n) { this.limitCount = n; return this; }
572
- fields(f) { this.selectedFields = f; return this; }
573
- enhanceWith(e) { this.enhancers.push(e); return this; }
574
- groupByFolder() { this._groupByFolder = true; return this; }
575
- get() {
576
- let results = [...this.items];
577
- for (const filter of this.filters) results = results.filter(filter);
578
- if (this.sortField) {
579
- results.sort((a, b) => {
580
- const valA = a[this.sortField];
581
- const valB = b[this.sortField];
582
- if (valA < valB) return this.sortOrder === 'asc' ? -1 : 1;
583
- if (valA > valB) return this.sortOrder === 'asc' ? 1 : -1;
584
- return 0;
585
- });
586
- }
587
- if (this.limitCount !== null) results = results.slice(0, this.limitCount);
588
-
589
- // Apply enhancers synchronously if possible
590
- if (this.enhancers.length > 0) {
591
- results = results.map(item => {
592
- let enrichedItem = Object.assign({}, item);
593
- for (const enhancer of this.enhancers) {
594
- if (typeof enhancer === 'string') {
595
- const fn = builtInEnhancers[enhancer];
596
- if (fn) enrichedItem = fn(enrichedItem);
597
- } else if (typeof enhancer === 'function') {
598
- enrichedItem = enhancer(enrichedItem);
599
- }
600
- }
601
- return enrichedItem;
602
- });
603
- }
604
-
605
- if (this.selectedFields) {
606
- results = results.map(item => {
607
- const newItem = {};
608
- this.selectedFields.forEach(f => { newItem[f] = item[f]; });
609
- return newItem;
610
- });
611
- }
612
-
613
- // Group by folder if requested
614
- if (this._groupByFolder) {
615
- const groups = {};
616
- const groupOrder = [];
617
- for (const item of results) {
618
- // Extract folder from slug (e.g., "getting-started/installation" -> "getting-started")
619
- const slug = item.slug || item.id || '';
620
- const parts = slug.split('/');
621
- const folder = parts.length > 1 ? parts[0] : 'root';
622
-
623
- if (!groups[folder]) {
624
- groups[folder] = {
625
- id: folder,
626
- title: folder.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
627
- items: []
628
- };
629
- groupOrder.push(folder);
630
- }
631
- groups[folder].items.push(item);
632
- }
633
- return groupOrder.map(f => groups[f]);
634
- }
635
-
636
- return results;
637
- }
638
- }
639
-
640
- function defineSchema(name, schema) { schemaRegistry.set(name, schema); }
641
-
642
- function zenCollection(collectionName) {
643
- // Access plugin data from the neutral envelope
644
- // Content plugin stores all items under 'content' namespace
645
- const pluginData = global.__ZENITH_PLUGIN_DATA__ || {};
646
- const contentItems = pluginData.content || [];
647
-
648
- // Filter by collection name (plugin owns data structure, runtime just filters)
649
- const data = Array.isArray(contentItems)
650
- ? contentItems.filter(item => item && item.collection === collectionName)
651
- : [];
652
-
653
- return new ZenCollection(data);
654
- }
655
-
656
- // ============================================
657
- // useZenOrder - Documentation ordering & navigation
658
- // ============================================
659
-
660
- function slugify(text) {
661
- return String(text || '')
662
- .toLowerCase()
663
- .replace(/[^\\w\\s-]/g, '')
664
- .replace(/\\s+/g, '-')
665
- .replace(/-+/g, '-')
666
- .trim();
667
- }
668
-
669
- function getDocSlug(doc) {
670
- const slugOrId = String(doc.slug || doc.id || '');
671
- const parts = slugOrId.split('/');
672
- const filename = parts[parts.length - 1];
673
- return filename ? slugify(filename) : slugify(doc.title || 'untitled');
674
- }
675
-
676
- function processRawSections(rawSections) {
677
- const sections = (rawSections || []).map(function(rawSection) {
678
- const sectionSlug = slugify(rawSection.title || rawSection.id || 'section');
679
- const items = (rawSection.items || []).map(function(item) {
680
- return Object.assign({}, item, {
681
- slug: getDocSlug(item),
682
- sectionSlug: sectionSlug,
683
- isIntro: item.intro === true || (item.tags && item.tags.includes && item.tags.includes('intro'))
684
- });
685
- });
686
-
687
- // Sort items: intro first, then order, then alphabetical
688
- items.sort(function(a, b) {
689
- if (a.isIntro && !b.isIntro) return -1;
690
- if (!a.isIntro && b.isIntro) return 1;
691
- if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
692
- if (a.order !== undefined) return -1;
693
- if (b.order !== undefined) return 1;
694
- return (a.title || '').localeCompare(b.title || '');
695
- });
696
-
697
- return {
698
- id: rawSection.id || sectionSlug,
699
- title: rawSection.title || 'Untitled',
700
- slug: sectionSlug,
701
- order: rawSection.order !== undefined ? rawSection.order : (rawSection.meta && rawSection.meta.order),
702
- hasIntro: items.some(function(i) { return i.isIntro; }),
703
- items: items
704
- };
705
- });
706
-
707
- // Sort sections: order → hasIntro → alphabetical
708
- sections.sort(function(a, b) {
709
- if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
710
- if (a.order !== undefined) return -1;
711
- if (b.order !== undefined) return 1;
712
- if (a.hasIntro && !b.hasIntro) return -1;
713
- if (!a.hasIntro && b.hasIntro) return 1;
714
- return a.title.localeCompare(b.title);
715
- });
716
-
717
- return sections;
718
- }
719
-
720
- function createZenOrder(rawSections) {
721
- const sections = processRawSections(rawSections);
722
-
723
- return {
724
- sections: sections,
725
- selectedSection: sections[0] || null,
726
- selectedDoc: sections[0] && sections[0].items[0] || null,
727
-
728
- getSectionBySlug: function(sectionSlug) {
729
- return sections.find(function(s) { return s.slug === sectionSlug; }) || null;
730
- },
731
-
732
- getDocBySlug: function(sectionSlug, docSlug) {
733
- var section = sections.find(function(s) { return s.slug === sectionSlug; });
734
- if (!section) return null;
735
- return section.items.find(function(d) { return d.slug === docSlug; }) || null;
736
- },
737
-
738
- getNextDoc: function(currentDoc) {
739
- if (!currentDoc) return null;
740
- var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
741
- if (!currentSection) return null;
742
- var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
743
- if (idx < currentSection.items.length - 1) return currentSection.items[idx + 1];
744
- var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
745
- if (secIdx < sections.length - 1) return sections[secIdx + 1].items[0] || null;
746
- return null;
747
- },
748
-
749
- getPrevDoc: function(currentDoc) {
750
- if (!currentDoc) return null;
751
- var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
752
- if (!currentSection) return null;
753
- var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
754
- if (idx > 0) return currentSection.items[idx - 1];
755
- var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
756
- if (secIdx > 0) {
757
- var prev = sections[secIdx - 1];
758
- return prev.items[prev.items.length - 1] || null;
759
- }
760
- return null;
761
- },
762
-
763
- buildDocUrl: function(sectionSlug, docSlug) {
764
- if (!docSlug || docSlug === 'index') return '/documentation/' + sectionSlug;
765
- return '/documentation/' + sectionSlug + '/' + docSlug;
766
- }
767
- };
768
- }
769
-
770
- // Virtual DOM Helper for JSX-style expressions
771
- function h(tag, props, children) {
772
- const el = document.createElement(tag);
773
- if (props) {
774
- for (const [key, value] of Object.entries(props)) {
775
- if (key.startsWith('on') && typeof value === 'function') {
776
- el.addEventListener(key.slice(2).toLowerCase(), value);
777
- } else if (key === 'class' || key === 'className') {
778
- el.className = String(value || '');
779
- } else if (key === 'style' && typeof value === 'object') {
780
- Object.assign(el.style, value);
781
- } else if (value != null && value !== false) {
782
- el.setAttribute(key, String(value));
783
- }
784
- }
785
- }
786
- if (children != null && children !== false) {
787
- // Flatten nested arrays (from .map() calls)
788
- const childrenArray = Array.isArray(children) ? children.flat(Infinity) : [children];
789
- for (const child of childrenArray) {
790
- // Skip null, undefined, and false
791
- if (child == null || child === false) continue;
792
-
793
- if (typeof child === 'string') {
794
- // Check if string looks like HTML
795
- if (child.trim().startsWith('<') && child.trim().endsWith('>')) {
796
- // Render as HTML
797
- const wrapper = document.createElement('div');
798
- wrapper.innerHTML = child;
799
- while (wrapper.firstChild) {
800
- el.appendChild(wrapper.firstChild);
801
- }
802
- } else {
803
- el.appendChild(document.createTextNode(child));
804
- }
805
- } else if (typeof child === 'number') {
806
- el.appendChild(document.createTextNode(String(child)));
807
- } else if (child instanceof Node) {
808
- el.appendChild(child);
809
- } else if (Array.isArray(child)) {
810
- // Handle nested arrays (shouldn't happen after flat() but just in case)
811
- for (const c of child) {
812
- if (c instanceof Node) el.appendChild(c);
813
- else if (c != null && c !== false) el.appendChild(document.createTextNode(String(c)));
814
- }
815
- }
816
- }
817
- }
818
- return el;
819
- }
820
-
821
- // ============================================
822
- // Export to window.__zenith
823
- // ============================================
824
-
825
- global.__zenith = {
826
- // Reactivity primitives
827
- signal: zenSignal,
828
- state: zenState,
829
- effect: zenEffect,
830
- memo: zenMemo,
831
- ref: zenRef,
832
- batch: zenBatch,
833
- untrack: zenUntrack,
834
- // zenith:content
835
- defineSchema: defineSchema,
836
- zenCollection: zenCollection,
837
- // useZenOrder hook
838
- createZenOrder: createZenOrder,
839
- processRawSections: processRawSections,
840
- slugify: slugify,
841
- // Virtual DOM helper for JSX
842
- h: h,
843
- // Lifecycle
844
- onMount: zenOnMount,
845
- onUnmount: zenOnUnmount,
846
- // Internal hooks
847
- triggerMount: triggerMount,
848
- triggerUnmount: triggerUnmount,
849
- // Hydration
850
- hydrate: zenithHydrate,
851
- hydrateComponents: hydrateComponents, // Marker-driven component instantiation
852
- registerExpression: registerExpression,
853
- getExpression: getExpression,
854
- // Component instance system
855
- createInstance: createComponentInstance,
856
- defineComponent: defineComponent,
857
- instantiate: instantiateComponent
858
- };
859
-
860
- // Expose with zen* prefix for direct usage
861
- global.zenSignal = zenSignal;
862
- global.zenState = zenState;
863
- global.zenEffect = zenEffect;
864
- global.zenMemo = zenMemo;
865
- global.zenRef = zenRef;
866
- global.zenBatch = zenBatch;
867
- global.zenUntrack = zenUntrack;
868
- global.zenOnMount = zenOnMount;
869
- global.zenOnUnmount = zenOnUnmount;
870
- global.zenithHydrate = zenithHydrate;
871
-
872
- // Clean aliases
873
- global.signal = zenSignal;
874
- global.state = zenState;
875
- global.effect = zenEffect;
876
- global.memo = zenMemo;
877
- global.ref = zenRef;
878
- global.batch = zenBatch;
879
- global.untrack = zenUntrack;
880
- global.onMount = zenOnMount;
881
- global.onUnmount = zenOnUnmount;
882
-
883
- // useZenOrder hook exports
884
- global.createZenOrder = createZenOrder;
885
- global.processRawSections = processRawSections;
886
- global.slugify = slugify;
887
-
888
- // ============================================
889
- // SPA Router Runtime
890
- // ============================================
891
-
892
- // Router state
893
- // Current route state
894
- var currentRoute = {
895
- path: '/',
896
- params: {},
897
- query: {}
898
- };
899
-
900
- // Route listeners
901
- var routeListeners = new Set();
902
-
903
- // Router outlet element
904
- var routerOutlet = null;
905
-
906
- // Page modules registry
907
- var pageModules = {};
908
-
909
- // Route manifest
910
- var routeManifest = [];
911
-
912
- /**
913
- * Parse query string
914
- */
915
- function parseQueryString(search) {
916
- var query = {};
917
- if (!search || search === '?') return query;
918
- var params = new URLSearchParams(search);
919
- params.forEach(function(value, key) { query[key] = value; });
920
- return query;
921
- }
922
-
923
- /**
924
- * Resolve route from pathname
925
- */
926
- function resolveRoute(pathname) {
927
- var normalizedPath = pathname === '' ? '/' : pathname;
928
-
929
- for (var i = 0; i < routeManifest.length; i++) {
930
- var route = routeManifest[i];
931
- var match = route.regex.exec(normalizedPath);
932
- if (match) {
933
- var params = {};
934
- for (var j = 0; j < route.paramNames.length; j++) {
935
- var paramValue = match[j + 1];
936
- if (paramValue !== undefined) {
937
- params[route.paramNames[j]] = decodeURIComponent(paramValue);
938
- }
939
- }
940
- return { record: route, params: params };
941
- }
942
- }
943
- return null;
944
- }
945
-
946
- /**
947
- * Clean up previous page
948
- */
949
- function cleanupPreviousPage() {
950
- // Trigger unmount lifecycle hooks
951
- if (global.__zenith && global.__zenith.triggerUnmount) {
952
- global.__zenith.triggerUnmount();
953
- }
954
-
955
- // Remove previous page styles
956
- var prevStyles = document.querySelectorAll('style[data-zen-page-style]');
957
- prevStyles.forEach(function(s) { s.remove(); });
958
-
959
- // Clean up window properties
960
- if (global.__zenith_cleanup) {
961
- global.__zenith_cleanup.forEach(function(key) {
962
- try { delete global[key]; } catch(e) {}
963
- });
964
- }
965
- global.__zenith_cleanup = [];
966
- }
967
-
968
- /**
969
- * Inject styles
970
- */
971
- function injectStyles(styles) {
972
- styles.forEach(function(content, i) {
973
- var style = document.createElement('style');
974
- style.setAttribute('data-zen-page-style', String(i));
975
- style.textContent = content;
976
- document.head.appendChild(style);
977
- });
978
- }
979
-
980
- /**
981
- * Execute scripts
982
- */
983
- function executeScripts(scripts) {
984
- scripts.forEach(function(content) {
985
- try {
986
- var fn = new Function(content);
987
- fn();
988
- } catch (e) {
989
- console.error('[Zenith Router] Script error:', e);
990
- }
991
- });
992
- }
993
-
994
- /**
995
- * Render page
996
- */
997
- function renderPage(pageModule) {
998
- if (!routerOutlet) {
999
- console.warn('[Zenith Router] No router outlet');
1000
- return;
1001
- }
1002
-
1003
- cleanupPreviousPage();
1004
- routerOutlet.innerHTML = pageModule.html;
1005
- injectStyles(pageModule.styles);
1006
- executeScripts(pageModule.scripts);
1007
-
1008
- // Trigger mount lifecycle hooks after scripts are executed
1009
- if (global.__zenith && global.__zenith.triggerMount) {
1010
- global.__zenith.triggerMount();
1011
- }
1012
- }
1013
-
1014
- /**
1015
- * Notify listeners
1016
- */
1017
- function notifyListeners(route, prevRoute) {
1018
- routeListeners.forEach(function(listener) {
1019
- try { listener(route, prevRoute); } catch(e) {}
1020
- });
1021
- }
1022
-
1023
- /**
1024
- * Resolve and render
1025
- */
1026
- function resolveAndRender(path, query, updateHistory, replace) {
1027
- replace = replace || false;
1028
- var prevRoute = Object.assign({}, currentRoute);
1029
- var resolved = resolveRoute(path);
1030
-
1031
- if (resolved) {
1032
- currentRoute = {
1033
- path: path,
1034
- params: resolved.params,
1035
- query: query,
1036
- matched: resolved.record
1037
- };
1038
-
1039
- var pageModule = pageModules[resolved.record.path];
1040
- if (pageModule) {
1041
- renderPage(pageModule);
1042
- }
1043
- } else {
1044
- currentRoute = { path: path, params: {}, query: query, matched: undefined };
1045
- console.warn('[Zenith Router] No route matched:', path);
1046
-
1047
- // Render 404 if available
1048
- if (routerOutlet) {
1049
- routerOutlet.innerHTML = '<div style="padding: 2rem; text-align: center;"><h1>404</h1><p>Page not found</p></div>';
1050
- }
1051
- }
1052
-
1053
- if (updateHistory) {
1054
- var url = path + (Object.keys(query).length ? '?' + new URLSearchParams(query) : '');
1055
- if (replace) {
1056
- history.replaceState(null, '', url);
1057
- } else {
1058
- history.pushState(null, '', url);
1059
- }
1060
- }
1061
-
1062
- notifyListeners(currentRoute, prevRoute);
1063
- global.__zenith_route = currentRoute;
1064
- }
1065
-
1066
- /**
1067
- * Handle popstate
1068
- */
1069
- function handlePopState() {
1070
- resolveAndRender(
1071
- location.pathname,
1072
- parseQueryString(location.search),
1073
- false,
1074
- false
1075
- );
1076
- }
1077
-
1078
- /**
1079
- * Navigate (public API)
1080
- */
1081
- function navigate(to, options) {
1082
- options = options || {};
1083
- var path, query = {};
1084
-
1085
- if (to.includes('?')) {
1086
- var parts = to.split('?');
1087
- path = parts[0];
1088
- query = parseQueryString('?' + parts[1]);
1089
- } else {
1090
- path = to;
1091
- }
1092
-
1093
- if (!path.startsWith('/')) {
1094
- var currentDir = currentRoute.path.split('/').slice(0, -1).join('/');
1095
- path = currentDir + '/' + path;
1096
- }
1097
-
1098
- var normalizedPath = path === '' ? '/' : path;
1099
- var currentPath = currentRoute.path === '' ? '/' : currentRoute.path;
1100
- var isSamePath = normalizedPath === currentPath;
1101
-
1102
- if (isSamePath && JSON.stringify(query) === JSON.stringify(currentRoute.query)) {
1103
- return;
1104
- }
1105
-
1106
- // Dev mode: If no route manifest is loaded, use browser navigation
1107
- // This allows ZenLink to work in dev server where pages are served fresh
1108
- if (routeManifest.length === 0) {
1109
- var url = normalizedPath + (Object.keys(query).length ? '?' + new URLSearchParams(query) : '');
1110
- if (options.replace) {
1111
- location.replace(url);
1112
- } else {
1113
- location.href = url;
1114
- }
1115
- return;
1116
- }
1117
-
1118
- resolveAndRender(path, query, true, options.replace || false);
1119
- }
1120
-
1121
- /**
1122
- * Get current route
1123
- */
1124
- function getRoute() {
1125
- return Object.assign({}, currentRoute);
1126
- }
1127
-
1128
- /**
1129
- * Subscribe to route changes
1130
- */
1131
- function onRouteChange(listener) {
1132
- routeListeners.add(listener);
1133
- return function() { routeListeners.delete(listener); };
1134
- }
1135
-
1136
- /**
1137
- * Check if path is active
1138
- */
1139
- function isActive(path, exact) {
1140
- if (exact) return currentRoute.path === path;
1141
- return currentRoute.path.startsWith(path);
1142
- }
1143
-
1144
- /**
1145
- * Prefetch a route
1146
- */
1147
- var prefetchedRoutes = new Set();
1148
- function prefetch(path) {
1149
- var normalizedPath = path === '' ? '/' : path;
1150
-
1151
- if (prefetchedRoutes.has(normalizedPath)) {
1152
- return Promise.resolve();
1153
- }
1154
- prefetchedRoutes.add(normalizedPath);
1155
-
1156
- var resolved = resolveRoute(normalizedPath);
1157
- if (!resolved) {
1158
- return Promise.resolve();
1159
- }
1160
-
1161
- // In SPA build, all modules are already loaded
1162
- return Promise.resolve();
1163
- }
1164
-
1165
- /**
1166
- * Initialize router
1167
- */
1168
- function initRouter(manifest, modules, outlet) {
1169
- routeManifest = manifest;
1170
- Object.assign(pageModules, modules);
1171
-
1172
- if (outlet) {
1173
- routerOutlet = typeof outlet === 'string'
1174
- ? document.querySelector(outlet)
1175
- : outlet;
1176
- }
1177
-
1178
- window.addEventListener('popstate', handlePopState);
1179
-
1180
- // Initial route resolution
1181
- resolveAndRender(
1182
- location.pathname,
1183
- parseQueryString(location.search),
1184
- false
1185
- );
1186
- }
1187
-
1188
- // Expose router API globally
1189
- global.__zenith_router = {
1190
- navigate: navigate,
1191
- getRoute: getRoute,
1192
- onRouteChange: onRouteChange,
1193
- isActive: isActive,
1194
- prefetch: prefetch,
1195
- initRouter: initRouter
1196
- };
1197
-
1198
- // Also expose navigate directly for convenience
1199
- global.navigate = navigate;
1200
-
1201
-
1202
- // ============================================
1203
- // HMR Client (Development Only)
1204
- // ============================================
1205
-
1206
- if (typeof window !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1')) {
1207
- let socket;
1208
- function connectHMR() {
1209
- const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
1210
- socket = new WebSocket(protocol + '//' + location.host + '/hmr');
1211
-
1212
- socket.onmessage = function(event) {
1213
- try {
1214
- const data = JSON.parse(event.data);
1215
- if (data.type === 'reload') {
1216
- console.log('[Zenith] HMR: Reloading page...');
1217
- location.reload();
1218
- } else if (data.type === 'style-update') {
1219
- console.log('[Zenith] HMR: Updating style ' + data.url);
1220
- const links = document.querySelectorAll('link[rel="stylesheet"]');
1221
- for (let i = 0; i < links.length; i++) {
1222
- const link = links[i];
1223
- const url = new URL(link.href);
1224
- if (url.pathname === data.url) {
1225
- link.href = data.url + '?t=' + Date.now();
1226
- break;
1227
- }
1228
- }
1229
- }
1230
- } catch (e) {
1231
- console.error('[Zenith] HMR Error:', e);
1232
- }
1233
- };
1234
-
1235
- socket.onclose = function() {
1236
- console.log('[Zenith] HMR: Connection closed. Retrying in 2s...');
1237
- setTimeout(connectHMR, 2000);
1238
- };
1239
- }
1240
-
1241
- // Connect unless explicitly disabled
1242
- if (!window.__ZENITH_NO_HMR__) {
1243
- connectHMR();
1244
- }
1245
- }
1246
-
1247
- })(typeof window !== 'undefined' ? window : this);
1248
- `
1249
- }
1250
-
1251
- /**
1252
- * Generate a minified version of the bundle
1253
- * For production builds
1254
- */
1255
- export function generateMinifiedBundleJS(): string {
1256
- // For now, return non-minified
1257
- // TODO: Add minification via terser or similar
1258
- return generateBundleJS()
1259
- }
1260
-
1261
- /**
1262
- * Get bundle version for cache busting
1263
- */
1264
- export function getBundleVersion(): string {
1265
- return '0.1.0'
1266
- }