create-fluxstack 1.16.0 → 1.17.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 (119) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/app/client/src/App.tsx +8 -0
  3. package/app/client/src/live/AuthDemo.tsx +4 -4
  4. package/core/build/bundler.ts +40 -26
  5. package/core/build/flux-plugins-generator.ts +325 -325
  6. package/core/build/index.ts +92 -21
  7. package/core/cli/command-registry.ts +44 -46
  8. package/core/cli/commands/build.ts +11 -6
  9. package/core/cli/commands/create.ts +7 -5
  10. package/core/cli/commands/dev.ts +6 -5
  11. package/core/cli/commands/help.ts +3 -2
  12. package/core/cli/commands/make-plugin.ts +8 -7
  13. package/core/cli/commands/plugin-add.ts +60 -43
  14. package/core/cli/commands/plugin-deps.ts +73 -57
  15. package/core/cli/commands/plugin-list.ts +44 -41
  16. package/core/cli/commands/plugin-remove.ts +33 -22
  17. package/core/cli/generators/component.ts +770 -769
  18. package/core/cli/generators/controller.ts +9 -8
  19. package/core/cli/generators/index.ts +148 -146
  20. package/core/cli/generators/interactive.ts +228 -227
  21. package/core/cli/generators/plugin.ts +11 -10
  22. package/core/cli/generators/prompts.ts +83 -82
  23. package/core/cli/generators/route.ts +7 -6
  24. package/core/cli/generators/service.ts +10 -9
  25. package/core/cli/generators/template-engine.ts +2 -1
  26. package/core/cli/generators/types.ts +7 -7
  27. package/core/cli/generators/utils.ts +191 -191
  28. package/core/cli/index.ts +9 -8
  29. package/core/cli/plugin-discovery.ts +2 -2
  30. package/core/client/hooks/useAuth.ts +48 -48
  31. package/core/client/standalone.ts +18 -17
  32. package/core/client/state/createStore.ts +192 -192
  33. package/core/client/state/index.ts +14 -14
  34. package/core/config/index.ts +1 -0
  35. package/core/framework/client.ts +131 -131
  36. package/core/framework/index.ts +7 -7
  37. package/core/framework/server.ts +72 -112
  38. package/core/framework/types.ts +2 -2
  39. package/core/plugins/built-in/live-components/commands/create-live-component.ts +6 -3
  40. package/core/plugins/built-in/monitoring/index.ts +110 -68
  41. package/core/plugins/built-in/static/index.ts +2 -2
  42. package/core/plugins/built-in/swagger/index.ts +9 -9
  43. package/core/plugins/built-in/vite/index.ts +3 -3
  44. package/core/plugins/built-in/vite/vite-dev.ts +3 -3
  45. package/core/plugins/config.ts +50 -47
  46. package/core/plugins/discovery.ts +10 -4
  47. package/core/plugins/executor.ts +2 -2
  48. package/core/plugins/index.ts +206 -203
  49. package/core/plugins/manager.ts +21 -20
  50. package/core/plugins/registry.ts +76 -12
  51. package/core/plugins/types.ts +14 -14
  52. package/core/server/framework.ts +3 -189
  53. package/core/server/live/auto-generated-components.ts +11 -29
  54. package/core/server/live/index.ts +41 -31
  55. package/core/server/live/websocket-plugin.ts +11 -1
  56. package/core/server/middleware/elysia-helpers.ts +16 -15
  57. package/core/server/middleware/errorHandling.ts +14 -14
  58. package/core/server/middleware/index.ts +31 -31
  59. package/core/server/plugins/database.ts +181 -180
  60. package/core/server/plugins/static-files-plugin.ts +4 -3
  61. package/core/server/plugins/swagger.ts +11 -8
  62. package/core/server/rooms/RoomBroadcaster.ts +11 -10
  63. package/core/server/rooms/RoomSystem.ts +14 -11
  64. package/core/server/services/BaseService.ts +7 -7
  65. package/core/server/services/ServiceContainer.ts +5 -5
  66. package/core/server/services/index.ts +8 -8
  67. package/core/templates/create-project.ts +28 -27
  68. package/core/testing/index.ts +9 -9
  69. package/core/testing/setup.ts +73 -73
  70. package/core/types/api.ts +168 -168
  71. package/core/types/config.ts +5 -5
  72. package/core/types/index.ts +1 -1
  73. package/core/types/plugin.ts +2 -2
  74. package/core/types/types.ts +3 -3
  75. package/core/utils/build-logger.ts +324 -324
  76. package/core/utils/config-schema.ts +480 -480
  77. package/core/utils/env.ts +10 -8
  78. package/core/utils/errors/codes.ts +114 -114
  79. package/core/utils/errors/handlers.ts +30 -20
  80. package/core/utils/errors/index.ts +54 -46
  81. package/core/utils/errors/middleware.ts +113 -113
  82. package/core/utils/helpers.ts +19 -16
  83. package/core/utils/logger/colors.ts +114 -114
  84. package/core/utils/logger/config.ts +2 -2
  85. package/core/utils/logger/formatter.ts +82 -82
  86. package/core/utils/logger/group-logger.ts +101 -101
  87. package/core/utils/logger/index.ts +13 -3
  88. package/core/utils/logger/startup-banner.ts +2 -2
  89. package/core/utils/logger/winston-logger.ts +152 -152
  90. package/core/utils/monitoring/index.ts +211 -211
  91. package/core/utils/sync-version.ts +67 -66
  92. package/core/utils/version.ts +1 -1
  93. package/package.json +104 -100
  94. package/playwright-report/index.html +85 -0
  95. package/playwright.config.ts +31 -0
  96. package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
  97. package/plugins/crypto-auth/client/components/index.ts +11 -11
  98. package/plugins/crypto-auth/client/index.ts +11 -11
  99. package/plugins/crypto-auth/package.json +65 -65
  100. package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
  101. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +6 -5
  102. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +6 -5
  103. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +3 -3
  104. package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
  105. package/plugins/crypto-auth/server/middlewares.ts +19 -19
  106. package/vite.config.ts +13 -0
  107. package/app/client/.live-stubs/LiveAdminPanel.js +0 -5
  108. package/app/client/.live-stubs/LiveCounter.js +0 -9
  109. package/app/client/.live-stubs/LiveForm.js +0 -11
  110. package/app/client/.live-stubs/LiveLocalCounter.js +0 -8
  111. package/app/client/.live-stubs/LivePingPong.js +0 -10
  112. package/app/client/.live-stubs/LiveRoomChat.js +0 -11
  113. package/app/client/.live-stubs/LiveSharedCounter.js +0 -10
  114. package/app/client/.live-stubs/LiveUpload.js +0 -15
  115. package/app/server/live/register-components.ts +0 -19
  116. package/core/build/live-components-generator.ts +0 -321
  117. package/core/live/ComponentRegistry.ts +0 -403
  118. package/core/live/types.ts +0 -241
  119. package/workspace.json +0 -6
