/// import fs = require('fs'); import path = require('path'); import util = require('util'); import pathParse = require('path-parse'); import async = require('async'); import mkdirp = require('@fibjs/mkdirp'); import Migration = require('./migration'); import MigrationDsl = require('./migration-dsl'); import { addPromiseInterface, prependIfNotAbsPath } from './utils'; /** * 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, '-'); } /** * Pad the given number. * * @param {Number} n * @return {String} */ function pad(n: number) { return Array(4 - n.toString().length).join('0') + n; } var jsTemplate = [ '' , 'exports.up = function(next){' , ' next();' , '};' , '' , 'exports.down = function(next){' , ' next();' , '};' , '' ].join('\n'); var coffeeTemplate = [ '' , 'exports.up = (next) ->' , ' next()' , '' , 'exports.down = (next) ->' , ' next()' , '' ].join('\n'); /** * Create a migration with the given `name`. * * @param {String} name */ function generate(name: string, extension: string) { var template = ((extension === "js") ? jsTemplate : coffeeTemplate) as string | Class_Buffer; var filePath = name + '.' + extension; log('create', prependIfNotAbsPath(filePath, process.cwd())); fs.writeFile(filePath, template as Class_Buffer); } class Migrator implements FxOrmPlugin__Migrator.Migrator { driver: FxOrmSqlDDLSync__Driver.Driver dir: string coffee: boolean logger: Function dsl?: MigrationDsl migration?: Migration constructor ( driver: FxOrmSqlDDLSync__Driver.Driver, opts?: FxOrmPlugin__Migrator.MigratorConstructorOptions ) { opts = (opts || {}); this.driver = driver; this.dir = (opts.dir || 'migrations'); this.coffee = (opts.coffee || false); this.logger = (opts.logger || log); if (this.driver) { this.dsl = new MigrationDsl(this.driver); this.migration = new Migration(this.dsl, this.logger); } } /** * Create the migration directory and storage table. */ setup (done) { this.mkdir(this.ensureMigrationsTable.bind(this, done)); }; /** * Create the migration directory */ mkdir (done) { let err = null try { mkdirp(this.dir, {mode: 0o774}); } catch (e) { err = e } finally { done(err); } }; /** * Load migrations modules from the file system. */ loadModules (): FxOrmPlugin__Migrator.MigratorModule[] { var self = this; // return fs.readdirSync(this.dir).filter(function(file) { return fs.readdir(this.dir).filter(function(file) { if (self.coffee) { return file.match(/^\d+.*\.coffee$/); } else { return file.match(/^\d+.*\.js$/); } }).sort().map(function(file) { const basedir = prependIfNotAbsPath(self.dir, process.cwd()); var mod_path = path.join(basedir, file); var mod = require(mod_path); return { file: file, up: mod.up, down: mod.down }; }); }; /** * Perform a migration in the given `direction`. * * @param {Number} direction */ performMigration ( direction: FxOrmPlugin__Migration.MigrationTableRow['direction'], migrationName: string, cb: FxOrmSqlDDLSync.ExecutionCallback ) { var self = this; var fileName: string; if(typeof migrationName === 'function') { cb = migrationName; migrationName = ''; } this.migration.all(function(err, appliedMigrations) { if(err) return cb(err); var migrationModules = self.loadModules(); if(direction === 'down') migrationModules.reverse(); // determine cut off point for a given migrationName var cutOff = migrationModules.findIndex(x => x.file === migrationName); if(cutOff > -1) { migrationModules = migrationModules.slice(0, cutOff + 1); } // is a migration module applied ? var isApplied = function(isMatch: boolean) { return (mod: FxOrmPlugin__Migrator.MigratorModule) => { fileName = pathParse(mod.file).name; const res = appliedMigrations.some(function (appliedMigration) { return !!appliedMigration.match(fileName); }); return isMatch ? res : !res; } } if(direction === 'up') { // up -> reject the applied migrations migrationModules = migrationModules.filter(isApplied(false)); } else { // down -> we only do the applied one migrationModules = migrationModules.filter(isApplied(true)); // down migration without parameter -> rollback only the first one if(util.isEmpty(migrationName)) migrationModules = [ util.first(migrationModules) ]; } var migrationCalls = migrationModules.map(function(mod) { return function(done: FxOrmSqlDDLSync.ExecutionCallback) { self.logger(direction, mod.file); // call the up/down function, using dsl as 'this' var cb = function (err?: Error) { if (err) return done(err); if (direction === 'up') { self.migration.save(mod.file, done); } else { fileName = pathParse(mod.file).name; self.migration.delete(fileName, done); } }; var migrationMethod = mod[direction]; var migrationMethodExpectsCallback = migrationMethod.length > 0; if (migrationMethodExpectsCallback) { migrationMethod.call(self.dsl, cb); } else { migrationMethod.call(self.dsl) .then(function() { return cb(); }) .catch(cb); } }; }); async.series(migrationCalls, function(err) { if(err) return cb(err); self.logger('migration', 'complete'); cb(); }); }) }; /** * up [name] * @promise */ up (migrationName: string, cb?: FxOrmSqlDDLSync.ExecutionCallback): void | PromiseLike | any { var self = this; this.setup(function(err: Error) { if (err) return cb(err); self.performMigration('up', migrationName, cb); }); }; /** * down [name] * @promise */ down (migrationName: string, cb?: FxOrmSqlDDLSync.ExecutionCallback): void | PromiseLike | any { var self = this; this.setup(function(err: Error) { if(err) return cb(err); self.performMigration('down', migrationName, cb); }); } /** * create [title] * @promise */ generate (title: string, cb?: FxOrmSqlDDLSync.ExecutionCallback): void | PromiseLike | any { var self = this; this.mkdir(function(){ // var migrations = fs.readdirSync(self.dir).filter(function(file) { var migrations = fs.readdir(self.dir).filter(function(file) { return file.match(/^\d+/); }).map(function(file){ return parseInt(file.match(/^(\d+)/)[1], 10); }).sort(function(a, b){ return a - b; }); var curr = pad((migrations.pop() || 0) + 1); title = title ? curr + '-' + title : curr; var extension = (self.coffee ? 'coffee' : 'js'); generate(self.dir + '/' + title, extension); cb(null, title); }) }; ensureMigrationsTable (cb?: FxOrmSqlDDLSync.ExecutionCallback): void | PromiseLike | any { this.migration.ensureMigrationsTable(cb); }; } ;[ "up", "down", "generate" ].forEach(methodName => { Migrator.prototype[methodName] = addPromiseInterface(Migrator.prototype[methodName]) }); export = Migrator;