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
@@ -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