@@ -1,15 +1,17 @@
1
1
  import { Elysia } from "elysia"
2
2
  import type { FluxStackConfig, FluxStackContext } from "@core/types"
3
- import type { FluxStack, PluginContext, PluginUtils } from "@core/plugins/types"
3
+ import type { FluxStack, PluginContext, PluginUtils, PluginConfigSchema, PluginHook } from "@core/plugins/types"
4
4
  import { PluginRegistry } from "@core/plugins/registry"
5
5
  import { PluginManager } from "@core/plugins/manager"
6
6
  import { fluxStackConfig } from "@config"
7
7
  import { getEnvironmentInfo } from "@core/config"
8
- import { logger } from "@core/utils/logger"
8
+ import { logger, type Logger } from "@core/utils/logger"
9
9
  import { displayStartupBanner, type StartupInfo } from "@core/utils/logger/startup-banner"
10
10
  import { componentRegistry } from "@core/server/live"
11
11
  import { FluxStackError } from "@core/utils/errors"
12
12
  import { createTimer, formatBytes, isProduction, isDevelopment } from "@core/utils/helpers"
13
+ import { createHash } from "crypto"
14
+ import { createPluginUtils } from "@core/plugins/config"
13
15
  import type { Plugin } from "@core/plugins"
14
16
 
