@mantiq/cli 0.0.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 (34) hide show
  1. package/README.md +19 -0
  2. package/package.json +59 -0
  3. package/src/Command.ts +24 -0
  4. package/src/IO.ts +96 -0
  5. package/src/Kernel.ts +84 -0
  6. package/src/Parser.ts +62 -0
  7. package/src/bin.ts +61 -0
  8. package/src/commands/AboutCommand.ts +73 -0
  9. package/src/commands/GeneratorCommand.ts +58 -0
  10. package/src/commands/MakeCommandCommand.ts +39 -0
  11. package/src/commands/MakeControllerCommand.ts +58 -0
  12. package/src/commands/MakeEventCommand.ts +30 -0
  13. package/src/commands/MakeExceptionCommand.ts +25 -0
  14. package/src/commands/MakeFactoryCommand.ts +29 -0
  15. package/src/commands/MakeListenerCommand.ts +29 -0
  16. package/src/commands/MakeMiddlewareCommand.ts +23 -0
  17. package/src/commands/MakeMigrationCommand.ts +125 -0
  18. package/src/commands/MakeModelCommand.ts +150 -0
  19. package/src/commands/MakeObserverCommand.ts +32 -0
  20. package/src/commands/MakeProviderCommand.ts +27 -0
  21. package/src/commands/MakeRequestCommand.ts +29 -0
  22. package/src/commands/MakeRuleCommand.ts +38 -0
  23. package/src/commands/MakeSeederCommand.ts +22 -0
  24. package/src/commands/MakeTestCommand.ts +69 -0
  25. package/src/commands/MigrateCommand.ts +33 -0
  26. package/src/commands/MigrateFreshCommand.ts +47 -0
  27. package/src/commands/MigrateResetCommand.ts +33 -0
  28. package/src/commands/MigrateRollbackCommand.ts +28 -0
  29. package/src/commands/MigrateStatusCommand.ts +41 -0
  30. package/src/commands/RouteListCommand.ts +89 -0
  31. package/src/commands/SeedCommand.ts +42 -0
  32. package/src/commands/ServeCommand.ts +43 -0
  33. package/src/commands/TinkerCommand.ts +261 -0
  34. package/src/index.ts +42 -0
