UNPKG

mig

Version:

MongoDB migration framework

174 lines (142 loc) 4.94 kB
var mongodb = require('mongodb') , _ = require('underscore') , async = require('async') , path = require('path') , fs = require('fs'); var Migration = module.exports.Migration = function Migration (options) { this.options = _.extend({ path: 'migrations', run: false, console: { log: function (s) { console.log(':: ' + s); }, error: function (s) { console.error('Error: ' + s); } } }, options); this.err = this.options.console.error; this.log = this.options.console.log; }; Migration.prototype.migrate = function (callback) { var self = this; var c = {err: this.err, log: this.log}; var migrationsPath = path.resolve(self.options.path) , configPath = path.join(migrationsPath, 'config'); // Load MongoDB configuration, can either be a JSON or Javascript file (with // access to environment, as set by foreman for example). var config try { config = require(configPath); } catch (err) { c.err('Could not load configuration'); throw err } // Connect to MongoDB using the provided configuration. mongodb.MongoClient.connect(config.url, function (err, db) { if (err) { c.err('Could not connect to MongoDB'); throw err; } // Grab the `migrations` collection, used to store schema version. db.collection('migrations', {strict: true}, function (err, migrations) { if (err) { c.err('Could not obtain `migrations` collection'); throw err; } c.log('Connected to MongoDB'); // Figure out the current version. migrations.findOne({_id: 'version'}, function (err, doc) { if (err) { c.err('Could not obtain version'); throw err; } var version = doc ? doc.val : 0; c.log('Current version: ' + version); var scripts; if (typeof self.options.script === 'undefined') { // Prepare a list of migration scripts to execute. scripts = _.chain(fs.readdirSync(migrationsPath)) // Extract version from filename. .map(function (item) { var m = item.match(/^([\d]+)-/); return { filename: item, version: m ? parseInt(m[1]) : null }; }) // Filter out non-scripts (ie. files without version numbers). .filter(function (item) { return item.version !== null; }) // Filter out elements below current version. .filter(function (item) { return item.version > version; }) // Sort by version. .sortBy(function (item) { return item.version; }) // Get the values. .value(); } else { if (_.isArray(self.options.script)) scripts = _.map(self.options.script, function (script) { return { filename: script, version: null } }); else scripts = [ { filename: self.options.script, version: null } ]; } if (!self.options.run) { c.log('Plan:' + _.map(scripts, function (item) { return '\n ' + item.filename; })); callback(0); } // Run migration scripts sequentially async.mapSeries(scripts, function (script, next) { c.log('Migrating to version ' + script.version + ' (' + script.filename + ')'); // Prepare a callback function passed to migration scripts; it's // responsible for bumping setting the database schema version number. var cb = function (err) { if (err) throw err; if (script.version === null) return next(); // Upsert the current version migrations.update({ _id: 'version' }, { $set: { val: script.version } }, { upsert: true }, function (err, doc) { if (err) throw err; if (!doc) throw new Error('Setting the current version failed'); version = script.version; next(); }); }; // Require the migration script and actually run it, passing along the // database connection, configuration and callback. require(path.join(migrationsPath, script.filename)) .migrate(db, config, cb, mongodb); }, function (err) { if (err) { c.log('Migration failed'); callback(1); } c.log('Migration complete, now at version ' + version); // Force-quit in case a fancy program spawned the current script (eg. // foreman). callback(0); }); }); }); }); };