create-fluxstack 1.0.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.
- package/.env +30 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/app/client/README.md +69 -0
- package/app/client/frontend-only.ts +12 -0
- package/app/client/index.html +13 -0
- package/app/client/public/vite.svg +1 -0
- package/app/client/src/App.css +883 -0
- package/app/client/src/App.tsx +669 -0
- package/app/client/src/assets/react.svg +1 -0
- package/app/client/src/components/TestPage.tsx +453 -0
- package/app/client/src/index.css +51 -0
- package/app/client/src/lib/eden-api.ts +110 -0
- package/app/client/src/main.tsx +10 -0
- package/app/client/src/vite-env.d.ts +1 -0
- package/app/client/tsconfig.app.json +43 -0
- package/app/client/tsconfig.json +7 -0
- package/app/client/tsconfig.node.json +25 -0
- package/app/server/app.ts +10 -0
- package/app/server/backend-only.ts +15 -0
- package/app/server/controllers/users.controller.ts +69 -0
- package/app/server/index.ts +104 -0
- package/app/server/routes/index.ts +25 -0
- package/app/server/routes/users.routes.ts +121 -0
- package/app/server/types/index.ts +1 -0
- package/app/shared/types/index.ts +18 -0
- package/bun.lock +1053 -0
- package/core/__tests__/integration.test.ts +227 -0
- package/core/build/index.ts +186 -0
- package/core/cli/command-registry.ts +334 -0
- package/core/cli/index.ts +394 -0
- package/core/cli/plugin-discovery.ts +200 -0
- package/core/client/standalone.ts +57 -0
- package/core/config/__tests__/config-loader.test.ts +591 -0
- package/core/config/__tests__/config-merger.test.ts +657 -0
- package/core/config/__tests__/env-converter.test.ts +372 -0
- package/core/config/__tests__/env-processor.test.ts +431 -0
- package/core/config/__tests__/env.test.ts +452 -0
- package/core/config/__tests__/integration.test.ts +418 -0
- package/core/config/__tests__/loader.test.ts +331 -0
- package/core/config/__tests__/schema.test.ts +129 -0
- package/core/config/__tests__/validator.test.ts +318 -0
- package/core/config/env-dynamic.ts +326 -0
- package/core/config/env.ts +597 -0
- package/core/config/index.ts +317 -0
- package/core/config/loader.ts +546 -0
- package/core/config/runtime-config.ts +322 -0
- package/core/config/schema.ts +694 -0
- package/core/config/validator.ts +540 -0
- package/core/framework/__tests__/server.test.ts +233 -0
- package/core/framework/client.ts +132 -0
- package/core/framework/index.ts +8 -0
- package/core/framework/server.ts +501 -0
- package/core/framework/types.ts +63 -0
- package/core/plugins/__tests__/built-in.test.ts.disabled +366 -0
- package/core/plugins/__tests__/manager.test.ts +398 -0
- package/core/plugins/__tests__/monitoring.test.ts +401 -0
- package/core/plugins/__tests__/registry.test.ts +335 -0
- package/core/plugins/built-in/index.ts +142 -0
- package/core/plugins/built-in/logger/index.ts +180 -0
- package/core/plugins/built-in/monitoring/README.md +193 -0
- package/core/plugins/built-in/monitoring/index.ts +912 -0
- package/core/plugins/built-in/static/index.ts +289 -0
- package/core/plugins/built-in/swagger/index.ts +229 -0
- package/core/plugins/built-in/vite/index.ts +316 -0
- package/core/plugins/config.ts +348 -0
- package/core/plugins/discovery.ts +350 -0
- package/core/plugins/executor.ts +351 -0
- package/core/plugins/index.ts +195 -0
- package/core/plugins/manager.ts +583 -0
- package/core/plugins/registry.ts +424 -0
- package/core/plugins/types.ts +254 -0
- package/core/server/framework.ts +123 -0
- package/core/server/index.ts +8 -0
- package/core/server/plugins/database.ts +182 -0
- package/core/server/plugins/logger.ts +47 -0
- package/core/server/plugins/swagger.ts +34 -0
- package/core/server/standalone.ts +91 -0
- package/core/templates/create-project.ts +455 -0
- package/core/types/api.ts +169 -0
- package/core/types/build.ts +174 -0
- package/core/types/config.ts +68 -0
- package/core/types/index.ts +127 -0
- package/core/types/plugin.ts +94 -0
- package/core/utils/__tests__/errors.test.ts +139 -0
- package/core/utils/__tests__/helpers.test.ts +297 -0
- package/core/utils/__tests__/logger.test.ts +141 -0
- package/core/utils/env-runtime-v2.ts +232 -0
- package/core/utils/env-runtime.ts +252 -0
- package/core/utils/errors/codes.ts +115 -0
- package/core/utils/errors/handlers.ts +63 -0
- package/core/utils/errors/index.ts +81 -0
- package/core/utils/helpers.ts +180 -0
- package/core/utils/index.ts +18 -0
- package/core/utils/logger/index.ts +161 -0
- package/core/utils/logger.ts +106 -0
- package/core/utils/monitoring/index.ts +212 -0
- package/create-fluxstack.ts +231 -0
- package/package.json +43 -0
- package/tsconfig.json +51 -0
- package/vite.config.ts +42 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Manager
|
|
3
|
+
* Handles plugin lifecycle, execution, and context management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
Plugin,
|
|
8
|
+
PluginContext,
|
|
9
|
+
PluginHook,
|
|
10
|
+
PluginHookResult,
|
|
11
|
+
PluginMetrics,
|
|
12
|
+
PluginExecutionContext,
|
|
13
|
+
HookExecutionOptions,
|
|
14
|
+
RequestContext,
|
|
15
|
+
ResponseContext,
|
|
16
|
+
ErrorContext,
|
|
17
|
+
BuildContext
|
|
18
|
+
} from "./types"
|
|
19
|
+
import type { FluxStackConfig } from "../config/schema"
|
|
20
|
+
import type { Logger } from "../utils/logger/index"
|
|
21
|
+
import { PluginRegistry } from "./registry"
|
|
22
|
+
import { createPluginUtils } from "./config"
|
|
23
|
+
import { FluxStackError } from "../utils/errors"
|
|
24
|
+
import { EventEmitter } from "events"
|
|
25
|
+
|
|
26
|
+
export interface PluginManagerConfig {
|
|
27
|
+
config: FluxStackConfig
|
|
28
|
+
logger: Logger
|
|
29
|
+
app?: any
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class PluginManager extends EventEmitter {
|
|
33
|
+
private registry: PluginRegistry
|
|
34
|
+
private config: FluxStackConfig
|
|
35
|
+
private logger: Logger
|
|
36
|
+
private app?: any
|
|
37
|
+
private metrics: Map<string, PluginMetrics> = new Map()
|
|
38
|
+
private contexts: Map<string, PluginContext> = new Map()
|
|
39
|
+
private initialized = false
|
|
40
|
+
|
|
41
|
+
constructor(options: PluginManagerConfig) {
|
|
42
|
+
super()
|
|
43
|
+
this.config = options.config
|
|
44
|
+
this.logger = options.logger
|
|
45
|
+
this.app = options.app
|
|
46
|
+
|
|
47
|
+
this.registry = new PluginRegistry({
|
|
48
|
+
logger: this.logger,
|
|
49
|
+
config: this.config
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Initialize the plugin manager
|
|
55
|
+
*/
|
|
56
|
+
async initialize(): Promise<void> {
|
|
57
|
+
if (this.initialized) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.logger.info('Initializing plugin manager')
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Discover and load plugins
|
|
65
|
+
await this.discoverPlugins()
|
|
66
|
+
|
|
67
|
+
// Setup plugin contexts
|
|
68
|
+
this.setupPluginContexts()
|
|
69
|
+
|
|
70
|
+
// Execute setup hooks
|
|
71
|
+
await this.executeHook('setup')
|
|
72
|
+
|
|
73
|
+
this.initialized = true
|
|
74
|
+
this.logger.info('Plugin manager initialized successfully', {
|
|
75
|
+
totalPlugins: this.registry.getStats().totalPlugins
|
|
76
|
+
})
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.logger.error('Failed to initialize plugin manager', { error })
|
|
79
|
+
throw error
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Shutdown the plugin manager
|
|
85
|
+
*/
|
|
86
|
+
async shutdown(): Promise<void> {
|
|
87
|
+
if (!this.initialized) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.logger.info('Shutting down plugin manager')
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await this.executeHook('onServerStop')
|
|
95
|
+
this.initialized = false
|
|
96
|
+
this.logger.info('Plugin manager shut down successfully')
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.logger.error('Error during plugin manager shutdown', { error })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get the plugin registry
|
|
104
|
+
*/
|
|
105
|
+
getRegistry(): PluginRegistry {
|
|
106
|
+
return this.registry
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Register a plugin
|
|
111
|
+
*/
|
|
112
|
+
async registerPlugin(plugin: Plugin): Promise<void> {
|
|
113
|
+
await this.registry.register(plugin)
|
|
114
|
+
this.setupPluginContext(plugin)
|
|
115
|
+
|
|
116
|
+
if (this.initialized && plugin.setup) {
|
|
117
|
+
await this.executePluginHook(plugin, 'setup')
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Unregister a plugin
|
|
123
|
+
*/
|
|
124
|
+
unregisterPlugin(name: string): void {
|
|
125
|
+
this.registry.unregister(name)
|
|
126
|
+
this.contexts.delete(name)
|
|
127
|
+
this.metrics.delete(name)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Execute a hook on all plugins
|
|
132
|
+
*/
|
|
133
|
+
async executeHook(
|
|
134
|
+
hook: PluginHook,
|
|
135
|
+
context?: any,
|
|
136
|
+
options: HookExecutionOptions = {}
|
|
137
|
+
): Promise<PluginHookResult[]> {
|
|
138
|
+
const {
|
|
139
|
+
timeout = 30000,
|
|
140
|
+
parallel = false,
|
|
141
|
+
stopOnError = false,
|
|
142
|
+
retries = 0
|
|
143
|
+
} = options
|
|
144
|
+
|
|
145
|
+
const results: PluginHookResult[] = []
|
|
146
|
+
const loadOrder = this.registry.getLoadOrder()
|
|
147
|
+
const enabledPlugins = this.getEnabledPlugins()
|
|
148
|
+
|
|
149
|
+
this.logger.debug(`Executing hook '${hook}' on ${enabledPlugins.length} plugins`, {
|
|
150
|
+
hook,
|
|
151
|
+
plugins: enabledPlugins.map(p => p.name),
|
|
152
|
+
parallel,
|
|
153
|
+
timeout
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const executePlugin = async (plugin: Plugin): Promise<PluginHookResult> => {
|
|
157
|
+
if (!enabledPlugins.includes(plugin)) {
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
duration: 0,
|
|
161
|
+
plugin: plugin.name,
|
|
162
|
+
hook
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return this.executePluginHook(plugin, hook, context, { timeout, retries })
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
if (parallel) {
|
|
171
|
+
// Execute all plugins in parallel
|
|
172
|
+
const promises = loadOrder
|
|
173
|
+
.map(name => this.registry.get(name))
|
|
174
|
+
.filter(Boolean)
|
|
175
|
+
.map(plugin => executePlugin(plugin!))
|
|
176
|
+
|
|
177
|
+
const settled = await Promise.allSettled(promises)
|
|
178
|
+
|
|
179
|
+
for (const result of settled) {
|
|
180
|
+
if (result.status === 'fulfilled') {
|
|
181
|
+
results.push(result.value)
|
|
182
|
+
} else {
|
|
183
|
+
results.push({
|
|
184
|
+
success: false,
|
|
185
|
+
error: result.reason,
|
|
186
|
+
duration: 0,
|
|
187
|
+
plugin: 'unknown',
|
|
188
|
+
hook
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
// Execute plugins sequentially
|
|
194
|
+
for (const pluginName of loadOrder) {
|
|
195
|
+
const plugin = this.registry.get(pluginName)
|
|
196
|
+
if (!plugin) continue
|
|
197
|
+
|
|
198
|
+
const result = await executePlugin(plugin)
|
|
199
|
+
results.push(result)
|
|
200
|
+
|
|
201
|
+
if (!result.success && stopOnError) {
|
|
202
|
+
this.logger.error(`Hook execution stopped due to error in plugin '${plugin.name}'`, {
|
|
203
|
+
hook,
|
|
204
|
+
plugin: plugin.name,
|
|
205
|
+
error: result.error
|
|
206
|
+
})
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Emit hook completion event
|
|
213
|
+
this.emit('hook:after', { hook, results, context })
|
|
214
|
+
|
|
215
|
+
return results
|
|
216
|
+
} catch (error) {
|
|
217
|
+
this.logger.error(`Hook '${hook}' execution failed`, { error })
|
|
218
|
+
this.emit('hook:error', { hook, error, context })
|
|
219
|
+
throw error
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Execute a specific hook on a specific plugin
|
|
225
|
+
*/
|
|
226
|
+
async executePluginHook(
|
|
227
|
+
plugin: Plugin,
|
|
228
|
+
hook: PluginHook,
|
|
229
|
+
context?: any,
|
|
230
|
+
options: { timeout?: number; retries?: number } = {}
|
|
231
|
+
): Promise<PluginHookResult> {
|
|
232
|
+
const { timeout = 30000, retries = 0 } = options
|
|
233
|
+
const startTime = Date.now()
|
|
234
|
+
|
|
235
|
+
// Check if plugin implements this hook
|
|
236
|
+
const hookFunction = plugin[hook]
|
|
237
|
+
if (!hookFunction || typeof hookFunction !== 'function') {
|
|
238
|
+
return {
|
|
239
|
+
success: true,
|
|
240
|
+
duration: 0,
|
|
241
|
+
plugin: plugin.name,
|
|
242
|
+
hook
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.emit('hook:before', { plugin: plugin.name, hook, context })
|
|
247
|
+
|
|
248
|
+
let attempt = 0
|
|
249
|
+
let lastError: Error | undefined
|
|
250
|
+
|
|
251
|
+
while (attempt <= retries) {
|
|
252
|
+
try {
|
|
253
|
+
const pluginContext = this.getPluginContext(plugin.name)
|
|
254
|
+
const executionContext: PluginExecutionContext = {
|
|
255
|
+
plugin,
|
|
256
|
+
hook,
|
|
257
|
+
startTime: Date.now(),
|
|
258
|
+
timeout,
|
|
259
|
+
retries
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Create timeout promise
|
|
263
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
264
|
+
setTimeout(() => {
|
|
265
|
+
reject(new FluxStackError(
|
|
266
|
+
`Plugin '${plugin.name}' hook '${hook}' timed out after ${timeout}ms`,
|
|
267
|
+
'PLUGIN_TIMEOUT',
|
|
268
|
+
408
|
|
269
|
+
))
|
|
270
|
+
}, timeout)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// Execute the hook with appropriate context
|
|
274
|
+
let hookPromise: Promise<any>
|
|
275
|
+
|
|
276
|
+
switch (hook) {
|
|
277
|
+
case 'setup':
|
|
278
|
+
case 'onServerStart':
|
|
279
|
+
case 'onServerStop':
|
|
280
|
+
hookPromise = Promise.resolve(hookFunction(pluginContext as any))
|
|
281
|
+
break
|
|
282
|
+
case 'onRequest':
|
|
283
|
+
case 'onResponse':
|
|
284
|
+
case 'onError':
|
|
285
|
+
hookPromise = Promise.resolve(hookFunction(context as any))
|
|
286
|
+
break
|
|
287
|
+
case 'onBuild':
|
|
288
|
+
case 'onBuildComplete':
|
|
289
|
+
hookPromise = Promise.resolve(hookFunction(context as any))
|
|
290
|
+
break
|
|
291
|
+
default:
|
|
292
|
+
hookPromise = Promise.resolve(hookFunction(context || pluginContext))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Race between hook execution and timeout
|
|
296
|
+
await Promise.race([hookPromise, timeoutPromise])
|
|
297
|
+
|
|
298
|
+
const duration = Date.now() - startTime
|
|
299
|
+
|
|
300
|
+
// Update metrics
|
|
301
|
+
this.updatePluginMetrics(plugin.name, hook, duration, true)
|
|
302
|
+
|
|
303
|
+
this.logger.debug(`Plugin '${plugin.name}' hook '${hook}' completed successfully`, {
|
|
304
|
+
plugin: plugin.name,
|
|
305
|
+
hook,
|
|
306
|
+
duration,
|
|
307
|
+
attempt: attempt + 1
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
success: true,
|
|
312
|
+
duration,
|
|
313
|
+
plugin: plugin.name,
|
|
314
|
+
hook,
|
|
315
|
+
context: executionContext
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
} catch (error) {
|
|
319
|
+
lastError = error instanceof Error ? error : new Error(String(error))
|
|
320
|
+
attempt++
|
|
321
|
+
|
|
322
|
+
this.logger.warn(`Plugin '${plugin.name}' hook '${hook}' failed (attempt ${attempt}/${retries + 1})`, {
|
|
323
|
+
plugin: plugin.name,
|
|
324
|
+
hook,
|
|
325
|
+
error: lastError.message,
|
|
326
|
+
attempt
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
if (attempt <= retries) {
|
|
330
|
+
// Wait before retry (exponential backoff)
|
|
331
|
+
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt - 1) * 1000))
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const duration = Date.now() - startTime
|
|
337
|
+
|
|
338
|
+
// Update metrics
|
|
339
|
+
this.updatePluginMetrics(plugin.name, hook, duration, false)
|
|
340
|
+
|
|
341
|
+
this.emit('plugin:error', { plugin: plugin.name, hook, error: lastError })
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
success: false,
|
|
345
|
+
error: lastError,
|
|
346
|
+
duration,
|
|
347
|
+
plugin: plugin.name,
|
|
348
|
+
hook
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get plugin metrics
|
|
354
|
+
*/
|
|
355
|
+
getPluginMetrics(pluginName?: string): PluginMetrics | Map<string, PluginMetrics> {
|
|
356
|
+
if (pluginName) {
|
|
357
|
+
return this.metrics.get(pluginName) || {
|
|
358
|
+
loadTime: 0,
|
|
359
|
+
setupTime: 0,
|
|
360
|
+
hookExecutions: new Map(),
|
|
361
|
+
errors: 0,
|
|
362
|
+
warnings: 0
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return this.metrics
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Get enabled plugins
|
|
370
|
+
*/
|
|
371
|
+
private getEnabledPlugins(): Plugin[] {
|
|
372
|
+
const allPlugins = this.registry.getAll()
|
|
373
|
+
const enabledNames = this.config.plugins.enabled
|
|
374
|
+
const disabledNames = this.config.plugins.disabled
|
|
375
|
+
|
|
376
|
+
return allPlugins.filter(plugin => {
|
|
377
|
+
// If explicitly disabled, exclude
|
|
378
|
+
if (disabledNames.includes(plugin.name)) {
|
|
379
|
+
return false
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// If enabled list is empty, include all non-disabled
|
|
383
|
+
if (enabledNames.length === 0) {
|
|
384
|
+
return true
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Otherwise, only include if explicitly enabled
|
|
388
|
+
return enabledNames.includes(plugin.name)
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Discover and load plugins
|
|
394
|
+
*/
|
|
395
|
+
private async discoverPlugins(): Promise<void> {
|
|
396
|
+
try {
|
|
397
|
+
const results = await this.registry.discoverPlugins({
|
|
398
|
+
includeBuiltIn: true,
|
|
399
|
+
includeExternal: true
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
let loaded = 0
|
|
403
|
+
let failed = 0
|
|
404
|
+
|
|
405
|
+
for (const result of results) {
|
|
406
|
+
if (result.success) {
|
|
407
|
+
loaded++
|
|
408
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
409
|
+
this.logger.warn(`Plugin '${result.plugin?.name}' loaded with warnings`, {
|
|
410
|
+
warnings: result.warnings
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
failed++
|
|
415
|
+
this.logger.error(`Failed to load plugin: ${result.error}`)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
this.logger.info('Plugin discovery completed', { loaded, failed })
|
|
420
|
+
} catch (error) {
|
|
421
|
+
this.logger.error('Plugin discovery failed', { error })
|
|
422
|
+
throw error
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Setup plugin contexts for all plugins
|
|
428
|
+
*/
|
|
429
|
+
private setupPluginContexts(): void {
|
|
430
|
+
const plugins = this.registry.getAll()
|
|
431
|
+
|
|
432
|
+
for (const plugin of plugins) {
|
|
433
|
+
this.setupPluginContext(plugin)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Setup context for a specific plugin
|
|
439
|
+
*/
|
|
440
|
+
private setupPluginContext(plugin: Plugin): void {
|
|
441
|
+
// Plugin config available but not used in current implementation
|
|
442
|
+
// const pluginConfig = this.config.plugins.config[plugin.name] || {}
|
|
443
|
+
// const mergedConfig = { ...plugin.defaultConfig, ...pluginConfig }
|
|
444
|
+
|
|
445
|
+
const context: PluginContext = {
|
|
446
|
+
config: this.config,
|
|
447
|
+
logger: this.logger.child({ plugin: plugin.name }),
|
|
448
|
+
app: this.app,
|
|
449
|
+
utils: createPluginUtils(this.logger),
|
|
450
|
+
registry: this.registry
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
this.contexts.set(plugin.name, context)
|
|
454
|
+
|
|
455
|
+
// Initialize metrics
|
|
456
|
+
this.metrics.set(plugin.name, {
|
|
457
|
+
loadTime: 0,
|
|
458
|
+
setupTime: 0,
|
|
459
|
+
hookExecutions: new Map(),
|
|
460
|
+
errors: 0,
|
|
461
|
+
warnings: 0
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Get plugin context
|
|
467
|
+
*/
|
|
468
|
+
private getPluginContext(pluginName: string): PluginContext {
|
|
469
|
+
const context = this.contexts.get(pluginName)
|
|
470
|
+
if (!context) {
|
|
471
|
+
throw new FluxStackError(
|
|
472
|
+
`Plugin context not found for '${pluginName}'`,
|
|
473
|
+
'PLUGIN_CONTEXT_NOT_FOUND',
|
|
474
|
+
500
|
|
475
|
+
)
|
|
476
|
+
}
|
|
477
|
+
return context
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Update plugin metrics
|
|
482
|
+
*/
|
|
483
|
+
private updatePluginMetrics(
|
|
484
|
+
pluginName: string,
|
|
485
|
+
hook: PluginHook,
|
|
486
|
+
duration: number,
|
|
487
|
+
success: boolean
|
|
488
|
+
): void {
|
|
489
|
+
const metrics = this.metrics.get(pluginName)
|
|
490
|
+
if (!metrics) return
|
|
491
|
+
|
|
492
|
+
// Update hook execution count
|
|
493
|
+
const currentCount = metrics.hookExecutions.get(hook) || 0
|
|
494
|
+
metrics.hookExecutions.set(hook, currentCount + 1)
|
|
495
|
+
|
|
496
|
+
// Update error/success counts
|
|
497
|
+
if (success) {
|
|
498
|
+
if (hook === 'setup') {
|
|
499
|
+
metrics.setupTime = duration
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
metrics.errors++
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
metrics.lastExecution = new Date()
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Create request context from HTTP request
|
|
511
|
+
*/
|
|
512
|
+
export function createRequestContext(request: Request, additionalData: any = {}): RequestContext {
|
|
513
|
+
const url = new URL(request.url)
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
request,
|
|
517
|
+
path: url.pathname,
|
|
518
|
+
method: request.method,
|
|
519
|
+
headers: (() => {
|
|
520
|
+
const headers: Record<string, string> = {}
|
|
521
|
+
request.headers.forEach((value, key) => {
|
|
522
|
+
headers[key] = value
|
|
523
|
+
})
|
|
524
|
+
return headers
|
|
525
|
+
})(),
|
|
526
|
+
query: Object.fromEntries(url.searchParams.entries()),
|
|
527
|
+
params: {},
|
|
528
|
+
startTime: Date.now(),
|
|
529
|
+
...additionalData
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Create response context from request context and response
|
|
535
|
+
*/
|
|
536
|
+
export function createResponseContext(
|
|
537
|
+
requestContext: RequestContext,
|
|
538
|
+
response: Response,
|
|
539
|
+
additionalData: any = {}
|
|
540
|
+
): ResponseContext {
|
|
541
|
+
return {
|
|
542
|
+
...requestContext,
|
|
543
|
+
response,
|
|
544
|
+
statusCode: response.status,
|
|
545
|
+
duration: Date.now() - requestContext.startTime,
|
|
546
|
+
size: parseInt(response.headers.get('content-length') || '0'),
|
|
547
|
+
...additionalData
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Create error context from request context and error
|
|
553
|
+
*/
|
|
554
|
+
export function createErrorContext(
|
|
555
|
+
requestContext: RequestContext,
|
|
556
|
+
error: Error,
|
|
557
|
+
additionalData: any = {}
|
|
558
|
+
): ErrorContext {
|
|
559
|
+
return {
|
|
560
|
+
...requestContext,
|
|
561
|
+
error,
|
|
562
|
+
duration: Date.now() - requestContext.startTime,
|
|
563
|
+
handled: false,
|
|
564
|
+
...additionalData
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Create build context
|
|
570
|
+
*/
|
|
571
|
+
export function createBuildContext(
|
|
572
|
+
target: string,
|
|
573
|
+
outDir: string,
|
|
574
|
+
mode: 'development' | 'production',
|
|
575
|
+
config: FluxStackConfig
|
|
576
|
+
): BuildContext {
|
|
577
|
+
return {
|
|
578
|
+
target,
|
|
579
|
+
outDir,
|
|
580
|
+
mode,
|
|
581
|
+
config
|
|
582
|
+
}
|
|
583
|
+
}
|