@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,68 +0,0 @@
1
- /**
2
- * Generate Bindings
3
- *
4
- * This module is handled by transformNode, but kept here for future extensibility
5
- */
6
-
7
- import type { Binding } from '../output/types'
8
-
9
- /**
10
- * Valid binding types
11
- */
12
- const VALID_BINDING_TYPES = new Set(['text', 'attribute', 'loop', 'conditional', 'optional'])
13
-
14
- /**
15
- * Validate bindings structure
16
- */
17
- export function validateBindings(bindings: Binding[]): void {
18
- for (const binding of bindings) {
19
- if (!binding.id || !binding.type || !binding.target || !binding.expression) {
20
- throw new Error(`Invalid binding: ${JSON.stringify(binding)}`)
21
- }
22
-
23
- if (!VALID_BINDING_TYPES.has(binding.type)) {
24
- throw new Error(`Invalid binding type: ${binding.type}`)
25
- }
26
-
27
- // Validate specific binding types
28
- switch (binding.type) {
29
- case 'text':
30
- if (binding.target !== 'data-zen-text') {
31
- throw new Error(`Text binding must have target 'data-zen-text', got: ${binding.target}`)
32
- }
33
- break
34
- case 'loop':
35
- if (binding.target !== 'data-zen-loop') {
36
- throw new Error(`Loop binding must have target 'data-zen-loop', got: ${binding.target}`)
37
- }
38
- break
39
- case 'conditional':
40
- if (binding.target !== 'data-zen-cond') {
41
- throw new Error(`Conditional binding must have target 'data-zen-cond', got: ${binding.target}`)
42
- }
43
- break
44
- case 'optional':
45
- if (binding.target !== 'data-zen-opt') {
46
- throw new Error(`Optional binding must have target 'data-zen-opt', got: ${binding.target}`)
47
- }
48
- break
49
- case 'attribute':
50
- // Attribute bindings can have various targets
51
- break
52
- }
53
- }
54
- }
55
-
56
- /**
57
- * Sort bindings by location for deterministic output
58
- */
59
- export function sortBindings(bindings: Binding[]): Binding[] {
60
- return [...bindings].sort((a, b) => {
61
- if (!a.location || !b.location) return 0
62
- if (a.location.line !== b.location.line) {
63
- return a.location.line - b.location.line
64
- }
65
- return a.location.column - b.location.column
66
- })
67
- }
68
-
@@ -1,28 +0,0 @@
1
- /**
2
- * Generate Static HTML from Transformed Nodes
3
- *
4
- * This generates pure HTML with no expressions or runtime code
5
- */
6
-
7
- import type { TemplateNode } from '../ir/types'
8
- import { transformNode } from './transformNode'
9
-
10
- /**
11
- * Generate HTML string from template nodes
12
- */
13
- export function generateHTML(
14
- nodes: TemplateNode[],
15
- expressions: any[]
16
- ): { html: string; bindings: any[] } {
17
- let html = ''
18
- const allBindings: any[] = []
19
-
20
- for (const node of nodes) {
21
- const { html: nodeHtml, bindings } = transformNode(node, expressions)
22
- html += nodeHtml
23
- allBindings.push(...bindings)
24
- }
25
-
26
- return { html, bindings: allBindings }
27
- }
28
-
@@ -1,132 +0,0 @@
1
- import type { LayoutMetadata } from '../discovery/layouts'
2
-
3
- /**
4
- * Process a page by inlining a layout
5
- */
6
- export function processLayout(
7
- source: string,
8
- layout: LayoutMetadata,
9
- props: Record<string, any> = {}
10
- ): string {
11
- // 1. Extract scripts and styles from the page source
12
- const pageScripts: string[] = []
13
- const pageStyles: string[] = []
14
- let isTypeScript = false
15
-
16
- // Extract script blocks
17
- const scriptRegex = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi
18
- let scriptMatch
19
- while ((scriptMatch = scriptRegex.exec(source)) !== null) {
20
- const attrString = scriptMatch[1] || ''
21
- const content = scriptMatch[2] || ''
22
- if (attrString.includes('lang="ts"') || attrString.includes('setup="ts"')) {
23
- isTypeScript = true
24
- }
25
- if (content) pageScripts.push(content.trim())
26
- }
27
-
28
- // Extract style blocks
29
- const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi
30
- let styleMatch
31
- while ((styleMatch = styleRegex.exec(source)) !== null) {
32
- if (styleMatch[1]) pageStyles.push(styleMatch[1].trim())
33
- }
34
-
35
- // 2. Extract content from page source and parse props
36
- const layoutTag = layout.name
37
- // Support both <DefaultLayout ...> and <DefaultLayout>...</DefaultLayout>
38
- const layoutRegex = new RegExp(`<${layoutTag}\\b([^>]*)>(?:([\\s\\S]*?)</${layoutTag}>)?`, 'i')
39
- const match = source.match(layoutRegex)
40
-
41
- let pageHtml = ''
42
- let layoutPropsStr = ''
43
-
44
- if (match) {
45
- layoutPropsStr = match[1] || ''
46
- pageHtml = match[2] || ''
47
-
48
- // If it's a self-closing tag or empty, it might not have captured content correctly if regex failed
49
- if (!pageHtml && !source.includes(`</${layoutTag}>`)) {
50
- // Self-closing check? No, Zenith usually expects explicit tags or the layout to wrap everything.
51
- }
52
- } else {
53
- // If layout tag not found as root, assume everything minus script/style is content
54
- pageHtml = source.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
55
- pageHtml = pageHtml.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '').trim()
56
- }
57
-
58
- // 3. Parse props from the tag
59
- const mergedProps = { ...props }
60
- if (layoutPropsStr) {
61
- // Support legacy props={{...}}
62
- const legacyMatch = layoutPropsStr.match(/props=\{\{([^}]+)\}\}/)
63
- if (legacyMatch && legacyMatch[1]) {
64
- const propsBody = legacyMatch[1]
65
- const pairs = propsBody.split(/,(?![^[]*\])(?![^{]*\})/)
66
- for (const pair of pairs) {
67
- const [key, ...valParts] = pair.split(':')
68
- if (key && valParts.length > 0) {
69
- mergedProps[key.trim()] = valParts.join(':').trim()
70
- }
71
- }
72
- }
73
-
74
- // Support natural props: title={"Home"} or title="Home" or title={title}
75
- const attrRegex = /([a-zA-Z0-9-]+)=(?:\{([^}]+)\}|"([^"]*)"|'([^']*)')/g
76
- let attrMatch
77
- while ((attrMatch = attrRegex.exec(layoutPropsStr)) !== null) {
78
- const name = attrMatch[1]
79
- const value = attrMatch[2] || attrMatch[3] || attrMatch[4]
80
- if (name && name !== 'props') {
81
- mergedProps[name] = value
82
- }
83
- }
84
- }
85
-
86
- // 4. Merge Scripts with Prop Injection
87
- // Layout scripts come first, then page scripts. Props are injected at the very top.
88
- const propDeclarations = Object.entries(mergedProps)
89
- .map(([key, value]) => {
90
- // If value looks like a string literal, keep it as is, otherwise wrap if needed
91
- // Actually, if it came from {expression}, we should treat it as code.
92
- // If it came from "string", we treat it as a string.
93
- const isExpression = layoutPropsStr.includes(`${key}={${value}}`)
94
- if (isExpression) {
95
- return `const ${key} = ${value};`
96
- }
97
- return `const ${key} = ${typeof value === 'string' && !value.startsWith("'") && !value.startsWith('"') ? `'${value}'` : value};`
98
- })
99
- .join('\n')
100
-
101
- const mergedScripts = [
102
- propDeclarations,
103
- ...layout.scripts,
104
- ...pageScripts
105
- ].filter(Boolean).join('\n\n')
106
-
107
- // 5. Merge Styles
108
- const mergedStyles = [
109
- ...layout.styles,
110
- ...pageStyles
111
- ].filter(Boolean).join('\n\n')
112
-
113
- // 6. Inline HTML into layout slot
114
- let finalizedHtml = layout.html.replace(/<Slot\s*\/>/gi, pageHtml)
115
- finalizedHtml = finalizedHtml.replace(/<slot\s*>[\s\S]*?<\/slot>/gi, pageHtml)
116
-
117
- // 7. Reconstruct the full .zen source
118
- const propNames = Object.keys(mergedProps).join(',')
119
- const scriptTag = `<script setup${isTypeScript ? '="ts"' : ''}${propNames ? ` props="${propNames}"` : ''}>`
120
-
121
- return `
122
- ${scriptTag}
123
- ${mergedScripts}
124
- </script>
125
-
126
- ${finalizedHtml}
127
-
128
- <style>
129
- ${mergedStyles}
130
- </style>
131
- `.trim()
132
- }
@@ -1,292 +0,0 @@
1
- /**
2
- * Slot Resolution - Compound Component Model
3
- *
4
- * Resolves slots using compound component pattern (Card.Header, Card.Body)
5
- * NOT template tags. This matches React/Astro semantics.
6
- *
7
- * IMPORTANT: Slot content must preserve the parent reactive scope.
8
- * Components are purely structural transforms - they don't create new reactive boundaries.
9
- *
10
- * Example usage:
11
- * <Card>
12
- * <Card.Header><h3>Title</h3></Card.Header>
13
- * <p>Body content goes to default slot</p>
14
- * <Card.Footer><Button>OK</Button></Card.Footer>
15
- * </Card>
16
- */
17
-
18
- import type { TemplateNode, ComponentNode, ElementNode, LoopContext } from '../ir/types'
19
-
20
- export interface ResolvedSlots {
21
- default: TemplateNode[]
22
- named: Map<string, TemplateNode[]>
23
- // Preserve the parent's reactive scope for slot content
24
- parentLoopContext?: LoopContext
25
- }
26
-
27
- /**
28
- * Extract slots from component children using compound component pattern
29
- *
30
- * Children named `ParentComponent.SlotName` become named slots.
31
- * All other children go to the default slot.
32
- * Preserves the parent's reactive scope (loopContext) for all slot content.
33
- *
34
- * @param parentName - Name of the parent component (e.g., "Card")
35
- * @param children - Child nodes from component usage
36
- * @param parentLoopContext - The reactive scope from the parent (must be preserved)
37
- */
38
- export function extractSlotsFromChildren(
39
- parentName: string,
40
- children: TemplateNode[],
41
- parentLoopContext?: LoopContext
42
- ): ResolvedSlots {
43
- const defaultSlot: TemplateNode[] = []
44
- const namedSlots = new Map<string, TemplateNode[]>()
45
-
46
- for (const child of children) {
47
- // Check if this is a compound component (e.g., Card.Header)
48
- if (child.type === 'component') {
49
- const compoundMatch = parseCompoundName(child.name, parentName)
50
-
51
- if (compoundMatch) {
52
- // This is a named slot (e.g., Card.Header -> "header")
53
- const slotName = compoundMatch.toLowerCase()
54
-
55
- if (!namedSlots.has(slotName)) {
56
- namedSlots.set(slotName, [])
57
- }
58
-
59
- // The compound component's children become the slot content
60
- // Preserve parent's loopContext on each child
61
- const scopedChildren = child.children.map(c =>
62
- rebindNodeToScope(c, parentLoopContext)
63
- )
64
- namedSlots.get(slotName)!.push(...scopedChildren)
65
- } else {
66
- // Regular component, goes to default slot
67
- // Preserve parent's loopContext
68
- defaultSlot.push(rebindNodeToScope(child, parentLoopContext))
69
- }
70
- } else {
71
- // Elements, text, expressions go to default slot
72
- // Preserve parent's loopContext
73
- defaultSlot.push(rebindNodeToScope(child, parentLoopContext))
74
- }
75
- }
76
-
77
- return {
78
- default: defaultSlot,
79
- named: namedSlots,
80
- parentLoopContext
81
- }
82
- }
83
-
84
- /**
85
- * Rebind a node to the parent's reactive scope
86
- *
87
- * This ensures that expressions and event bindings in slot content
88
- * remain connected to the parent component's reactive graph.
89
- * Components must be purely structural - they don't create new reactive boundaries.
90
- */
91
- function rebindNodeToScope(node: TemplateNode, loopContext?: LoopContext): TemplateNode {
92
- // If no parent scope to preserve, return as-is
93
- if (!loopContext) {
94
- return node
95
- }
96
-
97
- // Merge the parent's loopContext with existing loopContext
98
- // Parent scope takes precedence to ensure reactivity flows through
99
- switch (node.type) {
100
- case 'expression':
101
- return {
102
- ...node,
103
- loopContext: mergeLoopContext(node.loopContext, loopContext)
104
- }
105
-
106
- case 'element':
107
- return {
108
- ...node,
109
- loopContext: mergeLoopContext(node.loopContext, loopContext),
110
- attributes: node.attributes.map(attr => ({
111
- ...attr,
112
- loopContext: attr.loopContext
113
- ? mergeLoopContext(attr.loopContext, loopContext)
114
- : loopContext
115
- })),
116
- children: node.children.map(c => rebindNodeToScope(c, loopContext))
117
- }
118
-
119
- case 'component':
120
- return {
121
- ...node,
122
- loopContext: mergeLoopContext(node.loopContext, loopContext),
123
- children: node.children.map(c => rebindNodeToScope(c, loopContext))
124
- }
125
-
126
- case 'text':
127
- // Text nodes don't have reactive bindings
128
- return node
129
-
130
- default:
131
- return node
132
- }
133
- }
134
-
135
- /**
136
- * Merge two loop contexts, combining their variables
137
- * Parent context variables take precedence (added last so they shadow)
138
- */
139
- function mergeLoopContext(existing?: LoopContext, parent?: LoopContext): LoopContext | undefined {
140
- if (!existing && !parent) return undefined
141
- if (!existing) return parent
142
- if (!parent) return existing
143
-
144
- // Combine variables, parent variables shadow existing
145
- const allVars = new Set([...existing.variables, ...parent.variables])
146
-
147
- return {
148
- variables: Array.from(allVars),
149
- mapSource: parent.mapSource || existing.mapSource
150
- }
151
- }
152
-
153
- /**
154
- * Parse compound component name
155
- *
156
- * Given "Card.Header" and parent "Card", returns "Header"
157
- * Given "Card.Footer" and parent "Card", returns "Footer"
158
- * Given "Button" and parent "Card", returns null (not a compound)
159
- *
160
- * @param componentName - Full component name (e.g., "Card.Header")
161
- * @param parentName - Parent component name (e.g., "Card")
162
- * @returns Slot name or null if not a compound of this parent
163
- */
164
- function parseCompoundName(componentName: string, parentName: string): string | null {
165
- const prefix = `${parentName}.`
166
-
167
- if (componentName.startsWith(prefix)) {
168
- return componentName.slice(prefix.length)
169
- }
170
-
171
- return null
172
- }
173
-
174
- /**
175
- * Resolve slots in component template nodes
176
- *
177
- * Replaces <slot /> and <slot name="X" /> with children from resolved slots.
178
- * All slot content is rebound to the parent's reactive scope.
179
- */
180
- export function resolveSlots(
181
- componentNodes: TemplateNode[],
182
- slots: ResolvedSlots
183
- ): TemplateNode[] {
184
- const resolved: TemplateNode[] = []
185
-
186
- for (const node of componentNodes) {
187
- const result = resolveNode(node, slots)
188
- if (Array.isArray(result)) {
189
- resolved.push(...result)
190
- } else {
191
- resolved.push(result)
192
- }
193
- }
194
-
195
- return resolved
196
- }
197
-
198
- /**
199
- * Resolve a single node, replacing slot tags with content
200
- * Ensures all slot content maintains the parent's reactive scope
201
- */
202
- function resolveNode(
203
- node: TemplateNode,
204
- slots: ResolvedSlots
205
- ): TemplateNode | TemplateNode[] {
206
- if (node.type === 'element' && node.tag === 'slot') {
207
- // This is a slot tag - replace it with children
208
- const nameAttr = node.attributes.find(attr => attr.name === 'name')
209
- const slotName = typeof nameAttr?.value === 'string' ? nameAttr.value : null
210
-
211
- if (slotName) {
212
- // Named slot
213
- const namedChildren = slots.named.get(slotName.toLowerCase()) || []
214
-
215
- // If no children provided and slot has fallback content, use fallback
216
- if (namedChildren.length === 0 && node.children.length > 0) {
217
- return node.children
218
- }
219
-
220
- // Return slot content (already scoped during extraction)
221
- return namedChildren.length > 0 ? namedChildren : []
222
- } else {
223
- // Default slot
224
- // If no children provided and slot has fallback content, use fallback
225
- if (slots.default.length === 0 && node.children.length > 0) {
226
- return node.children
227
- }
228
-
229
- // Return slot content (already scoped during extraction)
230
- return slots.default
231
- }
232
- }
233
-
234
- if (node.type === 'element') {
235
- // Recursively resolve slots in children
236
- const resolvedChildren: TemplateNode[] = []
237
- for (const child of node.children) {
238
- const result = resolveNode(child, slots)
239
- if (Array.isArray(result)) {
240
- resolvedChildren.push(...result)
241
- } else {
242
- resolvedChildren.push(result)
243
- }
244
- }
245
-
246
- return {
247
- ...node,
248
- children: resolvedChildren
249
- }
250
- }
251
-
252
- if (node.type === 'component') {
253
- // Recursively resolve slots in component children
254
- const resolvedChildren: TemplateNode[] = []
255
- for (const child of node.children) {
256
- const result = resolveNode(child, slots)
257
- if (Array.isArray(result)) {
258
- resolvedChildren.push(...result)
259
- } else {
260
- resolvedChildren.push(result)
261
- }
262
- }
263
-
264
- return {
265
- ...node,
266
- children: resolvedChildren
267
- }
268
- }
269
-
270
- // Text and expression nodes pass through unchanged
271
- return node
272
- }
273
-
274
- /**
275
- * Check if a node tree contains any slots
276
- */
277
- export function hasSlots(nodes: TemplateNode[]): boolean {
278
- function checkNode(node: TemplateNode): boolean {
279
- if (node.type === 'element') {
280
- if (node.tag === 'slot') {
281
- return true
282
- }
283
- return node.children.some(checkNode)
284
- }
285
- if (node.type === 'component') {
286
- return node.children.some(checkNode)
287
- }
288
- return false
289
- }
290
-
291
- return nodes.some(checkNode)
292
- }