diff --git a/src/commands/migrate.js b/src/commands/migrate.js index 3a3eb3227..2e87a05c4 100644 --- a/src/commands/migrate.js +++ b/src/commands/migrate.js @@ -24,6 +24,11 @@ exports.builder = (yargs) => 'Migration name. When specified, only this migration will be run. Mutually exclusive with --to and --from', type: 'string', conflicts: ['to', 'from'], + }) + .option('skip-execution', { + describe: 'Mark migration as completed without actually executing it', + default: false, + type: 'boolean', }).argv; exports.handler = async function (args) { @@ -85,9 +90,53 @@ function migrate(args) { } options.from = args.from; } + if (args['skip-execution']) { + let migrationsToRun = migrations; + + if (args.name) { + migrationsToRun = [args.name]; + } else { + if (options.from) { + const fromIndex = migrations.findIndex( + (m) => m.file === options.from + ); + if (fromIndex !== -1) { + migrationsToRun = migrations.slice(fromIndex + 1); + } + } + if (options.to) { + const toIndex = migrationsToRun.findIndex( + (m) => m.file === options.to + ); + if (toIndex !== -1) { + migrationsToRun = migrationsToRun.slice(0, toIndex + 1); + } + } + } + + if (migrationsToRun.length === 0) { + helpers.view.log('No migrations to skip.'); + process.exit(0); + } + + return Promise.all( + migrationsToRun.map((migration) => { + helpers.view.log( + 'Marking migration as executed:', + migration.file + ); + return migrator.storage.logMigration(migration.file); + }) + ).then(() => { + return { executed: true }; + }); + } + return options; }) .then((options) => { + if (options && options.executed) return; + if (args.name) { return migrator.up(args.name); } else { diff --git a/test/db/migrate-skip.test.js b/test/db/migrate-skip.test.js new file mode 100644 index 000000000..4d41828d4 --- /dev/null +++ b/test/db/migrate-skip.test.js @@ -0,0 +1,64 @@ +const expect = require('expect.js'); +const Support = require(__dirname + '/../support'); +const helpers = require(__dirname + '/../support/helpers'); +const gulp = require('gulp'); + +describe(Support.getTestDialectTeaser('db:migrate --skip-execution'), () => { + const prepare = function (callback) { + const config = { url: helpers.getTestUrl() }; + const configContent = 'module.exports = ' + JSON.stringify(config); + let result = ''; + + return gulp + .src(Support.resolveSupportPath('tmp')) + .pipe(helpers.clearDirectory()) + .pipe(helpers.runCli('init')) + .pipe(helpers.removeFile('config/config.json')) + .pipe(helpers.copyMigration('createPerson.js')) + .pipe(helpers.overwriteFile(configContent, 'config/config.js')) + .pipe(helpers.runCli('db:migrate --skip-execution', { pipeStdout: true })) + .on('error', (e) => { + callback(e); + }) + .on('data', (data) => { + result += data.toString(); + }) + .on('end', () => { + callback(null, result); + }); + }; + + beforeEach(function () { + const queryInterface = this.sequelize.getQueryInterface(); + this.queryGenerator = + queryInterface.queryGenerator || queryInterface.QueryGenerator; + }); + + it('marks migration as executed but does not execute it', function (done) { + prepare((err, output) => { + if (err) return done(err); + + helpers.readTables(this.sequelize, (tables) => { + expect(tables).to.have.length(1); + expect(tables).to.contain('SequelizeMeta'); + expect(tables).to.not.contain('Person'); + + helpers + .execQuery( + this.sequelize, + this.queryGenerator.selectQuery('SequelizeMeta'), + { raw: true, type: 'SELECT' } + ) + .then((items) => { + expect(items.length).to.equal(1); + expect(items[0].name).to.contain('createPerson'); + + expect(output).to.contain('Marking migration as executed'); + + done(); + }) + .catch((e) => done(e)); + }); + }); + }); +});