arckode-framework 1.3.2 → 1.4.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 (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
@@ -1,121 +1,245 @@
1
- // cli/commands/db-migrate.ts — Ejecuta migraciones SQL versionadas
2
- // Corre los archivos de src/migrations/*.ts que tienen up() / down()
3
- // Usa _arckode_migrations para rastrear cuáles ya se aplicaron
4
-
5
- import Database from 'better-sqlite3'
6
- import { readdir, readFile } from 'node:fs/promises'
7
- import { join, resolve } from 'node:path'
1
+ // cli/commands/db-migrate.ts — Migraciones SQL versionadas (multi-DB)
2
+ // Soporta SQLite (bun:sqlite nativo), MySQL y PostgreSQL
3
+ // Detecta BD desde .env: DATABASE_URL (postgres), DB_HOST (mysql), DB_PATH (sqlite)
4
+ //
5
+ // Acciones disponibles:
6
+ // up — aplica migraciones pendientes (default)
7
+ // down — revierte la última migración aplicada
8
+ // fresh — dropea TODAS las tablas y re-corre todo desde cero
9
+ // reset — revierte TODAS las migraciones (newest first)
10
+ // refresh — reset + up (equivalente a migrate:refresh de Laravel)
11
+ // status — muestra el estado de cada migración
12
+
13
+ import { readdir } from 'node:fs/promises'
14
+ import { join } from 'node:path'
8
15
  import { existsSync } from 'node:fs'
16
+ import { parseEnv, createCliDbRunner, type CliDbRunner } from './db-utils'
17
+
18
+ // Interfaz que reciben up() y down() en cada archivo de migración.
19
+ // Importá este tipo en tus migraciones para evitar usar `any`:
20
+ // import type { MigrationRunner } from 'arckode-framework/cli/commands/db-migrate'
21
+ export interface MigrationRunner {
22
+ run(sql: string, params?: unknown[]): Promise<void>
23
+ exec(sql: string): Promise<void>
24
+ }
9
25
 
10
- async function readDbPath(basePath: string): Promise<string> {
11
- const envFile = join(basePath, '.env')
12
- if (existsSync(envFile)) {
13
- const content = await readFile(envFile, 'utf-8')
14
- const match = content.match(/^DB_PATH=(.+)$/m)
15
- if (match?.[1]) return match[1].trim()
16
- }
17
- return './data/db.sqlite'
26
+ export type MigrateAction = 'up' | 'down' | 'fresh' | 'reset' | 'refresh' | 'status'
27
+
28
+ // ─── Tracking table (_arckode_migrations) ────────────────────────────────────
29
+
30
+ async function ensureTable(db: CliDbRunner): Promise<void> {
31
+ await db.exec(`
32
+ CREATE TABLE IF NOT EXISTS _arckode_migrations (
33
+ name VARCHAR(255) PRIMARY KEY,
34
+ direction VARCHAR(10) NOT NULL DEFAULT 'up',
35
+ runAt VARCHAR(50) NOT NULL
36
+ )
37
+ `)
18
38
  }
19
39
 
20
- interface DbRunner {
21
- run(sql: string, params?: unknown[]): void
22
- exec(sql: string): void
40
+ async function getApplied(db: CliDbRunner): Promise<Set<string>> {
41
+ const rows = await db.query<{ name: string }>(
42
+ 'SELECT name FROM _arckode_migrations WHERE direction = ?', ['up'],
43
+ )
44
+ return new Set(rows.map(r => r.name))
23
45
  }
24
46
 
25
- export async function dbMigrate(direction: 'up' | 'down' = 'up') {
26
- const basePath = process.cwd()
27
- const migrationsDir = join(basePath, 'src', 'migrations')
47
+ async function getAllAppliedOrdered(db: CliDbRunner): Promise<string[]> {
48
+ const rows = await db.query<{ name: string }>(
49
+ 'SELECT name FROM _arckode_migrations WHERE direction = ? ORDER BY runAt ASC', ['up'],
50
+ )
51
+ return rows.map(r => r.name)
52
+ }
28
53
 
29
- if (!existsSync(migrationsDir)) {
30
- console.error('❌ No se encuentra src/migrations/')
31
- console.error(' Creá una con: arckode make:migration <nombre>')
54
+ async function getAppliedAt(db: CliDbRunner, name: string): Promise<string> {
55
+ const rows = await db.query<{ runAt: string }>(
56
+ 'SELECT runAt FROM _arckode_migrations WHERE name = ?', [name],
57
+ )
58
+ return rows[0]?.runAt ?? '?'
59
+ }
60
+
61
+ async function markApplied(db: CliDbRunner, name: string): Promise<void> {
62
+ await db.run(
63
+ 'INSERT INTO _arckode_migrations (name, direction, runAt) VALUES (?, ?, ?)',
64
+ [name, 'up', new Date().toISOString()],
65
+ )
66
+ }
67
+
68
+ async function markReverted(db: CliDbRunner, name: string): Promise<void> {
69
+ await db.run('DELETE FROM _arckode_migrations WHERE name = ?', [name])
70
+ }
71
+
72
+ async function getLastApplied(db: CliDbRunner): Promise<string | undefined> {
73
+ const rows = await db.query<{ name: string }>(
74
+ 'SELECT name FROM _arckode_migrations WHERE direction = ? ORDER BY runAt DESC LIMIT 1', ['up'],
75
+ )
76
+ return rows[0]?.name
77
+ }
78
+
79
+ // ─── Helpers de ejecución ────────────────────────────────────────────────────
80
+
81
+ async function applyOne(db: CliDbRunner, file: string, filePath: string): Promise<void> {
82
+ const migration = await import(filePath)
83
+ if (typeof migration.up !== 'function') {
84
+ console.warn(`⚠️ ${file} no exporta up() — saltando`)
32
85
  return
33
86
  }
87
+ console.log(` ▶ Aplicando: ${file}`)
88
+ await db.inTransaction(async () => {
89
+ await migration.up(db as MigrationRunner)
90
+ await markApplied(db, file)
91
+ })
92
+ console.log(` ✅ ${file}`)
93
+ }
34
94
 
35
- const dbRelPath = await readDbPath(basePath)
36
- const dbAbsPath = resolve(basePath, dbRelPath)
95
+ async function revertOne(db: CliDbRunner, name: string, migrationsDir: string): Promise<void> {
96
+ const filePath = join(migrationsDir, name)
97
+ if (!existsSync(filePath)) {
98
+ console.warn(` ⚠️ Archivo ${name} no existe — saltando revert`)
99
+ await markReverted(db, name)
100
+ return
101
+ }
102
+ const migration = await import(filePath)
103
+ if (typeof migration.down !== 'function') {
104
+ console.warn(` ⚠️ ${name} no exporta down() — saltando`)
105
+ return
106
+ }
107
+ console.log(` ▶ Revirtiendo: ${name}`)
108
+ await db.inTransaction(async () => {
109
+ await migration.down(db as MigrationRunner)
110
+ await markReverted(db, name)
111
+ })
112
+ console.log(` ✅ Revertida: ${name}`)
113
+ }
37
114
 
38
- const db = new Database(dbAbsPath)
115
+ // ─── Acciones ────────────────────────────────────────────────────────────────
39
116
 
40
- db.exec(`
41
- CREATE TABLE IF NOT EXISTS _arckode_migrations (
42
- name TEXT PRIMARY KEY,
43
- direction TEXT NOT NULL,
44
- runAt TEXT NOT NULL
45
- )
46
- `)
117
+ async function runUp(db: CliDbRunner, files: string[], migrationsDir: string): Promise<void> {
118
+ const applied = await getApplied(db)
119
+ const pending = files.filter(f => !applied.has(f))
47
120
 
48
- const files = (await readdir(migrationsDir))
49
- .filter(f => f.endsWith('.ts') || f.endsWith('.js'))
50
- .sort()
51
-
52
- if (files.length === 0) {
53
- console.log('📭 No hay archivos en src/migrations/')
54
- db.close()
121
+ if (pending.length === 0) {
122
+ console.log('✅ No hay migraciones pendientes')
55
123
  return
56
124
  }
57
125
 
58
- const applied = new Set<string>(
59
- (db.prepare('SELECT name FROM _arckode_migrations WHERE direction = ?').all('up') as { name: string }[])
60
- .map(r => r.name)
61
- )
126
+ for (const file of pending) {
127
+ await applyOne(db, file, join(migrationsDir, file))
128
+ }
129
+ console.log(`\n${pending.length} migración(es) aplicada(s)`)
130
+ }
131
+
132
+ async function runDown(db: CliDbRunner, migrationsDir: string): Promise<void> {
133
+ const last = await getLastApplied(db)
134
+ if (!last) { console.log('📭 No hay migraciones para revertir'); return }
135
+ await revertOne(db, last, migrationsDir)
136
+ }
62
137
 
63
- const runner: DbRunner = {
64
- run(sql, params = []) { db.prepare(sql).run(...params) },
65
- exec(sql) { db.exec(sql) },
138
+ async function runReset(db: CliDbRunner, migrationsDir: string): Promise<void> {
139
+ const applied = await getAllAppliedOrdered(db)
140
+ if (applied.length === 0) { console.log('📭 No hay migraciones para revertir'); return }
141
+
142
+ console.log(`🔄 Revirtiendo ${applied.length} migración(es)...\n`)
143
+ for (const name of [...applied].reverse()) {
144
+ await revertOne(db, name, migrationsDir)
66
145
  }
146
+ console.log(`\n${applied.length} migración(es) revertida(s)`)
147
+ }
67
148
 
68
- if (direction === 'up') {
69
- const pending = files.filter(f => !applied.has(f))
149
+ async function runFresh(db: CliDbRunner, files: string[], migrationsDir: string): Promise<void> {
150
+ console.log('🗑 Dropeando todas las tablas...')
151
+ await db.dropAllTables()
152
+ console.log('✅ Tablas dropeadas\n')
70
153
 
71
- if (pending.length === 0) {
72
- console.log('✅ No hay migraciones pendientes')
73
- db.close()
74
- return
75
- }
154
+ await ensureTable(db)
76
155
 
77
- for (const file of pending) {
78
- const filePath = join(migrationsDir, file)
79
- const migration = await import(filePath)
156
+ console.log(`▶ Aplicando ${files.length} migración(es)...\n`)
157
+ for (const file of files) {
158
+ await applyOne(db, file, join(migrationsDir, file))
159
+ }
160
+ console.log(`\n${files.length} migración(es) aplicada(s)`)
161
+ }
80
162
 
81
- if (typeof migration.up !== 'function') {
82
- console.warn(`⚠️ ${file} no exporta up() — saltando`)
83
- continue
84
- }
163
+ async function runRefresh(db: CliDbRunner, files: string[], migrationsDir: string): Promise<void> {
164
+ console.log('🔄 Refresh: reset + migrate\n')
165
+ await runReset(db, migrationsDir)
166
+ console.log()
167
+ await ensureTable(db)
168
+ await runUp(db, files, migrationsDir)
169
+ }
85
170
 
86
- console.log(`▶ Aplicando: ${file}`)
87
- await migration.up(runner)
88
- db.prepare('INSERT INTO _arckode_migrations (name, direction, runAt) VALUES (?, ?, ?)')
89
- .run(file, 'up', new Date().toISOString())
90
- console.log(`✅ ${file}`)
91
- }
171
+ async function runStatus(db: CliDbRunner, files: string[]): Promise<void> {
172
+ const applied = await getApplied(db)
92
173
 
93
- console.log(`\n${pending.length} migración(es) aplicada(s)`)
94
- } else {
95
- // down: revertir la última aplicada
96
- const last = db.prepare('SELECT name FROM _arckode_migrations WHERE direction = ? ORDER BY runAt DESC LIMIT 1')
97
- .get('up') as { name: string } | undefined
174
+ const COL_NAME = Math.max(40, ...files.map(f => f.length)) + 2
175
+ const header = ` ${'Migración'.padEnd(COL_NAME)} ${'Estado'.padEnd(10)} Aplicada en`
176
+ const sep = ` ${'-'.repeat(COL_NAME)} ${'-'.repeat(10)} ${'-'.repeat(26)}`
98
177
 
99
- if (!last) {
100
- console.log('📭 No hay migraciones para revertir')
101
- db.close()
102
- return
103
- }
178
+ console.log('\n📋 Estado de migraciones\n')
179
+ console.log(header)
180
+ console.log(sep)
104
181
 
105
- const filePath = join(migrationsDir, last.name)
106
- const migration = await import(filePath)
182
+ let appliedCount = 0
183
+ let pendingCount = 0
184
+
185
+ for (const file of files) {
186
+ const isApplied = applied.has(file)
187
+ const status = isApplied ? '✅ aplicada' : '⏳ pendiente'
188
+ const when = isApplied ? await getAppliedAt(db, file) : '—'
189
+ console.log(` ${file.padEnd(COL_NAME)} ${status.padEnd(10)} ${when}`)
190
+ if (isApplied) appliedCount++; else pendingCount++
191
+ }
107
192
 
108
- if (typeof migration.down !== 'function') {
109
- console.error(`❌ ${last.name} no exporta down()`)
110
- db.close()
111
- return
193
+ // Migraciones aplicadas cuyo archivo ya no existe (huérfanas)
194
+ for (const name of applied) {
195
+ if (!files.includes(name)) {
196
+ console.log(` ${name.padEnd(COL_NAME)} ⚠️ sin archivo (archivo eliminado del repo)`)
112
197
  }
198
+ }
199
+
200
+ console.log(sep)
201
+ console.log(`\n ${appliedCount} aplicada(s) · ${pendingCount} pendiente(s)\n`)
202
+ }
113
203
 
114
- console.log(`▶ Revirtiendo: ${last.name}`)
115
- await migration.down(runner)
116
- db.prepare('DELETE FROM _arckode_migrations WHERE name = ?').run(last.name)
117
- console.log(`✅ Revertida: ${last.name}`)
204
+ // ─── Comando principal ────────────────────────────────────────────────────────
205
+
206
+ export async function dbMigrate(action: MigrateAction = 'up'): Promise<void> {
207
+ const basePath = process.cwd()
208
+ const migrationsDir = join(basePath, 'src', 'migrations')
209
+
210
+ if (!existsSync(migrationsDir)) {
211
+ console.error('❌ No se encuentra src/migrations/')
212
+ console.error(' Creá una con: arckode make:migration <nombre>')
213
+ return
118
214
  }
119
215
 
120
- db.close()
216
+ const files = (await readdir(migrationsDir))
217
+ .filter(f => f.endsWith('.ts') || f.endsWith('.js'))
218
+ .sort()
219
+
220
+ const env = parseEnv(basePath)
221
+ const db = await createCliDbRunner(env, basePath)
222
+
223
+ try {
224
+ // fresh no llama ensureTable antes de dropear (dropeará todo incluyendo la tracking table)
225
+ if (action !== 'fresh') await ensureTable(db)
226
+
227
+ switch (action) {
228
+ case 'up': await runUp(db, files, migrationsDir); break
229
+ case 'down': await runDown(db, migrationsDir); break
230
+ case 'reset': await runReset(db, migrationsDir); break
231
+ case 'fresh': await runFresh(db, files, migrationsDir); break
232
+ case 'refresh': await runRefresh(db, files, migrationsDir); break
233
+ case 'status': {
234
+ if (files.length === 0) { console.log('📭 No hay migraciones en src/migrations/'); break }
235
+ await runStatus(db, files)
236
+ break
237
+ }
238
+ default:
239
+ console.error(`❌ Acción desconocida: "${action}"`)
240
+ console.error(' Opciones: up | down | fresh | reset | refresh | status')
241
+ }
242
+ } finally {
243
+ await db.close()
244
+ }
121
245
  }
@@ -1,54 +1,119 @@
1
- // cli/commands/db-seed.ts — Comando arckode db:seed
2
- // Ejecuta los seeds definidos en el composition root
1
+ // cli/commands/db-seed.ts — Ejecuta seeds en src/seeds/
2
+ // Conecta a la BD (misma detección que db:migrate) y corre cada seed file.
3
+ // Cada archivo exporta funciones que reciben un proxy del ORM con create().
4
+ // Úsalo para cargar datos de prueba o datos iniciales de producción.
3
5
 
4
- import { readFile } from 'node:fs/promises'
6
+ import { readdir } from 'node:fs/promises'
5
7
  import { join } from 'node:path'
6
8
  import { existsSync } from 'node:fs'
9
+ import { parseEnv, createCliDbRunner, type CliDbRunner } from './db-utils'
7
10
 
8
- export async function dbSeed(seedName?: string) {
9
- const basePath = process.cwd()
10
- const crPath = join(basePath, 'src', 'composition-root.ts')
11
+ // Interfaz pública para los archivos de seed auto-generados.
12
+ // Importá este tipo en tus seeds para evitar usar `any`:
13
+ // import type { SeedOrm } from 'arckode-framework/cli/commands/db-seed'
14
+ export interface SeedOrm {
15
+ create(modelName: string, data: Record<string, unknown>): Promise<Record<string, unknown>>
16
+ }
11
17
 
12
- if (!existsSync(crPath)) {
13
- console.error('❌ No se encuentra src/composition-root.ts')
14
- console.error(' Ejecutá este comando desde la raíz del proyecto.')
15
- return
18
+ // Proxy mínimo que imita orm.create() para seeds auto-generados.
19
+ // Infiere el nombre de tabla convirtiendo el model name a lowercase.
20
+ // Para seeds que necesiten JOINs o lógica compleja, importá el composition root.
21
+ function makeSeedProxy(db: CliDbRunner): SeedOrm {
22
+ return {
23
+ async create(modelName: string, data: Record<string, unknown>): Promise<Record<string, unknown>> {
24
+ const table = modelName.toLowerCase()
25
+ const record: Record<string, unknown> = {
26
+ id: (data.id as string | undefined) ?? crypto.randomUUID(),
27
+ ...data,
28
+ createdAt: (data.createdAt as string | undefined) ?? new Date().toISOString(),
29
+ updatedAt: new Date().toISOString(),
30
+ }
31
+ const cols = Object.keys(record)
32
+ const vals = Object.values(record)
33
+ const placeholders = cols.map(() => '?').join(', ')
34
+ await db.run(
35
+ `INSERT INTO ${table} (${cols.join(', ')}) VALUES (${placeholders})`,
36
+ vals,
37
+ )
38
+ return record
39
+ },
40
+ }
41
+ }
42
+
43
+ async function runSeedFile(filePath: string, db: CliDbRunner): Promise<void> {
44
+ const mod = await import(filePath)
45
+ const proxy = makeSeedProxy(db)
46
+ let ran = 0
47
+
48
+ for (const [key, value] of Object.entries(mod)) {
49
+ if (typeof value !== 'function') continue
50
+ // Convención: funciones seed* o run o default
51
+ if (!key.toLowerCase().startsWith('seed') && key !== 'run' && key !== 'default') continue
52
+
53
+ await (value as (orm: typeof proxy) => Promise<void>)(proxy)
54
+ ran++
55
+ }
56
+
57
+ if (ran === 0) {
58
+ console.warn(` ⚠️ No se encontró ninguna función seed en ${filePath}`)
59
+ console.warn(` Asegurate de exportar una función que empiece con "seed".`)
16
60
  }
61
+ }
62
+
63
+ export async function dbSeed(seedName?: string): Promise<void> {
64
+ const basePath = process.cwd()
65
+ const seedsDir = join(basePath, 'src', 'seeds')
17
66
 
18
- // Leer seeds/ y detectar archivos disponibles
19
- const seedsPath = join(basePath, 'src', 'seeds')
20
- if (!existsSync(seedsPath)) {
67
+ if (!existsSync(seedsDir)) {
21
68
  console.error('❌ No hay directorio src/seeds/')
22
69
  console.error(' Creá seeds con: arckode make:seed <Nombre>')
23
70
  return
24
71
  }
25
72
 
26
- const { readdir } = await import('node:fs/promises')
27
- const seedFiles = (await readdir(seedsPath)).filter(f => f.endsWith('.ts'))
73
+ const allFiles = (await readdir(seedsDir))
74
+ .filter(f => f.endsWith('.ts') || f.endsWith('.js'))
75
+ .sort()
28
76
 
29
- if (seedFiles.length === 0) {
77
+ if (allFiles.length === 0) {
30
78
  console.log('📭 No hay seeds para ejecutar.')
31
79
  return
32
80
  }
33
81
 
34
- if (seedName) {
35
- const file = seedFiles.find(f => f.startsWith(seedName))
36
- if (!file) {
37
- console.error(`❌ Seed "${seedName}" no encontrado.`)
38
- console.log(` Seeds disponibles: ${seedFiles.join(', ')}`)
39
- return
40
- }
41
- console.log(`🌱 Ejecutando seed: ${file}`)
42
- // La IA o el usuario debe agregar la lógica en composition-root según el seed
43
- console.log(` Para ejecutarlo, corré: RUN_SEEDS=true bun run src/composition-root.ts`)
82
+ // Sin argumento: listar + pedir confirmación o ejecutar todos
83
+ if (!seedName) {
84
+ console.log('🌱 Seeds disponibles:')
85
+ for (const f of allFiles) console.log(` - ${f.replace(/\.(ts|js)$/, '')}`)
86
+ console.log('\n Para ejecutar todos: arckode db:seed --all')
87
+ console.log(' Para ejecutar uno: arckode db:seed <nombre>')
88
+ return
89
+ }
90
+
91
+ const runAll = seedName === '--all'
92
+ const targets = runAll
93
+ ? allFiles
94
+ : allFiles.filter(f => f.startsWith(seedName))
95
+
96
+ if (targets.length === 0) {
97
+ console.error(`❌ Seed "${seedName}" no encontrado.`)
98
+ console.log(` Seeds disponibles: ${allFiles.map(f => f.replace(/\.(ts|js)$/, '')).join(', ')}`)
44
99
  return
45
100
  }
46
101
 
47
- console.log('🌱 Seeds disponibles:')
48
- for (const file of seedFiles) {
49
- console.log(` - ${file.replace('.ts', '')}`)
102
+ const env = parseEnv(basePath)
103
+ const db = await createCliDbRunner(env, basePath)
104
+
105
+ try {
106
+ for (const file of targets) {
107
+ const filePath = join(seedsDir, file)
108
+ console.log(`🌱 Ejecutando: ${file}`)
109
+ await runSeedFile(filePath, db)
110
+ console.log(`✅ ${file}`)
111
+ }
112
+ console.log(`\n${targets.length} seed(s) ejecutado(s)`)
113
+ } catch (err) {
114
+ console.error('❌ Error ejecutando seed:', err instanceof Error ? err.message : err)
115
+ throw err
116
+ } finally {
117
+ await db.close()
50
118
  }
51
- console.log('')
52
- console.log(' Para ejecutar todos: RUN_SEEDS=true bun run src/composition-root.ts')
53
- console.log(' Para ejecutar uno: bun arckode db:seed <nombre>')
54
119
  }