@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
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @mantiq/cli
|
|
2
|
+
|
|
3
|
+
Command kernel, code generators, database commands, REPL, and route listing for MantiqJS.
|
|
4
|
+
|
|
5
|
+
Part of [MantiqJS](https://github.com/abdullahkhan/mantiq) — a batteries-included TypeScript web framework for Bun.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @mantiq/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Documentation
|
|
14
|
+
|
|
15
|
+
See the [MantiqJS repository](https://github.com/abdullahkhan/mantiq) for full documentation.
|
|
16
|
+
|
|
17
|
+
## License
|
|
18
|
+
|
|
19
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mantiq/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Command runner, code generators, dev server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Abdullah Khan",
|
|
8
|
+
"homepage": "https://github.com/abdullahkhan/mantiq/tree/main/packages/cli",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/abdullahkhan/mantiq.git",
|
|
12
|
+
"directory": "packages/cli"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/abdullahkhan/mantiq/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mantiq",
|
|
19
|
+
"mantiqjs",
|
|
20
|
+
"bun",
|
|
21
|
+
"typescript",
|
|
22
|
+
"framework",
|
|
23
|
+
"cli"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"bun": ">=1.1.0"
|
|
27
|
+
},
|
|
28
|
+
"main": "./src/index.ts",
|
|
29
|
+
"types": "./src/index.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"bun": "./src/index.ts",
|
|
33
|
+
"default": "./src/index.ts"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"bin": {
|
|
37
|
+
"mantiq": "./src/bin.ts"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"src/",
|
|
41
|
+
"package.json",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun",
|
|
47
|
+
"test": "bun test",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"clean": "rm -rf dist"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@mantiq/core": "workspace:*",
|
|
53
|
+
"@mantiq/database": "workspace:*"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"bun-types": "latest",
|
|
57
|
+
"typescript": "^5.7.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/Command.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IO } from './IO.ts'
|
|
2
|
+
import type { ParsedArgs } from './Parser.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base class for all CLI commands.
|
|
6
|
+
*/
|
|
7
|
+
export abstract class Command {
|
|
8
|
+
/** Command name, e.g. 'migrate', 'make:model' */
|
|
9
|
+
abstract name: string
|
|
10
|
+
|
|
11
|
+
/** Short description shown in help */
|
|
12
|
+
abstract description: string
|
|
13
|
+
|
|
14
|
+
/** Usage hint, e.g. 'make:model <name> [--migration]' */
|
|
15
|
+
usage?: string
|
|
16
|
+
|
|
17
|
+
protected io = new IO()
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Execute the command.
|
|
21
|
+
* @returns exit code (0 = success)
|
|
22
|
+
*/
|
|
23
|
+
abstract handle(args: ParsedArgs): Promise<number>
|
|
24
|
+
}
|
package/src/IO.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal output helpers with ANSI colors.
|
|
3
|
+
* Zero dependencies — uses escape codes directly.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ESC = '\x1b['
|
|
7
|
+
const RESET = `${ESC}0m`
|
|
8
|
+
const BOLD = `${ESC}1m`
|
|
9
|
+
const DIM = `${ESC}2m`
|
|
10
|
+
|
|
11
|
+
const FG = {
|
|
12
|
+
red: `${ESC}31m`,
|
|
13
|
+
green: `${ESC}32m`,
|
|
14
|
+
yellow: `${ESC}33m`,
|
|
15
|
+
blue: `${ESC}34m`,
|
|
16
|
+
magenta: `${ESC}35m`,
|
|
17
|
+
cyan: `${ESC}36m`,
|
|
18
|
+
white: `${ESC}37m`,
|
|
19
|
+
gray: `${ESC}90m`,
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export class IO {
|
|
23
|
+
info(msg: string): void {
|
|
24
|
+
console.log(`${FG.blue} INFO${RESET} ${msg}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
success(msg: string): void {
|
|
28
|
+
console.log(`${FG.green} DONE${RESET} ${msg}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
error(msg: string): void {
|
|
32
|
+
console.error(`${FG.red} ERROR${RESET} ${msg}`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
warn(msg: string): void {
|
|
36
|
+
console.log(`${FG.yellow} WARN${RESET} ${msg}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
line(msg = ''): void {
|
|
40
|
+
console.log(msg)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
newLine(): void {
|
|
44
|
+
console.log()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Bold heading */
|
|
48
|
+
heading(msg: string): void {
|
|
49
|
+
console.log(`\n${BOLD}${msg}${RESET}`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Dim/muted text */
|
|
53
|
+
muted(msg: string): void {
|
|
54
|
+
console.log(`${DIM}${msg}${RESET}`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Colorize inline text */
|
|
58
|
+
green(msg: string): string { return `${FG.green}${msg}${RESET}` }
|
|
59
|
+
red(msg: string): string { return `${FG.red}${msg}${RESET}` }
|
|
60
|
+
yellow(msg: string): string { return `${FG.yellow}${msg}${RESET}` }
|
|
61
|
+
cyan(msg: string): string { return `${FG.cyan}${msg}${RESET}` }
|
|
62
|
+
gray(msg: string): string { return `${FG.gray}${msg}${RESET}` }
|
|
63
|
+
bold(msg: string): string { return `${BOLD}${msg}${RESET}` }
|
|
64
|
+
|
|
65
|
+
/** Print a table with aligned columns */
|
|
66
|
+
table(headers: string[], rows: string[][]): void {
|
|
67
|
+
// Strip ANSI codes for width calculation
|
|
68
|
+
const strip = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, '')
|
|
69
|
+
|
|
70
|
+
const widths = headers.map((h, i) =>
|
|
71
|
+
Math.max(strip(h).length, ...rows.map((r) => strip(r[i] ?? '').length)),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const sep = widths.map((w) => '-'.repeat(w + 2)).join('+')
|
|
75
|
+
const formatRow = (row: string[]) =>
|
|
76
|
+
row.map((cell, i) => {
|
|
77
|
+
const pad = widths[i]! - strip(cell).length
|
|
78
|
+
return ` ${cell}${' '.repeat(Math.max(0, pad))} `
|
|
79
|
+
}).join('|')
|
|
80
|
+
|
|
81
|
+
this.line(` ${FG.gray}${sep}${RESET}`)
|
|
82
|
+
this.line(` ${BOLD}${formatRow(headers)}${RESET}`)
|
|
83
|
+
this.line(` ${FG.gray}${sep}${RESET}`)
|
|
84
|
+
for (const row of rows) {
|
|
85
|
+
this.line(` ${formatRow(row)}`)
|
|
86
|
+
}
|
|
87
|
+
this.line(` ${FG.gray}${sep}${RESET}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Simple two-column display like Laravel */
|
|
91
|
+
twoColumn(label: string, value: string, width = 30): void {
|
|
92
|
+
this.line(` ${label.padEnd(width)} ${FG.gray}${value}${RESET}`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const io = new IO()
|
package/src/Kernel.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Command } from './Command.ts'
|
|
2
|
+
import { parse, type ParsedArgs } from './Parser.ts'
|
|
3
|
+
import { IO } from './IO.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The CLI Kernel — registers commands and dispatches to the right one.
|
|
7
|
+
*/
|
|
8
|
+
export class Kernel {
|
|
9
|
+
private commands = new Map<string, Command>()
|
|
10
|
+
private io = new IO()
|
|
11
|
+
|
|
12
|
+
constructor(public readonly app: any = null) {}
|
|
13
|
+
|
|
14
|
+
register(command: Command): this {
|
|
15
|
+
this.commands.set(command.name, command)
|
|
16
|
+
return this
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
registerAll(commands: Command[]): this {
|
|
20
|
+
for (const cmd of commands) this.register(cmd)
|
|
21
|
+
return this
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async run(argv: string[] = process.argv): Promise<number> {
|
|
25
|
+
const parsed = parse(argv)
|
|
26
|
+
|
|
27
|
+
if (parsed.command === 'help' || parsed.command === '--help' || parsed.command === '-h' || parsed.flags['help'] || parsed.flags['h']) {
|
|
28
|
+
this.showHelp(parsed)
|
|
29
|
+
return 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const command = this.commands.get(parsed.command)
|
|
33
|
+
if (!command) {
|
|
34
|
+
this.io.error(`Command "${parsed.command}" not found.`)
|
|
35
|
+
this.io.newLine()
|
|
36
|
+
this.showHelp(parsed)
|
|
37
|
+
return 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
return await command.handle(parsed)
|
|
42
|
+
} catch (e: any) {
|
|
43
|
+
this.io.error(e.message ?? String(e))
|
|
44
|
+
if (process.env['APP_DEBUG'] === 'true') {
|
|
45
|
+
this.io.line(e.stack ?? '')
|
|
46
|
+
}
|
|
47
|
+
return 1
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private showHelp(_parsed: ParsedArgs): void {
|
|
52
|
+
this.io.heading('MantiqJS CLI')
|
|
53
|
+
this.io.newLine()
|
|
54
|
+
this.io.line(' Usage: mantiq <command> [arguments] [options]')
|
|
55
|
+
this.io.newLine()
|
|
56
|
+
|
|
57
|
+
// Group commands by prefix
|
|
58
|
+
const groups = new Map<string, Command[]>()
|
|
59
|
+
for (const cmd of this.commands.values()) {
|
|
60
|
+
const prefix = cmd.name.includes(':') ? cmd.name.split(':')[0]! : ''
|
|
61
|
+
if (!groups.has(prefix)) groups.set(prefix, [])
|
|
62
|
+
groups.get(prefix)!.push(cmd)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Sort groups: top-level first, then alphabetical
|
|
66
|
+
const sortedGroups = [...groups.entries()].sort((a, b) => {
|
|
67
|
+
if (a[0] === '' && b[0] !== '') return -1
|
|
68
|
+
if (a[0] !== '' && b[0] === '') return 1
|
|
69
|
+
return a[0].localeCompare(b[0])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
for (const [prefix, cmds] of sortedGroups) {
|
|
73
|
+
if (prefix) {
|
|
74
|
+
this.io.line(` ${this.io.yellow(prefix)}`)
|
|
75
|
+
}
|
|
76
|
+
const sorted = cmds.sort((a, b) => a.name.localeCompare(b.name))
|
|
77
|
+
for (const cmd of sorted) {
|
|
78
|
+
this.io.twoColumn(` ${this.io.green(cmd.name)}`, cmd.description, 30)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.io.newLine()
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/Parser.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight argument parser for CLI commands.
|
|
3
|
+
* Parses process.argv into command name, positional args, and flags.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* // bun mantiq make:model User -m --table=users
|
|
7
|
+
* parse(['make:model', 'User', '-m', '--table=users'])
|
|
8
|
+
* // => { command: 'make:model', args: ['User'], flags: { m: true, table: 'users' } }
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface ParsedArgs {
|
|
12
|
+
/** The command name (e.g. 'migrate', 'make:model') */
|
|
13
|
+
command: string
|
|
14
|
+
/** Positional arguments after the command */
|
|
15
|
+
args: string[]
|
|
16
|
+
/** Named flags — boolean for switches, string for --key=value */
|
|
17
|
+
flags: Record<string, string | boolean>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function parse(argv: string[]): ParsedArgs {
|
|
21
|
+
// Skip bun/node executable and script path
|
|
22
|
+
const raw = argv.slice(2)
|
|
23
|
+
|
|
24
|
+
const command = raw[0] ?? 'help'
|
|
25
|
+
const args: string[] = []
|
|
26
|
+
const flags: Record<string, string | boolean> = {}
|
|
27
|
+
|
|
28
|
+
for (let i = 1; i < raw.length; i++) {
|
|
29
|
+
const arg = raw[i]!
|
|
30
|
+
|
|
31
|
+
if (arg.startsWith('--')) {
|
|
32
|
+
const eqIdx = arg.indexOf('=')
|
|
33
|
+
if (eqIdx !== -1) {
|
|
34
|
+
flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1)
|
|
35
|
+
} else {
|
|
36
|
+
// Check if next arg is the value (not another flag)
|
|
37
|
+
const key = arg.slice(2)
|
|
38
|
+
const next = raw[i + 1]
|
|
39
|
+
if (next && !next.startsWith('-')) {
|
|
40
|
+
flags[key] = next
|
|
41
|
+
i++
|
|
42
|
+
} else {
|
|
43
|
+
flags[key] = true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
47
|
+
// Short flag: -m, -f, -p 8080, etc.
|
|
48
|
+
const key = arg[1]!
|
|
49
|
+
const next = raw[i + 1]
|
|
50
|
+
if (next && !next.startsWith('-')) {
|
|
51
|
+
flags[key] = next
|
|
52
|
+
i++
|
|
53
|
+
} else {
|
|
54
|
+
flags[key] = true
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
args.push(arg)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { command, args, flags }
|
|
62
|
+
}
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Kernel } from './Kernel.ts'
|
|
3
|
+
import { MigrateCommand } from './commands/MigrateCommand.ts'
|
|
4
|
+
import { MigrateRollbackCommand } from './commands/MigrateRollbackCommand.ts'
|
|
5
|
+
import { MigrateResetCommand } from './commands/MigrateResetCommand.ts'
|
|
6
|
+
import { MigrateFreshCommand } from './commands/MigrateFreshCommand.ts'
|
|
7
|
+
import { MigrateStatusCommand } from './commands/MigrateStatusCommand.ts'
|
|
8
|
+
import { SeedCommand } from './commands/SeedCommand.ts'
|
|
9
|
+
import { MakeControllerCommand } from './commands/MakeControllerCommand.ts'
|
|
10
|
+
import { MakeModelCommand } from './commands/MakeModelCommand.ts'
|
|
11
|
+
import { MakeMigrationCommand } from './commands/MakeMigrationCommand.ts'
|
|
12
|
+
import { MakeSeederCommand } from './commands/MakeSeederCommand.ts'
|
|
13
|
+
import { MakeFactoryCommand } from './commands/MakeFactoryCommand.ts'
|
|
14
|
+
import { MakeMiddlewareCommand } from './commands/MakeMiddlewareCommand.ts'
|
|
15
|
+
import { MakeRequestCommand } from './commands/MakeRequestCommand.ts'
|
|
16
|
+
import { ServeCommand } from './commands/ServeCommand.ts'
|
|
17
|
+
import { RouteListCommand } from './commands/RouteListCommand.ts'
|
|
18
|
+
import { TinkerCommand } from './commands/TinkerCommand.ts'
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
// Try to bootstrap the application if an index.ts exists
|
|
22
|
+
let app: any = null
|
|
23
|
+
try {
|
|
24
|
+
const entryPath = `${process.cwd()}/index.ts`
|
|
25
|
+
const mod = await import(entryPath)
|
|
26
|
+
app = mod.default ?? mod.app ?? null
|
|
27
|
+
} catch {
|
|
28
|
+
// No app entry — that's fine for make:* commands
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const kernel = new Kernel(app)
|
|
32
|
+
|
|
33
|
+
kernel.registerAll([
|
|
34
|
+
// Database
|
|
35
|
+
new MigrateCommand(),
|
|
36
|
+
new MigrateRollbackCommand(),
|
|
37
|
+
new MigrateResetCommand(),
|
|
38
|
+
new MigrateFreshCommand(),
|
|
39
|
+
new MigrateStatusCommand(),
|
|
40
|
+
new SeedCommand(),
|
|
41
|
+
|
|
42
|
+
// Generators
|
|
43
|
+
new MakeControllerCommand(),
|
|
44
|
+
new MakeModelCommand(),
|
|
45
|
+
new MakeMigrationCommand(),
|
|
46
|
+
new MakeSeederCommand(),
|
|
47
|
+
new MakeFactoryCommand(),
|
|
48
|
+
new MakeMiddlewareCommand(),
|
|
49
|
+
new MakeRequestCommand(),
|
|
50
|
+
|
|
51
|
+
// Utility
|
|
52
|
+
new ServeCommand(),
|
|
53
|
+
new RouteListCommand(),
|
|
54
|
+
new TinkerCommand(),
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
const code = await kernel.run()
|
|
58
|
+
process.exit(code)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
main()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Command } from '../Command.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class AboutCommand extends Command {
|
|
5
|
+
override name = 'about'
|
|
6
|
+
override description = 'Display information about the framework'
|
|
7
|
+
|
|
8
|
+
override async handle(_args: ParsedArgs): Promise<number> {
|
|
9
|
+
const env = process.env
|
|
10
|
+
|
|
11
|
+
this.io.heading('MantiqJS Framework')
|
|
12
|
+
this.io.line('')
|
|
13
|
+
|
|
14
|
+
// Environment
|
|
15
|
+
this.io.info(' Environment')
|
|
16
|
+
this.io.line('')
|
|
17
|
+
this.printRow('Application Name', env.APP_NAME || 'MantiqJS')
|
|
18
|
+
this.printRow('Environment', env.APP_ENV || 'production')
|
|
19
|
+
this.printRow('Debug Mode', env.APP_DEBUG === 'true' ? this.io.yellow('ENABLED') : 'disabled')
|
|
20
|
+
this.printRow('URL', env.APP_URL || 'http://localhost:3000')
|
|
21
|
+
this.printRow('Port', env.APP_PORT || '3000')
|
|
22
|
+
this.io.line('')
|
|
23
|
+
|
|
24
|
+
// Runtime
|
|
25
|
+
this.io.info(' Runtime')
|
|
26
|
+
this.io.line('')
|
|
27
|
+
this.printRow('Bun', Bun.version)
|
|
28
|
+
this.printRow('TypeScript', 'ESNext')
|
|
29
|
+
this.printRow('Platform', `${process.platform} ${process.arch}`)
|
|
30
|
+
this.printRow('PID', String(process.pid))
|
|
31
|
+
this.printRow('Memory', `${Math.round(process.memoryUsage.rss() / 1024 / 1024)} MB RSS`)
|
|
32
|
+
this.io.line('')
|
|
33
|
+
|
|
34
|
+
// Database
|
|
35
|
+
this.io.info(' Database')
|
|
36
|
+
this.io.line('')
|
|
37
|
+
this.printRow('Connection', env.DB_CONNECTION || 'sqlite')
|
|
38
|
+
this.printRow('Database', env.DB_DATABASE || 'database/database.sqlite')
|
|
39
|
+
this.io.line('')
|
|
40
|
+
|
|
41
|
+
// Services
|
|
42
|
+
this.io.info(' Services')
|
|
43
|
+
this.io.line('')
|
|
44
|
+
this.printRow('Cache', env.CACHE_DRIVER || 'memory')
|
|
45
|
+
this.printRow('Queue', env.QUEUE_CONNECTION || 'sync')
|
|
46
|
+
this.printRow('Logging', env.LOG_CHANNEL || 'stack')
|
|
47
|
+
this.printRow('Session', env.SESSION_DRIVER || 'cookie')
|
|
48
|
+
this.io.line('')
|
|
49
|
+
|
|
50
|
+
// Packages
|
|
51
|
+
this.io.info(' Installed Packages')
|
|
52
|
+
this.io.line('')
|
|
53
|
+
const packages = [
|
|
54
|
+
'core', 'database', 'auth', 'cli', 'validation', 'helpers',
|
|
55
|
+
'filesystem', 'logging', 'events', 'queue', 'realtime', 'heartbeat', 'vite',
|
|
56
|
+
]
|
|
57
|
+
for (const pkg of packages) {
|
|
58
|
+
try {
|
|
59
|
+
require.resolve(`@mantiq/${pkg}`)
|
|
60
|
+
this.io.line(` ${this.io.green('●')} @mantiq/${pkg}`)
|
|
61
|
+
} catch {
|
|
62
|
+
this.io.line(` ${this.io.gray('○')} @mantiq/${pkg}`)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.io.line('')
|
|
66
|
+
|
|
67
|
+
return 0
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private printRow(label: string, value: string): void {
|
|
71
|
+
this.io.line(` ${this.io.gray(label.padEnd(22))} ${value}`)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Command } from '../Command.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
import { existsSync, mkdirSync } from 'node:fs'
|
|
4
|
+
import { dirname } from 'node:path'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base class for make:* generators.
|
|
8
|
+
* Subclasses provide the target directory, file suffix, and stub content.
|
|
9
|
+
*/
|
|
10
|
+
export abstract class GeneratorCommand extends Command {
|
|
11
|
+
/** Directory relative to project root, e.g. 'app/Http/Controllers' */
|
|
12
|
+
abstract directory(): string
|
|
13
|
+
|
|
14
|
+
/** File suffix, e.g. 'Controller' — will be appended to the name */
|
|
15
|
+
abstract suffix(): string
|
|
16
|
+
|
|
17
|
+
/** Generate the file content */
|
|
18
|
+
abstract stub(name: string, args: ParsedArgs): string
|
|
19
|
+
|
|
20
|
+
override async handle(args: ParsedArgs): Promise<number> {
|
|
21
|
+
const rawName = args.args[0]
|
|
22
|
+
if (!rawName) {
|
|
23
|
+
this.io.error(`Please provide a name. Usage: ${this.name} <name>`)
|
|
24
|
+
return 1
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const className = this.toClassName(rawName)
|
|
28
|
+
const fileName = `${className}${this.suffix()}.ts`
|
|
29
|
+
const dir = `${process.cwd()}/${this.directory()}`
|
|
30
|
+
const filePath = `${dir}/${fileName}`
|
|
31
|
+
|
|
32
|
+
if (existsSync(filePath)) {
|
|
33
|
+
this.io.error(`${fileName} already exists.`)
|
|
34
|
+
return 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Ensure directory exists
|
|
38
|
+
mkdirSync(dirname(filePath), { recursive: true })
|
|
39
|
+
|
|
40
|
+
const content = this.stub(className, args)
|
|
41
|
+
await Bun.write(filePath, content)
|
|
42
|
+
|
|
43
|
+
this.io.success(`Created ${this.directory()}/${fileName}`)
|
|
44
|
+
return 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected toClassName(name: string): string {
|
|
48
|
+
// Remove suffix if already present
|
|
49
|
+
const sfx = this.suffix()
|
|
50
|
+
if (sfx && name.endsWith(sfx)) {
|
|
51
|
+
name = name.slice(0, -sfx.length)
|
|
52
|
+
}
|
|
53
|
+
// PascalCase: foo_bar -> FooBar, foo-bar -> FooBar
|
|
54
|
+
return name
|
|
55
|
+
.replace(/[-_]+(.)/g, (_, c) => c.toUpperCase())
|
|
56
|
+
.replace(/^(.)/, (_, c) => c.toUpperCase())
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeCommandCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:command'
|
|
6
|
+
override description = 'Create a new CLI command class'
|
|
7
|
+
override usage = 'make:command <name>'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Console/Commands' }
|
|
10
|
+
override suffix() { return 'Command' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, _args: ParsedArgs): string {
|
|
13
|
+
const className = `${name}Command`
|
|
14
|
+
const cmdName = this.toCommandName(name)
|
|
15
|
+
return `import { Command } from '@mantiq/cli'
|
|
16
|
+
import type { ParsedArgs } from '@mantiq/cli'
|
|
17
|
+
|
|
18
|
+
export class ${className} extends Command {
|
|
19
|
+
override name = '${cmdName}'
|
|
20
|
+
override description = ''
|
|
21
|
+
override usage = '${cmdName}'
|
|
22
|
+
|
|
23
|
+
override async handle(args: ParsedArgs): Promise<number> {
|
|
24
|
+
// TODO: implement command
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Convert PascalCase to kebab-case command name, e.g. SendEmails → app:send-emails */
|
|
32
|
+
private toCommandName(name: string): string {
|
|
33
|
+
const kebab = name
|
|
34
|
+
.replace(/([A-Z])/g, '-$1')
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replace(/^-/, '')
|
|
37
|
+
return `app:${kebab}`
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeControllerCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:controller'
|
|
6
|
+
override description = 'Create a new controller class'
|
|
7
|
+
override usage = 'make:controller <name> [--resource]'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Http/Controllers' }
|
|
10
|
+
override suffix() { return 'Controller' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, args: ParsedArgs): string {
|
|
13
|
+
const className = `${name}Controller`
|
|
14
|
+
const isResource = !!args.flags['resource'] || !!args.flags['r']
|
|
15
|
+
|
|
16
|
+
if (isResource) {
|
|
17
|
+
return `import type { MantiqRequest } from '@mantiq/core'
|
|
18
|
+
import { MantiqResponse } from '@mantiq/core'
|
|
19
|
+
|
|
20
|
+
export class ${className} {
|
|
21
|
+
async index(request: MantiqRequest): Promise<Response> {
|
|
22
|
+
return MantiqResponse.json({ data: [] })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async show(request: MantiqRequest): Promise<Response> {
|
|
26
|
+
const id = request.param('id')
|
|
27
|
+
return MantiqResponse.json({ data: { id } })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async store(request: MantiqRequest): Promise<Response> {
|
|
31
|
+
const body = await request.input()
|
|
32
|
+
return MantiqResponse.json({ data: body }, 201)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async update(request: MantiqRequest): Promise<Response> {
|
|
36
|
+
const id = request.param('id')
|
|
37
|
+
const body = await request.input()
|
|
38
|
+
return MantiqResponse.json({ data: { id, ...body } })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async destroy(request: MantiqRequest): Promise<Response> {
|
|
42
|
+
return MantiqResponse.noContent()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `import type { MantiqRequest } from '@mantiq/core'
|
|
49
|
+
import { MantiqResponse } from '@mantiq/core'
|
|
50
|
+
|
|
51
|
+
export class ${className} {
|
|
52
|
+
async index(request: MantiqRequest): Promise<Response> {
|
|
53
|
+
return MantiqResponse.json({ data: [] })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeEventCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:event'
|
|
6
|
+
override description = 'Create a new event class'
|
|
7
|
+
override usage = 'make:event <name>'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Events' }
|
|
10
|
+
override suffix() { return '' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, _args: ParsedArgs): string {
|
|
13
|
+
return `export class ${name} {
|
|
14
|
+
constructor(
|
|
15
|
+
public readonly data: Record<string, unknown> = {},
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
/** Channels this event should broadcast on (return empty to skip broadcasting) */
|
|
19
|
+
broadcastOn(): string[] {
|
|
20
|
+
return []
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The broadcast event name */
|
|
24
|
+
broadcastAs(): string {
|
|
25
|
+
return '${name}'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`
|
|
29
|
+
}
|
|
30
|
+
}
|