create-fluxstack 1.4.0 → 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 (55) 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/index.ts +5 -2
  16. package/core/cli/generators/plugin.ts +580 -0
  17. package/core/cli/generators/template-engine.ts +5 -0
  18. package/core/cli/index.ts +88 -3
  19. package/core/cli/plugin-discovery.ts +33 -12
  20. package/core/framework/server.ts +10 -0
  21. package/core/plugins/dependency-manager.ts +89 -22
  22. package/core/plugins/index.ts +4 -0
  23. package/core/plugins/manager.ts +3 -2
  24. package/core/plugins/module-resolver.ts +216 -0
  25. package/core/plugins/registry.ts +28 -1
  26. package/core/utils/logger/index.ts +4 -0
  27. package/core/utils/version.ts +1 -1
  28. package/create-fluxstack.ts +117 -8
  29. package/fluxstack.config.ts +253 -114
  30. package/package.json +117 -117
  31. package/plugins/crypto-auth/README.md +722 -172
  32. package/plugins/crypto-auth/ai-context.md +1282 -0
  33. package/plugins/crypto-auth/cli/make-protected-route.command.ts +383 -0
  34. package/plugins/crypto-auth/client/CryptoAuthClient.ts +136 -159
  35. package/plugins/crypto-auth/client/components/AuthProvider.tsx +35 -94
  36. package/plugins/crypto-auth/client/components/LoginButton.tsx +36 -53
  37. package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +17 -37
  38. package/plugins/crypto-auth/client/components/index.ts +1 -4
  39. package/plugins/crypto-auth/client/index.ts +1 -1
  40. package/plugins/crypto-auth/config/index.ts +34 -0
  41. package/plugins/crypto-auth/index.ts +84 -152
  42. package/plugins/crypto-auth/package.json +65 -64
  43. package/plugins/crypto-auth/server/AuthMiddleware.ts +19 -75
  44. package/plugins/crypto-auth/server/CryptoAuthService.ts +60 -167
  45. package/plugins/crypto-auth/server/index.ts +15 -2
  46. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +65 -0
  47. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +26 -0
  48. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +76 -0
  49. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +45 -0
  50. package/plugins/crypto-auth/server/middlewares/helpers.ts +140 -0
  51. package/plugins/crypto-auth/server/middlewares/index.ts +22 -0
  52. package/plugins/crypto-auth/server/middlewares.ts +19 -0
  53. package/test-crypto-auth.ts +101 -0
  54. package/plugins/crypto-auth/client/components/SessionInfo.tsx +0 -242
  55. package/plugins/crypto-auth/plugin.json +0 -29
