@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.
- package/README.md +20 -19
- package/cli/commands/add.ts +2 -2
- package/cli/commands/build.ts +2 -3
- package/cli/commands/dev.ts +182 -103
- 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 +7465 -19128
- package/dist/zen-dev.js +7465 -19128
- package/dist/zen-preview.js +7465 -19128
- package/dist/zenith.js +7465 -19128
- package/package.json +21 -22
- package/cli/utils/content.ts +0 -112
- 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 -178
- 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 -34
- 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 -599
- 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 -317
- package/compiler/runtime/generateDOM.ts +0 -246
- 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 -370
- package/compiler/runtime/wrapExpression.ts +0 -95
- package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
- package/compiler/spa-build.ts +0 -917
- package/compiler/ssg-build.ts +0 -422
- package/compiler/test/validate-test.ts +0 -104
- package/compiler/transform/classifyExpression.ts +0 -444
- package/compiler/transform/componentResolver.ts +0 -312
- package/compiler/transform/componentScriptTransformer.ts +0 -303
- package/compiler/transform/expressionTransformer.ts +0 -385
- package/compiler/transform/fragmentLowering.ts +0 -634
- package/compiler/transform/generateBindings.ts +0 -47
- 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 -126
- 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 -16
- package/core/config/loader.ts +0 -69
- package/core/config/types.ts +0 -89
- package/core/plugins/index.ts +0 -7
- package/core/plugins/registry.ts +0 -81
- package/dist/cli.js +0 -11665
- package/router/manifest.ts +0 -314
- package/router/navigation/ZenLink.zen +0 -231
- package/router/navigation/index.ts +0 -78
- package/router/navigation/zen-link.ts +0 -584
- package/router/runtime.ts +0 -458
- package/router/types.ts +0 -168
- package/runtime/build.ts +0 -17
- package/runtime/bundle-generator.ts +0 -1247
- package/runtime/client-runtime.ts +0 -549
- package/runtime/serve.ts +0 -93
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Invariant Validation
|
|
3
|
-
*
|
|
4
|
-
* Compile-time checks that enforce Zenith's non-negotiable invariants.
|
|
5
|
-
* If any invariant is violated, compilation fails immediately with a clear explanation.
|
|
6
|
-
*
|
|
7
|
-
* INVARIANTS:
|
|
8
|
-
* 1. Components are structural only — no reactive scopes, no state
|
|
9
|
-
* 2. Reactivity is scope-owned — flows through components, never into them
|
|
10
|
-
* 3. Slot projection preserves identity — loopContext is preserved or merged upward
|
|
11
|
-
* 4. Attribute ownership belongs to usage — must be forwarded to semantic root
|
|
12
|
-
* 5. All resolution is compile-time — no unresolved components
|
|
13
|
-
* 6. Failure is a compiler error — no silent degradation
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type { ZenIR, TemplateNode, ElementNode, ComponentNode, LoopContext } from '../ir/types'
|
|
17
|
-
import { InvariantError } from '../errors/compilerError'
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Invariant Codes
|
|
21
|
-
*
|
|
22
|
-
* Each invariant has a unique ID for tracking and documentation.
|
|
23
|
-
*/
|
|
24
|
-
export const INVARIANT = {
|
|
25
|
-
LOOP_CONTEXT_LOST: 'INV001',
|
|
26
|
-
ATTRIBUTE_NOT_FORWARDED: 'INV002',
|
|
27
|
-
UNRESOLVED_COMPONENT: 'INV003',
|
|
28
|
-
REACTIVE_BOUNDARY: 'INV004',
|
|
29
|
-
TEMPLATE_TAG: 'INV005',
|
|
30
|
-
SLOT_ATTRIBUTE: 'INV006',
|
|
31
|
-
ORPHAN_COMPOUND: 'INV007',
|
|
32
|
-
NON_ENUMERABLE_JSX: 'INV008',
|
|
33
|
-
} as const
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Guarantee Messages
|
|
37
|
-
*
|
|
38
|
-
* Human-readable explanations of what each invariant guarantees.
|
|
39
|
-
*/
|
|
40
|
-
const GUARANTEES: Record<string, string> = {
|
|
41
|
-
[INVARIANT.LOOP_CONTEXT_LOST]:
|
|
42
|
-
'Slot content retains its original reactive scope. Expressions and event handlers continue to work after projection.',
|
|
43
|
-
[INVARIANT.ATTRIBUTE_NOT_FORWARDED]:
|
|
44
|
-
'Attributes passed to components are forwarded to the semantic root element.',
|
|
45
|
-
[INVARIANT.UNRESOLVED_COMPONENT]:
|
|
46
|
-
'All components are resolved at compile time. No runtime component discovery.',
|
|
47
|
-
[INVARIANT.REACTIVE_BOUNDARY]:
|
|
48
|
-
'Components are purely structural transforms. They do not create reactive scopes.',
|
|
49
|
-
[INVARIANT.TEMPLATE_TAG]:
|
|
50
|
-
'Named slots use compound component pattern (Card.Header), not <template> tags.',
|
|
51
|
-
[INVARIANT.SLOT_ATTRIBUTE]:
|
|
52
|
-
'Named slots use compound component pattern (Card.Header), not slot="" attributes.',
|
|
53
|
-
[INVARIANT.ORPHAN_COMPOUND]:
|
|
54
|
-
'Compound slot markers (Card.Header) must be direct children of their parent component.',
|
|
55
|
-
[INVARIANT.NON_ENUMERABLE_JSX]:
|
|
56
|
-
'JSX expressions must have statically enumerable output. The compiler must know all possible DOM shapes at compile time.',
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Validate all invariants on a compiled IR
|
|
61
|
-
*
|
|
62
|
-
* Called after component resolution to ensure all invariants hold.
|
|
63
|
-
* Throws InvariantError if any invariant is violated.
|
|
64
|
-
*/
|
|
65
|
-
export function validateInvariants(ir: ZenIR, filePath: string): void {
|
|
66
|
-
validateNoUnresolvedComponents(ir.template.nodes, filePath)
|
|
67
|
-
// Additional invariant checks can be added here
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* INV003: Validate that no unresolved components remain
|
|
72
|
-
*
|
|
73
|
-
* After component resolution, all ComponentNode instances should be
|
|
74
|
-
* resolved to ElementNode instances. If any remain, the compiler failed.
|
|
75
|
-
*/
|
|
76
|
-
export function validateNoUnresolvedComponents(
|
|
77
|
-
nodes: TemplateNode[],
|
|
78
|
-
filePath: string
|
|
79
|
-
): void {
|
|
80
|
-
for (const node of nodes) {
|
|
81
|
-
checkNodeForUnresolvedComponent(node, filePath)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function checkNodeForUnresolvedComponent(node: TemplateNode, filePath: string): void {
|
|
86
|
-
if (node.type === 'component') {
|
|
87
|
-
throw new InvariantError(
|
|
88
|
-
INVARIANT.UNRESOLVED_COMPONENT,
|
|
89
|
-
`Unresolved component: <${node.name}>. Component was not found or failed to resolve.`,
|
|
90
|
-
GUARANTEES[INVARIANT.UNRESOLVED_COMPONENT]!,
|
|
91
|
-
filePath,
|
|
92
|
-
node.location.line,
|
|
93
|
-
node.location.column
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (node.type === 'element') {
|
|
98
|
-
for (const child of node.children) {
|
|
99
|
-
checkNodeForUnresolvedComponent(child, filePath)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* INV005: Validate no <template> tags are used
|
|
106
|
-
*
|
|
107
|
-
* Zenith uses compound component pattern for named slots.
|
|
108
|
-
* <template> tags are a different mental model and are forbidden.
|
|
109
|
-
*/
|
|
110
|
-
export function validateNoTemplateTags(
|
|
111
|
-
nodes: TemplateNode[],
|
|
112
|
-
filePath: string
|
|
113
|
-
): void {
|
|
114
|
-
for (const node of nodes) {
|
|
115
|
-
checkNodeForTemplateTag(node, filePath)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function checkNodeForTemplateTag(node: TemplateNode, filePath: string): void {
|
|
120
|
-
if (node.type === 'element' && node.tag === 'template') {
|
|
121
|
-
throw new InvariantError(
|
|
122
|
-
INVARIANT.TEMPLATE_TAG,
|
|
123
|
-
`<template> tags are forbidden in Zenith. Use compound components (e.g., Card.Header) for named slots.`,
|
|
124
|
-
GUARANTEES[INVARIANT.TEMPLATE_TAG]!,
|
|
125
|
-
filePath,
|
|
126
|
-
node.location.line,
|
|
127
|
-
node.location.column
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (node.type === 'element') {
|
|
132
|
-
for (const child of node.children) {
|
|
133
|
-
checkNodeForTemplateTag(child, filePath)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* INV006: Validate no slot="" attributes are used
|
|
140
|
-
*
|
|
141
|
-
* Zenith uses compound component pattern, not slot props.
|
|
142
|
-
*/
|
|
143
|
-
export function validateNoSlotAttributes(
|
|
144
|
-
nodes: TemplateNode[],
|
|
145
|
-
filePath: string
|
|
146
|
-
): void {
|
|
147
|
-
for (const node of nodes) {
|
|
148
|
-
checkNodeForSlotAttribute(node, filePath)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function checkNodeForSlotAttribute(node: TemplateNode, filePath: string): void {
|
|
153
|
-
if (node.type === 'element') {
|
|
154
|
-
const slotAttr = node.attributes.find(attr => attr.name === 'slot')
|
|
155
|
-
if (slotAttr) {
|
|
156
|
-
throw new InvariantError(
|
|
157
|
-
INVARIANT.SLOT_ATTRIBUTE,
|
|
158
|
-
`slot="${typeof slotAttr.value === 'string' ? slotAttr.value : '...'}" attribute is forbidden. Use compound components (e.g., Card.Header) for named slots.`,
|
|
159
|
-
GUARANTEES[INVARIANT.SLOT_ATTRIBUTE]!,
|
|
160
|
-
filePath,
|
|
161
|
-
slotAttr.location.line,
|
|
162
|
-
slotAttr.location.column
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
for (const child of node.children) {
|
|
167
|
-
checkNodeForSlotAttribute(child, filePath)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (node.type === 'component') {
|
|
172
|
-
for (const child of node.children) {
|
|
173
|
-
checkNodeForSlotAttribute(child, filePath)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* INV001: Validate loopContext is preserved during slot projection
|
|
180
|
-
*
|
|
181
|
-
* When slot content is moved from the usage site to the slot target,
|
|
182
|
-
* the loopContext must be preserved so reactivity continues to work.
|
|
183
|
-
*
|
|
184
|
-
* @param originalNodes - Nodes before slot projection
|
|
185
|
-
* @param projectedNodes - Nodes after slot projection
|
|
186
|
-
*/
|
|
187
|
-
export function validateLoopContextPreservation(
|
|
188
|
-
originalNodes: TemplateNode[],
|
|
189
|
-
projectedNodes: TemplateNode[],
|
|
190
|
-
filePath: string
|
|
191
|
-
): void {
|
|
192
|
-
// Collect all loopContext from original nodes
|
|
193
|
-
const originalContexts = collectLoopContexts(originalNodes)
|
|
194
|
-
|
|
195
|
-
// Verify all contexts are still present in projected nodes
|
|
196
|
-
const projectedContexts = collectLoopContexts(projectedNodes)
|
|
197
|
-
|
|
198
|
-
for (const entry of Array.from(originalContexts.entries())) {
|
|
199
|
-
const [nodeId, context] = entry
|
|
200
|
-
const projected = projectedContexts.get(nodeId)
|
|
201
|
-
if (context && !projected) {
|
|
202
|
-
// loopContext was lost during projection
|
|
203
|
-
// This is a compiler bug, not a user error
|
|
204
|
-
throw new InvariantError(
|
|
205
|
-
INVARIANT.LOOP_CONTEXT_LOST,
|
|
206
|
-
`Reactive scope was lost during slot projection. This is a Zenith compiler bug.`,
|
|
207
|
-
GUARANTEES[INVARIANT.LOOP_CONTEXT_LOST]!,
|
|
208
|
-
filePath,
|
|
209
|
-
1, // We don't have precise location, use line 1
|
|
210
|
-
1
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function collectLoopContexts(nodes: TemplateNode[]): Map<string, LoopContext | undefined> {
|
|
217
|
-
const contexts = new Map<string, LoopContext | undefined>()
|
|
218
|
-
let nodeId = 0
|
|
219
|
-
|
|
220
|
-
function visit(node: TemplateNode): void {
|
|
221
|
-
const id = `node_${nodeId++}`
|
|
222
|
-
|
|
223
|
-
if (node.type === 'expression') {
|
|
224
|
-
contexts.set(id, node.loopContext)
|
|
225
|
-
} else if (node.type === 'element') {
|
|
226
|
-
contexts.set(id, node.loopContext)
|
|
227
|
-
for (const attr of node.attributes) {
|
|
228
|
-
if (attr.loopContext) {
|
|
229
|
-
contexts.set(`${id}_attr_${attr.name}`, attr.loopContext)
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
for (const child of node.children) {
|
|
233
|
-
visit(child)
|
|
234
|
-
}
|
|
235
|
-
} else if (node.type === 'component') {
|
|
236
|
-
contexts.set(id, node.loopContext)
|
|
237
|
-
for (const child of node.children) {
|
|
238
|
-
visit(child)
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
for (const node of nodes) {
|
|
244
|
-
visit(node)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return contexts
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* INV007: Throw error for orphan compound slot markers
|
|
252
|
-
*
|
|
253
|
-
* Called when a compound component (Card.Header) is found outside
|
|
254
|
-
* its parent component context.
|
|
255
|
-
*/
|
|
256
|
-
export function throwOrphanCompoundError(
|
|
257
|
-
componentName: string,
|
|
258
|
-
parentName: string,
|
|
259
|
-
filePath: string,
|
|
260
|
-
line: number,
|
|
261
|
-
column: number
|
|
262
|
-
): never {
|
|
263
|
-
throw new InvariantError(
|
|
264
|
-
INVARIANT.ORPHAN_COMPOUND,
|
|
265
|
-
`<${componentName}> must be a direct child of <${parentName}>. Compound slot markers cannot be used outside their parent component.`,
|
|
266
|
-
GUARANTEES[INVARIANT.ORPHAN_COMPOUND]!,
|
|
267
|
-
filePath,
|
|
268
|
-
line,
|
|
269
|
-
column
|
|
270
|
-
)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* INV003: Throw error for unresolved component
|
|
275
|
-
*
|
|
276
|
-
* Called when a component definition cannot be found.
|
|
277
|
-
*/
|
|
278
|
-
export function throwUnresolvedComponentError(
|
|
279
|
-
componentName: string,
|
|
280
|
-
filePath: string,
|
|
281
|
-
line: number,
|
|
282
|
-
column: number
|
|
283
|
-
): never {
|
|
284
|
-
throw new InvariantError(
|
|
285
|
-
INVARIANT.UNRESOLVED_COMPONENT,
|
|
286
|
-
`Component <${componentName}> not found. All components must be defined in the components directory.`,
|
|
287
|
-
GUARANTEES[INVARIANT.UNRESOLVED_COMPONENT]!,
|
|
288
|
-
filePath,
|
|
289
|
-
line,
|
|
290
|
-
column
|
|
291
|
-
)
|
|
292
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Expression Validation
|
|
3
|
-
*
|
|
4
|
-
* Phase 8/9/10: Compile-time validation of all expressions
|
|
5
|
-
*
|
|
6
|
-
* Ensures all expressions are valid JavaScript and will not cause runtime errors.
|
|
7
|
-
* Build fails immediately if any expression is invalid.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ExpressionIR } from '../ir/types'
|
|
11
|
-
import { CompilerError } from '../errors/compilerError'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Validation result
|
|
15
|
-
*/
|
|
16
|
-
export interface ValidationResult {
|
|
17
|
-
valid: boolean
|
|
18
|
-
errors: CompilerError[]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Validate all expressions in the IR
|
|
23
|
-
*
|
|
24
|
-
* @param expressions - Array of expressions to validate
|
|
25
|
-
* @param filePath - Source file path for error reporting
|
|
26
|
-
* @returns Validation result with errors
|
|
27
|
-
*/
|
|
28
|
-
export function validateExpressions(
|
|
29
|
-
expressions: ExpressionIR[],
|
|
30
|
-
filePath: string
|
|
31
|
-
): ValidationResult {
|
|
32
|
-
const errors: CompilerError[] = []
|
|
33
|
-
|
|
34
|
-
for (const expr of expressions) {
|
|
35
|
-
const exprErrors = validateSingleExpression(expr, filePath)
|
|
36
|
-
errors.push(...exprErrors)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
valid: errors.length === 0,
|
|
41
|
-
errors
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Validate a single expression
|
|
47
|
-
*/
|
|
48
|
-
function validateSingleExpression(
|
|
49
|
-
expr: ExpressionIR,
|
|
50
|
-
filePath: string
|
|
51
|
-
): CompilerError[] {
|
|
52
|
-
const errors: CompilerError[] = []
|
|
53
|
-
const { id, code, location } = expr
|
|
54
|
-
|
|
55
|
-
// Basic syntax validation using a safe approach
|
|
56
|
-
// We don't execute the code, just validate syntax
|
|
57
|
-
// Note: Expressions may contain JSX/HTML syntax (e.g., condition && <element>)
|
|
58
|
-
// which is not valid JavaScript but is valid in our expression language.
|
|
59
|
-
// We skip strict JavaScript validation for expressions that contain JSX.
|
|
60
|
-
|
|
61
|
-
const hasJSX = /<[a-zA-Z]/.test(code) || /\/>/.test(code)
|
|
62
|
-
|
|
63
|
-
if (!hasJSX) {
|
|
64
|
-
// Only validate JavaScript syntax if there's no JSX
|
|
65
|
-
// Note: Using Function constructor here is for syntax validation only (compile-time)
|
|
66
|
-
// This is safe because:
|
|
67
|
-
// 1. It's only called at compile time, not runtime
|
|
68
|
-
// 2. We're only checking syntax, not executing the code
|
|
69
|
-
// 3. The actual runtime uses pre-compiled functions, not Function constructor
|
|
70
|
-
try {
|
|
71
|
-
// Use Function constructor to validate syntax (doesn't execute)
|
|
72
|
-
// This is compile-time only - runtime never uses Function constructor
|
|
73
|
-
new Function('state', 'loaderData', 'props', 'stores', `return ${code}`)
|
|
74
|
-
} catch (error: any) {
|
|
75
|
-
errors.push(
|
|
76
|
-
new CompilerError(
|
|
77
|
-
`Invalid expression syntax: ${code}\n${error.message}`,
|
|
78
|
-
filePath,
|
|
79
|
-
location.line,
|
|
80
|
-
location.column
|
|
81
|
-
)
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
// If hasJSX, we skip JavaScript validation - JSX syntax is handled by the parser/runtime
|
|
86
|
-
|
|
87
|
-
// Check for dangerous patterns
|
|
88
|
-
if (code.includes('eval(') || code.includes('Function(') || code.includes('with (')) {
|
|
89
|
-
errors.push(
|
|
90
|
-
new CompilerError(
|
|
91
|
-
`Expression contains unsafe code: ${code}`,
|
|
92
|
-
filePath,
|
|
93
|
-
location.line,
|
|
94
|
-
location.column
|
|
95
|
-
)
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Check for undefined global references (basic heuristic)
|
|
100
|
-
// This is a simple check - can be enhanced with AST parsing
|
|
101
|
-
const globalPattern = /\b(window|document|console|globalThis)\./g
|
|
102
|
-
const matches = code.match(globalPattern)
|
|
103
|
-
if (matches && matches.length > 0) {
|
|
104
|
-
// Warn but don't fail - some global access might be intentional
|
|
105
|
-
// In a stricter mode, we could fail here
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Check for common syntax errors
|
|
109
|
-
const openBraces = (code.match(/\{/g) || []).length
|
|
110
|
-
const closeBraces = (code.match(/\}/g) || []).length
|
|
111
|
-
const openParens = (code.match(/\(/g) || []).length
|
|
112
|
-
const closeParens = (code.match(/\)/g) || []).length
|
|
113
|
-
const openBrackets = (code.match(/\[/g) || []).length
|
|
114
|
-
const closeBrackets = (code.match(/\]/g) || []).length
|
|
115
|
-
|
|
116
|
-
if (openBraces !== closeBraces) {
|
|
117
|
-
errors.push(
|
|
118
|
-
new CompilerError(
|
|
119
|
-
`Mismatched braces in expression: ${code}`,
|
|
120
|
-
filePath,
|
|
121
|
-
location.line,
|
|
122
|
-
location.column
|
|
123
|
-
)
|
|
124
|
-
)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (openParens !== closeParens) {
|
|
128
|
-
errors.push(
|
|
129
|
-
new CompilerError(
|
|
130
|
-
`Mismatched parentheses in expression: ${code}`,
|
|
131
|
-
filePath,
|
|
132
|
-
location.line,
|
|
133
|
-
location.column
|
|
134
|
-
)
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (openBrackets !== closeBrackets) {
|
|
139
|
-
errors.push(
|
|
140
|
-
new CompilerError(
|
|
141
|
-
`Mismatched brackets in expression: ${code}`,
|
|
142
|
-
filePath,
|
|
143
|
-
location.line,
|
|
144
|
-
location.column
|
|
145
|
-
)
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return errors
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Validate and throw if invalid
|
|
154
|
-
*
|
|
155
|
-
* @throws CompilerError if any expression is invalid
|
|
156
|
-
*/
|
|
157
|
-
export function validateExpressionsOrThrow(
|
|
158
|
-
expressions: ExpressionIR[],
|
|
159
|
-
filePath: string
|
|
160
|
-
): void {
|
|
161
|
-
const result = validateExpressions(expressions, filePath)
|
|
162
|
-
|
|
163
|
-
if (!result.valid && result.errors.length > 0) {
|
|
164
|
-
// Throw the first error (can be enhanced to collect all)
|
|
165
|
-
throw result.errors[0]
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
package/core/config/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Config
|
|
3
|
-
*
|
|
4
|
-
* Public exports for zenith/config
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export { defineConfig } from './types';
|
|
8
|
-
export type {
|
|
9
|
-
ZenithConfig,
|
|
10
|
-
ZenithPlugin,
|
|
11
|
-
PluginContext,
|
|
12
|
-
ContentSourceConfig,
|
|
13
|
-
ContentPluginOptions,
|
|
14
|
-
ContentItem
|
|
15
|
-
} from './types';
|
|
16
|
-
export { loadZenithConfig, hasZenithConfig } from './loader';
|
package/core/config/loader.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Config Loader
|
|
3
|
-
*
|
|
4
|
-
* Loads zenith.config.ts from the project root
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import fs from 'node:fs';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import type { ZenithConfig } from './types';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Load zenith.config.ts from the project root
|
|
13
|
-
*
|
|
14
|
-
* @param projectRoot - Absolute path to the project root
|
|
15
|
-
* @returns Parsed ZenithConfig or empty config if not found
|
|
16
|
-
*/
|
|
17
|
-
export async function loadZenithConfig(projectRoot: string): Promise<ZenithConfig> {
|
|
18
|
-
// Check for TypeScript config first, then JavaScript
|
|
19
|
-
const configPaths = [
|
|
20
|
-
path.join(projectRoot, 'zenith.config.ts'),
|
|
21
|
-
path.join(projectRoot, 'zenith.config.js'),
|
|
22
|
-
path.join(projectRoot, 'zenith.config.mjs'),
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
let configPath: string | null = null;
|
|
26
|
-
for (const p of configPaths) {
|
|
27
|
-
if (fs.existsSync(p)) {
|
|
28
|
-
configPath = p;
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!configPath) {
|
|
34
|
-
// No config file found, return empty config
|
|
35
|
-
return { plugins: [] };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
// Use dynamic import to load the config
|
|
40
|
-
// Bun supports importing TS files directly
|
|
41
|
-
const configModule = await import(configPath);
|
|
42
|
-
const config = configModule.default || configModule;
|
|
43
|
-
|
|
44
|
-
// Validate basic structure
|
|
45
|
-
if (typeof config !== 'object' || config === null) {
|
|
46
|
-
console.warn(`[Zenith] Invalid config format in ${configPath}`);
|
|
47
|
-
return { plugins: [] };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return config as ZenithConfig;
|
|
51
|
-
} catch (error: unknown) {
|
|
52
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
-
console.error(`[Zenith] Failed to load config from ${configPath}:`, message);
|
|
54
|
-
return { plugins: [] };
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Check if a zenith.config.ts exists in the project
|
|
60
|
-
*/
|
|
61
|
-
export function hasZenithConfig(projectRoot: string): boolean {
|
|
62
|
-
const configPaths = [
|
|
63
|
-
path.join(projectRoot, 'zenith.config.ts'),
|
|
64
|
-
path.join(projectRoot, 'zenith.config.js'),
|
|
65
|
-
path.join(projectRoot, 'zenith.config.mjs'),
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
return configPaths.some(p => fs.existsSync(p));
|
|
69
|
-
}
|
package/core/config/types.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Config Types
|
|
3
|
-
*
|
|
4
|
-
* Configuration interfaces for zenith.config.ts
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// ============================================
|
|
8
|
-
// Content Plugin Types
|
|
9
|
-
// ============================================
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Configuration for a content source
|
|
13
|
-
*/
|
|
14
|
-
export interface ContentSourceConfig {
|
|
15
|
-
/** Root directory relative to project root (e.g., "../zenith-docs" or "content") */
|
|
16
|
-
root: string;
|
|
17
|
-
/** Folders to include from the root (e.g., ["documentation"]). Defaults to all. */
|
|
18
|
-
include?: string[];
|
|
19
|
-
/** Folders to exclude from the root (e.g., ["changelog"]) */
|
|
20
|
-
exclude?: string[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Options for the content plugin
|
|
25
|
-
*/
|
|
26
|
-
export interface ContentPluginOptions {
|
|
27
|
-
/** Named content sources mapped to their configuration */
|
|
28
|
-
sources?: Record<string, ContentSourceConfig>;
|
|
29
|
-
/** Legacy: Single content directory (deprecated, use sources instead) */
|
|
30
|
-
contentDir?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ============================================
|
|
34
|
-
// Core Plugin Types
|
|
35
|
-
// ============================================
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Context passed to plugins during setup
|
|
39
|
-
*/
|
|
40
|
-
export interface PluginContext {
|
|
41
|
-
/** Absolute path to project root */
|
|
42
|
-
projectRoot: string;
|
|
43
|
-
/** Set content data for the runtime */
|
|
44
|
-
setContentData: (data: Record<string, ContentItem[]>) => void;
|
|
45
|
-
/** Additional options passed from config */
|
|
46
|
-
options?: Record<string, unknown>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* A content item loaded from a source
|
|
51
|
-
*/
|
|
52
|
-
export interface ContentItem {
|
|
53
|
-
id?: string | number;
|
|
54
|
-
slug?: string | null;
|
|
55
|
-
collection?: string | null;
|
|
56
|
-
content?: string | null;
|
|
57
|
-
[key: string]: unknown;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* A Zenith plugin definition
|
|
62
|
-
*/
|
|
63
|
-
export interface ZenithPlugin {
|
|
64
|
-
/** Unique plugin name */
|
|
65
|
-
name: string;
|
|
66
|
-
/** Setup function called during initialization */
|
|
67
|
-
setup: (ctx: PluginContext) => void | Promise<void>;
|
|
68
|
-
/** Plugin-specific configuration (preserved for reference) */
|
|
69
|
-
config?: unknown;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ============================================
|
|
73
|
-
// Main Config Types
|
|
74
|
-
// ============================================
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Zenith configuration object
|
|
78
|
-
*/
|
|
79
|
-
export interface ZenithConfig {
|
|
80
|
-
/** List of plugins to load */
|
|
81
|
-
plugins?: ZenithPlugin[];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Define a Zenith configuration with full type safety
|
|
86
|
-
*/
|
|
87
|
-
export function defineConfig(config: ZenithConfig): ZenithConfig {
|
|
88
|
-
return config;
|
|
89
|
-
}
|