@zenithbuild/core 0.6.3 → 1.2.0

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.
@@ -0,0 +1,193 @@
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
+ }
@@ -2,9 +2,44 @@
2
2
  * Zenith Plugin Registry
3
3
  *
4
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
+ * ═══════════════════════════════════════════════════════════════════════════════
5
18
  */
6
19
 
7
- import type { ZenithPlugin, PluginContext, ContentItem } from '../config/types';
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
+ }
8
43
 
9
44
  /**
10
45
  * Plugin registry for managing Zenith plugins
@@ -63,19 +98,29 @@ export class PluginRegistry {
63
98
  */
64
99
  clear(): void {
65
100
  this.plugins.clear();
101
+ // Also clear plugin data
102
+ for (const key of Object.keys(pluginDataStore)) {
103
+ delete pluginDataStore[key];
104
+ }
66
105
  }
67
106
  }
68
107
 
69
108
  /**
70
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
71
116
  */
72
- export function createPluginContext(
73
- projectRoot: string,
74
- contentSetter: (data: Record<string, ContentItem[]>) => void
75
- ): PluginContext {
117
+ export function createPluginContext(projectRoot: string): PluginContext {
76
118
  return {
77
119
  projectRoot,
78
- setContentData: contentSetter,
120
+ setPluginData: (namespace: string, data: unknown[]) => {
121
+ pluginDataStore[namespace] = data;
122
+ },
79
123
  options: {}
80
124
  };
81
125
  }
126
+
package/dist/cli.js CHANGED
@@ -21,6 +21,14 @@
21
21
  #!/usr/bin/env bun
22
22
  #!/usr/bin/env bun
23
23
  #!/usr/bin/env bun
24
+ #!/usr/bin/env bun
25
+ #!/usr/bin/env bun
26
+ #!/usr/bin/env bun
27
+ #!/usr/bin/env bun
28
+ #!/usr/bin/env bun
29
+ #!/usr/bin/env bun
30
+ #!/usr/bin/env bun
31
+ #!/usr/bin/env bun
24
32
  // @bun
25
33
  var __create = Object.create;
26
34
  var __getProtoOf = Object.getPrototypeOf;