├── .gitignore ├── .gitmodules ├── .npmignore ├── CONTRIBUTING.md ├── History.md ├── Makefile ├── Readme.md ├── bin └── migrate ├── examples ├── cli │ └── migrations │ │ ├── 000-add-pets.js │ │ ├── 001-add-jane.js │ │ ├── 002-add-owners.js │ │ ├── 003-coolest-pet.js │ │ └── db.js └── migrate.js ├── index.js ├── lib ├── migrate.js ├── migration.js └── set.js ├── package.json └── test └── test.migrate.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.sock 4 | .migrate 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "support/should"] 2 | path = support/should 3 | url = git://github.com/visionmedia/should.js.git 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | *.sock 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 1. Please maintain the current code style 2 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.2.2 / 2014-10-01 3 | ================== 4 | 5 | * bump 6 | 7 | 0.2.1 / 2014-10-01 8 | ================== 9 | 10 | * Exit when there are no migrations to run 11 | 12 | 0.2.0 / 2014-10-01 13 | ================== 14 | 15 | * change the way config file is read, use `require` to load the file 16 | - This way allow `module.exports` in the config file 17 | 18 | 0.1.5 / 2014-10-01 19 | ================== 20 | 21 | * set mongoose dependency 22 | 23 | 0.1.4 / 2013-05-02 24 | ================== 25 | 26 | * fix create command 27 | * use mongo for migrations 28 | 29 | 0.1.0 / 2013-04-21 30 | ================== 31 | 32 | * fix create command 33 | * use environments 34 | * use mongoose to store migrations 35 | * add mongoose 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @node test/test.migrate.js 4 | 5 | .PHONY: test -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## Mongoose migrate 3 | 4 | Fork of [visionmedia/node-migrate](https://github.com/visionmedia/node-migrate). Keeps track of the migrations in a mongodb collection instead of `.migrate` file. 5 | 6 | ## Installation 7 | 8 | ```sh 9 | $ npm install mongoose-migrate -g 10 | ``` 11 | 12 | or include it in `package.json` 13 | 14 | ## Usage 15 | 16 | mongoose-migrate needs an env variable `NODE_MONGOOSE_MIGRATIONS_CONFIG` which points to the path of the config file. The config file should contain 17 | 18 | ```js 19 | // Path : ./config/migrations.js 20 | module.exports = { 21 | development: { 22 | schema: { 'migration': {} }, 23 | modelName: 'Migration', 24 | db: process.env.MONGOHQ_URL || 'mongodb://localhost/app_development', 25 | dbOptions: { ... } 26 | }, 27 | test: { ... }, 28 | production: { ... } 29 | } 30 | ``` 31 | > `dbOptions` is an optional argument for `mongoose.connect` 32 | 33 | and then run the migrate command 34 | 35 | ```sh 36 | $ NODE_MONGOOSE_MIGRATIONS_CONFIG=./config/migrations.js mongoose-migrate 37 | ``` 38 | 39 | I created this fork because everytime I used to deploy to heroku, it used to deploy in a different folder and the `.migrate` file was not available anymore. 40 | 41 | --- 42 | 43 | # node migrate 44 | 45 | Abstract migration framework for node 46 | 47 | ## Installation 48 | 49 | $ npm install migrate 50 | 51 | ## Usage 52 | 53 | ``` 54 | Usage: migrate [options] [command] 55 | 56 | Options: 57 | 58 | -c, --chdir change the working directory 59 | 60 | Commands: 61 | 62 | down migrate down 63 | up migrate up (the default command) 64 | create [title] create a new migration file with optional [title] 65 | 66 | ``` 67 | 68 | ## Creating Migrations 69 | 70 | 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: 71 | 72 | exports.up = function(next){ 73 | next(); 74 | }; 75 | 76 | exports.down = function(next){ 77 | next(); 78 | }; 79 | 80 | All you have to do is populate these, invoking `next()` when complete, and you are ready to migrate! 81 | 82 | For example: 83 | 84 | $ migrate create add-pets 85 | $ migrate create add-owners 86 | 87 | The first call creates `./migrations/000-add-pets.js`, which we can populate: 88 | 89 | var db = require('./db'); 90 | 91 | exports.up = function(next){ 92 | db.rpush('pets', 'tobi'); 93 | db.rpush('pets', 'loki'); 94 | db.rpush('pets', 'jane', next); 95 | }; 96 | 97 | exports.down = function(next){ 98 | db.rpop('pets'); 99 | db.rpop('pets', next); 100 | }; 101 | 102 | The second creates `./migrations/001-add-owners.js`, which we can populate: 103 | 104 | var db = require('./db'); 105 | 106 | exports.up = function(next){ 107 | db.rpush('owners', 'taylor'); 108 | db.rpush('owners', 'tj', next); 109 | }; 110 | 111 | exports.down = function(next){ 112 | db.rpop('owners'); 113 | db.rpop('owners', next); 114 | }; 115 | 116 | ## Running Migrations 117 | 118 | When first running the migrations, all will be executed in sequence. 119 | 120 | $ migrate 121 | up : migrations/000-add-pets.js 122 | up : migrations/001-add-jane.js 123 | up : migrations/002-add-owners.js 124 | up : migrations/003-coolest-pet.js 125 | migration : complete 126 | 127 | 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. 128 | 129 | $ migrate 130 | migration : complete 131 | 132 | If we were to create another migration using `migrate create`, and then execute migrations again, we would execute only those not previously executed: 133 | 134 | $ migrate 135 | up : migrates/004-coolest-owner.js 136 | 137 | You can also run migrations incrementally by specifying a migration. 138 | 139 | $ migrate up 002-coolest-pet.js 140 | up : migrations/000-add-pets.js 141 | up : migrations/001-add-jane.js 142 | up : migrations/002-add-owners.js 143 | migration : complete 144 | 145 | This will run up-migrations upto (and including) `002-coolest-pet.js`. Similarly you can run down-migrations upto (and including) a specific migration, instead of migrating all the way down. 146 | 147 | $ migrate down 001-add-jane.js 148 | down : migrations/002-add-owners.js 149 | down : migrations/001-add-jane.js 150 | migration : complete 151 | 152 | ## License 153 | 154 | (The MIT License) 155 | 156 | Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca> 157 | 158 | Permission is hereby granted, free of charge, to any person obtaining 159 | a copy of this software and associated documentation files (the 160 | 'Software'), to deal in the Software without restriction, including 161 | without limitation the rights to use, copy, modify, merge, publish, 162 | distribute, sublicense, and/or sell copies of the Software, and to 163 | permit persons to whom the Software is furnished to do so, subject to 164 | the following conditions: 165 | 166 | The above copyright notice and this permission notice shall be 167 | included in all copies or substantial portions of the Software. 168 | 169 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 170 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 171 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 172 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 173 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 174 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 175 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 176 | -------------------------------------------------------------------------------- /bin/migrate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var migrate = require('../') 8 | , join = require('path').join 9 | , fs = require('fs'); 10 | 11 | /** 12 | * Arguments. 13 | */ 14 | 15 | var args = process.argv.slice(2); 16 | 17 | /** 18 | * Option defaults. 19 | */ 20 | 21 | var options = { args: [] }; 22 | 23 | /** 24 | * Current working directory. 25 | */ 26 | 27 | var cwd; 28 | 29 | /** 30 | * Usage information. 31 | */ 32 | 33 | var usage = [ 34 | '' 35 | , ' Usage: migrate [options] [command]' 36 | , '' 37 | , ' Options:' 38 | , '' 39 | , ' -c, --chdir change the working directory' 40 | , '' 41 | , ' Commands:' 42 | , '' 43 | , ' down [name] migrate down till given migration' 44 | , ' up [name] migrate up till given migration (the default command)' 45 | , ' create [title] create a new migration file with optional [title]' 46 | , '' 47 | ].join('\n'); 48 | 49 | /** 50 | * Migration template. 51 | */ 52 | 53 | var template = [ 54 | '' 55 | , 'exports.up = function(next){' 56 | , ' next();' 57 | , '};' 58 | , '' 59 | , 'exports.down = function(next){' 60 | , ' next();' 61 | , '};' 62 | , '' 63 | ].join('\n'); 64 | 65 | // require an argument 66 | 67 | function required() { 68 | if (args.length) return args.shift(); 69 | abort(arg + ' requires an argument'); 70 | } 71 | 72 | // abort with a message 73 | 74 | function abort(msg) { 75 | console.error(' %s', msg); 76 | process.exit(1); 77 | } 78 | 79 | // parse arguments 80 | 81 | var arg; 82 | while (args.length) { 83 | arg = args.shift(); 84 | switch (arg) { 85 | case '-h': 86 | case '--help': 87 | case 'help': 88 | console.log(usage); 89 | process.exit(); 90 | break; 91 | case '-c': 92 | case '--chdir': 93 | process.chdir(cwd = required()); 94 | break; 95 | default: 96 | if (options.command) { 97 | options.args.push(arg); 98 | } else { 99 | options.command = arg; 100 | } 101 | } 102 | } 103 | 104 | /** 105 | * Load migrations. 106 | */ 107 | 108 | function migrations() { 109 | return fs.readdirSync('migrations').filter(function(file){ 110 | return file.match(/^\d+.*\.js$/); 111 | }).sort().map(function(file){ 112 | return 'migrations/' + file; 113 | }); 114 | } 115 | 116 | /** 117 | * Log a keyed message. 118 | */ 119 | 120 | function log(key, msg) { 121 | console.log(' \033[90m%s :\033[0m \033[36m%s\033[0m', key, msg); 122 | } 123 | 124 | /** 125 | * Slugify the given `str`. 126 | */ 127 | 128 | function slugify(str) { 129 | return str.replace(/\s+/g, '-'); 130 | } 131 | 132 | // create ./migrations 133 | 134 | try { 135 | fs.mkdirSync('migrations', 0774); 136 | } catch (err) { 137 | // ignore 138 | } 139 | 140 | // commands 141 | 142 | var commands = { 143 | 144 | /** 145 | * up [name] 146 | */ 147 | 148 | up: function(migrationName){ 149 | performMigration('up', migrationName); 150 | }, 151 | 152 | /** 153 | * down [name] 154 | */ 155 | 156 | down: function(migrationName){ 157 | performMigration('down', migrationName); 158 | }, 159 | 160 | /** 161 | * create [title] 162 | */ 163 | 164 | create: function(){ 165 | var migrations = fs.readdirSync('migrations').filter(function(file){ 166 | return file.match(/^\d+/); 167 | }).map(function(file){ 168 | return parseInt(file.match(/^(\d+)/)[1], 10); 169 | }).sort(function(a, b){ 170 | return a - b; 171 | }); 172 | 173 | var curr = pad((migrations.pop() || 0) + 1) 174 | , title = slugify([].slice.call(arguments).join(' ')); 175 | title = title ? curr + '-' + title : curr; 176 | create(title); 177 | process.exit(); 178 | } 179 | }; 180 | 181 | /** 182 | * Pad the given number. 183 | * 184 | * @param {Number} n 185 | * @return {String} 186 | */ 187 | 188 | function pad(n) { 189 | return Array(4 - n.toString().length).join('0') + n; 190 | } 191 | 192 | /** 193 | * Create a migration with the given `name`. 194 | * 195 | * @param {String} name 196 | */ 197 | 198 | function create(name) { 199 | cwd = cwd || process.cwd(); 200 | var path = 'migrations/' + name + '.js'; 201 | log('create', join(cwd, path)); 202 | fs.writeFileSync(path, template); 203 | } 204 | 205 | /** 206 | * Perform a migration in the given `direction`. 207 | * 208 | * @param {Number} direction 209 | */ 210 | 211 | function performMigration(direction, migrationName) { 212 | migrate('migrations/.migrate'); 213 | var all = migrations(); 214 | all.forEach(function(path){ 215 | var mod = require(process.cwd() + '/' + path); 216 | migrate(path, mod.up, mod.down); 217 | }); 218 | 219 | if (!all.length) process.exit(); 220 | 221 | var set = migrate(); 222 | 223 | set.on('migration', function(migration, direction){ 224 | log(direction, migration.title); 225 | }); 226 | 227 | set.on('save', function(){ 228 | log('migration', 'complete'); 229 | process.exit(); 230 | }); 231 | 232 | var migrationPath = migrationName 233 | ? join('migrations', migrationName) 234 | : migrationName; 235 | 236 | set[direction](null, migrationPath); 237 | } 238 | 239 | // invoke command 240 | 241 | var command = options.command || 'up'; 242 | if (!(command in commands)) abort('unknown command "' + command + '"'); 243 | command = commands[command]; 244 | command.apply(this, options.args); 245 | -------------------------------------------------------------------------------- /examples/cli/migrations/000-add-pets.js: -------------------------------------------------------------------------------- 1 | 2 | var db = require('./db'); 3 | 4 | exports.up = function(next){ 5 | db.rpush('pets', 'tobi'); 6 | db.rpush('pets', 'loki', next); 7 | }; 8 | 9 | exports.down = function(next){ 10 | db.rpop('pets'); 11 | db.rpop('pets', next); 12 | }; 13 | -------------------------------------------------------------------------------- /examples/cli/migrations/001-add-jane.js: -------------------------------------------------------------------------------- 1 | 2 | var db = require('./db'); 3 | 4 | exports.up = function(next){ 5 | db.rpush('pets', 'jane', next); 6 | }; 7 | 8 | exports.down = function(next){ 9 | db.rpop('pets', next); 10 | }; 11 | -------------------------------------------------------------------------------- /examples/cli/migrations/002-add-owners.js: -------------------------------------------------------------------------------- 1 | 2 | var db = require('./db'); 3 | 4 | exports.up = function(next){ 5 | db.rpush('owners', 'taylor'); 6 | db.rpush('owners', 'tj', next); 7 | }; 8 | 9 | exports.down = function(next){ 10 | db.rpop('owners'); 11 | db.rpop('owners', next); 12 | }; 13 | -------------------------------------------------------------------------------- /examples/cli/migrations/003-coolest-pet.js: -------------------------------------------------------------------------------- 1 | 2 | var db = require('./db'); 3 | 4 | exports.up = function(next){ 5 | db.set('pets:coolest', 'tobi', next); 6 | }; 7 | 8 | exports.down = function(next){ 9 | db.del('pets:coolest', next); 10 | }; 11 | -------------------------------------------------------------------------------- /examples/cli/migrations/db.js: -------------------------------------------------------------------------------- 1 | 2 | // bad example, but you get the point ;) 3 | 4 | // $ npm install redis 5 | // $ redis-server 6 | 7 | var redis = require('redis') 8 | , db = redis.createClient(); 9 | 10 | module.exports = db; -------------------------------------------------------------------------------- /examples/migrate.js: -------------------------------------------------------------------------------- 1 | 2 | // bad example, but you get the point ;) 3 | 4 | // $ npm install redis 5 | // $ redis-server 6 | 7 | var migrate = require('../') 8 | , redis = require('redis') 9 | , db = redis.createClient(); 10 | 11 | migrate(__dirname + '/.migrate'); 12 | 13 | migrate('add pets', function(next){ 14 | db.rpush('pets', 'tobi'); 15 | db.rpush('pets', 'loki', next); 16 | }, function(next){ 17 | db.rpop('pets'); 18 | db.rpop('pets', next); 19 | }); 20 | 21 | migrate('add jane', function(next){ 22 | db.rpush('pets', 'jane', next); 23 | }, function(next){ 24 | db.rpop('pets', next); 25 | }); 26 | 27 | migrate('add owners', function(next){ 28 | db.rpush('owners', 'taylor'); 29 | db.rpush('owners', 'tj', next); 30 | }, function(next){ 31 | db.rpop('owners'); 32 | db.rpop('owners', next); 33 | }); 34 | 35 | migrate('coolest pet', function(next){ 36 | db.set('pets:coolest', 'tobi', next); 37 | }, function(next){ 38 | db.del('pets:coolest', next); 39 | }); 40 | 41 | var set = migrate(); 42 | 43 | console.log(); 44 | set.on('save', function(){ 45 | console.log(); 46 | }); 47 | 48 | set.on('migration', function(migration, direction){ 49 | console.log(' \033[90m%s\033[0m \033[36m%s\033[0m', direction, migration.title); 50 | }); 51 | 52 | set.up(function(err){ 53 | if (err) throw err; 54 | process.exit(); 55 | }); 56 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib/migrate'); -------------------------------------------------------------------------------- /lib/migrate.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * migrate 4 | * Copyright(c) 2011 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var Migration = require('./migration') 13 | , Set = require('./set') 14 | , pkg = require('../package.json'); 15 | 16 | /** 17 | * Expose the migrate function. 18 | */ 19 | 20 | exports = module.exports = migrate; 21 | 22 | /** 23 | * Library version. 24 | */ 25 | 26 | exports.version = pkg.version; 27 | 28 | function migrate(title, up, down) { 29 | // migration 30 | if ('string' == typeof title && up && down) { 31 | migrate.set.migrations.push(new Migration(title, up, down)); 32 | // specify migration file 33 | } else if ('string' == typeof title) { 34 | migrate.set = new Set(title); 35 | // no migration path 36 | } else if (!migrate.set) { 37 | throw new Error('must invoke migrate(path) before running migrations'); 38 | // run migrations 39 | } else { 40 | return migrate.set; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/migration.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * migrate - Migration 4 | * Copyright (c) 2010 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Expose `Migration`. 10 | */ 11 | 12 | module.exports = Migration; 13 | 14 | function Migration(title, up, down) { 15 | this.title = title; 16 | this.up = up; 17 | this.down = down; 18 | } -------------------------------------------------------------------------------- /lib/set.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * migrate - Set 4 | * Copyright (c) 2010 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var EventEmitter = require('events').EventEmitter 13 | , fs = require('fs'); 14 | 15 | /** 16 | * Expose `Set`. 17 | */ 18 | 19 | module.exports = Set; 20 | 21 | /** 22 | * Initialize a new migration `Set` with the given `path` 23 | * which is used to store data between migrations. 24 | * 25 | * @param {String} path 26 | * @api private 27 | */ 28 | 29 | function Set(path) { 30 | this.migrations = []; 31 | this.path = path; 32 | this.pos = 0; 33 | }; 34 | 35 | /** 36 | * Inherit from `EventEmitter.prototype`. 37 | */ 38 | 39 | Set.prototype.__proto__ = EventEmitter.prototype; 40 | 41 | /** 42 | * Save the migration data and call `fn(err)`. 43 | * 44 | * @param {Function} fn 45 | * @api public 46 | */ 47 | 48 | Set.prototype.save = function(fn){ 49 | var self = this 50 | this.connect(fn); 51 | }; 52 | 53 | /** 54 | * Load the migration data and call `fn(err, obj)`. 55 | * 56 | * @param {Function} fn 57 | * @return {Type} 58 | * @api public 59 | */ 60 | 61 | Set.prototype.load = function(fn){ 62 | this.emit('load'); 63 | this.connect(fn, 'new'); 64 | }; 65 | 66 | var path = require('path'); 67 | // get the config file path from env variable 68 | var configFile = process.env.NODE_MONGOOSE_MIGRATIONS_CONFIG || process.cwd() + '/config/migrations.js' 69 | var configPath = path.resolve(configFile); 70 | var mongoose = require('mongoose'); 71 | var json = require(configPath); 72 | var env = process.env.NODE_ENV || 'development'; 73 | var config = json[env]; 74 | var Schema = mongoose.Schema; 75 | var MigrationSchema = new Schema(config.schema); 76 | var Migration; 77 | mongoose.connect(config.db, config.dbOptions); 78 | 79 | /** 80 | * Connect to mongo 81 | */ 82 | Set.prototype.connect = function(fn, type) { 83 | var self = this; 84 | 85 | if (type) { 86 | Migration = mongoose.model(config.modelName, MigrationSchema); 87 | } else { 88 | Migration = mongoose.model(config.modelName); 89 | } 90 | 91 | Migration.findOne().exec(function (err, doc) { 92 | if (err) return fn(err); 93 | 94 | if (type) { 95 | try { 96 | var obj = doc && doc.migration 97 | ? doc.migration 98 | : { pos: 0, migrations: [] } 99 | fn(null, obj); 100 | } catch (err) { 101 | fn(err); 102 | } 103 | } else { 104 | if (!doc) { 105 | var m = new Migration({ migration: self }); 106 | m.save(cb); 107 | } else { 108 | doc.migration = self; 109 | doc.save(cb); 110 | } 111 | } 112 | }); 113 | 114 | function cb(err) { 115 | self.emit('save'); 116 | fn && fn(err); 117 | } 118 | }; 119 | 120 | /** 121 | * Run down migrations and call `fn(err)`. 122 | * 123 | * @param {Function} fn 124 | * @api public 125 | */ 126 | 127 | Set.prototype.down = function(fn, migrationName){ 128 | this.migrate('down', fn, migrationName); 129 | }; 130 | 131 | /** 132 | * Run up migrations and call `fn(err)`. 133 | * 134 | * @param {Function} fn 135 | * @api public 136 | */ 137 | 138 | Set.prototype.up = function(fn, migrationName){ 139 | this.migrate('up', fn, migrationName); 140 | }; 141 | 142 | /** 143 | * Migrate in the given `direction`, calling `fn(err)`. 144 | * 145 | * @param {String} direction 146 | * @param {Function} fn 147 | * @api public 148 | */ 149 | 150 | Set.prototype.migrate = function(direction, fn, migrationName){ 151 | var self = this; 152 | mongoose.connection.once('open', function() { 153 | fn = fn || function(){}; 154 | self.load(function(err, obj){ 155 | if (err) { 156 | if ('ENOENT' != err.code) return fn(err); 157 | } else { 158 | self.pos = obj.pos; 159 | } 160 | self._migrate(direction, fn, migrationName); 161 | }); 162 | }); 163 | }; 164 | 165 | /** 166 | * Get index of given migration in list of migrations 167 | * 168 | * @api private 169 | */ 170 | 171 | function positionOfMigration(migrations, filename) { 172 | for(var i=0; i < migrations.length; ++i) { 173 | if (migrations[i].title == filename) return i; 174 | } 175 | return -1; 176 | } 177 | 178 | /** 179 | * Perform migration. 180 | * 181 | * @api private 182 | */ 183 | 184 | Set.prototype._migrate = function(direction, fn, migrationName){ 185 | var self = this 186 | , migrations 187 | , migrationPos; 188 | 189 | if (!migrationName) { 190 | migrationPos = direction == 'up' ? this.migrations.length : 0; 191 | } else if ((migrationPos = positionOfMigration(this.migrations, migrationName)) == -1) { 192 | console.error("Could not find migration: " + migrationName); 193 | process.exit(1); 194 | } 195 | 196 | switch (direction) { 197 | case 'up': 198 | migrations = this.migrations.slice(this.pos, migrationPos+1); 199 | this.pos += migrations.length; 200 | break; 201 | case 'down': 202 | migrations = this.migrations.slice(migrationPos, this.pos).reverse(); 203 | this.pos -= migrations.length; 204 | break; 205 | } 206 | 207 | function next(err, migration) { 208 | // error from previous migration 209 | if (err) { 210 | console.error(err.toString()); 211 | process.exit(1); 212 | } 213 | 214 | // done 215 | if (!migration) { 216 | self.emit('complete'); 217 | self.save(fn); 218 | return; 219 | } 220 | 221 | self.emit('migration', migration, direction); 222 | migration[direction](function(err){ 223 | next(err, migrations.shift()); 224 | }); 225 | } 226 | 227 | next(null, migrations.shift()); 228 | }; 229 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoose-migrate", 3 | "version": "0.2.4", 4 | "description": "Abstract migration framework for node", 5 | "keywords": [ 6 | "migrate", 7 | "migrations", 8 | "mongoose" 9 | ], 10 | "author": "TJ Holowaychuk ", 11 | "contributors": [ 12 | { "name": "Madhusudhan Srinivasa", "email": "madhums8@gmail.com" } 13 | ], 14 | "repository": "git://github.com/madhums/mongoose-migrate", 15 | "bin": { "mongoose-migrate": "./bin/migrate" }, 16 | "dependencies" : { 17 | "mongoose": "4.1.x" 18 | }, 19 | "devDependencies": { 20 | "should": ">= 0.0.1" 21 | }, 22 | "main": "index", 23 | "engines": { "node": ">= 0.8.x" } 24 | } 25 | -------------------------------------------------------------------------------- /test/test.migrate.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var migrate = require('../') 7 | , should = require('should') 8 | , fs = require('fs'); 9 | 10 | // remove migration file 11 | 12 | try { 13 | fs.unlinkSync(__dirname + '/.migrate'); 14 | } catch (err) { 15 | // ignore 16 | } 17 | 18 | // dummy db 19 | 20 | var db = { pets: [] }; 21 | 22 | // dummy migrations 23 | 24 | migrate(__dirname + '/.migrate'); 25 | 26 | migrate('add guy ferrets', function(next){ 27 | db.pets.push({ name: 'tobi' }); 28 | db.pets.push({ name: 'loki' }); 29 | next(); 30 | }, function(next){ 31 | db.pets.pop(); 32 | db.pets.pop(); 33 | next(); 34 | }); 35 | 36 | migrate('add girl ferrets', function(next){ 37 | db.pets.push({ name: 'jane' }); 38 | next(); 39 | }, function(next){ 40 | db.pets.pop(); 41 | next(); 42 | }); 43 | 44 | migrate('add emails', function(next){ 45 | db.pets.forEach(function(pet){ 46 | pet.email = pet.name + '@learnboost.com'; 47 | }); 48 | next(); 49 | }, function(next){ 50 | db.pets.forEach(function(pet){ 51 | delete pet.email; 52 | }); 53 | next(); 54 | }); 55 | 56 | // tests 57 | 58 | migrate.version.should.match(/^\d+\.\d+\.\d+$/); 59 | 60 | // test migrating up / down several times 61 | 62 | var set = migrate(); 63 | 64 | set.up(function(){ 65 | assertPets(); 66 | set.up(function(){ 67 | assertPets(); 68 | set.down(function(){ 69 | assertNoPets(); 70 | set.down(function(){ 71 | assertNoPets(); 72 | set.up(function(){ 73 | assertPets(); 74 | testNewMigrations(); 75 | }); 76 | }); 77 | }); 78 | }); 79 | }); 80 | 81 | // test adding / running new migrations 82 | 83 | function testNewMigrations() { 84 | migrate('add dogs', function(next){ 85 | db.pets.push({ name: 'simon' }); 86 | db.pets.push({ name: 'suki' }); 87 | next(); 88 | }, function(next){ 89 | db.pets.pop(); 90 | db.pets.pop(); 91 | next(); 92 | }); 93 | 94 | set.up(function(){ 95 | assertPets.withDogs(); 96 | set.up(function(){ 97 | assertPets.withDogs(); 98 | set.down(function(){ 99 | assertNoPets(); 100 | testMigrationEvents(); 101 | }); 102 | }); 103 | }); 104 | } 105 | 106 | // test events 107 | 108 | function testMigrationEvents() { 109 | migrate('adjust emails', function(next){ 110 | db.pets.forEach(function(pet){ 111 | if (pet.email) 112 | pet.email = pet.email.replace('learnboost.com', 'lb.com'); 113 | }); 114 | next(); 115 | }, function(next){ 116 | db.pets.forEach(function(pet){ 117 | if (pet.email) 118 | pet.email = pet.email.replace('lb.com', 'learnboost.com'); 119 | }); 120 | next(); 121 | }); 122 | 123 | var migrations = [] 124 | , completed = 0 125 | , expectedMigrations = [ 126 | 'add guy ferrets' 127 | , 'add girl ferrets' 128 | , 'add emails' 129 | , 'add dogs' 130 | , 'adjust emails']; 131 | 132 | set.on('migration', function(migration, direction){ 133 | migrations.push(migration.title); 134 | direction.should.be.a('string'); 135 | }); 136 | 137 | set.on('complete', function(){ 138 | ++completed; 139 | }); 140 | 141 | set.up(function(){ 142 | db.pets[0].email.should.equal('tobi@lb.com'); 143 | migrations.should.eql(expectedMigrations); 144 | completed.should.equal(1); 145 | 146 | migrations = []; 147 | set.down(function(){ 148 | migrations.should.eql(expectedMigrations.reverse()); 149 | completed.should.equal(2); 150 | assertNoPets(); 151 | testNamedMigrations(); 152 | }); 153 | }); 154 | } 155 | 156 | // test migrations when migration name is given 157 | 158 | function testNamedMigrations() { 159 | assertNoPets(); 160 | set.up(function() { 161 | assertFirstMigration(); 162 | set.up(function() { 163 | assertSecondMigration(); 164 | set.down(function() { 165 | assertFirstMigration(); 166 | set.up(function() { 167 | assertSecondMigration(); 168 | set.down(function() { 169 | set.pos.should.equal(1); 170 | }, 'add girl ferrets'); 171 | },'add girl ferrets'); 172 | }, 'add girl ferrets'); 173 | },'add girl ferrets'); 174 | }, 'add guy ferrets'); 175 | } 176 | 177 | // helpers 178 | 179 | function assertNoPets() { 180 | db.pets.should.be.empty; 181 | set.pos.should.equal(0); 182 | } 183 | 184 | function assertPets() { 185 | db.pets.should.have.length(3); 186 | db.pets[0].name.should.equal('tobi'); 187 | db.pets[0].email.should.equal('tobi@learnboost.com'); 188 | set.pos.should.equal(3); 189 | } 190 | 191 | function assertFirstMigration() { 192 | db.pets.should.have.length(2); 193 | db.pets[0].name.should.equal('tobi'); 194 | db.pets[1].name.should.equal('loki'); 195 | set.pos.should.equal(1); 196 | } 197 | 198 | function assertSecondMigration() { 199 | db.pets.should.have.length(3); 200 | db.pets[0].name.should.equal('tobi'); 201 | db.pets[1].name.should.equal('loki'); 202 | db.pets[2].name.should.equal('jane'); 203 | set.pos.should.equal(2); 204 | } 205 | 206 | assertPets.withDogs = function(){ 207 | db.pets.should.have.length(5); 208 | db.pets[0].name.should.equal('tobi'); 209 | db.pets[0].email.should.equal('tobi@learnboost.com'); 210 | db.pets[4].name.should.equal('suki'); 211 | }; 212 | 213 | // status 214 | 215 | process.on('exit', function(){ 216 | console.log('\n ok\n'); 217 | }); --------------------------------------------------------------------------------