Browse files

Merge branch 'feature/cli'

  • Loading branch information...
2 parents c511451 + ce8c4ec commit e108aa7e1b927d4486b6869a84336bced95a6179 @tj committed Apr 22, 2011
View
216 bin/migrate
@@ -0,0 +1,216 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var migrate = require('../')
+ , fs = require('fs');
+
+/**
+ * Arguments.
+ */
+
+var args = process.argv.slice(2);
+
+/**
+ * Option defaults.
+ */
+
+var options = { args: [] };
+
+/**
+ * Usage information.
+ */
+
+var usage = [
+ ''
+ , ' Usage: migrate [options] [command]'
+ , ''
+ , ' Options:'
+ , ''
+ , ' -c, --chdir <path> change the working directory'
+ , ''
+ , ' Commands:'
+ , ''
+ , ' create [title] create a new migration file with optional [title]'
+ , ''
+].join('\n');
+
+/**
+ * Migration template.
+ */
+
+var template = [
+ ''
+ , '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');
+}
+
+// abort with a message
+
+function abort(msg) {
+ console.error(' %s', msg);
+ process.exit(1);
+}
+
+// parse arguments
+
+var arg;
+while (args.length) {
+ arg = args.shift();
+ switch (arg) {
+ case '-h':
+ case '--help':
+ case 'help':
+ console.log(usage);
+ process.exit();
+ break;
+ case '-c':
+ case '--chdir':
+ process.chdir(required());
+ break;
+ default:
+ if (options.command) {
+ options.args.push(arg);
+ } else {
+ options.command = arg;
+ }
+ }
+}
+
+/**
+ * Load configuration.
+ */
+
+function loadConfig() {
+ try {
+ return JSON.parse(fs.readFileSync('migrations/migrate.json', 'utf8'));
+ } catch (err) {
+ return { prev: -1 };
+ }
+}
+
+/**
+ * Save configuration.
+ */
+
+function saveConfig(obj) {
+ var json = JSON.stringify(obj);
+ fs.writeFileSync('migrations/migrate.json', json);
+}
+
+/**
+ * Load migrations.
+ */
+
+function migrations() {
+ return fs.readdirSync('migrations').filter(function(file){
+ return file.match(/^\d+/);
+ }).sort().map(function(file){
+ return 'migrations/' + file;
+ });
+}
+
+/**
+ * Log a keyed message.
+ */
+
+function log(key, msg) {
+ console.log(' \033[90m%s :\033[0m \033[36m%s\033[0m', key, msg);
+}
+
+/**
+ * Slugify the given `str`.
+ */
+
+function slugify(str) {
+ return str.replace(/\s+/g, '-');
+}
+
+// load config
+
+var config = loadConfig();
+
+// commands
+
+var commands = {
+
+ /**
+ * up
+ */
+
+ up: function(){
+ performMigration('up');
+ },
+
+ /**
+ * down
+ */
+
+ down: function(){
+ performMigration('down');
+ },
+
+ /**
+ * create [title]
+ */
+
+ create: function(){
+ var curr = ++config.prev
+ , title = slugify(Array.prototype.slice.call(arguments).join(' '));
+ // TODO: slug()
+ // append '-' when title is present
+ title = title ? curr + '-' + title : curr;
+
+ var path = 'migrations/' + title + '.js';
+ log('create', path);
+ fs.writeFileSync(path, template);
+ saveConfig(config);
+ }
+};
+
+// perform migration
+
+function performMigration(direction) {
+ // no migrations
+ if (-1 == config.prev) abort('add migrations before running $ migrate ' + direction);
+ migrate('migrations/.migrate');
+ migrations().forEach(function(path){
+ var mod = require(process.cwd() + '/' + path);
+ migrate(path, mod.up, mod.down);
+ });
+
+ var set = migrate();
+
+ set.on('migration', function(migration, direction){
+ log(direction, migration.title);
+ });
+
+ set.on('save', function(){
+ log('migration', 'complete');
+ process.exit();
+ });
+
+ set[direction]();
+}
+
+// invoke command
+
+var command = options.command || 'up';
+if (!(command in commands)) abort('unknown command "' + command + '"');
+command = commands[command];
+command.apply(this, options.args);
View
12 examples/cli/migrations/0-add-pets.js
@@ -0,0 +1,12 @@
+
+var db = require('./db');
+
+exports.up = function(next){
+ db.rpush('pets', 'tobi');
+ db.rpush('pets', 'loki', next);
+};
+
+exports.down = function(next){
+ db.rpop('pets');
+ db.rpop('pets', next);
+};
View
10 examples/cli/migrations/1-add-jane.js
@@ -0,0 +1,10 @@
+
+var db = require('./db');
+
+exports.up = function(next){
+ db.rpush('pets', 'jane', next);
+};
+
+exports.down = function(next){
+ db.rpop('pets', next);
+};
View
12 examples/cli/migrations/2-add-owners.js
@@ -0,0 +1,12 @@
+
+var db = require('./db');
+
+exports.up = function(next){
+ db.rpush('owners', 'taylor');
+ db.rpush('owners', 'tj', next);
+};
+
+exports.down = function(next){
+ db.rpop('owners');
+ db.rpop('owners', next);
+};
View
10 examples/cli/migrations/3-coolest-pet.js
@@ -0,0 +1,10 @@
+
+var db = require('./db');
+
+exports.up = function(next){
+ db.set('pets:coolest', 'tobi', next);
+};
+
+exports.down = function(next){
+ db.del('pets:coolest', next);
+};
View
10 examples/cli/migrations/db.js
@@ -0,0 +1,10 @@
+
+// bad example, but you get the point ;)
+
+// $ npm install redis
+// $ redis-server
+
+var redis = require('redis')
+ , db = redis.createClient();
+
+module.exports = db;
View
1 examples/cli/migrations/migrate.json
@@ -0,0 +1 @@
+{"prev":3}
View
9 lib/set.js
@@ -46,9 +46,12 @@ Set.prototype.__proto__ = EventEmitter.prototype;
*/
Set.prototype.save = function(fn){
- this.emit('save');
- var json = JSON.stringify(this);
- fs.writeFile(this.path, json, fn);
+ var self = this
+ , json = JSON.stringify(this);
+ fs.writeFile(this.path, json, function(err){
+ self.emit('save');
+ fn && fn(err);
+ });
};
/**

0 comments on commit e108aa7

Please sign in to comment.