add db migration
This commit is contained in:
parent
64fcacdb3d
commit
77e27a4b93
@ -30,7 +30,7 @@ export async function MigrationCreateHandler(
|
||||
}
|
||||
const runner = new TypeormMigrationCreate();
|
||||
console.log();
|
||||
await runner.handler({ name: cname, dir: dbConfig.path.migration });
|
||||
await runner.handler({ name: cname, dir: dbConfig.paths.migration });
|
||||
spinner.start(chalk.greenBright.underline('\n 👍 Finished create migration'));
|
||||
} catch (e) {
|
||||
await panic({ spinner, message: 'Create migration failed!', error: e });
|
||||
|
58
src/modules/database/commands/migration.generate.command.ts
Normal file
58
src/modules/database/commands/migration.generate.command.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { Arguments } from 'yargs';
|
||||
|
||||
import { CommandItem } from '@/modules/core/types';
|
||||
import { MigrationGenerateHandler } from '@/modules/database/commands/migration.generate.handler';
|
||||
import { MigrationGenerateArguments } from '@/modules/database/commands/types';
|
||||
|
||||
export const GenerateMigrationCommand: CommandItem<any, MigrationGenerateArguments> = async ({
|
||||
configure,
|
||||
}) => ({
|
||||
instant: true,
|
||||
command: ['db:migration:generate', 'dbmg'],
|
||||
describe: 'Auto generates a new migration file with sql needs to be executed to update schema.',
|
||||
builder: {
|
||||
connection: {
|
||||
type: 'string',
|
||||
alias: 'c',
|
||||
describe: 'Connection name of typeorm to connect database.',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
alias: 'n',
|
||||
describe: 'Name of the migration class.',
|
||||
},
|
||||
run: {
|
||||
type: 'boolean',
|
||||
alias: 'r',
|
||||
describe: 'Run migration after generated.',
|
||||
default: false,
|
||||
},
|
||||
dir: {
|
||||
type: 'string',
|
||||
alias: 'd',
|
||||
describe: 'Which directory where migration should be generated.',
|
||||
},
|
||||
pretty: {
|
||||
type: 'boolean',
|
||||
alias: 'p',
|
||||
describe: 'Pretty-print generated SQL',
|
||||
default: false,
|
||||
},
|
||||
dryrun: {
|
||||
type: 'boolean',
|
||||
alias: 'dr',
|
||||
describe: 'Prints out the contents of the migration instead of writing it to a file',
|
||||
default: false,
|
||||
},
|
||||
check: {
|
||||
type: 'boolean',
|
||||
alias: 'ch',
|
||||
describe:
|
||||
'Verifies that the current database is up to date and that no migrations are needed. Otherwise exits with code 1.',
|
||||
default: false,
|
||||
},
|
||||
} as const,
|
||||
|
||||
handler: async (args: Arguments<MigrationGenerateArguments>) =>
|
||||
MigrationGenerateHandler(configure, args),
|
||||
});
|
52
src/modules/database/commands/migration.generate.handler.ts
Normal file
52
src/modules/database/commands/migration.generate.handler.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import chalk from 'chalk';
|
||||
import { isNil, pick } from 'lodash';
|
||||
import ora from 'ora';
|
||||
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||
import { Arguments } from 'yargs';
|
||||
|
||||
import { Configure } from '@/modules/config/configure';
|
||||
import { getRandomString, panic } from '@/modules/core/helpers';
|
||||
import { MigrationRunHandler } from '@/modules/database/commands/migration.run.handler';
|
||||
import { TypeormMigrationGenerate } from '@/modules/database/commands/typeorm.migration.generate';
|
||||
import { MigrationGenerateArguments } from '@/modules/database/commands/types';
|
||||
|
||||
import { DBOptions } from '../types';
|
||||
|
||||
export async function MigrationGenerateHandler(
|
||||
configure: Configure,
|
||||
args: Arguments<MigrationGenerateArguments>,
|
||||
) {
|
||||
await MigrationRunHandler(configure, { connection: args.connection } as any);
|
||||
console.log();
|
||||
const spinner = ora('Start to generate migration');
|
||||
const cname = args.connection ?? 'default';
|
||||
try {
|
||||
spinner.start();
|
||||
console.log();
|
||||
const { connections = [] }: DBOptions = await configure.get<DBOptions>('database');
|
||||
const dbConfig = connections.find(({ name }) => name === cname);
|
||||
if (isNil(dbConfig)) {
|
||||
await panic(`Database connection named ${cname} not exists!`);
|
||||
}
|
||||
console.log();
|
||||
const runner = new TypeormMigrationGenerate();
|
||||
const dataSource = new DataSource({ ...dbConfig } as DataSourceOptions);
|
||||
console.log();
|
||||
await runner.handler({
|
||||
name: args.name ?? getRandomString(6),
|
||||
dir: dbConfig.paths.migration,
|
||||
dataSource,
|
||||
...pick(args, ['pretty', 'outputJs', 'dryrun', 'check']),
|
||||
});
|
||||
if (dataSource.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
spinner.succeed(chalk.greenBright.underline('\n 👍 Finished generate migration'));
|
||||
if (args.run) {
|
||||
console.log();
|
||||
await MigrationRunHandler(configure, { connection: args.connection } as any);
|
||||
}
|
||||
} catch (error) {
|
||||
await panic({ spinner, message: 'Generate migration failed!', error });
|
||||
}
|
||||
}
|
53
src/modules/database/commands/migration.run.command.ts
Normal file
53
src/modules/database/commands/migration.run.command.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Arguments } from 'yargs';
|
||||
|
||||
import { CommandItem } from '@/modules/core/types';
|
||||
import { MigrationRunHandler } from '@/modules/database/commands/migration.run.handler';
|
||||
import { MigrationRunArguments } from '@/modules/database/commands/types';
|
||||
|
||||
/**
|
||||
* 运行迁移
|
||||
* @param configure
|
||||
* @constructor
|
||||
*/
|
||||
export const RunMigrationCommand: CommandItem<any, MigrationRunArguments> = async ({
|
||||
configure,
|
||||
}) => ({
|
||||
source: true,
|
||||
command: ['db:migration:run', 'dbmr'],
|
||||
describe: 'Runs all pending migrations.',
|
||||
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)',
|
||||
},
|
||||
refresh: {
|
||||
type: 'boolean',
|
||||
alias: 'r',
|
||||
describe: 'drop database schema and run migration',
|
||||
default: false,
|
||||
},
|
||||
|
||||
onlydrop: {
|
||||
type: 'boolean',
|
||||
alias: 'o',
|
||||
describe: 'only drop database schema',
|
||||
default: false,
|
||||
},
|
||||
} as const,
|
||||
handler: async (args: Arguments<MigrationRunArguments>) => MigrationRunHandler(configure, args),
|
||||
});
|
75
src/modules/database/commands/migration.run.handler.ts
Normal file
75
src/modules/database/commands/migration.run.handler.ts
Normal file
@ -0,0 +1,75 @@
|
||||
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 { TypeormMigrationRun } from '@/modules/database/commands/typeorm.migration.run';
|
||||
import { MigrationRunArguments } from '@/modules/database/commands/types';
|
||||
import { DBOptions } from '@/modules/database/types';
|
||||
|
||||
/**
|
||||
* 运行迁移处理器
|
||||
* @param configure
|
||||
* @param args
|
||||
* @constructor
|
||||
*/
|
||||
export async function MigrationRunHandler(
|
||||
configure: Configure,
|
||||
args: Arguments<MigrationRunArguments>,
|
||||
) {
|
||||
const spinner = ora('Start to run migration...');
|
||||
const cname = args.connection ?? 'default';
|
||||
let dataSource: DataSource | undefined;
|
||||
try {
|
||||
spinner.start();
|
||||
const { connections = [] }: DBOptions = await configure.get<DBOptions>('database');
|
||||
const dbConfig = connections.find(({ name }) => name === cname);
|
||||
if (isNil(dbConfig)) {
|
||||
await panic(`Database connection named ${cname} not exists!`);
|
||||
}
|
||||
const dropSchema = args.refresh || args.onlydrop;
|
||||
console.log();
|
||||
const runner = new TypeormMigrationRun();
|
||||
dataSource = new DataSource({ ...dbConfig } as DataSourceOptions);
|
||||
if (dataSource && dataSource.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
const options = {
|
||||
subscribers: [],
|
||||
synchronize: false,
|
||||
migrationsRun: false,
|
||||
dropSchema,
|
||||
logging: ['error'],
|
||||
migrations: [
|
||||
join(dbConfig.paths.migration, '**/*.ts'),
|
||||
join(dbConfig.paths.migration, '**/*.js'),
|
||||
],
|
||||
} as any;
|
||||
if (dropSchema) {
|
||||
dataSource.setOptions(options);
|
||||
await dataSource.initialize();
|
||||
await dataSource.destroy();
|
||||
spinner.succeed(chalk.greenBright.underline('\n 👍 Finished drop database schema'));
|
||||
if (args.onlydrop) {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
dataSource.setOptions({ ...options, dropSchema: false });
|
||||
await dataSource.initialize();
|
||||
console.log();
|
||||
await runner.handler({ dataSource, transaction: args.transaction, fake: args.fake });
|
||||
spinner.succeed(chalk.greenBright.underline('\n 👍 Finished run migrations'));
|
||||
} catch (error) {
|
||||
await panic({ spinner, message: 'Run migrations failed!', error });
|
||||
} finally {
|
||||
if (dataSource && dataSource.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,152 @@
|
||||
import { resolve } from 'path';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { upperFirst } from 'lodash';
|
||||
import { format } from 'mysql2';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { CommandUtils } from 'typeorm/commands/CommandUtils';
|
||||
import { PlatformTools } from 'typeorm/platform/PlatformTools';
|
||||
|
||||
import { camelCase } from 'typeorm/util/StringUtils';
|
||||
|
||||
import { MigrationGenerateOptions } from '@/modules/database/commands/types';
|
||||
|
||||
type HandlerOptions = MigrationGenerateOptions & { dataSource: DataSource };
|
||||
type HandlerOptions = MigrationGenerateOptions & { dataSource: DataSource } & { dir: string };
|
||||
|
||||
export class TypeormMigrationGenerate {
|
||||
async handler(args: HandlerOptions) {}
|
||||
async handler(args: HandlerOptions) {
|
||||
const timestamp = new Date().getTime();
|
||||
const fileExt = '.ts';
|
||||
const directory = args.dir.startsWith('/') ? args.dir : resolve(process.cwd(), args.dir);
|
||||
const filename = `${timestamp}-${args.name}`;
|
||||
const filePath = `${directory}/${filename}${fileExt}`;
|
||||
const { dataSource } = args;
|
||||
|
||||
try {
|
||||
dataSource.setOptions({
|
||||
synchronize: false,
|
||||
migrationsRun: false,
|
||||
dropSchema: false,
|
||||
logging: false,
|
||||
});
|
||||
await dataSource.initialize();
|
||||
const upSqls: string[] = [];
|
||||
const downSqls: string[] = [];
|
||||
|
||||
try {
|
||||
const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
|
||||
if (args.pretty) {
|
||||
sqlInMemory.upQueries.forEach((upSql) => {
|
||||
upSql.query = TypeormMigrationGenerate.prettifyQuery(upSql.query);
|
||||
});
|
||||
sqlInMemory.downQueries.forEach((downSql) => {
|
||||
downSql.query = TypeormMigrationGenerate.prettifyQuery(downSql.query);
|
||||
});
|
||||
}
|
||||
sqlInMemory.upQueries.forEach((upQuery) => {
|
||||
upSqls.push(
|
||||
` await queryRunner.query(\`${upQuery.query.replace(
|
||||
/`/g,
|
||||
'\\`',
|
||||
)}\`${TypeormMigrationGenerate.queryParams(upQuery.parameters)});`,
|
||||
);
|
||||
});
|
||||
sqlInMemory.downQueries.forEach((downQuery) => {
|
||||
downSqls.push(
|
||||
` await queryRunner.query(\`${downQuery.query.replace(
|
||||
/`/g,
|
||||
'\\`',
|
||||
)}\`${TypeormMigrationGenerate.queryParams(downQuery.parameters)});`,
|
||||
);
|
||||
});
|
||||
} finally {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
|
||||
if (!upSqls.length) {
|
||||
console.log(chalk.green(`No changes in database schema were found`));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const fileContent = TypeormMigrationGenerate.getTemplate(
|
||||
args.name,
|
||||
timestamp,
|
||||
upSqls,
|
||||
downSqls.reverse(),
|
||||
);
|
||||
if (args.check) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`Unexpected changes in database schema were found in check mode:\n\n${chalk.white(
|
||||
fileContent,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (args.dryrun) {
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Migration ${chalk.blue(
|
||||
filePath,
|
||||
)} has content:\n\n${chalk.white(fileContent)}`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await CommandUtils.createFile(filePath, fileContent);
|
||||
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Migration ${chalk.blue(filePath)} has been generated successfully.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
PlatformTools.logCmdErr('Error during migration generation:', e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected static queryParams(params: any[] | undefined): string {
|
||||
if (!params || !params.length) {
|
||||
return '';
|
||||
}
|
||||
return `,${JSON.stringify(params)}`;
|
||||
}
|
||||
|
||||
protected static prettifyQuery(query: string) {
|
||||
const formatQuery = format(query, { indent: ' ' });
|
||||
return `\n${formatQuery.replace(/^/gm, ' ')}\n `;
|
||||
}
|
||||
|
||||
protected static getTemplate(
|
||||
name: string,
|
||||
timestamp: number,
|
||||
upSqls: string[],
|
||||
downSqls: string[],
|
||||
): string {
|
||||
const migrationName = `${camelCase(upperFirst(name), true)}${timestamp}`;
|
||||
|
||||
return `import typeorm = require('typeorm');
|
||||
|
||||
class ${migrationName} implements typeorm.MigrationInterface {
|
||||
name = '${migrationName}'
|
||||
|
||||
public async up(queryRunner: typeorm.QueryRunner): Promise<void> {
|
||||
${upSqls.join(`
|
||||
`)}
|
||||
}
|
||||
|
||||
public async down(queryRunner: typeorm.QueryRunner): Promise<void> {
|
||||
${downSqls.join(`
|
||||
`)}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ${migrationName}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
28
src/modules/database/commands/typeorm.migration.run.ts
Normal file
28
src/modules/database/commands/typeorm.migration.run.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { MigrationRunOptions } from '@/modules/database/commands/types';
|
||||
|
||||
type HandlerOptions = MigrationRunOptions & { dataSource: DataSource };
|
||||
|
||||
export class TypeormMigrationRun {
|
||||
async handler({ transaction, fake, dataSource }: HandlerOptions) {
|
||||
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.runMigrations(options);
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ export interface MigrationCreateOptions {
|
||||
/**
|
||||
* 生成迁移命令参数
|
||||
*/
|
||||
export type MigrationGenerateArguments = TypeOrmArguments & MigrationCreateOptions;
|
||||
export type MigrationGenerateArguments = TypeOrmArguments & MigrationGenerateOptions;
|
||||
|
||||
/**
|
||||
* 生成迁移处理器选项
|
||||
@ -32,3 +32,26 @@ export interface MigrationGenerateOptions {
|
||||
dryrun?: boolean;
|
||||
check?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行迁移的命令参数
|
||||
*/
|
||||
export type MigrationRunArguments = TypeOrmArguments & MigrationRunOptions;
|
||||
|
||||
/**
|
||||
* 运行迁移处理器选项
|
||||
*/
|
||||
export interface MigrationRunOptions extends MigrationRevertOptions {
|
||||
refresh?: boolean;
|
||||
|
||||
onlydrop?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复迁移处理器选项
|
||||
*/
|
||||
export interface MigrationRevertOptions {
|
||||
transaction?: string;
|
||||
|
||||
fake?: boolean;
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ export type DBOptions = RecordAny & {
|
||||
};
|
||||
|
||||
type DBAdditionalOption = {
|
||||
path?: {
|
||||
paths?: {
|
||||
migration?: string;
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user