Skip to content

Commit

Permalink
feat(commands): introduce force flag and prompt in production
Browse files Browse the repository at this point in the history
We must take the consent when running migrations in production
  • Loading branch information
thetutlage committed Jan 12, 2020
1 parent 231af82 commit 5e1db48
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 1 deletion.
24 changes: 24 additions & 0 deletions commands/Migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -46,6 +49,7 @@ export default class Migrate extends MigrationsBase {
*/
public async handle (): Promise<void> {
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
Expand All @@ -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
*/
Expand Down
24 changes: 24 additions & 0 deletions commands/Rollback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
})
Expand All @@ -51,6 +54,7 @@ export default class Migrate extends MigrationsBase {
*/
public async handle (): Promise<void> {
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
Expand All @@ -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
*/
Expand Down
97 changes: 96 additions & 1 deletion test/commands/migrate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof getDb>
const fs = new Filesystem(join(__dirname, 'app'))

test.group('MakeMigration', (group) => {
test.group('Migrate', (group) => {
group.beforeEach(async () => {
db = getDb()
await setup()
Expand Down Expand Up @@ -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()
})
})

0 comments on commit 5e1db48

Please sign in to comment.