You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/orm/directive/MigrateDirective.ts

118 lines
3.8 KiB

import {Directive, OptionDefinition, CLIDirective} from '../../cli'
import {Container, Inject, Injectable} from '../../di'
import {Collection} from '../../util'
import {Bus, EventHandlerSubscription} from '../../support/bus'
import {Migrations} from '../services/Migrations'
import {Migrator} from '../migrations/Migrator'
import {ApplyingMigrationEvent} from '../migrations/events/ApplyingMigrationEvent'
import {AppliedMigrationEvent} from '../migrations/events/AppliedMigrationEvent'
import {NothingToMigrateError} from '../migrations/NothingToMigrateError'
/**
* CLI directive that applies migrations using the default Migrator.
* @fixme Support dry run mode
*/
@Injectable()
@CLIDirective()
export class MigrateDirective extends Directive {
@Inject()
protected readonly bus!: Bus
@Inject('injector')
protected readonly injector!: Container
/** Event bus subscriptions. */
protected subscriptions: Collection<EventHandlerSubscription> = new Collection()
getKeywords(): string | string[] {
return ['migrate']
}
getDescription(): string {
return 'apply pending migrations'
}
getOptions(): OptionDefinition[] {
return [
'--package -p {name} | apply migrations for a specific namespace',
'--identifier -i {name} | apply a specific migration, by identifier',
]
}
getHelpText(): string {
return [
'Migrations are single-run code patches used to track changes to things like database schemata.',
'',
'You can create migrations in your app using the ./ex command and they can be applied and rolled-back.',
'',
'./ex migrate:create "Add version column to sessions table"',
'',
'Modules and packages can also register their own migrations. These are run by default.',
'',
'To run the migrations for a specific package, and no others, use the --package option. Example:',
'',
'./ex migrate --package @extollo',
'',
].join('\n')
}
async handle(): Promise<void> {
await this.registerListeners()
const namespace = this.option('package')
const identifier = this.option('identifier')
let identifiers
if ( namespace ) {
identifiers = (this.injector.make<Migrations>(Migrations))
.all(namespace)
.map(id => `${namespace}:${id}`)
}
if ( identifier ) {
if ( !identifiers ) {
identifiers = [identifier]
}
identifiers = identifiers.filter(x => x === identifier)
}
let error
try {
await (this.injector.make<Migrator>(Migrator)).migrate(identifiers)
} catch (e) {
if ( e instanceof NothingToMigrateError ) {
this.info(e.message)
} else {
error = e
this.error(e)
}
} finally {
await this.removeListeners()
}
if ( error ) {
throw error
}
}
/**
* Register event bus listeners to print messages for the user.
* @protected
*/
protected async registerListeners(): Promise<void> {
this.subscriptions.push(await this.bus.subscribe(ApplyingMigrationEvent, event => {
this.info(`Applying migration ${event.migration.identifier}...`)
}))
this.subscriptions.push(await this.bus.subscribe(AppliedMigrationEvent, event => {
this.success(`Applied migration: ${event.migration.identifier}`)
}))
}
/** Remove event bus listeners before finish. */
protected async removeListeners(): Promise<void> {
await this.subscriptions.awaitMapCall('unsubscribe')
}
}