@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,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
+ }