@@ -0,0 +1,33 @@
1
+ import { Command } from '../Command.ts'
2
+ import type { ParsedArgs } from '../Parser.ts'
3
+ import { Migrator } from '@mantiq/database'
4
+ import { getManager } from '@mantiq/database'
5
+
6
+ export class MigrateResetCommand extends Command {
7
+ override name = 'migrate:reset'
8
+ override description = 'Rollback all migrations'
9
+
10
+ override async handle(args: ParsedArgs): Promise<number> {
11
+ if (!args.flags['force'] && process.env['APP_ENV'] === 'production') {
12
+ this.io.error('Use --force to run in production.')
13
+ return 1
14
+ }
15
+
16
+ const connection = getManager().connection()
17
+ const migrator = new Migrator(connection, { migrationsPath: `${process.cwd()}/database/migrations` })
18
+
19
+ this.io.info('Resetting all migrations...')
20
+ const rolled = await migrator.reset()
21
+
22
+ if (rolled.length === 0) {
23
+ this.io.success('Nothing to reset.')
24
+ } else {
25
+ for (const name of rolled) {
26
+ this.io.twoColumn(` ${this.io.yellow('ROLLED BACK')}`, name, 16)
27
+ }
28
+ this.io.newLine()
29
+ this.io.success(`Reset ${rolled.length} migration${rolled.length > 1 ? 's' : ''}.`)
30
+ }
31
+ return 0
32
+ }
33
+ }
@@ -0,0 +1,28 @@
1
+ import { Command } from '../Command.ts'
2
+ import type { ParsedArgs } from '../Parser.ts'
3
+ import { Migrator } from '@mantiq/database'
4
+ import { getManager } from '@mantiq/database'
5
+
6
+ export class MigrateRollbackCommand extends Command {
7
+ override name = 'migrate:rollback'
8
+ override description = 'Rollback the last batch of migrations'
9
+
10
+ override async handle(_args: ParsedArgs): Promise<number> {
11
+ const connection = getManager().connection()
12
+ const migrator = new Migrator(connection, { migrationsPath: `${process.cwd()}/database/migrations` })
13
+
14
+ this.io.info('Rolling back last batch...')
15
+ const rolled = await migrator.rollback()
16
+
17
+ if (rolled.length === 0) {
18
+ this.io.success('Nothing to rollback.')
19
+ } else {
20
+ for (const name of rolled) {
21
+ this.io.twoColumn(` ${this.io.yellow('ROLLED BACK')}`, name, 16)
22
+ }
23
+ this.io.newLine()
24
+ this.io.success(`Rolled back ${rolled.length} migration${rolled.length > 1 ? 's' : ''}.`)
25
+ }
26
+ return 0
27
+ }
28
+ }
@@ -0,0 +1,41 @@
1
+ import { Command } from '../Command.ts'
2
+ import type { ParsedArgs } from '../Parser.ts'
3
+ import { Migrator } from '@mantiq/database'
4
+ import { getManager } from '@mantiq/database'
5
+
6
+ export class MigrateStatusCommand extends Command {
7
+ override name = 'migrate:status'
8
+ override description = 'Show the status of each migration'
9
+
10
+ override async handle(_args: ParsedArgs): Promise<number> {
11
+ const connection = getManager().connection()
12
+ const migrator = new Migrator(connection, { migrationsPath: `${process.cwd()}/database/migrations` })
13
+
14
+ const statuses = await migrator.status()
15
+
16
+ if (statuses.length === 0) {
17
+ this.io.info('No migrations found.')
18
+ return 0
19
+ }
20
+
21
+ this.io.heading('Migration Status')
22
+ this.io.newLine()
23
+
24
+ this.io.table(
25
+ ['Status', 'Migration', 'Batch'],
26
+ statuses.map((s) => [
27
+ s.ran ? this.io.green('Ran') : this.io.yellow('Pending'),
28
+ s.name,
29
+ s.batch !== null ? String(s.batch) : '',
30
+ ]),
31
+ )
32
+
33
+ const pending = statuses.filter((s) => !s.ran).length
34
+ if (pending > 0) {
35
+ this.io.newLine()
36
+ this.io.info(`${pending} pending migration${pending > 1 ? 's' : ''}.`)
37
+ }
38
+
39
+ return 0
40
+ }
41
+ }
@@ -0,0 +1,89 @@
1
+ import { Command } from '../Command.ts'
2
+ import type { ParsedArgs } from '../Parser.ts'
3
+
4
+ export class RouteListCommand extends Command {
5
+ override name = 'route:list'
6
+ override description = 'List all registered routes'
7
+ override usage = 'route:list [--method=GET] [--path=prefix]'
8
+
9
+ override async handle(args: ParsedArgs): Promise<number> {
10
+ try {
11
+ const entryPath = `${process.cwd()}/index.ts`
12
+ const mod = await import(entryPath)
13
+
14
+ const app = mod.default ?? mod.app
15
+ if (!app) {
16
+ this.io.error('No default export or "app" export found in index.ts')
17
+ return 1
18
+ }
19
+
20
+ // Resolve the router from the container
21
+ let router: any = null
22
+ try {
23
+ const { RouterImpl } = await import('@mantiq/core')
24
+ router = app.make?.(RouterImpl)
25
+ } catch {}
26
+ if (!router) {
27
+ try { router = app.make?.('router') } catch {}
28
+ }
29
+ if (!router || typeof router.routes !== 'function') {
30
+ this.io.error('Could not resolve router from the application container.')
31
+ return 1
32
+ }
33
+
34
+ let routes: any[] = router.routes()
35
+
36
+ // Filter by method
37
+ const methodFilter = args.flags['method'] as string | undefined
38
+ if (methodFilter) {
39
+ const m = methodFilter.toUpperCase()
40
+ routes = routes.filter((r: any) => r.method === m)
41
+ }
42
+
43
+ // Filter by path prefix
44
+ const pathFilter = args.flags['path'] as string | undefined
45
+ if (pathFilter) {
46
+ routes = routes.filter((r: any) => r.path.startsWith(pathFilter))
47
+ }
48
+
49
+ if (routes.length === 0) {
50
+ this.io.info('No routes found.')
51
+ return 0
52
+ }
53
+
54
+ this.io.heading('Registered Routes')
55
+ this.io.newLine()
56
+
57
+ this.io.table(
58
+ ['Method', 'Path', 'Name', 'Middleware'],
59
+ routes.map((r: any) => {
60
+ const method = Array.isArray(r.method) ? r.method.join('|') : String(r.method)
61
+ return [
62
+ this.colorMethod(method),
63
+ r.path,
64
+ r.name ?? '',
65
+ Array.isArray(r.middleware) ? r.middleware.join(', ') : (r.middleware ?? ''),
66
+ ]
67
+ }),
68
+ )
69
+
70
+ this.io.newLine()
71
+ this.io.muted(` Showing ${routes.length} route${routes.length !== 1 ? 's' : ''}`)
72
+ return 0
73
+ } catch (e: any) {
74
+ this.io.error(e.message ?? String(e))
75
+ return 1
76
+ }
77
+ }
78
+
79
+ private colorMethod(method: string): string {
80
+ switch (method) {
81
+ case 'GET': return this.io.green(method)
82
+ case 'POST': return this.io.cyan(method)
83
+ case 'PUT': return this.io.yellow(method)
84
+ case 'PATCH': return this.io.yellow(method)
85
+ case 'DELETE': return this.io.red(method)
86
+ default: return method
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,42 @@
1
+ import { Command } from '../Command.ts'
2
+ import type { ParsedArgs } from '../Parser.ts'
3
+ import { getManager } from '@mantiq/database'
4
+
5
+ export class SeedCommand extends Command {
6
+ override name = 'seed'
7
+ override description = 'Run database seeders'
8
+ override usage = 'seed [SeederName]'
9
+
10
+ override async handle(args: ParsedArgs): Promise<number> {
11
+ const connection = getManager().connection()
12
+ const seederName = args.args[0] ?? 'DatabaseSeeder'
13
+ const seedersDir = `${process.cwd()}/database/seeders`
14
+
15
+ this.io.info(`Seeding: ${seederName}...`)
16
+
17
+ try {
18
+ const mod = await import(`${seedersDir}/${seederName}.ts`)
19
+ const SeederClass = mod.default
20
+ if (!SeederClass) {
21
+ this.io.error(`No default export found in ${seederName}.ts`)
22
+ return 1
23
+ }
24
+
25
+ const seeder = new SeederClass()
26
+ if (typeof seeder.setConnection === 'function') {
27
+ seeder.setConnection(connection)
28
+ }
29
+ await seeder.run()
30
+
31
+ this.io.success(`${seederName} completed.`)
32
+ return 0
33
+ } catch (e: any) {
34
+ if (e.code === 'ERR_MODULE_NOT_FOUND' || e.message?.includes('Cannot find module')) {
35
+ this.io.error(`Seeder not found: ${seedersDir}/${seederName}.ts`)
36
+ } else {
37
+ this.io.error(e.message)
38
+ }
39
+ return 1
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,43 @@
1
+ import { Command } from '../Command.ts'
2
+ import type { ParsedArgs } from '../Parser.ts'
3
+
4
+ export class ServeCommand extends Command {
5
+ override name = 'serve'
6
+ override description = 'Start the development server'
7
+ override usage = 'serve [--port=3000] [--host=0.0.0.0]'
8
+
9
+ override async handle(args: ParsedArgs): Promise<number> {
10
+ const port = args.flags['port'] ? Number(args.flags['port']) : undefined
11
+ const host = args.flags['host'] as string | undefined
12
+
13
+ try {
14
+ // Load the app entry point
15
+ const entryPath = `${process.cwd()}/index.ts`
16
+ const mod = await import(entryPath)
17
+
18
+ const app = mod.default ?? mod.app
19
+ if (!app) {
20
+ this.io.error('No default export or "app" export found in index.ts')
21
+ return 1
22
+ }
23
+
24
+ // Resolve the HTTP kernel and start serving
25
+ const kernel = app.make?.('HttpKernel') ?? app.make?.('httpKernel')
26
+ if (!kernel) {
27
+ this.io.error('Could not resolve HttpKernel from the application container.')
28
+ return 1
29
+ }
30
+
31
+ // Override port/host if provided via flags
32
+ if (port) app.make?.('config')?.set?.('app.port', port)
33
+ if (host) app.make?.('config')?.set?.('app.host', host)
34
+
35
+ await kernel.start()
36
+ // Server is now running — keep process alive
37
+ return new Promise(() => {}) // never resolves, keeps the process running
38
+ } catch (e: any) {
39
+ this.io.error(e.message ?? String(e))
40
+ return 1
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,261 @@
1
+ import { Command } from '../Command.ts'
2
+ import type { ParsedArgs } from '../Parser.ts'
3
+ import { createInterface } from 'node:readline'
4
+ import { readdirSync, existsSync } from 'node:fs'
5
+
6
+ /**
7
+ * Interactive REPL with the full application context loaded.
8
+ * Like Laravel's `php artisan tinker`.
9
+ */
10
+ export class TinkerCommand extends Command {
11
+ override name = 'tinker'
12
+ override description = 'Interact with your application (REPL)'
13
+ override usage = 'tinker'
14
+
15
+ override async handle(_args: ParsedArgs): Promise<number> {
16
+ // ── Bootstrap the app ──────────────────────────────────────────────────
17
+ let app: any = null
18
+ const context: Record<string, any> = {}
19
+
20
+ try {
21
+ const entryPath = `${process.cwd()}/index.ts`
22
+ const mod = await import(entryPath)
23
+ app = mod.default ?? mod.app
24
+ } catch (e: any) {
25
+ this.io.warn(`Could not load app: ${e.message}`)
26
+ }
27
+
28
+ if (app) {
29
+ context['app'] = app
30
+
31
+ // Resolve core services
32
+ try {
33
+ const core = await import('@mantiq/core')
34
+ const tryMake = (key: any, name: string) => {
35
+ try { const v = app.make(key); if (v) context[name] = v } catch {}
36
+ }
37
+ tryMake(core.RouterImpl, 'router')
38
+ tryMake(core.ConfigRepository, 'config')
39
+ tryMake(core.HashManager, 'hash')
40
+ tryMake(core.CacheManager, 'cache')
41
+ tryMake(core.SessionManager, 'session')
42
+ tryMake(core.AesEncrypter, 'encrypter')
43
+ } catch {}
44
+
45
+ // Resolve database
46
+ try {
47
+ const db = await import('@mantiq/database')
48
+ if (db.getManager) {
49
+ const manager = db.getManager()
50
+ context['db'] = manager
51
+ context['connection'] = manager.connection()
52
+ }
53
+ } catch {}
54
+
55
+ // Resolve auth
56
+ try {
57
+ const auth = await import('@mantiq/auth')
58
+ if (auth.auth) context['auth'] = auth.auth
59
+ } catch {}
60
+ }
61
+
62
+ // ── Auto-import models ───────────────────────────────────────────────
63
+ const modelsDir = `${process.cwd()}/app/Models`
64
+ if (existsSync(modelsDir)) {
65
+ const files = readdirSync(modelsDir).filter((f) => f.endsWith('.ts'))
66
+ for (const file of files) {
67
+ try {
68
+ const mod = await import(`${modelsDir}/${file}`)
69
+ const name = file.replace(/\.ts$/, '')
70
+ // Export the class (named export matching filename, or first export)
71
+ const cls = mod[name] ?? mod.default ?? Object.values(mod)[0]
72
+ if (cls) context[name] = cls
73
+ } catch {}
74
+ }
75
+ }
76
+
77
+ // ── Assign to globalThis ─────────────────────────────────────────────
78
+ for (const [key, value] of Object.entries(context)) {
79
+ ;(globalThis as any)[key] = value
80
+ }
81
+
82
+ // ── Print banner ─────────────────────────────────────────────────────
83
+ this.io.heading('MantiqJS Tinker')
84
+ this.io.muted(' Interactive REPL — type expressions, use await for async.')
85
+ this.io.muted(' Type .help for commands, .exit to quit.\n')
86
+
87
+ if (Object.keys(context).length > 0) {
88
+ this.io.info('Available in scope:')
89
+ const items = Object.keys(context)
90
+ const perLine = 6
91
+ for (let i = 0; i < items.length; i += perLine) {
92
+ const chunk = items.slice(i, i + perLine)
93
+ this.io.line(` ${chunk.map((k) => this.io.cyan(k)).join(', ')}`)
94
+ }
95
+ this.io.newLine()
96
+ }
97
+
98
+ // ── REPL loop ────────────────────────────────────────────────────────
99
+ const rl = createInterface({
100
+ input: process.stdin,
101
+ output: process.stdout,
102
+ prompt: '\x1b[35m>\x1b[0m ',
103
+ historySize: 200,
104
+ })
105
+
106
+ let multiLine = ''
107
+ let multiLineMode = false
108
+
109
+ const evaluate = async (input: string) => {
110
+ const trimmed = input.trim()
111
+ if (!trimmed) return
112
+
113
+ // Dot commands
114
+ if (trimmed === '.exit' || trimmed === '.quit') {
115
+ rl.close()
116
+ return
117
+ }
118
+ if (trimmed === '.help') {
119
+ this.io.line(' .exit Exit the REPL')
120
+ this.io.line(' .clear Clear the terminal')
121
+ this.io.line(' .models List loaded models')
122
+ this.io.line(' .services List available services')
123
+ this.io.line('')
124
+ return
125
+ }
126
+ if (trimmed === '.clear') {
127
+ process.stdout.write('\x1b[2J\x1b[H')
128
+ return
129
+ }
130
+ if (trimmed === '.models') {
131
+ const modelFiles = existsSync(modelsDir)
132
+ ? readdirSync(modelsDir).filter((f) => f.endsWith('.ts')).map((f) => f.replace(/\.ts$/, ''))
133
+ : []
134
+ if (modelFiles.length) {
135
+ this.io.line(` ${modelFiles.map((m) => this.io.cyan(m)).join(', ')}`)
136
+ } else {
137
+ this.io.muted(' No models found.')
138
+ }
139
+ return
140
+ }
141
+ if (trimmed === '.services') {
142
+ const svcs = Object.keys(context).filter((k) => !['app'].includes(k) && typeof context[k] !== 'function' || k === 'auth')
143
+ this.io.line(` ${svcs.map((s) => this.io.cyan(s)).join(', ')}`)
144
+ return
145
+ }
146
+
147
+ try {
148
+ // Wrap in async to support top-level await
149
+ const code = `(async () => { return (${trimmed}) })()`
150
+ const result = await eval(code)
151
+
152
+ if (result !== undefined) {
153
+ this.printResult(result)
154
+ }
155
+ } catch {
156
+ // Retry as statement (not expression)
157
+ try {
158
+ const code = `(async () => { ${trimmed} })()`
159
+ const result = await eval(code)
160
+ if (result !== undefined) {
161
+ this.printResult(result)
162
+ }
163
+ } catch (e2: any) {
164
+ this.io.error(e2.message ?? String(e2))
165
+ }
166
+ }
167
+ }
168
+
169
+ rl.prompt()
170
+
171
+ return new Promise<number>((resolve) => {
172
+ rl.on('line', async (line) => {
173
+ const trimmed = line.trimEnd()
174
+
175
+ // Multiline: if line ends with \, accumulate
176
+ if (trimmed.endsWith('\\')) {
177
+ multiLine += trimmed.slice(0, -1) + '\n'
178
+ multiLineMode = true
179
+ process.stdout.write('\x1b[35m…\x1b[0m ')
180
+ return
181
+ }
182
+
183
+ if (multiLineMode) {
184
+ multiLine += line
185
+ multiLineMode = false
186
+ await evaluate(multiLine)
187
+ multiLine = ''
188
+ } else {
189
+ await evaluate(line)
190
+ }
191
+
192
+ rl.prompt()
193
+ })
194
+
195
+ rl.on('close', () => {
196
+ this.io.newLine()
197
+ this.io.muted(' Goodbye.')
198
+ resolve(0)
199
+ })
200
+ })
201
+ }
202
+
203
+ private printResult(value: any): void {
204
+ if (value === null) {
205
+ this.io.line(`\x1b[90mnull\x1b[0m`)
206
+ return
207
+ }
208
+ if (value === undefined) return
209
+
210
+ // Model instances — show as object
211
+ if (value && typeof value === 'object' && typeof value.toObject === 'function') {
212
+ try {
213
+ const obj = value.toObject()
214
+ this.io.line(this.formatObject(obj))
215
+ return
216
+ } catch {}
217
+ }
218
+
219
+ // Arrays of models
220
+ if (Array.isArray(value) && value.length > 0 && typeof value[0]?.toObject === 'function') {
221
+ try {
222
+ const arr = value.map((v: any) => v.toObject())
223
+ this.io.line(this.formatObject(arr))
224
+ return
225
+ } catch {}
226
+ }
227
+
228
+ // Plain objects/arrays
229
+ if (typeof value === 'object') {
230
+ this.io.line(this.formatObject(value))
231
+ return
232
+ }
233
+
234
+ // Primitives
235
+ if (typeof value === 'string') {
236
+ this.io.line(`\x1b[32m'${value}'\x1b[0m`)
237
+ } else if (typeof value === 'number' || typeof value === 'bigint') {
238
+ this.io.line(`\x1b[33m${value}\x1b[0m`)
239
+ } else if (typeof value === 'boolean') {
240
+ this.io.line(`\x1b[33m${value}\x1b[0m`)
241
+ } else if (typeof value === 'function') {
242
+ this.io.line(`\x1b[36m[Function: ${value.name || 'anonymous'}]\x1b[0m`)
243
+ } else {
244
+ this.io.line(String(value))
245
+ }
246
+ }
247
+
248
+ private formatObject(obj: any, indent = 0): string {
249
+ try {
250
+ const json = JSON.stringify(obj, null, 2)
251
+ // Colorize JSON output
252
+ return json
253
+ .replace(/"([^"]+)":/g, `\x1b[36m"$1"\x1b[0m:`)
254
+ .replace(/: "([^"]*)"/g, `: \x1b[32m"$1"\x1b[0m`)
255
+ .replace(/: (\d+)/g, `: \x1b[33m$1\x1b[0m`)
256
+ .replace(/: (true|false|null)/g, `: \x1b[33m$1\x1b[0m`)
257
+ } catch {
258
+ return String(obj)
259
+ }
260
+ }
261
+ }
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ // @mantiq/cli — public API exports
2
+
3
+ // Core
4
+ export { Command } from './Command.ts'
5
+ export { Kernel } from './Kernel.ts'
6
+ export { IO } from './IO.ts'
7
+ export { parse } from './Parser.ts'
8
+ export type { ParsedArgs } from './Parser.ts'
9
+
10
+ // Base
11
+ export { GeneratorCommand } from './commands/GeneratorCommand.ts'
12
+
13
+ // Database commands
14
+ export { MigrateCommand } from './commands/MigrateCommand.ts'
15
+ export { MigrateRollbackCommand } from './commands/MigrateRollbackCommand.ts'
16
+ export { MigrateResetCommand } from './commands/MigrateResetCommand.ts'
17
+ export { MigrateFreshCommand } from './commands/MigrateFreshCommand.ts'
18
+ export { MigrateStatusCommand } from './commands/MigrateStatusCommand.ts'
19
+ export { SeedCommand } from './commands/SeedCommand.ts'
20
+
21
+ // Generator commands
22
+ export { MakeCommandCommand } from './commands/MakeCommandCommand.ts'
23
+ export { MakeControllerCommand } from './commands/MakeControllerCommand.ts'
24
+ export { MakeEventCommand } from './commands/MakeEventCommand.ts'
25
+ export { MakeExceptionCommand } from './commands/MakeExceptionCommand.ts'
26
+ export { MakeFactoryCommand } from './commands/MakeFactoryCommand.ts'
27
+ export { MakeListenerCommand } from './commands/MakeListenerCommand.ts'
28
+ export { MakeMiddlewareCommand } from './commands/MakeMiddlewareCommand.ts'
29
+ export { MakeMigrationCommand } from './commands/MakeMigrationCommand.ts'
30
+ export { MakeModelCommand } from './commands/MakeModelCommand.ts'
31
+ export { MakeObserverCommand } from './commands/MakeObserverCommand.ts'
32
+ export { MakeProviderCommand } from './commands/MakeProviderCommand.ts'
33
+ export { MakeRequestCommand } from './commands/MakeRequestCommand.ts'
34
+ export { MakeRuleCommand } from './commands/MakeRuleCommand.ts'
35
+ export { MakeSeederCommand } from './commands/MakeSeederCommand.ts'
36
+ export { MakeTestCommand } from './commands/MakeTestCommand.ts'
37
+
38
+ // Utility commands
39
+ export { AboutCommand } from './commands/AboutCommand.ts'
40
+ export { ServeCommand } from './commands/ServeCommand.ts'
41
+ export { RouteListCommand } from './commands/RouteListCommand.ts'
42
+ export { TinkerCommand } from './commands/TinkerCommand.ts'