15
17
  export class FluxStackFramework {
@@ -20,6 +22,12 @@ export class FluxStackFramework {
20
22
  private pluginContext: PluginContext
21
23
  private isStarted: boolean = false
22
24
  private requestTimings: Map<string, number> = new Map()
25
+ private _originalStderrWrite?: typeof process.stderr.write
26
+
27
+ /** Access typed config from context (config is stored as unknown to avoid circular deps) */
28
+ private get cfg(): import('@config').FluxStackConfig {
29
+ return this.context.config as import('@config').FluxStackConfig
30
+ }
23
31
 
24
32
  /**
25
33
  * Helper to safely parse request.url which might be relative or absolute
@@ -59,7 +67,8 @@ export class FluxStackFramework {
59
67
 
60
68
  // Fallback: try to get from Bun's server socket (if available)
61
69
  // This is set by Bun when running in server mode
62
- const socketIP = (request as any).ip || (request as any).remoteAddress
70
+ const requestWithSocket = request as Request & { ip?: string; remoteAddress?: string }
71
+ const socketIP = requestWithSocket.ip || requestWithSocket.remoteAddress
63
72
  if (socketIP) {
64
73
  return socketIP
65
74
  }
@@ -96,58 +105,42 @@ export class FluxStackFramework {
96
105
  isDevelopment,
97
106
  getEnvironment: () => envInfo.name,
98
107
  createHash: (data: string) => {
99
- const crypto = require('crypto')
100
- return crypto.createHash('sha256').update(data).digest('hex')
108
+ return createHash('sha256').update(data).digest('hex')
101
109
  },
102
- deepMerge: (target: any, source: any) => {
110
+ deepMerge: (target: Record<string, unknown>, source: Record<string, unknown>) => {
103
111
  const result = { ...target }
104
112
  for (const key in source) {
105
113
  if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
106
- result[key] = pluginUtils.deepMerge(result[key] || {}, source[key])
114
+ result[key] = pluginUtils.deepMerge(result[key] as Record<string, unknown> || {}, source[key] as Record<string, unknown>)
107
115
  } else {
108
116
  result[key] = source[key]
109
117
  }
110
118
  }
111
119
  return result
112
120
  },
113
- validateSchema: (_data: any, _schema: any) => {
114
- // Simple validation - in a real implementation you'd use a proper schema validator
115
- try {
116
- // Basic validation logic
117
- return { valid: true, errors: [] }
118
- } catch (error) {
119
- return { valid: false, errors: [error instanceof Error ? error.message : 'Validation failed'] }
120
- }
121
+ validateSchema: (data: Record<string, unknown>, schema: PluginConfigSchema) => {
122
+ return createPluginUtils(logger).validateSchema(data, schema)
121
123
  }
122
124
  }
123
125
 
124
- // Create plugin-compatible logger interface
125
- interface PluginLogger {
126
- debug: (message: string, meta?: unknown) => void
127
- info: (message: string, meta?: unknown) => void
128
- warn: (message: string, meta?: unknown) => void
129
- error: (message: string, meta?: unknown) => void
130
- child: (context: Record<string, unknown>) => PluginLogger
131
- time: (label: string) => void
132
- timeEnd: (label: string) => void
133
- request: (method: string, path: string, status?: number, duration?: number, ip?: string) => void
134
- }
135
-
136
- const pluginLogger: PluginLogger = {
137
- debug: (message: string, meta?: unknown) => logger.debug(message, meta),
138
- info: (message: string, meta?: unknown) => logger.info(message, meta),
139
- warn: (message: string, meta?: unknown) => logger.warn(message, meta),
140
- error: (message: string, meta?: unknown) => logger.error(message, meta),
141
- child: (context: Record<string, unknown>) => pluginLogger,
126
+ // Create plugin-compatible logger
127
+ const pluginLogger: Logger = {
128
+ debug: (message: unknown, ...args: unknown[]) => logger.debug(message, ...args),
129
+ info: (message: unknown, ...args: unknown[]) => logger.info(message, ...args),
130
+ warn: (message: unknown, ...args: unknown[]) => logger.warn(message, ...args),
131
+ error: (message: unknown, ...args: unknown[]) => logger.error(message, ...args),
132
+ child: (_context: Record<string, unknown>) => pluginLogger,
142
133
  time: (label: string) => logger.time(label),
143
134
  timeEnd: (label: string) => logger.timeEnd(label),
144
135
  request: (method: string, path: string, status?: number, duration?: number, ip?: string) =>
145
- logger.request(method, path, status, duration, ip)
136
+ logger.request(method, path, status, duration, ip),
137
+ plugin: (pluginName: string, message: string, meta?: unknown) => logger.plugin(pluginName, message, meta),
138
+ framework: (message: string, meta?: unknown) => logger.framework(message, meta)
146
139
  }
147
140
 
148
141
  this.pluginContext = {
149
142
  config: fullConfig,
150
- logger: pluginLogger as any,
143
+ logger: pluginLogger,
151
144
  app: this.app,
152
145
  utils: pluginUtils
153
146
  }
@@ -155,7 +148,7 @@ export class FluxStackFramework {
155
148
  // Initialize plugin manager
156
149
  this.pluginManager = new PluginManager({
157
150
  config: fullConfig,
158
- logger: pluginLogger as any,
151
+ logger: pluginLogger,
159
152
  app: this.app
160
153
  })
161
154
 
@@ -169,11 +162,6 @@ export class FluxStackFramework {
169
162
  environment: envInfo.name,
170
163
  port: fullConfig.server.port
171
164
  })
172
-
173
- // Initialize automatic plugin discovery in background
174
- this.initializeAutomaticPlugins().catch(error => {
175
- logger.error('Failed to initialize automatic plugins', { error })
176
- })
177
165
  }
178
166
 
179
167
  private async initializeAutomaticPlugins() {
@@ -184,27 +172,16 @@ export class FluxStackFramework {
184
172
  const discoveredPlugins = this.pluginManager.getRegistry().getAll()
185
173
  for (const plugin of discoveredPlugins) {
186
174
  if (!this.pluginRegistry.has(plugin.name)) {
187
- // Register in main registry (synchronously, will call setup in start())
188
- (this.pluginRegistry as any).plugins.set(plugin.name, plugin)
189
- if (plugin.dependencies) {
190
- (this.pluginRegistry as any).dependencies.set(plugin.name, plugin.dependencies)
191
- }
175
+ this.pluginRegistry.registerSync(plugin)
192
176
  }
193
177
  }
194
178
 
195
- // Update load order
196
- try {
197
- (this.pluginRegistry as any).updateLoadOrder()
198
- } catch (error) {
199
- // Fallback: create basic load order
200
- const plugins = (this.pluginRegistry as any).plugins as Map<string, FluxStack.Plugin>
201
- const loadOrder = Array.from(plugins.keys())
202
- ;(this.pluginRegistry as any).loadOrder = loadOrder
203
- }
179
+ // Refresh load order (falls back to insertion-order on failure)
180
+ this.pluginRegistry.refreshLoadOrder()
204
181
 
205
182
  // Execute onConfigLoad hooks for all plugins
206
183
  const configLoadContext = {
207
- config: this.context.config,
184
+ config: this.context.config as import('@config').FluxStackConfig,
208
185
  envVars: process.env as Record<string, string | undefined>,
209
186
  configPath: undefined
210
187
  }
@@ -235,13 +212,13 @@ export class FluxStackFramework {
235
212
  }
236
213
 
237
214
  private setupCors() {
238
- const cors = this.context.config.cors
215
+ const cors = this.cfg.cors
239
216
 
240
217
  this.app
241
218
  .onRequest(({ set }) => {
242
- set.headers["Access-Control-Allow-Origin"] = cors.origins.join(", ") || "*"
243
- set.headers["Access-Control-Allow-Methods"] = cors.methods.join(", ") || "*"
244
- set.headers["Access-Control-Allow-Headers"] = cors.headers.join(", ") || "*"
219
+ set.headers["Access-Control-Allow-Origin"] = (cors.origins ?? []).join(", ") || "*"
220
+ set.headers["Access-Control-Allow-Methods"] = (cors.methods ?? []).join(", ") || "*"
221
+ set.headers["Access-Control-Allow-Headers"] = (cors.headers ?? []).join(", ") || "*"
245
222
  if (cors.credentials) {
246
223
  set.headers["Access-Control-Allow-Credentials"] = "true"
247
224
  }
@@ -258,7 +235,7 @@ export class FluxStackFramework {
258
235
  const url = this.parseRequestURL(request)
259
236
 
260
237
  // Handle API routes
261
- if (url.pathname.startsWith(this.context.config.server.apiPrefix)) {
238
+ if (url.pathname.startsWith(this.cfg.server.apiPrefix)) {
262
239
  set.status = 200
263
240
  set.headers['Content-Type'] = 'application/json'
264
241
  set.headers['Content-Length'] = '0'
@@ -270,7 +247,6 @@ export class FluxStackFramework {
270
247
  if (isStatic) {
271
248
  set.status = 200
272
249
  set.headers['Content-Type'] = 'text/html'
273
- set.headers['Content-Length'] = '478' // approximate size of index.html
274
250
  set.headers['Cache-Control'] = 'no-cache'
275
251
  return ""
276
252
  }
@@ -322,7 +298,7 @@ export class FluxStackFramework {
322
298
  }
323
299
 
324
300
  // Store reference to restore original behavior if needed
325
- ; (this as any)._originalStderrWrite = originalStderrWrite
301
+ this._originalStderrWrite = originalStderrWrite
326
302
  }
327
303
 
328
304
  private setupHooks() {
@@ -431,7 +407,7 @@ export class FluxStackFramework {
431
407
  query: Object.fromEntries(url.searchParams.entries()),
432
408
  params: {},
433
409
  response: currentResponse,
434
- statusCode: Number((currentResponse as any)?.status || set.status || 200),
410
+ statusCode: Number((currentResponse instanceof Response ? currentResponse.status : undefined) || set.status || 200),
435
411
  duration,
436
412
  startTime
437
413
  }
@@ -464,7 +440,7 @@ export class FluxStackFramework {
464
440
  }
465
441
 
466
442
  // Log the request automatically (if not disabled in config)
467
- if (this.context.config.server.enableRequestLogging !== false) {
443
+ if (this.cfg.server.enableRequestLogging !== false) {
468
444
  // Ensure status is always a number (HTTP status code)
469
445
  const status = typeof responseContext.statusCode === 'number'
470
446
  ? responseContext.statusCode
@@ -533,17 +509,17 @@ export class FluxStackFramework {
533
509
  })
534
510
  }
535
511
 
536
- private async executePluginHooks(hookName: string, context: any): Promise<void> {
512
+ private async executePluginHooks(hookName: PluginHook, context: unknown): Promise<void> {
537
513
  const loadOrder = this.pluginRegistry.getLoadOrder()
538
514
 
539
515
  for (const pluginName of loadOrder) {
540
516
  const plugin = this.pluginRegistry.get(pluginName)
541
517
  if (!plugin) continue
542
518
 
543
- const hookFn = (plugin as any)[hookName]
519
+ const hookFn = plugin[hookName]
544
520
  if (typeof hookFn === 'function') {
545
521
  try {
546
- await hookFn(context)
522
+ await (hookFn as Function)(context)
547
523
  } catch (error) {
548
524
  const err = error instanceof Error ? error : new Error(String(error))
549
525
  logger.error(`Plugin '${pluginName}' ${hookName} hook failed`, {
@@ -566,8 +542,8 @@ export class FluxStackFramework {
566
542
  const otherPlugin = this.pluginRegistry.get(otherPluginName)
567
543
  if (!otherPlugin) continue
568
544
 
569
- const hookFn = (otherPlugin as any).onPluginError
570
- if (typeof hookFn === 'function') {
545
+ const hookFn = otherPlugin.onPluginError
546
+ if (hookFn && typeof hookFn === 'function') {
571
547
  try {
572
548
  await hookFn({
573
549
  pluginName,
@@ -584,17 +560,17 @@ export class FluxStackFramework {
584
560
  }
585
561
  }
586
562
 
587
- private async executePluginBeforeRouteHooks(requestContext: any): Promise<Response | null> {
563
+ private async executePluginBeforeRouteHooks(requestContext: { handled?: boolean; response?: Response; [key: string]: unknown }): Promise<Response | null> {
588
564
  const loadOrder = this.pluginRegistry.getLoadOrder()
589
565
 
590
566
  for (const pluginName of loadOrder) {
591
567
  const plugin = this.pluginRegistry.get(pluginName)
592
568
  if (!plugin) continue
593
569
 
594
- const onBeforeRouteFn = (plugin as any).onBeforeRoute
595
- if (typeof onBeforeRouteFn === 'function') {
570
+ const onBeforeRouteFn = plugin.onBeforeRoute
571
+ if (onBeforeRouteFn && typeof onBeforeRouteFn === 'function') {
596
572
  try {
597
- await onBeforeRouteFn(requestContext)
573
+ await onBeforeRouteFn(requestContext as unknown as import('@core/plugins/types').RequestContext)
598
574
 
599
575
  // If this plugin handled the request, return the response
600
576
  if (requestContext.handled && requestContext.response) {
@@ -611,17 +587,17 @@ export class FluxStackFramework {
611
587
  return null
612
588
  }
613
589
 
614
- private async executePluginErrorHooks(errorContext: any): Promise<Response | null> {
590
+ private async executePluginErrorHooks(errorContext: { handled?: boolean; error: Error; request: Request; [key: string]: unknown }): Promise<Response | null> {
615
591
  const loadOrder = this.pluginRegistry.getLoadOrder()
616
592
 
617
593
  for (const pluginName of loadOrder) {
618
594
  const plugin = this.pluginRegistry.get(pluginName)
619
595
  if (!plugin) continue
620
596
 
621
- const onErrorFn = (plugin as any).onError
622
- if (typeof onErrorFn === 'function') {
597
+ const onErrorFn = plugin.onError
598
+ if (onErrorFn && typeof onErrorFn === 'function') {
623
599
  try {
624
- await onErrorFn(errorContext)
600
+ await onErrorFn(errorContext as unknown as import('@core/plugins/types').ErrorContext)
625
601
 
626
602
  // If this plugin handled the error, check if it provides a response
627
603
  if (errorContext.handled) {
@@ -644,8 +620,8 @@ export class FluxStackFramework {
644
620
  return null
645
621
  }
646
622
 
647
- private async handleViteProxy(errorContext: any): Promise<Response> {
648
- const vitePort = this.context.config.client?.port || 5173
623
+ private async handleViteProxy(errorContext: { request: Request; method?: string; headers?: Record<string, string> }): Promise<Response> {
624
+ const vitePort = this.cfg.client?.port || 5173
649
625
  const url = this.parseRequestURL(errorContext.request)
650
626
 
651
627
  try {
@@ -677,29 +653,7 @@ export class FluxStackFramework {
677
653
 
678
654
  use(plugin: Plugin) {
679
655
  try {
680
- // Use the registry's public register method, but don't await it since we need sync operation
681
- if (this.pluginRegistry.has(plugin.name)) {
682
- throw new Error(`Plugin '${plugin.name}' is already registered`)
683
- }
684
-
685
- // Store plugin without calling setup - setup will be called in start()
686
- // We need to manually set the plugin since register() is async but we need sync
687
- (this.pluginRegistry as any).plugins.set(plugin.name, plugin)
688
-
689
- // Update dependencies tracking
690
- if ((plugin as FluxStack.Plugin).dependencies) {
691
- (this.pluginRegistry as any).dependencies.set(plugin.name, (plugin as FluxStack.Plugin).dependencies)
692
- }
693
-
694
- // Update load order by calling the private method
695
- try {
696
- (this.pluginRegistry as any).updateLoadOrder()
697
- } catch (error) {
698
- // Fallback: create basic load order
699
- const plugins = (this.pluginRegistry as any).plugins as Map<string, Plugin>
700
- const loadOrder = Array.from(plugins.keys())
701
- ; (this.pluginRegistry as any).loadOrder = loadOrder
702
- }
656
+ this.pluginRegistry.registerSync(plugin as FluxStack.Plugin)
703
657
 
704
658
  logger.debug(`Plugin '${plugin.name}' registered`, {
705
659
  version: (plugin as FluxStack.Plugin).version,
@@ -712,7 +666,7 @@ export class FluxStackFramework {
712
666
  }
713
667
  }
714
668
 
715
- routes(routeModule: any) {
669
+ routes(routeModule: Elysia) {
716
670
  this.app.use(routeModule)
717
671
  return this
718
672
  }
@@ -724,8 +678,13 @@ export class FluxStackFramework {
724
678
  }
725
679
 
726
680
  try {
681
+ // Initialize automatic plugins before anything else
682
+ // This was previously fire-and-forget in the constructor, causing a race condition
683
+ // where listen() could be called before plugin discovery finished (issue #75)
684
+ await this.initializeAutomaticPlugins()
685
+
727
686
  // Validate plugin dependencies before starting
728
- const plugins = (this.pluginRegistry as any).plugins as Map<string, FluxStack.Plugin>
687
+ const plugins = this.pluginRegistry.getPluginsMap()
729
688
  for (const [pluginName, plugin] of plugins) {
730
689
  if (plugin.dependencies) {
731
690
  for (const depName of plugin.dependencies) {
@@ -762,8 +721,9 @@ export class FluxStackFramework {
762
721
  for (const pluginName of loadOrder) {
763
722
  const plugin = this.pluginRegistry.get(pluginName)!
764
723
 
765
- if ((plugin as any).plugin) {
766
- this.app.use((plugin as any).plugin)
724
+ const pluginWithRoutes = plugin as FluxStack.Plugin & { plugin?: Elysia }
725
+ if (pluginWithRoutes.plugin) {
726
+ this.app.use(pluginWithRoutes.plugin)
767
727
  logger.debug(`Plugin '${pluginName}' routes mounted`)
768
728
  }
769
729
  }
@@ -849,21 +809,21 @@ export class FluxStackFramework {
849
809
  // Start the framework (load plugins)
850
810
  await this.start()
851
811
 
852
- const port = this.context.config.server.port
853
- const apiPrefix = this.context.config.server.apiPrefix
812
+ const port = this.cfg.server.port
813
+ const apiPrefix = this.cfg.server.apiPrefix
854
814
 
855
815
  this.app.listen(port, () => {
856
- const showBanner = this.context.config.server.showBanner !== false // default: true
816
+ const showBanner = this.cfg.server.showBanner !== false // default: true
857
817
  const vitePluginActive = this.pluginRegistry.has('vite')
858
818
 
859
819
  // Prepare startup info for banner or callback
860
820
  const startupInfo: StartupInfo = {
861
821
  port,
862
- host: this.context.config.server.host || 'localhost',
822
+ host: this.cfg.server.host || 'localhost',
863
823
  apiPrefix,
864
824
  environment: this.context.environment,
865
825
  pluginCount: this.pluginRegistry.getAll().length,
866
- vitePort: this.context.config.client?.port,
826
+ vitePort: this.cfg.client?.port,
867
827
  viteEmbedded: vitePluginActive, // Vite is embedded when plugin is active
868
828
  swaggerPath: '/swagger', // TODO: Get from swagger plugin config
869
829
  liveComponents: componentRegistry.getRegisteredComponentNames()
@@ -42,7 +42,7 @@ export interface RouteDefinition {
42
42
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD'
43
43
  path: string
44
44
  handler: Function
45
- schema?: any
45
+ schema?: unknown
46
46
  middleware?: Function[]
47
47
  description?: string
48
48
  tags?: string[]
@@ -57,7 +57,7 @@ export interface MiddlewareDefinition {
57
57
 
58
58
  export interface ServiceDefinition {
59
59
  name: string
60
- instance: any
60
+ instance: unknown
61
61
  dependencies?: string[]
62
62
  singleton?: boolean
63
63
  }
@@ -218,7 +218,7 @@ export function ${name}Demo() {
218
218
  <input {...form.$field('email', { syncOn: 'change', debounce: 500 })} type="email" placeholder="Email" className="w-full px-3 py-2 border rounded-lg" />
219
219
  <textarea {...form.$field('message', { syncOn: 'blur' })} placeholder="Mensagem" rows={3} className="w-full px-3 py-2 border rounded-lg" />
220
220
  <div className="flex gap-2">
221
- <button onClick={async () => { try { await form.$sync(); await form.submit() } catch (e: any) { alert(e.message) }}} className="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg">Enviar</button>
221
+ <button onClick={async () => { try { await form.$sync(); await form.submit() } catch (e) { alert((e as Error).message) }}} className="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg">Enviar</button>
222
222
  <button onClick={() => form.reset()} className="px-4 py-2 bg-gray-500 text-white rounded-lg">Limpar</button>
223
223
  </div>
224
224
  </div>
@@ -327,8 +327,11 @@ export const createLiveComponentCommand: CliCommand = {
327
327
  { name: "force", short: "f", description: "Overwrite", type: "boolean" }
328
328
  ],
329
329
  handler: async (args, options, context) => {
330
- const [name] = args;
331
- const { type = 'basic', 'no-client': noClient, room, force } = options;
330
+ const [rawName] = args;
331
+ const name = rawName as string;
332
+ const { type: rawType = 'basic', 'no-client': noClient, room: rawRoom, force } = options;
333
+ const type = rawType as string;
334
+ const room = rawRoom as string | undefined;
332
335
 
333
336
  if (!name || !/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
334
337
  context.logger.error("❌ Nome inválido. Use PascalCase (ex: MeuComponente)");