create-fluxstack 1.4.1 → 1.5.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.
Files changed (51) hide show
  1. package/.env.example +8 -1
  2. package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +475 -0
  3. package/CRYPTO-AUTH-MIDDLEWARES.md +473 -0
  4. package/CRYPTO-AUTH-USAGE.md +491 -0
  5. package/EXEMPLO-ROTA-PROTEGIDA.md +347 -0
  6. package/QUICK-START-CRYPTO-AUTH.md +221 -0
  7. package/app/client/src/App.tsx +4 -1
  8. package/app/client/src/pages/CryptoAuthPage.tsx +394 -0
  9. package/app/server/index.ts +4 -0
  10. package/app/server/routes/crypto-auth-demo.routes.ts +167 -0
  11. package/app/server/routes/example-with-crypto-auth.routes.ts +235 -0
  12. package/app/server/routes/exemplo-posts.routes.ts +161 -0
  13. package/app/server/routes/index.ts +5 -1
  14. package/config/index.ts +9 -1
  15. package/core/cli/generators/plugin.ts +324 -34
  16. package/core/cli/generators/template-engine.ts +5 -0
  17. package/core/cli/plugin-discovery.ts +33 -12
  18. package/core/framework/server.ts +10 -0
  19. package/core/plugins/dependency-manager.ts +89 -22
  20. package/core/plugins/index.ts +4 -0
  21. package/core/plugins/manager.ts +3 -2
  22. package/core/plugins/module-resolver.ts +216 -0
  23. package/core/plugins/registry.ts +28 -1
  24. package/core/utils/logger/index.ts +4 -0
  25. package/fluxstack.config.ts +253 -114
  26. package/package.json +117 -117
  27. package/plugins/crypto-auth/README.md +722 -172
  28. package/plugins/crypto-auth/ai-context.md +1282 -0
  29. package/plugins/crypto-auth/cli/make-protected-route.command.ts +383 -0
  30. package/plugins/crypto-auth/client/CryptoAuthClient.ts +136 -159
  31. package/plugins/crypto-auth/client/components/AuthProvider.tsx +35 -94
  32. package/plugins/crypto-auth/client/components/LoginButton.tsx +36 -53
  33. package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +17 -37
  34. package/plugins/crypto-auth/client/components/index.ts +1 -4
  35. package/plugins/crypto-auth/client/index.ts +1 -1
  36. package/plugins/crypto-auth/config/index.ts +34 -0
  37. package/plugins/crypto-auth/index.ts +84 -152
  38. package/plugins/crypto-auth/package.json +65 -64
  39. package/plugins/crypto-auth/server/AuthMiddleware.ts +19 -75
  40. package/plugins/crypto-auth/server/CryptoAuthService.ts +60 -167
  41. package/plugins/crypto-auth/server/index.ts +15 -2
  42. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +65 -0
  43. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +26 -0
  44. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +76 -0
  45. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +45 -0
  46. package/plugins/crypto-auth/server/middlewares/helpers.ts +140 -0
  47. package/plugins/crypto-auth/server/middlewares/index.ts +22 -0
  48. package/plugins/crypto-auth/server/middlewares.ts +19 -0
  49. package/test-crypto-auth.ts +101 -0
  50. package/plugins/crypto-auth/client/components/SessionInfo.tsx +0 -242
  51. package/plugins/crypto-auth/plugin.json +0 -29
@@ -33,11 +33,12 @@ export class PluginGenerator implements Generator {
33
33
 
34
34
  console.log(`\n✅ Generated plugin '${options.name}' with ${files.length} files`)
35
35
  console.log(`\n📦 Next steps:`)
36
- console.log(` 1. Edit plugins/${options.name}/plugin.json with your plugin metadata`)
37
- console.log(` 2. Implement your plugin logic in plugins/${options.name}/index.ts`)
38
- console.log(` 3. Add server-side code in plugins/${options.name}/server/ (optional)`)
39
- console.log(` 4. Add client-side code in plugins/${options.name}/client/ (optional)`)
40
- console.log(` 5. Run: bun run cli plugin:deps install`)
36
+ console.log(` 1. Configure plugin in plugins/${options.name}/config/index.ts`)
37
+ console.log(` 2. Set environment variables (optional): ${options.name.toUpperCase().replace(/-/g, '_')}_*`)
38
+ console.log(` 3. Implement your plugin logic in plugins/${options.name}/index.ts`)
39
+ console.log(` 4. Add server-side code in plugins/${options.name}/server/ (optional)`)
40
+ console.log(` 5. Add client-side code in plugins/${options.name}/client/ (optional)`)
41
+ console.log(` 6. Run: bun run dev`)
41
42
  }
