arckode-framework 1.3.2 → 1.4.1
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/adapters/jwt.ts +6 -4
- package/adapters/mysql.ts +7 -2
- package/adapters/postgres.ts +37 -0
- package/adapters/sqlite.ts +7 -1
- package/adapters/vendor.d.ts +48 -0
- package/cli/analyze/checks.ts +333 -0
- package/cli/analyze/index.ts +44 -0
- package/cli/analyze/report.ts +107 -0
- package/cli/analyze/types.ts +46 -0
- package/cli/analyze/utils.ts +36 -0
- package/cli/analyze.ts +2 -647
- package/cli/commands/db-migrate.ts +213 -89
- package/cli/commands/db-seed.ts +97 -32
- package/cli/commands/db-utils.ts +192 -0
- package/cli/commands/new.ts +175 -0
- package/cli/commands/routes.ts +94 -0
- package/cli/index.ts +57 -404
- package/cli/stubs/module/core.ts +162 -0
- package/cli/stubs/module/data.ts +171 -0
- package/cli/stubs/module/index.ts +5 -0
- package/cli/stubs/module/service.ts +198 -0
- package/cli/stubs/module/types.ts +12 -0
- package/cli/stubs/module-stub.ts +2 -552
- package/kernel/auth.ts +114 -0
- package/kernel/cache.ts +37 -0
- package/kernel/config.ts +129 -0
- package/kernel/container.ts +64 -0
- package/kernel/db/orm-migrate.ts +136 -0
- package/kernel/db/orm-repository.ts +45 -0
- package/kernel/db/orm-utils.ts +93 -0
- package/kernel/db/orm.ts +254 -0
- package/kernel/db/transactor.ts +17 -0
- package/kernel/db/types.ts +72 -0
- package/kernel/errors.ts +102 -0
- package/kernel/framework.default.ts +41 -0
- package/kernel/framework.ts +8 -2144
- package/kernel/http/router.ts +131 -0
- package/kernel/http/server.ts +303 -0
- package/kernel/http/types.ts +56 -0
- package/kernel/index.ts +25 -0
- package/kernel/logger.ts +50 -0
- package/kernel/middlewares.ts +19 -7
- package/kernel/modules/create-module.ts +5 -0
- package/kernel/modules/system.ts +149 -0
- package/kernel/modules/types.ts +46 -0
- package/kernel/seeds.ts +48 -0
- package/kernel/static.ts +11 -2
- package/kernel/testing.ts +8 -3
- package/kernel/validator.ts +116 -0
- package/modules/events/index.ts +19 -3
- package/modules/mail/index.ts +14 -2
- package/modules/storage/local-adapter.ts +19 -5
- package/modules/ws/index.ts +123 -18
- package/package.json +8 -11
- package/skills/auth/SKILL.md +36 -220
- package/skills/cli/SKILL.md +32 -251
- package/skills/config/SKILL.md +30 -239
- package/skills/connectors/SKILL.md +32 -295
- package/skills/helpers/SKILL.md +26 -195
- package/skills/middlewares/SKILL.md +30 -280
- package/skills/orm/SKILL.md +42 -349
- package/skills/realtime/SKILL.md +22 -297
- package/skills/services/SKILL.md +40 -183
- package/skills/testing/SKILL.md +34 -266
package/cli/index.ts
CHANGED
|
@@ -1,237 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// CLI — Arckode Framework Tooling
|
|
3
|
-
// Punto de entrada
|
|
3
|
+
// Punto de entrada: routing de comandos. Cada comando vive en cli/commands/.
|
|
4
4
|
|
|
5
|
-
import { mkdir, writeFile } from 'node:fs/promises'
|
|
6
5
|
import { join } from 'node:path'
|
|
7
6
|
import { generateModule, generateConnector } from './generate'
|
|
8
7
|
import { analyzeProject, printAnalysis, buildManifest } from './analyze'
|
|
9
|
-
import {
|
|
8
|
+
import { writeFile } from 'node:fs/promises'
|
|
10
9
|
|
|
11
10
|
async function main() {
|
|
12
11
|
const [, , cmd, ...args] = process.argv
|
|
13
12
|
|
|
14
13
|
switch (cmd) {
|
|
15
|
-
// ─── Crear proyecto ───
|
|
16
14
|
case 'new': {
|
|
17
15
|
const name = args[0]
|
|
18
|
-
if (!name) { console.log('Uso:
|
|
19
|
-
|
|
20
|
-
// Detectar DB adapter: --db=mysql | --db=postgres | default: sqlite
|
|
16
|
+
if (!name) { console.log('Uso: arckode new <nombre-proyecto> [--db=sqlite|mysql|postgres]'); return }
|
|
21
17
|
const dbFlag = args.find(a => a.startsWith('--db='))?.split('=')[1] ?? 'sqlite'
|
|
22
|
-
const db: 'sqlite' | 'mysql' | 'postgres'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const base = join(process.cwd(), name)
|
|
26
|
-
|
|
27
|
-
// Crear estructura del proyecto
|
|
28
|
-
await mkdir(join(base, 'src', 'modules'), { recursive: true })
|
|
29
|
-
await mkdir(join(base, 'src', 'connectors'), { recursive: true })
|
|
30
|
-
|
|
31
|
-
// ─── CLAUDE.md ─────────────────────────────────────────────────────
|
|
32
|
-
// Contenido del stub vive en cli/stubs/claude-md-stub.ts
|
|
33
|
-
// (sincronizado con CLAUDE.md maestro del framework)
|
|
34
|
-
await writeFile(join(base, 'CLAUDE.md'), claudeMdStub({ projectName: name, db }))
|
|
35
|
-
|
|
36
|
-
// composition-root.ts
|
|
37
|
-
await writeFile(join(base, 'src', 'composition-root.ts'), [
|
|
38
|
-
`// composition-root.ts — ÚNICO archivo que describe TODO el sistema`,
|
|
39
|
-
`// La IA lee esto y sabe: módulos, conectores, dependencias.`,
|
|
40
|
-
`// ============================================================`,
|
|
41
|
-
``,
|
|
42
|
-
`import {`,
|
|
43
|
-
` ConfigStore, Container, Logger, ORM, Router,`,
|
|
44
|
-
` NodeServer, MemoryCache, System, Auth, loadEnv,`,
|
|
45
|
-
`} from 'arckode-framework'`,
|
|
46
|
-
...(db === 'sqlite' ? [
|
|
47
|
-
`import { SqliteAdapter } from 'arckode-framework/adapters/sqlite'`,
|
|
48
|
-
] : db === 'mysql' ? [
|
|
49
|
-
`import { MySQLAdapter } from 'arckode-framework/adapters/mysql'`,
|
|
50
|
-
] : [
|
|
51
|
-
`import { PostgresAdapter } from 'arckode-framework/adapters/postgres'`,
|
|
52
|
-
]),
|
|
53
|
-
`import { jwtTokenAdapter } from 'arckode-framework/adapters/jwt'`,
|
|
54
|
-
``,
|
|
55
|
-
`// ─── 1. ENV + CONFIG ─────────────────────────`,
|
|
56
|
-
`// loadEnv() carga .env + .env.{NODE_ENV} — process.env tiene prioridad máxima`,
|
|
57
|
-
`const env = await loadEnv()`,
|
|
58
|
-
``,
|
|
59
|
-
`const config = new ConfigStore()`,
|
|
60
|
-
`config.define({`,
|
|
61
|
-
` PORT: { type: 'number', default: 3000 },`,
|
|
62
|
-
...(db === 'sqlite' ? [
|
|
63
|
-
` DB_PATH: { type: 'string', default: './data/db.sqlite' },`,
|
|
64
|
-
] : db === 'mysql' ? [
|
|
65
|
-
` DB_HOST: { type: 'string', required: true },`,
|
|
66
|
-
` DB_USER: { type: 'string', required: true },`,
|
|
67
|
-
` DB_PASSWORD:{ type: 'string', required: true },`,
|
|
68
|
-
` DB_NAME: { type: 'string', required: true },`,
|
|
69
|
-
` DB_PORT: { type: 'number', default: 3306 },`,
|
|
70
|
-
] : [
|
|
71
|
-
` DATABASE_URL: { type: 'string', required: true },`,
|
|
72
|
-
]),
|
|
73
|
-
` JWT_SECRET: { type: 'string', required: true },`,
|
|
74
|
-
` LOG_LEVEL: { type: 'string', default: 'info' },`,
|
|
75
|
-
`}).load(env)`,
|
|
76
|
-
``,
|
|
77
|
-
`// ─── 2. INFRAESTRUCTURA ──────────────────────`,
|
|
78
|
-
`const logger = new Logger('app', config.get('LOG_LEVEL') as any)`,
|
|
79
|
-
`const container = new Container()`,
|
|
80
|
-
...(db === 'sqlite' ? [
|
|
81
|
-
`const dbAdapter = new SqliteAdapter({ path: config.get('DB_PATH') })`,
|
|
82
|
-
] : db === 'mysql' ? [
|
|
83
|
-
`const dbAdapter = new MySQLAdapter({`,
|
|
84
|
-
` host: config.get('DB_HOST'),`,
|
|
85
|
-
` port: config.get<number>('DB_PORT'),`,
|
|
86
|
-
` user: config.get('DB_USER'),`,
|
|
87
|
-
` password: config.get('DB_PASSWORD'),`,
|
|
88
|
-
` database: config.get('DB_NAME'),`,
|
|
89
|
-
`})`,
|
|
90
|
-
] : [
|
|
91
|
-
`const dbAdapter = new PostgresAdapter({ connectionString: config.get('DATABASE_URL') })`,
|
|
92
|
-
]),
|
|
93
|
-
`await dbAdapter.connect()`,
|
|
94
|
-
`const orm = new ORM(dbAdapter)`,
|
|
95
|
-
`const router = new Router()`,
|
|
96
|
-
`const http = new NodeServer(config.get<number>('PORT'), logger)`,
|
|
97
|
-
`const cache = new MemoryCache()`,
|
|
98
|
-
`const auth = new Auth(jwtTokenAdapter, config.get('JWT_SECRET'), logger)`,
|
|
99
|
-
``,
|
|
100
|
-
`// ─── 3. CONTAINER ────────────────────────────`,
|
|
101
|
-
`container.register('config', () => config)`,
|
|
102
|
-
`container.register('logger', () => logger)`,
|
|
103
|
-
`container.register('db', () => dbAdapter, () => dbAdapter.close())`,
|
|
104
|
-
`container.register('orm', () => orm)`,
|
|
105
|
-
`container.register('cache', () => cache)`,
|
|
106
|
-
`container.init()`,
|
|
107
|
-
``,
|
|
108
|
-
`// ─── 4. SISTEMA ───────────────────────────────`,
|
|
109
|
-
`const system = new System({`,
|
|
110
|
-
` config, container, logger, orm, router, http, cache, auth,`,
|
|
111
|
-
`})`,
|
|
112
|
-
``,
|
|
113
|
-
`// ─── 5. MÓDULOS ───────────────────────────────`,
|
|
114
|
-
`// Cada módulo se autorregistra: su index.ts llama registerXxxModels(orm)`,
|
|
115
|
-
`// en el create(). Acá SOLO addModule(). No tocar orm.define() manualmente.`,
|
|
116
|
-
``,
|
|
117
|
-
`// import { MiModuloModule } from './modules/mi-modulo'`,
|
|
118
|
-
`// system.addModule(MiModuloModule())`,
|
|
119
|
-
``,
|
|
120
|
-
`// ─── 6. CONECTORES ────────────────────────────`,
|
|
121
|
-
`// Generar con: arckode make:connector nombre-x mod-a mod-b`,
|
|
122
|
-
`// import { connectModAModB } from './connectors/mod-a-mod-b'`,
|
|
123
|
-
`// system.addConnector('mod-a-mod-b', connectModAModB)`,
|
|
124
|
-
``,
|
|
125
|
-
`// Para atomicidad multi-tabla (financieras, ledgers, transferencias):`,
|
|
126
|
-
`// import { OrmTransactor } from 'arckode-framework'`,
|
|
127
|
-
`// const transactor = new OrmTransactor(orm)`,
|
|
128
|
-
`// → inyectarlo al service que lo necesite (Regla #19)`,
|
|
129
|
-
``,
|
|
130
|
-
`// ─── 7. RUTAS BASE ────────────────────────────`,
|
|
131
|
-
`router.get('/health', async () => ({`,
|
|
132
|
-
` status: 200, body: { status: 'ok', uptime: process.uptime() }`,
|
|
133
|
-
`}))`,
|
|
134
|
-
``,
|
|
135
|
-
`// ─── 8. INIT + MIGRATE + START ────────────────`,
|
|
136
|
-
`// Orden CRÍTICO: init() ejecuta los create() de los módulos (registra modelos),`,
|
|
137
|
-
`// luego migrate() crea las tablas, luego start() levanta el HTTP.`,
|
|
138
|
-
`system.init()`,
|
|
139
|
-
`await orm.migrate()`,
|
|
140
|
-
``,
|
|
141
|
-
`// ─── 9. SEEDS (solo en desarrollo) ────────────`,
|
|
142
|
-
`if (env.RUN_SEEDS === 'true') {`,
|
|
143
|
-
` // await seeds.runAll()`,
|
|
144
|
-
`}`,
|
|
145
|
-
``,
|
|
146
|
-
`await system.start()`,
|
|
147
|
-
``,
|
|
148
|
-
`process.on('SIGINT', async () => { await system.stop(); process.exit(0) })`,
|
|
149
|
-
`process.on('SIGTERM', async () => { await system.stop(); process.exit(0) })`,
|
|
150
|
-
``,
|
|
151
|
-
].join('\n'))
|
|
152
|
-
|
|
153
|
-
// package.json — dependencias según el DB elegido
|
|
154
|
-
const dbDeps: Record<string, string> = db === 'mysql'
|
|
155
|
-
? { 'mysql2': '^3.0.0' }
|
|
156
|
-
: db === 'postgres'
|
|
157
|
-
? { 'pg': '^8.0.0' }
|
|
158
|
-
: { 'better-sqlite3': '^11.0.0' }
|
|
159
|
-
|
|
160
|
-
const dbDevDeps: Record<string, string> = db === 'postgres'
|
|
161
|
-
? { '@types/pg': '^8.0.0' }
|
|
162
|
-
: db === 'sqlite'
|
|
163
|
-
? { '@types/better-sqlite3': '^7.6.0' }
|
|
164
|
-
: {}
|
|
165
|
-
|
|
166
|
-
await writeFile(join(base, 'package.json'), JSON.stringify({
|
|
167
|
-
name,
|
|
168
|
-
version: '1.0.0',
|
|
169
|
-
type: 'module',
|
|
170
|
-
scripts: {
|
|
171
|
-
dev: `bun --watch src/composition-root.ts`,
|
|
172
|
-
start: `bun run src/composition-root.ts`,
|
|
173
|
-
test: `bun test`,
|
|
174
|
-
'test:watch': `bun test --watch`,
|
|
175
|
-
analyze: `bun run node_modules/arckode-framework/cli/index.ts analyze`,
|
|
176
|
-
'analyze:ci': `bun run node_modules/arckode-framework/cli/index.ts analyze --ci`,
|
|
177
|
-
},
|
|
178
|
-
dependencies: {
|
|
179
|
-
'arckode-framework': 'latest',
|
|
180
|
-
'jsonwebtoken': '^9.0.0',
|
|
181
|
-
...dbDeps,
|
|
182
|
-
},
|
|
183
|
-
devDependencies: {
|
|
184
|
-
'@types/jsonwebtoken': '^9.0.0',
|
|
185
|
-
...dbDevDeps,
|
|
186
|
-
},
|
|
187
|
-
}, null, 2))
|
|
188
|
-
|
|
189
|
-
// .env según el DB elegido
|
|
190
|
-
const envContent = db === 'mysql'
|
|
191
|
-
? `PORT=3000\nDB_HOST=localhost\nDB_PORT=3306\nDB_USER=root\nDB_PASSWORD=secret\nDB_NAME=${name}\nJWT_SECRET=cambiar-en-produccion\nLOG_LEVEL=info\n`
|
|
192
|
-
: db === 'postgres'
|
|
193
|
-
? `PORT=3000\nDATABASE_URL=postgres://user:password@localhost:5432/${name}\nJWT_SECRET=cambiar-en-produccion\nLOG_LEVEL=info\n`
|
|
194
|
-
: `PORT=3000\nDB_PATH=./data/db.sqlite\nJWT_SECRET=cambiar-en-produccion\nLOG_LEVEL=info\n`
|
|
195
|
-
|
|
196
|
-
await writeFile(join(base, '.env'), envContent)
|
|
197
|
-
|
|
198
|
-
// .env.example
|
|
199
|
-
await writeFile(join(base, '.env.example'), `PORT=3000
|
|
200
|
-
DB_PATH=./data/db.sqlite
|
|
201
|
-
JWT_SECRET=cambiar-en-produccion
|
|
202
|
-
LOG_LEVEL=info
|
|
203
|
-
`)
|
|
204
|
-
|
|
205
|
-
// .gitignore
|
|
206
|
-
await writeFile(join(base, '.gitignore'), `node_modules/
|
|
207
|
-
dist/
|
|
208
|
-
data/
|
|
209
|
-
.env
|
|
210
|
-
`)
|
|
211
|
-
|
|
212
|
-
// Crear data/
|
|
213
|
-
await mkdir(join(base, 'data'), { recursive: true })
|
|
214
|
-
|
|
215
|
-
console.log(`\n✅ Proyecto "${name}" creado (DB: ${db})`)
|
|
216
|
-
console.log(`\n Siguientes pasos:`)
|
|
217
|
-
console.log(` cd ${name}`)
|
|
218
|
-
console.log(` bun install`)
|
|
219
|
-
console.log(` bun run dev`)
|
|
220
|
-
console.log(`\n La IA arranca leyendo CLAUDE.md → arckode.json`)
|
|
18
|
+
const db = (dbFlag === 'mysql' || dbFlag === 'postgres' ? dbFlag : 'sqlite') as 'sqlite' | 'mysql' | 'postgres'
|
|
19
|
+
const { newProject } = await import('./commands/new')
|
|
20
|
+
await newProject(name, db)
|
|
221
21
|
break
|
|
222
22
|
}
|
|
223
23
|
|
|
224
|
-
// ─── Generar módulo ───
|
|
225
24
|
case 'make:module': {
|
|
226
25
|
const name = args[0]
|
|
227
|
-
if (!name) { console.log('Uso:
|
|
228
|
-
|
|
229
|
-
const basePath = join(process.cwd(), 'src')
|
|
230
|
-
|
|
231
|
-
// Preguntar campos (versión simple con defaults)
|
|
26
|
+
if (!name) { console.log('Uso: arckode make:module <Nombre>'); return }
|
|
232
27
|
await generateModule({
|
|
233
28
|
name,
|
|
234
|
-
basePath,
|
|
29
|
+
basePath: join(process.cwd(), 'src'),
|
|
235
30
|
description: `Módulo de ${name}`,
|
|
236
31
|
fields: {
|
|
237
32
|
nombre: { type: 'string', required: true },
|
|
@@ -241,32 +36,20 @@ data/
|
|
|
241
36
|
break
|
|
242
37
|
}
|
|
243
38
|
|
|
244
|
-
// ─── Generar conector ───
|
|
245
39
|
case 'make:connector': {
|
|
246
40
|
const name = args[0]
|
|
247
41
|
const modules = args.slice(1)
|
|
248
|
-
if (!name || modules.length < 2) {
|
|
249
|
-
|
|
250
|
-
return
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const basePath = join(process.cwd(), 'src')
|
|
254
|
-
await generateConnector({ name, basePath, modules })
|
|
42
|
+
if (!name || modules.length < 2) { console.log('Uso: arckode make:connector <nombre> <mod1> <mod2> ...'); return }
|
|
43
|
+
await generateConnector({ name, basePath: join(process.cwd(), 'src'), modules })
|
|
255
44
|
break
|
|
256
45
|
}
|
|
257
46
|
|
|
258
|
-
// ─── Analizar arquitectura ───
|
|
259
47
|
case 'analyze': {
|
|
260
48
|
const basePath = process.cwd()
|
|
261
49
|
const result = await analyzeProject(basePath)
|
|
262
|
-
|
|
263
|
-
// arckode.json siempre se regenera — es el snapshot que la IA lee primero
|
|
264
50
|
const manifest = buildManifest(result)
|
|
265
|
-
|
|
266
|
-
await writeFile(dest, JSON.stringify(manifest, null, 2))
|
|
267
|
-
|
|
51
|
+
await writeFile(join(basePath, 'arckode.json'), JSON.stringify(manifest, null, 2))
|
|
268
52
|
if (args.includes('--ci')) {
|
|
269
|
-
// Modo CI: sin output de consola, exit 1 si hay violaciones
|
|
270
53
|
if (!result.valid) process.exit(1)
|
|
271
54
|
} else {
|
|
272
55
|
printAnalysis(result)
|
|
@@ -276,29 +59,24 @@ data/
|
|
|
276
59
|
break
|
|
277
60
|
}
|
|
278
61
|
|
|
279
|
-
// ─── Generar auth ───
|
|
280
62
|
case 'make:auth': {
|
|
281
63
|
const { makeAuth } = await import('./commands/make-auth')
|
|
282
64
|
await makeAuth(join(process.cwd(), 'src'))
|
|
283
65
|
break
|
|
284
66
|
}
|
|
285
67
|
|
|
286
|
-
// ─── Generar helper puro ───
|
|
287
68
|
case 'make:helper': {
|
|
288
69
|
const helperName = args[0]
|
|
289
|
-
if (!helperName) { console.log('Uso:
|
|
70
|
+
if (!helperName) { console.log('Uso: arckode make:helper <nombre>'); return }
|
|
290
71
|
const { makeHelper } = await import('./commands/make-helper')
|
|
291
72
|
await makeHelper(helperName, join(process.cwd(), 'src'))
|
|
292
73
|
break
|
|
293
74
|
}
|
|
294
75
|
|
|
295
|
-
// ─── Generar adapter de librería externa ───
|
|
296
76
|
case 'make:adapter': {
|
|
297
|
-
const adapterName = args
|
|
298
|
-
const interfaceName = args[1]
|
|
77
|
+
const [adapterName, interfaceName] = args
|
|
299
78
|
if (!adapterName || !interfaceName) {
|
|
300
|
-
console.log('Uso:
|
|
301
|
-
console.log('Ejemplo: bun arckode.ts make:adapter Stripe PaymentAdapter')
|
|
79
|
+
console.log('Uso: arckode make:adapter <NombreAdapter> <NombreInterfaz>')
|
|
302
80
|
return
|
|
303
81
|
}
|
|
304
82
|
const { makeAdapter } = await import('./commands/make-adapter')
|
|
@@ -306,57 +84,47 @@ data/
|
|
|
306
84
|
break
|
|
307
85
|
}
|
|
308
86
|
|
|
309
|
-
// ─── Generar seed ───
|
|
310
87
|
case 'make:seed': {
|
|
311
88
|
const seedName = args[0]
|
|
312
|
-
if (!seedName) { console.log('Uso:
|
|
89
|
+
if (!seedName) { console.log('Uso: arckode make:seed <Nombre>'); return }
|
|
313
90
|
const { makeSeed } = await import('./commands/make-seed')
|
|
314
91
|
await makeSeed(seedName, join(process.cwd(), 'src'))
|
|
315
92
|
break
|
|
316
93
|
}
|
|
317
94
|
|
|
318
|
-
// ─── Ejecutar seeds ───
|
|
319
95
|
case 'db:seed': {
|
|
320
96
|
const { dbSeed } = await import('./commands/db-seed')
|
|
321
97
|
await dbSeed(args[0])
|
|
322
98
|
break
|
|
323
99
|
}
|
|
324
100
|
|
|
325
|
-
// ─── Generar migración ───
|
|
326
101
|
case 'make:migration': {
|
|
327
102
|
const migName = args[0]
|
|
328
|
-
if (!migName) { console.log('Uso:
|
|
103
|
+
if (!migName) { console.log('Uso: arckode make:migration <nombre>'); return }
|
|
329
104
|
const { makeMigration } = await import('./commands/make-migration')
|
|
330
105
|
await makeMigration(migName, join(process.cwd(), 'src'))
|
|
331
106
|
break
|
|
332
107
|
}
|
|
333
108
|
|
|
334
|
-
// ─── Nuevo frontend SPA ───
|
|
335
109
|
case 'new:frontend': {
|
|
336
110
|
const feName = args[0] ?? 'frontend'
|
|
337
111
|
const dest = join(process.cwd(), feName)
|
|
112
|
+
const { mkdir, cp } = await import('node:fs/promises')
|
|
338
113
|
await mkdir(dest, { recursive: true })
|
|
339
|
-
// Copiar templates del frontend
|
|
340
|
-
const { cp } = await import('node:fs/promises')
|
|
341
114
|
const templateDir = join(import.meta.dirname, '..', 'templates', 'frontend')
|
|
342
115
|
await cp(templateDir, dest, { recursive: true })
|
|
343
116
|
console.log(`✅ Frontend SPA creado en ${dest}`)
|
|
344
|
-
console.log(` cd ${feName}`)
|
|
345
|
-
console.log(` npm install`)
|
|
346
|
-
console.log(` npm run dev`)
|
|
347
117
|
break
|
|
348
118
|
}
|
|
349
119
|
|
|
350
|
-
// ─── Generar módulo frontend ───
|
|
351
120
|
case 'make:page': {
|
|
352
121
|
const pageName = args[0]
|
|
353
|
-
if (!pageName) { console.log('Uso:
|
|
122
|
+
if (!pageName) { console.log('Uso: arckode make:page <Nombre>'); return }
|
|
354
123
|
const { makeFrontendModule } = await import('./commands/make-frontend-module')
|
|
355
124
|
await makeFrontendModule(pageName, join(process.cwd(), 'src'))
|
|
356
125
|
break
|
|
357
126
|
}
|
|
358
127
|
|
|
359
|
-
// ─── Generar API clients ───
|
|
360
128
|
case 'generate:api': {
|
|
361
129
|
const fePath = args[0] ?? join(process.cwd(), '..', 'frontend')
|
|
362
130
|
const { generateApiClient } = await import('./commands/generate-api-client')
|
|
@@ -364,182 +132,67 @@ data/
|
|
|
364
132
|
break
|
|
365
133
|
}
|
|
366
134
|
|
|
367
|
-
// ─── Listar rutas (análisis estático) ───
|
|
368
135
|
case 'routes': {
|
|
369
|
-
const {
|
|
370
|
-
|
|
371
|
-
const modulesPath = join(process.cwd(), 'src', 'modules')
|
|
372
|
-
|
|
373
|
-
if (!existsSync(modulesPath)) {
|
|
374
|
-
console.log('❌ No se encuentra src/modules/')
|
|
375
|
-
break
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const methods = ['get', 'post', 'put', 'patch', 'delete'] as const
|
|
379
|
-
const methodColors: Record<string, string> = {
|
|
380
|
-
get: '\x1b[32m',
|
|
381
|
-
post: '\x1b[33m',
|
|
382
|
-
put: '\x1b[34m',
|
|
383
|
-
patch: '\x1b[35m',
|
|
384
|
-
delete: '\x1b[31m',
|
|
385
|
-
}
|
|
386
|
-
const reset = '\x1b[0m'
|
|
387
|
-
|
|
388
|
-
// Recolecta todos los .ts de un directorio recursivamente
|
|
389
|
-
async function collectTsFiles(dir: string): Promise<string[]> {
|
|
390
|
-
const files: string[] = []
|
|
391
|
-
if (!existsSync(dir)) return files
|
|
392
|
-
for (const entry of await readdir(dir)) {
|
|
393
|
-
const full = join(dir, entry)
|
|
394
|
-
const s = await stat(full)
|
|
395
|
-
if (s.isDirectory()) files.push(...await collectTsFiles(full))
|
|
396
|
-
else if (entry.endsWith('.ts') && !entry.endsWith('.test.ts') && !entry.endsWith('.d.ts')) files.push(full)
|
|
397
|
-
}
|
|
398
|
-
return files
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Extrae rutas de un bloque de código fuente — deduplica por método+path
|
|
402
|
-
function extractRoutes(src: string, seen: Set<string>): string[] {
|
|
403
|
-
const found: string[] = []
|
|
404
|
-
for (const method of methods) {
|
|
405
|
-
// Soporta comillas simples, dobles y backticks sin interpolación
|
|
406
|
-
const re = new RegExp(`router\\.${method}\\s*\\(\\s*['"\`]([^'"\`\${}]+)['"\`]`, 'g')
|
|
407
|
-
let match: RegExpExecArray | null
|
|
408
|
-
while ((match = re.exec(src)) !== null) {
|
|
409
|
-
const key = `${method}:${match[1]}`
|
|
410
|
-
if (seen.has(key)) continue
|
|
411
|
-
seen.add(key)
|
|
412
|
-
const color = methodColors[method]
|
|
413
|
-
found.push(` ${color}${method.toUpperCase().padEnd(7)}${reset} ${match[1]}`)
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
return found
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
console.log('\n📋 Rutas registradas (análisis estático)\n')
|
|
420
|
-
|
|
421
|
-
const moduleDirs = await readdir(modulesPath)
|
|
422
|
-
let total = 0
|
|
423
|
-
const globalSeen = new Set<string>()
|
|
424
|
-
|
|
425
|
-
for (const mod of moduleDirs) {
|
|
426
|
-
const modDir = join(modulesPath, mod)
|
|
427
|
-
const s = await stat(modDir).catch(() => null)
|
|
428
|
-
if (!s?.isDirectory()) continue
|
|
429
|
-
|
|
430
|
-
const files = await collectTsFiles(modDir)
|
|
431
|
-
const seen = new Set<string>()
|
|
432
|
-
const routes: string[] = []
|
|
433
|
-
|
|
434
|
-
for (const file of files) {
|
|
435
|
-
const src = await readFile(file, 'utf-8')
|
|
436
|
-
routes.push(...extractRoutes(src, seen))
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Marcar en el set global para no duplicar con composition-root
|
|
440
|
-
for (const key of seen) globalSeen.add(key)
|
|
441
|
-
|
|
442
|
-
if (routes.length > 0) {
|
|
443
|
-
console.log(`[${mod}]`)
|
|
444
|
-
routes.forEach(r => console.log(r))
|
|
445
|
-
console.log()
|
|
446
|
-
total += routes.length
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Rutas globales en composition-root (no registradas por ningún módulo)
|
|
451
|
-
const crPath = join(process.cwd(), 'src', 'composition-root.ts')
|
|
452
|
-
if (existsSync(crPath)) {
|
|
453
|
-
const src = await readFile(crPath, 'utf-8')
|
|
454
|
-
const globalRoutes: string[] = []
|
|
455
|
-
for (const method of methods) {
|
|
456
|
-
// Soporta comillas simples, dobles y backticks sin interpolación
|
|
457
|
-
const re = new RegExp(`router\\.${method}\\s*\\(\\s*['"\`]([^'"\`\${}]+)['"\`]`, 'g')
|
|
458
|
-
let match: RegExpExecArray | null
|
|
459
|
-
while ((match = re.exec(src)) !== null) {
|
|
460
|
-
const key = `${method}:${match[1]}`
|
|
461
|
-
if (globalSeen.has(key)) continue
|
|
462
|
-
globalSeen.add(key)
|
|
463
|
-
const color = methodColors[method]
|
|
464
|
-
globalRoutes.push(` ${color}${method.toUpperCase().padEnd(7)}${reset} ${match[1]}`)
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
if (globalRoutes.length > 0) {
|
|
468
|
-
console.log('[global]')
|
|
469
|
-
globalRoutes.forEach(r => console.log(r))
|
|
470
|
-
console.log()
|
|
471
|
-
total += globalRoutes.length
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
console.log(`Total: ${total} ruta(s)`)
|
|
136
|
+
const { routes } = await import('./commands/routes')
|
|
137
|
+
await routes(process.cwd())
|
|
476
138
|
break
|
|
477
139
|
}
|
|
478
140
|
|
|
479
|
-
// ─── Ejecutar migraciones ───
|
|
480
141
|
case 'db:migrate': {
|
|
481
|
-
const direction = args[0] === 'down' ? 'down' : 'up'
|
|
482
142
|
const { dbMigrate } = await import('./commands/db-migrate')
|
|
483
|
-
|
|
143
|
+
const validActions = ['up', 'down', 'fresh', 'reset', 'refresh', 'status'] as const
|
|
144
|
+
const action = (args[0] ?? 'up') as typeof validActions[number]
|
|
145
|
+
if (!(validActions as readonly string[]).includes(action)) {
|
|
146
|
+
console.error(`❌ Acción desconocida: "${args[0]}"\n Opciones: ${validActions.join(' | ')}`)
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
await dbMigrate(action)
|
|
484
150
|
break
|
|
485
151
|
}
|
|
486
152
|
|
|
487
153
|
default: {
|
|
488
|
-
|
|
154
|
+
printHelp()
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function printHelp(): void {
|
|
160
|
+
console.log(`
|
|
489
161
|
╔═══════════════════════════════════════════╗
|
|
490
162
|
║ Arckode Framework — Tooling ║
|
|
491
163
|
║ Para IA · Modular · SOLID · Predictible ║
|
|
492
164
|
╚═══════════════════════════════════════════╝
|
|
493
165
|
|
|
494
166
|
BACKEND:
|
|
495
|
-
arckode new <proyecto>
|
|
496
|
-
arckode make:auth
|
|
497
|
-
arckode make:module <Nombre>
|
|
498
|
-
arckode make:connector <n> <m1> <m2>
|
|
499
|
-
arckode make:seed <Nombre>
|
|
500
|
-
arckode make:migration <nombre>
|
|
501
|
-
arckode make:helper <nombre>
|
|
167
|
+
arckode new <proyecto> Crear proyecto backend completo
|
|
168
|
+
arckode make:auth Generar autenticación (login, register, JWT)
|
|
169
|
+
arckode make:module <Nombre> Generar módulo completo
|
|
170
|
+
arckode make:connector <n> <m1> <m2> Conectar módulos
|
|
171
|
+
arckode make:seed <Nombre> Generar seed
|
|
172
|
+
arckode make:migration <nombre> Generar migración
|
|
173
|
+
arckode make:helper <nombre> Generar helper puro
|
|
502
174
|
arckode make:adapter <Adapter> <Interfaz> Generar adapter de librería externa
|
|
503
175
|
|
|
504
|
-
FRONTEND (SPA separado):
|
|
505
|
-
arckode new:frontend [nombre] Crear proyecto frontend Vue 3 + Vite + TypeScript
|
|
506
|
-
arckode make:page <Nombre> Generar módulo frontend (api, composable, components, page, router)
|
|
507
|
-
arckode generate:api [frontend] Generar API clients desde modelos del backend
|
|
508
|
-
|
|
509
|
-
MONOLITO:
|
|
510
|
-
Usar new y después new:frontend dentro del proyecto. El servidor sirve API + frontend.
|
|
511
|
-
|
|
512
176
|
BASE DE DATOS:
|
|
513
|
-
arckode db:migrate
|
|
514
|
-
arckode db:migrate down
|
|
515
|
-
arckode db:
|
|
177
|
+
arckode db:migrate Aplicar migraciones pendientes
|
|
178
|
+
arckode db:migrate down Revertir la última migración
|
|
179
|
+
arckode db:migrate fresh Drop all + re-migrar desde cero
|
|
180
|
+
arckode db:migrate reset Revertir TODAS las migraciones
|
|
181
|
+
arckode db:migrate refresh Reset + migrate (desarrollo)
|
|
182
|
+
arckode db:migrate status Ver estado de cada migración
|
|
183
|
+
arckode db:seed Listar seeds disponibles
|
|
184
|
+
arckode db:seed --all Ejecutar todos los seeds
|
|
185
|
+
arckode db:seed <nombre> Ejecutar un seed específico
|
|
186
|
+
|
|
187
|
+
FRONTEND:
|
|
188
|
+
arckode new:frontend [nombre] Crear proyecto frontend Vue 3 + Vite
|
|
189
|
+
arckode make:page <Nombre> Generar módulo frontend
|
|
190
|
+
arckode generate:api [frontend] Generar API clients
|
|
516
191
|
|
|
517
192
|
ANÁLISIS:
|
|
518
|
-
arckode analyze
|
|
519
|
-
arckode routes
|
|
520
|
-
|
|
521
|
-
Ejemplo completo (SPA separado):
|
|
522
|
-
arckode new mi-api # Backend
|
|
523
|
-
cd mi-api
|
|
524
|
-
arckode make:auth # Auth
|
|
525
|
-
arckode make:module Productos # CRUD
|
|
526
|
-
arckode make:connector pedidos productos
|
|
527
|
-
bun run src/composition-root.ts # Iniciar API
|
|
528
|
-
|
|
529
|
-
# En otra terminal:
|
|
530
|
-
arckode new:frontend frontend # Frontend SPA
|
|
531
|
-
cd frontend
|
|
532
|
-
npm install && npm run dev # Iniciar en :5173
|
|
533
|
-
|
|
534
|
-
Ejemplo completo (Monolito):
|
|
535
|
-
arckode nuevo monito-app
|
|
536
|
-
cd monito-app
|
|
537
|
-
arckode make:auth
|
|
538
|
-
arckode make:module Productos
|
|
539
|
-
# El composition-root ya incluye serveStatic para el frontend
|
|
193
|
+
arckode analyze Analizar arquitectura
|
|
194
|
+
arckode routes Listar todas las rutas
|
|
540
195
|
`)
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
196
|
}
|
|
544
197
|
|
|
545
198
|
main().catch(console.error)
|