@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.
- package/README.md +20 -19
- package/cli/commands/add.ts +2 -2
- package/cli/commands/build.ts +2 -3
- package/cli/commands/dev.ts +94 -74
- 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
|
@@ -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,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Config
|
|
3
|
-
*
|
|
4
|
-
* Public exports for zenith/config
|
|
5
|
-
*
|
|
6
|
-
* Core exports ONLY generic plugin infrastructure.
|
|
7
|
-
* Plugin-specific types are owned by their respective plugins.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export { defineConfig } from './types';
|
|
11
|
-
export type {
|
|
12
|
-
ZenithConfig,
|
|
13
|
-
ZenithPlugin,
|
|
14
|
-
PluginContext,
|
|
15
|
-
PluginData
|
|
16
|
-
} from './types';
|
|
17
|
-
export { loadZenithConfig, hasZenithConfig } from './loader';
|
|
18
|
-
|
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,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Config Types
|
|
3
|
-
*
|
|
4
|
-
* Configuration interfaces for zenith.config.ts
|
|
5
|
-
*
|
|
6
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
-
* HOOK OWNERSHIP RULE (CANONICAL)
|
|
8
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
9
|
-
*
|
|
10
|
-
* Core may ONLY define types that are universally valid in all Zenith applications.
|
|
11
|
-
* Plugin-specific types MUST be owned by their respective plugins.
|
|
12
|
-
*
|
|
13
|
-
* ✅ ALLOWED in Core:
|
|
14
|
-
* - ZenithConfig, ZenithPlugin, PluginContext (generic plugin infrastructure)
|
|
15
|
-
* - Universal lifecycle hooks (onMount, onUnmount)
|
|
16
|
-
* - Reactivity primitives (signal, effect, etc.)
|
|
17
|
-
*
|
|
18
|
-
* ❌ PROHIBITED in Core:
|
|
19
|
-
* - Content plugin types (ContentItem, ContentSourceConfig, etc.)
|
|
20
|
-
* - Router plugin types (RouteState, NavigationGuard, etc.)
|
|
21
|
-
* - Documentation plugin types
|
|
22
|
-
* - Any type that exists only because a plugin exists
|
|
23
|
-
*
|
|
24
|
-
* If removing a plugin would make a type meaningless, that type belongs to the plugin.
|
|
25
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import type { CLIBridgeAPI } from '../plugins/bridge';
|
|
29
|
-
|
|
30
|
-
// ============================================
|
|
31
|
-
// Core Plugin Types (Generic Infrastructure)
|
|
32
|
-
// ============================================
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Generic data record for plugin data exchange
|
|
36
|
-
* Plugins define their own specific types internally
|
|
37
|
-
*/
|
|
38
|
-
export type PluginData = Record<string, unknown[]>;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Context passed to plugins during setup
|
|
42
|
-
*
|
|
43
|
-
* This is intentionally generic - plugins define their own data shapes.
|
|
44
|
-
* Core provides the stage, plugins bring the actors.
|
|
45
|
-
*/
|
|
46
|
-
export interface PluginContext {
|
|
47
|
-
/** Absolute path to project root */
|
|
48
|
-
projectRoot: string;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Set plugin data for the runtime
|
|
52
|
-
*
|
|
53
|
-
* Generic setter - plugins define their own data structures.
|
|
54
|
-
* The runtime stores this data and makes it available to components.
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* // Content plugin uses it for content items
|
|
58
|
-
* ctx.setPluginData('content', contentItems);
|
|
59
|
-
*
|
|
60
|
-
* // Analytics plugin uses it for tracking config
|
|
61
|
-
* ctx.setPluginData('analytics', analyticsConfig);
|
|
62
|
-
*/
|
|
63
|
-
setPluginData: (namespace: string, data: unknown[]) => void;
|
|
64
|
-
|
|
65
|
-
/** Additional options passed from config */
|
|
66
|
-
options?: Record<string, unknown>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* A Zenith plugin definition
|
|
71
|
-
*
|
|
72
|
-
* Plugins are self-contained, removable extensions.
|
|
73
|
-
* Core must build and run identically with or without any plugin installed.
|
|
74
|
-
*/
|
|
75
|
-
export interface ZenithPlugin {
|
|
76
|
-
/** Unique plugin name */
|
|
77
|
-
name: string;
|
|
78
|
-
|
|
79
|
-
/** Setup function called during initialization */
|
|
80
|
-
setup: (ctx: PluginContext) => void | Promise<void>;
|
|
81
|
-
|
|
82
|
-
/** Plugin-specific configuration (preserved for reference) */
|
|
83
|
-
config?: unknown;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Optional CLI registration
|
|
87
|
-
*
|
|
88
|
-
* Plugin receives the CLI bridge API to register namespaced hooks.
|
|
89
|
-
* CLI lifecycle hooks: 'cli:*' (owned by CLI)
|
|
90
|
-
* Plugin hooks: '<namespace>:*' (owned by plugin)
|
|
91
|
-
*
|
|
92
|
-
* @example
|
|
93
|
-
* registerCLI(api) {
|
|
94
|
-
* api.on('cli:runtime:collect', (ctx) => {
|
|
95
|
-
* return { namespace: 'myPlugin', payload: ctx.getPluginData('myPlugin') }
|
|
96
|
-
* })
|
|
97
|
-
* }
|
|
98
|
-
*/
|
|
99
|
-
registerCLI?: (api: CLIBridgeAPI) => void;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ============================================
|
|
103
|
-
// Main Config Types
|
|
104
|
-
// ============================================
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Zenith configuration object
|
|
108
|
-
*/
|
|
109
|
-
export interface ZenithConfig {
|
|
110
|
-
/** List of plugins to load */
|
|
111
|
-
plugins?: ZenithPlugin[];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Define a Zenith configuration with full type safety
|
|
116
|
-
*/
|
|
117
|
-
export function defineConfig(config: ZenithConfig): ZenithConfig {
|
|
118
|
-
return config;
|
|
119
|
-
}
|
package/core/plugins/bridge.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith CLI Bridge
|
|
3
|
-
*
|
|
4
|
-
* The ONLY interface between CLI and plugins.
|
|
5
|
-
*
|
|
6
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
-
* CLI BRIDGE RULES (CANONICAL)
|
|
8
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
9
|
-
*
|
|
10
|
-
* 1. No runtime emitters - plugins return data, CLI serializes blindly
|
|
11
|
-
* 2. No plugin typing - all data is unknown
|
|
12
|
-
* 3. No semantic helpers - CLI is blind to what data means
|
|
13
|
-
*
|
|
14
|
-
* The CLI dispatches hooks and collects returns. It never inspects payloads.
|
|
15
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* CLI Bridge API - passed to plugins during CLI registration
|
|
20
|
-
*
|
|
21
|
-
* Plugins use this to register namespaced hooks.
|
|
22
|
-
* CLI lifecycle hooks: 'cli:*'
|
|
23
|
-
* Plugin hooks: '<namespace>:*'
|
|
24
|
-
*/
|
|
25
|
-
export interface CLIBridgeAPI {
|
|
26
|
-
/**
|
|
27
|
-
* Register a hook handler
|
|
28
|
-
*
|
|
29
|
-
* @param hook - Namespaced hook name (e.g., 'cli:runtime:collect', 'content:dev:watch')
|
|
30
|
-
* @param handler - Handler function that receives context and optionally returns data
|
|
31
|
-
*/
|
|
32
|
-
on(hook: string, handler: (ctx: HookContext) => unknown | void | Promise<unknown | void>): void
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Context passed to hook handlers
|
|
37
|
-
*
|
|
38
|
-
* CLI provides this but never uses getPluginData itself.
|
|
39
|
-
* Only plugins call getPluginData with their own namespace.
|
|
40
|
-
*/
|
|
41
|
-
export interface HookContext {
|
|
42
|
-
/** Absolute path to project root */
|
|
43
|
-
projectRoot: string
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Opaque data accessor
|
|
47
|
-
*
|
|
48
|
-
* CLI passes this function but NEVER calls it.
|
|
49
|
-
* Only plugins use it to access their own namespaced data.
|
|
50
|
-
*/
|
|
51
|
-
getPluginData: (namespace: string) => unknown
|
|
52
|
-
|
|
53
|
-
/** Additional context data (e.g., filename for file-change hooks) */
|
|
54
|
-
[key: string]: unknown
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Runtime payload returned by plugins
|
|
59
|
-
*
|
|
60
|
-
* CLI collects these and serializes without inspection.
|
|
61
|
-
* The envelope structure is: { [namespace]: payload }
|
|
62
|
-
*/
|
|
63
|
-
export interface RuntimePayload {
|
|
64
|
-
/** Plugin namespace (e.g., 'content', 'router') */
|
|
65
|
-
namespace: string
|
|
66
|
-
/** Opaque payload - CLI never inspects this */
|
|
67
|
-
payload: unknown
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ============================================
|
|
71
|
-
// Hook Registry (Internal)
|
|
72
|
-
// ============================================
|
|
73
|
-
|
|
74
|
-
type HookHandler = (ctx: HookContext) => unknown | void | Promise<unknown | void>
|
|
75
|
-
|
|
76
|
-
const hookRegistry = new Map<string, HookHandler[]>()
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Register a hook handler
|
|
80
|
-
*
|
|
81
|
-
* @internal Called by CLIBridgeAPI.on()
|
|
82
|
-
*/
|
|
83
|
-
export function registerHook(hook: string, handler: HookHandler): void {
|
|
84
|
-
if (!hookRegistry.has(hook)) {
|
|
85
|
-
hookRegistry.set(hook, [])
|
|
86
|
-
}
|
|
87
|
-
hookRegistry.get(hook)!.push(handler)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Clear all registered hooks
|
|
92
|
-
*
|
|
93
|
-
* @internal Used for testing and cleanup
|
|
94
|
-
*/
|
|
95
|
-
export function clearHooks(): void {
|
|
96
|
-
hookRegistry.clear()
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ============================================
|
|
100
|
-
// Hook Execution (CLI-facing)
|
|
101
|
-
// ============================================
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Run all handlers for a hook (fire-and-forget)
|
|
105
|
-
*
|
|
106
|
-
* CLI calls this for lifecycle events.
|
|
107
|
-
* No return values are collected.
|
|
108
|
-
*
|
|
109
|
-
* @param hook - Hook name to dispatch
|
|
110
|
-
* @param ctx - Hook context
|
|
111
|
-
*/
|
|
112
|
-
export async function runPluginHooks(hook: string, ctx: HookContext): Promise<void> {
|
|
113
|
-
const handlers = hookRegistry.get(hook) || []
|
|
114
|
-
for (const handler of handlers) {
|
|
115
|
-
try {
|
|
116
|
-
await handler(ctx)
|
|
117
|
-
} catch (error) {
|
|
118
|
-
console.error(`[Zenith] Hook "${hook}" error:`, error)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Collect return values from all handlers for a hook
|
|
125
|
-
*
|
|
126
|
-
* CLI calls this for 'cli:runtime:collect' to gather plugin payloads.
|
|
127
|
-
* Only RuntimePayload-shaped returns are collected.
|
|
128
|
-
*
|
|
129
|
-
* @param hook - Hook name to dispatch
|
|
130
|
-
* @param ctx - Hook context
|
|
131
|
-
* @returns Array of runtime payloads from plugins
|
|
132
|
-
*/
|
|
133
|
-
export async function collectHookReturns(hook: string, ctx: HookContext): Promise<RuntimePayload[]> {
|
|
134
|
-
const handlers = hookRegistry.get(hook) || []
|
|
135
|
-
const results: RuntimePayload[] = []
|
|
136
|
-
|
|
137
|
-
for (const handler of handlers) {
|
|
138
|
-
try {
|
|
139
|
-
const result = await handler(ctx)
|
|
140
|
-
|
|
141
|
-
// Only collect properly shaped payloads
|
|
142
|
-
if (
|
|
143
|
-
result &&
|
|
144
|
-
typeof result === 'object' &&
|
|
145
|
-
'namespace' in result &&
|
|
146
|
-
'payload' in result &&
|
|
147
|
-
typeof (result as RuntimePayload).namespace === 'string'
|
|
148
|
-
) {
|
|
149
|
-
results.push(result as RuntimePayload)
|
|
150
|
-
}
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error(`[Zenith] Hook "${hook}" collection error:`, error)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return results
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Build runtime envelope from collected payloads
|
|
161
|
-
*
|
|
162
|
-
* CLI calls this to serialize plugin data for injection.
|
|
163
|
-
* CLI never inspects the envelope contents.
|
|
164
|
-
*
|
|
165
|
-
* @param payloads - Array of runtime payloads from collectHookReturns
|
|
166
|
-
* @returns Envelope object: { [namespace]: payload }
|
|
167
|
-
*/
|
|
168
|
-
export function buildRuntimeEnvelope(payloads: RuntimePayload[]): Record<string, unknown> {
|
|
169
|
-
const envelope: Record<string, unknown> = {}
|
|
170
|
-
|
|
171
|
-
for (const { namespace, payload } of payloads) {
|
|
172
|
-
envelope[namespace] = payload
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return envelope
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ============================================
|
|
179
|
-
// Bridge API Factory
|
|
180
|
-
// ============================================
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Create a CLI Bridge API for plugin registration
|
|
184
|
-
*
|
|
185
|
-
* CLI calls this once and passes to each plugin's registerCLI method.
|
|
186
|
-
*
|
|
187
|
-
* @returns CLIBridgeAPI instance
|
|
188
|
-
*/
|
|
189
|
-
export function createBridgeAPI(): CLIBridgeAPI {
|
|
190
|
-
return {
|
|
191
|
-
on: registerHook
|
|
192
|
-
}
|
|
193
|
-
}
|
package/core/plugins/index.ts
DELETED
package/core/plugins/registry.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenith Plugin Registry
|
|
3
|
-
*
|
|
4
|
-
* Manages plugin registration and initialization
|
|
5
|
-
*
|
|
6
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
-
* HOOK OWNERSHIP RULE (CANONICAL)
|
|
8
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
9
|
-
*
|
|
10
|
-
* The plugin registry is part of core infrastructure.
|
|
11
|
-
* It MUST remain plugin-agnostic:
|
|
12
|
-
* - No plugin-specific types
|
|
13
|
-
* - No plugin-specific logic
|
|
14
|
-
* - Generic data handling only
|
|
15
|
-
*
|
|
16
|
-
* Plugins own their data structures; core provides the storage mechanism.
|
|
17
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import type { ZenithPlugin, PluginContext } from '../config/types';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Global plugin data store
|
|
24
|
-
*
|
|
25
|
-
* Plugins store their data here using namespaced keys.
|
|
26
|
-
* Core does not interpret this data - it just stores and serves it.
|
|
27
|
-
*/
|
|
28
|
-
const pluginDataStore: Record<string, unknown[]> = {};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Get all plugin data (for runtime access)
|
|
32
|
-
*/
|
|
33
|
-
export function getPluginData(): Record<string, unknown[]> {
|
|
34
|
-
return { ...pluginDataStore };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get plugin data by namespace
|
|
39
|
-
*/
|
|
40
|
-
export function getPluginDataByNamespace(namespace: string): unknown[] {
|
|
41
|
-
return pluginDataStore[namespace] || [];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Plugin registry for managing Zenith plugins
|
|
46
|
-
*/
|
|
47
|
-
export class PluginRegistry {
|
|
48
|
-
private plugins = new Map<string, ZenithPlugin>();
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Register a plugin
|
|
52
|
-
*/
|
|
53
|
-
register(plugin: ZenithPlugin): void {
|
|
54
|
-
if (this.plugins.has(plugin.name)) {
|
|
55
|
-
console.warn(`[Zenith] Plugin "${plugin.name}" is already registered. Overwriting.`);
|
|
56
|
-
}
|
|
57
|
-
this.plugins.set(plugin.name, plugin);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get a plugin by name
|
|
62
|
-
*/
|
|
63
|
-
get(name: string): ZenithPlugin | undefined {
|
|
64
|
-
return this.plugins.get(name);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Check if a plugin is registered
|
|
69
|
-
*/
|
|
70
|
-
has(name: string): boolean {
|
|
71
|
-
return this.plugins.has(name);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get all registered plugins
|
|
76
|
-
*/
|
|
77
|
-
all(): ZenithPlugin[] {
|
|
78
|
-
return Array.from(this.plugins.values());
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Initialize all plugins with the provided context
|
|
83
|
-
*/
|
|
84
|
-
async initAll(ctx: PluginContext): Promise<void> {
|
|
85
|
-
for (const plugin of this.plugins.values()) {
|
|
86
|
-
try {
|
|
87
|
-
await plugin.setup(ctx);
|
|
88
|
-
console.log(`[Zenith] Plugin "${plugin.name}" initialized`);
|
|
89
|
-
} catch (error: unknown) {
|
|
90
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
-
console.error(`[Zenith] Failed to initialize plugin "${plugin.name}":`, message);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Clear all registered plugins
|
|
98
|
-
*/
|
|
99
|
-
clear(): void {
|
|
100
|
-
this.plugins.clear();
|
|
101
|
-
// Also clear plugin data
|
|
102
|
-
for (const key of Object.keys(pluginDataStore)) {
|
|
103
|
-
delete pluginDataStore[key];
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Create a plugin context for initialization
|
|
110
|
-
*
|
|
111
|
-
* Uses a generic data setter that stores data by namespace.
|
|
112
|
-
* Plugins define their own data structures internally.
|
|
113
|
-
*
|
|
114
|
-
* @param projectRoot - Absolute path to the project root
|
|
115
|
-
* @returns A PluginContext for plugin initialization
|
|
116
|
-
*/
|
|
117
|
-
export function createPluginContext(projectRoot: string): PluginContext {
|
|
118
|
-
return {
|
|
119
|
-
projectRoot,
|
|
120
|
-
setPluginData: (namespace: string, data: unknown[]) => {
|
|
121
|
-
pluginDataStore[namespace] = data;
|
|
122
|
-
},
|
|
123
|
-
options: {}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|