@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.
- package/README.md +20 -19
- package/cli/commands/add.ts +2 -2
- package/cli/commands/build.ts +2 -3
- package/cli/commands/dev.ts +93 -73
- package/cli/commands/index.ts +1 -1
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/remove.ts +2 -2
- package/cli/index.ts +1 -1
- package/cli/main.ts +1 -1
- package/cli/utils/logger.ts +1 -1
- package/cli/utils/plugin-manager.ts +1 -1
- package/cli/utils/project.ts +4 -4
- package/core/components/ErrorPage.zen +218 -0
- package/core/components/index.ts +15 -0
- package/core/config.ts +1 -0
- package/core/index.ts +29 -0
- package/dist/compiler-native-frej59m4.node +0 -0
- package/dist/core/compiler-native-frej59m4.node +0 -0
- package/dist/core/index.js +6293 -0
- package/dist/runtime/lifecycle/index.js +1 -0
- package/dist/runtime/reactivity/index.js +1 -0
- package/dist/zen-build.js +1 -20118
- package/dist/zen-dev.js +1 -20118
- package/dist/zen-preview.js +1 -20118
- package/dist/zenith.js +1 -20118
- package/package.json +11 -20
- package/compiler/README.md +0 -380
- package/compiler/build-analyzer.ts +0 -122
- package/compiler/css/index.ts +0 -317
- package/compiler/discovery/componentDiscovery.ts +0 -242
- package/compiler/discovery/layouts.ts +0 -70
- package/compiler/errors/compilerError.ts +0 -56
- package/compiler/finalize/finalizeOutput.ts +0 -192
- package/compiler/finalize/generateFinalBundle.ts +0 -82
- package/compiler/index.ts +0 -83
- package/compiler/ir/types.ts +0 -174
- package/compiler/output/types.ts +0 -48
- package/compiler/parse/detectMapExpressions.ts +0 -102
- package/compiler/parse/importTypes.ts +0 -78
- package/compiler/parse/parseImports.ts +0 -309
- package/compiler/parse/parseScript.ts +0 -46
- package/compiler/parse/parseTemplate.ts +0 -628
- package/compiler/parse/parseZenFile.ts +0 -66
- package/compiler/parse/scriptAnalysis.ts +0 -91
- package/compiler/parse/trackLoopContext.ts +0 -82
- package/compiler/runtime/dataExposure.ts +0 -332
- package/compiler/runtime/generateDOM.ts +0 -255
- package/compiler/runtime/generateHydrationBundle.ts +0 -407
- package/compiler/runtime/hydration.ts +0 -309
- package/compiler/runtime/navigation.ts +0 -432
- package/compiler/runtime/thinRuntime.ts +0 -160
- package/compiler/runtime/transformIR.ts +0 -406
- package/compiler/runtime/wrapExpression.ts +0 -114
- package/compiler/runtime/wrapExpressionWithLoop.ts +0 -97
- package/compiler/spa-build.ts +0 -917
- package/compiler/ssg-build.ts +0 -486
- package/compiler/test/component-stacking.test.ts +0 -365
- package/compiler/test/map-lowering.test.ts +0 -130
- package/compiler/test/validate-test.ts +0 -104
- package/compiler/transform/classifyExpression.ts +0 -444
- package/compiler/transform/componentResolver.ts +0 -350
- package/compiler/transform/componentScriptTransformer.ts +0 -303
- package/compiler/transform/expressionTransformer.ts +0 -385
- package/compiler/transform/fragmentLowering.ts +0 -819
- package/compiler/transform/generateBindings.ts +0 -68
- package/compiler/transform/generateHTML.ts +0 -28
- package/compiler/transform/layoutProcessor.ts +0 -132
- package/compiler/transform/slotResolver.ts +0 -292
- package/compiler/transform/transformNode.ts +0 -314
- package/compiler/transform/transformTemplate.ts +0 -38
- package/compiler/validate/invariants.ts +0 -292
- package/compiler/validate/validateExpressions.ts +0 -168
- package/core/config/index.ts +0 -18
- package/core/config/loader.ts +0 -69
- package/core/config/types.ts +0 -119
- package/core/plugins/bridge.ts +0 -193
- package/core/plugins/index.ts +0 -7
- package/core/plugins/registry.ts +0 -126
- package/dist/cli.js +0 -11675
- package/runtime/build.ts +0 -17
- package/runtime/bundle-generator.ts +0 -1266
- package/runtime/client-runtime.ts +0 -891
- package/runtime/serve.ts +0 -93
package/compiler/css/index.ts
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith CSS Compiler Module
|
|
3
|
-
*
|
|
4
|
-
* Compiler-owned CSS processing that integrates Tailwind CSS v4 JIT
|
|
5
|
-
* at compile time. This module ensures:
|
|
6
|
-
*
|
|
7
|
-
* 1. All CSS is processed at build time (no runtime generation)
|
|
8
|
-
* 2. Tailwind sees all .zen templates for class scanning
|
|
9
|
-
* 3. HMR support for instant CSS updates in dev mode
|
|
10
|
-
* 4. Deterministic, cacheable output for production
|
|
11
|
-
*
|
|
12
|
-
* Per Zenith CSS Directive: The compiler owns styles.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { spawn, spawnSync } from 'child_process'
|
|
16
|
-
import path from 'path'
|
|
17
|
-
import fs from 'fs'
|
|
18
|
-
|
|
19
|
-
// ============================================
|
|
20
|
-
// Types
|
|
21
|
-
// ============================================
|
|
22
|
-
|
|
23
|
-
export interface CSSCompileOptions {
|
|
24
|
-
/** Input CSS file path (e.g., src/styles/globals.css) */
|
|
25
|
-
input: string
|
|
26
|
-
/** Output CSS file path, or ':memory:' for in-memory result */
|
|
27
|
-
output: string
|
|
28
|
-
/** Enable minification for production */
|
|
29
|
-
minify?: boolean
|
|
30
|
-
/** Watch mode for HMR */
|
|
31
|
-
watch?: boolean
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface CSSCompileResult {
|
|
35
|
-
/** Compiled CSS content */
|
|
36
|
-
css: string
|
|
37
|
-
/** Compilation time in milliseconds */
|
|
38
|
-
duration: number
|
|
39
|
-
/** Whether compilation succeeded */
|
|
40
|
-
success: boolean
|
|
41
|
-
/** Error message if failed */
|
|
42
|
-
error?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ============================================
|
|
46
|
-
// CSS Compilation
|
|
47
|
-
// ============================================
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Compile CSS using Tailwind CSS v4 CLI
|
|
51
|
-
*
|
|
52
|
-
* This function synchronously compiles CSS for use in:
|
|
53
|
-
* - Dev server startup
|
|
54
|
-
* - SSG build
|
|
55
|
-
* - On-demand recompilation
|
|
56
|
-
*
|
|
57
|
-
* @param options Compilation options
|
|
58
|
-
* @returns Compiled CSS result
|
|
59
|
-
*/
|
|
60
|
-
export function compileCss(options: CSSCompileOptions): CSSCompileResult {
|
|
61
|
-
const startTime = performance.now()
|
|
62
|
-
const { input, output, minify = false } = options
|
|
63
|
-
|
|
64
|
-
// Validate input exists
|
|
65
|
-
if (!fs.existsSync(input)) {
|
|
66
|
-
return {
|
|
67
|
-
css: '',
|
|
68
|
-
duration: 0,
|
|
69
|
-
success: false,
|
|
70
|
-
error: `CSS input file not found: ${input}`
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
// Build Tailwind CLI arguments
|
|
76
|
-
const args = [
|
|
77
|
-
'@tailwindcss/cli',
|
|
78
|
-
'-i', input
|
|
79
|
-
]
|
|
80
|
-
|
|
81
|
-
// For in-memory compilation, use stdout
|
|
82
|
-
const useStdout = output === ':memory:'
|
|
83
|
-
if (!useStdout) {
|
|
84
|
-
args.push('-o', output)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (minify) {
|
|
88
|
-
args.push('--minify')
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Execute Tailwind CLI synchronously
|
|
92
|
-
const result = spawnSync('bunx', args, {
|
|
93
|
-
cwd: path.dirname(input),
|
|
94
|
-
encoding: 'utf-8',
|
|
95
|
-
stdio: useStdout ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'inherit', 'pipe'],
|
|
96
|
-
env: { ...process.env }
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
const duration = Math.round(performance.now() - startTime)
|
|
100
|
-
|
|
101
|
-
if (result.status !== 0) {
|
|
102
|
-
const errorMsg = result.stderr?.toString() || 'Unknown compilation error'
|
|
103
|
-
return {
|
|
104
|
-
css: '',
|
|
105
|
-
duration,
|
|
106
|
-
success: false,
|
|
107
|
-
error: `Tailwind compilation failed: ${errorMsg}`
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Get CSS content
|
|
112
|
-
let css = ''
|
|
113
|
-
if (useStdout) {
|
|
114
|
-
css = result.stdout?.toString() || ''
|
|
115
|
-
} else if (fs.existsSync(output)) {
|
|
116
|
-
css = fs.readFileSync(output, 'utf-8')
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
css,
|
|
121
|
-
duration,
|
|
122
|
-
success: true
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
} catch (error: any) {
|
|
126
|
-
return {
|
|
127
|
-
css: '',
|
|
128
|
-
duration: Math.round(performance.now() - startTime),
|
|
129
|
-
success: false,
|
|
130
|
-
error: error.message
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Compile CSS asynchronously (non-blocking)
|
|
137
|
-
*
|
|
138
|
-
* Used for HMR updates where we don't want to block the main thread.
|
|
139
|
-
*/
|
|
140
|
-
export async function compileCssAsync(options: CSSCompileOptions): Promise<CSSCompileResult> {
|
|
141
|
-
return new Promise((resolve) => {
|
|
142
|
-
const startTime = performance.now()
|
|
143
|
-
const { input, output, minify = false } = options
|
|
144
|
-
|
|
145
|
-
if (!fs.existsSync(input)) {
|
|
146
|
-
resolve({
|
|
147
|
-
css: '',
|
|
148
|
-
duration: 0,
|
|
149
|
-
success: false,
|
|
150
|
-
error: `CSS input file not found: ${input}`
|
|
151
|
-
})
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const args = ['@tailwindcss/cli', '-i', input]
|
|
156
|
-
const useStdout = output === ':memory:'
|
|
157
|
-
|
|
158
|
-
if (!useStdout) {
|
|
159
|
-
args.push('-o', output)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (minify) {
|
|
163
|
-
args.push('--minify')
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const child = spawn('bunx', args, {
|
|
167
|
-
cwd: path.dirname(input),
|
|
168
|
-
stdio: useStdout ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'inherit', 'pipe'],
|
|
169
|
-
env: { ...process.env }
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
let stdout = ''
|
|
173
|
-
let stderr = ''
|
|
174
|
-
|
|
175
|
-
if (useStdout && child.stdout) {
|
|
176
|
-
child.stdout.on('data', (data) => { stdout += data.toString() })
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (child.stderr) {
|
|
180
|
-
child.stderr.on('data', (data) => { stderr += data.toString() })
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
child.on('close', (code) => {
|
|
184
|
-
const duration = Math.round(performance.now() - startTime)
|
|
185
|
-
|
|
186
|
-
if (code !== 0) {
|
|
187
|
-
resolve({
|
|
188
|
-
css: '',
|
|
189
|
-
duration,
|
|
190
|
-
success: false,
|
|
191
|
-
error: `Tailwind compilation failed: ${stderr}`
|
|
192
|
-
})
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
let css = ''
|
|
197
|
-
if (useStdout) {
|
|
198
|
-
css = stdout
|
|
199
|
-
} else if (fs.existsSync(output)) {
|
|
200
|
-
css = fs.readFileSync(output, 'utf-8')
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
resolve({
|
|
204
|
-
css,
|
|
205
|
-
duration,
|
|
206
|
-
success: true
|
|
207
|
-
})
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
child.on('error', (err) => {
|
|
211
|
-
resolve({
|
|
212
|
-
css: '',
|
|
213
|
-
duration: Math.round(performance.now() - startTime),
|
|
214
|
-
success: false,
|
|
215
|
-
error: err.message
|
|
216
|
-
})
|
|
217
|
-
})
|
|
218
|
-
})
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// ============================================
|
|
222
|
-
// CSS Watcher for HMR
|
|
223
|
-
// ============================================
|
|
224
|
-
|
|
225
|
-
export interface CSSWatchOptions extends CSSCompileOptions {
|
|
226
|
-
/** Callback when CSS changes */
|
|
227
|
-
onChange: (result: CSSCompileResult) => void
|
|
228
|
-
/** Debounce delay in ms */
|
|
229
|
-
debounce?: number
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Watch CSS and source files for changes, recompile on change
|
|
234
|
-
*
|
|
235
|
-
* This is used by the dev server for HMR support.
|
|
236
|
-
* It watches both the CSS entry point AND all .zen files
|
|
237
|
-
* that Tailwind scans for class names.
|
|
238
|
-
*/
|
|
239
|
-
export function watchCss(options: CSSWatchOptions): () => void {
|
|
240
|
-
const { input, output, minify, onChange, debounce = 100 } = options
|
|
241
|
-
|
|
242
|
-
let timeout: NodeJS.Timeout | null = null
|
|
243
|
-
let isCompiling = false
|
|
244
|
-
|
|
245
|
-
const recompile = async () => {
|
|
246
|
-
if (isCompiling) return
|
|
247
|
-
isCompiling = true
|
|
248
|
-
|
|
249
|
-
const result = await compileCssAsync({ input, output, minify })
|
|
250
|
-
onChange(result)
|
|
251
|
-
|
|
252
|
-
isCompiling = false
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const debouncedRecompile = () => {
|
|
256
|
-
if (timeout) clearTimeout(timeout)
|
|
257
|
-
timeout = setTimeout(recompile, debounce)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Watch the styles directory
|
|
261
|
-
const stylesDir = path.dirname(input)
|
|
262
|
-
const stylesWatcher = fs.watch(stylesDir, { recursive: true }, (event, filename) => {
|
|
263
|
-
if (filename?.endsWith('.css')) {
|
|
264
|
-
debouncedRecompile()
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
// Watch source files that Tailwind scans (for class changes)
|
|
269
|
-
// This assumes standard Zenith structure: src/pages, src/components, src/layouts
|
|
270
|
-
const srcDir = path.resolve(stylesDir, '..')
|
|
271
|
-
let srcWatcher: fs.FSWatcher | null = null
|
|
272
|
-
|
|
273
|
-
if (fs.existsSync(srcDir)) {
|
|
274
|
-
srcWatcher = fs.watch(srcDir, { recursive: true }, (event, filename) => {
|
|
275
|
-
if (filename?.endsWith('.zen') || filename?.endsWith('.tsx') || filename?.endsWith('.jsx')) {
|
|
276
|
-
debouncedRecompile()
|
|
277
|
-
}
|
|
278
|
-
})
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Return cleanup function
|
|
282
|
-
return () => {
|
|
283
|
-
if (timeout) clearTimeout(timeout)
|
|
284
|
-
stylesWatcher.close()
|
|
285
|
-
srcWatcher?.close()
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// ============================================
|
|
290
|
-
// Path Utilities
|
|
291
|
-
// ============================================
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Resolve the canonical globals.css path for a Zenith project
|
|
295
|
-
*/
|
|
296
|
-
export function resolveGlobalsCss(projectRoot: string): string | null {
|
|
297
|
-
// Check for globals.css (canonical)
|
|
298
|
-
const globalsPath = path.join(projectRoot, 'src', 'styles', 'globals.css')
|
|
299
|
-
if (fs.existsSync(globalsPath)) return globalsPath
|
|
300
|
-
|
|
301
|
-
// Check for global.css (legacy)
|
|
302
|
-
const globalPath = path.join(projectRoot, 'src', 'styles', 'global.css')
|
|
303
|
-
if (fs.existsSync(globalPath)) return globalPath
|
|
304
|
-
|
|
305
|
-
return null
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Get the output path for compiled CSS
|
|
310
|
-
*/
|
|
311
|
-
export function getCompiledCssPath(projectRoot: string, mode: 'dev' | 'build'): string {
|
|
312
|
-
if (mode === 'build') {
|
|
313
|
-
return path.join(projectRoot, 'dist', 'assets', 'styles.css')
|
|
314
|
-
}
|
|
315
|
-
// In dev mode, we use in-memory compilation
|
|
316
|
-
return ':memory:'
|
|
317
|
-
}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component Discovery
|
|
3
|
-
*
|
|
4
|
-
* Discovers and catalogs components in a Zenith project.
|
|
5
|
-
* Components are auto-imported based on filename:
|
|
6
|
-
*
|
|
7
|
-
* Auto-Import Rules:
|
|
8
|
-
* - Component name = filename (without .zen extension)
|
|
9
|
-
* - Subdirectories are for organization, not namespacing
|
|
10
|
-
* - Name collisions produce compile-time errors with clear messages
|
|
11
|
-
*
|
|
12
|
-
* Examples:
|
|
13
|
-
* components/Header.zen → <Header />
|
|
14
|
-
* components/sections/HeroSection.zen → <HeroSection />
|
|
15
|
-
* components/ui/buttons/Primary.zen → <Primary />
|
|
16
|
-
*
|
|
17
|
-
* If you have name collisions (same filename in different directories),
|
|
18
|
-
* you must rename one of the components.
|
|
19
|
-
*
|
|
20
|
-
* Requirements:
|
|
21
|
-
* - Auto-import resolution is deterministic and compile-time only
|
|
22
|
-
* - Name collisions produce compile-time errors with clear messages
|
|
23
|
-
* - No runtime component registration or global singleton registries
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import * as fs from 'fs'
|
|
27
|
-
import * as path from 'path'
|
|
28
|
-
import { parseZenFile } from '../parse/parseZenFile'
|
|
29
|
-
import { CompilerError } from '../errors/compilerError'
|
|
30
|
-
import type { TemplateNode, ExpressionIR } from '../ir/types'
|
|
31
|
-
|
|
32
|
-
export interface SlotDefinition {
|
|
33
|
-
name: string | null // null = default slot, string = named slot
|
|
34
|
-
location: {
|
|
35
|
-
line: number
|
|
36
|
-
column: number
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface ComponentMetadata {
|
|
41
|
-
name: string // Component name (e.g., "Card", "HeroSection")
|
|
42
|
-
path: string // Absolute path to .zen file
|
|
43
|
-
relativePath: string // Relative path from components directory
|
|
44
|
-
template: string // Raw template HTML
|
|
45
|
-
nodes: TemplateNode[] // Parsed template nodes
|
|
46
|
-
expressions: ExpressionIR[] // Expressions referenced by nodes
|
|
47
|
-
slots: SlotDefinition[]
|
|
48
|
-
props: string[] // Declared props
|
|
49
|
-
styles: string[] // Raw CSS from <style> blocks
|
|
50
|
-
script: string | null // Raw script content for bundling
|
|
51
|
-
scriptAttributes: Record<string, string> | null // Script attributes (setup, lang)
|
|
52
|
-
hasScript: boolean
|
|
53
|
-
hasStyles: boolean
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Discover all components in a directory with auto-import naming
|
|
58
|
-
*
|
|
59
|
-
* Components are named by their filename (without .zen extension).
|
|
60
|
-
* Subdirectories are for organization only and do not affect the component name.
|
|
61
|
-
*
|
|
62
|
-
* @param baseDir - Base directory to search (e.g., src/components)
|
|
63
|
-
* @returns Map of component name to metadata
|
|
64
|
-
* @throws CompilerError on name collisions
|
|
65
|
-
*/
|
|
66
|
-
export function discoverComponents(baseDir: string): Map<string, ComponentMetadata> {
|
|
67
|
-
const components = new Map<string, ComponentMetadata>()
|
|
68
|
-
const collisions = new Map<string, string[]>() // name → [relative paths]
|
|
69
|
-
|
|
70
|
-
// Check if components directory exists
|
|
71
|
-
if (!fs.existsSync(baseDir)) {
|
|
72
|
-
return components
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Recursively find all .zen files
|
|
76
|
-
const zenFiles = findZenFiles(baseDir)
|
|
77
|
-
|
|
78
|
-
for (const filePath of zenFiles) {
|
|
79
|
-
try {
|
|
80
|
-
const metadata = parseComponentFile(filePath, baseDir)
|
|
81
|
-
if (metadata) {
|
|
82
|
-
// Check for collision
|
|
83
|
-
if (components.has(metadata.name)) {
|
|
84
|
-
const existing = components.get(metadata.name)!
|
|
85
|
-
if (!collisions.has(metadata.name)) {
|
|
86
|
-
collisions.set(metadata.name, [existing.relativePath])
|
|
87
|
-
}
|
|
88
|
-
collisions.get(metadata.name)!.push(metadata.relativePath)
|
|
89
|
-
} else {
|
|
90
|
-
components.set(metadata.name, metadata)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} catch (error: any) {
|
|
94
|
-
console.warn(`[Zenith] Failed to parse component ${filePath}: ${error.message}`)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Report all collisions as a single error
|
|
99
|
-
if (collisions.size > 0) {
|
|
100
|
-
const collisionMessages = Array.from(collisions.entries())
|
|
101
|
-
.map(([name, paths]) => {
|
|
102
|
-
const pathList = paths.map(p => ` - ${p}`).join('\n')
|
|
103
|
-
return `Component name "${name}" is used by multiple files:\n${pathList}`
|
|
104
|
-
})
|
|
105
|
-
.join('\n\n')
|
|
106
|
-
|
|
107
|
-
throw new CompilerError(
|
|
108
|
-
`Component name collision detected!\n\n${collisionMessages}\n\n` +
|
|
109
|
-
`Each component must have a unique filename.\n` +
|
|
110
|
-
`To fix: Rename one of the conflicting components to have a unique name.`,
|
|
111
|
-
baseDir,
|
|
112
|
-
0,
|
|
113
|
-
0
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return components
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Recursively find all .zen files in a directory
|
|
122
|
-
*/
|
|
123
|
-
function findZenFiles(dir: string): string[] {
|
|
124
|
-
const files: string[] = []
|
|
125
|
-
|
|
126
|
-
if (!fs.existsSync(dir)) {
|
|
127
|
-
return files
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
131
|
-
|
|
132
|
-
for (const entry of entries) {
|
|
133
|
-
const fullPath = path.join(dir, entry.name)
|
|
134
|
-
|
|
135
|
-
if (entry.isDirectory()) {
|
|
136
|
-
files.push(...findZenFiles(fullPath))
|
|
137
|
-
} else if (entry.isFile() && entry.name.endsWith('.zen')) {
|
|
138
|
-
files.push(fullPath)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return files
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Parse a component file and extract metadata
|
|
147
|
-
*
|
|
148
|
-
* Component name is derived from the filename (without .zen extension).
|
|
149
|
-
*
|
|
150
|
-
* @param filePath - Absolute path to the component file
|
|
151
|
-
* @param baseDir - Base directory for component discovery (used for relative path)
|
|
152
|
-
*/
|
|
153
|
-
function parseComponentFile(filePath: string, baseDir: string): ComponentMetadata | null {
|
|
154
|
-
const ir = parseZenFile(filePath)
|
|
155
|
-
|
|
156
|
-
// Component name is just the filename (without .zen extension)
|
|
157
|
-
const componentName = path.basename(filePath, '.zen')
|
|
158
|
-
|
|
159
|
-
// Relative path for error messages and debugging
|
|
160
|
-
const relativePath = path.relative(baseDir, filePath)
|
|
161
|
-
|
|
162
|
-
// Extract slots from template
|
|
163
|
-
const slots = extractSlots(ir.template.nodes)
|
|
164
|
-
|
|
165
|
-
// Extract props from script attributes
|
|
166
|
-
const props = ir.script?.attributes['props']?.split(',').map(p => p.trim()) || []
|
|
167
|
-
|
|
168
|
-
// Extract raw CSS from styles
|
|
169
|
-
const styles = ir.styles.map(s => s.raw)
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
name: componentName,
|
|
173
|
-
path: filePath,
|
|
174
|
-
relativePath,
|
|
175
|
-
template: ir.template.raw,
|
|
176
|
-
nodes: ir.template.nodes,
|
|
177
|
-
expressions: ir.template.expressions, // Store expressions for later merging
|
|
178
|
-
slots,
|
|
179
|
-
props,
|
|
180
|
-
styles,
|
|
181
|
-
script: ir.script?.raw || null, // Store raw script content
|
|
182
|
-
scriptAttributes: ir.script?.attributes || null, // Store script attributes
|
|
183
|
-
hasScript: ir.script !== null,
|
|
184
|
-
hasStyles: ir.styles.length > 0
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Extract slot definitions from template nodes
|
|
190
|
-
*/
|
|
191
|
-
function extractSlots(nodes: TemplateNode[]): SlotDefinition[] {
|
|
192
|
-
const slots: SlotDefinition[] = []
|
|
193
|
-
|
|
194
|
-
function traverse(node: TemplateNode) {
|
|
195
|
-
if (node.type === 'element') {
|
|
196
|
-
// Check if this is a <slot> tag
|
|
197
|
-
if (node.tag === 'slot') {
|
|
198
|
-
// Extract slot name from attributes
|
|
199
|
-
const nameAttr = node.attributes.find(attr => attr.name === 'name')
|
|
200
|
-
const slotName = typeof nameAttr?.value === 'string' ? nameAttr.value : null
|
|
201
|
-
|
|
202
|
-
slots.push({
|
|
203
|
-
name: slotName,
|
|
204
|
-
location: node.location
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Traverse children
|
|
209
|
-
for (const child of node.children) {
|
|
210
|
-
traverse(child)
|
|
211
|
-
}
|
|
212
|
-
} else if (node.type === 'component') {
|
|
213
|
-
// Also traverse component children
|
|
214
|
-
for (const child of node.children) {
|
|
215
|
-
traverse(child)
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
for (const node of nodes) {
|
|
221
|
-
traverse(node)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return slots
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Check if a tag name represents a component (starts with uppercase)
|
|
229
|
-
*/
|
|
230
|
-
export function isComponentTag(tagName: string): boolean {
|
|
231
|
-
return tagName.length > 0 && tagName[0] !== undefined && tagName[0] === tagName[0].toUpperCase()
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Get component metadata by name
|
|
236
|
-
*/
|
|
237
|
-
export function getComponent(
|
|
238
|
-
components: Map<string, ComponentMetadata>,
|
|
239
|
-
name: string
|
|
240
|
-
): ComponentMetadata | undefined {
|
|
241
|
-
return components.get(name)
|
|
242
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { parseTemplate } from '../parse/parseTemplate'
|
|
4
|
-
import { parseScript } from '../parse/parseScript'
|
|
5
|
-
import { extractProps, extractStateDeclarations } from '../parse/scriptAnalysis'
|
|
6
|
-
|
|
7
|
-
export interface LayoutMetadata {
|
|
8
|
-
name: string
|
|
9
|
-
filePath: string
|
|
10
|
-
props: string[]
|
|
11
|
-
states: Map<string, string>
|
|
12
|
-
html: string
|
|
13
|
-
scripts: string[]
|
|
14
|
-
styles: string[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Discover layouts in a directory
|
|
19
|
-
*/
|
|
20
|
-
export function discoverLayouts(layoutsDir: string): Map<string, LayoutMetadata> {
|
|
21
|
-
const layouts = new Map<string, LayoutMetadata>()
|
|
22
|
-
|
|
23
|
-
if (!fs.existsSync(layoutsDir)) return layouts
|
|
24
|
-
|
|
25
|
-
const files = fs.readdirSync(layoutsDir)
|
|
26
|
-
for (const file of files) {
|
|
27
|
-
if (file.endsWith('.zen')) {
|
|
28
|
-
const filePath = path.join(layoutsDir, file)
|
|
29
|
-
const name = path.basename(file, '.zen')
|
|
30
|
-
const source = fs.readFileSync(filePath, 'utf-8')
|
|
31
|
-
|
|
32
|
-
const script = parseScript(source)
|
|
33
|
-
const props = script ? extractProps(script.raw) : []
|
|
34
|
-
const states = script ? extractStateDeclarations(script.raw) : new Map()
|
|
35
|
-
|
|
36
|
-
// Extract styles
|
|
37
|
-
const styles: string[] = []
|
|
38
|
-
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi
|
|
39
|
-
let match
|
|
40
|
-
while ((match = styleRegex.exec(source)) !== null) {
|
|
41
|
-
if (match[1]) styles.push(match[1].trim())
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Extract HTML (everything except inline scripts/style)
|
|
45
|
-
// Preserve external script tags (<script src="...">) but remove inline <script setup> blocks
|
|
46
|
-
// Use a function-based replace to check for src attribute
|
|
47
|
-
let html = source.replace(/<script([^>]*)>([\s\S]*?)<\/script>/gi, (match, attrs, content) => {
|
|
48
|
-
// Keep script tags with src attribute (external scripts)
|
|
49
|
-
if (attrs.includes('src=')) {
|
|
50
|
-
return match;
|
|
51
|
-
}
|
|
52
|
-
// Remove inline scripts (those without src)
|
|
53
|
-
return '';
|
|
54
|
-
})
|
|
55
|
-
html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '').trim()
|
|
56
|
-
|
|
57
|
-
layouts.set(name, {
|
|
58
|
-
name,
|
|
59
|
-
filePath,
|
|
60
|
-
props,
|
|
61
|
-
states,
|
|
62
|
-
html,
|
|
63
|
-
scripts: script ? [script.raw] : [],
|
|
64
|
-
styles
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return layouts
|
|
70
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compiler Error Handling
|
|
3
|
-
*
|
|
4
|
-
* Compiler errors with source location information
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export class CompilerError extends Error {
|
|
8
|
-
file: string
|
|
9
|
-
line: number
|
|
10
|
-
column: number
|
|
11
|
-
|
|
12
|
-
constructor(message: string, file: string, line: number, column: number) {
|
|
13
|
-
super(`${file}:${line}:${column} ${message}`)
|
|
14
|
-
this.name = 'CompilerError'
|
|
15
|
-
this.file = file
|
|
16
|
-
this.line = line
|
|
17
|
-
this.column = column
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
override toString(): string {
|
|
21
|
-
return `${this.file}:${this.line}:${this.column} ${this.message}`
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Invariant Error
|
|
27
|
-
*
|
|
28
|
-
* Thrown when a Zenith compiler invariant is violated.
|
|
29
|
-
* Invariants are non-negotiable rules that guarantee correct behavior.
|
|
30
|
-
*
|
|
31
|
-
* If an invariant fails, the compiler is at fault — not the user.
|
|
32
|
-
* The user receives a clear explanation of what is forbidden and why.
|
|
33
|
-
*/
|
|
34
|
-
export class InvariantError extends CompilerError {
|
|
35
|
-
invariantId: string
|
|
36
|
-
guarantee: string
|
|
37
|
-
|
|
38
|
-
constructor(
|
|
39
|
-
invariantId: string,
|
|
40
|
-
message: string,
|
|
41
|
-
guarantee: string,
|
|
42
|
-
file: string,
|
|
43
|
-
line: number,
|
|
44
|
-
column: number
|
|
45
|
-
) {
|
|
46
|
-
super(`[${invariantId}] ${message}\n\n Zenith Guarantee: ${guarantee}`, file, line, column)
|
|
47
|
-
this.name = 'InvariantError'
|
|
48
|
-
this.invariantId = invariantId
|
|
49
|
-
this.guarantee = guarantee
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
override toString(): string {
|
|
53
|
-
return `${this.file}:${this.line}:${this.column} [${this.invariantId}] ${this.message}`
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|