@zenithbuild/core 0.6.2 → 1.0.1

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 (87) 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 +182 -103
  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 +7465 -19128
  23. package/dist/zen-dev.js +7465 -19128
  24. package/dist/zen-preview.js +7465 -19128
  25. package/dist/zenith.js +7465 -19128
  26. package/package.json +21 -22
  27. package/cli/utils/content.ts +0 -112
  28. package/compiler/README.md +0 -380
  29. package/compiler/build-analyzer.ts +0 -122
  30. package/compiler/css/index.ts +0 -317
  31. package/compiler/discovery/componentDiscovery.ts +0 -178
  32. package/compiler/discovery/layouts.ts +0 -70
  33. package/compiler/errors/compilerError.ts +0 -56
  34. package/compiler/finalize/finalizeOutput.ts +0 -192
  35. package/compiler/finalize/generateFinalBundle.ts +0 -82
  36. package/compiler/index.ts +0 -83
  37. package/compiler/ir/types.ts +0 -174
  38. package/compiler/output/types.ts +0 -34
  39. package/compiler/parse/detectMapExpressions.ts +0 -102
  40. package/compiler/parse/importTypes.ts +0 -78
  41. package/compiler/parse/parseImports.ts +0 -309
  42. package/compiler/parse/parseScript.ts +0 -46
  43. package/compiler/parse/parseTemplate.ts +0 -599
  44. package/compiler/parse/parseZenFile.ts +0 -66
  45. package/compiler/parse/scriptAnalysis.ts +0 -91
  46. package/compiler/parse/trackLoopContext.ts +0 -82
  47. package/compiler/runtime/dataExposure.ts +0 -317
  48. package/compiler/runtime/generateDOM.ts +0 -246
  49. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  50. package/compiler/runtime/hydration.ts +0 -309
  51. package/compiler/runtime/navigation.ts +0 -432
  52. package/compiler/runtime/thinRuntime.ts +0 -160
  53. package/compiler/runtime/transformIR.ts +0 -370
  54. package/compiler/runtime/wrapExpression.ts +0 -95
  55. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
  56. package/compiler/spa-build.ts +0 -917
  57. package/compiler/ssg-build.ts +0 -422
  58. package/compiler/test/validate-test.ts +0 -104
  59. package/compiler/transform/classifyExpression.ts +0 -444
  60. package/compiler/transform/componentResolver.ts +0 -312
  61. package/compiler/transform/componentScriptTransformer.ts +0 -303
  62. package/compiler/transform/expressionTransformer.ts +0 -385
  63. package/compiler/transform/fragmentLowering.ts +0 -634
  64. package/compiler/transform/generateBindings.ts +0 -47
  65. package/compiler/transform/generateHTML.ts +0 -28
  66. package/compiler/transform/layoutProcessor.ts +0 -132
  67. package/compiler/transform/slotResolver.ts +0 -292
  68. package/compiler/transform/transformNode.ts +0 -126
  69. package/compiler/transform/transformTemplate.ts +0 -38
  70. package/compiler/validate/invariants.ts +0 -292
  71. package/compiler/validate/validateExpressions.ts +0 -168
  72. package/core/config/index.ts +0 -16
  73. package/core/config/loader.ts +0 -69
  74. package/core/config/types.ts +0 -89
  75. package/core/plugins/index.ts +0 -7
  76. package/core/plugins/registry.ts +0 -81
  77. package/dist/cli.js +0 -11665
  78. package/router/manifest.ts +0 -314
  79. package/router/navigation/ZenLink.zen +0 -231
  80. package/router/navigation/index.ts +0 -78
  81. package/router/navigation/zen-link.ts +0 -584
  82. package/router/runtime.ts +0 -458
  83. package/router/types.ts +0 -168
  84. package/runtime/build.ts +0 -17
  85. package/runtime/bundle-generator.ts +0 -1247
  86. package/runtime/client-runtime.ts +0 -549
  87. package/runtime/serve.ts +0 -93
