2024-01-22 09:25:36 -08:00
import { Command , Flags } from '@oclif/core' ;
2024-06-24 01:24:05 -07:00
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
2024-02-08 06:13:29 -08:00
import type { DataSourceOptions as ConnectionOptions } from '@n8n/typeorm' ;
2024-06-24 01:24:05 -07:00
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
2024-05-23 09:16:26 -07:00
import { MigrationExecutor , DataSource as Connection } from '@n8n/typeorm' ;
2023-10-25 07:35:22 -07:00
import { Container } from 'typedi' ;
2024-08-22 02:10:37 -07:00
import { Logger } from '@/logger' ;
2024-02-20 05:28:53 -08:00
import { getConnectionOptions } from '@db/config' ;
2023-06-22 00:06:31 -07:00
import type { Migration } from '@db/types' ;
2024-08-27 07:44:32 -07:00
import { wrapMigration } from '@/databases/utils/migration-helpers' ;
2021-10-13 15:21:00 -07:00
2024-04-11 00:20:48 -07:00
// This function is extracted to make it easier to unit test it.
// Mocking turned into a mess due to this command using typeorm and the db
// config directly and customizing and monkey patching parts.
export async function main (
logger : Logger ,
2024-05-23 09:16:26 -07:00
connection : Connection ,
migrationExecutor : MigrationExecutor ,
2024-04-11 00:20:48 -07:00
) {
2024-05-23 09:16:26 -07:00
const executedMigrations = await migrationExecutor . getExecutedMigrations ( ) ;
const lastExecutedMigration = executedMigrations . at ( 0 ) ;
2024-04-11 00:20:48 -07:00
2024-05-23 09:16:26 -07:00
if ( lastExecutedMigration === undefined ) {
logger . error (
"Cancelled command. The database was never migrated. Are you sure you're connected to the right database?." ,
) ;
return ;
}
2024-04-11 00:20:48 -07:00
2024-05-23 09:16:26 -07:00
const lastMigrationInstance = connection . migrations . find ( ( m ) = > {
// Migration names are optional. If a migration has no name property
// TypeORM will default to the class name.
const name1 = m . name ? ? m . constructor . name ;
const name2 = lastExecutedMigration . name ;
return name1 === name2 ;
} ) ;
if ( lastMigrationInstance === undefined ) {
logger . error (
` The last migration that was executed is " ${ lastExecutedMigration . name } ", but I could not find that migration's code in the currently installed version of n8n. ` ,
) ;
logger . error (
'This usually means that you downgraded n8n before running `n8n db:revert`. Please upgrade n8n again and run `n8n db:revert` and then downgrade again.' ,
) ;
2024-04-11 00:20:48 -07:00
return ;
}
2024-05-23 09:16:26 -07:00
if ( ! lastMigrationInstance . down ) {
const message = lastMigrationInstance . name
? ` Cancelled command. The last migration " ${ lastMigrationInstance . name } " was irreversible. `
: 'Cancelled command. The last migration was irreversible.' ;
logger . error ( message ) ;
2024-04-11 00:20:48 -07:00
return ;
}
await connection . undoLastMigration ( ) ;
await connection . destroy ( ) ;
}
2021-10-13 15:21:00 -07:00
export class DbRevertMigrationCommand extends Command {
static description = 'Revert last database migration' ;
static examples = [ '$ n8n db:revert' ] ;
static flags = {
2024-01-22 09:25:36 -08:00
help : Flags.help ( { char : 'h' } ) ,
2021-10-13 15:21:00 -07:00
} ;
2023-10-25 07:35:22 -07:00
protected logger = Container . get ( Logger ) ;
2023-02-10 05:59:20 -08:00
private connection : Connection ;
2021-10-13 15:21:00 -07:00
2023-02-10 05:59:20 -08:00
async init() {
2024-01-22 09:25:36 -08:00
await this . parse ( DbRevertMigrationCommand ) ;
2023-02-10 05:59:20 -08:00
}
async run() {
const connectionOptions : ConnectionOptions = {
2024-02-20 05:28:53 -08:00
. . . getConnectionOptions ( ) ,
2023-02-10 05:59:20 -08:00
subscribers : [ ] ,
synchronize : false ,
migrationsRun : false ,
dropSchema : false ,
logging : [ 'query' , 'error' , 'schema' ] ,
} ;
2024-05-23 09:16:26 -07:00
const connection = new Connection ( connectionOptions ) ;
await connection . initialize ( ) ;
const migrationExecutor = new MigrationExecutor ( connection ) ;
( connectionOptions . migrations as Migration [ ] ) . forEach ( wrapMigration ) ;
return await main ( this . logger , connection , migrationExecutor ) ;
2023-02-10 05:59:20 -08:00
}
async catch ( error : Error ) {
this . logger . error ( 'Error reverting last migration. See log messages for details.' ) ;
this . logger . error ( error . message ) ;
}
protected async finally ( error : Error | undefined ) {
if ( this . connection ? . isInitialized ) await this . connection . destroy ( ) ;
2021-10-13 15:21:00 -07:00
2023-02-10 05:59:20 -08:00
this . exit ( error ? 1 : 0 ) ;
2021-10-13 15:21:00 -07:00
}
}