@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.
- package/README.md +19 -0
- package/package.json +59 -0
- package/src/Command.ts +24 -0
- package/src/IO.ts +96 -0
- package/src/Kernel.ts +84 -0
- package/src/Parser.ts +62 -0
- package/src/bin.ts +61 -0
- package/src/commands/AboutCommand.ts +73 -0
- package/src/commands/GeneratorCommand.ts +58 -0
- package/src/commands/MakeCommandCommand.ts +39 -0
- package/src/commands/MakeControllerCommand.ts +58 -0
- package/src/commands/MakeEventCommand.ts +30 -0
- package/src/commands/MakeExceptionCommand.ts +25 -0
- package/src/commands/MakeFactoryCommand.ts +29 -0
- package/src/commands/MakeListenerCommand.ts +29 -0
- package/src/commands/MakeMiddlewareCommand.ts +23 -0
- package/src/commands/MakeMigrationCommand.ts +125 -0
- package/src/commands/MakeModelCommand.ts +150 -0
- package/src/commands/MakeObserverCommand.ts +32 -0
- package/src/commands/MakeProviderCommand.ts +27 -0
- package/src/commands/MakeRequestCommand.ts +29 -0
- package/src/commands/MakeRuleCommand.ts +38 -0
- package/src/commands/MakeSeederCommand.ts +22 -0
- package/src/commands/MakeTestCommand.ts +69 -0
- package/src/commands/MigrateCommand.ts +33 -0
- package/src/commands/MigrateFreshCommand.ts +47 -0
- package/src/commands/MigrateResetCommand.ts +33 -0
- package/src/commands/MigrateRollbackCommand.ts +28 -0
- package/src/commands/MigrateStatusCommand.ts +41 -0
- package/src/commands/RouteListCommand.ts +89 -0
- package/src/commands/SeedCommand.ts +42 -0
- package/src/commands/ServeCommand.ts +43 -0
- package/src/commands/TinkerCommand.ts +261 -0
- 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'
|