diff --git a/commands/Migrate.ts b/commands/Migrate.ts index dcd67c92..5a07b59c 100644 --- a/commands/Migrate.ts +++ b/commands/Migrate.ts @@ -26,6 +26,9 @@ export default class Migrate extends MigrationsBase { @flags.string({ description: 'Define a custom database connection' }) public connection: string + @flags.boolean({ description: 'Explictly force to run migrations in production' }) + public force: boolean + @flags.boolean({ description: 'Print SQL queries, instead of running the migrations' }) public dryRun: boolean @@ -46,6 +49,7 @@ export default class Migrate extends MigrationsBase { */ public async handle (): Promise { const connection = this._db.getRawConnection(this.connection || this._db.primaryConnectionName) + let continueMigrations = !this.application.inProduction || this.force /** * Ensure the define connection name does exists in the @@ -58,6 +62,26 @@ export default class Migrate extends MigrationsBase { return } + /** + * Ask for prompt when running in production and `force` flag is + * not defined + */ + if (!continueMigrations) { + try { + continueMigrations = await this.prompt + .confirm('You are in production environment. Want to continue running migrations?') + } catch (error) { + continueMigrations = false + } + } + + /** + * Prompt cancelled or rejected and hence do not continue + */ + if (!continueMigrations) { + return + } + /** * New up migrator */ diff --git a/commands/Rollback.ts b/commands/Rollback.ts index e82f91e0..731d9e64 100644 --- a/commands/Rollback.ts +++ b/commands/Rollback.ts @@ -29,6 +29,9 @@ export default class Migrate extends MigrationsBase { @flags.boolean({ description: 'Print SQL queries, instead of running the migrations' }) public dryRun: boolean + @flags.boolean({ description: 'Explictly force to run migrations in production' }) + public force: boolean + @flags.number({ description: 'Define custom batch number for rollback. Use 0 to rollback to initial state', }) @@ -51,6 +54,7 @@ export default class Migrate extends MigrationsBase { */ public async handle (): Promise { const connection = this._db.getRawConnection(this.connection || this._db.primaryConnectionName) + let continueMigrations = !this.application.inProduction || this.force /** * Ensure the define connection name does exists in the @@ -63,6 +67,26 @@ export default class Migrate extends MigrationsBase { return } + /** + * Ask for prompt when running in production and `force` flag is + * not defined + */ + if (!continueMigrations) { + try { + continueMigrations = await this.prompt + .confirm('You are in production environment. Want to continue running migrations?') + } catch (error) { + continueMigrations = false + } + } + + /** + * Prompt cancelled or rejected and hence do not continue + */ + if (!continueMigrations) { + return + } + /** * New up migrator */ diff --git a/test/commands/migrate.spec.ts b/test/commands/migrate.spec.ts index e3d69b12..bde5bffb 100644 --- a/test/commands/migrate.spec.ts +++ b/test/commands/migrate.spec.ts @@ -17,12 +17,13 @@ import { Filesystem } from '@poppinss/dev-utils' import { Application } from '@adonisjs/application/build/standalone' import Migrate from '../../commands/Migrate' +import Rollback from '../../commands/Rollback' import { setup, cleanup, getDb } from '../../test-helpers' let db: ReturnType const fs = new Filesystem(join(__dirname, 'app')) -test.group('MakeMigration', (group) => { +test.group('Migrate', (group) => { group.beforeEach(async () => { db = getDb() await setup() @@ -96,4 +97,98 @@ test.group('MakeMigration', (group) => { const migrated = await db.connection().from('adonis_schema').select('*') assert.lengthOf(migrated, 0) }) + + test('prompt during migrations in production without force flag', async (assert) => { + assert.plan(1) + + await fs.add('database/migrations/users.ts', ` + import { Schema } from '../../../../../src/Schema' + module.exports = class User extends Schema { + public async up () { + this.schema.createTable('schema_users', (table) => { + table.increments() + }) + } + } + `) + + const app = new Application(fs.basePath, {} as any, {} as any, {}) + app.inProduction = true + app.environment = 'test' + + const migrate = new Migrate(app, new Kernel(app), db) + migrate.prompt.on('prompt', (prompt) => { + assert.equal(prompt.message, 'You are in production environment. Want to continue running migrations?') + prompt.accept() + }) + await migrate.handle() + }) + + test('do not prompt during migration when force flag is defined', async () => { + await fs.add('database/migrations/users.ts', ` + import { Schema } from '../../../../../src/Schema' + module.exports = class User extends Schema { + public async up () { + this.schema.createTable('schema_users', (table) => { + table.increments() + }) + } + } + `) + + const app = new Application(fs.basePath, {} as any, {} as any, {}) + app.inProduction = true + app.environment = 'test' + + const migrate = new Migrate(app, new Kernel(app), db) + migrate.force = true + migrate.prompt.on('prompt', () => { + throw new Error('Never expected to be here') + }) + await migrate.handle() + }) + + test('prompt during rollback in production without force flag', async (assert) => { + assert.plan(1) + + await fs.add('database/migrations/users.ts', ` + import { Schema } from '../../../../../src/Schema' + module.exports = class User extends Schema { + public async down () { + } + } + `) + + const app = new Application(fs.basePath, {} as any, {} as any, {}) + app.inProduction = true + app.environment = 'test' + + const rollback = new Rollback(app, new Kernel(app), db) + rollback.prompt.on('prompt', (prompt) => { + assert.equal(prompt.message, 'You are in production environment. Want to continue running migrations?') + prompt.accept() + }) + await rollback.handle() + }) + + test('do not prompt during rollback in production when force flag is defined', async () => { + await fs.add('database/migrations/users.ts', ` + import { Schema } from '../../../../../src/Schema' + module.exports = class User extends Schema { + public async down () { + } + } + `) + + const app = new Application(fs.basePath, {} as any, {} as any, {}) + app.inProduction = true + app.environment = 'test' + + const rollback = new Rollback(app, new Kernel(app), db) + rollback.force = true + rollback.prompt.on('prompt', () => { + throw new Error('Never expected to be here') + }) + await rollback.handle() + }) })