migrat
Version:
A pluggable migration runner.
106 lines (94 loc) • 2.96 kB
JavaScript
var fs = require('fs');
var path = require('path');
var async = require('async');
var MigratMigration = require('./MigratMigration.js');
/**
* Reads all the migrations in a folder and
* returns them sorted oldest-to-newest.
*
* @param {string} dir
* @param {MigratPluginSystem} plugins
* @param {function} callback
* @return {void}
*/
module.exports.dir = function(dir, plugins, callback) {
fs.readdir(dir, function(err, files) {
if (err) return callback(err);
async.map(files, function(file, callback) {
module.exports.file(dir + '/' + file, plugins, callback);
}, function(err, migrations) {
if (err) return callback(err);
migrations = migrations.filter(Boolean);
// sanity check: make sure no migrations have the same timestamp
var counts = {};
var i, n, ts;
for (i = 0, n = migrations.length; i < n; i++) {
ts = migrations[i].timestamp;
counts[ts] = (counts[ts] || 0) + 1;
if (counts[ts] > 1) {
callback(new Error('Two or more migrations find to have the same timestamp: ' + ts));
return false;
}
}
callback(null, migrations);
});
});
};
/**
* Reads a single migration file and returns
* its MigratMigration object.
*
* @param {string} file
* @param {MigratPluginSystem} plugins
* @param {function} callback
* @return {void}
*/
module.exports.file = function(file, plugins, callback) {
plugins.loadMigration(file, function(err, methods) {
if (err) return callback(err);
var filename = path.basename(file);
// default .js loader (fallback)
if (!methods) {
if (!/^\d+\-.+\.js$/.test(filename)) {
return callback();
}
try { methods = require(file); }
catch (e) { return callback(e); }
}
// validate methods
if (!methods || typeof methods !== 'object') {
return callback(new Error('Migration file does not export an object: ' + file));
} else if (typeof methods.up !== 'function') {
return callback(new Error('Migration file missing "up" method: ' + file));
} else if (typeof methods.down !== 'function') {
return callback(new Error('Migration file missing "down" method: ' + file));
}
// determine details from filename / path
var details;
try { details = module.exports.fileDetails(file); }
catch (e) { return callback(e); }
details.methods = methods;
callback(null, new MigratMigration(details));
});
};
/**
* Returns basic info about a file, given its path.
* Does not attempt to open it to populate methods.
*
* @throws
* @param {string} file
* @return {object}
*/
module.exports.fileDetails = function(file, callback) {
var filename = path.basename(file);
if (!/^\d+\-.+\.[a-z]+$/i.test(filename)) {
throw new Error('File not a valid migration file (' + file + ')');
}
return {
type: /\.all\.[a-z]+$/.test(filename) ? 'all' : 'once',
timestamp: parseInt(filename.match(/^\d+/)[0], 10),
name: filename.match(/^\d+\-(.+?)(\.all)?\.[a-z]+$/)[1],
filename: filename,
file: file
};
};