@zenithbuild/core 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +94 -74
  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,486 +0,0 @@
1
- /**
2
- * Zenith SSG Build System
3
- *
4
- * SSG-first (Static Site Generation) build system that outputs:
5
- * - Per-page HTML files: dist/{route}/index.html
6
- * - Shared runtime: dist/assets/bundle.js
7
- * - Global styles: dist/assets/styles.css
8
- * - Page-specific JS only for pages needing hydration: dist/assets/page_{name}.js
9
- *
10
- * Static pages get pure HTML+CSS, no JavaScript.
11
- * Hydrated pages reference the shared bundle.js and their page-specific JS.
12
- */
13
-
14
- /**
15
- * ═══════════════════════════════════════════════════════════════════════════════
16
- * CLI HARDENING: BLIND ORCHESTRATOR PATTERN
17
- * ═══════════════════════════════════════════════════════════════════════════════
18
- *
19
- * This build system uses the plugin bridge pattern:
20
- * - Plugins are initialized unconditionally
21
- * - Data is collected via 'cli:runtime:collect' hook
22
- * - CLI never inspects or branches on plugin data
23
- * ═══════════════════════════════════════════════════════════════════════════════
24
- */
25
-
26
- import fs from "fs"
27
- import path from "path"
28
- import { compileZenSource } from "./index"
29
- import { discoverLayouts } from "./discovery/layouts"
30
- import { processLayout } from "./transform/layoutProcessor"
31
- import { discoverPages, generateRouteDefinition } from "@zenithbuild/router/manifest"
32
- import { analyzePageSource, getAnalysisSummary, getBuildOutputType, type PageAnalysis } from "./build-analyzer"
33
- import { generateBundleJS } from "../runtime/bundle-generator"
34
- import { compileCss, resolveGlobalsCss } from "./css"
35
- import { loadZenithConfig } from "../core/config/loader"
36
- import { PluginRegistry, createPluginContext, getPluginDataByNamespace } from "../core/plugins/registry"
37
- import {
38
- createBridgeAPI,
39
- collectHookReturns,
40
- buildRuntimeEnvelope,
41
- clearHooks,
42
- type HookContext
43
- } from "../core/plugins/bridge"
44
-
45
- // ============================================
46
- // Types
47
- // ============================================
48
-
49
- interface CompiledPage {
50
- /** Route path like "/" or "/about" or "/blog/:id" */
51
- routePath: string
52
- /** Original file path */
53
- filePath: string
54
- /** Compiled HTML content */
55
- html: string
56
- /** Page-specific JavaScript (empty if static) */
57
- pageScript: string
58
- /** Page styles */
59
- styles: string[]
60
- /** Route score for matching priority */
61
- score: number
62
- /** Dynamic route parameter names */
63
- paramNames: string[]
64
- /** Build analysis result */
65
- analysis: PageAnalysis
66
- /** Output directory relative to dist/ */
67
- outputDir: string
68
- }
69
-
70
- export interface SSGBuildOptions {
71
- /** Pages directory (e.g., app/pages) */
72
- pagesDir: string
73
- /** Output directory (e.g., app/dist) */
74
- outDir: string
75
- /** Base directory for components/layouts (e.g., app/) */
76
- baseDir?: string
77
- /** Include source maps */
78
- sourceMaps?: boolean
79
- }
80
-
81
- // ============================================
82
- // Page Compilation
83
- // ============================================
84
-
85
- /**
86
- * Compile a single page file for SSG output
87
- */
88
- async function compilePage(
89
- pagePath: string,
90
- pagesDir: string,
91
- baseDir: string = process.cwd()
92
- ): Promise<CompiledPage> {
93
- const source = fs.readFileSync(pagePath, 'utf-8')
94
-
95
- // Analyze page requirements
96
- const analysis = analyzePageSource(source)
97
-
98
- // Discover layouts
99
- const layoutsDir = path.join(baseDir, 'layouts')
100
- const layouts = discoverLayouts(layoutsDir)
101
-
102
- // Process with layout if one is used
103
- let processedSource = source
104
- const layoutToUse = layouts.get('DefaultLayout')
105
-
106
- if (layoutToUse) {
107
- processedSource = processLayout(source, layoutToUse)
108
- }
109
-
110
- // Compile with new pipeline
111
- const result = await compileZenSource(processedSource, pagePath)
112
-
113
- if (!result.finalized) {
114
- throw new Error(`Compilation failed for ${pagePath}: No finalized output`)
115
- }
116
-
117
- // Extract compiled output
118
- const html = result.finalized.html
119
- const js = result.finalized.js || ''
120
- const styles = result.finalized.styles || []
121
-
122
- // Generate route definition
123
- const routeDef = generateRouteDefinition(pagePath, pagesDir)
124
-
125
- // Determine output directory from route path
126
- // "/" -> "index", "/about" -> "about", "/blog/post" -> "blog/post"
127
- let outputDir = routeDef.path === '/' ? 'index' : routeDef.path.replace(/^\//, '')
128
-
129
- // Handle dynamic routes - they'll be placeholders for now
130
- // [id] segments become _id_ for folder names
131
- outputDir = outputDir.replace(/\[([^\]]+)\]/g, '_$1_')
132
-
133
- return {
134
- routePath: routeDef.path,
135
- filePath: pagePath,
136
- html,
137
- pageScript: analysis.needsHydration ? js : '',
138
- styles,
139
- score: routeDef.score,
140
- paramNames: routeDef.paramNames,
141
- analysis,
142
- outputDir
143
- }
144
- }
145
-
146
- // ============================================
147
- // HTML Generation
148
- // ============================================
149
-
150
- /**
151
- * Generate the final HTML for a page
152
- * Static pages: no JS references
153
- * Hydrated pages: bundle.js + page-specific JS
154
- *
155
- * Uses the neutral __ZENITH_PLUGIN_DATA__ envelope - CLI never inspects contents.
156
- */
157
- function generatePageHTML(page: CompiledPage, globalStyles: string, pluginEnvelope: Record<string, unknown>): string {
158
- const { html, styles, analysis, routePath, pageScript } = page
159
-
160
- // Combine styles
161
- const pageStyles = styles.join('\n')
162
- const allStyles = globalStyles + '\n' + pageStyles
163
-
164
- // Build script tags only if needed
165
- let scriptTags = ''
166
- if (analysis.needsHydration) {
167
- // Escape </script> sequences in JSON to prevent breaking the script tag
168
- const envelopeJson = JSON.stringify(pluginEnvelope).replace(/<\//g, '<\\/')
169
- scriptTags = `
170
- <script>window.__ZENITH_PLUGIN_DATA__ = ${envelopeJson};</script>
171
- <script src="/assets/bundle.js"></script>`
172
-
173
- if (pageScript) {
174
- // Generate a safe filename from route path
175
- const pageJsName = routePath === '/'
176
- ? 'page_index.js'
177
- : `page_${routePath.replace(/^\//, '').replace(/\//g, '_')}.js`
178
- scriptTags += `
179
- <script src="/assets/${pageJsName}"></script>`
180
- }
181
- }
182
-
183
- // Check if HTML already has full document structure
184
- const hasHtmlTag = /<html[^>]*>/i.test(html)
185
-
186
- if (hasHtmlTag) {
187
- // HTML already has structure from layout - inject styles and scripts
188
- let finalHtml = html
189
-
190
- // Inject styles into <head> if not already there
191
- if (!/<style[^>]*>/.test(finalHtml)) {
192
- finalHtml = finalHtml.replace(
193
- '</head>',
194
- ` <style>\n${allStyles}\n </style>\n</head>`
195
- )
196
- }
197
-
198
- // Inject scripts before </body>
199
- if (scriptTags) {
200
- finalHtml = finalHtml.replace(
201
- '</body>',
202
- `${scriptTags}\n</body>`
203
- )
204
- }
205
-
206
- return finalHtml
207
- }
208
-
209
- // Generate full HTML document for pages without layout
210
- return `<!DOCTYPE html>
211
- <html lang="en">
212
- <head>
213
- <meta charset="UTF-8">
214
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
215
- <title>Zenith App</title>
216
- <style>
217
- ${allStyles}
218
- </style>
219
- </head>
220
- <body>
221
- ${html}${scriptTags}
222
- </body>
223
- </html>`
224
- }
225
-
226
- // ============================================
227
- // Asset Generation
228
- // ============================================
229
-
230
- /**
231
- * Generate page-specific JavaScript
232
- */
233
- function generatePageJS(page: CompiledPage): string {
234
- if (!page.pageScript) return ''
235
-
236
- // Wrap in IIFE for isolation
237
- return `// Zenith Page: ${page.routePath}
238
- (function() {
239
- 'use strict';
240
-
241
- ${page.pageScript}
242
-
243
- // Trigger hydration after DOM is ready
244
- if (document.readyState === 'loading') {
245
- document.addEventListener('DOMContentLoaded', function() {
246
- if (window.__zenith && window.__zenith.triggerMount) {
247
- window.__zenith.triggerMount();
248
- }
249
- });
250
- } else {
251
- if (window.__zenith && window.__zenith.triggerMount) {
252
- window.__zenith.triggerMount();
253
- }
254
- }
255
- })();
256
- `
257
- }
258
-
259
- // ============================================
260
- // Main Build Function
261
- // ============================================
262
-
263
- /**
264
- * Build all pages using SSG approach
265
- *
266
- * Follows the blind orchestrator pattern:
267
- * - Plugins are initialized unconditionally
268
- * - Data is collected via hooks
269
- * - CLI never inspects plugin data
270
- */
271
- export async function buildSSG(options: SSGBuildOptions): Promise<void> {
272
- const { pagesDir, outDir, baseDir = path.dirname(pagesDir) } = options
273
-
274
- console.log('🔨 Zenith SSG Build')
275
- console.log(` Pages: ${pagesDir}`)
276
- console.log(` Output: ${outDir}`)
277
- console.log('')
278
-
279
- // ============================================
280
- // Plugin Initialization (Unconditional)
281
- // ============================================
282
- // Load config and initialize all plugins without checking which ones exist.
283
- const config = await loadZenithConfig(baseDir)
284
- const registry = new PluginRegistry()
285
- const bridgeAPI = createBridgeAPI()
286
-
287
- // Clear any previously registered hooks
288
- clearHooks()
289
-
290
- // Register ALL plugins unconditionally
291
- for (const plugin of config.plugins || []) {
292
- console.log(` Plugin: ${plugin.name}`)
293
- registry.register(plugin)
294
-
295
- // Let plugin register its CLI hooks
296
- if (plugin.registerCLI) {
297
- plugin.registerCLI(bridgeAPI)
298
- }
299
- }
300
-
301
- // Initialize all plugins
302
- await registry.initAll(createPluginContext(baseDir))
303
-
304
- // Create hook context - CLI provides this but NEVER uses getPluginData itself
305
- const hookCtx: HookContext = {
306
- projectRoot: baseDir,
307
- getPluginData: getPluginDataByNamespace
308
- }
309
-
310
- // Collect runtime payloads from ALL plugins
311
- const payloads = await collectHookReturns('cli:runtime:collect', hookCtx)
312
- const pluginEnvelope = buildRuntimeEnvelope(payloads)
313
-
314
- console.log('')
315
-
316
- // Clean and create output directory
317
- if (fs.existsSync(outDir)) {
318
- fs.rmSync(outDir, { recursive: true, force: true })
319
- }
320
- fs.mkdirSync(outDir, { recursive: true })
321
- fs.mkdirSync(path.join(outDir, 'assets'), { recursive: true })
322
-
323
- // Discover pages
324
- const pageFiles = discoverPages(pagesDir)
325
-
326
- if (pageFiles.length === 0) {
327
- console.warn('⚠️ No pages found in', pagesDir)
328
- return
329
- }
330
-
331
- console.log(`📄 Found ${pageFiles.length} page(s)`)
332
-
333
- // Compile all pages
334
- const compiledPages: CompiledPage[] = []
335
- let hasHydratedPages = false
336
-
337
- for (const pageFile of pageFiles) {
338
- const relativePath = path.relative(pagesDir, pageFile)
339
- console.log(` Compiling: ${relativePath}`)
340
-
341
- try {
342
- const compiled = await compilePage(pageFile, pagesDir, baseDir)
343
- compiledPages.push(compiled)
344
-
345
- if (compiled.analysis.needsHydration) {
346
- hasHydratedPages = true
347
- }
348
-
349
- const outputType = getBuildOutputType(compiled.analysis)
350
- const summary = getAnalysisSummary(compiled.analysis)
351
- console.log(` → ${outputType.toUpperCase()} [${summary}]`)
352
- } catch (error: any) {
353
- console.error(` ❌ Error: ${error.message}`)
354
- throw error
355
- }
356
- }
357
-
358
- console.log('')
359
-
360
- // Compile global styles (Tailwind CSS)
361
- let globalStyles = ''
362
- const globalsCssPath = resolveGlobalsCss(baseDir)
363
- if (globalsCssPath) {
364
- console.log('📦 Compiling CSS:', path.relative(baseDir, globalsCssPath))
365
- const cssOutputPath = path.join(outDir, 'assets', 'styles.css')
366
- const result = compileCss({
367
- input: globalsCssPath,
368
- output: cssOutputPath,
369
- minify: true
370
- })
371
- if (result.success) {
372
- globalStyles = result.css
373
- console.log(`📦 Generated assets/styles.css (${result.duration}ms)`)
374
- } else {
375
- console.error('❌ CSS compilation failed:', result.error)
376
- }
377
- }
378
-
379
- // Write bundle.js if any pages need hydration
380
- if (hasHydratedPages) {
381
- const bundleJS = generateBundleJS()
382
- fs.writeFileSync(path.join(outDir, 'assets', 'bundle.js'), bundleJS)
383
- console.log('📦 Generated assets/bundle.js')
384
- }
385
-
386
- // Write each page
387
- for (const page of compiledPages) {
388
- // Create output directory
389
- const pageOutDir = path.join(outDir, page.outputDir)
390
- fs.mkdirSync(pageOutDir, { recursive: true })
391
-
392
- // Generate and write HTML
393
- const html = generatePageHTML(page, globalStyles, pluginEnvelope)
394
- fs.writeFileSync(path.join(pageOutDir, 'index.html'), html)
395
-
396
- // Write page-specific JS if needed
397
- if (page.pageScript) {
398
- const pageJsName = page.routePath === '/'
399
- ? 'page_index.js'
400
- : `page_${page.routePath.replace(/^\//, '').replace(/\//g, '_')}.js`
401
- const pageJS = generatePageJS(page)
402
- fs.writeFileSync(path.join(outDir, 'assets', pageJsName), pageJS)
403
- }
404
-
405
- console.log(`✅ ${page.outputDir}/index.html`)
406
- }
407
-
408
- // Copy favicon if exists
409
- const faviconPath = path.join(baseDir, 'favicon.ico')
410
- if (fs.existsSync(faviconPath)) {
411
- fs.copyFileSync(faviconPath, path.join(outDir, 'favicon.ico'))
412
- console.log('📦 Copied favicon.ico')
413
- }
414
-
415
- // Generate 404 page
416
- const custom404Candidates = ['404.zen', '+404.zen', 'not-found.zen']
417
- let has404 = false
418
-
419
- for (const candidate of custom404Candidates) {
420
- const custom404Path = path.join(pagesDir, candidate)
421
- if (fs.existsSync(custom404Path)) {
422
- try {
423
- const compiled = await compilePage(custom404Path, pagesDir, baseDir)
424
- const html = generatePageHTML(compiled, globalStyles, pluginEnvelope)
425
- fs.writeFileSync(path.join(outDir, '404.html'), html)
426
- console.log('📦 Generated 404.html (custom)')
427
- has404 = true
428
- if (compiled.pageScript) {
429
- const pageJS = generatePageJS(compiled)
430
- fs.writeFileSync(path.join(outDir, 'assets', 'page_404.js'), pageJS)
431
- }
432
- } catch (error: any) {
433
- console.warn(` ⚠️ Could not compile ${candidate}: ${error.message}`)
434
- }
435
- break
436
- }
437
- }
438
-
439
- if (!has404) {
440
- const default404HTML = `<!DOCTYPE html>
441
- <html lang="en">
442
- <head>
443
- <meta charset="UTF-8">
444
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
445
- <title>Page Not Found | Zenith</title>
446
- <style>
447
- * { box-sizing: border-box; margin: 0; padding: 0; }
448
- body { font-family: system-ui, sans-serif; background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); color: #f1f5f9; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
449
- .container { text-align: center; padding: 2rem; }
450
- .error-code { font-size: 8rem; font-weight: 800; background: linear-gradient(135deg, #3b82f6, #06b6d4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; line-height: 1; margin-bottom: 1rem; }
451
- h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem; color: #e2e8f0; }
452
- .message { color: #94a3b8; margin-bottom: 2rem; }
453
- a { display: inline-block; background: linear-gradient(135deg, #3b82f6, #2563eb); color: white; text-decoration: none; padding: 0.75rem 1.5rem; border-radius: 8px; font-weight: 500; }
454
- </style>
455
- </head>
456
- <body>
457
- <div class="container">
458
- <div class="error-code">404</div>
459
- <h1>Page Not Found</h1>
460
- <p class="message">The page you're looking for doesn't exist.</p>
461
- <a href="/">← Go Home</a>
462
- </div>
463
- </body>
464
- </html>`
465
- fs.writeFileSync(path.join(outDir, '404.html'), default404HTML)
466
- console.log('📦 Generated 404.html (default)')
467
- }
468
-
469
- // Summary
470
- console.log('')
471
- console.log('✨ Build complete!')
472
- console.log(` Static pages: ${compiledPages.filter(p => p.analysis.isStatic).length}`)
473
- console.log(` Hydrated pages: ${compiledPages.filter(p => p.analysis.needsHydration).length}`)
474
- console.log(` SSR pages: ${compiledPages.filter(p => p.analysis.needsSSR).length}`)
475
- console.log('')
476
-
477
- // Route manifest
478
- console.log('📍 Routes:')
479
- for (const page of compiledPages.sort((a, b) => b.score - a.score)) {
480
- const type = getBuildOutputType(page.analysis)
481
- console.log(` ${page.routePath.padEnd(20)} → ${page.outputDir}/index.html (${type})`)
482
- }
483
- }
484
-
485
- // Legacy export for backwards compatibility
486
- export { buildSSG as buildSPA }