diff --git a/src/modules/database/commands/index.ts b/src/modules/database/commands/index.ts new file mode 100644 index 0000000..1bf6ce2 --- /dev/null +++ b/src/modules/database/commands/index.ts @@ -0,0 +1,4 @@ +export * from './migration.create.command'; +export * from './migration.revert.command'; +export * from './migration.generate.command'; +export * from './migration.run.command'; diff --git a/src/modules/database/commands/migration.revert.command.ts b/src/modules/database/commands/migration.revert.command.ts new file mode 100644 index 0000000..691ff53 --- /dev/null +++ b/src/modules/database/commands/migration.revert.command.ts @@ -0,0 +1,37 @@ +import { Arguments } from 'yargs'; + +import { CommandItem } from '@/modules/core/types'; +import { MigrationRevertHandler } from '@/modules/database/commands/migration.revert.handler'; +import { MigrationRevertArguments } from '@/modules/database/commands/types'; + +export const RevertMigrationCommand: CommandItem = async ({ + configure, +}) => ({ + source: true, + command: ['db:migration:revert', 'dbmv'], + describe: 'Reverts last executed migration.', + builder: { + connection: { + type: 'string', + alias: 'c', + describe: 'Connection name of typeorm to connect database.', + }, + transaction: { + type: 'string', + alias: 't', + describe: + 'Indicates if transaction should be used or not for migration run/revert/reflash. Enabled by default.', + default: 'default', + }, + fake: { + type: 'boolean', + alias: 'f', + describe: + 'Fakes running the migrations if table schema has already been changed manually or externally ' + + '(e.g. through another project)', + }, + } as const, + + handler: async (args: Arguments) => + MigrationRevertHandler(configure, args), +}); diff --git a/src/modules/database/commands/migration.revert.handler.ts b/src/modules/database/commands/migration.revert.handler.ts new file mode 100644 index 0000000..da43fe3 --- /dev/null +++ b/src/modules/database/commands/migration.revert.handler.ts @@ -0,0 +1,62 @@ +import { join } from 'path'; + +import chalk from 'chalk'; +import { isNil } from 'lodash'; +import ora from 'ora'; +import { DataSource, DataSourceOptions } from 'typeorm'; +import { Arguments } from 'yargs'; + +import { Configure } from '@/modules/config/configure'; +import { panic } from '@/modules/core/helpers'; +import { TypeormMigrationRevert } from '@/modules/database/commands/typeorm.migration.revert'; +import { MigrationRevertArguments } from '@/modules/database/commands/types'; + +import { DBOptions } from '@/modules/database/types'; + +/** + * 移除迁移处理器 + * @param configure + * @param args + * @constructor + */ +export async function MigrationRevertHandler( + configure: Configure, + args: Arguments, +) { + const spinner = ora('Start to revert migration...'); + const cname = args.connection ?? 'default'; + let dataSource: DataSource | undefined; + try { + spinner.start(); + const { connections = [] }: DBOptions = await configure.get('database'); + const dbConfig = connections.find(({ name }) => name === cname); + if (isNil(dbConfig)) { + await panic(`Database connection named ${cname} not exists!`); + } + console.log(); + const runner = new TypeormMigrationRevert(); + dataSource = new DataSource({ ...dbConfig } as DataSourceOptions); + + dataSource.setOptions({ + subscribers: [], + synchronize: false, + migrationsRun: false, + dropSchema: false, + logging: ['error'], + migrations: [ + join(dbConfig.paths.migration, '**/*.ts'), + join(dbConfig.paths.migration, '**/*.js'), + ], + }); + await dataSource.initialize(); + console.log(); + await runner.handler({ dataSource, transaction: args.transaction, fake: args.fake }); + spinner.succeed(chalk.greenBright.underline('\n 👍 Finished revert migrations')); + } catch (error) { + await panic({ spinner, message: 'revert migrations failed!', error }); + } finally { + if (dataSource && dataSource.isInitialized) { + await dataSource.destroy(); + } + } +} diff --git a/src/modules/database/commands/typeorm.migration.revert.ts b/src/modules/database/commands/typeorm.migration.revert.ts new file mode 100644 index 0000000..301aabe --- /dev/null +++ b/src/modules/database/commands/typeorm.migration.revert.ts @@ -0,0 +1,28 @@ +import { DataSource } from 'typeorm'; + +import { MigrationRevertOptions } from '@/modules/database/commands/types'; + +type HandleOptions = MigrationRevertOptions & { dataSource: DataSource }; + +export class TypeormMigrationRevert { + async handler({ transaction, fake, dataSource }: HandleOptions) { + const options = { + transaction: dataSource.options.migrationsTransactionMode ?? 'all', + fake, + }; + switch (transaction) { + case 'all': + options.transaction = 'all'; + break; + case 'none': + case 'false': + options.transaction = 'none'; + break; + case 'each': + options.transaction = 'each'; + break; + default: + } + await dataSource.undoLastMigration(options); + } +} diff --git a/src/modules/database/commands/types.ts b/src/modules/database/commands/types.ts index e2c5b37..0260545 100644 --- a/src/modules/database/commands/types.ts +++ b/src/modules/database/commands/types.ts @@ -55,3 +55,8 @@ export interface MigrationRevertOptions { fake?: boolean; } + +/** + * 恢复迁移的命令参数 + */ +export type MigrationRevertArguments = TypeOrmArguments & MigrationRevertOptions; diff --git a/src/modules/meilisearch/meili.module.ts b/src/modules/meilisearch/meili.module.ts index 53eae74..91fd168 100644 --- a/src/modules/meilisearch/meili.module.ts +++ b/src/modules/meilisearch/meili.module.ts @@ -7,9 +7,9 @@ import { panic } from '../core/helpers'; @Module({}) export class MeiliModule { - static forRoot(configure: Configure): DynamicModule { + static async forRoot(configure: Configure): Promise { if (!configure.has('meili')) { - panic({ message: 'MeilliSearch config not exists' }); + await panic({ message: 'MeiliSearch config not exists' }); } return { global: true, diff --git a/src/options.ts b/src/options.ts index 4889211..c909b44 100644 --- a/src/options.ts +++ b/src/options.ts @@ -10,6 +10,7 @@ import { isNil } from 'lodash'; import * as configs from './config'; import { ContentModule } from './modules/content/content.module'; import { CreateOptions } from './modules/core/types'; +import * as dbCommands from './modules/database/commands'; import { DatabaseModule } from './modules/database/database.module'; import { MeiliModule } from './modules/meilisearch/meili.module'; import { Restful } from './modules/restful/restful'; @@ -17,13 +18,13 @@ import { RestfulModule } from './modules/restful/restful.module'; import { ApiConfig } from './modules/restful/types'; export const createOptions: CreateOptions = { - commands: () => [], + commands: () => [...Object.values(dbCommands)], config: { factories: configs as any, storage: { enable: true } }, modules: async (configure) => [ - DatabaseModule.forRoot(configure), - MeiliModule.forRoot(configure), - RestfulModule.forRoot(configure), - ContentModule.forRoot(configure), + await DatabaseModule.forRoot(configure), + await MeiliModule.forRoot(configure), + await RestfulModule.forRoot(configure), + await ContentModule.forRoot(configure), ], globals: {}, builder: async ({ configure, BootModule }) => {