@@ -1,549 +0,0 @@
1
- /**
2
- * Zenith Client Runtime
3
- *
4
- * Shared runtime module served as /runtime.js in dev mode.
5
- * Includes:
6
- * - Reactivity primitives (signal, state, effect, memo)
7
- * - Lifecycle hooks (zenOnMount, zenOnUnmount)
8
- * - Event wiring
9
- * - Hydration functions
10
- *
11
- * This is a standalone module that can be imported/served separately
12
- * from page-specific code.
13
- */
14
-
15
- // ============================================
16
- // Dependency Tracking System
17
- // ============================================
18
-
19
- let currentEffect: any = null;
20
- const effectStack: any[] = [];
21
- let batchDepth = 0;
22
- const pendingEffects = new Set<any>();
23
-
24
- function pushContext(effect: any) {
25
- effectStack.push(currentEffect);
26
- currentEffect = effect;
27
- }
28
-
29
- function popContext() {
30
- currentEffect = effectStack.pop() || null;
31
- }
32
-
33
- function trackDependency(subscribers: Set<any>) {
34
- if (currentEffect) {
35
- subscribers.add(currentEffect);
36
- currentEffect.dependencies.add(subscribers);
37
- }
38
- }
39
-
40
- function notifySubscribers(subscribers: Set<any>) {
41
- const effects = [...subscribers];
42
- for (const effect of effects) {
43
- if (batchDepth > 0) {
44
- pendingEffects.add(effect);
45
- } else {
46
- effect.run();
47
- }
48
- }
49
- }
50
-
51
- function cleanupEffect(effect: any) {
52
- for (const deps of effect.dependencies) {
53
- deps.delete(effect);
54
- }
55
- effect.dependencies.clear();
56
- }
57
-
58
- // ============================================
59
- // zenSignal - Atomic reactive value
60
- // ============================================
61
-
62
- export function zenSignal<T>(initialValue: T): (newValue?: T) => T {
63
- let value = initialValue;
64
- const subscribers = new Set<any>();
65
-
66
- function signal(newValue?: T): T {
67
- if (arguments.length === 0) {
68
- trackDependency(subscribers);
69
- return value;
70
- }
71
- if (newValue !== value) {
72
- value = newValue as T;
73
- notifySubscribers(subscribers);
74
- }
75
- return value;
76
- }
77
-
78
- return signal;
79
- }
80
-
81
- // ============================================
82
- // zenState - Deep reactive object with Proxy
83
- // ============================================
84
-
85
- export function zenState<T extends object>(initialObj: T): T {
86
- const subscribers = new Map<string, Set<any>>();
87
-
88
- function getSubscribers(path: string): Set<any> {
89
- if (!subscribers.has(path)) {
90
- subscribers.set(path, new Set());
91
- }
92
- return subscribers.get(path)!;
93
- }
94
-
95
- function createProxy(obj: any, parentPath: string = ''): any {
96
- if (obj === null || typeof obj !== 'object') {
97
- return obj;
98
- }
99
-
100
- return new Proxy(obj, {
101
- get(target, prop) {
102
- if (typeof prop === 'symbol') return target[prop];
103
-
104
- const path = parentPath ? `${parentPath}.${String(prop)}` : String(prop);
105
- trackDependency(getSubscribers(path));
106
-
107
- const value = target[prop];
108
- if (value !== null && typeof value === 'object') {
109
- return createProxy(value, path);
110
- }
111
- return value;
112
- },
113
-
114
- set(target, prop, newValue) {
115
- if (typeof prop === 'symbol') {
116
- target[prop] = newValue;
117
- return true;
118
- }
119
-
120
- const path = parentPath ? `${parentPath}.${String(prop)}` : String(prop);
121
- const oldValue = target[prop];
122
-
123
- if (oldValue !== newValue) {
124
- target[prop] = newValue;
125
-
126
- // Notify this path
127
- const subs = subscribers.get(path);
128
- if (subs) notifySubscribers(subs);
129
-
130
- // Notify parent paths
131
- const parts = path.split('.');
132
- for (let i = parts.length - 1; i >= 0; i--) {
133
- const parentPath = parts.slice(0, i).join('.');
134
- if (parentPath) {
135
- const parentSubs = subscribers.get(parentPath);
136
- if (parentSubs) notifySubscribers(parentSubs);
137
- }
138
- }
139
- }
140
- return true;
141
- }
142
- });
143
- }
144
-
145
- return createProxy(initialObj);
146
- }
147
-
148
- // ============================================
149
- // zenEffect - Reactive effect
150
- // ============================================
151
-
152
- export function zenEffect(fn: () => void | (() => void)): () => void {
153
- let cleanup: (() => void) | void;
154
-
155
- const effect = {
156
- dependencies: new Set<Set<any>>(),
157
- run() {
158
- cleanupEffect(effect);
159
- pushContext(effect);
160
- try {
161
- if (cleanup) cleanup();
162
- cleanup = fn();
163
- } finally {
164
- popContext();
165
- }
166
- }
167
- };
168
-
169
- effect.run();
170
-
171
- return () => {
172
- cleanupEffect(effect);
173
- if (cleanup) cleanup();
174
- };
175
- }
176
-
177
- // ============================================
178
- // zenMemo - Computed/derived value
179
- // ============================================
180
-
181
- export function zenMemo<T>(fn: () => T): () => T {
182
- let value: T;
183
- let dirty = true;
184
- const subscribers = new Set<any>();
185
-
186
- const effect = {
187
- dependencies: new Set<Set<any>>(),
188
- run() {
189
- cleanupEffect(effect);
190
- pushContext(effect);
191
- try {
192
- value = fn();
193
- dirty = false;
194
- notifySubscribers(subscribers);
195
- } finally {
196
- popContext();
197
- }
198
- }
199
- };
200
-
201
- return () => {
202
- trackDependency(subscribers);
203
- if (dirty) {
204
- effect.run();
205
- }
206
- return value;
207
- };
208
- }
209
-
210
- // ============================================
211
- // zenRef - Non-reactive mutable container
212
- // ============================================
213
-
214
- export function zenRef<T>(initialValue?: T): { current: T | null } {
215
- return { current: initialValue !== undefined ? initialValue : null };
216
- }
217
-
218
- // ============================================
219
- // zenBatch - Batch updates
220
- // ============================================
221
-
222
- export function zenBatch(fn: () => void): void {
223
- batchDepth++;
224
- try {
225
- fn();
226
- } finally {
227
- batchDepth--;
228
- if (batchDepth === 0) {
229
- const effects = [...pendingEffects];
230
- pendingEffects.clear();
231
- for (const effect of effects) {
232
- effect.run();
233
- }
234
- }
235
- }
236
- }
237
-
238
- // ============================================
239
- // zenUntrack - Read without tracking
240
- // ============================================
241
-
242
- export function zenUntrack<T>(fn: () => T): T {
243
- const prevEffect = currentEffect;
244
- currentEffect = null;
245
- try {
246
- return fn();
247
- } finally {
248
- currentEffect = prevEffect;
249
- }
250
- }
251
-
252
- // ============================================
253
- // Lifecycle Hooks
254
- // ============================================
255
-
256
- const mountCallbacks: Array<() => void | (() => void)> = [];
257
- const unmountCallbacks: Array<() => void> = [];
258
- let isMounted = false;
259
-
260
- export function zenOnMount(fn: () => void | (() => void)): void {
261
- if (isMounted) {
262
- const cleanup = fn();
263
- if (typeof cleanup === 'function') {
264
- unmountCallbacks.push(cleanup);
265
- }
266
- } else {
267
- mountCallbacks.push(fn);
268
- }
269
- }
270
-
271
- export function zenOnUnmount(fn: () => void): void {
272
- unmountCallbacks.push(fn);
273
- }
274
-
275
- export function triggerMount(): void {
276
- isMounted = true;
277
- for (const cb of mountCallbacks) {
278
- const cleanup = cb();
279
- if (typeof cleanup === 'function') {
280
- unmountCallbacks.push(cleanup);
281
- }
282
- }
283
- mountCallbacks.length = 0;
284
- }
285
-
286
- export function triggerUnmount(): void {
287
- isMounted = false;
288
- for (const cb of unmountCallbacks) {
289
- try { cb(); } catch (e) { console.error('[Zenith] Unmount error:', e); }
290
- }
291
- unmountCallbacks.length = 0;
292
- }
293
-
294
- // ============================================
295
- // Expression Registry
296
- // ============================================
297
-
298
- const expressionRegistry = new Map<string, (state: any) => any>();
299
-
300
- export function registerExpression(id: string, fn: (state: any) => any): void {
301
- expressionRegistry.set(id, fn);
302
- }
303
-
304
- export function getExpression(id: string): ((state: any) => any) | undefined {
305
- return expressionRegistry.get(id);
306
- }
307
-
308
- // ============================================
309
- // Hydration Functions
310
- // ============================================
311
-
312
- const bindings: Array<{ node: Element; type: string; expressionId: string; attributeName?: string }> = [];
313
-
314
- export function hydrate(state: any, container?: Element | Document): void {
315
- const root = container || document;
316
-
317
- // Clear existing bindings
318
- bindings.length = 0;
319
-
320
- // Find all text expression placeholders
321
- const textPlaceholders = root.querySelectorAll('[data-zen-text]');
322
- textPlaceholders.forEach((node) => {
323
- const expressionId = node.getAttribute('data-zen-text');
324
- if (!expressionId) return;
325
-
326
- bindings.push({ node: node as Element, type: 'text', expressionId });
327
- updateTextBinding(node as Element, expressionId, state);
328
- });
329
-
330
- // Find attribute bindings
331
- const attrSelectors = ['class', 'style', 'src', 'href', 'disabled', 'checked'];
332
- for (const attr of attrSelectors) {
333
- const attrPlaceholders = root.querySelectorAll(`[data-zen-attr-${attr}]`);
334
- attrPlaceholders.forEach((node) => {
335
- const expressionId = node.getAttribute(`data-zen-attr-${attr}`);
336
- if (!expressionId) return;
337
-
338
- bindings.push({ node: node as Element, type: 'attribute', expressionId, attributeName: attr });
339
- updateAttributeBinding(node as Element, attr, expressionId, state);
340
- });
341
- }
342
-
343
- // Bind event handlers
344
- bindEvents(root);
345
-
346
- // Trigger mount
347
- triggerMount();
348
- }
349
-
350
- function updateTextBinding(node: Element, expressionId: string, state: any): void {
351
- const expression = expressionRegistry.get(expressionId);
352
- if (!expression) {
353
- console.warn(`[Zenith] Expression ${expressionId} not found`);
354
- return;
355
- }
356
-
357
- try {
358
- const result = expression(state);
359
- if (result === null || result === undefined || result === false) {
360
- node.textContent = '';
361
- } else if (typeof result === 'string') {
362
- if (result.trim().startsWith('<') && result.trim().endsWith('>')) {
363
- node.innerHTML = result;
364
- } else {
365
- node.textContent = result;
366
- }
367
- } else if (result instanceof Node) {
368
- node.innerHTML = '';
369
- node.appendChild(result);
370
- } else if (Array.isArray(result)) {
371
- node.innerHTML = '';
372
- const fragment = document.createDocumentFragment();
373
- result.flat(Infinity).forEach(item => {
374
- if (item instanceof Node) fragment.appendChild(item);
375
- else if (item != null && item !== false) fragment.appendChild(document.createTextNode(String(item)));
376
- });
377
- node.appendChild(fragment);
378
- } else {
379
- node.textContent = String(result);
380
- }
381
- } catch (error) {
382
- console.error(`[Zenith] Error evaluating expression ${expressionId}:`, error);
383
- }
384
- }
385
-
386
- function updateAttributeBinding(element: Element, attrName: string, expressionId: string, state: any): void {
387
- const expression = expressionRegistry.get(expressionId);
388
- if (!expression) return;
389
-
390
- try {
391
- const result = expression(state);
392
-
393
- if (attrName === 'class' || attrName === 'className') {
394
- (element as HTMLElement).className = String(result ?? '');
395
- } else if (attrName === 'style' && typeof result === 'object') {
396
- const styleStr = Object.entries(result).map(([k, v]) => `${k}: ${v}`).join('; ');
397
- element.setAttribute('style', styleStr);
398
- } else if (['disabled', 'checked', 'readonly'].includes(attrName)) {
399
- if (result) {
400
- element.setAttribute(attrName, '');
401
- } else {
402
- element.removeAttribute(attrName);
403
- }
404
- } else {
405
- if (result === null || result === undefined || result === false) {
406
- element.removeAttribute(attrName);
407
- } else {
408
- element.setAttribute(attrName, String(result));
409
- }
410
- }
411
- } catch (error) {
412
- console.error(`[Zenith] Error updating attribute ${attrName}:`, error);
413
- }
414
- }
415
-
416
- export function update(state: any): void {
417
- for (const binding of bindings) {
418
- if (binding.type === 'text') {
419
- updateTextBinding(binding.node, binding.expressionId, state);
420
- } else if (binding.type === 'attribute' && binding.attributeName) {
421
- updateAttributeBinding(binding.node, binding.attributeName, binding.expressionId, state);
422
- }
423
- }
424
- }
425
-
426
- export function bindEvents(container: Element | Document): void {
427
- const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
428
-
429
- for (const eventType of eventTypes) {
430
- const elements = container.querySelectorAll(`[data-zen-${eventType}]`);
431
-
432
- elements.forEach((element) => {
433
- const handlerName = element.getAttribute(`data-zen-${eventType}`);
434
- if (!handlerName) return;
435
-
436
- // Remove existing handler if any
437
- const handlerKey = `__zen_${eventType}_handler`;
438
- const existingHandler = (element as any)[handlerKey];
439
- if (existingHandler) {
440
- element.removeEventListener(eventType, existingHandler);
441
- }
442
-
443
- // Create new handler
444
- const handler = (event: Event) => {
445
- try {
446
- // Try window first, then expression registry
447
- let handlerFunc = (window as any)[handlerName];
448
- if (typeof handlerFunc !== 'function') {
449
- handlerFunc = (window as any).__ZENITH_EXPRESSIONS__?.get(handlerName);
450
- }
451
-
452
- if (typeof handlerFunc === 'function') {
453
- handlerFunc(event, element);
454
- } else {
455
- console.warn(`[Zenith] Event handler "${handlerName}" not found`);
456
- }
457
- } catch (error) {
458
- console.error(`[Zenith] Error executing handler "${handlerName}":`, error);
459
- }
460
- };
461
-
462
- (element as any)[handlerKey] = handler;
463
- element.addEventListener(eventType, handler);
464
- });
465
- }
466
- }
467
-
468
- export function cleanup(container?: Element | Document): void {
469
- const root = container || document;
470
- const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
471
-
472
- for (const eventType of eventTypes) {
473
- const elements = root.querySelectorAll(`[data-zen-${eventType}]`);
474
- elements.forEach((element) => {
475
- const handlerKey = `__zen_${eventType}_handler`;
476
- const handler = (element as any)[handlerKey];
477
- if (handler) {
478
- element.removeEventListener(eventType, handler);
479
- delete (element as any)[handlerKey];
480
- }
481
- });
482
- }
483
-
484
- bindings.length = 0;
485
- triggerUnmount();
486
- }
487
-
488
- // ============================================
489
- // Browser Globals Setup
490
- // ============================================
491
-
492
- export function setupGlobals(): void {
493
- if (typeof window === 'undefined') return;
494
-
495
- const w = window as any;
496
-
497
- // Zenith namespace
498
- w.__zenith = {
499
- signal: zenSignal,
500
- state: zenState,
501
- effect: zenEffect,
502
- memo: zenMemo,
503
- ref: zenRef,
504
- batch: zenBatch,
505
- untrack: zenUntrack,
506
- onMount: zenOnMount,
507
- onUnmount: zenOnUnmount,
508
- triggerMount,
509
- triggerUnmount
510
- };
511
-
512
- // Expression registry
513
- w.__ZENITH_EXPRESSIONS__ = expressionRegistry;
514
-
515
- // Hydration functions
516
- w.__zenith_hydrate = hydrate;
517
- w.__zenith_update = update;
518
- w.__zenith_bindEvents = bindEvents;
519
- w.__zenith_cleanup = cleanup;
520
- w.zenithHydrate = hydrate;
521
- w.zenithUpdate = update;
522
- w.zenithBindEvents = bindEvents;
523
- w.zenithCleanup = cleanup;
524
-
525
- // Direct primitives
526
- w.zenSignal = zenSignal;
527
- w.zenState = zenState;
528
- w.zenEffect = zenEffect;
529
- w.zenMemo = zenMemo;
530
- w.zenRef = zenRef;
531
- w.zenBatch = zenBatch;
532
- w.zenUntrack = zenUntrack;
533
- w.zenOnMount = zenOnMount;
534
- w.zenOnUnmount = zenOnUnmount;
535
-
536
- // Short aliases
537
- w.signal = zenSignal;
538
- w.state = zenState;
539
- w.effect = zenEffect;
540
- w.memo = zenMemo;
541
- w.ref = zenRef;
542
- w.batch = zenBatch;
543
- w.untrack = zenUntrack;
544
- w.onMount = zenOnMount;
545
- w.onUnmount = zenOnUnmount;
546
- }
547
-
548
- // Auto-setup globals on import
549
- setupGlobals();
package/runtime/serve.ts DELETED
@@ -1,93 +0,0 @@
1
- /**
2
- * Zenith Development Server
3
- *
4
- * SPA-compatible server that:
5
- * - Serves static assets directly (js, css, ico, images)
6
- * - Serves index.html for all other routes (SPA fallback)
7
- *
8
- * This enables client-side routing to work on:
9
- * - Direct URL entry
10
- * - Hard refresh
11
- * - Back/forward navigation
12
- */
13
-
14
- import { serve } from "bun"
15
- import path from "path"
16
-
17
- const distDir = path.resolve(import.meta.dir, "..", "app", "dist")
18
-
19
- // File extensions that should be served as static assets
20
- const STATIC_EXTENSIONS = new Set([
21
- ".js",
22
- ".css",
23
- ".ico",
24
- ".png",
25
- ".jpg",
26
- ".jpeg",
27
- ".gif",
28
- ".svg",
29
- ".webp",
30
- ".woff",
31
- ".woff2",
32
- ".ttf",
33
- ".eot",
34
- ".json",
35
- ".map"
36
- ])
37
-
38
- serve({
39
- port: 3000,
40
-
41
- async fetch(req) {
42
- const url = new URL(req.url)
43
- const pathname = url.pathname
44
-
45
- // Get file extension
46
- const ext = path.extname(pathname).toLowerCase()
47
-
48
- // Check if this is a static asset request
49
- if (STATIC_EXTENSIONS.has(ext)) {
50
- const filePath = path.join(distDir, pathname)
51
- const file = Bun.file(filePath)
52
-
53
- // Check if file exists
54
- if (await file.exists()) {
55
- return new Response(file)
56
- }
57
-
58
- // Static file not found
59
- return new Response("Not found", { status: 404 })
60
- }
61
-
62
- // For all other routes, serve index.html (SPA fallback)
63
- const indexPath = path.join(distDir, "index.html")
64
- const indexFile = Bun.file(indexPath)
65
-
66
- if (await indexFile.exists()) {
67
- return new Response(indexFile, {
68
- headers: {
69
- "Content-Type": "text/html; charset=utf-8"
70
- }
71
- })
72
- }
73
-
74
- // No index.html found - likely need to run build first
75
- return new Response(
76
- `<html>
77
- <head><title>Zenith - Build Required</title></head>
78
- <body style="font-family: system-ui; padding: 2rem; text-align: center;">
79
- <h1>Build Required</h1>
80
- <p>Run <code>bun runtime/build.ts</code> first to compile the pages.</p>
81
- </body>
82
- </html>`,
83
- {
84
- status: 500,
85
- headers: { "Content-Type": "text/html; charset=utf-8" }
86
- }
87
- )
88
- }
89
- })
90
-
91
- console.log("🚀 Zenith dev server running at http://localhost:3000")
92
- console.log(" SPA mode: All routes serve index.html")
93
-