create-fluxstack 1.0.0 → 1.0.2

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.
Files changed (101) hide show
  1. package/create-fluxstack.ts +32 -17
  2. package/package-template.json +51 -0
  3. package/package.json +2 -1
  4. package/.env +0 -30
  5. package/LICENSE +0 -21
  6. package/app/client/README.md +0 -69
  7. package/app/client/frontend-only.ts +0 -12
  8. package/app/client/index.html +0 -13
  9. package/app/client/public/vite.svg +0 -1
  10. package/app/client/src/App.css +0 -883
  11. package/app/client/src/App.tsx +0 -669
  12. package/app/client/src/assets/react.svg +0 -1
  13. package/app/client/src/components/TestPage.tsx +0 -453
  14. package/app/client/src/index.css +0 -51
  15. package/app/client/src/lib/eden-api.ts +0 -110
  16. package/app/client/src/main.tsx +0 -10
  17. package/app/client/src/vite-env.d.ts +0 -1
  18. package/app/client/tsconfig.app.json +0 -43
  19. package/app/client/tsconfig.json +0 -7
  20. package/app/client/tsconfig.node.json +0 -25
  21. package/app/server/app.ts +0 -10
  22. package/app/server/backend-only.ts +0 -15
  23. package/app/server/controllers/users.controller.ts +0 -69
  24. package/app/server/index.ts +0 -104
  25. package/app/server/routes/index.ts +0 -25
  26. package/app/server/routes/users.routes.ts +0 -121
  27. package/app/server/types/index.ts +0 -1
  28. package/app/shared/types/index.ts +0 -18
  29. package/bun.lock +0 -1053
  30. package/core/__tests__/integration.test.ts +0 -227
  31. package/core/build/index.ts +0 -186
  32. package/core/cli/command-registry.ts +0 -334
  33. package/core/cli/index.ts +0 -394
  34. package/core/cli/plugin-discovery.ts +0 -200
  35. package/core/client/standalone.ts +0 -57
  36. package/core/config/__tests__/config-loader.test.ts +0 -591
  37. package/core/config/__tests__/config-merger.test.ts +0 -657
  38. package/core/config/__tests__/env-converter.test.ts +0 -372
  39. package/core/config/__tests__/env-processor.test.ts +0 -431
  40. package/core/config/__tests__/env.test.ts +0 -452
  41. package/core/config/__tests__/integration.test.ts +0 -418
  42. package/core/config/__tests__/loader.test.ts +0 -331
  43. package/core/config/__tests__/schema.test.ts +0 -129
  44. package/core/config/__tests__/validator.test.ts +0 -318
  45. package/core/config/env-dynamic.ts +0 -326
  46. package/core/config/env.ts +0 -597
  47. package/core/config/index.ts +0 -317
  48. package/core/config/loader.ts +0 -546
  49. package/core/config/runtime-config.ts +0 -322
  50. package/core/config/schema.ts +0 -694
  51. package/core/config/validator.ts +0 -540
  52. package/core/framework/__tests__/server.test.ts +0 -233
  53. package/core/framework/client.ts +0 -132
  54. package/core/framework/index.ts +0 -8
  55. package/core/framework/server.ts +0 -501
  56. package/core/framework/types.ts +0 -63
  57. package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
  58. package/core/plugins/__tests__/manager.test.ts +0 -398
  59. package/core/plugins/__tests__/monitoring.test.ts +0 -401
  60. package/core/plugins/__tests__/registry.test.ts +0 -335
  61. package/core/plugins/built-in/index.ts +0 -142
  62. package/core/plugins/built-in/logger/index.ts +0 -180
  63. package/core/plugins/built-in/monitoring/README.md +0 -193
  64. package/core/plugins/built-in/monitoring/index.ts +0 -912
  65. package/core/plugins/built-in/static/index.ts +0 -289
  66. package/core/plugins/built-in/swagger/index.ts +0 -229
  67. package/core/plugins/built-in/vite/index.ts +0 -316
  68. package/core/plugins/config.ts +0 -348
  69. package/core/plugins/discovery.ts +0 -350
  70. package/core/plugins/executor.ts +0 -351
  71. package/core/plugins/index.ts +0 -195
  72. package/core/plugins/manager.ts +0 -583
  73. package/core/plugins/registry.ts +0 -424
  74. package/core/plugins/types.ts +0 -254
  75. package/core/server/framework.ts +0 -123
  76. package/core/server/index.ts +0 -8
  77. package/core/server/plugins/database.ts +0 -182
  78. package/core/server/plugins/logger.ts +0 -47
  79. package/core/server/plugins/swagger.ts +0 -34
  80. package/core/server/standalone.ts +0 -91
  81. package/core/templates/create-project.ts +0 -455
  82. package/core/types/api.ts +0 -169
  83. package/core/types/build.ts +0 -174
  84. package/core/types/config.ts +0 -68
  85. package/core/types/index.ts +0 -127
  86. package/core/types/plugin.ts +0 -94
  87. package/core/utils/__tests__/errors.test.ts +0 -139
  88. package/core/utils/__tests__/helpers.test.ts +0 -297
  89. package/core/utils/__tests__/logger.test.ts +0 -141
  90. package/core/utils/env-runtime-v2.ts +0 -232
  91. package/core/utils/env-runtime.ts +0 -252
  92. package/core/utils/errors/codes.ts +0 -115
  93. package/core/utils/errors/handlers.ts +0 -63
  94. package/core/utils/errors/index.ts +0 -81
  95. package/core/utils/helpers.ts +0 -180
  96. package/core/utils/index.ts +0 -18
  97. package/core/utils/logger/index.ts +0 -161
  98. package/core/utils/logger.ts +0 -106
  99. package/core/utils/monitoring/index.ts +0 -212
  100. package/tsconfig.json +0 -51
  101. package/vite.config.ts +0 -42
