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 = 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 { await this.registerListeners() const namespace = this.option('package') const identifier = this.option('identifier') let identifiers if ( namespace ) { identifiers = (this.injector.make(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)).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 { 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 { await this.subscriptions.awaitMapCall('unsubscribe') } }