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.
Files changed (64) hide show
  1. package/adapters/jwt.ts +6 -4
  2. package/adapters/mysql.ts +7 -2
  3. package/adapters/postgres.ts +37 -0
  4. package/adapters/sqlite.ts +7 -1
  5. package/adapters/vendor.d.ts +48 -0
  6. package/cli/analyze/checks.ts +333 -0
  7. package/cli/analyze/index.ts +44 -0
  8. package/cli/analyze/report.ts +107 -0
  9. package/cli/analyze/types.ts +46 -0
  10. package/cli/analyze/utils.ts +36 -0
  11. package/cli/analyze.ts +2 -647
  12. package/cli/commands/db-migrate.ts +213 -89
  13. package/cli/commands/db-seed.ts +97 -32
  14. package/cli/commands/db-utils.ts +192 -0
  15. package/cli/commands/new.ts +175 -0
  16. package/cli/commands/routes.ts +94 -0
  17. package/cli/index.ts +57 -404
  18. package/cli/stubs/module/core.ts +162 -0
  19. package/cli/stubs/module/data.ts +171 -0
  20. package/cli/stubs/module/index.ts +5 -0
  21. package/cli/stubs/module/service.ts +198 -0
  22. package/cli/stubs/module/types.ts +12 -0
  23. package/cli/stubs/module-stub.ts +2 -552
  24. package/kernel/auth.ts +114 -0
  25. package/kernel/cache.ts +37 -0
  26. package/kernel/config.ts +129 -0
  27. package/kernel/container.ts +64 -0
  28. package/kernel/db/orm-migrate.ts +136 -0
  29. package/kernel/db/orm-repository.ts +45 -0
  30. package/kernel/db/orm-utils.ts +93 -0
  31. package/kernel/db/orm.ts +254 -0
  32. package/kernel/db/transactor.ts +17 -0
  33. package/kernel/db/types.ts +72 -0
  34. package/kernel/errors.ts +102 -0
  35. package/kernel/framework.default.ts +41 -0
  36. package/kernel/framework.ts +8 -2144
  37. package/kernel/http/router.ts +131 -0
  38. package/kernel/http/server.ts +303 -0
  39. package/kernel/http/types.ts +56 -0
  40. package/kernel/index.ts +25 -0
  41. package/kernel/logger.ts +50 -0
  42. package/kernel/middlewares.ts +19 -7
  43. package/kernel/modules/create-module.ts +5 -0
  44. package/kernel/modules/system.ts +149 -0
  45. package/kernel/modules/types.ts +46 -0
  46. package/kernel/seeds.ts +48 -0
  47. package/kernel/static.ts +11 -2
  48. package/kernel/testing.ts +8 -3
  49. package/kernel/validator.ts +116 -0
  50. package/modules/events/index.ts +19 -3
  51. package/modules/mail/index.ts +14 -2
  52. package/modules/storage/local-adapter.ts +19 -5
  53. package/modules/ws/index.ts +123 -18
  54. package/package.json +8 -11
  55. package/skills/auth/SKILL.md +36 -220
  56. package/skills/cli/SKILL.md +32 -251
  57. package/skills/config/SKILL.md +30 -239
  58. package/skills/connectors/SKILL.md +32 -295
  59. package/skills/helpers/SKILL.md +26 -195
  60. package/skills/middlewares/SKILL.md +30 -280
  61. package/skills/orm/SKILL.md +42 -349
  62. package/skills/realtime/SKILL.md +22 -297
  63. package/skills/services/SKILL.md +40 -183
  64. 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 para generación y análisis
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 { claudeMdStub } from './stubs/claude-md-stub'
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: bun arckode.ts new <nombre-proyecto> [--db=sqlite|mysql|postgres]'); return }
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
- dbFlag === 'mysql' || dbFlag === 'postgres' ? dbFlag : 'sqlite'
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: bun arckode.ts make:module <Nombre>'); return }
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
- console.log('Uso: bun arckode.ts make:connector <nombre> <mod1> <mod2> ...')
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
- const dest = join(basePath, 'arckode.json')
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: bun arckode.ts make:helper <nombre>'); return }
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[0]
298
- const interfaceName = args[1]
77
+ const [adapterName, interfaceName] = args
299
78
  if (!adapterName || !interfaceName) {
300
- console.log('Uso: bun arckode.ts make:adapter <NombreAdapter> <NombreInterfaz>')
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: bun arckode.ts make:seed <Nombre>'); return }
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: bun arckode.ts make:migration <nombre>'); return }
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: bun arckode.ts make:page <Nombre>'); return }
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 { readdir, readFile, stat } = await import('node:fs/promises')
370
- const { existsSync } = await import('node:fs')
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
- await dbMigrate(direction)
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
- console.log(`
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> Crear proyecto backend completo
496
- arckode make:auth Generar autenticación (login, register, JWT)
497
- arckode make:module <Nombre> Generar módulo completo (+ migration + seed + tests)
498
- arckode make:connector <n> <m1> <m2> Conectar módulos
499
- arckode make:seed <Nombre> Generar seed
500
- arckode make:migration <nombre> Generar migración
501
- arckode make:helper <nombre> Generar helper puro en shared/helpers/
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 Ejecutar migraciones pendientes (src/migrations/)
514
- arckode db:migrate down Revertir la última migración aplicada
515
- arckode db:seed Listar o ejecutar seeds disponibles
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 Analizar arquitectura (detecta violaciones)
519
- arckode routes Listar todas las rutas (análisis estático)
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)