@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,25 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeExceptionCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:exception'
|
|
6
|
+
override description = 'Create a new exception class'
|
|
7
|
+
override usage = 'make:exception <name>'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Exceptions' }
|
|
10
|
+
override suffix() { return 'Exception' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, args: ParsedArgs): string {
|
|
13
|
+
const className = `${name}Exception`
|
|
14
|
+
const statusCode = (args.flags['status'] as string) || '500'
|
|
15
|
+
return `import { MantiqError } from '@mantiq/core'
|
|
16
|
+
|
|
17
|
+
export class ${className} extends MantiqError {
|
|
18
|
+
constructor(message = '${name} error', public override statusCode = ${statusCode}) {
|
|
19
|
+
super(message)
|
|
20
|
+
this.name = '${className}'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
`
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeFactoryCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:factory'
|
|
6
|
+
override description = 'Create a new model factory'
|
|
7
|
+
override usage = 'make:factory <name>'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'database/factories' }
|
|
10
|
+
override suffix() { return 'Factory' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, _args: ParsedArgs): string {
|
|
13
|
+
const modelName = name.replace(/Factory$/, '')
|
|
14
|
+
return `import { Factory } from '@mantiq/database'
|
|
15
|
+
import type { Faker } from '@mantiq/database'
|
|
16
|
+
import { ${modelName} } from '../../app/Models/${modelName}.ts'
|
|
17
|
+
|
|
18
|
+
export class ${name}Factory extends Factory<${modelName}> {
|
|
19
|
+
protected override model = ${modelName}
|
|
20
|
+
|
|
21
|
+
override definition(index: number, fake: Faker) {
|
|
22
|
+
return {
|
|
23
|
+
name: fake.name(),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeListenerCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:listener'
|
|
6
|
+
override description = 'Create a new event listener class'
|
|
7
|
+
override usage = 'make:listener <name> [--event=EventName]'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Listeners' }
|
|
10
|
+
override suffix() { return 'Listener' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, args: ParsedArgs): string {
|
|
13
|
+
const className = `${name}Listener`
|
|
14
|
+
const event = (args.flags['event'] as string) || 'Event'
|
|
15
|
+
const hasEvent = args.flags['event']
|
|
16
|
+
|
|
17
|
+
const importLine = hasEvent
|
|
18
|
+
? `import type { ${event} } from '../Events/${event}.ts'\n\n`
|
|
19
|
+
: ''
|
|
20
|
+
const paramType = hasEvent ? event : 'unknown'
|
|
21
|
+
|
|
22
|
+
return `${importLine}export class ${className} {
|
|
23
|
+
async handle(event: ${paramType}): Promise<void> {
|
|
24
|
+
// TODO: handle event
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeMiddlewareCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:middleware'
|
|
6
|
+
override description = 'Create a new middleware class'
|
|
7
|
+
override usage = 'make:middleware <name>'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Http/Middleware' }
|
|
10
|
+
override suffix() { return 'Middleware' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, _args: ParsedArgs): string {
|
|
13
|
+
const className = `${name}Middleware`
|
|
14
|
+
return `import type { Middleware, MantiqRequest, NextFunction } from '@mantiq/core'
|
|
15
|
+
|
|
16
|
+
export class ${className} implements Middleware {
|
|
17
|
+
async handle(request: MantiqRequest, next: NextFunction): Promise<Response> {
|
|
18
|
+
return next()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
`
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Command } from '../Command.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
import { existsSync, mkdirSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
export class MakeMigrationCommand extends Command {
|
|
6
|
+
override name = 'make:migration'
|
|
7
|
+
override description = 'Create a new migration file'
|
|
8
|
+
override usage = 'make:migration <name> [--create=table] [--table=table]'
|
|
9
|
+
|
|
10
|
+
override async handle(args: ParsedArgs): Promise<number> {
|
|
11
|
+
const rawName = args.args[0]
|
|
12
|
+
if (!rawName) {
|
|
13
|
+
this.io.error('Please provide a migration name. Usage: make:migration <name>')
|
|
14
|
+
return 1
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const createTable = args.flags['create'] as string | undefined
|
|
18
|
+
const alterTable = args.flags['table'] as string | undefined
|
|
19
|
+
|
|
20
|
+
const timestamp = this.timestamp()
|
|
21
|
+
const snakeName = this.toSnakeCase(rawName)
|
|
22
|
+
const fileName = `${timestamp}_${snakeName}.ts`
|
|
23
|
+
const dir = `${process.cwd()}/database/migrations`
|
|
24
|
+
mkdirSync(dir, { recursive: true })
|
|
25
|
+
const filePath = `${dir}/${fileName}`
|
|
26
|
+
|
|
27
|
+
if (existsSync(filePath)) {
|
|
28
|
+
this.io.error(`${fileName} already exists.`)
|
|
29
|
+
return 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const className = this.toClassName(snakeName)
|
|
33
|
+
const content = createTable
|
|
34
|
+
? this.createStub(className, createTable)
|
|
35
|
+
: alterTable
|
|
36
|
+
? this.alterStub(className, alterTable)
|
|
37
|
+
: this.blankStub(className)
|
|
38
|
+
|
|
39
|
+
await Bun.write(filePath, content)
|
|
40
|
+
this.io.success(`Created database/migrations/${fileName}`)
|
|
41
|
+
return 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private createStub(className: string, table: string): string {
|
|
45
|
+
return `import { Migration } from '@mantiq/database'
|
|
46
|
+
import type { SchemaBuilder } from '@mantiq/database'
|
|
47
|
+
|
|
48
|
+
export default class ${className} extends Migration {
|
|
49
|
+
override async up(schema: SchemaBuilder) {
|
|
50
|
+
await schema.create('${table}', (t) => {
|
|
51
|
+
t.id()
|
|
52
|
+
t.timestamps()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override async down(schema: SchemaBuilder) {
|
|
57
|
+
await schema.dropIfExists('${table}')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private alterStub(className: string, table: string): string {
|
|
64
|
+
return `import { Migration } from '@mantiq/database'
|
|
65
|
+
import type { SchemaBuilder } from '@mantiq/database'
|
|
66
|
+
|
|
67
|
+
export default class ${className} extends Migration {
|
|
68
|
+
override async up(schema: SchemaBuilder) {
|
|
69
|
+
await schema.table('${table}', (t) => {
|
|
70
|
+
//
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override async down(schema: SchemaBuilder) {
|
|
75
|
+
await schema.table('${table}', (t) => {
|
|
76
|
+
//
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private blankStub(className: string): string {
|
|
84
|
+
return `import { Migration } from '@mantiq/database'
|
|
85
|
+
import type { SchemaBuilder } from '@mantiq/database'
|
|
86
|
+
|
|
87
|
+
export default class ${className} extends Migration {
|
|
88
|
+
override async up(schema: SchemaBuilder) {
|
|
89
|
+
//
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override async down(schema: SchemaBuilder) {
|
|
93
|
+
//
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private timestamp(): string {
|
|
100
|
+
const now = new Date()
|
|
101
|
+
return [
|
|
102
|
+
now.getFullYear(),
|
|
103
|
+
String(now.getMonth() + 1).padStart(2, '0'),
|
|
104
|
+
String(now.getDate()).padStart(2, '0'),
|
|
105
|
+
String(now.getHours()).padStart(2, '0'),
|
|
106
|
+
String(now.getMinutes()).padStart(2, '0'),
|
|
107
|
+
String(now.getSeconds()).padStart(2, '0'),
|
|
108
|
+
].join('')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private toSnakeCase(name: string): string {
|
|
112
|
+
return name
|
|
113
|
+
.replace(/([A-Z])/g, '_$1')
|
|
114
|
+
.toLowerCase()
|
|
115
|
+
.replace(/^_/, '')
|
|
116
|
+
.replace(/[-\s]+/g, '_')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private toClassName(snakeName: string): string {
|
|
120
|
+
return snakeName
|
|
121
|
+
.split('_')
|
|
122
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
123
|
+
.join('')
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
import { existsSync, mkdirSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
export class MakeModelCommand extends GeneratorCommand {
|
|
6
|
+
override name = 'make:model'
|
|
7
|
+
override description = 'Create a new model class'
|
|
8
|
+
override usage = 'make:model <name> [-m|--migration] [-f|--factory] [-s|--seed]'
|
|
9
|
+
|
|
10
|
+
override directory() { return 'app/Models' }
|
|
11
|
+
override suffix() { return '' }
|
|
12
|
+
|
|
13
|
+
override stub(name: string): string {
|
|
14
|
+
const tableName = this.toTableName(name)
|
|
15
|
+
return `import { Model } from '@mantiq/database'
|
|
16
|
+
|
|
17
|
+
export class ${name} extends Model {
|
|
18
|
+
static override table = '${tableName}'
|
|
19
|
+
|
|
20
|
+
static override fillable = ['name']
|
|
21
|
+
|
|
22
|
+
static override hidden = []
|
|
23
|
+
|
|
24
|
+
static override casts = {}
|
|
25
|
+
}
|
|
26
|
+
`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override async handle(args: ParsedArgs): Promise<number> {
|
|
30
|
+
const code = await super.handle(args)
|
|
31
|
+
if (code !== 0) return code
|
|
32
|
+
|
|
33
|
+
const name = this.toClassName(args.args[0]!)
|
|
34
|
+
|
|
35
|
+
// Also create migration if -m or --migration
|
|
36
|
+
if (args.flags['m'] || args.flags['migration']) {
|
|
37
|
+
await this.createMigration(name)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Also create factory if -f or --factory
|
|
41
|
+
if (args.flags['f'] || args.flags['factory']) {
|
|
42
|
+
await this.createFactory(name)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Also create seeder if -s or --seed
|
|
46
|
+
if (args.flags['s'] || args.flags['seed']) {
|
|
47
|
+
await this.createSeeder(name)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private async createMigration(name: string): Promise<void> {
|
|
54
|
+
const tableName = this.toTableName(name)
|
|
55
|
+
const timestamp = this.timestamp()
|
|
56
|
+
const fileName = `${timestamp}_create_${tableName}_table.ts`
|
|
57
|
+
const dir = `${process.cwd()}/database/migrations`
|
|
58
|
+
mkdirSync(dir, { recursive: true })
|
|
59
|
+
const filePath = `${dir}/${fileName}`
|
|
60
|
+
|
|
61
|
+
if (existsSync(filePath)) return
|
|
62
|
+
|
|
63
|
+
const content = `import { Migration } from '@mantiq/database'
|
|
64
|
+
import type { SchemaBuilder } from '@mantiq/database'
|
|
65
|
+
|
|
66
|
+
export default class Create${name}sTable extends Migration {
|
|
67
|
+
override async up(schema: SchemaBuilder) {
|
|
68
|
+
await schema.create('${tableName}', (t) => {
|
|
69
|
+
t.id()
|
|
70
|
+
t.string('name')
|
|
71
|
+
t.timestamps()
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override async down(schema: SchemaBuilder) {
|
|
76
|
+
await schema.dropIfExists('${tableName}')
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`
|
|
80
|
+
await Bun.write(filePath, content)
|
|
81
|
+
this.io.success(`Created database/migrations/${fileName}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async createFactory(name: string): Promise<void> {
|
|
85
|
+
const dir = `${process.cwd()}/database/factories`
|
|
86
|
+
const fileName = `${name}Factory.ts`
|
|
87
|
+
const filePath = `${dir}/${fileName}`
|
|
88
|
+
mkdirSync(dir, { recursive: true })
|
|
89
|
+
|
|
90
|
+
if (existsSync(filePath)) return
|
|
91
|
+
|
|
92
|
+
const content = `import { Factory } from '@mantiq/database'
|
|
93
|
+
import type { Faker } from '@mantiq/database'
|
|
94
|
+
import { ${name} } from '../../app/Models/${name}.ts'
|
|
95
|
+
|
|
96
|
+
export class ${name}Factory extends Factory<${name}> {
|
|
97
|
+
protected override model = ${name}
|
|
98
|
+
|
|
99
|
+
override definition(index: number, fake: Faker) {
|
|
100
|
+
return {
|
|
101
|
+
name: fake.name(),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`
|
|
106
|
+
await Bun.write(filePath, content)
|
|
107
|
+
this.io.success(`Created database/factories/${fileName}`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async createSeeder(name: string): Promise<void> {
|
|
111
|
+
const dir = `${process.cwd()}/database/seeders`
|
|
112
|
+
const fileName = `${name}Seeder.ts`
|
|
113
|
+
const filePath = `${dir}/${fileName}`
|
|
114
|
+
mkdirSync(dir, { recursive: true })
|
|
115
|
+
|
|
116
|
+
if (existsSync(filePath)) return
|
|
117
|
+
|
|
118
|
+
const content = `import { Seeder } from '@mantiq/database'
|
|
119
|
+
|
|
120
|
+
export default class ${name}Seeder extends Seeder {
|
|
121
|
+
override async run() {
|
|
122
|
+
// TODO: seed ${name.toLowerCase()} data
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
`
|
|
126
|
+
await Bun.write(filePath, content)
|
|
127
|
+
this.io.success(`Created database/seeders/${fileName}`)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private toTableName(name: string): string {
|
|
131
|
+
// PascalCase to snake_case plural: User -> users, BlogPost -> blog_posts
|
|
132
|
+
return name
|
|
133
|
+
.replace(/([A-Z])/g, '_$1')
|
|
134
|
+
.toLowerCase()
|
|
135
|
+
.replace(/^_/, '')
|
|
136
|
+
.replace(/s?$/, 's')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private timestamp(): string {
|
|
140
|
+
const now = new Date()
|
|
141
|
+
return [
|
|
142
|
+
now.getFullYear(),
|
|
143
|
+
String(now.getMonth() + 1).padStart(2, '0'),
|
|
144
|
+
String(now.getDate()).padStart(2, '0'),
|
|
145
|
+
String(now.getHours()).padStart(2, '0'),
|
|
146
|
+
String(now.getMinutes()).padStart(2, '0'),
|
|
147
|
+
String(now.getSeconds()).padStart(2, '0'),
|
|
148
|
+
].join('')
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeObserverCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:observer'
|
|
6
|
+
override description = 'Create a new model observer class'
|
|
7
|
+
override usage = 'make:observer <name> [--model=ModelName]'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Observers' }
|
|
10
|
+
override suffix() { return 'Observer' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, args: ParsedArgs): string {
|
|
13
|
+
const className = `${name}Observer`
|
|
14
|
+
const model = (args.flags['model'] as string) || name
|
|
15
|
+
return `import type { ${model} } from '../Models/${model}.ts'
|
|
16
|
+
|
|
17
|
+
export class ${className} {
|
|
18
|
+
async created(model: ${model}): Promise<void> {
|
|
19
|
+
//
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async updated(model: ${model}): Promise<void> {
|
|
23
|
+
//
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async deleted(model: ${model}): Promise<void> {
|
|
27
|
+
//
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeProviderCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:provider'
|
|
6
|
+
override description = 'Create a new service provider class'
|
|
7
|
+
override usage = 'make:provider <name>'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Providers' }
|
|
10
|
+
override suffix() { return 'ServiceProvider' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, _args: ParsedArgs): string {
|
|
13
|
+
const className = `${name}ServiceProvider`
|
|
14
|
+
return `import { ServiceProvider } from '@mantiq/core'
|
|
15
|
+
|
|
16
|
+
export class ${className} extends ServiceProvider {
|
|
17
|
+
override register(): void {
|
|
18
|
+
// Register bindings into the container
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override async boot(): Promise<void> {
|
|
22
|
+
// Boot application services
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeRequestCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:request'
|
|
6
|
+
override description = 'Create a new form request class'
|
|
7
|
+
override usage = 'make:request <name>'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Http/Requests' }
|
|
10
|
+
override suffix() { return 'Request' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, _args: ParsedArgs): string {
|
|
13
|
+
const className = `${name}Request`
|
|
14
|
+
return `import { FormRequest } from '@mantiq/validation'
|
|
15
|
+
|
|
16
|
+
export class ${className} extends FormRequest {
|
|
17
|
+
override authorize(): boolean {
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override rules(): Record<string, string> {
|
|
22
|
+
return {
|
|
23
|
+
//
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeRuleCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:rule'
|
|
6
|
+
override description = 'Create a new validation rule'
|
|
7
|
+
override usage = 'make:rule <name>'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'app/Rules' }
|
|
10
|
+
override suffix() { return 'Rule' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, _args: ParsedArgs): string {
|
|
13
|
+
const className = `${name}Rule`
|
|
14
|
+
const ruleName = this.toRuleName(name)
|
|
15
|
+
return `import type { ValidationRule } from '@mantiq/validation'
|
|
16
|
+
|
|
17
|
+
export const ${ruleName}: ValidationRule = {
|
|
18
|
+
name: '${ruleName}',
|
|
19
|
+
|
|
20
|
+
validate(value: unknown, args: string[], field: string): boolean | string {
|
|
21
|
+
// Return true if valid, or a string error message if invalid
|
|
22
|
+
return true
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ${className} {
|
|
27
|
+
/** Register this rule with the validator */
|
|
28
|
+
static rule(): ValidationRule {
|
|
29
|
+
return ${ruleName}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private toRuleName(name: string): string {
|
|
36
|
+
return name.charAt(0).toLowerCase() + name.slice(1)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { GeneratorCommand } from './GeneratorCommand.ts'
|
|
2
|
+
import type { ParsedArgs } from '../Parser.ts'
|
|
3
|
+
|
|
4
|
+
export class MakeSeederCommand extends GeneratorCommand {
|
|
5
|
+
override name = 'make:seeder'
|
|
6
|
+
override description = 'Create a new seeder class'
|
|
7
|
+
override usage = 'make:seeder <name>'
|
|
8
|
+
|
|
9
|
+
override directory() { return 'database/seeders' }
|
|
10
|
+
override suffix() { return 'Seeder' }
|
|
11
|
+
|
|
12
|
+
override stub(name: string, _args: ParsedArgs): string {
|
|
13
|
+
return `import { Seeder } from '@mantiq/database'
|
|
14
|
+
|
|
15
|
+
export default class ${name}Seeder extends Seeder {
|
|
16
|
+
override async run() {
|
|
17
|
+
// TODO: seed data
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
`
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
export class MakeTestCommand extends Command {
|
|
7
|
+
override name = 'make:test'
|
|
8
|
+
override description = 'Create a new test file'
|
|
9
|
+
override usage = 'make:test <name> [--unit]'
|
|
10
|
+
|
|
11
|
+
override async handle(args: ParsedArgs): Promise<number> {
|
|
12
|
+
const rawName = args.args[0]
|
|
13
|
+
if (!rawName) {
|
|
14
|
+
this.io.error('Please provide a test name. Usage: make:test <name>')
|
|
15
|
+
return 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const isUnit = !!args.flags['unit']
|
|
19
|
+
const subDir = isUnit ? 'unit' : 'feature'
|
|
20
|
+
const className = this.toClassName(rawName)
|
|
21
|
+
const fileName = `${className}.test.ts`
|
|
22
|
+
const dir = `${process.cwd()}/tests/${subDir}`
|
|
23
|
+
const filePath = `${dir}/${fileName}`
|
|
24
|
+
|
|
25
|
+
if (existsSync(filePath)) {
|
|
26
|
+
this.io.error(`${fileName} already exists.`)
|
|
27
|
+
return 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
mkdirSync(dirname(filePath), { recursive: true })
|
|
31
|
+
|
|
32
|
+
const content = isUnit
|
|
33
|
+
? this.unitStub(className)
|
|
34
|
+
: this.featureStub(className)
|
|
35
|
+
|
|
36
|
+
await Bun.write(filePath, content)
|
|
37
|
+
this.io.success(`Created tests/${subDir}/${fileName}`)
|
|
38
|
+
return 0
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private unitStub(name: string): string {
|
|
42
|
+
return `import { describe, test, expect } from 'bun:test'
|
|
43
|
+
|
|
44
|
+
describe('${name}', () => {
|
|
45
|
+
test('example', () => {
|
|
46
|
+
expect(true).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private featureStub(name: string): string {
|
|
53
|
+
return `import { describe, test, expect } from 'bun:test'
|
|
54
|
+
|
|
55
|
+
describe('${name}', () => {
|
|
56
|
+
test('returns a successful response', async () => {
|
|
57
|
+
const response = await fetch('http://localhost:3000/api/ping')
|
|
58
|
+
expect(response.status).toBe(200)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private toClassName(name: string): string {
|
|
65
|
+
return name
|
|
66
|
+
.replace(/[-_]+(.)/g, (_, c: string) => c.toUpperCase())
|
|
67
|
+
.replace(/^(.)/, (_, c: string) => c.toUpperCase())
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -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 MigrateCommand extends Command {
|
|
7
|
+
override name = 'migrate'
|
|
8
|
+
override description = 'Run pending database migrations'
|
|
9
|
+
|
|
10
|
+
override async handle(_args: ParsedArgs): Promise<number> {
|
|
11
|
+
const connection = getManager().connection()
|
|
12
|
+
const migrationsPath = this.resolveMigrationsPath()
|
|
13
|
+
const migrator = new Migrator(connection, { migrationsPath })
|
|
14
|
+
|
|
15
|
+
this.io.info('Running migrations...')
|
|
16
|
+
const ran = await migrator.run()
|
|
17
|
+
|
|
18
|
+
if (ran.length === 0) {
|
|
19
|
+
this.io.success('Nothing to migrate.')
|
|
20
|
+
} else {
|
|
21
|
+
for (const name of ran) {
|
|
22
|
+
this.io.twoColumn(` ${this.io.green('DONE')}`, name, 8)
|
|
23
|
+
}
|
|
24
|
+
this.io.newLine()
|
|
25
|
+
this.io.success(`Ran ${ran.length} migration${ran.length > 1 ? 's' : ''}.`)
|
|
26
|
+
}
|
|
27
|
+
return 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected resolveMigrationsPath(): string {
|
|
31
|
+
return `${process.cwd()}/database/migrations`
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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 MigrateFreshCommand extends Command {
|
|
7
|
+
override name = 'migrate:fresh'
|
|
8
|
+
override description = 'Drop all tables and re-run 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.warn('Dropping all tables...')
|
|
20
|
+
const ran = await migrator.fresh()
|
|
21
|
+
|
|
22
|
+
for (const name of ran) {
|
|
23
|
+
this.io.twoColumn(` ${this.io.green('DONE')}`, name, 8)
|
|
24
|
+
}
|
|
25
|
+
this.io.newLine()
|
|
26
|
+
this.io.success(`Fresh migration complete — ${ran.length} migration${ran.length > 1 ? 's' : ''} ran.`)
|
|
27
|
+
|
|
28
|
+
// Run seeder if --seed flag
|
|
29
|
+
if (args.flags['seed']) {
|
|
30
|
+
this.io.info('Seeding...')
|
|
31
|
+
try {
|
|
32
|
+
const seederPath = `${process.cwd()}/database/seeders/DatabaseSeeder.ts`
|
|
33
|
+
const mod = await import(seederPath)
|
|
34
|
+
const SeederClass = mod.default
|
|
35
|
+
const seeder = new SeederClass()
|
|
36
|
+
seeder.setConnection(connection)
|
|
37
|
+
await seeder.run()
|
|
38
|
+
this.io.success('Database seeded.')
|
|
39
|
+
} catch (e: any) {
|
|
40
|
+
this.io.error(`Seeding failed: ${e.message}`)
|
|
41
|
+
return 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return 0
|
|
46
|
+
}
|
|
47
|
+
}
|