@@ -1,350 +0,0 @@
1
- /**
2
- * Plugin Discovery System
3
- * Handles automatic discovery and loading of plugins from various sources
4
- */
5
-
6
- import type { Plugin, PluginManifest, PluginLoadResult, PluginDiscoveryOptions } from "./types"
7
- import type { Logger } from "../utils/logger/index"
8
- import { readdir, readFile } from "fs/promises"
9
- import { join, resolve } from "path"
10
- import { existsSync } from "fs"
11
-
12
- export interface PluginDiscoveryConfig {
13
- logger?: Logger
14
- baseDir?: string
15
- builtInDir?: string
16
- externalDir?: string
17
- nodeModulesDir?: string
18
- }
19
-
20
- export class PluginDiscovery {
21
- private logger?: Logger
22
- private baseDir: string
23
- private builtInDir: string
24
- private externalDir: string
25
- private nodeModulesDir: string
26
-
27
- constructor(config: PluginDiscoveryConfig = {}) {
28
- this.logger = config.logger
29
- this.baseDir = config.baseDir || process.cwd()
30
- this.builtInDir = config.builtInDir || join(this.baseDir, 'core/plugins/built-in')
31
- this.externalDir = config.externalDir || join(this.baseDir, 'plugins')
32
- this.nodeModulesDir = config.nodeModulesDir || join(this.baseDir, 'node_modules')
33
- }
34
-
35
- /**
36
- * Discover all available plugins
37
- */
38
- async discoverAll(options: PluginDiscoveryOptions = {}): Promise<PluginLoadResult[]> {
39
- const results: PluginLoadResult[] = []
40
- const {
41
- includeBuiltIn = true,
42
- includeExternal = true
43
- } = options
44
-
45
- // Discover built-in plugins
46
- if (includeBuiltIn) {
47
- const builtInResults = await this.discoverBuiltInPlugins()
48
- results.push(...builtInResults)
49
- }
50
-
51
- // Discover external plugins
52
- if (includeExternal) {
53
- const externalResults = await this.discoverExternalPlugins()
54
- results.push(...externalResults)
55
-
56
- const npmResults = await this.discoverNpmPlugins()
57
- results.push(...npmResults)
58
- }
59
-
60
- return results
61
- }
62
-
63
- /**
64
- * Discover built-in plugins
65
- */
66
- async discoverBuiltInPlugins(): Promise<PluginLoadResult[]> {
67
- if (!existsSync(this.builtInDir)) {
68
- this.logger?.debug('Built-in plugins directory not found', { dir: this.builtInDir })
69
- return []
70
- }
71
-
72
- return this.discoverPluginsInDirectory(this.builtInDir, 'built-in')
73
- }
74
-
75
- /**
76
- * Discover external plugins in the plugins directory
77
- */
78
- async discoverExternalPlugins(): Promise<PluginLoadResult[]> {
79
- if (!existsSync(this.externalDir)) {
80
- this.logger?.debug('External plugins directory not found', { dir: this.externalDir })
81
- return []
82
- }
83
-
84
- return this.discoverPluginsInDirectory(this.externalDir, 'external')
85
- }
86
-
87
- /**
88
- * Discover npm-installed plugins
89
- */
90
- async discoverNpmPlugins(): Promise<PluginLoadResult[]> {
91
- if (!existsSync(this.nodeModulesDir)) {
92
- this.logger?.debug('Node modules directory not found', { dir: this.nodeModulesDir })
93
- return []
94
- }
95
-
96
- const results: PluginLoadResult[] = []
97
-
98
- try {
99
- const entries = await readdir(this.nodeModulesDir, { withFileTypes: true })
100
-
101
- for (const entry of entries) {
102
- if (entry.isDirectory() && entry.name.startsWith('fluxstack-plugin-')) {
103
- const pluginDir = join(this.nodeModulesDir, entry.name)
104
- const result = await this.loadPluginFromDirectory(pluginDir, 'npm')
105
- results.push(result)
106
- }
107
- }
108
- } catch (error) {
109
- this.logger?.error('Failed to discover npm plugins', { error })
110
- }
111
-
112
- return results
113
- }
114
-
115
- /**
116
- * Load a specific plugin by name
117
- */
118
- async loadPlugin(name: string): Promise<PluginLoadResult> {
119
- // Try built-in first
120
- const builtInPath = join(this.builtInDir, name)
121
- if (existsSync(builtInPath)) {
122
- return this.loadPluginFromDirectory(builtInPath, 'built-in')
123
- }
124
-
125
- // Try external plugins
126
- const externalPath = join(this.externalDir, name)
127
- if (existsSync(externalPath)) {
128
- return this.loadPluginFromDirectory(externalPath, 'external')
129
- }
130
-
131
- // Try npm plugins
132
- const npmPath = join(this.nodeModulesDir, `fluxstack-plugin-${name}`)
133
- if (existsSync(npmPath)) {
134
- return this.loadPluginFromDirectory(npmPath, 'npm')
135
- }
136
-
137
- return {
138
- success: false,
139
- error: `Plugin '${name}' not found in any plugin directory`
140
- }
141
- }
142
-
143
- /**
144
- * Discover plugins in a specific directory
145
- */
146
- private async discoverPluginsInDirectory(
147
- directory: string,
148
- source: 'built-in' | 'external' | 'npm'
149
- ): Promise<PluginLoadResult[]> {
150
- const results: PluginLoadResult[] = []
151
-
152
- try {
153
- const entries = await readdir(directory, { withFileTypes: true })
154
-
155
- for (const entry of entries) {
156
- if (entry.isDirectory()) {
157
- const pluginDir = join(directory, entry.name)
158
- const result = await this.loadPluginFromDirectory(pluginDir, source)
159
- results.push(result)
160
- }
161
- }
162
- } catch (error) {
163
- this.logger?.error(`Failed to discover plugins in directory '${directory}'`, { error })
164
- results.push({
165
- success: false,
166
- error: `Failed to scan directory: ${error instanceof Error ? error.message : String(error)}`
167
- })
168
- }
169
-
170
- return results
171
- }
172
-
173
- /**
174
- * Load a plugin from a specific directory
175
- */
176
- private async loadPluginFromDirectory(
177
- pluginDir: string,
178
- source: 'built-in' | 'external' | 'npm'
179
- ): Promise<PluginLoadResult> {
180
- try {
181
- // Load manifest if it exists
182
- const manifest = await this.loadPluginManifest(pluginDir)
183
-
184
- // Find the main plugin file
185
- const pluginFile = await this.findPluginFile(pluginDir)
186
- if (!pluginFile) {
187
- return {
188
- success: false,
189
- error: 'No plugin entry point found (index.ts, index.js, plugin.ts, or plugin.js)'
190
- }
191
- }
192
-
193
- // Import the plugin
194
- const pluginModule = await import(resolve(pluginFile))
195
- const plugin: Plugin = pluginModule.default || pluginModule
196
-
197
- if (!this.isValidPlugin(plugin)) {
198
- return {
199
- success: false,
200
- error: 'Invalid plugin: must export a plugin object with a name property'
201
- }
202
- }
203
-
204
- // Validate manifest compatibility
205
- const warnings: string[] = []
206
- if (manifest) {
207
- const manifestWarnings = this.validateManifestCompatibility(plugin, manifest)
208
- warnings.push(...manifestWarnings)
209
- } else {
210
- warnings.push('No plugin manifest found')
211
- }
212
-
213
- this.logger?.debug(`Loaded plugin '${plugin.name}' from ${source}`, {
214
- plugin: plugin.name,
215
- version: plugin.version,
216
- source,
217
- path: pluginDir
218
- })
219
-
220
- return {
221
- success: true,
222
- plugin,
223
- warnings
224
- }
225
- } catch (error) {
226
- this.logger?.error(`Failed to load plugin from '${pluginDir}'`, { error })
227
- return {
228
- success: false,
229
- error: error instanceof Error ? error.message : String(error)
230
- }
231
- }
232
- }
233
-
234
- /**
235
- * Load plugin manifest from directory
236
- */
237
- private async loadPluginManifest(pluginDir: string): Promise<PluginManifest | undefined> {
238
- const manifestPath = join(pluginDir, 'plugin.json')
239
-
240
- if (!existsSync(manifestPath)) {
241
- // Try package.json for npm plugins
242
- const packagePath = join(pluginDir, 'package.json')
243
- if (existsSync(packagePath)) {
244
- try {
245
- const packageContent = await readFile(packagePath, 'utf-8')
246
- const packageJson = JSON.parse(packageContent)
247
-
248
- if (packageJson.fluxstack) {
249
- return {
250
- name: packageJson.name,
251
- version: packageJson.version,
252
- description: packageJson.description || '',
253
- author: packageJson.author || '',
254
- license: packageJson.license || '',
255
- homepage: packageJson.homepage,
256
- repository: packageJson.repository,
257
- keywords: packageJson.keywords || [],
258
- dependencies: packageJson.dependencies || {},
259
- peerDependencies: packageJson.peerDependencies,
260
- fluxstack: packageJson.fluxstack
261
- }
262
- }
263
- } catch (error) {
264
- this.logger?.warn(`Failed to parse package.json in '${pluginDir}'`, { error })
265
- }
266
- }
267
- return undefined
268
- }
269
-
270
- try {
271
- const manifestContent = await readFile(manifestPath, 'utf-8')
272
- return JSON.parse(manifestContent)
273
- } catch (error) {
274
- this.logger?.warn(`Failed to parse plugin manifest in '${pluginDir}'`, { error })
275
- return undefined
276
- }
277
- }
278
-
279
- /**
280
- * Find the main plugin file in a directory
281
- */
282
- private async findPluginFile(pluginDir: string): Promise<string | null> {
283
- const possibleFiles = [
284
- 'index.ts',
285
- 'index.js',
286
- 'plugin.ts',
287
- 'plugin.js',
288
- 'src/index.ts',
289
- 'src/index.js',
290
- 'dist/index.js'
291
- ]
292
-
293
- for (const file of possibleFiles) {
294
- const filePath = join(pluginDir, file)
295
- if (existsSync(filePath)) {
296
- return filePath
297
- }
298
- }
299
-
300
- return null
301
- }
302
-
303
- /**
304
- * Validate if an object is a valid plugin
305
- */
306
- private isValidPlugin(plugin: any): plugin is Plugin {
307
- return (
308
- plugin &&
309
- typeof plugin === 'object' &&
310
- typeof plugin.name === 'string' &&
311
- plugin.name.length > 0
312
- )
313
- }
314
-
315
- /**
316
- * Validate manifest compatibility with plugin
317
- */
318
- private validateManifestCompatibility(plugin: Plugin, manifest: PluginManifest): string[] {
319
- const warnings: string[] = []
320
-
321
- if (plugin.name !== manifest.name) {
322
- warnings.push(`Plugin name mismatch: plugin exports '${plugin.name}' but manifest declares '${manifest.name}'`)
323
- }
324
-
325
- if (plugin.version && plugin.version !== manifest.version) {
326
- warnings.push(`Plugin version mismatch: plugin exports '${plugin.version}' but manifest declares '${manifest.version}'`)
327
- }
328
-
329
- if (plugin.dependencies && manifest.fluxstack.hooks) {
330
- // Check if plugin implements the hooks declared in manifest
331
- const declaredHooks = manifest.fluxstack.hooks
332
- const implementedHooks = Object.keys(plugin).filter(key =>
333
- key.startsWith('on') || key === 'setup'
334
- )
335
-
336
- for (const hook of declaredHooks) {
337
- if (!implementedHooks.includes(hook)) {
338
- warnings.push(`Plugin declares hook '${hook}' in manifest but doesn't implement it`)
339
- }
340
- }
341
- }
342
-
343
- return warnings
344
- }
345
- }
346
-
347
- /**
348
- * Default plugin discovery instance
349
- */
350
- export const pluginDiscovery = new PluginDiscovery()
@@ -1,351 +0,0 @@
1
- /**
2
- * Plugin Executor
3
- * Handles plugin execution with priority and dependency resolution
4
- */
5
-
6
- import type {
7
- Plugin,
8
- PluginHook,
9
- PluginHookResult,
10
- PluginPriority,
11
- HookExecutionOptions
12
- } from "./types"
13
- import type { Logger } from "../utils/logger/index"
14
- import { FluxStackError } from "../utils/errors"
15
-
16
- export interface PluginExecutionPlan {
17
- hook: PluginHook
18
- plugins: PluginExecutionStep[]
19
- parallel: boolean
20
- totalPlugins: number
21
- }
22
-
23
- export interface PluginExecutionStep {
24
- plugin: Plugin
25
- priority: number
26
- dependencies: string[]
27
- dependents: string[]
28
- canExecuteInParallel: boolean
29
- }
30
-
31
- export class PluginExecutor {
32
- private logger: Logger
33
-
34
- constructor(logger: Logger) {
35
- this.logger = logger
36
- }
37
-
38
- /**
39
- * Create execution plan for a hook
40
- */
41
- createExecutionPlan(
42
- plugins: Plugin[],
43
- hook: PluginHook,
44
- options: HookExecutionOptions = {}
45
- ): PluginExecutionPlan {
46
- const { parallel = false } = options
47
-
48
- // Filter plugins that implement this hook
49
- const applicablePlugins = plugins.filter(plugin => {
50
- const hookFunction = plugin[hook]
51
- return hookFunction && typeof hookFunction === 'function'
52
- })
53
-
54
- // Create execution steps
55
- const steps = applicablePlugins.map(plugin => this.createExecutionStep(plugin, plugins))
56
-
57
- // Sort by priority and dependencies
58
- const sortedSteps = this.sortExecutionSteps(steps, hook)
59
-
60
- return {
61
- hook,
62
- plugins: sortedSteps,
63
- parallel,
64
- totalPlugins: applicablePlugins.length
65
- }
66
- }
67
-
68
- /**
69
- * Execute plugins according to plan
70
- */
71
- async executePlan(
72
- plan: PluginExecutionPlan,
73
- context: any,
74
- executor: (plugin: Plugin, hook: PluginHook, context: any) => Promise<PluginHookResult>
75
- ): Promise<PluginHookResult[]> {
76
- const results: PluginHookResult[] = []
77
-
78
- this.logger.debug(`Executing plan for hook '${plan.hook}'`, {
79
- hook: plan.hook,
80
- totalPlugins: plan.totalPlugins,
81
- parallel: plan.parallel
82
- })
83
-
84
- if (plan.parallel) {
85
- // Execute in parallel groups based on dependencies
86
- const groups = this.createParallelGroups(plan.plugins)
87
-
88
- for (const group of groups) {
89
- const groupPromises = group.map(step =>
90
- executor(step.plugin, plan.hook, context)
91
- )
92
-
93
- const groupResults = await Promise.allSettled(groupPromises)
94
-
95
- for (let i = 0; i < groupResults.length; i++) {
96
- const result = groupResults[i]
97
- if (result.status === 'fulfilled') {
98
- results.push(result.value)
99
- } else {
100
- results.push({
101
- success: false,
102
- error: result.reason,
103
- duration: 0,
104
- plugin: group[i].plugin.name,
105
- hook: plan.hook
106
- })
107
- }
108
- }
109
- }
110
- } else {
111
- // Execute sequentially
112
- for (const step of plan.plugins) {
113
- const result = await executor(step.plugin, plan.hook, context)
114
- results.push(result)
115
- }
116
- }
117
-
118
- return results
119
- }
120
-
121
- /**
122
- * Validate execution plan
123
- */
124
- validateExecutionPlan(plan: PluginExecutionPlan): { valid: boolean; errors: string[] } {
125
- const errors: string[] = []
126
-
127
- // Check for circular dependencies
128
- const visited = new Set<string>()
129
- const visiting = new Set<string>()
130
-
131
- const checkCircular = (step: PluginExecutionStep) => {
132
- if (visiting.has(step.plugin.name)) {
133
- errors.push(`Circular dependency detected involving plugin '${step.plugin.name}'`)
134
- return
135
- }
136
-
137
- if (visited.has(step.plugin.name)) {
138
- return
139
- }
140
-
141
- visiting.add(step.plugin.name)
142
-
143
- for (const depName of step.dependencies) {
144
- const depStep = plan.plugins.find(s => s.plugin.name === depName)
145
- if (depStep) {
146
- checkCircular(depStep)
147
- }
148
- }
149
-
150
- visiting.delete(step.plugin.name)
151
- visited.add(step.plugin.name)
152
- }
153
-
154
- for (const step of plan.plugins) {
155
- checkCircular(step)
156
- }
157
-
158
- // Check for missing dependencies
159
- for (const step of plan.plugins) {
160
- for (const depName of step.dependencies) {
161
- const depExists = plan.plugins.some(s => s.plugin.name === depName)
162
- if (!depExists) {
163
- errors.push(`Plugin '${step.plugin.name}' depends on '${depName}' which is not available`)
164
- }
165
- }
166
- }
167
-
168
- return {
169
- valid: errors.length === 0,
170
- errors
171
- }
172
- }
173
-
174
- /**
175
- * Create execution step for a plugin
176
- */
177
- private createExecutionStep(plugin: Plugin, allPlugins: Plugin[]): PluginExecutionStep {
178
- const priority = this.normalizePriority(plugin.priority)
179
- const dependencies = plugin.dependencies || []
180
-
181
- // Find dependents
182
- const dependents = allPlugins
183
- .filter(p => p.dependencies?.includes(plugin.name))
184
- .map(p => p.name)
185
-
186
- // Determine if can execute in parallel
187
- const canExecuteInParallel = dependencies.length === 0
188
-
189
- return {
190
- plugin,
191
- priority,
192
- dependencies,
193
- dependents,
194
- canExecuteInParallel
195
- }
196
- }
197
-
198
- /**
199
- * Sort execution steps by priority and dependencies
200
- */
201
- private sortExecutionSteps(steps: PluginExecutionStep[], hook: PluginHook): PluginExecutionStep[] {
202
- // Topological sort with priority consideration
203
- const sorted: PluginExecutionStep[] = []
204
- const visited = new Set<string>()
205
- const visiting = new Set<string>()
206
-
207
- const visit = (step: PluginExecutionStep) => {
208
- if (visiting.has(step.plugin.name)) {
209
- throw new FluxStackError(
210
- `Circular dependency detected involving plugin '${step.plugin.name}' for hook '${hook}'`,
211
- 'CIRCULAR_DEPENDENCY',
212
- 400
213
- )
214
- }
215
-
216
- if (visited.has(step.plugin.name)) {
217
- return
218
- }
219
-
220
- visiting.add(step.plugin.name)
221
-
222
- // Visit dependencies first
223
- for (const depName of step.dependencies) {
224
- const depStep = steps.find(s => s.plugin.name === depName)
225
- if (depStep) {
226
- visit(depStep)
227
- }
228
- }
229
-
230
- visiting.delete(step.plugin.name)
231
- visited.add(step.plugin.name)
232
- sorted.push(step)
233
- }
234
-
235
- // Sort by priority first, then visit
236
- const prioritySorted = [...steps].sort((a, b) => b.priority - a.priority)
237
-
238
- for (const step of prioritySorted) {
239
- visit(step)
240
- }
241
-
242
- return sorted
243
- }
244
-
245
- /**
246
- * Create parallel execution groups
247
- */
248
- private createParallelGroups(steps: PluginExecutionStep[]): PluginExecutionStep[][] {
249
- const groups: PluginExecutionStep[][] = []
250
- const processed = new Set<string>()
251
-
252
- while (processed.size < steps.length) {
253
- const currentGroup: PluginExecutionStep[] = []
254
-
255
- for (const step of steps) {
256
- if (processed.has(step.plugin.name)) {
257
- continue
258
- }
259
-
260
- // Check if all dependencies are already processed
261
- const canExecute = step.dependencies.every(dep => processed.has(dep))
262
-
263
- if (canExecute) {
264
- currentGroup.push(step)
265
- processed.add(step.plugin.name)
266
- }
267
- }
268
-
269
- if (currentGroup.length === 0) {
270
- // This shouldn't happen if dependencies are valid
271
- const remaining = steps.filter(s => !processed.has(s.plugin.name))
272
- throw new FluxStackError(
273
- `Unable to resolve dependencies for plugins: ${remaining.map(s => s.plugin.name).join(', ')}`,
274
- 'DEPENDENCY_RESOLUTION_ERROR',
275
- 400
276
- )
277
- }
278
-
279
- // Sort group by priority
280
- currentGroup.sort((a, b) => b.priority - a.priority)
281
- groups.push(currentGroup)
282
- }
283
-
284
- return groups
285
- }
286
-
287
- /**
288
- * Normalize plugin priority to numeric value
289
- */
290
- private normalizePriority(priority?: number | PluginPriority): number {
291
- if (typeof priority === 'number') {
292
- return priority
293
- }
294
-
295
- switch (priority) {
296
- case 'highest': return 1000
297
- case 'high': return 750
298
- case 'normal': return 500
299
- case 'low': return 250
300
- case 'lowest': return 0
301
- default: return 500 // default to normal
302
- }
303
- }
304
- }
305
-
306
- /**
307
- * Plugin execution statistics
308
- */
309
- export interface PluginExecutionStats {
310
- totalPlugins: number
311
- successfulPlugins: number
312
- failedPlugins: number
313
- totalDuration: number
314
- averageDuration: number
315
- slowestPlugin: { name: string; duration: number } | null
316
- fastestPlugin: { name: string; duration: number } | null
317
- }
318
-
319
- /**
320
- * Calculate execution statistics
321
- */
322
- export function calculateExecutionStats(results: PluginHookResult[]): PluginExecutionStats {
323
- const totalPlugins = results.length
324
- const successfulPlugins = results.filter(r => r.success).length
325
- const failedPlugins = totalPlugins - successfulPlugins
326
- const totalDuration = results.reduce((sum, r) => sum + r.duration, 0)
327
- const averageDuration = totalPlugins > 0 ? totalDuration / totalPlugins : 0
328
-
329
- let slowestPlugin: { name: string; duration: number } | null = null
330
- let fastestPlugin: { name: string; duration: number } | null = null
331
-
332
- for (const result of results) {
333
- if (!slowestPlugin || result.duration > slowestPlugin.duration) {
334
- slowestPlugin = { name: result.plugin, duration: result.duration }
335
- }
336
-
337
- if (!fastestPlugin || result.duration < fastestPlugin.duration) {
338
- fastestPlugin = { name: result.plugin, duration: result.duration }
339
- }
340
- }
341
-
342
- return {
343
- totalPlugins,
344
- successfulPlugins,
345
- failedPlugins,
346
- totalDuration,
347
- averageDuration,
348
- slowestPlugin,
349
- fastestPlugin
350
- }
351
- }