diff --git a/lib/migrate/index.js b/lib/migrate/index.js index 5416087e..b35e7eb2 100644 --- a/lib/migrate/index.js +++ b/lib/migrate/index.js @@ -1,3 +1,9 @@ +'use strict'; + +exports.__esModule = true; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + // Migrator // ------- 'use strict'; @@ -8,212 +14,264 @@ var _ = require('lodash'); var mkdirp = require('mkdirp'); var Promise = require('../promise'); var helpers = require('../helpers'); +var assign = require('lodash/object/assign'); // The new migration we're performing, typically called from the `knex.migrate` // interface on the main `knex` object. Passes the `knex` instance performing // the migration. -function Migrator(knex) { - this.knex = knex; - this.config = this.setConfig(knex.client.config.migrations); -} -// Migrators to the latest configuration. -Migrator.prototype.latest = Promise.method(function (config) { - this.config = this.setConfig(config); - return this._migrationData().tap(validateMigrationList).bind(this).spread(function (all, completed) { - return this._runBatch(_.difference(all, completed), 'up'); - }); -}); +var Migrator = (function () { + function Migrator(knex) { + _classCallCheck(this, Migrator); -// Rollback the last "batch" of migrations that were run. -Migrator.prototype.rollback = Promise.method(function (config) { - this.config = this.setConfig(config); - return this._migrationData().tap(validateMigrationList).bind(this).then(this._getLastBatch).then(function (migrations) { - return this._runBatch(_.pluck(migrations, 'name'), 'down'); - }); -}); + this.knex = knex; + this.config = this.setConfig(knex.client.config.migrations); + } -// Retrieves and returns the current migration version -// we're on, as a promise. If there aren't any migrations run yet, -// return "none" as the value for the `currentVersion`. -Migrator.prototype.currentVersion = function (config) { - this.config = this.setConfig(config); - return this._listCompleted(config).then(function (completed) { - var val = _.chain(completed).map(function (value) { - return value.split('_')[0]; - }).max().value(); - return val === -Infinity ? 'none' : val; - }); -}; + // Migrators to the latest configuration. -// Creates a new migration, with a given name. -Migrator.prototype.make = function (name, config) { - this.config = this.setConfig(config); - if (!name) Promise.rejected(new Error('A name must be specified for the generated migration')); - return this._ensureFolder(config).bind(this).then(this._generateStubTemplate).then(this._writeNewMigration(name)); -}; + Migrator.prototype.latest = function latest(config) { + var _this = this; -// Lists all available migration versions, as a sorted array. -Migrator.prototype._listAll = Promise.method(function (config) { - this.config = this.setConfig(config); - return Promise.promisify(fs.readdir, fs)(this._absoluteConfigDir()).bind(this).then(function (migrations) { - return _.filter(migrations, function (value) { - var extension = path.extname(value); - return _.contains(['.co', '.coffee', '.iced', '.js', '.litcoffee', '.ls'], extension); - }).sort(); - }); -}); + this.config = this.setConfig(config); + return this._migrationData().tap(validateMigrationList).spread(function (all, completed) { + return _this._runBatch(_.difference(all, completed), 'up'); + }); + }; -// Ensures a folder for the migrations exist, dependent on the -// migration config settings. -Migrator.prototype._ensureFolder = function () { - var dir = this._absoluteConfigDir(); - return Promise.promisify(fs.stat, fs)(dir)['catch'](function () { - return Promise.promisify(mkdirp)(dir); - }); -}; + // Rollback the last "batch" of migrations that were run. -// Ensures that the proper table has been created, -// dependent on the migration config settings. -Migrator.prototype._ensureTable = Promise.method(function () { - var table = this.config.tableName; - return this.knex.schema.hasTable(table).bind(this).then(function (exists) { - if (!exists) return this._createMigrationTable(table); - }); -}); + Migrator.prototype.rollback = function rollback(config) { + var _this2 = this; -// Create the migration table, if it doesn't already exist. -Migrator.prototype._createMigrationTable = function (tableName) { - return this.knex.schema.createTable(tableName, function (t) { - t.increments(); - t.string('name'); - t.integer('batch'); - t.timestamp('migration_time'); - }); -}; + return Promise['try'](function () { + _this2.config = _this2.setConfig(config); + return _this2._migrationData().tap(validateMigrationList).then(function (val) { + return _this2._getLastBatch(val); + }).then(function (migrations) { + return _this2._runBatch(_.pluck(migrations, 'name'), 'down'); + }); + }); + }; -// Run a batch of current migrations, in sequence. -Migrator.prototype._runBatch = function (migrations, direction) { - return Promise.all(_.map(migrations, this._validateMigrationStructure, this)).bind(this).then(function (migrations) { - return Promise.bind(this).then(this._latestBatchNumber).then(function (batchNo) { + // Retrieves and returns the current migration version + // we're on, as a promise. If there aren't any migrations run yet, + // return "none" as the value for the `currentVersion`. + + Migrator.prototype.currentVersion = function currentVersion(config) { + this.config = this.setConfig(config); + return this._listCompleted(config).then(function (completed) { + var val = _.chain(completed).map(function (value) { + return value.split('_')[0]; + }).max().value(); + return val === -Infinity ? 'none' : val; + }); + }; + + // Creates a new migration, with a given name. + + Migrator.prototype.make = function make(name, config) { + var _this3 = this; + + this.config = this.setConfig(config); + if (!name) Promise.rejected(new Error('A name must be specified for the generated migration')); + return this._ensureFolder(config).then(function (val) { + return _this3._generateStubTemplate(val); + }).then(function (val) { + return _this3._writeNewMigration(name, val); + }); + }; + + // Lists all available migration versions, as a sorted array. + + Migrator.prototype._listAll = function _listAll(config) { + this.config = this.setConfig(config); + return Promise.promisify(fs.readdir, fs)(this._absoluteConfigDir()).then(function (migrations) { + return _.filter(migrations, function (value) { + var extension = path.extname(value); + return _.contains(['.co', '.coffee', '.iced', '.js', '.litcoffee', '.ls'], extension); + }).sort(); + }); + }; + + // Ensures a folder for the migrations exist, dependent on the + // migration config settings. + + Migrator.prototype._ensureFolder = function _ensureFolder() { + var dir = this._absoluteConfigDir(); + return Promise.promisify(fs.stat, fs)(dir)['catch'](function () { + return Promise.promisify(mkdirp)(dir); + }); + }; + + // Ensures that the proper table has been created, + // dependent on the migration config settings. + + Migrator.prototype._ensureTable = function _ensureTable() { + var _this4 = this; + + var table = this.config.tableName; + return this.knex.schema.hasTable(table).then(function (exists) { + if (!exists) return _this4._createMigrationTable(table); + }); + }; + + // Create the migration table, if it doesn't already exist. + + Migrator.prototype._createMigrationTable = function _createMigrationTable(tableName) { + return this.knex.schema.createTable(tableName, function (t) { + t.increments(); + t.string('name'); + t.integer('batch'); + t.timestamp('migration_time'); + }); + }; + + // Run a batch of current migrations, in sequence. + + Migrator.prototype._runBatch = function _runBatch(migrations, direction) { + var _this5 = this; + + return Promise.all(_.map(migrations, this._validateMigrationStructure, this)).then(function () { + return _this5._latestBatchNumber(); + }).then(function (batchNo) { if (direction === 'up') batchNo++; return batchNo; }).then(function (batchNo) { - return this._waterfallBatch(batchNo, migrations, direction); + return _this5._waterfallBatch(batchNo, migrations, direction); })['catch'](function (error) { helpers.warn('migrations failed with error: ' + error.message); + throw error; }); - }); -}; + }; -// Validates some migrations by requiring and checking for an `up` and `down` function. -Migrator.prototype._validateMigrationStructure = function (name) { - var migration = require(path.join(this._absoluteConfigDir(), name)); - if (typeof migration.up !== 'function' || typeof migration.down !== 'function') { - throw new Error('Invalid migration: ' + name + ' must have both an up and down function'); - } - return name; -}; + // Validates some migrations by requiring and checking for an `up` and `down` function. -// Lists all migrations that have been completed for the current db, as an array. -Migrator.prototype._listCompleted = Promise.method(function () { - var tableName = this.config.tableName; - return this._ensureTable(tableName).bind(this).then(function () { - return this.knex(tableName).orderBy('id').select('name'); - }).then(function (migrations) { - return _.pluck(migrations, 'name'); - }); -}); + Migrator.prototype._validateMigrationStructure = function _validateMigrationStructure(name) { + var migration = require(path.join(this._absoluteConfigDir(), name)); + if (typeof migration.up !== 'function' || typeof migration.down !== 'function') { + throw new Error('Invalid migration: ' + name + ' must have both an up and down function'); + } + return name; + }; -// Gets the migration list from the specified migration directory, -// as well as the list of completed migrations to check what -// should be run. -Migrator.prototype._migrationData = function () { - return Promise.all([this._listAll(), this._listCompleted()]); -}; + // Lists all migrations that have been completed for the current db, as an array. -// Generates the stub template for the current migration, returning a compiled template. -Migrator.prototype._generateStubTemplate = function () { - var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub'); - return Promise.promisify(fs.readFile, fs)(stubPath).then(function (stub) { - return _.template(stub.toString(), null, { variable: 'd' }); - }); -}; + Migrator.prototype._listCompleted = function _listCompleted() { + var _this6 = this; -// Write a new migration to disk, using the config and generated filename, -// passing any `variables` given in the config to the template. -Migrator.prototype._writeNewMigration = function (name) { - var config = this.config; - var dir = this._absoluteConfigDir(); - return function (tmpl) { + var tableName = this.config.tableName; + return this._ensureTable(tableName).then(function () { + return _this6.knex(tableName).orderBy('id').select('name'); + }).then(function (migrations) { + return _.pluck(migrations, 'name'); + }); + }; + + // Gets the migration list from the specified migration directory, + // as well as the list of completed migrations to check what + // should be run. + + Migrator.prototype._migrationData = function _migrationData() { + return Promise.all([this._listAll(), this._listCompleted()]); + }; + + // Generates the stub template for the current migration, returning a compiled template. + + Migrator.prototype._generateStubTemplate = function _generateStubTemplate() { + var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub'); + return Promise.promisify(fs.readFile, fs)(stubPath).then(function (stub) { + return _.template(stub.toString(), null, { variable: 'd' }); + }); + }; + + // Write a new migration to disk, using the config and generated filename, + // passing any `variables` given in the config to the template. + + Migrator.prototype._writeNewMigration = function _writeNewMigration(name, tmpl) { + var config = this.config; + var dir = this._absoluteConfigDir(); if (name[0] === '-') name = name.slice(1); var filename = yyyymmddhhmmss() + '_' + name + '.' + config.extension; return Promise.promisify(fs.writeFile, fs)(path.join(dir, filename), tmpl(config.variables || {}))['return'](path.join(dir, filename)); }; -}; -// Get the last batch of migrations, by name, ordered by insert id -// in reverse order. -Migrator.prototype._getLastBatch = function () { - var tableName = this.config.tableName; - return this.knex(tableName).where('batch', function (qb) { - qb.max('batch').from(tableName); - }).orderBy('id', 'desc'); -}; + // Get the last batch of migrations, by name, ordered by insert id + // in reverse order. -// Returns the latest batch number. -Migrator.prototype._latestBatchNumber = function () { - return this.knex(this.config.tableName).max('batch as max_batch').then(function (obj) { - return obj[0].max_batch || 0; - }); -}; + Migrator.prototype._getLastBatch = function _getLastBatch() { + var tableName = this.config.tableName; + return this.knex(tableName).where('batch', function (qb) { + qb.max('batch').from(tableName); + }).orderBy('id', 'desc'); + }; -// Runs a batch of `migrations` in a specified `direction`, -// saving the appropriate database information as the migrations are run. -Migrator.prototype._waterfallBatch = function (batchNo, migrations, direction) { - var knex = this.knex; - var tableName = this.config.tableName; - var directory = this._absoluteConfigDir(); - var current = Promise.bind({ failed: false, failedOn: 0 }); - var log = []; - _.each(migrations, function (migration) { - var name = migration; - migration = require(directory + '/' + name); + // Returns the latest batch number. - // We're going to run each of the migrations in the current "up" - current = current.then(function () { - return knex.transaction(function (trx) { - return warnPromise(migration[direction](trx, Promise), 'migration ' + name + ' did not return a promise'); - }); - }).then(function () { - log.push(path.join(directory, name)); - if (direction === 'up') { - return knex(tableName).insert({ - name: name, - batch: batchNo, - migration_time: new Date() - }); - } - if (direction === 'down') { - return knex(tableName).where({ name: name }).del(); - } + Migrator.prototype._latestBatchNumber = function _latestBatchNumber() { + return this.knex(this.config.tableName).max('batch as max_batch').then(function (obj) { + return obj[0].max_batch || 0; }); - }); + }; - return current.thenReturn([batchNo, log]); -}; + // Runs a batch of `migrations` in a specified `direction`, + // saving the appropriate database information as the migrations are run. -Migrator.prototype._absoluteConfigDir = function () { - return path.resolve(process.cwd(), this.config.directory); -}; + Migrator.prototype._waterfallBatch = function _waterfallBatch(batchNo, migrations, direction) { + var knex = this.knex; + var _config = this.config; + var tableName = _config.tableName; + var disableTransactions = _config.disableTransactions; -Migrator.prototype.setConfig = function (config) { - return _.extend({ - extension: 'js', - tableName: 'knex_migrations', - directory: './migrations' - }, this.config || {}, config); -}; + var directory = this._absoluteConfigDir(); + var current = Promise.bind({ failed: false, failedOn: 0 }); + var log = []; + _.each(migrations, function (migration) { + var name = migration; + migration = require(directory + '/' + name); + + // We're going to run each of the migrations in the current "up" + current = current.then(function () { + if (disableTransactions) { + return warnPromise(migration[direction](knex, Promise), 'migration ' + name + ' did not return a promise'); + } + return knex.transaction(function (trx) { + return warnPromise(migration[direction](trx, Promise), 'migration ' + name + ' did not return a promise'); + }); + }).then(function () { + log.push(path.join(directory, name)); + if (direction === 'up') { + return knex(tableName).insert({ + name: name, + batch: batchNo, + migration_time: new Date() + }); + } + if (direction === 'down') { + return knex(tableName).where({ name: name }).del(); + } + }); + }); + + return current.thenReturn([batchNo, log]); + }; + + Migrator.prototype._absoluteConfigDir = function _absoluteConfigDir() { + return path.resolve(process.cwd(), this.config.directory); + }; + + Migrator.prototype.setConfig = function setConfig(config) { + return assign({ + extension: 'js', + tableName: 'knex_migrations', + directory: './migrations' + }, this.config || {}, config); + }; + + return Migrator; +})(); + +exports['default'] = Migrator; // Validates that migrations are present in the appropriate directories. function validateMigrationList(migrations) { @@ -233,16 +291,15 @@ function warnPromise(value, message) { } // Ensure that we have 2 places for each of the date segments -var padDate = function padDate(segment) { +function padDate(segment) { segment = segment.toString(); return segment[1] ? segment : '0' + segment; -}; +} // Get a date object in the correct format, without requiring // a full out library like "moment.js". -var yyyymmddhhmmss = function yyyymmddhhmmss() { +function yyyymmddhhmmss() { var d = new Date(); return d.getFullYear().toString() + padDate(d.getMonth() + 1) + padDate(d.getDate()) + padDate(d.getHours()) + padDate(d.getMinutes()) + padDate(d.getSeconds()); -}; - -module.exports = Migrator; \ No newline at end of file +} +module.exports = exports['default']; \ No newline at end of file diff --git a/package.json b/package.json index 1b0f52a4..a442dad0 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "through": "^2.3.4" }, "scripts": { - "dev": "babel -D -w src/ --out-dir lib/", + "dev": "babel -L -D -w src/ --out-dir lib/", "build": "./scripts/build.sh", "tape": "node test/tape/index.js", "test": "npm run jshint && istanbul --config=test/.istanbul.yml cover _mocha -- --check-leaks -t 5000 -b -R spec test/index.js && npm run tape", diff --git a/src/migrate/index.js b/src/migrate/index.js index a16ac7cf..5edb4928 100644 --- a/src/migrate/index.js +++ b/src/migrate/index.js @@ -8,247 +8,242 @@ var _ = require('lodash'); var mkdirp = require('mkdirp'); var Promise = require('../promise'); var helpers = require('../helpers'); +var assign = require('lodash/object/assign'); // The new migration we're performing, typically called from the `knex.migrate` // interface on the main `knex` object. Passes the `knex` instance performing // the migration. -function Migrator(knex) { - this.knex = knex - this.config = this.setConfig(knex.client.config.migrations); -} - -// Migrators to the latest configuration. -Migrator.prototype.latest = Promise.method(function(config) { - this.config = this.setConfig(config); - return this._migrationData() - .tap(validateMigrationList) - .bind(this) - .spread(function(all, completed) { - return this._runBatch(_.difference(all, completed), 'up'); - }); -}); - -// Rollback the last "batch" of migrations that were run. -Migrator.prototype.rollback = Promise.method(function(config) { - this.config = this.setConfig(config); - return this._migrationData() - .tap(validateMigrationList) - .bind(this) - .then(this._getLastBatch) - .then(function(migrations) { - return this._runBatch(_.pluck(migrations, 'name'), 'down'); - }); -}); - -// Retrieves and returns the current migration version -// we're on, as a promise. If there aren't any migrations run yet, -// return "none" as the value for the `currentVersion`. -Migrator.prototype.currentVersion = function(config) { - this.config = this.setConfig(config); - return this._listCompleted(config).then(function(completed) { - var val = _.chain(completed).map(function(value) { - return value.split('_')[0]; - }).max().value(); - return (val === -Infinity ? 'none' : val); - }); -}; - -// Creates a new migration, with a given name. -Migrator.prototype.make = function(name, config) { - this.config = this.setConfig(config); - if (!name) Promise.rejected(new Error('A name must be specified for the generated migration')); - return this._ensureFolder(config) - .bind(this) - .then(this._generateStubTemplate) - .then(this._writeNewMigration(name)); -}; - -// Lists all available migration versions, as a sorted array. -Migrator.prototype._listAll = Promise.method(function(config) { - this.config = this.setConfig(config); - return Promise.promisify(fs.readdir, fs)(this._absoluteConfigDir()) - .bind(this) - .then(function(migrations) { - return _.filter(migrations, function(value) { - var extension = path.extname(value); - return _.contains(['.co', '.coffee', '.iced', '.js', '.litcoffee', '.ls'], extension); - }).sort(); - }); -}); - -// Ensures a folder for the migrations exist, dependent on the -// migration config settings. -Migrator.prototype._ensureFolder = function() { - var dir = this._absoluteConfigDir(); - return Promise.promisify(fs.stat, fs)(dir) - .catch(function() { - return Promise.promisify(mkdirp)(dir); - }); -}; - -// Ensures that the proper table has been created, -// dependent on the migration config settings. -Migrator.prototype._ensureTable = Promise.method(function() { - var table = this.config.tableName; - return this.knex.schema.hasTable(table) - .bind(this) - .then(function(exists) { - if (!exists) return this._createMigrationTable(table); - }); -}); - -// Create the migration table, if it doesn't already exist. -Migrator.prototype._createMigrationTable = function(tableName) { - return this.knex.schema.createTable(tableName, function(t) { - t.increments(); - t.string('name'); - t.integer('batch'); - t.timestamp('migration_time'); - }); -}; - -// Run a batch of current migrations, in sequence. -Migrator.prototype._runBatch = function(migrations, direction) { - return Promise.all(_.map(migrations, this._validateMigrationStructure, this)) - .bind(this) - .then(function(migrations) { - return Promise.bind(this) - .then(this._latestBatchNumber) - .then(function(batchNo) { - if (direction === 'up') batchNo++; - return batchNo; - }) - .then(function(batchNo) { - return this._waterfallBatch(batchNo, migrations, direction) - }) - .catch(function(error) { - helpers.warn('migrations failed with error: ' + error.message) - throw error - }); - }); -}; - -// Validates some migrations by requiring and checking for an `up` and `down` function. -Migrator.prototype._validateMigrationStructure = function(name) { - var migration = require(path.join(this._absoluteConfigDir(), name)); - if (typeof migration.up !== 'function' || typeof migration.down !== 'function') { - throw new Error('Invalid migration: ' + name + ' must have both an up and down function'); +export default class Migrator { + + constructor(knex) { + this.knex = knex + this.config = this.setConfig(knex.client.config.migrations); } - return name; -}; -// Lists all migrations that have been completed for the current db, as an array. -Migrator.prototype._listCompleted = Promise.method(function() { - var tableName = this.config.tableName; - return this._ensureTable(tableName) - .bind(this) - .then(function () { - return this.knex(tableName).orderBy('id').select('name'); + // Migrators to the latest configuration. + latest(config) { + this.config = this.setConfig(config); + return this._migrationData() + .tap(validateMigrationList) + .spread((all, completed) => { + return this._runBatch(_.difference(all, completed), 'up'); + }) + } + + // Rollback the last "batch" of migrations that were run. + rollback(config) { + return Promise.try(() => { + this.config = this.setConfig(config); + return this._migrationData() + .tap(validateMigrationList) + .then((val) => this._getLastBatch(val)) + .then((migrations) => { + return this._runBatch(_.pluck(migrations, 'name'), 'down'); + }); }) - .then(function(migrations) { - return _.pluck(migrations, 'name'); + } + + // Retrieves and returns the current migration version + // we're on, as a promise. If there aren't any migrations run yet, + // return "none" as the value for the `currentVersion`. + currentVersion(config) { + this.config = this.setConfig(config); + return this._listCompleted(config) + .then((completed) => { + var val = _.chain(completed).map(function(value) { + return value.split('_')[0]; + }).max().value(); + return (val === -Infinity ? 'none' : val); + }) + } + + // Creates a new migration, with a given name. + make(name, config) { + this.config = this.setConfig(config); + if (!name) Promise.rejected(new Error('A name must be specified for the generated migration')); + return this._ensureFolder(config) + .then((val) => this._generateStubTemplate(val)) + .then((val) => this._writeNewMigration(name, val)); + } + + // Lists all available migration versions, as a sorted array. + _listAll(config) { + this.config = this.setConfig(config); + return Promise.promisify(fs.readdir, fs)(this._absoluteConfigDir()) + .then((migrations) => { + return _.filter(migrations, function(value) { + var extension = path.extname(value); + return _.contains(['.co', '.coffee', '.iced', '.js', '.litcoffee', '.ls'], extension); + }).sort(); + }) + } + + // Ensures a folder for the migrations exist, dependent on the + // migration config settings. + _ensureFolder() { + var dir = this._absoluteConfigDir(); + return Promise.promisify(fs.stat, fs)(dir) + .catch(function() { + return Promise.promisify(mkdirp)(dir); + }); + } + + // Ensures that the proper table has been created, + // dependent on the migration config settings. + _ensureTable() { + var table = this.config.tableName; + return this.knex.schema.hasTable(table) + .then((exists) => { + if (!exists) return this._createMigrationTable(table); + }); + } + + // Create the migration table, if it doesn't already exist. + _createMigrationTable(tableName) { + return this.knex.schema.createTable(tableName, function(t) { + t.increments(); + t.string('name'); + t.integer('batch'); + t.timestamp('migration_time'); }); -}); + } -// Gets the migration list from the specified migration directory, -// as well as the list of completed migrations to check what -// should be run. -Migrator.prototype._migrationData = function() { - return Promise.all([ - this._listAll(), - this._listCompleted() - ]); -}; + // Run a batch of current migrations, in sequence. + _runBatch(migrations, direction) { + return Promise.all(_.map(migrations, this._validateMigrationStructure, this)) + .then(() => this._latestBatchNumber()) + .then((batchNo) => { + if (direction === 'up') batchNo++; + return batchNo; + }) + .then((batchNo) => { + return this._waterfallBatch(batchNo, migrations, direction) + }) + .catch((error) => { + helpers.warn('migrations failed with error: ' + error.message) + throw error + }) + } -// Generates the stub template for the current migration, returning a compiled template. -Migrator.prototype._generateStubTemplate = function() { - var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub'); - return Promise.promisify(fs.readFile, fs)(stubPath).then(function(stub) { - return _.template(stub.toString(), null, {variable: 'd'}); - }); -}; + // Validates some migrations by requiring and checking for an `up` and `down` function. + _validateMigrationStructure(name) { + var migration = require(path.join(this._absoluteConfigDir(), name)); + if (typeof migration.up !== 'function' || typeof migration.down !== 'function') { + throw new Error('Invalid migration: ' + name + ' must have both an up and down function'); + } + return name; + } -// Write a new migration to disk, using the config and generated filename, -// passing any `variables` given in the config to the template. -Migrator.prototype._writeNewMigration = function(name) { - var config = this.config; - var dir = this._absoluteConfigDir(); - return function(tmpl) { + // Lists all migrations that have been completed for the current db, as an array. + _listCompleted() { + var tableName = this.config.tableName + return this._ensureTable(tableName) + .then(() => this.knex(tableName).orderBy('id').select('name')) + .then((migrations) => _.pluck(migrations, 'name')) + } + + // Gets the migration list from the specified migration directory, + // as well as the list of completed migrations to check what + // should be run. + _migrationData() { + return Promise.all([ + this._listAll(), + this._listCompleted() + ]); + } + + // Generates the stub template for the current migration, returning a compiled template. + _generateStubTemplate() { + var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub'); + return Promise.promisify(fs.readFile, fs)(stubPath).then(function(stub) { + return _.template(stub.toString(), null, {variable: 'd'}); + }); + } + + // Write a new migration to disk, using the config and generated filename, + // passing any `variables` given in the config to the template. + _writeNewMigration(name, tmpl) { + var config = this.config; + var dir = this._absoluteConfigDir(); if (name[0] === '-') name = name.slice(1); var filename = yyyymmddhhmmss() + '_' + name + '.' + config.extension; return Promise.promisify(fs.writeFile, fs)( path.join(dir, filename), tmpl(config.variables || {}) ).return(path.join(dir, filename)); - }; -}; + } -// Get the last batch of migrations, by name, ordered by insert id -// in reverse order. -Migrator.prototype._getLastBatch = function() { - var tableName = this.config.tableName; - return this.knex(tableName) - .where('batch', function(qb) { - qb.max('batch').from(tableName) - }) - .orderBy('id', 'desc'); -}; + // Get the last batch of migrations, by name, ordered by insert id + // in reverse order. + _getLastBatch() { + var tableName = this.config.tableName; + return this.knex(tableName) + .where('batch', function(qb) { + qb.max('batch').from(tableName) + }) + .orderBy('id', 'desc'); + } -// Returns the latest batch number. -Migrator.prototype._latestBatchNumber = function() { - return this.knex(this.config.tableName) - .max('batch as max_batch').then(function(obj) { - return (obj[0].max_batch || 0); - }); -}; - -// Runs a batch of `migrations` in a specified `direction`, -// saving the appropriate database information as the migrations are run. -Migrator.prototype._waterfallBatch = function(batchNo, migrations, direction) { - var knex = this.knex; - var tableName = this.config.tableName; - var directory = this._absoluteConfigDir(); - var current = Promise.bind({failed: false, failedOn: 0}); - var log = []; - _.each(migrations, function(migration) { - var name = migration; - migration = require(directory + '/' + name); - - // We're going to run each of the migrations in the current "up" - current = current.then(function() { - return knex.transaction(function(trx) { - return warnPromise(migration[direction](trx, Promise), 'migration ' + name + ' did not return a promise'); + // Returns the latest batch number. + _latestBatchNumber() { + return this.knex(this.config.tableName) + .max('batch as max_batch').then(function(obj) { + return (obj[0].max_batch || 0); }); - }).then(function() { - log.push(path.join(directory, name)); - if (direction === 'up') { - return knex(tableName).insert({ - name: name, - batch: batchNo, - migration_time: new Date() + } + + // Runs a batch of `migrations` in a specified `direction`, + // saving the appropriate database information as the migrations are run. + _waterfallBatch(batchNo, migrations, direction) { + var knex = this.knex; + var {tableName, disableTransactions} = this.config + var directory = this._absoluteConfigDir() + var current = Promise.bind({failed: false, failedOn: 0}); + var log = []; + _.each(migrations, function(migration) { + var name = migration; + migration = require(directory + '/' + name); + + // We're going to run each of the migrations in the current "up" + current = current.then(function() { + if (disableTransactions) { + return warnPromise(migration[direction](knex, Promise), 'migration ' + name + ' did not return a promise') + } + return knex.transaction(function(trx) { + return warnPromise(migration[direction](trx, Promise), 'migration ' + name + ' did not return a promise'); }); - } - if (direction === 'down') { - return knex(tableName).where({name: name}).del(); - } - }); - }); + }) + .then(() => { + log.push(path.join(directory, name)); + if (direction === 'up') { + return knex(tableName).insert({ + name: name, + batch: batchNo, + migration_time: new Date() + }); + } + if (direction === 'down') { + return knex(tableName).where({name: name}).del(); + } + }); + }) - return current.thenReturn([batchNo, log]); -}; + return current.thenReturn([batchNo, log]); + } -Migrator.prototype._absoluteConfigDir = function() { - return path.resolve(process.cwd(), this.config.directory); -}; + _absoluteConfigDir() { + return path.resolve(process.cwd(), this.config.directory); + } -Migrator.prototype.setConfig = function(config) { - return _.extend({ - extension: 'js', - tableName: 'knex_migrations', - directory: './migrations' - }, this.config || {}, config); -}; + setConfig(config) { + return assign({ + extension: 'js', + tableName: 'knex_migrations', + directory: './migrations' + }, this.config || {}, config); + } +} // Validates that migrations are present in the appropriate directories. function validateMigrationList(migrations) { @@ -270,14 +265,14 @@ function warnPromise(value, message) { } // Ensure that we have 2 places for each of the date segments -var padDate = function(segment) { +function padDate(segment) { segment = segment.toString(); return segment[1] ? segment : '0' + segment; -}; +} // Get a date object in the correct format, without requiring // a full out library like "moment.js". -var yyyymmddhhmmss = function() { +function yyyymmddhhmmss() { var d = new Date(); return d.getFullYear().toString() + padDate(d.getMonth() + 1) + @@ -285,7 +280,4 @@ var yyyymmddhhmmss = function() { padDate(d.getHours()) + padDate(d.getMinutes()) + padDate(d.getSeconds()); -}; - - -module.exports = Migrator; +} diff --git a/test/integration/migrate/index.js b/test/integration/migrate/index.js index ec04f5bf..2b0d6bb0 100644 --- a/test/integration/migrate/index.js +++ b/test/integration/migrate/index.js @@ -31,7 +31,7 @@ module.exports = function(knex) { describe('knex.migrate.latest', function() { before(function() { - return knex.migrate.latest({directory: 'test/integration/migrate/test'}); + return knex.migrate.latest({directory: 'test/integration/migrate/test'}).catch(function() {}); }); it('should run all migration files in the specified directory', function() {