@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
package/README.md
CHANGED
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
# @zenithbuild/core ⚡
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The execution target and orchestrator for the Zenith framework. Contains the minimal reactive runtime and CLI tools.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Zenith is a modern reactive web framework designed for maximum performance and developer experience. The core package
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **Build Primitives**: Tools for dev servers and production builds.
|
|
7
|
+
Zenith is a modern reactive web framework designed for maximum performance and developer experience. The core package serves as the **Execution Target**:
|
|
8
|
+
- **Reactivity Engine**: Atomic signals and deep state with zero runtime abstraction.
|
|
9
|
+
- **Lifecycle System**: Efficient `onMount`/`onUnmount` management.
|
|
10
|
+
- **CLI Orchestrator**: Commands (`dev`, `build`, `preview`) that drive the native compiler.
|
|
12
11
|
|
|
13
12
|
## Key Components
|
|
14
13
|
|
|
15
|
-
### 1.
|
|
16
|
-
The
|
|
14
|
+
### 1. Reactivity (`/core/reactivity`)
|
|
15
|
+
The foundational reactive system. Pure, fast, and deterministic.
|
|
17
16
|
|
|
18
|
-
### 2.
|
|
19
|
-
|
|
17
|
+
### 2. Lifecycle (`/core/lifecycle`)
|
|
18
|
+
Instance-based lifecycle management tied to the DOM.
|
|
20
19
|
|
|
21
|
-
### 3.
|
|
22
|
-
|
|
20
|
+
### 3. CLI (`/cli`)
|
|
21
|
+
The command-line interface that orchestrates the system build chain.
|
|
23
22
|
|
|
24
|
-
## Architecture
|
|
23
|
+
## Coordinated Architecture
|
|
25
24
|
|
|
26
|
-
Zenith follows a "Compiler-First" philosophy
|
|
25
|
+
Zenith follows a strict "Compiler-First" philosophy:
|
|
26
|
+
- **@zenithbuild/compiler**: Owns all structures, wiring, and build-time guarantees.
|
|
27
|
+
- **@zenithbuild/core**: Owns the minimal runtime execution needed to run compiled plans.
|
|
27
28
|
|
|
28
|
-
## Usage
|
|
29
|
-
|
|
30
|
-
This package is typically consumed by the Zenith CLI and other ecosystem tools.
|
|
29
|
+
## Usage
|
|
31
30
|
|
|
32
31
|
```typescript
|
|
33
|
-
import {
|
|
34
|
-
|
|
32
|
+
import { signal, effect, onMount } from '@zenithbuild/core';
|
|
33
|
+
|
|
34
|
+
const count = signal(0);
|
|
35
|
+
onMount(() => console.log('Ready'));
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
## License
|
package/cli/commands/add.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @zenithbuild/cli - Add Command
|
|
3
3
|
*
|
|
4
4
|
* Registers a plugin in the project
|
|
5
5
|
*/
|
|
@@ -32,6 +32,6 @@ export async function add(pluginName: string, options: AddOptions = {}): Promise
|
|
|
32
32
|
if (success) {
|
|
33
33
|
logger.info(`Plugin "${pluginName}" has been registered.`)
|
|
34
34
|
logger.info('Note: You may need to install the package manually:')
|
|
35
|
-
logger.log(` bun add @
|
|
35
|
+
logger.log(` bun add @zenithbuild/plugin-${pluginName}`)
|
|
36
36
|
}
|
|
37
37
|
}
|
package/cli/commands/build.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @zenithbuild/cli - Build Command
|
|
3
3
|
*
|
|
4
4
|
* Builds the application for production using SSG.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import path from 'path'
|
|
8
7
|
import { requireProject } from '../utils/project'
|
|
9
8
|
import * as logger from '../utils/logger'
|
|
10
|
-
import { buildSSG } from '
|
|
9
|
+
import { buildSSG } from '@zenithbuild/compiler'
|
|
11
10
|
|
|
12
11
|
export interface BuildOptions {
|
|
13
12
|
outDir?: string
|
package/cli/commands/dev.ts
CHANGED
|
@@ -1,19 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenithbuild/cli - Dev Command
|
|
3
|
+
*
|
|
4
|
+
* Development server with HMR support.
|
|
5
|
+
*
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
* CLI HARDENING: BLIND ORCHESTRATOR PATTERN
|
|
8
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
9
|
+
*
|
|
10
|
+
* This file follows the CLI Hardening Plan:
|
|
11
|
+
* - NO plugin-specific branching (no `if (hasContentPlugin)`)
|
|
12
|
+
* - NO semantic helpers (no `getContentData()`)
|
|
13
|
+
* - NO plugin type imports or casts
|
|
14
|
+
* - ONLY opaque data forwarding via hooks
|
|
15
|
+
*
|
|
16
|
+
* The CLI dispatches lifecycle hooks and collects payloads.
|
|
17
|
+
* It never understands what the data means.
|
|
18
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
19
|
+
*/
|
|
20
|
+
|
|
1
21
|
import path from 'path'
|
|
2
22
|
import fs from 'fs'
|
|
3
23
|
import { serve, type ServerWebSocket } from 'bun'
|
|
4
24
|
import { requireProject } from '../utils/project'
|
|
5
25
|
import * as logger from '../utils/logger'
|
|
6
26
|
import * as brand from '../utils/branding'
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
import {
|
|
28
|
+
compileZenSource,
|
|
29
|
+
discoverLayouts,
|
|
30
|
+
processLayout,
|
|
31
|
+
generateBundleJS,
|
|
32
|
+
loadZenithConfig,
|
|
33
|
+
PluginRegistry,
|
|
34
|
+
createPluginContext,
|
|
35
|
+
getPluginDataByNamespace,
|
|
36
|
+
compileCssAsync,
|
|
37
|
+
resolveGlobalsCss,
|
|
38
|
+
createBridgeAPI,
|
|
39
|
+
runPluginHooks,
|
|
40
|
+
collectHookReturns,
|
|
41
|
+
buildRuntimeEnvelope,
|
|
42
|
+
clearHooks,
|
|
43
|
+
type HookContext
|
|
44
|
+
} from '@zenithbuild/compiler'
|
|
17
45
|
|
|
18
46
|
export interface DevOptions {
|
|
19
47
|
port?: number
|
|
@@ -30,92 +58,58 @@ interface CompiledPage {
|
|
|
30
58
|
const pageCache = new Map<string, CompiledPage>()
|
|
31
59
|
|
|
32
60
|
/**
|
|
33
|
-
* Bundle page script using
|
|
34
|
-
*
|
|
61
|
+
* Bundle page script using Rolldown to resolve npm imports at compile time.
|
|
62
|
+
* Only called when compiler emits a BundlePlan - bundler performs no inference.
|
|
35
63
|
*/
|
|
36
|
-
|
|
37
|
-
// If no import statements, return as-is
|
|
38
|
-
if (!script.includes('import ')) {
|
|
39
|
-
return script
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Write temp file in PROJECT directory so Bun can find node_modules
|
|
43
|
-
const tempDir = path.join(projectRoot, '.zenith-cache')
|
|
44
|
-
if (!fs.existsSync(tempDir)) {
|
|
45
|
-
fs.mkdirSync(tempDir, { recursive: true })
|
|
46
|
-
}
|
|
47
|
-
const tempFile = path.join(tempDir, `bundle-${Date.now()}.js`)
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
// Write script to temp file
|
|
51
|
-
fs.writeFileSync(tempFile, script, 'utf-8')
|
|
52
|
-
|
|
53
|
-
// Use Bun.build to bundle with npm resolution from project's node_modules
|
|
54
|
-
const result = await Bun.build({
|
|
55
|
-
entrypoints: [tempFile],
|
|
56
|
-
target: 'browser',
|
|
57
|
-
format: 'esm',
|
|
58
|
-
minify: false,
|
|
59
|
-
external: [], // Bundle everything
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
if (!result.success || !result.outputs[0]) {
|
|
63
|
-
console.error('[Zenith] Bundle errors:', result.logs)
|
|
64
|
-
return script // Fall back to original
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Get the bundled output
|
|
68
|
-
const bundledCode = await result.outputs[0].text()
|
|
69
|
-
return bundledCode
|
|
70
|
-
} catch (error: any) {
|
|
71
|
-
console.error('[Zenith] Failed to bundle page script:', error.message)
|
|
72
|
-
return script // Fall back to original
|
|
73
|
-
} finally {
|
|
74
|
-
// Clean up temp file
|
|
75
|
-
try {
|
|
76
|
-
fs.unlinkSync(tempFile)
|
|
77
|
-
} catch { }
|
|
78
|
-
}
|
|
79
|
-
}
|
|
64
|
+
import { bundlePageScript, type BundlePlan, generateRouteDefinition } from '@zenithbuild/compiler'
|
|
80
65
|
|
|
81
66
|
export async function dev(options: DevOptions = {}): Promise<void> {
|
|
82
67
|
const project = requireProject()
|
|
83
68
|
const port = options.port || parseInt(process.env.PORT || '3000', 10)
|
|
84
69
|
const pagesDir = project.pagesDir
|
|
85
70
|
const rootDir = project.root
|
|
86
|
-
const contentDir = path.join(rootDir, 'content')
|
|
87
71
|
|
|
88
72
|
// Load zenith.config.ts if present
|
|
89
73
|
const config = await loadZenithConfig(rootDir)
|
|
90
74
|
const registry = new PluginRegistry()
|
|
75
|
+
const bridgeAPI = createBridgeAPI()
|
|
76
|
+
|
|
77
|
+
// Clear any previously registered hooks (important for restarts)
|
|
78
|
+
clearHooks()
|
|
91
79
|
|
|
92
80
|
console.log('[Zenith] Config plugins:', config.plugins?.length ?? 0)
|
|
93
81
|
|
|
94
|
-
//
|
|
82
|
+
// ============================================
|
|
83
|
+
// Plugin Registration (Unconditional)
|
|
84
|
+
// ============================================
|
|
85
|
+
// CLI registers ALL plugins without checking which ones exist.
|
|
86
|
+
// Each plugin decides what hooks to register.
|
|
95
87
|
for (const plugin of config.plugins || []) {
|
|
96
88
|
console.log('[Zenith] Registering plugin:', plugin.name)
|
|
97
89
|
registry.register(plugin)
|
|
90
|
+
|
|
91
|
+
// Let plugin register its CLI hooks (if it wants to)
|
|
92
|
+
// CLI does NOT check what the plugin is - it just offers the API
|
|
93
|
+
if (plugin.registerCLI) {
|
|
94
|
+
plugin.registerCLI(bridgeAPI)
|
|
95
|
+
}
|
|
98
96
|
}
|
|
99
97
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// Initialize plugins
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}))
|
|
112
|
-
} else {
|
|
113
|
-
// Fallback to legacy content loading if no content plugin configured
|
|
114
|
-
console.log('[Zenith] Using legacy content loading from:', contentDir)
|
|
115
|
-
contentData = loadContent(contentDir)
|
|
98
|
+
// ============================================
|
|
99
|
+
// Plugin Initialization (Unconditional)
|
|
100
|
+
// ============================================
|
|
101
|
+
// Initialize ALL plugins unconditionally.
|
|
102
|
+
// If no plugins, this is a no-op. CLI doesn't branch on plugin presence.
|
|
103
|
+
await registry.initAll(createPluginContext(rootDir))
|
|
104
|
+
|
|
105
|
+
// Create hook context - CLI provides this but NEVER uses getPluginData itself
|
|
106
|
+
const hookCtx: HookContext = {
|
|
107
|
+
projectRoot: rootDir,
|
|
108
|
+
getPluginData: getPluginDataByNamespace
|
|
116
109
|
}
|
|
117
110
|
|
|
118
|
-
|
|
111
|
+
// Dispatch lifecycle hook - plugins decide if they care
|
|
112
|
+
await runPluginHooks('cli:dev:start', hookCtx)
|
|
119
113
|
|
|
120
114
|
// ============================================
|
|
121
115
|
// CSS Compilation (Compiler-Owned)
|
|
@@ -173,8 +167,69 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
173
167
|
|
|
174
168
|
const routeDef = generateRouteDefinition(pagePath, pagesDir)
|
|
175
169
|
|
|
176
|
-
|
|
177
|
-
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// Safely strip imports from the top of the script
|
|
173
|
+
// This relies on the fact that duplicate imports (from Rust codegen)
|
|
174
|
+
// appear at the beginning of result.finalized.js
|
|
175
|
+
let jsLines = result.finalized.js.split('\n')
|
|
176
|
+
|
|
177
|
+
// Remove lines from top that are imports, whitespace, or comments
|
|
178
|
+
while (jsLines.length > 0 && jsLines[0] !== undefined) {
|
|
179
|
+
const line = jsLines[0].trim()
|
|
180
|
+
if (
|
|
181
|
+
line.startsWith('import ') ||
|
|
182
|
+
line === '' ||
|
|
183
|
+
line.startsWith('//') ||
|
|
184
|
+
line.startsWith('/*') ||
|
|
185
|
+
line.startsWith('*')
|
|
186
|
+
) {
|
|
187
|
+
jsLines.shift()
|
|
188
|
+
} else {
|
|
189
|
+
break
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let jsWithoutImports = jsLines.join('\n')
|
|
194
|
+
|
|
195
|
+
// PATCH: Fix unquoted keys with dashes (Rust codegen bug in jsx_lowerer)
|
|
196
|
+
// e.g. stroke-width: "1.5" -> "stroke-width": "1.5"
|
|
197
|
+
// We only apply this to the JS portions (Script and Expressions)
|
|
198
|
+
// to avoid corrupting the Styles section.
|
|
199
|
+
const stylesMarker = '// 6. Styles injection'
|
|
200
|
+
const parts = jsWithoutImports.split(stylesMarker)
|
|
201
|
+
|
|
202
|
+
if (parts.length > 1) {
|
|
203
|
+
// Apply patch only to the JS part
|
|
204
|
+
parts[0] = parts[0]!.replace(
|
|
205
|
+
/(^|[{,])\s*([a-zA-Z][a-zA-Z0-9-]*-[a-zA-Z0-9-]*)\s*:/gm,
|
|
206
|
+
'$1"$2":'
|
|
207
|
+
)
|
|
208
|
+
jsWithoutImports = parts.join(stylesMarker)
|
|
209
|
+
} else {
|
|
210
|
+
// Fallback if marker not found
|
|
211
|
+
jsWithoutImports = jsWithoutImports.replace(
|
|
212
|
+
/(^|[{,])\s*([a-zA-Z][a-zA-Z0-9-]*-[a-zA-Z0-9-]*)\s*:/gm,
|
|
213
|
+
'$1"$2":'
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Combine: structured imports first, then cleaned script body
|
|
218
|
+
const fullScript = (result.finalized.npmImports || '') + '\n\n' + jsWithoutImports
|
|
219
|
+
|
|
220
|
+
console.log('[Dev] Page Imports:', result.finalized.npmImports ? result.finalized.npmImports.split('\n').length : 0, 'lines')
|
|
221
|
+
|
|
222
|
+
// Bundle ONLY if compiler emitted a BundlePlan (no inference)
|
|
223
|
+
let bundledScript = fullScript
|
|
224
|
+
if (result.finalized.bundlePlan) {
|
|
225
|
+
// Compiler decided bundling is needed - pass plan with proper resolve roots
|
|
226
|
+
const plan: BundlePlan = {
|
|
227
|
+
...result.finalized.bundlePlan,
|
|
228
|
+
entry: fullScript,
|
|
229
|
+
resolveRoots: [path.join(rootDir, 'node_modules'), 'node_modules']
|
|
230
|
+
}
|
|
231
|
+
bundledScript = await bundlePageScript(plan)
|
|
232
|
+
}
|
|
178
233
|
|
|
179
234
|
return {
|
|
180
235
|
html: result.finalized.html,
|
|
@@ -189,10 +244,46 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
189
244
|
}
|
|
190
245
|
}
|
|
191
246
|
|
|
192
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Generate dev HTML with plugin data envelope
|
|
249
|
+
*
|
|
250
|
+
* CLI collects payloads from plugins via 'cli:runtime:collect' hook.
|
|
251
|
+
* It serializes blindly - never inspecting what's inside.
|
|
252
|
+
*/
|
|
253
|
+
async function generateDevHTML(page: CompiledPage): Promise<string> {
|
|
254
|
+
// Single neutral injection point
|
|
255
|
+
const runtimeTag = `<script src="/runtime.js"></script>`
|
|
256
|
+
const scriptTag = `<script type="module">\n${page.script}\n</script>`
|
|
257
|
+
const allScripts = `${runtimeTag}\n${scriptTag}`
|
|
258
|
+
|
|
259
|
+
let html = page.html.includes('</body>')
|
|
260
|
+
? page.html.replace('</body>', `${allScripts}\n</body>`)
|
|
261
|
+
: `${page.html}\n${allScripts}`
|
|
262
|
+
|
|
263
|
+
// Ensure DOCTYPE is present to prevent Quirks Mode (critical for SVG namespace)
|
|
264
|
+
if (!html.trimStart().toLowerCase().startsWith('<!doctype')) {
|
|
265
|
+
html = `<!DOCTYPE html>\n${html}`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return html
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ============================================
|
|
272
|
+
// File Watcher (Plugin-Agnostic)
|
|
273
|
+
// ============================================
|
|
274
|
+
// CLI watches files but delegates decisions to plugins via hooks.
|
|
275
|
+
// No branching on file types that are "content" vs "not content".
|
|
193
276
|
const watcher = fs.watch(path.join(pagesDir, '..'), { recursive: true }, async (event, filename) => {
|
|
194
277
|
if (!filename) return
|
|
195
278
|
|
|
279
|
+
// Dispatch file change hook to ALL plugins
|
|
280
|
+
// Each plugin decides if it cares about this file
|
|
281
|
+
await runPluginHooks('cli:dev:file-change', {
|
|
282
|
+
...hookCtx,
|
|
283
|
+
filename,
|
|
284
|
+
event
|
|
285
|
+
})
|
|
286
|
+
|
|
196
287
|
if (filename.endsWith('.zen')) {
|
|
197
288
|
logger.hmr('Page', filename)
|
|
198
289
|
|
|
@@ -223,16 +314,13 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
223
314
|
for (const client of clients) {
|
|
224
315
|
client.send(JSON.stringify({ type: 'style-update', url: '/assets/styles.css' }))
|
|
225
316
|
}
|
|
226
|
-
} else
|
|
227
|
-
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
} else {
|
|
234
|
-
contentData = loadContent(contentDir)
|
|
235
|
-
}
|
|
317
|
+
} else {
|
|
318
|
+
// For all other file changes, re-initialize plugins unconditionally
|
|
319
|
+
// Plugins decide internally whether they need to reload data
|
|
320
|
+
// CLI does NOT branch on "is this a content file"
|
|
321
|
+
await registry.initAll(createPluginContext(rootDir))
|
|
322
|
+
|
|
323
|
+
// Broadcast reload for any non-code file changes
|
|
236
324
|
for (const client of clients) {
|
|
237
325
|
client.send(JSON.stringify({ type: 'reload' }))
|
|
238
326
|
}
|
|
@@ -255,7 +343,11 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
255
343
|
|
|
256
344
|
// Handle Zenith assets
|
|
257
345
|
if (pathname === '/runtime.js') {
|
|
258
|
-
|
|
346
|
+
// Collect runtime payloads from ALL plugins
|
|
347
|
+
const payloads = await collectHookReturns('cli:runtime:collect', hookCtx)
|
|
348
|
+
const envelope = buildRuntimeEnvelope(payloads)
|
|
349
|
+
|
|
350
|
+
const response = new Response(generateBundleJS(envelope), {
|
|
259
351
|
headers: { 'Content-Type': 'application/javascript; charset=utf-8' }
|
|
260
352
|
})
|
|
261
353
|
logger.route('GET', pathname, 200, Math.round(performance.now() - startTime), 0, Math.round(performance.now() - startTime))
|
|
@@ -305,7 +397,7 @@ export async function dev(options: DevOptions = {}): Promise<void> {
|
|
|
305
397
|
|
|
306
398
|
if (cached) {
|
|
307
399
|
const renderStart = performance.now()
|
|
308
|
-
const html = generateDevHTML(cached
|
|
400
|
+
const html = await generateDevHTML(cached)
|
|
309
401
|
const renderEnd = performance.now()
|
|
310
402
|
|
|
311
403
|
const totalTime = Math.round(performance.now() - startTime)
|
|
@@ -373,16 +465,3 @@ function findPageForRoute(route: string, pagesDir: string): string | null {
|
|
|
373
465
|
|
|
374
466
|
return null
|
|
375
467
|
}
|
|
376
|
-
|
|
377
|
-
function generateDevHTML(page: CompiledPage, contentData: any = {}): string {
|
|
378
|
-
const runtimeTag = `<script src="/runtime.js"></script>`
|
|
379
|
-
// Escape </script> sequences in JSON content to prevent breaking the script tag
|
|
380
|
-
const contentJson = JSON.stringify(contentData).replace(/<\//g, '<\\/')
|
|
381
|
-
const contentTag = `<script>window.__ZENITH_CONTENT__ = ${contentJson};</script>`
|
|
382
|
-
// Use type="module" to support ES6 imports from npm packages
|
|
383
|
-
const scriptTag = `<script type="module">\n${page.script}\n</script>`
|
|
384
|
-
const allScripts = `${runtimeTag}\n${contentTag}\n${scriptTag}`
|
|
385
|
-
return page.html.includes('</body>')
|
|
386
|
-
? page.html.replace('</body>', `${allScripts}\n</body>`)
|
|
387
|
-
: `${page.html}\n${allScripts}`
|
|
388
|
-
}
|
package/cli/commands/index.ts
CHANGED
package/cli/commands/preview.ts
CHANGED
package/cli/commands/remove.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @zenithbuild/cli - Remove Command
|
|
3
3
|
*
|
|
4
4
|
* Removes a plugin from the project registry
|
|
5
5
|
*/
|
|
@@ -28,6 +28,6 @@ export async function remove(pluginName: string): Promise<void> {
|
|
|
28
28
|
if (success) {
|
|
29
29
|
logger.info(`Plugin "${pluginName}" has been unregistered.`)
|
|
30
30
|
logger.info('Note: You may want to remove the package manually:')
|
|
31
|
-
logger.log(` bun remove @
|
|
31
|
+
logger.log(` bun remove @zenithbuild/plugin-${pluginName}`)
|
|
32
32
|
}
|
|
33
33
|
}
|
package/cli/index.ts
CHANGED
package/cli/main.ts
CHANGED
package/cli/utils/logger.ts
CHANGED
package/cli/utils/project.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @zenithbuild/cli - Project Utility
|
|
3
3
|
*
|
|
4
4
|
* Detects Zenith project root and configuration
|
|
5
5
|
*/
|
|
@@ -28,8 +28,8 @@ export function findProjectRoot(startDir: string = process.cwd()): string | null
|
|
|
28
28
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
29
29
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
30
30
|
|
|
31
|
-
// Check for any @
|
|
32
|
-
const hasZenith = Object.keys(deps).some(d => d.startsWith('@
|
|
31
|
+
// Check for any @zenithbuild/* or @zenithbuild/* dependency
|
|
32
|
+
const hasZenith = Object.keys(deps).some(d => d.startsWith('@zenithbuild/') || d.startsWith('@zenithbuild/'))
|
|
33
33
|
if (hasZenith) {
|
|
34
34
|
return current
|
|
35
35
|
}
|
|
@@ -71,7 +71,7 @@ export function getProject(cwd: string = process.cwd()): ZenithProject | null {
|
|
|
71
71
|
export function requireProject(cwd: string = process.cwd()): ZenithProject {
|
|
72
72
|
const project = getProject(cwd)
|
|
73
73
|
if (!project) {
|
|
74
|
-
throw new Error('Not in a Zenith project. Run this command from a directory with @
|
|
74
|
+
throw new Error('Not in a Zenith project. Run this command from a directory with @zenithbuild/* dependencies.')
|
|
75
75
|
}
|
|
76
76
|
return project
|
|
77
77
|
}
|