diff --git a/.gitignore b/.gitignore index 41c4de95..d00ab01d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ migrations/ node_modules/ +*.db diff --git a/.npmignore b/.npmignore index 9889f038..e5935c10 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,4 @@ test migrations node_modules -database.json +*.db diff --git a/bin/db-migrate b/bin/db-migrate index ff27b22c..b5ca8243 100755 --- a/bin/db-migrate +++ b/bin/db-migrate @@ -21,13 +21,16 @@ var argv = optimist .default({ verbose: false, db: process.cwd() + '/database.json', + 'migrations-dir': process.cwd() + '/migrations', env: 'dev' }) .usage('Usage: db-migrate [up|down|create] migrationName [options]') .alias('e', 'env') + .alias('m', 'migrations-dir') .alias('v', 'verbose') .alias('h', 'help') .alias('h', '?') .describe('env', 'The environment to run the migrations under (dev, test, prod).') + .describe('migrations-dir', 'The directory containing your migration files.') .describe('db', 'Location of the database.json file.') .describe('verbose', 'Verbose mode.') .argv diff --git a/lib/cli.js b/lib/cli.js index 1142e829..ca0c4137 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,7 +1,11 @@ +var util = require('util'); var Migration = require('./migration'); +var config = require('./config'); +var fs = require('fs'); +var path = require('path'); exports.create = function(options) { - var migration = new Migration(options.title); + var migration = new Migration(options.title, new Date()); migration.write(function(err) { if (err) { console.error('[FAILED]', err); @@ -12,9 +16,72 @@ exports.create = function(options) { }; exports.up = function(options) { - + var env = config.getCurrent(); + var driver = require('./driver/' + env.driver); + driver.connect(env, function(err, db) { + if (err) { console.error('[FAILED]', err); return; } + db.createMigrationsTable(function(err) { + if (err) { console.error('[FAILED]', err); return; } + db.all('SELECT * FROM migrations ORDER BY name', function(err, dbResults) { + if (err) { console.error('[FAILED]', err); return; } + fs.readdir(options['migrations-dir'], function(err, files) { + if (err) { console.error('[FAILED]', err); return; } + files = files.sort(); + files = files.filter(function(file) { + return /\.js$/.test(file); + }); + files.forEach(function(file) { + if (verbose) { + console.log('processing file ' + file); + } + var migration = new Migration(path.join(options['migrations-dir'], file)); + var hasRun = dbResults.some(function(result) { + return result.name === migration.name; + }); + if (!hasRun) { + migration.up(db, function(err) { + if (err) { console.error('[FAILED]', err); return; } + }); + } + }); + }); + }); + }); + }); }; exports.down = function(options) { + var env = config.getCurrent(); + var driver = require('./driver/' + env.driver); + driver.connect(env, function(err, db) { + if (err) { console.error('[FAILED]', err); return; } + db.createMigrationsTable(function(err) { + if (err) { console.error('[FAILED]', err); return; } + db.all('SELECT * FROM migrations ORDER BY name DESC', function(err, dbResults) { + if (err) { console.error('[FAILED]', err); return; } + fs.readdir(options['migrations-dir'], function(err, files) { + if (err) { console.error('[FAILED]', err); return; } + files = files.sort(); + files = files.filter(function(file) { + return /\.js$/.test(file); + }); + files.forEach(function(file) { + if (verbose) { + console.log('processing file ' + file); + } + var migration = new Migration(path.join(options['migrations-dir'], file)); + var hasRun = dbResults.some(function(result) { + return result.name === migration.name; + }); + if (!hasRun) { + migration.up(db, function(err) { + if (err) { console.error('[FAILED]', err); return; } + }); + } + }); + }); + }); + }); + }); }; diff --git a/lib/driver/base.js b/lib/driver/base.js index f1ab39a1..65368dbc 100644 --- a/lib/driver/base.js +++ b/lib/driver/base.js @@ -4,6 +4,10 @@ var events = require('events'); module.exports = Base = function() { }; util.inherits(Base, events.EventEmitter); +Base.prototype.createMigrationsTable = function(callback) { + throw new Error('not yet implemented'); +}; + Base.prototype.createTable = function(tableName, columnSpecs, callback) { throw new Error('not yet implemented'); }; @@ -55,6 +59,10 @@ Base.prototype.runSql = function(sql, callback) { throw new Error('not yet implemented'); }; +Base.prototype.all = function(sql, params, callback) { + throw new Error('not yet implemented'); +}; + Base.prototype.escape = function(str) { return str.replace(/'/g, "\'"); }; diff --git a/lib/driver/sqlite3.js b/lib/driver/sqlite3.js index 29e585a2..27d85edb 100644 --- a/lib/driver/sqlite3.js +++ b/lib/driver/sqlite3.js @@ -71,6 +71,15 @@ Driver = function(connection) { }; util.inherits(Driver, Base); +Driver.prototype.createMigrationsTable = function(callback) { + var columnDefs = [ + createColumnDef('name', { type: type.STRING, notNull: true, primaryKey: true}), + createColumnDef('run_on', { type: type.DATE_TIME, notNull: true}), + ]; + var sql = util.format('CREATE TABLE IF NOT EXISTS %s (%s)', 'migrations', columnDefs.join(', ')); + this.runSql(sql, callback); +}; + Driver.prototype.createTable = function(tableName, columnSpecs, callback) { var columnDefs = []; for (var columnName in columnSpecs) { @@ -122,6 +131,10 @@ Driver.prototype.runSql = function(sql, callback) { this.connection.run(sql, callback); }; +Driver.prototype.all = function() { + this.connection.all.apply(this.connection, arguments); +}; + exports.connect = function(config, callback) { var mode = config.mode || defaultMode; var db = new sqlite3.Database(config.filename, mode); diff --git a/lib/migrate.js b/lib/migrate.js deleted file mode 100644 index e0d34d83..00000000 --- a/lib/migrate.js +++ /dev/null @@ -1,7 +0,0 @@ -exports.create = function(options) { - -}; - -exports.migrate = function(options) { - -}; diff --git a/lib/migration.js b/lib/migration.js index 815b3d0e..de64ca7f 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -73,8 +73,17 @@ Migration = function() { }; Migration.prototype.write = function(callback) { - fs.writeFileSync(this.path, migrationTemplate); - return this; + fs.writeFile(this.path, migrationTemplate, callback); +}; + +Migration.prototype.up = function(db, callback) { + var migrationFile = require(this.path); + migrationFile.up(db, callback); +}; + +Migration.prototype.down = function(db, callback) { + var migrationFile = require(this.path); + migrationFile.down(db, callback); }; module.exports = Migration; diff --git a/package.json b/package.json index db525c70..3c3a72ff 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ ], "dependencies": { "sqlite3": "~2.1.1", - "mysql": "~0.9.5" + "mysql": "~0.9.5", + "optimist": "~0.3.0" }, "devDependencies": { "vows": "~0.5.13", diff --git a/test/database.json b/test/database.json index 17ba6ff2..e107e6d1 100644 --- a/test/database.json +++ b/test/database.json @@ -7,5 +7,10 @@ "test": { "driver": "sqlite3", "filename": ":memory:" + }, + + "prod": { + "driver": "sqlite3", + "filename": "prod.db" } }