42
43
 
43
44
  private getTemplate(templateName?: string): Template {
@@ -59,31 +60,80 @@ export class PluginGenerator implements Generator {
59
60
  description: 'Basic plugin template with essential files',
60
61
  files: [
61
62
  {
62
- path: 'plugins/{{name}}/plugin.json',
63
+ path: 'plugins/{{name}}/package.json',
63
64
  content: `{
64
- "name": "{{name}}",
65
+ "name": "@fluxstack/{{name}}-plugin",
65
66
  "version": "1.0.0",
66
67
  "description": "{{description}}",
67
- "author": "Your Name",
68
- "type": "fluxstack-plugin",
69
68
  "main": "index.ts",
69
+ "types": "index.ts",
70
+ "exports": {
71
+ ".": {
72
+ "import": "./index.ts",
73
+ "types": "./index.ts"
74
+ },
75
+ "./config": {
76
+ "import": "./config/index.ts",
77
+ "types": "./config/index.ts"
78
+ }
79
+ },
80
+ "keywords": [
81
+ "fluxstack",
82
+ "plugin",
83
+ "{{name}}",
84
+ "typescript"
85
+ ],
86
+ "author": "FluxStack Developer",
87
+ "license": "MIT",
88
+ "peerDependencies": {},
70
89
  "dependencies": {},
71
- "fluxstack": {
72
- "minVersion": "1.4.0"
90
+ "devDependencies": {
91
+ "typescript": "^5.0.0"
73
92
  },
74
- "hooks": {
75
- "setup": true,
76
- "onServerStart": false,
77
- "onRequest": false,
78
- "onResponse": false,
79
- "onError": false
93
+ "fluxstack": {
94
+ "plugin": true,
95
+ "version": "^1.0.0",
96
+ "hooks": [
97
+ "setup",
98
+ "onServerStart"
99
+ ],
100
+ "category": "utility",
101
+ "tags": ["{{name}}"]
80
102
  }
81
103
  }
104
+ `
105
+ },
106
+ {
107
+ path: 'plugins/{{name}}/config/index.ts',
108
+ content: `/**
109
+ * {{pascalName}} Plugin Configuration
110
+ * Declarative config using FluxStack config system
111
+ */
112
+
113
+ import { defineConfig, config } from '@/core/utils/config-schema'
114
+
115
+ const {{camelName}}ConfigSchema = {
116
+ // Enable/disable plugin
117
+ enabled: config.boolean('{{constantName}}_ENABLED', true),
118
+
119
+ // Add your configuration options here
120
+ // Example:
121
+ // apiKey: config.string('{{constantName}}_API_KEY', ''),
122
+ // timeout: config.number('{{constantName}}_TIMEOUT', 5000),
123
+ // debug: config.boolean('{{constantName}}_DEBUG', false),
124
+ } as const
125
+
126
+ export const {{camelName}}Config = defineConfig({{camelName}}ConfigSchema)
127
+
128
+ export type {{pascalName}}Config = typeof {{camelName}}Config
129
+ export default {{camelName}}Config
82
130
  `
83
131
  },
84
132
  {
85
133
  path: 'plugins/{{name}}/index.ts',
86
134
  content: `import type { FluxStackPlugin, PluginContext } from '@/core/types/plugin'
135
+ // ✅ Plugin imports its own configuration
136
+ import { {{camelName}}Config } from './config'
87
137
 
88
138
  /**
89
139
  * {{pascalName}} Plugin
@@ -97,6 +147,12 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
97
147
  * Setup hook - called when plugin is loaded
98
148
  */
99
149
  async setup(context: PluginContext): Promise<void> {
150
+ // Check if plugin is enabled
151
+ if (!{{camelName}}Config.enabled) {
152
+ context.logger.info(\`[{{name}}] Plugin disabled by configuration\`)
153
+ return
154
+ }
155
+
100
156
  console.log(\`[{{name}}] Plugin initialized\`)
101
157
 
102
158
  // Add your initialization logic here
@@ -107,6 +163,8 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
107
163
  * Server start hook - called when server starts
108
164
  */
109
165
  async onServerStart?(context: PluginContext): Promise<void> {
166
+ if (!{{camelName}}Config.enabled) return
167
+
110
168
  console.log(\`[{{name}}] Server started\`)
111
169
 
112
170
  // Add logic to run when server starts
@@ -116,6 +174,8 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
116
174
  * Request hook - called on each request
117
175
  */
118
176
  async onRequest?(context: PluginContext, request: Request): Promise<void> {
177
+ if (!{{camelName}}Config.enabled) return
178
+
119
179
  // Add request processing logic
120
180
  }
121
181
 
@@ -123,6 +183,8 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
123
183
  * Response hook - called on each response
124
184
  */
125
185
  async onResponse?(context: PluginContext, response: Response): Promise<void> {
186
+ if (!{{camelName}}Config.enabled) return
187
+
126
188
  // Add response processing logic
127
189
  }
128
190
 
@@ -156,17 +218,29 @@ This plugin is already in your FluxStack project. To use it:
156
218
  bun run cli plugin:deps install
157
219
  \`\`\`
158
220
 
221
+ ## Configuration
222
+
223
+ This plugin uses FluxStack's declarative configuration system. Configure it by editing \`config/index.ts\` or by setting environment variables:
224
+
225
+ \`\`\`bash
226
+ # Enable/disable plugin
227
+ {{constantName}}_ENABLED=true
228
+
229
+ # Add your environment variables here
230
+ # Example:
231
+ # {{constantName}}_API_KEY=your-api-key
232
+ # {{constantName}}_TIMEOUT=5000
233
+ \`\`\`
234
+
235
+ The plugin's configuration is located in \`plugins/{{name}}/config/index.ts\` and is self-contained, making the plugin fully portable.
236
+
159
237
  ## Usage
160
238
 
161
239
  \`\`\`typescript
162
240
  // The plugin is automatically loaded by FluxStack
163
- // Configure it in your app/server/index.ts if needed
241
+ // It imports its own configuration from ./config
164
242
  \`\`\`
165
243
 
166
- ## Configuration
167
-
168
- Add configuration options here.
169
-
170
244
  ## API
171
245
 
172
246
  Document your plugin's API here.
@@ -184,8 +258,8 @@ This plugin uses the following hooks:
184
258
 
185
259
  To modify this plugin:
186
260
 
187
- 1. Edit \`index.ts\` with your logic
188
- 2. Update \`plugin.json\` with metadata
261
+ 1. Edit \`config/index.ts\` to add configuration options
262
+ 2. Edit \`index.ts\` with your logic
189
263
  3. Test with: \`bun run dev\`
190
264
 
191
265
  ## License
@@ -204,7 +278,60 @@ MIT
204
278
  name: 'server-plugin',
205
279
  description: 'Plugin with server-side code',
206
280
  files: [
207
- ...basic.files,
281
+ {
282
+ path: 'plugins/{{name}}/package.json',
283
+ content: `{
284
+ "name": "@fluxstack/{{name}}-plugin",
285
+ "version": "1.0.0",
286
+ "description": "{{description}}",
287
+ "main": "index.ts",
288
+ "types": "index.ts",
289
+ "exports": {
290
+ ".": {
291
+ "import": "./index.ts",
292
+ "types": "./index.ts"
293
+ },
294
+ "./config": {
295
+ "import": "./config/index.ts",
296
+ "types": "./config/index.ts"
297
+ },
298
+ "./server": {
299
+ "import": "./server/index.ts",
300
+ "types": "./server/index.ts"
301
+ }
302
+ },
303
+ "keywords": [
304
+ "fluxstack",
305
+ "plugin",
306
+ "{{name}}",
307
+ "server",
308
+ "typescript"
309
+ ],
310
+ "author": "FluxStack Developer",
311
+ "license": "MIT",
312
+ "peerDependencies": {
313
+ "elysia": "^1.0.0"
314
+ },
315
+ "dependencies": {},
316
+ "devDependencies": {
317
+ "typescript": "^5.0.0"
318
+ },
319
+ "fluxstack": {
320
+ "plugin": true,
321
+ "version": "^1.0.0",
322
+ "hooks": [
323
+ "setup",
324
+ "onServerStart",
325
+ "onRequest",
326
+ "onResponse"
327
+ ],
328
+ "category": "utility",
329
+ "tags": ["{{name}}", "server"]
330
+ }
331
+ }
332
+ `
333
+ },
334
+ ...basic.files.slice(1), // Skip package.json from basic
208
335
  {
209
336
  path: 'plugins/{{name}}/server/index.ts',
210
337
  content: `/**
@@ -233,7 +360,65 @@ export const {{camelName}}Service = new {{pascalName}}Service()
233
360
  name: 'client-plugin',
234
361
  description: 'Plugin with client-side code',
235
362
  files: [
236
- ...basic.files,
363
+ {
364
+ path: 'plugins/{{name}}/package.json',
365
+ content: `{
366
+ "name": "@fluxstack/{{name}}-plugin",
367
+ "version": "1.0.0",
368
+ "description": "{{description}}",
369
+ "main": "index.ts",
370
+ "types": "index.ts",
371
+ "exports": {
372
+ ".": {
373
+ "import": "./index.ts",
374
+ "types": "./index.ts"
375
+ },
376
+ "./config": {
377
+ "import": "./config/index.ts",
378
+ "types": "./config/index.ts"
379
+ },
380
+ "./client": {
381
+ "import": "./client/index.ts",
382
+ "types": "./client/index.ts"
383
+ }
384
+ },
385
+ "keywords": [
386
+ "fluxstack",
387
+ "plugin",
388
+ "{{name}}",
389
+ "react",
390
+ "client",
391
+ "typescript"
392
+ ],
393
+ "author": "FluxStack Developer",
394
+ "license": "MIT",
395
+ "peerDependencies": {
396
+ "react": ">=16.8.0"
397
+ },
398
+ "peerDependenciesMeta": {
399
+ "react": {
400
+ "optional": true
401
+ }
402
+ },
403
+ "dependencies": {},
404
+ "devDependencies": {
405
+ "@types/react": "^18.0.0",
406
+ "typescript": "^5.0.0"
407
+ },
408
+ "fluxstack": {
409
+ "plugin": true,
410
+ "version": "^1.0.0",
411
+ "hooks": [
412
+ "setup",
413
+ "onServerStart"
414
+ ],
415
+ "category": "utility",
416
+ "tags": ["{{name}}", "client", "react"]
417
+ }
418
+ }
419
+ `
420
+ },
421
+ ...basic.files.slice(1), // Skip package.json from basic
237
422
  {
238
423
  path: 'plugins/{{name}}/client/index.ts',
239
424
  content: `/**
@@ -265,22 +450,127 @@ export const {{camelName}}Client = new {{pascalName}}Client()
265
450
  name: 'full-plugin',
266
451
  description: 'Complete plugin with server and client code',
267
452
  files: [
268
- ...basic.files,
269
- ...server.files.slice(basic.files.length), // Add server files
270
- ...client.files.slice(basic.files.length), // Add client files
453
+ {
454
+ path: 'plugins/{{name}}/package.json',
455
+ content: `{
456
+ "name": "@fluxstack/{{name}}-plugin",
457
+ "version": "1.0.0",
458
+ "description": "{{description}}",
459
+ "main": "index.ts",
460
+ "types": "index.ts",
461
+ "exports": {
462
+ ".": {
463
+ "import": "./index.ts",
464
+ "types": "./index.ts"
465
+ },
466
+ "./config": {
467
+ "import": "./config/index.ts",
468
+ "types": "./config/index.ts"
469
+ },
470
+ "./server": {
471
+ "import": "./server/index.ts",
472
+ "types": "./server/index.ts"
473
+ },
474
+ "./client": {
475
+ "import": "./client/index.ts",
476
+ "types": "./client/index.ts"
477
+ },
478
+ "./types": {
479
+ "import": "./types.ts",
480
+ "types": "./types.ts"
481
+ }
482
+ },
483
+ "keywords": [
484
+ "fluxstack",
485
+ "plugin",
486
+ "{{name}}",
487
+ "react",
488
+ "server",
489
+ "client",
490
+ "typescript"
491
+ ],
492
+ "author": "FluxStack Developer",
493
+ "license": "MIT",
494
+ "peerDependencies": {
495
+ "react": ">=16.8.0",
496
+ "elysia": "^1.0.0"
497
+ },
498
+ "peerDependenciesMeta": {
499
+ "react": {
500
+ "optional": true
501
+ }
502
+ },
503
+ "dependencies": {},
504
+ "devDependencies": {
505
+ "@types/react": "^18.0.0",
506
+ "typescript": "^5.0.0"
507
+ },
508
+ "fluxstack": {
509
+ "plugin": true,
510
+ "version": "^1.0.0",
511
+ "hooks": [
512
+ "setup",
513
+ "onServerStart",
514
+ "onRequest",
515
+ "onResponse",
516
+ "onError"
517
+ ],
518
+ "category": "utility",
519
+ "tags": ["{{name}}", "server", "client", "react"]
520
+ }
521
+ }
522
+ `
523
+ },
524
+ ...basic.files.slice(1), // Skip package.json from basic
525
+ {
526
+ path: 'plugins/{{name}}/server/index.ts',
527
+ content: `/**
528
+ * Server-side logic for {{pascalName}} plugin
529
+ */
530
+
531
+ export class {{pascalName}}Service {
532
+ async initialize() {
533
+ console.log(\`[{{name}}] Server service initialized\`)
534
+ }
535
+
536
+ // Add your server-side methods here
537
+ }
538
+
539
+ export const {{camelName}}Service = new {{pascalName}}Service()
540
+ `
541
+ },
542
+ {
543
+ path: 'plugins/{{name}}/client/index.ts',
544
+ content: `/**
545
+ * Client-side logic for {{pascalName}} plugin
546
+ */
547
+
548
+ export class {{pascalName}}Client {
549
+ initialize() {
550
+ console.log(\`[{{name}}] Client initialized\`)
551
+ }
552
+
553
+ // Add your client-side methods here
554
+ }
555
+
556
+ export const {{camelName}}Client = new {{pascalName}}Client()
557
+ `
558
+ },
271
559
  {
272
560
  path: 'plugins/{{name}}/types.ts',
273
561
  content: `/**
274
562
  * Type definitions for {{pascalName}} plugin
275
563
  */
276
564
 
277
- export interface {{pascalName}}Config {
278
- // Add your configuration types here
279
- enabled: boolean
280
- }
565
+ // Config types are exported from ./config/index.ts
566
+ // Import them like: import type { {{pascalName}}Config } from './config'
281
567
 
282
568
  export interface {{pascalName}}Options {
283
- // Add your options types here
569
+ // Add your runtime options types here
570
+ }
571
+
572
+ export interface {{pascalName}}Event {
573
+ // Add your event types here
284
574
  }
285
575
  `
286
576
  }
@@ -85,6 +85,7 @@ export class TemplateEngine {
85
85
  camelName: this.toCamelCase(options.name),
86
86
  pascalName: this.toPascalCase(options.name),
87
87
  snakeName: this.toSnakeCase(options.name),
88
+ constantName: this.toConstantCase(options.name), // SCREAMING_SNAKE_CASE
88
89
  timestamp: new Date().toISOString(),
89
90
  date: new Date().toLocaleDateString(),
90
91
  year: new Date().getFullYear(),
@@ -149,6 +150,10 @@ export class TemplateEngine {
149
150
  .replace(/[\s-]+/g, '_')
150
151
  .toLowerCase()
151
152
  }
153
+
154
+ private toConstantCase(str: string): string {
155
+ return this.toSnakeCase(str).toUpperCase()
156
+ }
152
157
  }
153
158
 
154
159
  export const templateEngine = new TemplateEngine()
@@ -10,14 +10,14 @@ export class CliPluginDiscovery {
10
10
  async discoverAndRegisterCommands(): Promise<void> {
11
11
  // 1. Load built-in plugins with CLI commands
12
12
  await this.loadBuiltInPlugins()
13
-
13
+
14
14
  // 2. Load local plugins from project
15
15
  await this.loadLocalPlugins()
16
16
  }
17
17
 
18
18
  private async loadBuiltInPlugins(): Promise<void> {
19
19
  const builtInPluginsDir = join(__dirname, '../plugins/built-in')
20
-
20
+
21
21
  if (!existsSync(builtInPluginsDir)) {
22
22
  return
23
23
  }
@@ -33,7 +33,7 @@ export class CliPluginDiscovery {
33
33
  const pluginPath = join(builtInPluginsDir, pluginName, 'index.ts')
34
34
  if (existsSync(pluginPath)) {
35
35
  const pluginModule = await import(pluginPath)
36
-
36
+
37
37
  if (pluginModule.commands) {
38
38
  for (const command of pluginModule.commands) {
39
39
  cliRegistry.register(command)
@@ -57,7 +57,7 @@ export class CliPluginDiscovery {
57
57
 
58
58
  private async loadLocalPlugins(): Promise<void> {
59
59
  const localPluginsDir = join(process.cwd(), 'plugins')
60
-
60
+
61
61
  if (!existsSync(localPluginsDir)) {
62
62
  return
63
63
  }
@@ -65,17 +65,18 @@ export class CliPluginDiscovery {
65
65
  try {
66
66
  const fs = await import('fs')
67
67
  const entries = fs.readdirSync(localPluginsDir, { withFileTypes: true })
68
-
68
+
69
69
  for (const entry of entries) {
70
+ // Buscar arquivos .ts/.js diretamente
70
71
  if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.js'))) {
71
72
  const pluginPath = join(localPluginsDir, entry.name)
72
-
73
+
73
74
  try {
74
75
  const pluginModule = await import(pluginPath)
75
76
  const plugin = pluginModule.default || Object.values(pluginModule).find(
76
77
  (exp: any) => exp && typeof exp === 'object' && exp.name && exp.commands
77
78
  ) as Plugin
78
-
79
+
79
80
  if (plugin && plugin.commands) {
80
81
  this.registerPluginCommands(plugin)
81
82
  }
@@ -83,6 +84,26 @@ export class CliPluginDiscovery {
83
84
  logger.debug(`Failed to load local plugin ${entry.name}:`, error)
84
85
  }
85
86
  }
87
+
88
+ // ✅ Buscar em subdiretórios (plugins/nome-plugin/index.ts)
89
+ if (entry.isDirectory()) {
90
+ const pluginIndexPath = join(localPluginsDir, entry.name, 'index.ts')
91
+
92
+ if (existsSync(pluginIndexPath)) {
93
+ try {
94
+ const pluginModule = await import(pluginIndexPath)
95
+ const plugin = pluginModule.default || Object.values(pluginModule).find(
96
+ (exp: any) => exp && typeof exp === 'object' && exp.name && exp.commands
97
+ ) as Plugin
98
+
99
+ if (plugin && plugin.commands) {
100
+ this.registerPluginCommands(plugin)
101
+ }
102
+ } catch (error) {
103
+ logger.debug(`Failed to load local plugin ${entry.name}:`, error)
104
+ }
105
+ }
106
+ }
86
107
  }
87
108
  } catch (error) {
88
109
  logger.debug('Failed to scan local plugins:', error)
@@ -103,9 +124,9 @@ export class CliPluginDiscovery {
103
124
  category: command.category || `Plugin: ${plugin.name}`,
104
125
  aliases: command.aliases?.map(alias => `${plugin.name}:${alias}`)
105
126
  }
106
-
127
+
107
128
  cliRegistry.register(prefixedCommand)
108
-
129
+
109
130
  // Also register without prefix if no conflict exists
110
131
  if (!cliRegistry.has(command.name)) {
111
132
  cliRegistry.register({
@@ -114,10 +135,10 @@ export class CliPluginDiscovery {
114
135
  })
115
136
  }
116
137
  }
117
-
138
+
118
139
  this.loadedPlugins.add(plugin.name)
119
140
  logger.debug(`Registered ${plugin.commands.length} CLI commands from plugin: ${plugin.name}`)
120
-
141
+
121
142
  } catch (error) {
122
143
  logger.error(`Failed to register CLI commands for plugin ${plugin.name}:`, error)
123
144
  }
@@ -128,4 +149,4 @@ export class CliPluginDiscovery {
128
149
  }
129
150
  }
130
151
 
131
- export const pluginDiscovery = new CliPluginDiscovery()
152
+ export const pluginDiscovery = new CliPluginDiscovery()
@@ -469,6 +469,16 @@ export class FluxStackFramework {
469
469
  }
470
470
  }
471
471
 
472
+ // Mount plugin routes if they have a plugin property
473
+ for (const pluginName of loadOrder) {
474
+ const plugin = this.pluginRegistry.get(pluginName)!
475
+
476
+ if ((plugin as any).plugin) {
477
+ this.app.use((plugin as any).plugin)
478
+ logger.debug(`Plugin '${pluginName}' routes mounted`)
479
+ }
480
+ }
481
+
472
482
  // Call onServerStart hooks
473
483
  for (const pluginName of loadOrder) {
474
484
  const plugin = this.pluginRegistry.get(pluginName)!