create-fluxstack 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -8
- package/app/client/src/App.tsx +1 -4
- package/app/server/index.ts +0 -4
- package/app/server/routes/index.ts +1 -5
- package/config/index.ts +1 -9
- package/core/cli/generators/plugin.ts +34 -324
- package/core/cli/generators/template-engine.ts +0 -5
- package/core/cli/plugin-discovery.ts +12 -33
- package/core/framework/server.ts +0 -10
- package/core/plugins/dependency-manager.ts +22 -89
- package/core/plugins/index.ts +0 -4
- package/core/plugins/manager.ts +2 -3
- package/core/plugins/registry.ts +1 -28
- package/core/utils/logger/index.ts +0 -4
- package/core/utils/version.ts +1 -1
- package/fluxstack.config.ts +114 -253
- package/package.json +117 -117
- package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +0 -475
- package/CRYPTO-AUTH-MIDDLEWARES.md +0 -473
- package/CRYPTO-AUTH-USAGE.md +0 -491
- package/EXEMPLO-ROTA-PROTEGIDA.md +0 -347
- package/QUICK-START-CRYPTO-AUTH.md +0 -221
- package/app/client/src/pages/CryptoAuthPage.tsx +0 -394
- package/app/server/routes/crypto-auth-demo.routes.ts +0 -167
- package/app/server/routes/example-with-crypto-auth.routes.ts +0 -235
- package/app/server/routes/exemplo-posts.routes.ts +0 -161
- package/core/plugins/module-resolver.ts +0 -216
- package/plugins/crypto-auth/README.md +0 -788
- package/plugins/crypto-auth/ai-context.md +0 -1282
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +0 -383
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +0 -302
- package/plugins/crypto-auth/client/components/AuthProvider.tsx +0 -131
- package/plugins/crypto-auth/client/components/LoginButton.tsx +0 -138
- package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +0 -89
- package/plugins/crypto-auth/client/components/index.ts +0 -12
- package/plugins/crypto-auth/client/index.ts +0 -12
- package/plugins/crypto-auth/config/index.ts +0 -34
- package/plugins/crypto-auth/index.ts +0 -162
- package/plugins/crypto-auth/package.json +0 -66
- package/plugins/crypto-auth/server/AuthMiddleware.ts +0 -181
- package/plugins/crypto-auth/server/CryptoAuthService.ts +0 -186
- package/plugins/crypto-auth/server/index.ts +0 -22
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +0 -65
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +0 -26
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +0 -76
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +0 -45
- package/plugins/crypto-auth/server/middlewares/helpers.ts +0 -140
- package/plugins/crypto-auth/server/middlewares/index.ts +0 -22
- package/plugins/crypto-auth/server/middlewares.ts +0 -19
- package/test-crypto-auth.ts +0 -101
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exemplo de uso dos middlewares crypto-auth
|
|
3
|
-
* Demonstra como proteger rotas com autenticação criptográfica
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Elysia, t } from 'elysia'
|
|
7
|
-
import {
|
|
8
|
-
cryptoAuthRequired,
|
|
9
|
-
cryptoAuthAdmin,
|
|
10
|
-
cryptoAuthPermissions,
|
|
11
|
-
cryptoAuthOptional,
|
|
12
|
-
getCryptoAuthUser,
|
|
13
|
-
isCryptoAuthAdmin
|
|
14
|
-
} from '@/plugins/crypto-auth/server'
|
|
15
|
-
|
|
16
|
-
// ========================================
|
|
17
|
-
// 1️⃣ ROTAS QUE REQUEREM AUTENTICAÇÃO
|
|
18
|
-
// ========================================
|
|
19
|
-
|
|
20
|
-
export const protectedRoutes = new Elysia()
|
|
21
|
-
// ✅ Aplica middleware a TODAS as rotas deste grupo
|
|
22
|
-
.use(cryptoAuthRequired())
|
|
23
|
-
|
|
24
|
-
// Agora TODAS as rotas abaixo requerem autenticação
|
|
25
|
-
.get('/users/me', ({ request }) => {
|
|
26
|
-
const user = getCryptoAuthUser(request)!
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
profile: {
|
|
30
|
-
publicKey: user.publicKey,
|
|
31
|
-
isAdmin: user.isAdmin,
|
|
32
|
-
permissions: user.permissions
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
.get('/users', ({ request }) => {
|
|
38
|
-
const user = getCryptoAuthUser(request)!
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
users: [
|
|
42
|
-
{ id: 1, name: 'João' },
|
|
43
|
-
{ id: 2, name: 'Maria' }
|
|
44
|
-
],
|
|
45
|
-
requestedBy: user.publicKey.substring(0, 16) + '...'
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
.post('/posts', ({ request, body }) => {
|
|
50
|
-
const user = getCryptoAuthUser(request)!
|
|
51
|
-
const { title, content } = body as { title: string; content: string }
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
success: true,
|
|
55
|
-
post: {
|
|
56
|
-
title,
|
|
57
|
-
content,
|
|
58
|
-
author: user.publicKey,
|
|
59
|
-
createdAt: new Date()
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}, {
|
|
63
|
-
body: t.Object({
|
|
64
|
-
title: t.String(),
|
|
65
|
-
content: t.String()
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
// ========================================
|
|
70
|
-
// 2️⃣ ROTAS QUE REQUEREM ADMIN
|
|
71
|
-
// ========================================
|
|
72
|
-
|
|
73
|
-
export const adminRoutes = new Elysia()
|
|
74
|
-
// ✅ Apenas admins podem acessar
|
|
75
|
-
.use(cryptoAuthAdmin())
|
|
76
|
-
|
|
77
|
-
.get('/admin/stats', () => ({
|
|
78
|
-
totalUsers: 100,
|
|
79
|
-
totalPosts: 500,
|
|
80
|
-
systemHealth: 'optimal'
|
|
81
|
-
}))
|
|
82
|
-
|
|
83
|
-
.delete('/admin/users/:id', ({ params, request }) => {
|
|
84
|
-
const user = getCryptoAuthUser(request)!
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
success: true,
|
|
88
|
-
message: `Usuário ${params.id} deletado`,
|
|
89
|
-
deletedBy: user.publicKey
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
.post('/admin/broadcast', ({ body }) => ({
|
|
94
|
-
success: true,
|
|
95
|
-
message: 'Mensagem enviada para todos os usuários',
|
|
96
|
-
content: body
|
|
97
|
-
}), {
|
|
98
|
-
body: t.Object({
|
|
99
|
-
message: t.String()
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
// ========================================
|
|
104
|
-
// 3️⃣ ROTAS COM PERMISSÕES ESPECÍFICAS
|
|
105
|
-
// ========================================
|
|
106
|
-
|
|
107
|
-
export const writeRoutes = new Elysia()
|
|
108
|
-
// ✅ Requer permissão 'write'
|
|
109
|
-
.use(cryptoAuthPermissions(['write']))
|
|
110
|
-
|
|
111
|
-
.put('/posts/:id', ({ params, body }) => ({
|
|
112
|
-
success: true,
|
|
113
|
-
message: `Post ${params.id} atualizado`,
|
|
114
|
-
data: body
|
|
115
|
-
}), {
|
|
116
|
-
body: t.Object({
|
|
117
|
-
title: t.Optional(t.String()),
|
|
118
|
-
content: t.Optional(t.String())
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
.patch('/posts/:id/publish', ({ params }) => ({
|
|
123
|
-
success: true,
|
|
124
|
-
message: `Post ${params.id} publicado`
|
|
125
|
-
}))
|
|
126
|
-
|
|
127
|
-
// ========================================
|
|
128
|
-
// 4️⃣ ROTAS MISTAS (Opcional)
|
|
129
|
-
// ========================================
|
|
130
|
-
|
|
131
|
-
export const mixedRoutes = new Elysia()
|
|
132
|
-
// ✅ Autenticação OPCIONAL - adiciona user se autenticado
|
|
133
|
-
.use(cryptoAuthOptional())
|
|
134
|
-
|
|
135
|
-
// Comportamento diferente se autenticado
|
|
136
|
-
.get('/posts/:id', ({ request, params }) => {
|
|
137
|
-
const user = getCryptoAuthUser(request)
|
|
138
|
-
const isAdmin = isCryptoAuthAdmin(request)
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
post: {
|
|
142
|
-
id: params.id,
|
|
143
|
-
title: 'Título do Post',
|
|
144
|
-
// Mostra conteúdo completo apenas se autenticado
|
|
145
|
-
content: user ? 'Conteúdo completo do post...' : 'Prévia...',
|
|
146
|
-
author: 'João'
|
|
147
|
-
},
|
|
148
|
-
viewer: user ? {
|
|
149
|
-
publicKey: user.publicKey.substring(0, 16) + '...',
|
|
150
|
-
canEdit: isAdmin,
|
|
151
|
-
canComment: true
|
|
152
|
-
} : {
|
|
153
|
-
canEdit: false,
|
|
154
|
-
canComment: false
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
.get('/posts', ({ request }) => {
|
|
160
|
-
const user = getCryptoAuthUser(request)
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
posts: [
|
|
164
|
-
{ id: 1, title: 'Post 1' },
|
|
165
|
-
{ id: 2, title: 'Post 2' }
|
|
166
|
-
],
|
|
167
|
-
authenticated: !!user
|
|
168
|
-
}
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
// ========================================
|
|
172
|
-
// 5️⃣ ROTAS COM VERIFICAÇÃO MANUAL
|
|
173
|
-
// ========================================
|
|
174
|
-
|
|
175
|
-
export const customRoutes = new Elysia()
|
|
176
|
-
.use(cryptoAuthRequired())
|
|
177
|
-
|
|
178
|
-
.get('/posts/:id/edit', ({ request, params, set }) => {
|
|
179
|
-
const user = getCryptoAuthUser(request)!
|
|
180
|
-
|
|
181
|
-
// Verificação customizada - apenas autor ou admin
|
|
182
|
-
const post = { id: params.id, authorKey: 'abc123...' } // Buscar do DB
|
|
183
|
-
|
|
184
|
-
const canEdit = user.isAdmin || user.publicKey === post.authorKey
|
|
185
|
-
|
|
186
|
-
if (!canEdit) {
|
|
187
|
-
set.status = 403
|
|
188
|
-
return {
|
|
189
|
-
error: 'Apenas o autor ou admin podem editar este post'
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
post,
|
|
195
|
-
canEdit: true
|
|
196
|
-
}
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
// ========================================
|
|
200
|
-
// 6️⃣ COMBINANDO MÚLTIPLOS MIDDLEWARES
|
|
201
|
-
// ========================================
|
|
202
|
-
|
|
203
|
-
export const combinedRoutes = new Elysia({ prefix: '/api/v1' })
|
|
204
|
-
// Primeiro grupo - rotas públicas
|
|
205
|
-
.get('/health', () => ({ status: 'ok' }))
|
|
206
|
-
|
|
207
|
-
.get('/posts', () => ({
|
|
208
|
-
posts: [/* ... */]
|
|
209
|
-
}))
|
|
210
|
-
|
|
211
|
-
// Segundo grupo - rotas protegidas
|
|
212
|
-
.group('/users', (app) => app
|
|
213
|
-
.use(cryptoAuthRequired())
|
|
214
|
-
.get('/', () => ({ users: [] }))
|
|
215
|
-
.post('/', ({ body }) => ({ created: body }))
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
// Terceiro grupo - rotas admin
|
|
219
|
-
.group('/admin', (app) => app
|
|
220
|
-
.use(cryptoAuthAdmin())
|
|
221
|
-
.get('/stats', () => ({ stats: {} }))
|
|
222
|
-
.delete('/users/:id', ({ params }) => ({ deleted: params.id }))
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
// ========================================
|
|
226
|
-
// 7️⃣ EXPORTAR TODAS AS ROTAS
|
|
227
|
-
// ========================================
|
|
228
|
-
|
|
229
|
-
export const allExampleRoutes = new Elysia()
|
|
230
|
-
.use(protectedRoutes)
|
|
231
|
-
.use(adminRoutes)
|
|
232
|
-
.use(writeRoutes)
|
|
233
|
-
.use(mixedRoutes)
|
|
234
|
-
.use(customRoutes)
|
|
235
|
-
.use(combinedRoutes)
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🎓 EXEMPLO PRÁTICO: Como criar rotas com Crypto-Auth
|
|
3
|
-
*
|
|
4
|
-
* Este arquivo demonstra como um desenvolvedor cria rotas usando
|
|
5
|
-
* o sistema de autenticação crypto-auth do FluxStack.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Elysia, t } from 'elysia'
|
|
9
|
-
import {
|
|
10
|
-
cryptoAuthRequired,
|
|
11
|
-
cryptoAuthAdmin,
|
|
12
|
-
cryptoAuthOptional,
|
|
13
|
-
getCryptoAuthUser
|
|
14
|
-
} from '@/plugins/crypto-auth/server'
|
|
15
|
-
|
|
16
|
-
// Mock database (simulação)
|
|
17
|
-
const posts = [
|
|
18
|
-
{ id: 1, title: 'Post Público 1', content: 'Conteúdo aberto', public: true },
|
|
19
|
-
{ id: 2, title: 'Post Público 2', content: 'Conteúdo aberto', public: true }
|
|
20
|
-
]
|
|
21
|
-
|
|
22
|
-
export const exemploPostsRoutes = new Elysia({ prefix: '/exemplo-posts' })
|
|
23
|
-
|
|
24
|
-
// ========================================
|
|
25
|
-
// 🌐 ROTA PÚBLICA - Qualquer um acessa
|
|
26
|
-
// ========================================
|
|
27
|
-
.get('/', () => {
|
|
28
|
-
return {
|
|
29
|
-
success: true,
|
|
30
|
-
message: 'Lista pública de posts',
|
|
31
|
-
posts: posts.filter(p => p.public)
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
// ========================================
|
|
36
|
-
// 🌓 ROTA COM AUTH OPCIONAL
|
|
37
|
-
// Funciona com ou sem autenticação
|
|
38
|
-
// ========================================
|
|
39
|
-
.guard({}, (app) =>
|
|
40
|
-
app.use(cryptoAuthOptional())
|
|
41
|
-
|
|
42
|
-
.get('/:id', ({ request, params }) => {
|
|
43
|
-
const user = getCryptoAuthUser(request)
|
|
44
|
-
const isAuthenticated = !!user
|
|
45
|
-
const post = posts.find(p => p.id === parseInt(params.id))
|
|
46
|
-
|
|
47
|
-
if (!post) {
|
|
48
|
-
return { success: false, error: 'Post não encontrado' }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
success: true,
|
|
53
|
-
post: {
|
|
54
|
-
...post,
|
|
55
|
-
// ✅ Conteúdo extra apenas para autenticados
|
|
56
|
-
premiumContent: isAuthenticated
|
|
57
|
-
? 'Conteúdo premium exclusivo para usuários autenticados!'
|
|
58
|
-
: null,
|
|
59
|
-
viewer: isAuthenticated
|
|
60
|
-
? `Autenticado: ${user.publicKey.substring(0, 8)}...`
|
|
61
|
-
: 'Visitante anônimo'
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
// ========================================
|
|
68
|
-
// 🔒 ROTAS PROTEGIDAS - Requer autenticação
|
|
69
|
-
// ========================================
|
|
70
|
-
.guard({}, (app) =>
|
|
71
|
-
app.use(cryptoAuthRequired())
|
|
72
|
-
|
|
73
|
-
// GET /api/exemplo-posts/meus-posts
|
|
74
|
-
.get('/meus-posts', ({ request }) => {
|
|
75
|
-
const user = getCryptoAuthUser(request)!
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
success: true,
|
|
79
|
-
message: `Posts criados por você`,
|
|
80
|
-
user: {
|
|
81
|
-
publicKey: user.publicKey.substring(0, 16) + '...',
|
|
82
|
-
isAdmin: user.isAdmin
|
|
83
|
-
},
|
|
84
|
-
posts: [
|
|
85
|
-
{
|
|
86
|
-
id: 999,
|
|
87
|
-
title: 'Meu Post Privado',
|
|
88
|
-
content: 'Só eu posso ver',
|
|
89
|
-
author: user.publicKey
|
|
90
|
-
}
|
|
91
|
-
]
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
// POST /api/exemplo-posts/criar
|
|
96
|
-
.post('/criar', ({ request, body }) => {
|
|
97
|
-
const user = getCryptoAuthUser(request)!
|
|
98
|
-
const { title, content } = body as { title: string; content: string }
|
|
99
|
-
|
|
100
|
-
const newPost = {
|
|
101
|
-
id: Date.now(),
|
|
102
|
-
title,
|
|
103
|
-
content,
|
|
104
|
-
author: user.publicKey,
|
|
105
|
-
createdAt: new Date().toISOString(),
|
|
106
|
-
public: false
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
posts.push(newPost)
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
success: true,
|
|
113
|
-
message: 'Post criado com sucesso!',
|
|
114
|
-
post: newPost
|
|
115
|
-
}
|
|
116
|
-
}, {
|
|
117
|
-
body: t.Object({
|
|
118
|
-
title: t.String({ minLength: 3 }),
|
|
119
|
-
content: t.String({ minLength: 10 })
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
// ========================================
|
|
125
|
-
// 👑 ROTAS ADMIN - Apenas administradores
|
|
126
|
-
// ========================================
|
|
127
|
-
.guard({}, (app) =>
|
|
128
|
-
app.use(cryptoAuthAdmin())
|
|
129
|
-
|
|
130
|
-
// GET /api/exemplo-posts/admin/todos
|
|
131
|
-
.get('/admin/todos', ({ request }) => {
|
|
132
|
-
const user = getCryptoAuthUser(request)!
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
success: true,
|
|
136
|
-
message: 'Painel administrativo',
|
|
137
|
-
admin: user.publicKey.substring(0, 8) + '...',
|
|
138
|
-
totalPosts: posts.length,
|
|
139
|
-
posts: posts // Admin vê tudo
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
// DELETE /api/exemplo-posts/admin/:id
|
|
144
|
-
.delete('/admin/:id', ({ request, params }) => {
|
|
145
|
-
const user = getCryptoAuthUser(request)!
|
|
146
|
-
const postIndex = posts.findIndex(p => p.id === parseInt(params.id))
|
|
147
|
-
|
|
148
|
-
if (postIndex === -1) {
|
|
149
|
-
return { success: false, error: 'Post não encontrado' }
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const deletedPost = posts.splice(postIndex, 1)[0]
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
success: true,
|
|
156
|
-
message: `Post "${deletedPost.title}" deletado pelo admin`,
|
|
157
|
-
deletedBy: user.publicKey.substring(0, 8) + '...',
|
|
158
|
-
deletedPost
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
)
|
|
@@ -1,216 +0,0 @@
|
|
|
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
|
-
}
|