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