From ec449af4259b393838cd98a9df1a0ceb78e47466 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 14 Apr 2017 12:02:03 -0500 Subject: [PATCH 01/29] Compliance with the standard formatting --- Readme.md | 2 + bin/migrate | 184 ++++++++-------- examples/migrate.js | 97 ++++---- index.js | 2 +- lib/migrate.js | 39 ++-- lib/migration.js | 12 +- lib/set.js | 143 ++++++------ package.json | 9 +- test/basic.js | 256 +++++++++++----------- test/fixtures/basic/1-add-guy-ferrets.js | 18 +- test/fixtures/basic/2-add-girl-ferrets.js | 14 +- test/fixtures/basic/3-add-emails.js | 18 +- test/fixtures/db.js | 10 +- test/fixtures/issue-33/1-migration.js | 14 +- test/fixtures/issue-33/2-migration.js | 14 +- test/fixtures/issue-33/3-migration.js | 14 +- test/issue-33.js | 65 +++--- 17 files changed, 446 insertions(+), 465 deletions(-) diff --git a/Readme.md b/Readme.md index 8e48beb..00f724b 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,7 @@ # migrate +[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) + Abstract migration framework for node *NOTE:* A large refactor is underway for the next major version of this package. Check out the `1.x` pull request tracking this work, or to explicitly opt in, install the `next` tag (`npm install migrate@next`). diff --git a/bin/migrate b/bin/migrate index f77fd15..b8a958e 100755 --- a/bin/migrate +++ b/bin/migrate @@ -5,111 +5,105 @@ */ var migrate = require('../') - , join = require('path').join - , fs = require('fs') - , dateFormat = require('dateformat'); +var chalk = require('chalk') +var join = require('path').join +var fs = require('fs') +var dateFormat = require('dateformat') /** * Arguments. */ -var args = process.argv.slice(2); +var args = process.argv.slice(2) /** * Option defaults. */ -var options = { args: [] }; - -/** - * Current working directory. - */ - -var cwd; +var options = { args: [] } /** * Usage information. */ var usage = [ - '' - , ' Usage: migrate [options] [command]' - , '' - , ' Options:' - , '' - , ' -c, --chdir change the working directory' - , ' --state-file set path to state file (migrations/.migrate)' - , ' --template-file set path to template file to use for new migrations' - , ' --date-format set a date format to use for new migration filenames' - , ' Commands:' - , '' - , ' down [name] migrate down till given migration' - , ' up [name] migrate up till given migration (the default command)' - , ' create [title] create a new migration file with optional [title]' - , '' -].join('\n'); + '', + ' Usage: migrate [options] [command]', + '', + ' Options:', + '', + ' -c, --chdir change the working directory', + ' --state-file set path to state file (migrations/.migrate)', + ' --template-file set path to template file to use for new migrations', + ' --date-format set a date format to use for new migration filenames', + ' Commands:', + '', + ' down [name] migrate down till given migration', + ' up [name] migrate up till given migration (the default command)', + ' create [title] create a new migration file with optional [title]', + '' +].join('\n') /** * Migration template. */ var template = [ - '\'use strict\'' - , '' - , 'exports.up = function(next) {' - , ' next();' - , '};' - , '' - , 'exports.down = function(next) {' - , ' next();' - , '};' - , '' -].join('\n'); + '\'use strict\'', + '', + 'exports.up = function(next) {', + ' next();', + '};', + '', + 'exports.down = function(next) {', + ' next();', + '};', + '' +].join('\n') // require an argument -function required() { - if (args.length) return args.shift(); - abort(arg + ' requires an argument'); +function required () { + if (args.length) return args.shift() + abort(arg + ' requires an argument') } // abort with a message -function abort(msg) { - console.error(' %s', msg); - process.exit(1); +function abort (msg) { + console.error(' %s', msg) + process.exit(1) } // parse arguments -var arg; +var arg while (args.length) { - arg = args.shift(); + arg = args.shift() switch (arg) { case '-h': case '--help': case 'help': - console.log(usage); - process.exit(); - break; + console.log(usage) + process.exit() case '-c': case '--chdir': - process.chdir(cwd = required()); - break; + process.chdir(required()) + break case '--state-file': - options.stateFile = required(); - break; + options.stateFile = required() + break case '--template-file': - template = fs.readFileSync(required()); - break; + template = fs.readFileSync(required()) + break case '--date-format': - options.dateFormat = required(); - break; + options.dateFormat = required() + break default: if (options.command) { - options.args.push(arg); + options.args.push(arg) } else { - options.command = arg; + options.command = arg } } } @@ -118,22 +112,22 @@ while (args.length) { * Log a keyed message. */ -function log(key, msg) { - console.log(' \033[90m%s :\033[0m \033[36m%s\033[0m', key, msg); +function log (key, msg) { + console.log(' ' + chalk.grey(key) + ' : ' + chalk.teal(msg)) } /** * Slugify the given `str`. */ -function slugify(str) { - return str.replace(/\s+/g, '-'); +function slugify (str) { + return str.replace(/\s+/g, '-') } // create ./migrations try { - fs.mkdirSync('migrations', 0774); + fs.mkdirSync('migrations', 774) } catch (err) { // ignore } @@ -146,32 +140,32 @@ var commands = { * up [name] */ - up: function(migrationName){ - performMigration('up', migrationName); + up: function (migrationName) { + performMigration('up', migrationName) }, /** * down [name] */ - down: function(migrationName){ - performMigration('down', migrationName); + down: function (migrationName) { + performMigration('down', migrationName) }, /** * create [title] */ - create: function(){ + create: function () { var curr = Date.now() - , title = slugify([].slice.call(arguments).join(' ')); + var title = slugify([].slice.call(arguments).join(' ')) if (options.dateFormat) { - curr = dateFormat(curr, options.dateFormat); + curr = dateFormat(curr, options.dateFormat) } - title = title ? curr + '-' + title : curr; - create(title); + title = title ? curr + '-' + title : curr + create(title) } -}; +} /** * Create a migration with the given `name`. @@ -179,10 +173,10 @@ var commands = { * @param {String} name */ -function create(name) { - var path = join('migrations', name + '.js'); - log('create', join(process.cwd(), path)); - fs.writeFileSync(path, template); +function create (name) { + var path = join('migrations', name + '.js') + log('create', join(process.cwd(), path)) + fs.writeFileSync(path, template) } /** @@ -191,32 +185,28 @@ function create(name) { * @param {Number} direction */ -function performMigration(direction, migrationName) { - var state = options.stateFile || join('migrations', '.migrate'); - var set = migrate.load(state, 'migrations'); - - set.on('migration', function(migration, direction){ - log(direction, migration.title); - }); +function performMigration (direction, migrationName) { + var state = options.stateFile || join('migrations', '.migrate') + var set = migrate.load(state, 'migrations') - var migrationPath = migrationName - ? join('migrations', migrationName) - : migrationName; + set.on('migration', function (migration, direction) { + log(direction, migration.title) + }) set[direction](migrationName, function (err) { if (err) { - log('error', err); - process.exit(1); + log('error', err) + process.exit(1) } - log('migration', 'complete'); - process.exit(0); - }); + log('migration', 'complete') + process.exit(0) + }) } // invoke command -var command = options.command || 'up'; -if (!(command in commands)) abort('unknown command "' + command + '"'); -command = commands[command]; -command.apply(this, options.args); +var command = options.command || 'up' +if (!(command in commands)) abort('unknown command "' + command + '"') +command = commands[command] +command.apply(this, options.args) diff --git a/examples/migrate.js b/examples/migrate.js index 7f48570..c6a56eb 100644 --- a/examples/migrate.js +++ b/examples/migrate.js @@ -4,52 +4,53 @@ // $ npm install redis // $ redis-server +var path = require('path') var migrate = require('../') - , redis = require('redis') - , db = redis.createClient(); - -migrate(__dirname + '/.migrate'); - -migrate('add pets', function(next){ - db.rpush('pets', 'tobi'); - db.rpush('pets', 'loki', next); -}, function(next){ - db.rpop('pets'); - db.rpop('pets', next); -}); - -migrate('add jane', function(next){ - db.rpush('pets', 'jane', next); -}, function(next){ - db.rpop('pets', next); -}); - -migrate('add owners', function(next){ - db.rpush('owners', 'taylor'); - db.rpush('owners', 'tj', next); -}, function(next){ - db.rpop('owners'); - db.rpop('owners', next); -}); - -migrate('coolest pet', function(next){ - db.set('pets:coolest', 'tobi', next); -}, function(next){ - db.del('pets:coolest', next); -}); - -var set = migrate(); - -console.log(); -set.on('save', function(){ - console.log(); -}); - -set.on('migration', function(migration, direction){ - console.log(' \033[90m%s\033[0m \033[36m%s\033[0m', direction, migration.title); -}); - -set.up(function(err){ - if (err) throw err; - process.exit(); -}); +var redis = require('redis') +var db = redis.createClient() + +migrate(path.join(__dirname, '.migrate')) + +migrate('add pets', function (next) { + db.rpush('pets', 'tobi') + db.rpush('pets', 'loki', next) +}, function (next) { + db.rpop('pets') + db.rpop('pets', next) +}) + +migrate('add jane', function (next) { + db.rpush('pets', 'jane', next) +}, function (next) { + db.rpop('pets', next) +}) + +migrate('add owners', function (next) { + db.rpush('owners', 'taylor') + db.rpush('owners', 'tj', next) +}, function (next) { + db.rpop('owners') + db.rpop('owners', next) +}) + +migrate('coolest pet', function (next) { + db.set('pets:coolest', 'tobi', next) +}, function (next) { + db.del('pets:coolest', next) +}) + +var set = migrate() + +console.log() +set.on('save', function () { + console.log() +}) + +set.on('migration', function (migration, direction) { + console.log(direction, migration.title) +}) + +set.up(function (err) { + if (err) throw err + process.exit() +}) diff --git a/index.js b/index.js index 7aebfd7..e80af03 100644 --- a/index.js +++ b/index.js @@ -1,2 +1,2 @@ -module.exports = require('./lib/migrate'); \ No newline at end of file +module.exports = require('./lib/migrate') diff --git a/lib/migrate.js b/lib/migrate.js index 04756e3..b01ef31 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -10,42 +10,41 @@ */ var Set = require('./set') - , path = require('path') - , fs = require('fs'); +var path = require('path') +var fs = require('fs') /** * Expose the migrate function. */ -exports = module.exports = migrate; +exports = module.exports = migrate -function migrate(title, up, down) { +function migrate (title, up, down) { // migration - if ('string' == typeof title && up && down) { - migrate.set.addMigration(title, up, down); + if (typeof title === 'string' && up && down) { + migrate.set.addMigration(title, up, down) // specify migration file - } else if ('string' == typeof title) { - migrate.set = new Set(title); + } else if (typeof title === 'string') { + migrate.set = new Set(title) // no migration path } else if (!migrate.set) { - throw new Error('must invoke migrate(path) before running migrations'); + throw new Error('must invoke migrate(path) before running migrations') // run migrations } else { - return migrate.set; + return migrate.set } } exports.load = function (stateFile, migrationsDirectory) { + var set = new Set(stateFile) + var dir = path.resolve(migrationsDirectory) - var set = new Set(stateFile); - var dir = path.resolve(migrationsDirectory); - - fs.readdirSync(dir).filter(function(file){ - return file.match(/^\d+.*\.js$/); + fs.readdirSync(dir).filter(function (file) { + return file.match(/^\d+.*\.js$/) }).sort().forEach(function (file) { - var mod = require(path.join(dir, file)); - set.addMigration(file, mod.up, mod.down); - }); + var mod = require(path.join(dir, file)) + set.addMigration(file, mod.up, mod.down) + }) - return set; -}; + return set +} diff --git a/lib/migration.js b/lib/migration.js index 361e828..9100f0f 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -9,10 +9,10 @@ * Expose `Migration`. */ -module.exports = Migration; +module.exports = Migration -function Migration(title, up, down) { - this.title = title; - this.up = up; - this.down = down; -} \ No newline at end of file +function Migration (title, up, down) { + this.title = title + this.up = up + this.down = down +} diff --git a/lib/set.js b/lib/set.js index 1bf3947..7c49cea 100644 --- a/lib/set.js +++ b/lib/set.js @@ -9,15 +9,16 @@ * Module dependencies. */ -var EventEmitter = require('events').EventEmitter - , Migration = require('./migration') - , fs = require('fs'); +var EventEmitter = require('events') +var Migration = require('./migration') +var fs = require('fs') +var inherits = require('inherits') /** * Expose `Set`. */ -module.exports = Set; +module.exports = MigrationSet /** * Initialize a new migration `Set` with the given `path` @@ -27,17 +28,17 @@ module.exports = Set; * @api private */ -function Set(path) { - this.migrations = []; - this.path = path; - this.pos = 0; +function MigrationSet (path) { + this.migrations = [] + this.path = path + this.pos = 0 }; /** * Inherit from `EventEmitter.prototype`. */ -Set.prototype.__proto__ = EventEmitter.prototype; +inherits(MigrationSet, EventEmitter) /** * Add a migration. @@ -48,9 +49,9 @@ Set.prototype.__proto__ = EventEmitter.prototype; * @api public */ -Set.prototype.addMigration = function(title, up, down){ - this.migrations.push(new Migration(title, up, down)); -}; +MigrationSet.prototype.addMigration = function (title, up, down) { + this.migrations.push(new Migration(title, up, down)) +} /** * Save the migration data. @@ -58,16 +59,16 @@ Set.prototype.addMigration = function(title, up, down){ * @api public */ -Set.prototype.save = function(fn){ +MigrationSet.prototype.save = function (fn) { var self = this - , json = JSON.stringify(this); - fs.writeFile(this.path, json, function(err){ - if (err) return fn(err); + var json = JSON.stringify(this) + fs.writeFile(this.path, json, function (err) { + if (err) return fn(err) - self.emit('save'); - fn(null); - }); -}; + self.emit('save') + fn(null) + }) +} /** * Load the migration data and call `fn(err, obj)`. @@ -77,17 +78,17 @@ Set.prototype.save = function(fn){ * @api public */ -Set.prototype.load = function(fn){ - this.emit('load'); - fs.readFile(this.path, 'utf8', function(err, json){ - if (err) return fn(err); +MigrationSet.prototype.load = function (fn) { + this.emit('load') + fs.readFile(this.path, 'utf8', function (err, json) { + if (err) return fn(err) try { - fn(null, JSON.parse(json)); + fn(null, JSON.parse(json)) } catch (err) { - fn(err); + fn(err) } - }); -}; + }) +} /** * Run down migrations and call `fn(err)`. @@ -96,9 +97,9 @@ Set.prototype.load = function(fn){ * @api public */ -Set.prototype.down = function(migrationName, fn){ - this.migrate('down', migrationName, fn); -}; +MigrationSet.prototype.down = function (migrationName, fn) { + this.migrate('down', migrationName, fn) +} /** * Run up migrations and call `fn(err)`. @@ -107,9 +108,9 @@ Set.prototype.down = function(migrationName, fn){ * @api public */ -Set.prototype.up = function(migrationName, fn){ - this.migrate('up', migrationName, fn); -}; +MigrationSet.prototype.up = function (migrationName, fn) { + this.migrate('up', migrationName, fn) +} /** * Migrate in the given `direction`, calling `fn(err)`. @@ -119,21 +120,21 @@ Set.prototype.up = function(migrationName, fn){ * @api public */ -Set.prototype.migrate = function(direction, migrationName, fn){ +MigrationSet.prototype.migrate = function (direction, migrationName, fn) { if (typeof migrationName === 'function') { - fn = migrationName; - migrationName = null; + fn = migrationName + migrationName = null } - var self = this; - this.load(function(err, obj){ + var self = this + this.load(function (err, obj) { if (err) { - if ('ENOENT' != err.code) return fn(err); + if (err.code !== 'ENOENT') return fn(err) } else { - self.pos = obj.pos; + self.pos = obj.pos } - self._migrate(direction, migrationName, fn); - }); -}; + self._migrate(direction, migrationName, fn) + }) +} /** * Get index of given migration in list of migrations @@ -141,12 +142,12 @@ Set.prototype.migrate = function(direction, migrationName, fn){ * @api private */ - function positionOfMigration(migrations, filename) { - for(var i=0; i < migrations.length; ++i) { - if (migrations[i].title == filename) return i; - } - return -1; - } +function positionOfMigration (migrations, filename) { + for (var i = 0; i < migrations.length; ++i) { + if (migrations[i].title === filename) return i + } + return -1 +} /** * Perform migration. @@ -154,41 +155,41 @@ Set.prototype.migrate = function(direction, migrationName, fn){ * @api private */ -Set.prototype._migrate = function(direction, migrationName, fn){ +MigrationSet.prototype._migrate = function (direction, migrationName, fn) { var self = this - , migrations - , migrationPos; + var migrations + var migrationPos if (!migrationName) { - migrationPos = direction == 'up' ? this.migrations.length : 0; - } else if ((migrationPos = positionOfMigration(this.migrations, migrationName)) == -1) { - return fn(new Error("Could not find migration: " + migrationName)); + migrationPos = direction === 'up' ? this.migrations.length : 0 + } else if ((migrationPos = positionOfMigration(this.migrations, migrationName)) === -1) { + return fn(new Error('Could not find migration: ' + migrationName)) } switch (direction) { case 'up': - migrations = this.migrations.slice(this.pos, migrationPos+1); - break; + migrations = this.migrations.slice(this.pos, migrationPos + 1) + break case 'down': - migrations = this.migrations.slice(migrationPos, this.pos).reverse(); - break; + migrations = this.migrations.slice(migrationPos, this.pos).reverse() + break } - function next(migration) { - if (!migration) return fn(null); + function next (migration) { + if (!migration) return fn(null) - self.emit('migration', migration, direction); - migration[direction](function(err){ - if (err) return fn(err); + self.emit('migration', migration, direction) + migration[direction](function (err) { + if (err) return fn(err) - self.pos += (direction === 'up' ? 1 : -1); + self.pos += (direction === 'up' ? 1 : -1) self.save(function (err) { - if (err) return fn(err); + if (err) return fn(err) next(migrations.shift()) - }); - }); + }) + }) } - next(migrations.shift()); -}; + next(migrations.shift()) +} diff --git a/package.json b/package.json index d6f8489..f8dd952 100644 --- a/package.json +++ b/package.json @@ -12,17 +12,20 @@ "migrate": "./bin/migrate" }, "devDependencies": { - "mocha": "^2.2.1" + "mocha": "^2.2.1", + "standard": "^10.0.1" }, "main": "index", "engines": { "node": ">= 0.4.x" }, "scripts": { - "test": "mocha" + "test": "standard --fix && standard --fix ./bin/migrate && mocha" }, "license": "MIT", "dependencies": { - "dateformat": "^1.0.12" + "chalk": "^1.1.3", + "dateformat": "^1.0.12", + "inherits": "^2.0.3" } } diff --git a/test/basic.js b/test/basic.js index d2a1e10..f9670bd 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,195 +1,183 @@ +/* global describe, it, beforeEach, afterEach */ -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); +var fs = require('fs') +var path = require('path') +var assert = require('assert') -var migrate = require('../'); -var db = require('./fixtures/db'); +var migrate = require('../') +var db = require('./fixtures/db') -var BASE = path.join(__dirname, 'fixtures', 'basic'); -var STATE = path.join(BASE, '.migrate'); +var BASE = path.join(__dirname, 'fixtures', 'basic') +var STATE = path.join(BASE, '.migrate') describe('migrate', function () { + var set - var set; - - function assertNoPets() { - assert.equal(db.pets.length, 0); - assert.equal(set.pos, 0); + function assertNoPets () { + assert.equal(db.pets.length, 0) + assert.equal(set.pos, 0) } - function assertPets() { - assert.equal(db.pets.length, 3); - assert.equal(db.pets[0].name, 'tobi'); - assert.equal(db.pets[0].email, 'tobi@learnboost.com'); - assert.equal(set.pos, 3); + function assertPets () { + assert.equal(db.pets.length, 3) + assert.equal(db.pets[0].name, 'tobi') + assert.equal(db.pets[0].email, 'tobi@learnboost.com') + assert.equal(set.pos, 3) } - function assertPetsWithDogs() { - assert.equal(db.pets.length, 5); - assert.equal(db.pets[0].name, 'tobi'); - assert.equal(db.pets[0].email, 'tobi@learnboost.com'); - assert.equal(db.pets[4].name, 'suki'); + function assertPetsWithDogs () { + assert.equal(db.pets.length, 5) + assert.equal(db.pets[0].name, 'tobi') + assert.equal(db.pets[0].email, 'tobi@learnboost.com') + assert.equal(db.pets[4].name, 'suki') }; - function assertFirstMigration() { - assert.equal(db.pets.length, 2); - assert.equal(db.pets[0].name, 'tobi'); - assert.equal(db.pets[1].name, 'loki'); - assert.equal(set.pos, 1); + function assertFirstMigration () { + assert.equal(db.pets.length, 2) + assert.equal(db.pets[0].name, 'tobi') + assert.equal(db.pets[1].name, 'loki') + assert.equal(set.pos, 1) } - function assertSecondMigration() { - assert.equal(db.pets.length, 3); - assert.equal(db.pets[0].name, 'tobi'); - assert.equal(db.pets[1].name, 'loki'); - assert.equal(db.pets[2].name, 'jane'); - assert.equal(set.pos, 2); + function assertSecondMigration () { + assert.equal(db.pets.length, 3) + assert.equal(db.pets[0].name, 'tobi') + assert.equal(db.pets[1].name, 'loki') + assert.equal(db.pets[2].name, 'jane') + assert.equal(set.pos, 2) } beforeEach(function () { - set = migrate.load(STATE, BASE); - }); + set = migrate.load(STATE, BASE) + }) it('should handle basic migration', function (done) { - set.up(function (err) { - assert.ifError(err); - assertPets(); + assert.ifError(err) + assertPets() set.up(function (err) { - assert.ifError(err); - assertPets(); + assert.ifError(err) + assertPets() set.down(function (err) { - assert.ifError(err); - assertNoPets(); + assert.ifError(err) + assertNoPets() set.down(function (err) { - assert.ifError(err); - assertNoPets(); + assert.ifError(err) + assertNoPets() set.up(function (err) { - assert.ifError(err); - assertPets(); - done(); - }); - }); - }); - }); - }); - - }); + assert.ifError(err) + assertPets() + done() + }) + }) + }) + }) + }) + }) it('should add a new migration', function (done) { - set.addMigration('add dogs', function (next) { - db.pets.push({ name: 'simon' }); - db.pets.push({ name: 'suki' }); - next(); + db.pets.push({ name: 'simon' }) + db.pets.push({ name: 'suki' }) + next() }, function (next) { - db.pets.pop(); - db.pets.pop(); - next(); - }); + db.pets.pop() + db.pets.pop() + next() + }) set.up(function (err) { - assert.ifError(err); - assertPetsWithDogs(); + assert.ifError(err) + assertPetsWithDogs() set.up(function (err) { - assert.ifError(err); - assertPetsWithDogs(); + assert.ifError(err) + assertPetsWithDogs() set.down(function (err) { - assert.ifError(err); - assertNoPets(); - done(); - }); - }); - }); - - }); + assert.ifError(err) + assertNoPets() + done() + }) + }) + }) + }) it('should emit events', function (done) { - set.addMigration('4-adjust-emails.js', function (next) { db.pets.forEach(function (pet) { - if (pet.email) - pet.email = pet.email.replace('learnboost.com', 'lb.com'); - }); - next(); + if (pet.email) { pet.email = pet.email.replace('learnboost.com', 'lb.com') } + }) + next() }, function (next) { db.pets.forEach(function (pet) { - if (pet.email) - pet.email = pet.email.replace('lb.com', 'learnboost.com'); - }); - next(); - }); - - var saved = 0; - var migrations = []; + if (pet.email) { pet.email = pet.email.replace('lb.com', 'learnboost.com') } + }) + next() + }) + + var saved = 0 + var migrations = [] var expectedMigrations = [ '1-add-guy-ferrets.js', '2-add-girl-ferrets.js', '3-add-emails.js', '4-adjust-emails.js' - ]; + ] set.on('save', function () { - saved++; - }); + saved++ + }) set.on('migration', function (migration, direction) { - migrations.push(migration.title); - assert.equal(typeof direction, 'string'); - }); + migrations.push(migration.title) + assert.equal(typeof direction, 'string') + }) set.up(function (err) { - assert.ifError(err); - assert.equal(saved, 4); - assert.equal(db.pets[0].email, 'tobi@lb.com'); - assert.deepEqual(migrations, expectedMigrations); + assert.ifError(err) + assert.equal(saved, 4) + assert.equal(db.pets[0].email, 'tobi@lb.com') + assert.deepEqual(migrations, expectedMigrations) - migrations = []; - expectedMigrations = expectedMigrations.reverse(); + migrations = [] + expectedMigrations = expectedMigrations.reverse() set.down(function (err) { - assert.ifError(err); - assert.equal(saved, 8); - assert.deepEqual(migrations, expectedMigrations); - assertNoPets(); - done(); - }); - - }); - - }); + assert.ifError(err) + assert.equal(saved, 8) + assert.deepEqual(migrations, expectedMigrations) + assertNoPets() + done() + }) + }) + }) it('should migrate to named migration', function (done) { - - assertNoPets(); + assertNoPets() set.up('1-add-guy-ferrets.js', function (err) { - assert.ifError(err); - assertFirstMigration(); + assert.ifError(err) + assertFirstMigration() set.up('2-add-girl-ferrets.js', function (err) { - assert.ifError(err); - assertSecondMigration(); + assert.ifError(err) + assertSecondMigration() set.down('2-add-girl-ferrets.js', function (err) { - assert.ifError(err); - assertFirstMigration(); + assert.ifError(err) + assertFirstMigration() set.up('2-add-girl-ferrets.js', function (err) { - assert.ifError(err); - assertSecondMigration(); + assert.ifError(err) + assertSecondMigration() set.down('2-add-girl-ferrets.js', function (err) { - assert.ifError(err); - assert.equal(set.pos, 1); - done(); - }); - }); - }); - }); - }); - - }); + assert.ifError(err) + assert.equal(set.pos, 1) + done() + }) + }) + }) + }) + }) + }) afterEach(function (done) { - db.nuke(); - fs.unlink(STATE, done); - }); - -}); + db.nuke() + fs.unlink(STATE, done) + }) +}) diff --git a/test/fixtures/basic/1-add-guy-ferrets.js b/test/fixtures/basic/1-add-guy-ferrets.js index 0f4c750..fd1b9c7 100644 --- a/test/fixtures/basic/1-add-guy-ferrets.js +++ b/test/fixtures/basic/1-add-guy-ferrets.js @@ -1,14 +1,14 @@ -var db = require('../db'); +var db = require('../db') exports.up = function (next) { - db.pets.push({ name: 'tobi' }); - db.pets.push({ name: 'loki' }); - next(); -}; + db.pets.push({ name: 'tobi' }) + db.pets.push({ name: 'loki' }) + next() +} exports.down = function (next) { - db.pets.pop(); - db.pets.pop(); - next(); -}; + db.pets.pop() + db.pets.pop() + next() +} diff --git a/test/fixtures/basic/2-add-girl-ferrets.js b/test/fixtures/basic/2-add-girl-ferrets.js index 61610fa..a661149 100644 --- a/test/fixtures/basic/2-add-girl-ferrets.js +++ b/test/fixtures/basic/2-add-girl-ferrets.js @@ -1,12 +1,12 @@ -var db = require('../db'); +var db = require('../db') exports.up = function (next) { - db.pets.push({ name: 'jane' }); - next(); -}; + db.pets.push({ name: 'jane' }) + next() +} exports.down = function (next) { - db.pets.pop(); - next(); -}; + db.pets.pop() + next() +} diff --git a/test/fixtures/basic/3-add-emails.js b/test/fixtures/basic/3-add-emails.js index 4553a87..e4ad65e 100644 --- a/test/fixtures/basic/3-add-emails.js +++ b/test/fixtures/basic/3-add-emails.js @@ -1,16 +1,16 @@ -var db = require('../db'); +var db = require('../db') exports.up = function (next) { db.pets.forEach(function (pet) { - pet.email = pet.name + '@learnboost.com'; - }); - next(); -}; + pet.email = pet.name + '@learnboost.com' + }) + next() +} exports.down = function (next) { db.pets.forEach(function (pet) { - delete pet.email; - }); - next(); -}; + delete pet.email + }) + next() +} diff --git a/test/fixtures/db.js b/test/fixtures/db.js index e19497c..30b9482 100644 --- a/test/fixtures/db.js +++ b/test/fixtures/db.js @@ -1,9 +1,9 @@ -function nuke() { - exports.pets = []; - exports.issue33 = []; +function nuke () { + exports.pets = [] + exports.issue33 = [] } -exports.nuke = nuke; +exports.nuke = nuke -nuke(); +nuke() diff --git a/test/fixtures/issue-33/1-migration.js b/test/fixtures/issue-33/1-migration.js index beb3741..81cc27c 100644 --- a/test/fixtures/issue-33/1-migration.js +++ b/test/fixtures/issue-33/1-migration.js @@ -1,12 +1,12 @@ -var db = require('../db'); +var db = require('../db') exports.up = function (next) { - db.issue33.push('1-up'); - next(); -}; + db.issue33.push('1-up') + next() +} exports.down = function (next) { - db.issue33.push('1-down'); - next(); -}; + db.issue33.push('1-down') + next() +} diff --git a/test/fixtures/issue-33/2-migration.js b/test/fixtures/issue-33/2-migration.js index a5f529e..27ea3b7 100644 --- a/test/fixtures/issue-33/2-migration.js +++ b/test/fixtures/issue-33/2-migration.js @@ -1,12 +1,12 @@ -var db = require('../db'); +var db = require('../db') exports.up = function (next) { - db.issue33.push('2-up'); - next(); -}; + db.issue33.push('2-up') + next() +} exports.down = function (next) { - db.issue33.push('2-down'); - next(); -}; + db.issue33.push('2-down') + next() +} diff --git a/test/fixtures/issue-33/3-migration.js b/test/fixtures/issue-33/3-migration.js index f9ad2aa..60761fa 100644 --- a/test/fixtures/issue-33/3-migration.js +++ b/test/fixtures/issue-33/3-migration.js @@ -1,12 +1,12 @@ -var db = require('../db'); +var db = require('../db') exports.up = function (next) { - db.issue33.push('3-up'); - next(); -}; + db.issue33.push('3-up') + next() +} exports.down = function (next) { - db.issue33.push('3-down'); - next(); -}; + db.issue33.push('3-down') + next() +} diff --git a/test/issue-33.js b/test/issue-33.js index bf6b22d..cd33c7c 100644 --- a/test/issue-33.js +++ b/test/issue-33.js @@ -1,54 +1,51 @@ +/* global describe, it, beforeEach, afterEach */ -var fs = require('fs'); -var path = require('path'); -var assert = require('assert'); +var fs = require('fs') +var path = require('path') +var assert = require('assert') -var migrate = require('../'); -var db = require('./fixtures/db'); +var migrate = require('../') +var db = require('./fixtures/db') -var BASE = path.join(__dirname, 'fixtures', 'issue-33'); -var STATE = path.join(BASE, '.migrate'); +var BASE = path.join(__dirname, 'fixtures', 'issue-33') +var STATE = path.join(BASE, '.migrate') -var A1 = ['1-up', '2-up', '3-up']; -var A2 = A1.concat(['3-down', '2-down', '1-down']); +var A1 = ['1-up', '2-up', '3-up'] +var A2 = A1.concat(['3-down', '2-down', '1-down']) describe('issue #33', function () { - - var set; + var set beforeEach(function () { - set = migrate.load(STATE, BASE); - }); + set = migrate.load(STATE, BASE) + }) it('should run migrations in the correct order', function (done) { - set.up(function (err) { - assert.ifError(err); - assert.deepEqual(db.issue33, A1); + assert.ifError(err) + assert.deepEqual(db.issue33, A1) set.up(function (err) { - assert.ifError(err); - assert.deepEqual(db.issue33, A1); + assert.ifError(err) + assert.deepEqual(db.issue33, A1) set.down(function (err) { - assert.ifError(err); - assert.deepEqual(db.issue33, A2); + assert.ifError(err) + assert.deepEqual(db.issue33, A2) set.down(function (err) { - assert.ifError(err); - assert.deepEqual(db.issue33, A2); + assert.ifError(err) + assert.deepEqual(db.issue33, A2) - done(); - }); - }); - }); - }); - - }); + done() + }) + }) + }) + }) + }) afterEach(function (done) { - db.nuke(); - fs.unlink(STATE, done); - }); - -}); + db.nuke() + fs.unlink(STATE, done) + }) +}) From 9cca25c91a39a74029457b8fac99611a22f51ac6 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 14 Apr 2017 12:34:29 -0500 Subject: [PATCH 02/29] refactored structure, removed makefile, added .editorconfig --- .editorconfig | 15 ++++++++++++++ Makefile | 5 ----- bin/migrate | 1 + index.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++- lib/migrate.js | 50 ----------------------------------------------- lib/migration.js | 1 + lib/set.js | 1 + 7 files changed, 68 insertions(+), 56 deletions(-) create mode 100644 .editorconfig delete mode 100644 Makefile delete mode 100644 lib/migrate.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4eff431 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[{*.js,*.json,*.yml}] +indent_size = 2 +indent_style = space + +[*.md] +trim_trailing_whitespace = false diff --git a/Makefile b/Makefile deleted file mode 100644 index d260bf0..0000000 --- a/Makefile +++ /dev/null @@ -1,5 +0,0 @@ - -test: - @node test/test.migrate.js - -.PHONY: test \ No newline at end of file diff --git a/bin/migrate b/bin/migrate index b8a958e..a2b49a6 100755 --- a/bin/migrate +++ b/bin/migrate @@ -1,4 +1,5 @@ #!/usr/bin/env node +'use strict' /** * Module dependencies. diff --git a/index.js b/index.js index e80af03..ee6cd1a 100644 --- a/index.js +++ b/index.js @@ -1,2 +1,51 @@ +'use strict' -module.exports = require('./lib/migrate') +/*! + * migrate + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var MigrationSet = require('./lib/set') +var path = require('path') +var fs = require('fs') + +/** + * Expose the migrate function. + */ + +exports = module.exports = migrate + +function migrate (title, up, down) { + // migration + if (typeof title === 'string' && up && down) { + migrate.set.addMigration(title, up, down) + // specify migration file + } else if (typeof title === 'string') { + migrate.set = new MigrationSet(title) + // no migration path + } else if (!migrate.set) { + throw new Error('must invoke migrate(path) before running migrations') + // run migrations + } else { + return migrate.set + } +} + +exports.load = function (stateFile, migrationsDirectory) { + var set = new MigrationSet(stateFile) + var dir = path.resolve(migrationsDirectory) + + fs.readdirSync(dir).filter(function (file) { + return file.match(/^\d+.*\.js$/) + }).sort().forEach(function (file) { + var mod = require(path.join(dir, file)) + set.addMigration(file, mod.up, mod.down) + }) + + return set +} diff --git a/lib/migrate.js b/lib/migrate.js deleted file mode 100644 index b01ef31..0000000 --- a/lib/migrate.js +++ /dev/null @@ -1,50 +0,0 @@ - -/*! - * migrate - * Copyright(c) 2011 TJ Holowaychuk - * MIT Licensed - */ - -/** - * Module dependencies. - */ - -var Set = require('./set') -var path = require('path') -var fs = require('fs') - -/** - * Expose the migrate function. - */ - -exports = module.exports = migrate - -function migrate (title, up, down) { - // migration - if (typeof title === 'string' && up && down) { - migrate.set.addMigration(title, up, down) - // specify migration file - } else if (typeof title === 'string') { - migrate.set = new Set(title) - // no migration path - } else if (!migrate.set) { - throw new Error('must invoke migrate(path) before running migrations') - // run migrations - } else { - return migrate.set - } -} - -exports.load = function (stateFile, migrationsDirectory) { - var set = new Set(stateFile) - var dir = path.resolve(migrationsDirectory) - - fs.readdirSync(dir).filter(function (file) { - return file.match(/^\d+.*\.js$/) - }).sort().forEach(function (file) { - var mod = require(path.join(dir, file)) - set.addMigration(file, mod.up, mod.down) - }) - - return set -} diff --git a/lib/migration.js b/lib/migration.js index 9100f0f..130ae6e 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -1,3 +1,4 @@ +'use strict' /*! * migrate - Migration diff --git a/lib/set.js b/lib/set.js index 7c49cea..91e3590 100644 --- a/lib/set.js +++ b/lib/set.js @@ -1,3 +1,4 @@ +'use strict' /*! * migrate - Set From a18219dd7e10dc6fb3d5ee7531b27e2785dc6f78 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 14 Apr 2017 12:38:55 -0500 Subject: [PATCH 03/29] replaced self = this with arrow functions --- lib/set.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/set.js b/lib/set.js index 91e3590..51e9fda 100644 --- a/lib/set.js +++ b/lib/set.js @@ -61,12 +61,10 @@ MigrationSet.prototype.addMigration = function (title, up, down) { */ MigrationSet.prototype.save = function (fn) { - var self = this var json = JSON.stringify(this) - fs.writeFile(this.path, json, function (err) { + fs.writeFile(this.path, json, (err) => { if (err) return fn(err) - - self.emit('save') + this.emit('save') fn(null) }) } @@ -126,14 +124,13 @@ MigrationSet.prototype.migrate = function (direction, migrationName, fn) { fn = migrationName migrationName = null } - var self = this - this.load(function (err, obj) { + this.load((err, obj) => { if (err) { if (err.code !== 'ENOENT') return fn(err) } else { - self.pos = obj.pos + this.pos = obj.pos } - self._migrate(direction, migrationName, fn) + this._migrate(direction, migrationName, fn) }) } @@ -157,7 +154,6 @@ function positionOfMigration (migrations, filename) { */ MigrationSet.prototype._migrate = function (direction, migrationName, fn) { - var self = this var migrations var migrationPos @@ -176,15 +172,15 @@ MigrationSet.prototype._migrate = function (direction, migrationName, fn) { break } - function next (migration) { + var next = (migration) => { if (!migration) return fn(null) - self.emit('migration', migration, direction) - migration[direction](function (err) { + this.emit('migration', migration, direction) + migration[direction]((err) => { if (err) return fn(err) - self.pos += (direction === 'up' ? 1 : -1) - self.save(function (err) { + this.pos += (direction === 'up' ? 1 : -1) + this.save((err) => { if (err) return fn(err) next(migrations.shift()) From bdda2a35534362bf19b638d5afac4f6067124aab Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 14 Apr 2017 12:47:25 -0500 Subject: [PATCH 04/29] Fixes https://github.com/tj/node-migrate/pull/60 --- lib/set.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/set.js b/lib/set.js index 51e9fda..55f29bf 100644 --- a/lib/set.js +++ b/lib/set.js @@ -81,6 +81,9 @@ MigrationSet.prototype.load = function (fn) { this.emit('load') fs.readFile(this.path, 'utf8', function (err, json) { if (err) return fn(err) + if (!json || json === '') { + return fn(null, this) + } try { fn(null, JSON.parse(json)) } catch (err) { From 5600a7415bb2999964958ba17d82c79440473269 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 14 Apr 2017 12:56:31 -0500 Subject: [PATCH 05/29] resolves https://github.com/tj/node-migrate/issues/59 with code from https://github.com/jarvisaoieong/migration --- bin/migrate | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/bin/migrate b/bin/migrate index a2b49a6..ff6095d 100755 --- a/bin/migrate +++ b/bin/migrate @@ -23,6 +23,12 @@ var args = process.argv.slice(2) var options = { args: [] } +/** + * Default extension + */ + +var extension = 'js' + /** * Usage information. */ @@ -33,10 +39,11 @@ var usage = [ '', ' Options:', '', - ' -c, --chdir change the working directory', - ' --state-file set path to state file (migrations/.migrate)', - ' --template-file set path to template file to use for new migrations', - ' --date-format set a date format to use for new migration filenames', + ' -c, --chdir change the working directory', + ' --state-file set path to state file (migrations/.migrate)', + ' --template-file set path to template file to use for new migrations', + ' --date-format set a date format to use for new migration filenames', + ' --compiler : use the given module to create or compile files', ' Commands:', '', ' down [name] migrate down till given migration', @@ -76,6 +83,19 @@ function abort (msg) { process.exit(1) } +var registerCompiler = function (c) { + var compiler = c.split(':') + var ext = compiler[0] + var mod = compiler[1] + + extension = ext + + if (mod[0] === '.') mod = join(process.cwd(), mod) + require(mod)({ + extensions: ['.' + ext] + }) +} + // parse arguments var arg @@ -100,6 +120,9 @@ while (args.length) { case '--date-format': options.dateFormat = required() break + case '--compiler': + registerCompiler(required()) + break default: if (options.command) { options.args.push(arg) @@ -175,7 +198,7 @@ var commands = { */ function create (name) { - var path = join('migrations', name + '.js') + var path = join('migrations', name + '.' + extension) log('create', join(process.cwd(), path)) fs.writeFileSync(path, template) } From 750a4b12d95a571f105317da79cda6e5ef2db54f Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 14 Apr 2017 13:08:54 -0500 Subject: [PATCH 06/29] move template into a separate file --- bin/migrate | 21 ++++++++------------- lib/template.js | 9 +++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 lib/template.js diff --git a/bin/migrate b/bin/migrate index ff6095d..135bcc4 100755 --- a/bin/migrate +++ b/bin/migrate @@ -56,18 +56,8 @@ var usage = [ * Migration template. */ -var template = [ - '\'use strict\'', - '', - 'exports.up = function(next) {', - ' next();', - '};', - '', - 'exports.down = function(next) {', - ' next();', - '};', - '' -].join('\n') +var template +var templatePath = join(__dirname, '..', 'lib', 'template.js') // require an argument @@ -115,7 +105,7 @@ while (args.length) { options.stateFile = required() break case '--template-file': - template = fs.readFileSync(required()) + templatePath = required() break case '--date-format': options.dateFormat = required() @@ -132,6 +122,11 @@ while (args.length) { } } +/** + * Load template file + */ +template = fs.readFileSync(templatePath) + /** * Log a keyed message. */ diff --git a/lib/template.js b/lib/template.js new file mode 100644 index 0000000..f158549 --- /dev/null +++ b/lib/template.js @@ -0,0 +1,9 @@ +'use strict' + +module.exports.up = function (next) { + next() +} + +module.exports.down = function (next) { + next() +} From 2a909162330ed3e2f939f61b037e9aa12704870b Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 15 Apr 2017 11:15:03 -0500 Subject: [PATCH 07/29] converted bin scripts to commander, refactored tests to test new functionality --- .editorconfig | 2 +- .gitignore | 2 +- bin/migrate | 241 ++-------------------- bin/migrate-create | 39 ++++ bin/migrate-down | 47 +++++ bin/migrate-init | 22 ++ bin/migrate-up | 34 +++ lib/log.js | 6 + package.json | 17 +- test/basic.js | 4 +- test/cli.js | 177 ++++++++++++++++ test/fixtures/basic/1-add-guy-ferrets.js | 2 +- test/fixtures/basic/2-add-girl-ferrets.js | 2 +- test/fixtures/basic/3-add-emails.js | 2 +- test/fixtures/db.js | 9 - test/fixtures/issue-33/1-migration.js | 2 +- test/fixtures/issue-33/2-migration.js | 2 +- test/fixtures/issue-33/3-migration.js | 2 +- test/issue-33.js | 2 +- test/util/db.js | 38 ++++ test/util/run.js | 23 +++ 21 files changed, 423 insertions(+), 252 deletions(-) create mode 100755 bin/migrate-create create mode 100755 bin/migrate-down create mode 100755 bin/migrate-init create mode 100755 bin/migrate-up create mode 100644 lib/log.js create mode 100644 test/cli.js delete mode 100644 test/fixtures/db.js create mode 100644 test/util/db.js create mode 100644 test/util/run.js diff --git a/.editorconfig b/.editorconfig index 4eff431..6148158 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -[{*.js,*.json,*.yml}] +[{*.js,*.json,*.yml,bin/*}] indent_size = 2 indent_style = space diff --git a/.gitignore b/.gitignore index e9af0b1..57f3a0a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ node_modules *.sock .migrate - +*.db migrations/ diff --git a/bin/migrate b/bin/migrate index 135bcc4..1382ca2 100755 --- a/bin/migrate +++ b/bin/migrate @@ -1,231 +1,16 @@ #!/usr/bin/env node 'use strict' -/** - * Module dependencies. - */ - -var migrate = require('../') -var chalk = require('chalk') -var join = require('path').join -var fs = require('fs') -var dateFormat = require('dateformat') - -/** - * Arguments. - */ - -var args = process.argv.slice(2) - -/** - * Option defaults. - */ - -var options = { args: [] } - -/** - * Default extension - */ - -var extension = 'js' - -/** - * Usage information. - */ - -var usage = [ - '', - ' Usage: migrate [options] [command]', - '', - ' Options:', - '', - ' -c, --chdir change the working directory', - ' --state-file set path to state file (migrations/.migrate)', - ' --template-file set path to template file to use for new migrations', - ' --date-format set a date format to use for new migration filenames', - ' --compiler : use the given module to create or compile files', - ' Commands:', - '', - ' down [name] migrate down till given migration', - ' up [name] migrate up till given migration (the default command)', - ' create [title] create a new migration file with optional [title]', - '' -].join('\n') - -/** - * Migration template. - */ - -var template -var templatePath = join(__dirname, '..', 'lib', 'template.js') - -// require an argument - -function required () { - if (args.length) return args.shift() - abort(arg + ' requires an argument') -} - -// abort with a message - -function abort (msg) { - console.error(' %s', msg) - process.exit(1) -} - -var registerCompiler = function (c) { - var compiler = c.split(':') - var ext = compiler[0] - var mod = compiler[1] - - extension = ext - - if (mod[0] === '.') mod = join(process.cwd(), mod) - require(mod)({ - extensions: ['.' + ext] - }) -} - -// parse arguments - -var arg -while (args.length) { - arg = args.shift() - switch (arg) { - case '-h': - case '--help': - case 'help': - console.log(usage) - process.exit() - case '-c': - case '--chdir': - process.chdir(required()) - break - case '--state-file': - options.stateFile = required() - break - case '--template-file': - templatePath = required() - break - case '--date-format': - options.dateFormat = required() - break - case '--compiler': - registerCompiler(required()) - break - default: - if (options.command) { - options.args.push(arg) - } else { - options.command = arg - } - } -} - -/** - * Load template file - */ -template = fs.readFileSync(templatePath) - -/** - * Log a keyed message. - */ - -function log (key, msg) { - console.log(' ' + chalk.grey(key) + ' : ' + chalk.teal(msg)) -} - -/** - * Slugify the given `str`. - */ - -function slugify (str) { - return str.replace(/\s+/g, '-') -} - -// create ./migrations - -try { - fs.mkdirSync('migrations', 774) -} catch (err) { - // ignore -} - -// commands - -var commands = { - - /** - * up [name] - */ - - up: function (migrationName) { - performMigration('up', migrationName) - }, - - /** - * down [name] - */ - - down: function (migrationName) { - performMigration('down', migrationName) - }, - - /** - * create [title] - */ - - create: function () { - var curr = Date.now() - var title = slugify([].slice.call(arguments).join(' ')) - if (options.dateFormat) { - curr = dateFormat(curr, options.dateFormat) - } - title = title ? curr + '-' + title : curr - create(title) - } -} - -/** - * Create a migration with the given `name`. - * - * @param {String} name - */ - -function create (name) { - var path = join('migrations', name + '.' + extension) - log('create', join(process.cwd(), path)) - fs.writeFileSync(path, template) -} - -/** - * Perform a migration in the given `direction`. - * - * @param {Number} direction - */ - -function performMigration (direction, migrationName) { - var state = options.stateFile || join('migrations', '.migrate') - var set = migrate.load(state, 'migrations') - - set.on('migration', function (migration, direction) { - log(direction, migration.title) - }) - - set[direction](migrationName, function (err) { - if (err) { - log('error', err) - process.exit(1) - } - - log('migration', 'complete') - process.exit(0) - }) -} - -// invoke command - -var command = options.command || 'up' -if (!(command in commands)) abort('unknown command "' + command + '"') -command = commands[command] -command.apply(this, options.args) +var program = require('commander') +var path = require('path') +var pkg = require('../package.json') + +program + .version(pkg.version) + .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) + .option('-s, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) + .command('init') + .command('create [name]', 'Create a new migration') + .command('up [name]', 'Migrate up to a give migration', {isDefault: true}) + .command('down [name]', 'Migrate down to a given migration') + .parse(process.argv) diff --git a/bin/migrate-create b/bin/migrate-create new file mode 100755 index 0000000..6d9f3ef --- /dev/null +++ b/bin/migrate-create @@ -0,0 +1,39 @@ +#!/usr/bin/env node +'use strict' + +var program = require('commander') +var slug = require('slug') +var moment = require('moment') +var mkdirp = require('mkdirp') +var path = require('path') +var fs = require('fs') +var log = require('../lib/log') +var pkg = require('../package.json') + +program + .version(pkg.version) + .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) + .option('-d, --date-format [format]', 'Set a date format to use', 'x') + .option('-t, --template-file [filePath]', 'Set path to template file to use for new migrations', path.join(__dirname, '..', 'lib', 'template.js')) + .option('-e, --extention [extention]', 'Use the given extention to create the file', '.js') + .arguments('[name]') + .action(create) + .parse(process.argv) + +function create (name) { + // Change the working dir + process.chdir(program.chdir) + + // Load template file + var template = fs.readFileSync(program.templateFile) + + // Create date string + var now = moment().format(program.dateFormat) + + // Create file path + var p = path.join(process.cwd(), 'migrations', slug(now + (name ? '-' + name : '')) + program.extention) + + log('create', p) + mkdirp.sync(path.dirname(p)) + fs.writeFileSync(p, template) +} diff --git a/bin/migrate-down b/bin/migrate-down new file mode 100755 index 0000000..63005c3 --- /dev/null +++ b/bin/migrate-down @@ -0,0 +1,47 @@ +#!/usr/bin/env node +'use strict' + +var program = require('commander') +var path = require('path') +var migrate = require('../') +var log = require('../lib/log') +var pkg = require('../package.json') + +program + .version(pkg.version) + .usage('[options] ') + .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) + .option('-s, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) + .parse(process.argv) + +// Change the working dir +process.chdir(program.chdir) + +var set = migrate.load(program.stateFile, 'migrations') + +set.on('migration', function (migration, direction) { + log('down', migration.title) +}) + +set.down(program.args[0], function (err) { + if (err) { + log('error', err) + process.exit(1) + } + + log('migration', 'complete') + process.exit(0) +}) + +// var registerCompiler = function (c) { +// var compiler = c.split(':') +// var ext = compiler[0] +// var mod = compiler[1] +// +// extension = ext +// +// if (mod[0] === '.') mod = join(process.cwd(), mod) +// require(mod)({ +// extensions: ['.' + ext] +// }) +// } diff --git a/bin/migrate-init b/bin/migrate-init new file mode 100755 index 0000000..b2960f7 --- /dev/null +++ b/bin/migrate-init @@ -0,0 +1,22 @@ +#!/usr/bin/env node +'use strict' + +var program = require('commander') +var mkdirp = require('mkdirp') +var path = require('path') +var log = require('../lib/log') +var pkg = require('../package.json') + +program + .version(pkg.version) + .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) + .parse(process.argv) + +// Change the working dir +process.chdir(program.chdir) + +// Create migrations dir path +var p = path.join(process.cwd(), 'migrations') + +log('init', p) +mkdirp.sync(p) diff --git a/bin/migrate-up b/bin/migrate-up new file mode 100755 index 0000000..04e3d07 --- /dev/null +++ b/bin/migrate-up @@ -0,0 +1,34 @@ +#!/usr/bin/env node +'use strict' + +var program = require('commander') +var path = require('path') +var migrate = require('../') +var log = require('../lib/log') +var pkg = require('../package.json') + +program + .version(pkg.version) + .usage('[options] ') + .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) + .option('-s, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) + .parse(process.argv) + +// Change the working dir +process.chdir(program.chdir) + +var set = migrate.load(program.stateFile, 'migrations') + +set.on('migration', function (migration, direction) { + log('up', migration.title) +}) + +set.up(program.args[0], function (err) { + if (err) { + log('error', err) + process.exit(1) + } + + log('migration', 'complete') + process.exit(0) +}) diff --git a/lib/log.js b/lib/log.js new file mode 100644 index 0000000..38c06a4 --- /dev/null +++ b/lib/log.js @@ -0,0 +1,6 @@ +'use strict' +var chalk = require('chalk') + +module.exports = function log (key, msg) { + console.log(' ' + chalk.grey(key) + ' : ' + chalk.cyan(msg)) +} diff --git a/package.json b/package.json index f8dd952..ab1031c 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,15 @@ "author": "TJ Holowaychuk ", "repository": "git://github.com/visionmedia/node-migrate", "bin": { - "migrate": "./bin/migrate" + "migrate": "./bin/migrate", + "migrate-init": "./bin/migrate-init", + "migrate-create": "./bin/migrate-create", + "migrate-up": "./bin/migrate-up", + "migrate-down": "./bin/migrate-down" }, "devDependencies": { - "mocha": "^2.2.1", + "mocha": "^3.2.0", + "rimraf": "^2.6.1", "standard": "^10.0.1" }, "main": "index", @@ -20,12 +25,16 @@ "node": ">= 0.4.x" }, "scripts": { - "test": "standard --fix && standard --fix ./bin/migrate && mocha" + "test": "standard --fix && standard --fix ./bin/* && mocha" }, "license": "MIT", "dependencies": { "chalk": "^1.1.3", + "commander": "^2.9.0", "dateformat": "^1.0.12", - "inherits": "^2.0.3" + "inherits": "^2.0.3", + "mkdirp": "^0.5.1", + "moment": "^2.18.1", + "slug": "^0.9.1" } } diff --git a/test/basic.js b/test/basic.js index f9670bd..1277e7f 100644 --- a/test/basic.js +++ b/test/basic.js @@ -5,12 +5,12 @@ var path = require('path') var assert = require('assert') var migrate = require('../') -var db = require('./fixtures/db') +var db = require('./util/db') var BASE = path.join(__dirname, 'fixtures', 'basic') var STATE = path.join(BASE, '.migrate') -describe('migrate', function () { +describe('migration set', function () { var set function assertNoPets () { diff --git a/test/cli.js b/test/cli.js new file mode 100644 index 0000000..824dc4b --- /dev/null +++ b/test/cli.js @@ -0,0 +1,177 @@ +/* global describe, it, beforeEach, afterEach */ +const path = require('path') +const fs = require('fs') +const assert = require('assert') +const rimraf = require('rimraf') +const mkdirp = require('mkdirp') +const moment = require('moment') +const db = require('./util/db') +const run = require('./util/run') + +// Paths +const FIX_DIR = path.join(__dirname, 'fixtures', 'numbers') +const TMP_DIR = path.join(__dirname, 'fixtures', 'tmp') +const UP = path.join(__dirname, '..', 'bin', 'migrate-up') +const DOWN = path.join(__dirname, '..', 'bin', 'migrate-down') +const CREATE = path.join(__dirname, '..', 'bin', 'migrate-create') +const INIT = path.join(__dirname, '..', 'bin', 'migrate-init') + +// Run helper +const up = run.bind(null, UP, FIX_DIR) +const down = run.bind(null, DOWN, FIX_DIR) +const create = run.bind(null, CREATE, TMP_DIR) +const init = run.bind(null, INIT, TMP_DIR) + +function reset () { + rimraf.sync(path.join(FIX_DIR, 'migrations', '.migrate')) + rimraf.sync(TMP_DIR) + mkdirp.sync(TMP_DIR) + db.nuke() +} + +describe('$ migrate', function () { + beforeEach(reset) + afterEach(reset) + + describe('init', function () { + it('should create a migrations directory', function (done) { + init([], function (err, out) { + assert(!err) + assert(out.indexOf('init') !== -1) + assert.doesNotThrow(() => { + fs.accessSync(path.join(TMP_DIR, 'migrations')) + }) + done() + }) + }) + }) // end init + + describe('create', function () { + it('should create a fixture file', function (done) { + create(['test'], function (err, out) { + assert(!err) + assert(out.indexOf('create') !== -1) + var file = out.split(':')[1].trim() + var content = fs.readFileSync(file, { + encoding: 'utf8' + }) + assert(content) + assert(content.indexOf('module.exports.up') !== -1) + assert(content.indexOf('module.exports.down') !== -1) + done() + }) + }) + + it('should respect the --date-format', function (done) { + var name = 'test' + var fmt = 'YYYY-MM-DD' + var now = moment().format(fmt) + + create([name, '-d', fmt], function (err, out) { + assert(!err) + assert.doesNotThrow(() => { + fs.accessSync(path.join(TMP_DIR, 'migrations', now + '-' + name + '.js')) + }) + done() + }) + }) + + it('should respect the --extention', function (done) { + var name = 'test' + var fmt = 'YYYY-MM-DD' + var ext = '.mjs' + var now = moment().format(fmt) + + create([name, '-d', fmt, '-e', ext], function (err, out) { + assert(!err) + assert.doesNotThrow(() => { + fs.accessSync(path.join(TMP_DIR, 'migrations', now + '-' + name + ext)) + }) + done() + }) + }) + }) // end create + + describe('up', function () { + it('should run up on multiple migrations', function (done) { + up([], function (err, out) { + assert(!err) + db.load() + assert(out.indexOf('up') !== -1) + assert.equal(db.numbers.length, 2) + assert(db.numbers.indexOf(1) !== -1) + assert(db.numbers.indexOf(2) !== -1) + done() + }) + }) + + it('should run up to a specified migration', function (done) { + up(['1-one.js'], function (err, out) { + assert(!err) + db.load() + assert(out.indexOf('up') !== -1) + assert.equal(db.numbers.length, 1) + assert(db.numbers.indexOf(1) !== -1) + assert(db.numbers.indexOf(2) === -1) + done() + }) + }) + + it('should run up multiple times', function (done) { + up([], function (err, out) { + assert(!err) + db.load() + assert(out.indexOf('up') !== -1) + up([], function (err, out) { + assert(!err) + assert(out.indexOf('up') === -1) + assert.equal(db.numbers.length, 2) + done() + }) + }) + }) + }) // end up + + describe('down', function () { + beforeEach(function (done) { + up([], done) + }) + it('should run down on multiple migrations', function (done) { + down([], function (err, out) { + assert(!err) + db.load() + assert(out.indexOf('down') !== -1) + assert.equal(db.numbers.length, 0) + assert(db.numbers.indexOf(1) === -1) + assert(db.numbers.indexOf(2) === -1) + done() + }) + }) + + it('should run down to a specified migration', function (done) { + down(['2-two.js'], function (err, out) { + assert(!err) + db.load() + assert(out.indexOf('down') !== -1) + assert.equal(db.numbers.length, 1) + assert(db.numbers.indexOf(1) !== -1) + assert(db.numbers.indexOf(2) === -1) + done() + }) + }) + + it('should run down multiple times', function (done) { + down([], function (err, out) { + assert(!err) + assert(out.indexOf('down') !== -1) + db.load() + down([], function (err, out) { + assert(!err) + assert(out.indexOf('down') === -1) + assert.equal(db.numbers.length, 0) + done() + }) + }) + }) + }) // end down +}) diff --git a/test/fixtures/basic/1-add-guy-ferrets.js b/test/fixtures/basic/1-add-guy-ferrets.js index fd1b9c7..0ca18e4 100644 --- a/test/fixtures/basic/1-add-guy-ferrets.js +++ b/test/fixtures/basic/1-add-guy-ferrets.js @@ -1,5 +1,5 @@ -var db = require('../db') +var db = require('../../util/db') exports.up = function (next) { db.pets.push({ name: 'tobi' }) diff --git a/test/fixtures/basic/2-add-girl-ferrets.js b/test/fixtures/basic/2-add-girl-ferrets.js index a661149..a096e31 100644 --- a/test/fixtures/basic/2-add-girl-ferrets.js +++ b/test/fixtures/basic/2-add-girl-ferrets.js @@ -1,5 +1,5 @@ -var db = require('../db') +var db = require('../../util/db') exports.up = function (next) { db.pets.push({ name: 'jane' }) diff --git a/test/fixtures/basic/3-add-emails.js b/test/fixtures/basic/3-add-emails.js index e4ad65e..4e89607 100644 --- a/test/fixtures/basic/3-add-emails.js +++ b/test/fixtures/basic/3-add-emails.js @@ -1,5 +1,5 @@ -var db = require('../db') +var db = require('../../util/db') exports.up = function (next) { db.pets.forEach(function (pet) { diff --git a/test/fixtures/db.js b/test/fixtures/db.js deleted file mode 100644 index 30b9482..0000000 --- a/test/fixtures/db.js +++ /dev/null @@ -1,9 +0,0 @@ - -function nuke () { - exports.pets = [] - exports.issue33 = [] -} - -exports.nuke = nuke - -nuke() diff --git a/test/fixtures/issue-33/1-migration.js b/test/fixtures/issue-33/1-migration.js index 81cc27c..6d866d3 100644 --- a/test/fixtures/issue-33/1-migration.js +++ b/test/fixtures/issue-33/1-migration.js @@ -1,5 +1,5 @@ -var db = require('../db') +var db = require('../../util/db') exports.up = function (next) { db.issue33.push('1-up') diff --git a/test/fixtures/issue-33/2-migration.js b/test/fixtures/issue-33/2-migration.js index 27ea3b7..159c3d5 100644 --- a/test/fixtures/issue-33/2-migration.js +++ b/test/fixtures/issue-33/2-migration.js @@ -1,5 +1,5 @@ -var db = require('../db') +var db = require('../../util/db') exports.up = function (next) { db.issue33.push('2-up') diff --git a/test/fixtures/issue-33/3-migration.js b/test/fixtures/issue-33/3-migration.js index 60761fa..b94ad4c 100644 --- a/test/fixtures/issue-33/3-migration.js +++ b/test/fixtures/issue-33/3-migration.js @@ -1,5 +1,5 @@ -var db = require('../db') +var db = require('../../util/db') exports.up = function (next) { db.issue33.push('3-up') diff --git a/test/issue-33.js b/test/issue-33.js index cd33c7c..bedc8de 100644 --- a/test/issue-33.js +++ b/test/issue-33.js @@ -5,7 +5,7 @@ var path = require('path') var assert = require('assert') var migrate = require('../') -var db = require('./fixtures/db') +var db = require('./util/db') var BASE = path.join(__dirname, 'fixtures', 'issue-33') var STATE = path.join(BASE, '.migrate') diff --git a/test/util/db.js b/test/util/db.js new file mode 100644 index 0000000..82a261b --- /dev/null +++ b/test/util/db.js @@ -0,0 +1,38 @@ +var fs = require('fs') +var path = require('path') +var rimraf = require('rimraf') + +var DB_PATH = path.join(__dirname, 'test.db') + +function init () { + exports.pets = [] + exports.issue33 = [] + exports.numbers = [] +} + +function nuke () { + init() + rimraf.sync(DB_PATH) +} + +function load () { + try { + var c = fs.readFileSync(DB_PATH, 'utf8') + } catch (e) { + return + } + var j = JSON.parse(c) + Object.keys(j).forEach(function (k) { + exports[k] = j[k] + }) +} + +function persist () { + fs.writeFileSync(DB_PATH, JSON.stringify(exports)) +} + +exports.nuke = nuke +exports.persist = persist +exports.load = load + +init() diff --git a/test/util/run.js b/test/util/run.js new file mode 100644 index 0000000..fa681f1 --- /dev/null +++ b/test/util/run.js @@ -0,0 +1,23 @@ +'use strict' +const spawn = require('child_process').spawn +const assert = require('assert') + +module.exports = function run (cmd, dir, args, done) { + var p = spawn(cmd, ['-c', dir, ...args]) + + var out = '' + p.stdout.on('data', function (d) { + out += d.toString('utf8') + }) + p.stderr.on('data', function (d) { + out += d.toString('utf8') + }) + p.on('error', done) + p.on('close', function (code) { + if (code !== 0) { + console.log(out) + } + assert.equal(code, 0) + done(null, out) + }) +} From d2fd9617b8673b05f5c0530bdf0bd2f60bdfd81e Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 15 Apr 2017 11:34:33 -0500 Subject: [PATCH 08/29] removed the 'private' method in favor of a more functional approach --- lib/migrate.js | 54 +++++++++++++++++++++++++++++++++++++++++++++ lib/set.js | 60 ++------------------------------------------------ 2 files changed, 56 insertions(+), 58 deletions(-) create mode 100644 lib/migrate.js diff --git a/lib/migrate.js b/lib/migrate.js new file mode 100644 index 0000000..520365b --- /dev/null +++ b/lib/migrate.js @@ -0,0 +1,54 @@ +'use strict' + +module.exports = migrate + +function migrate (set, direction, migrationName, fn) { + var migrations + var migrationPos + + if (!migrationName) { + migrationPos = direction === 'up' ? set.migrations.length : 0 + } else if ((migrationPos = positionOfMigration(set.migrations, migrationName)) === -1) { + return fn(new Error('Could not find migration: ' + migrationName)) + } + + switch (direction) { + case 'up': + migrations = set.migrations.slice(set.pos, migrationPos + 1) + break + case 'down': + migrations = set.migrations.slice(migrationPos, set.pos).reverse() + break + } + + function next (migration) { + if (!migration) return fn(null) + + set.emit('migration', migration, direction) + migration[direction](function (err) { + if (err) return fn(err) + + set.pos += (direction === 'up' ? 1 : -1) + set.save(function (err) { + if (err) return fn(err) + + next(migrations.shift()) + }) + }) + } + + next(migrations.shift()) +} + +/** + * Get index of given migration in list of migrations + * + * @api private + */ + +function positionOfMigration (migrations, filename) { + for (var i = 0; i < migrations.length; ++i) { + if (migrations[i].title === filename) return i + } + return -1 +} diff --git a/lib/set.js b/lib/set.js index 55f29bf..38a1538 100644 --- a/lib/set.js +++ b/lib/set.js @@ -12,6 +12,7 @@ var EventEmitter = require('events') var Migration = require('./migration') +var migrate = require('./migrate') var fs = require('fs') var inherits = require('inherits') @@ -133,63 +134,6 @@ MigrationSet.prototype.migrate = function (direction, migrationName, fn) { } else { this.pos = obj.pos } - this._migrate(direction, migrationName, fn) + migrate(this, direction, migrationName, fn) }) } - -/** - * Get index of given migration in list of migrations - * - * @api private - */ - -function positionOfMigration (migrations, filename) { - for (var i = 0; i < migrations.length; ++i) { - if (migrations[i].title === filename) return i - } - return -1 -} - -/** - * Perform migration. - * - * @api private - */ - -MigrationSet.prototype._migrate = function (direction, migrationName, fn) { - var migrations - var migrationPos - - if (!migrationName) { - migrationPos = direction === 'up' ? this.migrations.length : 0 - } else if ((migrationPos = positionOfMigration(this.migrations, migrationName)) === -1) { - return fn(new Error('Could not find migration: ' + migrationName)) - } - - switch (direction) { - case 'up': - migrations = this.migrations.slice(this.pos, migrationPos + 1) - break - case 'down': - migrations = this.migrations.slice(migrationPos, this.pos).reverse() - break - } - - var next = (migration) => { - if (!migration) return fn(null) - - this.emit('migration', migration, direction) - migration[direction]((err) => { - if (err) return fn(err) - - this.pos += (direction === 'up' ? 1 : -1) - this.save((err) => { - if (err) return fn(err) - - next(migrations.shift()) - }) - }) - } - - next(migrations.shift()) -} From b8633c7b69fc1f2d6f8ff56572230cb5c9b180ba Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 15 Apr 2017 11:59:38 -0500 Subject: [PATCH 09/29] moved migration state storage into a separate abstraction --- index.js | 12 ++++++++++-- lib/file-store.js | 42 ++++++++++++++++++++++++++++++++++++++++++ lib/set.js | 20 ++++---------------- 3 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 lib/file-store.js diff --git a/index.js b/index.js index ee6cd1a..ae40b47 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ */ var MigrationSet = require('./lib/set') +var FileStore = require('./lib/file-store') var path = require('path') var fs = require('fs') @@ -26,7 +27,7 @@ function migrate (title, up, down) { migrate.set.addMigration(title, up, down) // specify migration file } else if (typeof title === 'string') { - migrate.set = new MigrationSet(title) + migrate.set = exports.load(title) // no migration path } else if (!migrate.set) { throw new Error('must invoke migrate(path) before running migrations') @@ -37,7 +38,14 @@ function migrate (title, up, down) { } exports.load = function (stateFile, migrationsDirectory) { - var set = new MigrationSet(stateFile) + var store + if (typeof stateFile === 'string') { + store = new FileStore(stateFile) + } else { + store = stateFile + } + + var set = new MigrationSet(store) var dir = path.resolve(migrationsDirectory) fs.readdirSync(dir).filter(function (file) { diff --git a/lib/file-store.js b/lib/file-store.js new file mode 100644 index 0000000..1737485 --- /dev/null +++ b/lib/file-store.js @@ -0,0 +1,42 @@ +var fs = require('fs') + +module.exports = FileStore + +function FileStore (path) { + this.path = path +} + +/** + * Save the migration data. + * + * @api public + */ + +FileStore.prototype.save = function (set, fn) { + fs.writeFile(this.path, JSON.stringify({ + pos: set.pos, + migrations: set.migrations + }), fn) +} + +/** + * Load the migration data and call `fn(err, obj)`. + * + * @param {Function} fn + * @return {Type} + * @api public + */ + +FileStore.prototype.load = function (fn) { + fs.readFile(this.path, 'utf8', function (err, json) { + if (err) return fn(err) + if (!json || json === '') { + return fn(null, this) + } + try { + fn(null, JSON.parse(json)) + } catch (err) { + fn(err) + } + }) +} diff --git a/lib/set.js b/lib/set.js index 38a1538..2eead87 100644 --- a/lib/set.js +++ b/lib/set.js @@ -13,7 +13,6 @@ var EventEmitter = require('events') var Migration = require('./migration') var migrate = require('./migrate') -var fs = require('fs') var inherits = require('inherits') /** @@ -30,9 +29,9 @@ module.exports = MigrationSet * @api private */ -function MigrationSet (path) { +function MigrationSet (store) { + this.store = store this.migrations = [] - this.path = path this.pos = 0 }; @@ -62,8 +61,7 @@ MigrationSet.prototype.addMigration = function (title, up, down) { */ MigrationSet.prototype.save = function (fn) { - var json = JSON.stringify(this) - fs.writeFile(this.path, json, (err) => { + this.store.save(this, (err) => { if (err) return fn(err) this.emit('save') fn(null) @@ -80,17 +78,7 @@ MigrationSet.prototype.save = function (fn) { MigrationSet.prototype.load = function (fn) { this.emit('load') - fs.readFile(this.path, 'utf8', function (err, json) { - if (err) return fn(err) - if (!json || json === '') { - return fn(null, this) - } - try { - fn(null, JSON.parse(json)) - } catch (err) { - fn(err) - } - }) + this.store.load(fn) } /** From 808ce4ef4e5b7165eb38792bb847a1563ca6eb03 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 15 Apr 2017 12:06:22 -0500 Subject: [PATCH 10/29] fixed tests so it removes tmp directory at end --- test/cli.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/cli.js b/test/cli.js index 824dc4b..f431e39 100644 --- a/test/cli.js +++ b/test/cli.js @@ -25,7 +25,6 @@ const init = run.bind(null, INIT, TMP_DIR) function reset () { rimraf.sync(path.join(FIX_DIR, 'migrations', '.migrate')) rimraf.sync(TMP_DIR) - mkdirp.sync(TMP_DIR) db.nuke() } @@ -34,6 +33,8 @@ describe('$ migrate', function () { afterEach(reset) describe('init', function () { + beforeEach(mkdirp.bind(mkdirp, TMP_DIR)) + it('should create a migrations directory', function (done) { init([], function (err, out) { assert(!err) @@ -47,6 +48,8 @@ describe('$ migrate', function () { }) // end init describe('create', function () { + beforeEach(mkdirp.bind(mkdirp, TMP_DIR)) + it('should create a fixture file', function (done) { create(['test'], function (err, out) { assert(!err) From 8ca9d63bc9eb18357c1350f85f95718d5933b5bc Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 15 Apr 2017 12:20:26 -0500 Subject: [PATCH 11/29] moved loading logic into separate file --- index.js | 11 ++++------- lib/load-migrations.js | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 lib/load-migrations.js diff --git a/index.js b/index.js index ae40b47..11cc026 100644 --- a/index.js +++ b/index.js @@ -12,8 +12,7 @@ var MigrationSet = require('./lib/set') var FileStore = require('./lib/file-store') -var path = require('path') -var fs = require('fs') +var loadMigrationsIntoSet = require('./lib/load-migrations') /** * Expose the migrate function. @@ -38,6 +37,7 @@ function migrate (title, up, down) { } exports.load = function (stateFile, migrationsDirectory) { + // Create store var store if (typeof stateFile === 'string') { store = new FileStore(stateFile) @@ -46,13 +46,10 @@ exports.load = function (stateFile, migrationsDirectory) { } var set = new MigrationSet(store) - var dir = path.resolve(migrationsDirectory) - fs.readdirSync(dir).filter(function (file) { + // Load directory into set + loadMigrationsIntoSet(set, migrationsDirectory, function (file) { return file.match(/^\d+.*\.js$/) - }).sort().forEach(function (file) { - var mod = require(path.join(dir, file)) - set.addMigration(file, mod.up, mod.down) }) return set diff --git a/lib/load-migrations.js b/lib/load-migrations.js new file mode 100644 index 0000000..0e51d9f --- /dev/null +++ b/lib/load-migrations.js @@ -0,0 +1,23 @@ +'use strict' + +var path = require('path') +var fs = require('fs') + +module.exports = loadMigrationsIntoSet + +function loadMigrationsIntoSet (set, migrationsDirectory, filter, sort) { + var dir = path.resolve(migrationsDirectory) + var files = fs.readdirSync(dir) + + // Run filter + files = files.filter(filter || (() => true)) + + // Run sort + files = files.sort(sort) + + // Load the migrations into the set + files.forEach(function (file) { + var mod = require(path.join(dir, file)) + set.addMigration(file, mod.up, mod.down) + }) +} From 00f38ebc4bdde6a7703618982359be118cd055ec Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 15 Apr 2017 14:37:01 -0500 Subject: [PATCH 12/29] accept -s as a requirable string to the migrations store --- bin/migrate-down | 10 ++++++++-- bin/migrate-up | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/bin/migrate-down b/bin/migrate-down index 63005c3..a876305 100755 --- a/bin/migrate-down +++ b/bin/migrate-down @@ -11,13 +11,19 @@ program .version(pkg.version) .usage('[options] ') .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) - .option('-s, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) + .option('-f, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) + .option('-s, --store ', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) .parse(process.argv) // Change the working dir process.chdir(program.chdir) -var set = migrate.load(program.stateFile, 'migrations') +// Setup store +var Store = require(program.store) +var store = new Store(program.stateFile) + +// Load migrations +var set = migrate.load(store, 'migrations') set.on('migration', function (migration, direction) { log('down', migration.title) diff --git a/bin/migrate-up b/bin/migrate-up index 04e3d07..8699dd9 100755 --- a/bin/migrate-up +++ b/bin/migrate-up @@ -11,13 +11,19 @@ program .version(pkg.version) .usage('[options] ') .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) - .option('-s, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) + .option('-f, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) + .option('-s, --store ', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) .parse(process.argv) // Change the working dir process.chdir(program.chdir) -var set = migrate.load(program.stateFile, 'migrations') +// Setup store +var Store = require(program.store) +var store = new Store(program.stateFile) + +// Load migrations +var set = migrate.load(store, 'migrations') set.on('migration', function (migration, direction) { log('up', migration.title) From eb7f8f9847e58dbad1ae94aa6b6b06a147605160 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 15 Apr 2017 14:47:04 -0500 Subject: [PATCH 13/29] added ability to use a compiler back after commander conversion --- bin/migrate-down | 20 +++++++------------- bin/migrate-up | 7 +++++++ lib/register-compiler.js | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 lib/register-compiler.js diff --git a/bin/migrate-down b/bin/migrate-down index a876305..94da554 100755 --- a/bin/migrate-down +++ b/bin/migrate-down @@ -5,6 +5,7 @@ var program = require('commander') var path = require('path') var migrate = require('../') var log = require('../lib/log') +var registerCompiler = require('../lib/register-compiler') var pkg = require('../package.json') program @@ -13,11 +14,17 @@ program .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) .option('-f, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) .option('-s, --store ', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) + .option('--compiler ', 'Use the given module to compile files') .parse(process.argv) // Change the working dir process.chdir(program.chdir) +// Load compiler +if (program.compiler) { + registerCompiler(program.compiler) +} + // Setup store var Store = require(program.store) var store = new Store(program.stateFile) @@ -38,16 +45,3 @@ set.down(program.args[0], function (err) { log('migration', 'complete') process.exit(0) }) - -// var registerCompiler = function (c) { -// var compiler = c.split(':') -// var ext = compiler[0] -// var mod = compiler[1] -// -// extension = ext -// -// if (mod[0] === '.') mod = join(process.cwd(), mod) -// require(mod)({ -// extensions: ['.' + ext] -// }) -// } diff --git a/bin/migrate-up b/bin/migrate-up index 8699dd9..0bff1bb 100755 --- a/bin/migrate-up +++ b/bin/migrate-up @@ -5,6 +5,7 @@ var program = require('commander') var path = require('path') var migrate = require('../') var log = require('../lib/log') +var registerCompiler = require('../lib/register-compiler') var pkg = require('../package.json') program @@ -13,11 +14,17 @@ program .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) .option('-f, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) .option('-s, --store ', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) + .option('--compiler ', 'Use the given module to compile files') .parse(process.argv) // Change the working dir process.chdir(program.chdir) +// Load compiler +if (program.compiler) { + registerCompiler(program.compiler) +} + // Setup store var Store = require(program.store) var store = new Store(program.stateFile) diff --git a/lib/register-compiler.js b/lib/register-compiler.js new file mode 100644 index 0000000..8294383 --- /dev/null +++ b/lib/register-compiler.js @@ -0,0 +1,15 @@ +'use strict' +var path = require('path') + +module.exports = registerCompiler + +function registerCompiler (c) { + var compiler = c.split(':') + var ext = compiler[0] + var mod = compiler[1] + + if (mod[0] === '.') mod = path.join(process.cwd(), mod) + require(mod)({ + extensions: ['.' + ext] + }) +} From 60ab43172d2b1bfaefac225c96cf9dd1d7106ec8 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 15 Apr 2017 15:06:20 -0500 Subject: [PATCH 14/29] removed options not used at root --- bin/migrate | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/migrate b/bin/migrate index 1382ca2..1d9f63e 100755 --- a/bin/migrate +++ b/bin/migrate @@ -2,13 +2,10 @@ 'use strict' var program = require('commander') -var path = require('path') var pkg = require('../package.json') program .version(pkg.version) - .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) - .option('-s, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) .command('init') .command('create [name]', 'Create a new migration') .command('up [name]', 'Migrate up to a give migration', {isDefault: true}) From 2a77217b0a7a50427e7522d88965a4d10384b8ba Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Mon, 17 Apr 2017 18:53:53 -0500 Subject: [PATCH 15/29] more robust errors on create command --- bin/migrate-create | 19 +++++++++++++++++-- lib/log.js | 4 ++++ test/cli.js | 39 +++++++++++++++++++++++++++++---------- test/util/run.js | 8 +------- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/bin/migrate-create b/bin/migrate-create index 6d9f3ef..de48008 100755 --- a/bin/migrate-create +++ b/bin/migrate-create @@ -16,16 +16,26 @@ program .option('-d, --date-format [format]', 'Set a date format to use', 'x') .option('-t, --template-file [filePath]', 'Set path to template file to use for new migrations', path.join(__dirname, '..', 'lib', 'template.js')) .option('-e, --extention [extention]', 'Use the given extention to create the file', '.js') - .arguments('[name]') + .arguments('') .action(create) .parse(process.argv) +var _name function create (name) { + // Name provided? + _name = name + // Change the working dir process.chdir(program.chdir) // Load template file - var template = fs.readFileSync(program.templateFile) + var template + try { + template = fs.readFileSync(program.templateFile) + } catch (e) { + log.error('error loading template ' + program.templateFile, e.message) + process.exit(1) + } // Create date string var now = moment().format(program.dateFormat) @@ -37,3 +47,8 @@ function create (name) { mkdirp.sync(path.dirname(p)) fs.writeFileSync(p, template) } + +if (!_name) { + log.error('error', 'Migration name required') + log('usage', 'migrate create ') +} diff --git a/lib/log.js b/lib/log.js index 38c06a4..25ed549 100644 --- a/lib/log.js +++ b/lib/log.js @@ -4,3 +4,7 @@ var chalk = require('chalk') module.exports = function log (key, msg) { console.log(' ' + chalk.grey(key) + ' : ' + chalk.cyan(msg)) } + +module.exports.error = function log (key, msg) { + console.error(' ' + chalk.red(key) + ' : ' + chalk.white(msg)) +} diff --git a/test/cli.js b/test/cli.js index f431e39..9814bf1 100644 --- a/test/cli.js +++ b/test/cli.js @@ -36,8 +36,9 @@ describe('$ migrate', function () { beforeEach(mkdirp.bind(mkdirp, TMP_DIR)) it('should create a migrations directory', function (done) { - init([], function (err, out) { + init([], function (err, out, code) { assert(!err) + assert.equal(code, 0) assert(out.indexOf('init') !== -1) assert.doesNotThrow(() => { fs.accessSync(path.join(TMP_DIR, 'migrations')) @@ -51,8 +52,9 @@ describe('$ migrate', function () { beforeEach(mkdirp.bind(mkdirp, TMP_DIR)) it('should create a fixture file', function (done) { - create(['test'], function (err, out) { + create(['test'], function (err, out, code) { assert(!err) + assert.equal(code, 0) assert(out.indexOf('create') !== -1) var file = out.split(':')[1].trim() var content = fs.readFileSync(file, { @@ -70,8 +72,9 @@ describe('$ migrate', function () { var fmt = 'YYYY-MM-DD' var now = moment().format(fmt) - create([name, '-d', fmt], function (err, out) { + create([name, '-d', fmt], function (err, out, code) { assert(!err) + assert.equal(code, 0) assert.doesNotThrow(() => { fs.accessSync(path.join(TMP_DIR, 'migrations', now + '-' + name + '.js')) }) @@ -85,20 +88,31 @@ describe('$ migrate', function () { var ext = '.mjs' var now = moment().format(fmt) - create([name, '-d', fmt, '-e', ext], function (err, out) { + create([name, '-d', fmt, '-e', ext], function (err, out, code) { assert(!err) + assert.equal(code, 0) assert.doesNotThrow(() => { fs.accessSync(path.join(TMP_DIR, 'migrations', now + '-' + name + ext)) }) done() }) }) + + it('should fail with non-zero and a helpful message when template is unreadable', function (done) { + create(['test', '-t', 'fake'], function (err, out, code) { + assert(!err) + assert.equal(code, 1) + assert(out.indexOf('fake') !== -1) + done() + }) + }) }) // end create describe('up', function () { it('should run up on multiple migrations', function (done) { - up([], function (err, out) { + up([], function (err, out, code) { assert(!err) + assert.equal(code, 0) db.load() assert(out.indexOf('up') !== -1) assert.equal(db.numbers.length, 2) @@ -109,8 +123,9 @@ describe('$ migrate', function () { }) it('should run up to a specified migration', function (done) { - up(['1-one.js'], function (err, out) { + up(['1-one.js'], function (err, out, code) { assert(!err) + assert.equal(code, 0) db.load() assert(out.indexOf('up') !== -1) assert.equal(db.numbers.length, 1) @@ -121,8 +136,9 @@ describe('$ migrate', function () { }) it('should run up multiple times', function (done) { - up([], function (err, out) { + up([], function (err, out, code) { assert(!err) + assert.equal(code, 0) db.load() assert(out.indexOf('up') !== -1) up([], function (err, out) { @@ -140,8 +156,9 @@ describe('$ migrate', function () { up([], done) }) it('should run down on multiple migrations', function (done) { - down([], function (err, out) { + down([], function (err, out, code) { assert(!err) + assert.equal(code, 0) db.load() assert(out.indexOf('down') !== -1) assert.equal(db.numbers.length, 0) @@ -152,8 +169,9 @@ describe('$ migrate', function () { }) it('should run down to a specified migration', function (done) { - down(['2-two.js'], function (err, out) { + down(['2-two.js'], function (err, out, code) { assert(!err) + assert.equal(code, 0) db.load() assert(out.indexOf('down') !== -1) assert.equal(db.numbers.length, 1) @@ -164,8 +182,9 @@ describe('$ migrate', function () { }) it('should run down multiple times', function (done) { - down([], function (err, out) { + down([], function (err, out, code) { assert(!err) + assert.equal(code, 0) assert(out.indexOf('down') !== -1) db.load() down([], function (err, out) { diff --git a/test/util/run.js b/test/util/run.js index fa681f1..aff7d36 100644 --- a/test/util/run.js +++ b/test/util/run.js @@ -1,10 +1,8 @@ 'use strict' const spawn = require('child_process').spawn -const assert = require('assert') module.exports = function run (cmd, dir, args, done) { var p = spawn(cmd, ['-c', dir, ...args]) - var out = '' p.stdout.on('data', function (d) { out += d.toString('utf8') @@ -14,10 +12,6 @@ module.exports = function run (cmd, dir, args, done) { }) p.on('error', done) p.on('close', function (code) { - if (code !== 0) { - console.log(out) - } - assert.equal(code, 0) - done(null, out) + done(null, out, code) }) } From 21894e46a5272a3848f0850ae6f0fed6f3ba6066 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 25 Aug 2017 10:15:03 -0500 Subject: [PATCH 16/29] Major refactors - Cleaned up main export - Added --clean - Added migrate list - Switched to timestamp and title migration tracking - Moved the migrations file out of the migrations directory - Added store init method - Added generator template option --- bin/migrate | 4 +- bin/migrate-create | 43 ++++++----- bin/migrate-down | 40 ++++++---- bin/migrate-init | 20 ++++- bin/migrate-list | 57 +++++++++++++++ bin/migrate-up | 93 ++++++++++++++++++++---- index.js | 39 +++++++--- lib/file-store.js | 6 +- lib/load-migrations.js | 4 +- lib/migrate.js | 69 ++++++++++++++---- lib/migration.js | 4 +- lib/set.js | 46 +++++++++--- lib/template-generator.js | 51 +++++++++++++ package.json | 5 +- test/basic.js | 25 ++++--- test/cli.js | 33 ++++++++- test/fixtures/basic/1-add-guy-ferrets.js | 2 + test/fixtures/numbers/.gitkeep | 0 test/issue-33.js | 10 ++- test/util/run.js | 3 + 20 files changed, 444 insertions(+), 110 deletions(-) create mode 100755 bin/migrate-list create mode 100644 lib/template-generator.js create mode 100644 test/fixtures/numbers/.gitkeep diff --git a/bin/migrate b/bin/migrate index 1d9f63e..77b2f76 100755 --- a/bin/migrate +++ b/bin/migrate @@ -1,4 +1,5 @@ #!/usr/bin/env node +// vim: set ft=javascript: 'use strict' var program = require('commander') @@ -6,7 +7,8 @@ var pkg = require('../package.json') program .version(pkg.version) - .command('init') + .command('init', 'Initalize the migrations tool in a project') + .command('list', 'List migrations and their status') .command('create [name]', 'Create a new migration') .command('up [name]', 'Migrate up to a give migration', {isDefault: true}) .command('down [name]', 'Migrate down to a given migration') diff --git a/bin/migrate-create b/bin/migrate-create index de48008..b3faaa1 100755 --- a/bin/migrate-create +++ b/bin/migrate-create @@ -1,21 +1,22 @@ #!/usr/bin/env node +// vim: set ft=javascript: 'use strict' var program = require('commander') -var slug = require('slug') -var moment = require('moment') -var mkdirp = require('mkdirp') var path = require('path') -var fs = require('fs') var log = require('../lib/log') +var registerCompiler = require('../lib/register-compiler') var pkg = require('../package.json') program .version(pkg.version) .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) + .option('--migrations-dir ', 'Change the migrations directory name', 'migrations') + .option('--compiler ', 'Use the given module to compile files') .option('-d, --date-format [format]', 'Set a date format to use', 'x') .option('-t, --template-file [filePath]', 'Set path to template file to use for new migrations', path.join(__dirname, '..', 'lib', 'template.js')) .option('-e, --extention [extention]', 'Use the given extention to create the file', '.js') + .option('-g, --generator ', 'A template generator function', path.join(__dirname, '..', 'lib', 'template-generator')) .arguments('') .action(create) .parse(process.argv) @@ -28,24 +29,26 @@ function create (name) { // Change the working dir process.chdir(program.chdir) - // Load template file - var template - try { - template = fs.readFileSync(program.templateFile) - } catch (e) { - log.error('error loading template ' + program.templateFile, e.message) - process.exit(1) + // Load compiler + if (program.compiler) { + registerCompiler(program.compiler) } - // Create date string - var now = moment().format(program.dateFormat) - - // Create file path - var p = path.join(process.cwd(), 'migrations', slug(now + (name ? '-' + name : '')) + program.extention) - - log('create', p) - mkdirp.sync(path.dirname(p)) - fs.writeFileSync(p, template) + // Load the template generator + var gen = require(program.generator) + gen({ + name: name, + dateFormat: program.dateFormat, + templateFile: program.templateFile, + migrationsDirectory: program.migrationsDir, + extention: program.extention + }, function (err, p) { + if (err) { + log.error('Template generation error', err.message) + process.exit(1) + } + log('create', p) + }) } if (!_name) { diff --git a/bin/migrate-down b/bin/migrate-down index 94da554..82ca467 100755 --- a/bin/migrate-down +++ b/bin/migrate-down @@ -1,9 +1,12 @@ #!/usr/bin/env node +// vim: set ft=javascript: 'use strict' var program = require('commander') var path = require('path') +var minimatch = require('minimatch') var migrate = require('../') +var runMigrations = require('../lib/migrate') var log = require('../lib/log') var registerCompiler = require('../lib/register-compiler') var pkg = require('../package.json') @@ -11,9 +14,11 @@ var pkg = require('../package.json') program .version(pkg.version) .usage('[options] ') - .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) - .option('-f, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) + .option('-c, --chdir ', 'Change the working directory', process.cwd()) + .option('-f, --state-file ', 'Set path to state file', '.migrate') .option('-s, --store ', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) + .option('--migrations-dir ', 'Change the migrations directory name', 'migrations') + .option('--matches ', 'A glob pattern to filter migration files', '*') .option('--compiler ', 'Use the given module to compile files') .parse(process.argv) @@ -29,19 +34,28 @@ if (program.compiler) { var Store = require(program.store) var store = new Store(program.stateFile) -// Load migrations -var set = migrate.load(store, 'migrations') - -set.on('migration', function (migration, direction) { - log('down', migration.title) -}) - -set.down(program.args[0], function (err) { +// Load in migrations +migrate.load({ + stateStore: store, + migrationsDirectory: program.migrationsDir, + filterFunction: minimatch.filter(program.matches) +}, function (err, set) { if (err) { - log('error', err) + log.error('error', err) process.exit(1) } - log('migration', 'complete') - process.exit(0) + set.on('migration', function (migration, direction) { + log(direction, migration.title) + }) + + runMigrations(set, 'down', program.args[0], function (err) { + if (err) { + log('error', err) + process.exit(1) + } + + log('migration', 'complete') + process.exit(0) + }) }) diff --git a/bin/migrate-init b/bin/migrate-init index b2960f7..e809a1a 100755 --- a/bin/migrate-init +++ b/bin/migrate-init @@ -1,4 +1,5 @@ #!/usr/bin/env node +// vim: set ft=javascript: 'use strict' var program = require('commander') @@ -9,14 +10,29 @@ var pkg = require('../package.json') program .version(pkg.version) + .option('-f, --state-file ', 'Set path to state file', '.migrate') + .option('-s, --store ', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) + .option('--migrations-dir ', 'Change the migrations directory name', 'migrations') .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) .parse(process.argv) // Change the working dir process.chdir(program.chdir) +// Setup store +var Store = require(program.store) +var store = new Store(program.stateFile) + // Create migrations dir path -var p = path.join(process.cwd(), 'migrations') +var p = path.join(process.cwd(), program.migrationsDir) -log('init', p) +log('migrations dir', p) mkdirp.sync(p) + +// Call store init +if (typeof store.init === 'function') { + store.init(function (err) { + if (err) return log.error(err) + log('init') + }) +} diff --git a/bin/migrate-list b/bin/migrate-list new file mode 100755 index 0000000..47da0dd --- /dev/null +++ b/bin/migrate-list @@ -0,0 +1,57 @@ +#!/usr/bin/env node +// vim: set ft=javascript: +'use strict' + +var program = require('commander') +var path = require('path') +var moment = require('moment') +var minimatch = require('minimatch') +var migrate = require('../') +var log = require('../lib/log') +var registerCompiler = require('../lib/register-compiler') +var pkg = require('../package.json') + +program + .version(pkg.version) + .usage('[options] ') + .option('-c, --chdir ', 'Change the working directory', process.cwd()) + .option('-f, --state-file ', 'Set path to state file', '.migrate') + .option('-s, --store ', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) + .option('--migrations-dir ', 'Change the migrations directory name', 'migrations') + .option('--matches ', 'A glob pattern to filter migration files', '*') + .option('--compiler ', 'Use the given module to compile files') + .parse(process.argv) + +// Check clean flag, exit if NODE_ENV === 'production' and force not specified +if (program.clean && process.env.NODE_ENV === 'production' && !program.force) { + log.error('error', 'Cowardly refusing to clean while node environment set to production, use --force to continue.') + process.exit(1) +} + +// Change the working dir +process.chdir(program.chdir) + +// Load compiler +if (program.compiler) { + registerCompiler(program.compiler) +} + +// Setup store +var Store = require(program.store) +var store = new Store(program.stateFile) + +// Load in migrations +migrate.load({ + stateStore: store, + migrationsDirectory: program.migrationsDir, + filterFunction: minimatch.filter(program.matches) +}, function (err, set) { + if (err) { + log.error('error', err) + process.exit(1) + } + + set.migrations.forEach(function (migration) { + log(migration.title + (migration.timestamp ? ' [' + moment(migration.timestamp).format('YYYY-MM-DD') + ']' : ' [not run]'), migration.description || '') + }) +}) diff --git a/bin/migrate-up b/bin/migrate-up index 0bff1bb..fc333ef 100755 --- a/bin/migrate-up +++ b/bin/migrate-up @@ -1,9 +1,12 @@ #!/usr/bin/env node +// vim: set ft=javascript: 'use strict' var program = require('commander') var path = require('path') +var minimatch = require('minimatch') var migrate = require('../') +var runMigrations = require('../lib/migrate') var log = require('../lib/log') var registerCompiler = require('../lib/register-compiler') var pkg = require('../package.json') @@ -11,12 +14,29 @@ var pkg = require('../package.json') program .version(pkg.version) .usage('[options] ') - .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) - .option('-f, --state-file [path]', 'Set path to state file', path.join('migrations', '.migrate')) + .option('-c, --chdir ', 'Change the working directory', process.cwd()) + .option('-f, --state-file ', 'Set path to state file', '.migrate') .option('-s, --store ', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) + .option('--clean', 'Tears down the migration state before running up') + .option('--force', 'Force through the command, ignoring warnings') + .option('--init', 'Runs init for the store') + .option('--migrations-dir ', 'Change the migrations directory name', 'migrations') + .option('--matches ', 'A glob pattern to filter migration files', '*') .option('--compiler ', 'Use the given module to compile files') .parse(process.argv) +// Check clean flag, exit if NODE_ENV === 'production' and force not specified +if (program.clean && process.env.NODE_ENV === 'production' && !program.force) { + log.error('error', 'Cowardly refusing to clean while node environment set to production, use --force to continue.') + process.exit(1) +} + +// Check init flag, exit if NODE_ENV === 'production' and force not specified +if (program.init && process.env.NODE_ENV === 'production' && !program.force) { + log.error('error', 'Cowardly refusing to init while node environment set to production, use --force to continue.') + process.exit(1) +} + // Change the working dir process.chdir(program.chdir) @@ -29,19 +49,62 @@ if (program.compiler) { var Store = require(program.store) var store = new Store(program.stateFile) -// Load migrations -var set = migrate.load(store, 'migrations') +// Call store init +if (program.init && typeof store.init === 'function') { + store.init(function (err) { + if (err) return log.error(err) + loadAndGo() + }) +} else { + loadAndGo() +} + +// Load in migrations +function loadAndGo () { + migrate.load({ + stateStore: store, + migrationsDirectory: program.migrationsDir, + filterFunction: minimatch.filter(program.matches) + }, function (err, set) { + if (err) { + log.error('error', err) + process.exit(1) + } -set.on('migration', function (migration, direction) { - log('up', migration.title) -}) + set.on('warning', function (msg) { + log('warning', msg) + }) -set.up(program.args[0], function (err) { - if (err) { - log('error', err) - process.exit(1) - } + set.on('migration', function (migration, direction) { + log(direction, migration.title) + }) - log('migration', 'complete') - process.exit(0) -}) + // Run + ;(program.clean ? cleanUp : up)(set, function (err) { + if (err) { + log('error', err) + process.exit(1) + } + log('migration', 'complete') + process.exit(0) + }) + }) +} + +function cleanUp (set, fn) { + runMigrations(set, 'down', null, function (err) { + if (err) { + return fn(err) + } + up(set, fn) + }) +} + +function up (set, fn) { + runMigrations(set, 'up', program.args[0], function (err) { + if (err) { + return fn(err) + } + fn() + }) +} diff --git a/index.js b/index.js index 11cc026..3d04988 100644 --- a/index.js +++ b/index.js @@ -36,21 +36,36 @@ function migrate (title, up, down) { } } -exports.load = function (stateFile, migrationsDirectory) { - // Create store - var store - if (typeof stateFile === 'string') { - store = new FileStore(stateFile) - } else { - store = stateFile +/** + * Expose MigrationSet + */ +exports.MigrationSet = MigrationSet + +exports.load = function (options, fn) { + var opts = options || {} + + // Create default store + var store = (typeof opts.stateStore === 'string') ? new FileStore(opts.stateStore) : opts.stateStore + + // Default migrations directory + var dir = opts.migrationsDirectory || 'migrations' + + // Default filter function + var filter = opts.filterFunction || function (file) { + return file.match(/^\d+.*\.js$/) } + // Create migration set var set = new MigrationSet(store) - // Load directory into set - loadMigrationsIntoSet(set, migrationsDirectory, function (file) { - return file.match(/^\d+.*\.js$/) - }) + // Load state information + set.load(function (err) { + if (err) return fn(err) + + // Load directory into set + loadMigrationsIntoSet(set, dir, filter) - return set + // set loaded + fn(null, set) + }) } diff --git a/lib/file-store.js b/lib/file-store.js index 1737485..0763f58 100644 --- a/lib/file-store.js +++ b/lib/file-store.js @@ -14,7 +14,7 @@ function FileStore (path) { FileStore.prototype.save = function (set, fn) { fs.writeFile(this.path, JSON.stringify({ - pos: set.pos, + lastRun: set.lastRun, migrations: set.migrations }), fn) } @@ -29,9 +29,9 @@ FileStore.prototype.save = function (set, fn) { FileStore.prototype.load = function (fn) { fs.readFile(this.path, 'utf8', function (err, json) { - if (err) return fn(err) + if (err && err.code !== 'ENOENT') return fn(err) if (!json || json === '') { - return fn(null, this) + return fn(null, {}) } try { fn(null, JSON.parse(json)) diff --git a/lib/load-migrations.js b/lib/load-migrations.js index 0e51d9f..2aa7fdb 100644 --- a/lib/load-migrations.js +++ b/lib/load-migrations.js @@ -2,6 +2,7 @@ var path = require('path') var fs = require('fs') +var Migration = require('./migration') module.exports = loadMigrationsIntoSet @@ -18,6 +19,7 @@ function loadMigrationsIntoSet (set, migrationsDirectory, filter, sort) { // Load the migrations into the set files.forEach(function (file) { var mod = require(path.join(dir, file)) - set.addMigration(file, mod.up, mod.down) + var migration = new Migration(file, mod.up, mod.down, mod.description) + set.addMigration(migration) }) } diff --git a/lib/migrate.js b/lib/migrate.js index 520365b..4a86724 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -3,32 +3,39 @@ module.exports = migrate function migrate (set, direction, migrationName, fn) { - var migrations - var migrationPos + var migrations = [] + var lastRunIndex + var toIndex if (!migrationName) { - migrationPos = direction === 'up' ? set.migrations.length : 0 - } else if ((migrationPos = positionOfMigration(set.migrations, migrationName)) === -1) { + toIndex = direction === 'up' ? set.migrations.length : 0 + } else if ((toIndex = positionOfMigration(set.migrations, migrationName)) === -1) { return fn(new Error('Could not find migration: ' + migrationName)) } - switch (direction) { - case 'up': - migrations = set.migrations.slice(set.pos, migrationPos + 1) - break - case 'down': - migrations = set.migrations.slice(migrationPos, set.pos).reverse() - break - } + lastRunIndex = positionOfMigration(set.migrations, set.lastRun) + migrations = (direction === 'up' ? upMigrations : downMigrations)(set, lastRunIndex, toIndex) function next (migration) { + // Done running migrations if (!migration) return fn(null) + // Missing direction method + if (typeof migration[direction] !== 'function') { + return fn(new TypeError('Migration ' + migration.title + ' does not have method ' + direction)) + } + set.emit('migration', migration, direction) migration[direction](function (err) { if (err) return fn(err) - set.pos += (direction === 'up' ? 1 : -1) + // Set timestamp if running up, clear it if down + migration.timestamp = direction === 'up' ? Date.now() : null + + // Decrement last run index + lastRunIndex-- + + set.lastRun = direction === 'up' ? migration.title : set.migrations[lastRunIndex] && set.migrations[lastRunIndex].title set.save(function (err) { if (err) return fn(err) @@ -40,15 +47,47 @@ function migrate (set, direction, migrationName, fn) { next(migrations.shift()) } +function upMigrations (set, lastRunIndex, toIndex) { + return set.migrations.reduce(function (arr, migration, index) { + if (index > toIndex) { + return arr + } + + if (index < lastRunIndex && !migration.timestamp) { + set.emit('warning', 'migrations running out of order') + } + + if (!migration.timestamp) { + arr.push(migration) + } + + return arr + }, []) +} + +function downMigrations (set, lastRunIndex, toIndex) { + return set.migrations.reduce(function (arr, migration, index) { + if (index < toIndex || index > lastRunIndex) { + return arr + } + + if (migration.timestamp) { + arr.push(migration) + } + + return arr + }, []).reverse() +} + /** * Get index of given migration in list of migrations * * @api private */ -function positionOfMigration (migrations, filename) { +function positionOfMigration (migrations, title) { for (var i = 0; i < migrations.length; ++i) { - if (migrations[i].title === filename) return i + if (migrations[i].title === title) return i } return -1 } diff --git a/lib/migration.js b/lib/migration.js index 130ae6e..02a408c 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -12,8 +12,10 @@ module.exports = Migration -function Migration (title, up, down) { +function Migration (title, up, down, description) { this.title = title this.up = up this.down = down + this.description = description + this.timestamp = null } diff --git a/lib/set.js b/lib/set.js index 2eead87..9d2f620 100644 --- a/lib/set.js +++ b/lib/set.js @@ -32,7 +32,8 @@ module.exports = MigrationSet function MigrationSet (store) { this.store = store this.migrations = [] - this.pos = 0 + this.map = {} + this.lastRun = null }; /** @@ -51,7 +52,23 @@ inherits(MigrationSet, EventEmitter) */ MigrationSet.prototype.addMigration = function (title, up, down) { - this.migrations.push(new Migration(title, up, down)) + var migration + if (!(title instanceof Migration)) { + migration = new Migration(title, up, down) + } else { + migration = title + } + + // Only add the migration once, but update + if (this.map[migration.title]) { + this.map[migration.title].up = migration.up + this.map[migration.title].down = migration.down + this.map[migration.title].description = migration.description + return + } + + this.migrations.push(migration) + this.map[migration.title] = migration } /** @@ -78,7 +95,21 @@ MigrationSet.prototype.save = function (fn) { MigrationSet.prototype.load = function (fn) { this.emit('load') - this.store.load(fn) + this.store.load((err, state) => { + if (err) return fn(err) + + // Set current position + this.lastRun = state.lastRun || null + + // Add migrations + state.migrations && state.migrations.forEach((m) => { + var migration = new Migration(m.title) + migration.timestamp = m.timestamp + this.addMigration(migration) + }) + + fn(null, this) + }) } /** @@ -116,12 +147,5 @@ MigrationSet.prototype.migrate = function (direction, migrationName, fn) { fn = migrationName migrationName = null } - this.load((err, obj) => { - if (err) { - if (err.code !== 'ENOENT') return fn(err) - } else { - this.pos = obj.pos - } - migrate(this, direction, migrationName, fn) - }) + migrate(this, direction, migrationName, fn) } diff --git a/lib/template-generator.js b/lib/template-generator.js new file mode 100644 index 0000000..0d9dc23 --- /dev/null +++ b/lib/template-generator.js @@ -0,0 +1,51 @@ +'use strict' +var path = require('path') +var fs = require('fs') +var slug = require('slug') +var moment = require('moment') +var mkdirp = require('mkdirp') + +module.exports = function templateGenerator (opts, cb) { + // Setup default options + opts = opts || {} + var name = opts.name + var dateFormat = opts.dateFormat || 'X' + var templateFile = opts.templateFile || path.join(__dirname, 'template.js') + var migrationsDirectory = opts.migrationsDirectory || 'migrations' + var extention = opts.extention || '.js' + + loadTemplate(templateFile, function (err, template) { + if (err) return cb(err) + + // Ensure migrations directory exists + mkdirp(migrationsDirectory, function (err) { + if (err) return cb(err) + + // Create date string + var now = moment().format(dateFormat) + + // Fix up file path + var p = path.join(process.cwd(), migrationsDirectory, slug(now + (name ? '-' + name : '')) + extention) + + // Write the template file + fs.writeFile(p, template, function (err) { + if (err) return cb(err) + cb(null, p) + }) + }) + }) +} + +var _templateCache = {} +function loadTemplate (tmpl, cb) { + if (_templateCache[tmpl]) { + return cb(null, _templateCache) + } + fs.readFile(tmpl, { + encoding: 'utf8' + }, function (err, content) { + if (err) return cb(err) + _templateCache[tmpl] = content + cb(null, content) + }) +} diff --git a/package.json b/package.json index ab1031c..79ae6e8 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "bin": { "migrate": "./bin/migrate", "migrate-init": "./bin/migrate-init", + "migrate-list": "./bin/migrate-list", "migrate-create": "./bin/migrate-create", "migrate-up": "./bin/migrate-up", "migrate-down": "./bin/migrate-down" @@ -25,14 +26,14 @@ "node": ">= 0.4.x" }, "scripts": { - "test": "standard --fix && standard --fix ./bin/* && mocha" + "test": "standard && standard ./bin/* && mocha" }, "license": "MIT", "dependencies": { "chalk": "^1.1.3", "commander": "^2.9.0", - "dateformat": "^1.0.12", "inherits": "^2.0.3", + "minimatch": "^3.0.3", "mkdirp": "^0.5.1", "moment": "^2.18.1", "slug": "^0.9.1" diff --git a/test/basic.js b/test/basic.js index 1277e7f..824ef0b 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,6 +1,6 @@ /* global describe, it, beforeEach, afterEach */ -var fs = require('fs') +var rimraf = require('rimraf') var path = require('path') var assert = require('assert') @@ -15,14 +15,12 @@ describe('migration set', function () { function assertNoPets () { assert.equal(db.pets.length, 0) - assert.equal(set.pos, 0) } function assertPets () { assert.equal(db.pets.length, 3) assert.equal(db.pets[0].name, 'tobi') assert.equal(db.pets[0].email, 'tobi@learnboost.com') - assert.equal(set.pos, 3) } function assertPetsWithDogs () { @@ -36,7 +34,6 @@ describe('migration set', function () { assert.equal(db.pets.length, 2) assert.equal(db.pets[0].name, 'tobi') assert.equal(db.pets[1].name, 'loki') - assert.equal(set.pos, 1) } function assertSecondMigration () { @@ -44,11 +41,16 @@ describe('migration set', function () { assert.equal(db.pets[0].name, 'tobi') assert.equal(db.pets[1].name, 'loki') assert.equal(db.pets[2].name, 'jane') - assert.equal(set.pos, 2) } - beforeEach(function () { - set = migrate.load(STATE, BASE) + beforeEach(function (done) { + migrate.load({ + stateStore: STATE, + migrationsDirectory: BASE + }, function (err, s) { + set = s + done(err) + }) }) it('should handle basic migration', function (done) { @@ -165,9 +167,10 @@ describe('migration set', function () { set.up('2-add-girl-ferrets.js', function (err) { assert.ifError(err) assertSecondMigration() + assert.equal(set.lastRun, '2-add-girl-ferrets.js') set.down('2-add-girl-ferrets.js', function (err) { assert.ifError(err) - assert.equal(set.pos, 1) + assert.equal(set.lastRun, '1-add-guy-ferrets.js') done() }) }) @@ -176,8 +179,12 @@ describe('migration set', function () { }) }) + it('should load migration descriptions', function () { + assert.equal(set.migrations[0].description, 'Adds two pets') + }) + afterEach(function (done) { db.nuke() - fs.unlink(STATE, done) + rimraf(STATE, done) }) }) diff --git a/test/cli.js b/test/cli.js index 9814bf1..71be4af 100644 --- a/test/cli.js +++ b/test/cli.js @@ -15,15 +15,17 @@ const UP = path.join(__dirname, '..', 'bin', 'migrate-up') const DOWN = path.join(__dirname, '..', 'bin', 'migrate-down') const CREATE = path.join(__dirname, '..', 'bin', 'migrate-create') const INIT = path.join(__dirname, '..', 'bin', 'migrate-init') +const LIST = path.join(__dirname, '..', 'bin', 'migrate-list') // Run helper const up = run.bind(null, UP, FIX_DIR) const down = run.bind(null, DOWN, FIX_DIR) const create = run.bind(null, CREATE, TMP_DIR) const init = run.bind(null, INIT, TMP_DIR) +const list = run.bind(null, LIST, FIX_DIR) function reset () { - rimraf.sync(path.join(FIX_DIR, 'migrations', '.migrate')) + rimraf.sync(path.join(FIX_DIR, '.migrate')) rimraf.sync(TMP_DIR) db.nuke() } @@ -39,7 +41,6 @@ describe('$ migrate', function () { init([], function (err, out, code) { assert(!err) assert.equal(code, 0) - assert(out.indexOf('init') !== -1) assert.doesNotThrow(() => { fs.accessSync(path.join(TMP_DIR, 'migrations')) }) @@ -55,7 +56,6 @@ describe('$ migrate', function () { create(['test'], function (err, out, code) { assert(!err) assert.equal(code, 0) - assert(out.indexOf('create') !== -1) var file = out.split(':')[1].trim() var content = fs.readFileSync(file, { encoding: 'utf8' @@ -149,6 +149,21 @@ describe('$ migrate', function () { }) }) }) + + it('should run down when passed --clean', function (done) { + up([], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + up(['--clean'], function (err, out) { + assert(!err) + db.load() + assert(out.indexOf('down') !== -1) + assert(out.indexOf('up') !== -1) + assert.equal(db.numbers.length, 2) + done() + }) + }) + }) }) // end up describe('down', function () { @@ -196,4 +211,16 @@ describe('$ migrate', function () { }) }) }) // end down + + describe('list', function () { + it('should list available migrations', function (done) { + list([], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + assert(out.indexOf('1-one.js') !== -1) + assert(out.indexOf('2-two.js') !== -1) + done() + }) + }) + }) // end init }) diff --git a/test/fixtures/basic/1-add-guy-ferrets.js b/test/fixtures/basic/1-add-guy-ferrets.js index 0ca18e4..d328485 100644 --- a/test/fixtures/basic/1-add-guy-ferrets.js +++ b/test/fixtures/basic/1-add-guy-ferrets.js @@ -1,6 +1,8 @@ var db = require('../../util/db') +exports.description = 'Adds two pets' + exports.up = function (next) { db.pets.push({ name: 'tobi' }) db.pets.push({ name: 'loki' }) diff --git a/test/fixtures/numbers/.gitkeep b/test/fixtures/numbers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/issue-33.js b/test/issue-33.js index bedc8de..db86079 100644 --- a/test/issue-33.js +++ b/test/issue-33.js @@ -16,8 +16,14 @@ var A2 = A1.concat(['3-down', '2-down', '1-down']) describe('issue #33', function () { var set - beforeEach(function () { - set = migrate.load(STATE, BASE) + beforeEach(function (done) { + migrate.load({ + stateStore: STATE, + migrationsDirectory: BASE + }, function (err, s) { + set = s + done(err) + }) }) it('should run migrations in the correct order', function (done) { diff --git a/test/util/run.js b/test/util/run.js index aff7d36..86a8ed7 100644 --- a/test/util/run.js +++ b/test/util/run.js @@ -12,6 +12,9 @@ module.exports = function run (cmd, dir, args, done) { }) p.on('error', done) p.on('close', function (code) { + if (code !== 0) { + console.error(out) + } done(null, out, code) }) } From 62464e772c0ea9e657b5ff7f4821a7cbbc46103f Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 25 Aug 2017 10:35:33 -0500 Subject: [PATCH 17/29] added no migrations message to list --- bin/migrate-list | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/migrate-list b/bin/migrate-list index 47da0dd..bb65400 100755 --- a/bin/migrate-list +++ b/bin/migrate-list @@ -51,6 +51,10 @@ migrate.load({ process.exit(1) } + if (set.migrations.length === 0) { + return log('list', 'No Migrations') + } + set.migrations.forEach(function (migration) { log(migration.title + (migration.timestamp ? ' [' + moment(migration.timestamp).format('YYYY-MM-DD') + ']' : ' [not run]'), migration.description || '') }) From ffa1acfdbda0b50d3b88bc493e3ba9df9e4c9a96 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 25 Aug 2017 10:44:58 -0500 Subject: [PATCH 18/29] template generator option should be relative to current working directory --- bin/migrate-create | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/migrate-create b/bin/migrate-create index b3faaa1..effdbfe 100755 --- a/bin/migrate-create +++ b/bin/migrate-create @@ -35,7 +35,7 @@ function create (name) { } // Load the template generator - var gen = require(program.generator) + var gen = require(path.resolve(process.cwd(), program.generator)) gen({ name: name, dateFormat: program.dateFormat, From 19d84486134d3c03fafb63b01a6eb0099f716ed1 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 25 Aug 2017 11:03:59 -0500 Subject: [PATCH 19/29] Converted from moment to dateformat --- bin/migrate-create | 2 +- lib/template-generator.js | 8 ++++---- package.json | 2 +- test/cli.js | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bin/migrate-create b/bin/migrate-create index effdbfe..574800c 100755 --- a/bin/migrate-create +++ b/bin/migrate-create @@ -13,7 +13,7 @@ program .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) .option('--migrations-dir ', 'Change the migrations directory name', 'migrations') .option('--compiler ', 'Use the given module to compile files') - .option('-d, --date-format [format]', 'Set a date format to use', 'x') + .option('-d, --date-format [format]', 'Set a date format to use') .option('-t, --template-file [filePath]', 'Set path to template file to use for new migrations', path.join(__dirname, '..', 'lib', 'template.js')) .option('-e, --extention [extention]', 'Use the given extention to create the file', '.js') .option('-g, --generator ', 'A template generator function', path.join(__dirname, '..', 'lib', 'template-generator')) diff --git a/lib/template-generator.js b/lib/template-generator.js index 0d9dc23..cedf4eb 100644 --- a/lib/template-generator.js +++ b/lib/template-generator.js @@ -2,14 +2,14 @@ var path = require('path') var fs = require('fs') var slug = require('slug') -var moment = require('moment') +var formatDate = require('dateformat') var mkdirp = require('mkdirp') module.exports = function templateGenerator (opts, cb) { // Setup default options opts = opts || {} var name = opts.name - var dateFormat = opts.dateFormat || 'X' + var dateFormat = opts.dateFormat var templateFile = opts.templateFile || path.join(__dirname, 'template.js') var migrationsDirectory = opts.migrationsDirectory || 'migrations' var extention = opts.extention || '.js' @@ -22,10 +22,10 @@ module.exports = function templateGenerator (opts, cb) { if (err) return cb(err) // Create date string - var now = moment().format(dateFormat) + var formattedDate = dateFormat ? formatDate(new Date(), dateFormat) : Date.now() // Fix up file path - var p = path.join(process.cwd(), migrationsDirectory, slug(now + (name ? '-' + name : '')) + extention) + var p = path.join(process.cwd(), migrationsDirectory, slug(formattedDate + (name ? '-' + name : '')) + extention) // Write the template file fs.writeFile(p, template, function (err) { diff --git a/package.json b/package.json index 79ae6e8..67a7690 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,10 @@ "dependencies": { "chalk": "^1.1.3", "commander": "^2.9.0", + "dateformat": "^2.0.0", "inherits": "^2.0.3", "minimatch": "^3.0.3", "mkdirp": "^0.5.1", - "moment": "^2.18.1", "slug": "^0.9.1" } } diff --git a/test/cli.js b/test/cli.js index 71be4af..b9c7edb 100644 --- a/test/cli.js +++ b/test/cli.js @@ -4,7 +4,7 @@ const fs = require('fs') const assert = require('assert') const rimraf = require('rimraf') const mkdirp = require('mkdirp') -const moment = require('moment') +const formatDate = require('dateformat') const db = require('./util/db') const run = require('./util/run') @@ -69,8 +69,8 @@ describe('$ migrate', function () { it('should respect the --date-format', function (done) { var name = 'test' - var fmt = 'YYYY-MM-DD' - var now = moment().format(fmt) + var fmt = 'yyyy-mm-dd' + var now = formatDate(new Date(), fmt) create([name, '-d', fmt], function (err, out, code) { assert(!err) @@ -84,9 +84,9 @@ describe('$ migrate', function () { it('should respect the --extention', function (done) { var name = 'test' - var fmt = 'YYYY-MM-DD' + var fmt = 'yyyy-mm-dd' var ext = '.mjs' - var now = moment().format(fmt) + var now = formatDate(new Date(), fmt) create([name, '-d', fmt, '-e', ext], function (err, out, code) { assert(!err) From b1a400f1cdbb2bcf4b848790e266363fc1b9ce43 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Fri, 25 Aug 2017 11:18:20 -0500 Subject: [PATCH 20/29] format file store json --- lib/file-store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/file-store.js b/lib/file-store.js index 0763f58..210eb49 100644 --- a/lib/file-store.js +++ b/lib/file-store.js @@ -16,7 +16,7 @@ FileStore.prototype.save = function (set, fn) { fs.writeFile(this.path, JSON.stringify({ lastRun: set.lastRun, migrations: set.migrations - }), fn) + }, null, ' '), fn) } /** From 9052c0bdf75f5c9af97c4f7b9318cf9c5ddc8210 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 26 Aug 2017 10:36:02 -0500 Subject: [PATCH 21/29] refactor loading to take a more functional approach --- index.js | 25 +++++---------- lib/load-migrations.js | 71 +++++++++++++++++++++++++++++++++++------- lib/set.js | 27 ---------------- 3 files changed, 67 insertions(+), 56 deletions(-) diff --git a/index.js b/index.js index 3d04988..f52e191 100644 --- a/index.js +++ b/index.js @@ -47,25 +47,16 @@ exports.load = function (options, fn) { // Create default store var store = (typeof opts.stateStore === 'string') ? new FileStore(opts.stateStore) : opts.stateStore - // Default migrations directory - var dir = opts.migrationsDirectory || 'migrations' - - // Default filter function - var filter = opts.filterFunction || function (file) { - return file.match(/^\d+.*\.js$/) - } - // Create migration set var set = new MigrationSet(store) - // Load state information - set.load(function (err) { - if (err) return fn(err) - - // Load directory into set - loadMigrationsIntoSet(set, dir, filter) - - // set loaded - fn(null, set) + loadMigrationsIntoSet({ + set: set, + store: store, + migrationsDirectory: opts.migrationsDirectory, + filterFunction: opts.filterFunction, + sortFunction: opts.sortFunction + }, function (err) { + fn(err, set) }) } diff --git a/lib/load-migrations.js b/lib/load-migrations.js index 2aa7fdb..4e3bec8 100644 --- a/lib/load-migrations.js +++ b/lib/load-migrations.js @@ -6,20 +6,67 @@ var Migration = require('./migration') module.exports = loadMigrationsIntoSet -function loadMigrationsIntoSet (set, migrationsDirectory, filter, sort) { - var dir = path.resolve(migrationsDirectory) - var files = fs.readdirSync(dir) +function loadMigrationsIntoSet (options, fn) { + // Process options, set and store are required, rest optional + var opts = options || {} + if (!opts.set || !opts.store) { + throw new TypeError((opts.set ? 'store' : 'set') + ' is required for loading migrations') + } + var set = opts.set + var store = opts.store + var migrationsDirectory = path.resolve(opts.migrationsDirectory || 'migrations') + var filterFn = opts.filterFunction || (() => true) + var sortFn = opts.sortFunction || function (m1, m2) { + return m1.title > m2.title ? 1 : (m1.title < m2.title ? -1 : 0) + } - // Run filter - files = files.filter(filter || (() => true)) + // Load from migrations store first up + store.load(function (err, state) { + if (err) return fn(err) - // Run sort - files = files.sort(sort) + // Set last run date on the set + set.lastRun = state.lastRun || null - // Load the migrations into the set - files.forEach(function (file) { - var mod = require(path.join(dir, file)) - var migration = new Migration(file, mod.up, mod.down, mod.description) - set.addMigration(migration) + // Read migrations directory + fs.readdir(migrationsDirectory, function (err, files) { + if (err) return fn(err) + + // Filter out non-matching files + files = files.filter(filterFn) + + // Create migrations, keep a lookup map for the next step + var migMap = {} + var migrations = files.map(function (file) { + // Try to load the migrations file + var mod + try { + mod = require(path.join(migrationsDirectory, file)) + } catch (e) { + return fn(e) + } + + var migration = new Migration(file, mod.up, mod.down, mod.description) + migMap[file] = migration + return migration + }) + + // Fill in timestamp from state, or error if missing + state.migrations && state.migrations.forEach(function (m) { + if (!migMap[m.title]) { + // @TODO is this the best way to handle this? + return fn(new Error('Missing migration file: ' + m.title)) + } + migMap[m.title].timestamp = m.timestamp + }) + + // Sort the migrations by their title + migrations = migrations.sort(sortFn) + + // Add the migrations to the set + migrations.forEach(set.addMigration.bind(set)) + + // Successfully loaded + fn() + }) }) } diff --git a/lib/set.js b/lib/set.js index 9d2f620..4c8c3cc 100644 --- a/lib/set.js +++ b/lib/set.js @@ -85,33 +85,6 @@ MigrationSet.prototype.save = function (fn) { }) } -/** - * Load the migration data and call `fn(err, obj)`. - * - * @param {Function} fn - * @return {Type} - * @api public - */ - -MigrationSet.prototype.load = function (fn) { - this.emit('load') - this.store.load((err, state) => { - if (err) return fn(err) - - // Set current position - this.lastRun = state.lastRun || null - - // Add migrations - state.migrations && state.migrations.forEach((m) => { - var migration = new Migration(m.title) - migration.timestamp = m.timestamp - this.addMigration(migration) - }) - - fn(null, this) - }) -} - /** * Run down migrations and call `fn(err)`. * From 3dbe09eee3f3203475f341b9974d27a3477415b7 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 23 Sep 2017 11:15:16 -0500 Subject: [PATCH 22/29] fixed broken list command --- bin/migrate-list | 5 +++-- test/cli.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/migrate-list b/bin/migrate-list index bb65400..8088efd 100755 --- a/bin/migrate-list +++ b/bin/migrate-list @@ -4,7 +4,7 @@ var program = require('commander') var path = require('path') -var moment = require('moment') +var dateFormat = require('dateformat') var minimatch = require('minimatch') var migrate = require('../') var log = require('../lib/log') @@ -17,6 +17,7 @@ program .option('-c, --chdir ', 'Change the working directory', process.cwd()) .option('-f, --state-file ', 'Set path to state file', '.migrate') .option('-s, --store ', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) + .option('-d, --date-format [format]', 'Set a date format to use', 'yyyy-mm-dd') .option('--migrations-dir ', 'Change the migrations directory name', 'migrations') .option('--matches ', 'A glob pattern to filter migration files', '*') .option('--compiler ', 'Use the given module to compile files') @@ -56,6 +57,6 @@ migrate.load({ } set.migrations.forEach(function (migration) { - log(migration.title + (migration.timestamp ? ' [' + moment(migration.timestamp).format('YYYY-MM-DD') + ']' : ' [not run]'), migration.description || '') + log(migration.title + (migration.timestamp ? ' [' + dateFormat(migration.timestamp, program.dateFormat) + ']' : ' [not run]'), migration.description || '') }) }) diff --git a/test/cli.js b/test/cli.js index b9c7edb..26593a0 100644 --- a/test/cli.js +++ b/test/cli.js @@ -216,7 +216,7 @@ describe('$ migrate', function () { it('should list available migrations', function (done) { list([], function (err, out, code) { assert(!err) - assert.equal(code, 0) + assert.equal(code, 0, out) assert(out.indexOf('1-one.js') !== -1) assert(out.indexOf('2-two.js') !== -1) done() From 1ede40926b545bb01e0627af0e448a94740f3f26 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 23 Sep 2017 12:49:11 -0500 Subject: [PATCH 23/29] Updated readme with new feature docs --- LICENSE | 21 +++++ Readme.md | 270 ++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 193 insertions(+), 98 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ed68bac --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2011-2017 TJ Holowaychuk +Copyright (c) 2017 Wes Todd + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Readme.md b/Readme.md index 00f724b..347f1ea 100644 --- a/Readme.md +++ b/Readme.md @@ -1,8 +1,10 @@ -# migrate +# Migrate +[![NPM Version](https://img.shields.io/npm/v/migrate.svg)](https://npmjs.org/package/migrate) +[![NPM Downloads](https://img.shields.io/npm/dm/migrate.svg)](https://npmjs.org/package/migrate) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) - Abstract migration framework for node +Abstract migration framework for node. *NOTE:* A large refactor is underway for the next major version of this package. Check out the `1.x` pull request tracking this work, or to explicitly opt in, install the `next` tag (`npm install migrate@next`). @@ -17,124 +19,221 @@ Usage: migrate [options] [command] Options: - -c, --chdir change the working directory - --state-file set path to state file (migrations/.migrate) - --template-file set path to template file to use for new migrations - --date-format set a date format to use for new migration filenames + -V, --version output the version number + -h, --help output usage information Commands: - down [name] migrate down till given migration - up [name] migrate up till given migration (the default command) - create [title] create a new migration file with optional [title] - + init Initalize the migrations tool in a project + list List migrations and their status + create Create a new migration + up [name] Migrate up to a give migration + down [name] Migrate down to a given migration + help [cmd] display help for [cmd] ``` +For help with the individual commands, see `migrate help [cmd]`. Each command has some helpful flags +for customising the behavior of the tool. + ## Programmatic usage ```javascript -var migrate = require('migrate'); -var set = migrate.load('migration/.migrate', 'migration'); - -set.up(function (err) { - if (err) throw err; - - console.log('Migration completed'); -}); +var migrate = require('migrate') + +migrate.load({ + stateStore: '.migrate' +}, function (err, set) { + if (err) { + throw err + } + set.up(function (err) { + if (err) { + throw err + } + console.log('migrations successfully ran') + }) +}) ``` ## Creating Migrations -To create a migration, execute `migrate create` with an optional title. `node-migrate` will create a node module within `./migrations/` which contains the following two exports: +To create a migration, execute `migrate create ` with a title. By default, a file in `./migrations/` will be created with the following content: - exports.up = function(next){ - next(); - }; +```javascript +'use strict' + +module.exports.up = function (next) { + next() +} - exports.down = function(next){ - next(); - }; +module.exports.down = function (next) { + next() +} +``` All you have to do is populate these, invoking `next()` when complete, and you are ready to migrate! For example: - $ migrate create add-pets - $ migrate create add-owners +``` +$ migrate create add-pets +$ migrate create add-owners +``` The first call creates `./migrations/{timestamp in milliseconds}-add-pets.js`, which we can populate: - var db = require('./db'); +```javascript +// db is just an object shared between the migrations +var db = require('./db'); + +exports.up = function (next) { + db.pets = []; + db.pets.push('tobi') + db.pets.push('loki') + db.pets.push('jane') + next() +} + +exports.down = function (next) { + db.pets.pop('pets') + db.pets.pop('pets') + db.pets.pop('pets') + delete db.pets + next() +} +``` - exports.up = function(next){ - db.rpush('pets', 'tobi'); - db.rpush('pets', 'loki'); - db.rpush('pets', 'jane', next); - }; +The second creates `./migrations/{timestamp in milliseconds}-add-owners.js`, which we can populate: - exports.down = function(next){ - db.rpop('pets'); - db.rpop('pets'); - db.rpop('pets', next); - }; +```javascript +var db = require('./db'); + +exports.up = function (next) { + db.owners = []; + db.owners.push('taylor') + db.owners.push('tj', next) +} + +exports.down = function (next) { + db.owners.pop() + db.owners.pop() + delete db.owners + next() +} +``` -The second creates `./migrations/{timestamp in milliseconds}-add-owners.js`, which we can populate: +### Advanced migration creation + +When creating migrations you have a bunch of other options to help you control how the migrations +are created. You can fully configure the way the migration is made with a `generator`, which is just a +function exported as a node module. A good example of a generator is the default one [shipped with +this package](https://github.com/tj/node-migrate/blob/b282cacbb4c0e73631d651394da52396131dd5de/lib/template-generator.js). - var db = require('./db'); +The `create` command accepts a flag for pointing the tool at a generator, for example: - exports.up = function(next){ - db.rpush('owners', 'taylor'); - db.rpush('owners', 'tj', next); - }; +``` +$ migrate create --generator ./my-migrate-generator.js +``` - exports.down = function(next){ - db.rpop('owners'); - db.rpop('owners', next); - }; +A more simple and common thing you might want is to just change the default template file which is created. To do this, you +can simply pass the `template-file` flag: + +``` +$ migrate create --template-file ./my-migration-template.js +``` + +Lastly, if you want to use newer ECMAscript features, or language addons like TypeScript, for your migrations, you can +use the `comipler` flag. For example, to use babel with your migrations, you can do the following: + +``` +$ npm install --save babel-register +$ migrate create --compiler=".js:babel-register" foo +$ migrate up --compiler=".js:babel-register" +``` ## Running Migrations When first running the migrations, all will be executed in sequence. - $ migrate - up : migrations/1316027432511-add-pets.js - up : migrations/1316027432512-add-jane.js - up : migrations/1316027432575-add-owners.js - up : migrations/1316027433425-coolest-pet.js - migration : complete +``` +$ migrate + up : migrations/1316027432511-add-pets.js + up : migrations/1316027432512-add-jane.js + up : migrations/1316027432575-add-owners.js + up : migrations/1316027433425-coolest-pet.js + migration : complete +``` -Subsequent attempts will simply output "complete", as they have already been executed in this machine. `node-migrate` knows this because it stores the current state in `./migrations/.migrate` which is typically a file that SCMs like GIT should ignore. +Subsequent attempts will simply output "complete", as they have already been executed. `migrate` knows this because it stores the current state in +`./.migrate` which is typically a file that SCMs like GIT should ignore. - $ migrate - migration : complete +``` +$ migrate + migration : complete +``` If we were to create another migration using `migrate create`, and then execute migrations again, we would execute only those not previously executed: - $ migrate - up : migrates/1316027433455-coolest-owner.js +``` +$ migrate + up : migrates/1316027433455-coolest-owner.js +``` You can also run migrations incrementally by specifying a migration. - $ migrate up 1316027433425-coolest-pet.js - up : migrations/1316027432511-add-pets.js - up : migrations/1316027432512-add-jane.js - up : migrations/1316027432575-add-owners.js - up : migrations/1316027433425-coolest-pet.js - migration : complete +``` +$ migrate up 1316027433425-coolest-pet.js + up : migrations/1316027432511-add-pets.js + up : migrations/1316027432512-add-jane.js + up : migrations/1316027432575-add-owners.js + up : migrations/1316027433425-coolest-pet.js + migration : complete +``` + +This will run up-migrations up to (and including) `1316027433425-coolest-pet.js`. Similarly you can run down-migrations up to (and including) a +specific migration, instead of migrating all the way down. + +``` +$ migrate down 1316027432512-add-jane.js + down : migrations/1316027432575-add-owners.js + down : migrations/1316027432512-add-jane.js + migration : complete +``` + +Any time you want to see the current state of the migrations, you can run `migrate list` to see an output like: + +``` +$ migrate list + 1316027432511-add-pets.js [2017-09-23] : <No Description> + 1316027432512-add-jane.js [2017-09-23] : <No Description> +``` + +The description can be added by exporting a `description` field from the migration file. -This will run up-migrations upto (and including) `1316027433425-coolest-pet.js`. Similarly you can run down-migrations upto (and including) a specific migration, instead of migrating all the way down. +## Custom State Storage - $ migrate down 1316027432512-add-jane.js - down : migrations/1316027432575-add-owners.js - down : migrations/1316027432512-add-jane.js - migration : complete +By default, `migrate` stores the state of the migrations which have been run in a file (`.migrate`). But you +can provide a custom storage engine if you would like to do something different, like storing them in your database of choice. +A storage engine has a simple interface of `load(fn)` and `save(set, fn)`. As long as what goes in as `set` comes out +the same on `load`, then you are good to go! + +If you are using the provided cli, you can specify the store implementation with the `--store` flag, which is be a `require`-able node module. For example: + +``` +$ migrate up --store="my-migration-store" +``` ## API -### `migrate.load(stateFile, migrationsDirectory)` +### `migrate.load(opts, cb)` + +Calls the callback with a `Set` based on the options passed. Options: -Returns a `Set` populated with migration scripts from the `migrationsDirectory` -and state loaded from `stateFile`. +- `set`: A set instance if you created your own +- `stateStore`: A store instance to load and store migration state, or a string which is a path to the migration state file +- `migrationsDirectory`: The path to the migrations directory +- `filterFunction`: A filter function which will be called for each file found in the migrations directory +- `sortFunction`: A sort function to ensure migration order ### `Set.up([migration, ]cb)` @@ -146,28 +245,3 @@ migration. Calls the callback `cb`, possibly with an error `err`, when done. Migrates down to the specified `migration` or, if none is specified, to the first migration. Calls the callback `cb`, possibly with an error `err`, when done. - -## License - -(The MIT License) - -Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From f58e51ec28d807074096d0b8125aa8337e5c8f61 Mon Sep 17 00:00:00 2001 From: Wes Todd <wes@wesleytodd.com> Date: Fri, 25 Aug 2017 12:27:33 -0500 Subject: [PATCH 24/29] Added test case for the ordering warning --- .gitignore | 1 + test/integration.js | 57 +++++++++++++++++++++++++++++++++++++++++++++ test/util/run.js | 13 +++++++---- 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 test/integration.js diff --git a/.gitignore b/.gitignore index 57f3a0a..aa239b0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules .migrate *.db migrations/ +test/fixtures/tmp diff --git a/test/integration.js b/test/integration.js new file mode 100644 index 0000000..e9fbf7b --- /dev/null +++ b/test/integration.js @@ -0,0 +1,57 @@ +/* global describe, it, beforeEach */ +const path = require('path') +const assert = require('assert') +const rimraf = require('rimraf') +const mkdirp = require('mkdirp') +const run = require('./util/run') + +// Paths +const TMP_DIR = path.join(__dirname, 'fixtures', 'tmp') + +function reset () { + rimraf.sync(TMP_DIR) + mkdirp.sync(TMP_DIR) +} + +describe('integration tests', function () { + beforeEach(reset) + + it('should warn when the migrations are run out of order', function (done) { + run.init(TMP_DIR, [], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + run.create(TMP_DIR, ['1-one', '-d', 'W'], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + run.create(TMP_DIR, ['3-three', '-d', 'W'], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + run.up(TMP_DIR, [], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + run.create(TMP_DIR, ['2-two', '-d', 'W'], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + run.up(TMP_DIR, [], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + // A warning should log, and the process not exit with 0 + // because migration 2 should come before migration 3, + // but migration 3 was already run from the previous + // state + assert(out.indexOf('warn') !== -1) + done() + }) + }) + }) + }) + }) + }) + }) +}) diff --git a/test/util/run.js b/test/util/run.js index 86a8ed7..e7c1c73 100644 --- a/test/util/run.js +++ b/test/util/run.js @@ -1,7 +1,8 @@ 'use strict' +const path = require('path') const spawn = require('child_process').spawn -module.exports = function run (cmd, dir, args, done) { +var run = module.exports = function run (cmd, dir, args, done) { var p = spawn(cmd, ['-c', dir, ...args]) var out = '' p.stdout.on('data', function (d) { @@ -12,9 +13,13 @@ module.exports = function run (cmd, dir, args, done) { }) p.on('error', done) p.on('close', function (code) { - if (code !== 0) { - console.error(out) - } done(null, out, code) }) } + +// Run specific commands +module.exports.up = run.bind(null, path.join(__dirname, '..', '..', 'bin', 'migrate-up')) +module.exports.down = run.bind(null, path.join(__dirname, '..', '..', 'bin', 'migrate-down')) +module.exports.create = run.bind(null, path.join(__dirname, '..', '..', 'bin', 'migrate-create')) +module.exports.init = run.bind(null, path.join(__dirname, '..', '..', 'bin', 'migrate-init')) +module.exports.list = run.bind(null, path.join(__dirname, '..', '..', 'bin', 'migrate-list')) From 32a732bf63fa25a9dc826ee741d117a39e3fd4fb Mon Sep 17 00:00:00 2001 From: Wes Todd <wes@wesleytodd.com> Date: Tue, 3 Oct 2017 15:53:24 -0500 Subject: [PATCH 25/29] added test case for missing migration error --- test/integration.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/integration.js b/test/integration.js index e9fbf7b..f4dff6d 100644 --- a/test/integration.js +++ b/test/integration.js @@ -54,4 +54,44 @@ describe('integration tests', function () { }) }) }) + + it('should error when migrations are present in the state file, but not loadable', function (done) { + run.init(TMP_DIR, [], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + run.create(TMP_DIR, ['1-one', '-d', 'W'], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + run.create(TMP_DIR, ['3-three', '-d', 'W'], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + // Keep migration filename to remove + var filename = out.split(' : ')[1].trim() + + run.up(TMP_DIR, [], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + // Remove the three migration + rimraf.sync(filename) + + run.create(TMP_DIR, ['2-two', '-d', 'W'], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + + run.up(TMP_DIR, [], function (err, out, code) { + assert(!err) + assert.equal(code, 1) + assert(out.indexOf('error') !== -1) + done() + }) + }) + }) + }) + }) + }) + }) }) From 38ab52e82df81a4de56b3bc4704dc136633b9371 Mon Sep 17 00:00:00 2001 From: Joshua Nielsen <jdn@umwelt.dk> Date: Fri, 6 Oct 2017 12:21:09 -0500 Subject: [PATCH 26/29] Implements support for promise based migrations (closes #82) --- lib/migrate.js | 23 ++++++- test/fixtures/promises/1-callback-test.js | 13 ++++ test/fixtures/promises/2-promise-test.js | 17 +++++ .../promises/3-callback-promise-test.js | 19 ++++++ test/fixtures/promises/4-failure-test.js | 17 +++++ test/promises.js | 62 +++++++++++++++++++ 6 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/promises/1-callback-test.js create mode 100644 test/fixtures/promises/2-promise-test.js create mode 100644 test/fixtures/promises/3-callback-promise-test.js create mode 100644 test/fixtures/promises/4-failure-test.js create mode 100644 test/promises.js diff --git a/lib/migrate.js b/lib/migrate.js index 4a86724..3d6ae7b 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -25,8 +25,27 @@ function migrate (set, direction, migrationName, fn) { return fn(new TypeError('Migration ' + migration.title + ' does not have method ' + direction)) } + // Status for supporting promises and callbacks + var isPromise = false + + // Run the migration function set.emit('migration', migration, direction) - migration[direction](function (err) { + var returnValue = migration[direction](function (err) { + if (isPromise) return set.emit('warning', 'if your migration returns a promise, do not call the done callback') + completeMigration(err) + }) + + // Is it a promise? + isPromise = typeof Promise !== 'undefined' && returnValue instanceof Promise + + // Handle the promises + if (isPromise) { + returnValue + .then(completeMigration) + .catch(completeMigration) + } + + function completeMigration (err) { if (err) return fn(err) // Set timestamp if running up, clear it if down @@ -41,7 +60,7 @@ function migrate (set, direction, migrationName, fn) { next(migrations.shift()) }) - }) + } } next(migrations.shift()) diff --git a/test/fixtures/promises/1-callback-test.js b/test/fixtures/promises/1-callback-test.js new file mode 100644 index 0000000..a43e85c --- /dev/null +++ b/test/fixtures/promises/1-callback-test.js @@ -0,0 +1,13 @@ +'use strict' + +module.exports.up = function (next) { + setTimeout(function () { + next() + }, 1) +} + +module.exports.down = function (next) { + setTimeout(function () { + next() + }, 1) +} diff --git a/test/fixtures/promises/2-promise-test.js b/test/fixtures/promises/2-promise-test.js new file mode 100644 index 0000000..2954828 --- /dev/null +++ b/test/fixtures/promises/2-promise-test.js @@ -0,0 +1,17 @@ +'use strict' + +module.exports.up = function () { + return new Promise(function (resolve, reject) { + setTimeout(function () { + resolve() + }, 1) + }) +} + +module.exports.down = function () { + return new Promise(function (resolve, reject) { + setTimeout(function () { + resolve() + }, 1) + }) +} diff --git a/test/fixtures/promises/3-callback-promise-test.js b/test/fixtures/promises/3-callback-promise-test.js new file mode 100644 index 0000000..142c96c --- /dev/null +++ b/test/fixtures/promises/3-callback-promise-test.js @@ -0,0 +1,19 @@ +'use strict' + +module.exports.up = function (next) { + return new Promise(function (resolve, reject) { + setTimeout(function () { + next() + resolve() + }, 1) + }) +} + +module.exports.down = function (next) { + return new Promise(function (resolve, reject) { + setTimeout(function () { + next() + resolve() + }, 1) + }) +} diff --git a/test/fixtures/promises/4-failure-test.js b/test/fixtures/promises/4-failure-test.js new file mode 100644 index 0000000..d0d3315 --- /dev/null +++ b/test/fixtures/promises/4-failure-test.js @@ -0,0 +1,17 @@ +'use strict' + +module.exports.up = function () { + return new Promise(function (resolve, reject) { + setTimeout(function () { + reject(new Error('foo')) + }, 1) + }) +} + +module.exports.down = function () { + return new Promise(function (resolve, reject) { + setTimeout(function () { + reject(new Error('foo')) + }, 1) + }) +} diff --git a/test/promises.js b/test/promises.js new file mode 100644 index 0000000..bbb47bb --- /dev/null +++ b/test/promises.js @@ -0,0 +1,62 @@ +/* global describe, it, beforeEach, afterEach */ + +var rimraf = require('rimraf') +var path = require('path') +var assert = require('assert') + +var migrate = require('../') + +var BASE = path.join(__dirname, 'fixtures', 'promises') +var STATE = path.join(__dirname, 'fixtures', '.migrate') + +describe('Promise migrations', function () { + var set + + beforeEach(function (done) { + migrate.load({ + stateStore: STATE, + migrationsDirectory: BASE + }, function (err, s) { + set = s + done(err) + }) + }) + + afterEach(function (done) { + rimraf(STATE, done) + }) + + it('should handle callback migration', function (done) { + set.up('1-callback-test.js', function (err) { + assert.ifError(err) + done() + }) + }) + + it('should handle promise migration', function (done) { + set.up('2-promise-test.js', function (err) { + assert.ifError(err) + done() + }) + }) + + it('should warn when using promise but still calling callback', function (done) { + var warned = false + set.on('warning', function (msg) { + assert(msg) + warned = true + }) + set.up('3-callback-promise-test.js', function () { + assert(warned) + done() + }) + }) + + it('should error with rejected promises', function (done) { + set.up('4-failure-test.js', function (err) { + assert(err) + assert.equal(err.message, 'foo') + done() + }) + }) +}) From b4cbd87410ba9afbd5ecab8b9750812a3fa5625b Mon Sep 17 00:00:00 2001 From: Wes Todd <wes@wesleytodd.com> Date: Fri, 6 Oct 2017 12:31:59 -0500 Subject: [PATCH 27/29] fix generator argument to not append current path --- bin/migrate-create | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/migrate-create b/bin/migrate-create index 574800c..61dda0e 100755 --- a/bin/migrate-create +++ b/bin/migrate-create @@ -35,7 +35,7 @@ function create (name) { } // Load the template generator - var gen = require(path.resolve(process.cwd(), program.generator)) + var gen = require(program.generator) gen({ name: name, dateFormat: program.dateFormat, From cf93f5e220c07f50bd6085d12a6fcd7cb25c1fe0 Mon Sep 17 00:00:00 2001 From: Wes Todd <wes@wesleytodd.com> Date: Sun, 5 Nov 2017 16:49:39 -0600 Subject: [PATCH 28/29] warn when migration did not take a callback and did not return a promise --- lib/migrate.js | 4 ++++ test/fixtures/promises/4-neither-test.js | 9 +++++++++ .../{4-failure-test.js => 99-failure-test.js} | 0 test/promises.js | 16 +++++++++++++++- 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/promises/4-neither-test.js rename test/fixtures/promises/{4-failure-test.js => 99-failure-test.js} (100%) diff --git a/lib/migrate.js b/lib/migrate.js index 3d6ae7b..8c1b5ca 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -30,6 +30,7 @@ function migrate (set, direction, migrationName, fn) { // Run the migration function set.emit('migration', migration, direction) + var arity = migration[direction].length var returnValue = migration[direction](function (err) { if (isPromise) return set.emit('warning', 'if your migration returns a promise, do not call the done callback') completeMigration(err) @@ -38,6 +39,9 @@ function migrate (set, direction, migrationName, fn) { // Is it a promise? isPromise = typeof Promise !== 'undefined' && returnValue instanceof Promise + // If not a promise and arity is not 1, warn + if (!isPromise && arity < 1) set.emit('warning', 'it looks like your migration did not take or callback or return a Promise, this might be an error') + // Handle the promises if (isPromise) { returnValue diff --git a/test/fixtures/promises/4-neither-test.js b/test/fixtures/promises/4-neither-test.js new file mode 100644 index 0000000..980cef4 --- /dev/null +++ b/test/fixtures/promises/4-neither-test.js @@ -0,0 +1,9 @@ +'use strict' + +module.exports.up = function () { + arguments[0]() +} + +module.exports.down = function () { + arguments[0]() +} diff --git a/test/fixtures/promises/4-failure-test.js b/test/fixtures/promises/99-failure-test.js similarity index 100% rename from test/fixtures/promises/4-failure-test.js rename to test/fixtures/promises/99-failure-test.js diff --git a/test/promises.js b/test/promises.js index bbb47bb..a1e0481 100644 --- a/test/promises.js +++ b/test/promises.js @@ -52,8 +52,22 @@ describe('Promise migrations', function () { }) }) + it('should warn with no promise or callback', function (done) { + set.up('3-callback-promise-test.js', function () { + var warned = false + set.on('warning', function (msg) { + assert(msg) + warned = true + }) + set.up('4-neither-test.js', function () { + assert(warned) + done() + }) + }) + }) + it('should error with rejected promises', function (done) { - set.up('4-failure-test.js', function (err) { + set.up('99-failure-test.js', function (err) { assert(err) assert.equal(err.message, 'foo') done() From 08af26ca159e0f95bc132a9257eda94fcb210af6 Mon Sep 17 00:00:00 2001 From: Wes Todd <wes@wesleytodd.com> Date: Sun, 5 Nov 2017 18:19:11 -0600 Subject: [PATCH 29/29] added support for environments with dotenv --- .npmignore | 2 ++ bin/migrate-create | 12 ++++++++++++ bin/migrate-down | 12 ++++++++++++ bin/migrate-init | 12 ++++++++++++ bin/migrate-list | 12 ++++++++++++ bin/migrate-up | 18 ++++++++++++++--- examples/env/.env | 1 + examples/env/.foo | 1 + examples/env/README.md | 11 +++++++++++ examples/env/db.js | 44 ++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + test/fixtures/env/env | 1 + test/integration.js | 21 +++++++++++++++++++- 13 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 examples/env/.env create mode 100644 examples/env/.foo create mode 100644 examples/env/README.md create mode 100644 examples/env/db.js create mode 100644 test/fixtures/env/env diff --git a/.npmignore b/.npmignore index f1250e5..8757baa 100644 --- a/.npmignore +++ b/.npmignore @@ -2,3 +2,5 @@ support test examples *.sock +.db +.migrate diff --git a/bin/migrate-create b/bin/migrate-create index 61dda0e..31ed414 100755 --- a/bin/migrate-create +++ b/bin/migrate-create @@ -4,6 +4,7 @@ var program = require('commander') var path = require('path') +var dotenv = require('dotenv') var log = require('../lib/log') var registerCompiler = require('../lib/register-compiler') var pkg = require('../package.json') @@ -17,10 +18,21 @@ program .option('-t, --template-file [filePath]', 'Set path to template file to use for new migrations', path.join(__dirname, '..', 'lib', 'template.js')) .option('-e, --extention [extention]', 'Use the given extention to create the file', '.js') .option('-g, --generator <name>', 'A template generator function', path.join(__dirname, '..', 'lib', 'template-generator')) + .option('--env [name]', 'Use dotenv to load an environment file') .arguments('<name>') .action(create) .parse(process.argv) +// Setup environment +if (program.env) { + var e = dotenv.config({ + path: typeof program.env === 'string' ? program.env : '.env' + }) + if (e && e.error instanceof Error) { + throw e.error + } +} + var _name function create (name) { // Name provided? diff --git a/bin/migrate-down b/bin/migrate-down index 82ca467..7ced975 100755 --- a/bin/migrate-down +++ b/bin/migrate-down @@ -5,6 +5,7 @@ var program = require('commander') var path = require('path') var minimatch = require('minimatch') +var dotenv = require('dotenv') var migrate = require('../') var runMigrations = require('../lib/migrate') var log = require('../lib/log') @@ -20,11 +21,22 @@ program .option('--migrations-dir <dir>', 'Change the migrations directory name', 'migrations') .option('--matches <glob>', 'A glob pattern to filter migration files', '*') .option('--compiler <ext:module>', 'Use the given module to compile files') + .option('--env [name]', 'Use dotenv to load an environment file') .parse(process.argv) // Change the working dir process.chdir(program.chdir) +// Setup environment +if (program.env) { + var e = dotenv.config({ + path: typeof program.env === 'string' ? program.env : '.env' + }) + if (e && e.error instanceof Error) { + throw e.error + } +} + // Load compiler if (program.compiler) { registerCompiler(program.compiler) diff --git a/bin/migrate-init b/bin/migrate-init index e809a1a..b36d5cd 100755 --- a/bin/migrate-init +++ b/bin/migrate-init @@ -4,6 +4,7 @@ var program = require('commander') var mkdirp = require('mkdirp') +var dotenv = require('dotenv') var path = require('path') var log = require('../lib/log') var pkg = require('../package.json') @@ -14,11 +15,22 @@ program .option('-s, --store <store>', 'Set the migrations store', path.join(__dirname, '..', 'lib', 'file-store')) .option('--migrations-dir <dir>', 'Change the migrations directory name', 'migrations') .option('-c, --chdir [dir]', 'Change the working directory', process.cwd()) + .option('--env [name]', 'Use dotenv to load an environment file') .parse(process.argv) // Change the working dir process.chdir(program.chdir) +// Setup environment +if (program.env) { + var e = dotenv.config({ + path: typeof program.env === 'string' ? program.env : '.env' + }) + if (e && e.error instanceof Error) { + throw e.error + } +} + // Setup store var Store = require(program.store) var store = new Store(program.stateFile) diff --git a/bin/migrate-list b/bin/migrate-list index 8088efd..a52eed6 100755 --- a/bin/migrate-list +++ b/bin/migrate-list @@ -6,6 +6,7 @@ var program = require('commander') var path = require('path') var dateFormat = require('dateformat') var minimatch = require('minimatch') +var dotenv = require('dotenv') var migrate = require('../') var log = require('../lib/log') var registerCompiler = require('../lib/register-compiler') @@ -21,6 +22,7 @@ program .option('--migrations-dir <dir>', 'Change the migrations directory name', 'migrations') .option('--matches <glob>', 'A glob pattern to filter migration files', '*') .option('--compiler <ext:module>', 'Use the given module to compile files') + .option('--env [name]', 'Use dotenv to load an environment file') .parse(process.argv) // Check clean flag, exit if NODE_ENV === 'production' and force not specified @@ -32,6 +34,16 @@ if (program.clean && process.env.NODE_ENV === 'production' && !program.force) { // Change the working dir process.chdir(program.chdir) +// Setup environment +if (program.env) { + var e = dotenv.config({ + path: typeof program.env === 'string' ? program.env : '.env' + }) + if (e && e.error instanceof Error) { + throw e.error + } +} + // Load compiler if (program.compiler) { registerCompiler(program.compiler) diff --git a/bin/migrate-up b/bin/migrate-up index fc333ef..71d67aa 100755 --- a/bin/migrate-up +++ b/bin/migrate-up @@ -5,6 +5,7 @@ var program = require('commander') var path = require('path') var minimatch = require('minimatch') +var dotenv = require('dotenv') var migrate = require('../') var runMigrations = require('../lib/migrate') var log = require('../lib/log') @@ -23,8 +24,22 @@ program .option('--migrations-dir <dir>', 'Change the migrations directory name', 'migrations') .option('--matches <glob>', 'A glob pattern to filter migration files', '*') .option('--compiler <ext:module>', 'Use the given module to compile files') + .option('--env [name]', 'Use dotenv to load an environment file') .parse(process.argv) +// Change the working dir +process.chdir(program.chdir) + +// Setup environment +if (program.env) { + var e = dotenv.config({ + path: typeof program.env === 'string' ? program.env : '.env' + }) + if (e && e.error instanceof Error) { + throw e.error + } +} + // Check clean flag, exit if NODE_ENV === 'production' and force not specified if (program.clean && process.env.NODE_ENV === 'production' && !program.force) { log.error('error', 'Cowardly refusing to clean while node environment set to production, use --force to continue.') @@ -37,9 +52,6 @@ if (program.init && process.env.NODE_ENV === 'production' && !program.force) { process.exit(1) } -// Change the working dir -process.chdir(program.chdir) - // Load compiler if (program.compiler) { registerCompiler(program.compiler) diff --git a/examples/env/.env b/examples/env/.env new file mode 100644 index 0000000..a71ce89 --- /dev/null +++ b/examples/env/.env @@ -0,0 +1 @@ +DB=contributors diff --git a/examples/env/.foo b/examples/env/.foo new file mode 100644 index 0000000..e9902e4 --- /dev/null +++ b/examples/env/.foo @@ -0,0 +1 @@ +DB=foo diff --git a/examples/env/README.md b/examples/env/README.md new file mode 100644 index 0000000..e91a95b --- /dev/null +++ b/examples/env/README.md @@ -0,0 +1,11 @@ +# Environment Example + +``` +$ migrate up --env +$ migrate down --env +$ cat .db # should see table of `contributors` + +$ migrate up --env .foo +$ migrate down --env .foo +$ cat .db # should see table of `foo` +``` diff --git a/examples/env/db.js b/examples/env/db.js new file mode 100644 index 0000000..b151444 --- /dev/null +++ b/examples/env/db.js @@ -0,0 +1,44 @@ +'use strict' +var fs = require('fs') + +module.exports = { + loaded: false, + tables: {}, + table: function (name) { + this.tables[name] = [] + this.save() + }, + removeTable: function (name) { + delete this.tables[name] + this.save() + }, + insert: function (table, value) { + this.tables[table].push(value) + this.save() + }, + remove: function (table, value) { + this.tables[table].splice(this.tables[table].indexOf(value), 1) + this.save() + }, + save: function () { + fs.writeFileSync('.db', JSON.stringify(this)) + }, + load: function () { + if (this.loaded) return this + var json + try { + json = JSON.parse(fs.readFileSync('.db', 'utf8')) + } catch (e) { + // ignore + return this + } + this.loaded = true + this.tables = json.tables + return this + }, + toJSON: function () { + return { + tables: this.tables + } + } +} diff --git a/package.json b/package.json index 67a7690..4ea54ef 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "chalk": "^1.1.3", "commander": "^2.9.0", "dateformat": "^2.0.0", + "dotenv": "^4.0.0", "inherits": "^2.0.3", "minimatch": "^3.0.3", "mkdirp": "^0.5.1", diff --git a/test/fixtures/env/env b/test/fixtures/env/env new file mode 100644 index 0000000..8a6696f --- /dev/null +++ b/test/fixtures/env/env @@ -0,0 +1 @@ +DB=pets diff --git a/test/integration.js b/test/integration.js index f4dff6d..2965e42 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1,20 +1,25 @@ -/* global describe, it, beforeEach */ +/* global describe, it, beforeEach, afterEach */ const path = require('path') const assert = require('assert') const rimraf = require('rimraf') const mkdirp = require('mkdirp') const run = require('./util/run') +const db = require('./util/db') // Paths const TMP_DIR = path.join(__dirname, 'fixtures', 'tmp') +const ENV_DIR = path.join(__dirname, 'fixtures', 'env') function reset () { + rimraf.sync(path.join(ENV_DIR, '.migrate')) rimraf.sync(TMP_DIR) mkdirp.sync(TMP_DIR) + db.nuke() } describe('integration tests', function () { beforeEach(reset) + afterEach(reset) it('should warn when the migrations are run out of order', function (done) { run.init(TMP_DIR, [], function (err, out, code) { @@ -94,4 +99,18 @@ describe('integration tests', function () { }) }) }) + + it('should load the enviroment file when passed --env', function (done) { + run.up(ENV_DIR, ['--env', 'env'], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + assert(out.indexOf('error') === -1) + run.down(ENV_DIR, ['--env', 'env'], function (err, out, code) { + assert(!err) + assert.equal(code, 0) + assert(out.indexOf('error') === -1) + done() + }) + }) + }) })