@@ -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)!
@@ -136,6 +136,8 @@ export class PluginDependencyManager {
136
136
 
137
137
  /**
138
138
  * Instalar dependências de plugins
139
+ * NOVA ESTRATÉGIA: Instala no node_modules local do plugin primeiro,
140
+ * com fallback para o projeto principal
139
141
  */
140
142
  async installPluginDependencies(resolutions: DependencyResolution[]): Promise<void> {
141
143
  if (!this.config.autoInstall) {
@@ -143,44 +145,109 @@ export class PluginDependencyManager {
143
145
  return
144
146
  }
145
147
 
146
- const toInstall: PluginDependency[] = []
147
- const conflicts: DependencyConflict[] = []
148
-
149
- // Coletar todas as dependências e conflitos
148
+ // Instalar dependências para cada plugin individualmente
150
149
  for (const resolution of resolutions) {
151
- toInstall.push(...resolution.dependencies)
152
- conflicts.push(...resolution.conflicts)
153
- }
150
+ if (resolution.dependencies.length === 0) continue
151
+
152
+ const pluginPath = this.findPluginDirectory(resolution.plugin)
153
+ if (!pluginPath) {
154
+ this.logger?.warn(`Não foi possível encontrar diretório do plugin '${resolution.plugin}'`)
155
+ continue
156
+ }
154
157
 
155
- // Resolver conflitos primeiro
156
- if (conflicts.length > 0) {
157
- await this.resolveConflicts(conflicts)
158
+ this.logger?.debug(`📦 Instalando dependências localmente para plugin '${resolution.plugin}'`, {
159
+ plugin: resolution.plugin,
160
+ path: pluginPath,
161
+ dependencies: resolution.dependencies.length
162
+ })
163
+
164
+ try {
165
+ // Instalar APENAS no node_modules local do plugin
166
+ await this.installPluginDependenciesLocally(pluginPath, resolution.dependencies)
167
+
168
+ this.logger?.debug(`✅ Dependências do plugin '${resolution.plugin}' instaladas localmente`)
169
+ } catch (error) {
170
+ this.logger?.error(`❌ Erro ao instalar dependências do plugin '${resolution.plugin}'`, { error })
171
+ // Continuar com outros plugins
172
+ }
158
173
  }
174
+ }
175
+
176
+ /**
177
+ * Instalar dependências no diretório local do plugin
178
+ */
179
+ private async installPluginDependenciesLocally(pluginPath: string, dependencies: PluginDependency[]): Promise<void> {
180
+ if (dependencies.length === 0) return
181
+
182
+ const regularDeps = dependencies.filter(d => d.type === 'dependency')
183
+ const peerDeps = dependencies.filter(d => d.type === 'peerDependency' && !d.optional)
184
+
185
+ const allDeps = [...regularDeps, ...peerDeps]
186
+ if (allDeps.length === 0) return
159
187
 
160
- // Filtrar dependências que já estão instaladas
161
- const needsInstallation = toInstall.filter(dep => {
162
- const installed = this.installedDependencies.get(dep.name)
163
- return !installed || !this.isVersionCompatible(installed, dep.version)
188
+ // Verificar quais dependências já estão instaladas localmente
189
+ const toInstall = allDeps.filter(dep => {
190
+ const depPath = join(pluginPath, 'node_modules', dep.name, 'package.json')
191
+ if (!existsSync(depPath)) {
192
+ return true // Precisa instalar
193
+ }
194
+
195
+ try {
196
+ const installedPkg = JSON.parse(readFileSync(depPath, 'utf-8'))
197
+ const installedVersion = installedPkg.version
198
+
199
+ // Verificar se a versão é compatível
200
+ if (!this.isVersionCompatible(installedVersion, dep.version)) {
201
+ this.logger?.debug(`📦 Dependência '${dep.name}' está desatualizada (${installedVersion} → ${dep.version})`)
202
+ return true // Precisa atualizar
203
+ }
204
+
205
+ return false // Já está instalado corretamente
206
+ } catch (error) {
207
+ return true // Erro ao ler, melhor reinstalar
208
+ }
164
209
  })
165
210
 
166
- if (needsInstallation.length === 0) {
167
- this.logger?.debug('Todas as dependências de plugins já estão instaladas')
211
+ if (toInstall.length === 0) {
212
+ this.logger?.debug(`✅ Todas as dependências do plugin já estão instaladas`)
168
213
  return
169
214
  }
170
215
 
171
- this.logger?.debug(`Instalando ${needsInstallation.length} dependências de plugins`, {
172
- dependencies: needsInstallation.map(d => `${d.name}@${d.version}`)
173
- })
216
+ const packages = toInstall.map(d => `${d.name}@${d.version}`).join(' ')
217
+ const command = this.getInstallCommand(packages, false)
218
+
219
+ this.logger?.debug(`🔧 Instalando ${toInstall.length} dependência(s): ${command}`, { cwd: pluginPath })
174
220
 
175
221
  try {
176
- await this.installDependencies(needsInstallation)
177
- this.logger?.debug('Dependências de plugins instaladas com sucesso')
222
+ execSync(command, {
223
+ cwd: pluginPath,
224
+ stdio: 'inherit'
225
+ })
226
+ this.logger?.debug(`✅ Pacotes instalados localmente em ${pluginPath}`)
178
227
  } catch (error) {
179
- this.logger?.error('Erro ao instalar dependências de plugins', { error })
228
+ this.logger?.error(`❌ Falha ao instalar dependências localmente`, { error, pluginPath })
180
229
  throw error
181
230
  }
182
231
  }
183
232
 
233
+ /**
234
+ * Encontrar diretório de um plugin pelo nome
235
+ */
236
+ private findPluginDirectory(pluginName: string): string | null {
237
+ const possiblePaths = [
238
+ `plugins/${pluginName}`,
239
+ `core/plugins/built-in/${pluginName}`
240
+ ]
241
+
242
+ for (const path of possiblePaths) {
243
+ if (existsSync(path)) {
244
+ return resolve(path)
245
+ }
246
+ }
247
+
248
+ return null
249
+ }
250
+
184
251
  /**
185
252
  * Detectar conflitos de versão
186
253
  */
@@ -54,6 +54,10 @@ export {
54
54
  } from './manager'
55
55
  export type { PluginManagerConfig } from './manager'
56
56
 
57
+ // Module resolver for plugins
58
+ export { PluginModuleResolver } from './module-resolver'
59
+ export type { ModuleResolverConfig } from './module-resolver'
60
+
57
61
  // Plugin executor
58
62
  export {
59
63
  PluginExecutor,
@@ -19,7 +19,7 @@ import type {
19
19
 
20
20
  type Plugin = FluxStack.Plugin
21
21
  import type { FluxStackConfig } from "../config/schema"
22
- import type { Logger } from "../utils/logger/index"
22
+ import type { Logger } from "../utils/logger"
23
23
  import { PluginRegistry } from "./registry"
24
24
  import { createPluginUtils } from "./config"
25
25
  import { FluxStackError } from "../utils/errors"
@@ -422,6 +422,7 @@ export class PluginManager extends EventEmitter {
422
422
  // Try to use auto-generated registry for external plugins (if available from build)
423
423
  let externalResults: any[] = []
424
424
  try {
425
+ // @ts-expect-error - auto-registry is generated during build, may not exist in dev
425
426
  const autoRegistryModule = await import('./auto-registry')
426
427
  if (autoRegistryModule.discoveredPlugins && autoRegistryModule.registerDiscoveredPlugins) {
427
428
  this.logger.debug('🚀 Using auto-generated external plugins registry')
@@ -432,7 +433,7 @@ export class PluginManager extends EventEmitter {
432
433
  }))
433
434
  }
434
435
  } catch (error) {
435
- this.logger.debug('Auto-generated external plugins registry not found, falling back to discovery', { error: error.message })
436
+ this.logger.debug('Auto-generated external plugins registry not found, falling back to discovery', { error: (error as Error).message })
436
437
 
437
438
  // Fallback to runtime discovery for external plugins
438
439
  this.logger.debug('Discovering external plugins in directory: plugins')
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Module Resolver para Plugins
3
+ * Implementa resolução em cascata: plugin local → projeto principal
4
+ */
5
+
6
+ import { existsSync } from 'fs'
7
+ import { join, resolve } from 'path'
8
+ import type { Logger } from '../utils/logger'
9
+
10
+ export interface ModuleResolverConfig {
11
+ projectRoot: string
12
+ logger?: Logger
13
+ }
14
+
15
+ export class PluginModuleResolver {
16
+ private config: ModuleResolverConfig
17
+ private logger?: Logger
18
+ private resolveCache: Map<string, string> = new Map()
19
+
20
+ constructor(config: ModuleResolverConfig) {
21
+ this.config = config
22
+ this.logger = config.logger
23
+ }
24
+
25
+ /**
26
+ * Resolve um módulo com estratégia em cascata:
27
+ * 1. node_modules local do plugin
28
+ * 2. node_modules do projeto principal
29
+ */
30
+ resolveModule(moduleName: string, pluginPath: string): string | null {
31
+ const cacheKey = `${pluginPath}::${moduleName}`
32
+
33
+ // Verificar cache
34
+ if (this.resolveCache.has(cacheKey)) {
35
+ return this.resolveCache.get(cacheKey)!
36
+ }
37
+
38
+ this.logger?.debug(`Resolvendo módulo '${moduleName}' para plugin em '${pluginPath}'`)
39
+
40
+ // 1. Tentar no node_modules local do plugin
41
+ const localPath = this.tryResolveLocal(moduleName, pluginPath)
42
+ if (localPath) {
43
+ this.logger?.debug(`✅ Módulo '${moduleName}' encontrado localmente: ${localPath}`)
44
+ this.resolveCache.set(cacheKey, localPath)
45
+ return localPath
46
+ }
47
+
48
+ // 2. Tentar no node_modules do projeto principal
49
+ const projectPath = this.tryResolveProject(moduleName)
50
+ if (projectPath) {
51
+ this.logger?.debug(`✅ Módulo '${moduleName}' encontrado no projeto: ${projectPath}`)
52
+ this.resolveCache.set(cacheKey, projectPath)
53
+ return projectPath
54
+ }
55
+
56
+ this.logger?.warn(`❌ Módulo '${moduleName}' não encontrado em nenhum contexto`)
57
+ return null
58
+ }
59
+
60
+ /**
61
+ * Tenta resolver no node_modules local do plugin
62
+ */
63
+ private tryResolveLocal(moduleName: string, pluginPath: string): string | null {
64
+ const pluginDir = resolve(pluginPath)
65
+ const localNodeModules = join(pluginDir, 'node_modules', moduleName)
66
+
67
+ if (existsSync(localNodeModules)) {
68
+ // Verificar se tem package.json para pegar o entry point
69
+ const packageJsonPath = join(localNodeModules, 'package.json')
70
+ if (existsSync(packageJsonPath)) {
71
+ try {
72
+ const pkg = require(packageJsonPath)
73
+ const entry = pkg.module || pkg.main || 'index.js'
74
+ const entryPath = join(localNodeModules, entry)
75
+
76
+ if (existsSync(entryPath)) {
77
+ return entryPath
78
+ }
79
+ } catch (error) {
80
+ this.logger?.debug(`Erro ao ler package.json de '${moduleName}'`, { error })
81
+ }
82
+ }
83
+
84
+ // Fallback: tentar index.js/index.ts
85
+ const indexJs = join(localNodeModules, 'index.js')
86
+ const indexTs = join(localNodeModules, 'index.ts')
87
+
88
+ if (existsSync(indexJs)) return indexJs
89
+ if (existsSync(indexTs)) return indexTs
90
+
91
+ return localNodeModules
92
+ }
93
+
94
+ return null
95
+ }
96
+
97
+ /**
98
+ * Tenta resolver no node_modules do projeto principal
99
+ */
100
+ private tryResolveProject(moduleName: string): string | null {
101
+ const projectNodeModules = join(this.config.projectRoot, 'node_modules', moduleName)
102
+
103
+ if (existsSync(projectNodeModules)) {
104
+ // Verificar se tem package.json para pegar o entry point
105
+ const packageJsonPath = join(projectNodeModules, 'package.json')
106
+ if (existsSync(packageJsonPath)) {
107
+ try {
108
+ const pkg = require(packageJsonPath)
109
+ const entry = pkg.module || pkg.main || 'index.js'
110
+ const entryPath = join(projectNodeModules, entry)
111
+
112
+ if (existsSync(entryPath)) {
113
+ return entryPath
114
+ }
115
+ } catch (error) {
116
+ this.logger?.debug(`Erro ao ler package.json de '${moduleName}'`, { error })
117
+ }
118
+ }
119
+
120
+ // Fallback: tentar index.js/index.ts
121
+ const indexJs = join(projectNodeModules, 'index.js')
122
+ const indexTs = join(projectNodeModules, 'index.ts')
123
+
124
+ if (existsSync(indexJs)) return indexJs
125
+ if (existsSync(indexTs)) return indexTs
126
+
127
+ return projectNodeModules
128
+ }
129
+
130
+ return null
131
+ }
132
+
133
+ /**
134
+ * Resolve sub-paths (ex: @noble/curves/ed25519)
135
+ */
136
+ resolveSubpath(moduleName: string, subpath: string, pluginPath: string): string | null {
137
+ const fullModule = `${moduleName}/${subpath}`
138
+ const cacheKey = `${pluginPath}::${fullModule}`
139
+
140
+ // Verificar cache
141
+ if (this.resolveCache.has(cacheKey)) {
142
+ return this.resolveCache.get(cacheKey)!
143
+ }
144
+
145
+ this.logger?.debug(`Resolvendo subpath '${fullModule}' para plugin em '${pluginPath}'`)
146
+
147
+ // 1. Tentar no node_modules local do plugin
148
+ const pluginDir = resolve(pluginPath)
149
+ const localPath = join(pluginDir, 'node_modules', fullModule)
150
+
151
+ if (this.existsWithExtension(localPath)) {
152
+ const resolvedLocal = this.findFileWithExtension(localPath)
153
+ if (resolvedLocal) {
154
+ this.logger?.debug(`✅ Subpath '${fullModule}' encontrado localmente: ${resolvedLocal}`)
155
+ this.resolveCache.set(cacheKey, resolvedLocal)
156
+ return resolvedLocal
157
+ }
158
+ }
159
+
160
+ // 2. Tentar no node_modules do projeto principal
161
+ const projectPath = join(this.config.projectRoot, 'node_modules', fullModule)
162
+
163
+ if (this.existsWithExtension(projectPath)) {
164
+ const resolvedProject = this.findFileWithExtension(projectPath)
165
+ if (resolvedProject) {
166
+ this.logger?.debug(`✅ Subpath '${fullModule}' encontrado no projeto: ${resolvedProject}`)
167
+ this.resolveCache.set(cacheKey, resolvedProject)
168
+ return resolvedProject
169
+ }
170
+ }
171
+
172
+ this.logger?.warn(`❌ Subpath '${fullModule}' não encontrado em nenhum contexto`)
173
+ return null
174
+ }
175
+
176
+ /**
177
+ * Verifica se arquivo existe com alguma extensão comum
178
+ */
179
+ private existsWithExtension(basePath: string): boolean {
180
+ const extensions = ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '/index.js', '/index.ts']
181
+ return extensions.some(ext => existsSync(basePath + ext))
182
+ }
183
+
184
+ /**
185
+ * Encontra arquivo com extensão
186
+ */
187
+ private findFileWithExtension(basePath: string): string | null {
188
+ const extensions = ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '/index.js', '/index.ts']
189
+
190
+ for (const ext of extensions) {
191
+ const fullPath = basePath + ext
192
+ if (existsSync(fullPath)) {
193
+ return fullPath
194
+ }
195
+ }
196
+
197
+ return null
198
+ }
199
+
200
+ /**
201
+ * Limpar cache
202
+ */
203
+ clearCache(): void {
204
+ this.resolveCache.clear()
205
+ }
206
+
207
+ /**
208
+ * Obter estatísticas
209
+ */
210
+ getStats() {
211
+ return {
212
+ cachedModules: this.resolveCache.size,
213
+ projectRoot: this.config.projectRoot
214
+ }
215
+ }
216
+ }
@@ -2,7 +2,7 @@ import type { FluxStack, PluginManifest, PluginLoadResult, PluginDiscoveryOption
2
2
 
3
3
  type FluxStackPlugin = FluxStack.Plugin
4
4
  import type { FluxStackConfig } from "../config/schema"
5
- import type { Logger } from "../utils/logger/index"
5
+ import type { Logger } from "../utils/logger"
6
6
  import { FluxStackError } from "../utils/errors"
7
7
  import { PluginDependencyManager } from "./dependency-manager"
8
8
  import { readdir, readFile } from "fs/promises"
@@ -262,6 +262,33 @@ export class PluginRegistry {
262
262
  if (existsSync(manifestPath)) {
263
263
  const manifestContent = await readFile(manifestPath, 'utf-8')
264
264
  manifest = JSON.parse(manifestContent)
265
+ } else {
266
+ // Try package.json for npm plugins
267
+ const packagePath = join(pluginPath, 'package.json')
268
+ if (existsSync(packagePath)) {
269
+ try {
270
+ const packageContent = await readFile(packagePath, 'utf-8')
271
+ const packageJson = JSON.parse(packageContent)
272
+
273
+ if (packageJson.fluxstack) {
274
+ manifest = {
275
+ name: packageJson.name,
276
+ version: packageJson.version,
277
+ description: packageJson.description || '',
278
+ author: packageJson.author || '',
279
+ license: packageJson.license || '',
280
+ homepage: packageJson.homepage,
281
+ repository: packageJson.repository,
282
+ keywords: packageJson.keywords || [],
283
+ dependencies: packageJson.dependencies || {},
284
+ peerDependencies: packageJson.peerDependencies,
285
+ fluxstack: packageJson.fluxstack
286
+ }
287
+ }
288
+ } catch (error) {
289
+ this.logger?.warn(`Failed to parse package.json in '${pluginPath}'`, { error })
290
+ }
291
+ }
265
292
  }
266
293
 
267
294
  // Try to import the plugin
@@ -23,6 +23,10 @@ export { clearColorCache } from './colors'
23
23
  export { clearCallerCache } from './stack-trace'
24
24
  export { clearLoggerCache } from './winston-logger'
25
25
 
26
+ // Export Logger type from winston
27
+ import type winston from 'winston'
28
+ export type Logger = winston.Logger
29
+
26
30
  // Re-export banner utilities for custom banners
27
31
  export { displayStartupBanner, type StartupInfo } from './startup-banner'
28
32
 
@@ -2,4 +2,4 @@
2
2
  * FluxStack Framework Version
3
3
  * Single source of truth for version number
4
4
  */
5
- export const FLUXSTACK_VERSION = '1.4.0'
5
+ export const FLUXSTACK_VERSION = '1.4.1'