From 50e3bef27d5c4bb2401ffdf3582e673c9b8a3647 Mon Sep 17 00:00:00 2001 From: Anand Date: Thu, 25 May 2017 23:13:46 +0530 Subject: [PATCH 1/5] added support for typescript --- package.json | 17 +- src/cli.js | 5 + src/db.js | 6 +- src/lib.js | 659 ++++++++++++++++++++++++++------------------------- 4 files changed, 355 insertions(+), 332 deletions(-) diff --git a/package.json b/package.json index 20f91cc..1dc6afe 100644 --- a/package.json +++ b/package.json @@ -26,17 +26,18 @@ "url": "https://github.com/balmasi/migrate-mongoose.git" }, "dependencies": { - "babel-cli": "^6.18.0", - "babel-core": "^6.20.0", - "babel-plugin-transform-runtime": "^6.15.0", - "babel-polyfill": "^6.20.0", - "babel-preset-latest": "^6.16.0", - "babel-register": "^6.18.0", - "bluebird": "^3.3.3", + "babel-cli": "^6.24.1", + "babel-core": "^6.24.1", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-polyfill": "^6.23.0", + "babel-preset-latest": "^6.24.1", + "babel-register": "^6.24.1", + "bluebird": "^3.5.0", "colors": "^1.1.2", "inquirer": "^0.12.0", "mkdirp": "^0.5.1", - "mongoose": "^4.4.6", + "mongoose": "^4.10.2", + "ts-node": "^3.0.4", "yargs": "^4.8.1" } } diff --git a/src/cli.js b/src/cli.js index 5c1ffb3..74ff8ff 100755 --- a/src/cli.js +++ b/src/cli.js @@ -65,6 +65,10 @@ let { argv: args } = yargs type: 'boolean', description: 'use es6 migration template?' }) + .option('typescript', { + type: 'boolean', + description: 'use typescript migration template?' + }) .option('md', { alias: 'migrations-dir', description: 'The path to the migration files', @@ -115,6 +119,7 @@ let migrator = new Migrator({ templatePath: args['template-file'], dbConnectionUri: args.dbConnectionUri, es6Templates: args.es6, + typescript: args.typescript, collectionName: args.collection, autosync: args.autosync, cli: true diff --git a/src/db.js b/src/db.js index 7fc1829..be7ca99 100644 --- a/src/db.js +++ b/src/db.js @@ -3,7 +3,7 @@ import Promise from 'bluebird'; // Factory function for a mongoose model mongoose.Promise = Promise; -export default function ( collection = 'migrations', dbConnection ) { +export default function ( collection = 'migrations', dbConnection, {typescript} ) { const MigrationSchema = new Schema({ name: String, @@ -27,7 +27,9 @@ export default function ( collection = 'migrations', dbConnection ) { }); MigrationSchema.virtual('filename').get(function() { - return `${this.createdAt.getTime()}-${this.name}.js`; + let basename = `${this.createdAt.getTime()}-${this.name}`; + if(typescript) return `${basename}.ts`; + return `${basename}.js`; }); dbConnection.on('error', err => { diff --git a/src/lib.js b/src/lib.js index 5ad3489..ec283fb 100644 --- a/src/lib.js +++ b/src/lib.js @@ -11,11 +11,11 @@ import MigrationModelFactory from './db'; let MigrationModel; Promise.config({ - warnings: false + warnings: false }); const es6Template = -` + ` /** * Make any changes you need to make to the database here */ @@ -32,7 +32,7 @@ export async function down () { `; const es5Template = -`'use strict'; + `'use strict'; /** * Make any changes you need to make to the database here @@ -49,330 +49,345 @@ exports.down = function down(done) { }; `; - export default class Migrator { - constructor({ - templatePath, - migrationsPath = './migrations', - dbConnectionUri, - es6Templates = false, - collectionName = 'migrations', - autosync = false, - cli = false, - connection - }) { - const defaultTemplate = es6Templates ? es6Template : es5Template; - this.template = templatePath ? fs.readFileSync(templatePath, 'utf-8') : defaultTemplate; - this.migrationPath = path.resolve(migrationsPath); - this.connection = connection || mongoose.createConnection(dbConnectionUri); - this.es6 = es6Templates; - this.collection = collectionName; - this.autosync = autosync; - this.cli = cli; - MigrationModel = MigrationModelFactory(collectionName, this.connection); - } - - log (logString, force = false) { - if (force || this.cli) { - console.log(logString); - } - } - - /** - * Use your own Mongoose connection object (so you can use this('modelname') - * @param {mongoose.connection} connection - Mongoose connection - */ - setMongooseConnection (connection) { - MigrationModel = MigrationModelFactory(this.collection, connection) - } - - /** - * Close the underlying connection to mongo - * @returns {Promise} A promise that resolves when connection is closed - */ - close() { - return this.connection ? this.connection.close() : Promise.resolve(); - } - - /** - * Create a new migration - * @param {string} migrationName - * @returns {Promise} A promise of the Migration created - */ - async create(migrationName) { - try { - const existingMigration = await MigrationModel.findOne({ name: migrationName }); - if (!!existingMigration) { - throw new Error(`There is already a migration with name '${migrationName}' in the database`.red); - } - - await this.sync(); - const now = Date.now(); - const newMigrationFile = `${now}-${migrationName}.js`; - mkdirp.sync(this.migrationPath); - fs.writeFileSync(path.join(this.migrationPath, newMigrationFile), this.template); - // create instance in db - await this.connection; - const migrationCreated = await MigrationModel.create({ - name: migrationName, - createdAt: now - }); - this.log(`Created migration ${migrationName} in ${this.migrationPath}.`); - return migrationCreated; - } catch(error){ - this.log(error.stack); - fileRequired(error); - } - } - - /** - * Runs migrations up to or down to a given migration name - * - * @param migrationName - * @param direction - */ - async run(direction = 'up', migrationName) { - await this.sync(); - - const untilMigration = migrationName ? - await MigrationModel.findOne({name: migrationName}) : - await MigrationModel.findOne().sort({createdAt: -1}); - - if (!untilMigration) { - if (migrationName) throw new ReferenceError("Could not find that migration in the database"); - else throw new Error("There are no pending migrations."); - } - - let query = { - createdAt: {$lte: untilMigration.createdAt}, - state: 'down' - }; - - if (direction == 'down') { - query = { - createdAt: {$gte: untilMigration.createdAt}, - state: 'up' - }; - } - - - const sortDirection = direction == 'up' ? 1 : -1; - const migrationsToRun = await MigrationModel.find(query) - .sort({createdAt: sortDirection}); - - if (!migrationsToRun.length) { - if (this.cli) { - this.log('There are no migrations to run'.yellow); - this.log(`Current Migrations' Statuses: `); - await this.list(); - } - throw new Error('There are no migrations to run'); - } - - let self = this; - let numMigrationsRan = 0; - let migrationsRan = []; - - for (const migration of migrationsToRun) { - const migrationFilePath = path.join(self.migrationPath, migration.filename); - if (this.es6) { - require('babel-register')({ - "presets": [require("babel-preset-latest")], - "plugins": [require("babel-plugin-transform-runtime")] - }); - - require('babel-polyfill'); - } - - let migrationFunctions; - - try { - migrationFunctions = require(migrationFilePath); - } catch (err) { - err.message = err.message && /Unexpected token/.test(err.message) ? - 'Unexpected Token when parsing migration. If you are using an ES6 migration file, use option --es6' : - err.message; - throw err; - } - - if (!migrationFunctions[direction]) { - throw new Error (`The ${direction} export is not defined in ${migration.filename}.`.red); - } - - try { - await new Promise( (resolve, reject) => { - const callPromise = migrationFunctions[direction].call( - this.connection.model.bind(this.connection), - function callback(err) { - if (err) return reject(err); - resolve(); - } - ); - - if (callPromise && typeof callPromise.then === 'function') { - callPromise.then(resolve).catch(reject); - } - }); - - this.log(`${direction.toUpperCase()}: `[direction == 'up'? 'green' : 'red'] + ` ${migration.filename} `); - - await MigrationModel.where({name: migration.name}).update({$set: {state: direction}}); - migrationsRan.push(migration.toJSON()); - numMigrationsRan++; - } catch(err) { - this.log(`Failed to run migration ${migration.name} due to an error.`.red); - this.log(`Not continuing. Make sure your data is in consistent state`.red); - throw err instanceof(Error) ? err : new Error(err); - } - } - - if (migrationsToRun.length == numMigrationsRan) this.log('All migrations finished successfully.'.green); - return migrationsRan; - } - - /** - * Looks at the file system migrations and imports any migrations that are - * on the file system but missing in the database into the database - * - * This functionality is opposite of prune() - */ - async sync() { - try { - const filesInMigrationFolder = fs.readdirSync(this.migrationPath); - const migrationsInDatabase = await MigrationModel.find({}); - // Go over migrations in folder and delete any files not in DB - const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.js$/.test(file)) - .map(filename => { - const fileCreatedAt = parseInt(filename.split('-')[0]); - const existsInDatabase = migrationsInDatabase.some(m => filename == m.filename); - return {createdAt: fileCreatedAt, filename, existsInDatabase}; - }); - - const filesNotInDb = _.filter(migrationsInFolder, {existsInDatabase: false}).map(f => f.filename); - let migrationsToImport = filesNotInDb; - this.log('Synchronizing database with file system migrations...'); - if (!this.autosync && migrationsToImport.length) { - const answers = await new Promise(function (resolve) { - ask.prompt({ - type: 'checkbox', - message: 'The following migrations exist in the migrations folder but not in the database. Select the ones you want to import into the database', - name: 'migrationsToImport', - choices: filesNotInDb - }, (answers) => { - resolve(answers); - }); - }); - - migrationsToImport = answers.migrationsToImport; - } - - return Promise.map(migrationsToImport, async (migrationToImport) => { - const filePath = path.join(this.migrationPath, migrationToImport), - timestampSeparatorIndex = migrationToImport.indexOf('-'), - timestamp = migrationToImport.slice(0, timestampSeparatorIndex), - migrationName = migrationToImport.slice(timestampSeparatorIndex + 1, migrationToImport.lastIndexOf('.')); - - this.log(`Adding migration ${filePath} into database from file system. State is ` + `DOWN`.red); - const createdMigration = await MigrationModel.create({ - name: migrationName, - createdAt: timestamp - }); - return createdMigration.toJSON(); - }); - } catch (error) { - this.log(`Could not synchronise migrations in the migrations folder up to the database.`.red); - throw error; - } - } - - /** - * Opposite of sync(). - * Removes files in migration directory which don't exist in database. - */ - async prune() { - try { - const filesInMigrationFolder = fs.readdirSync(this.migrationPath); - const migrationsInDatabase = await MigrationModel.find({}); - // Go over migrations in folder and delete any files not in DB - const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.js/.test(file) ) - .map(filename => { - const fileCreatedAt = parseInt(filename.split('-')[0]); - const existsInDatabase = migrationsInDatabase.some(m => filename == m.filename); - return { createdAt: fileCreatedAt, filename, existsInDatabase }; - }); - - const dbMigrationsNotOnFs = _.filter(migrationsInDatabase, m => { - return !_.find(migrationsInFolder, { filename: m.filename }) - }); - - - let migrationsToDelete = dbMigrationsNotOnFs.map( m => m.name ); - - if (!this.autosync && !!migrationsToDelete.length) { - const answers = await new Promise(function (resolve) { - ask.prompt({ - type: 'checkbox', - message: 'The following migrations exist in the database but not in the migrations folder. Select the ones you want to remove from the file system.', - name: 'migrationsToDelete', - choices: migrationsToDelete - }, (answers) => { - resolve(answers); - }); - }); - - migrationsToDelete = answers.migrationsToDelete; - } - - const migrationsToDeleteDocs = await MigrationModel - .find({ - name: { $in: migrationsToDelete } - }).lean(); - - if (migrationsToDelete.length) { - this.log(`Removing migration(s) `, `${migrationsToDelete.join(', ')}`.cyan, ` from database`); - await MigrationModel.remove({ - name: { $in: migrationsToDelete } - }); - } - - return migrationsToDeleteDocs; - } catch(error) { - this.log(`Could not prune extraneous migrations from database.`.red); - throw error; - } - } - - /** - * Lists the current migrations and their statuses - * @returns {Promise>} - * @example - * [ - * { name: 'my-migration', filename: '149213223424_my-migration.js', state: 'up' }, - * { name: 'add-cows', filename: '149213223453_add-cows.js', state: 'down' } - * ] - */ - async list() { - await this.sync(); - const migrations = await MigrationModel.find().sort({ createdAt: 1 }); - if (!migrations.length) this.log('There are no migrations to list.'.yellow); - return migrations.map((m) => { - this.log( - `${m.state == 'up' ? 'UP: \t' : 'DOWN:\t'}`[m.state == 'up'? 'green' : 'red'] + - ` ${m.filename}` - ); - return m.toJSON(); - }); - } + constructor({ + templatePath, + migrationsPath = './migrations', + dbConnectionUri, + es6Templates = false, + typescript = false, + collectionName = 'migrations', + autosync = false, + cli = false, + connection + }) { + const defaultTemplate = typescript || es6Templates ? es6Template : es5Template; + this.template = templatePath ? fs.readFileSync(templatePath, 'utf-8') : defaultTemplate; + this.migrationPath = path.resolve(migrationsPath); + this.connection = connection || mongoose.createConnection(dbConnectionUri); + this.es6 = es6Templates; + this.typescript = typescript; + this.collection = collectionName; + this.autosync = autosync; + this.cli = cli; + MigrationModel = MigrationModelFactory(collectionName, this.connection,{typescript}); + } + + log(logString, force = false) { + if (force || this.cli) { + console.log(logString); + } + } + + /** + * Use your own Mongoose connection object (so you can use this('modelname') + * @param {mongoose.connection} connection - Mongoose connection + */ + setMongooseConnection(connection) { + MigrationModel = MigrationModelFactory(this.collection, connection) + } + + /** + * Close the underlying connection to mongo + * @returns {Promise} A promise that resolves when connection is closed + */ + close() { + return this.connection ? this.connection.close() : Promise.resolve(); + } + + getMigrationFileName(rootName) { + if (this.typescript) return `${rootName}.ts`; + return `${rootName}.js`; + } + + /** + * Create a new migration + * @param {string} migrationName + * @returns {Promise} A promise of the Migration created + */ + async create(migrationName) { + try { + const existingMigration = await MigrationModel.findOne({name: migrationName}); + if (!!existingMigration) { + throw new Error(`There is already a migration with name '${migrationName}' in the database`.red); + } + + await this.sync(); + const now = Date.now(); + const newMigrationFile = this.getMigrationFileName(`${now}-${migrationName}`); + mkdirp.sync(this.migrationPath); + fs.writeFileSync(path.join(this.migrationPath, newMigrationFile), this.template); + // create instance in db + await this.connection; + const migrationCreated = await MigrationModel.create({ + name : migrationName, + createdAt: now + }); + this.log(`Created migration ${migrationName} in ${this.migrationPath}.`); + return migrationCreated; + } catch (error) { + this.log(error.stack); + fileRequired(error); + } + } + + /** + * Runs migrations up to or down to a given migration name + * + * @param migrationName + * @param direction + */ + async run(direction = 'up', migrationName) { + await this.sync(); + + const untilMigration = migrationName ? + await MigrationModel.findOne({name: migrationName}) : + await MigrationModel.findOne().sort({createdAt: -1}); + + if (!untilMigration) { + if (migrationName) throw new ReferenceError("Could not find that migration in the database"); + else throw new Error("There are no pending migrations."); + } + + let query = { + createdAt: {$lte: untilMigration.createdAt}, + state : 'down' + }; + + if (direction == 'down') { + query = { + createdAt: {$gte: untilMigration.createdAt}, + state : 'up' + }; + } + + const sortDirection = direction == 'up' ? 1 : -1; + const migrationsToRun = await MigrationModel.find(query) + .sort({createdAt: sortDirection}); + + if (!migrationsToRun.length) { + if (this.cli) { + this.log('There are no migrations to run'.yellow); + this.log(`Current Migrations' Statuses: `); + await this.list(); + } + throw new Error('There are no migrations to run'); + } + + let self = this; + let numMigrationsRan = 0; + let migrationsRan = []; + + for (const migration of migrationsToRun) { + const migrationFilePath = path.join(self.migrationPath, migration.filename); + if (this.es6) { + require('babel-register')({ + "presets": [require("babel-preset-latest")], + "plugins": [require("babel-plugin-transform-runtime")] + }); + + require('babel-polyfill'); + } + + if (this.typescript) { + require("ts-node").register({ + ignoreWarnings: [ + "2345", + "2346", + "2349", + "2350", + "2339", + "2459", + "1015", + "7027"] + }); + } + + let migrationFunctions; + + try { + migrationFunctions = require(migrationFilePath); + } catch (err) { + err.message = err.message && /Unexpected token/.test(err.message) ? + 'Unexpected Token when parsing migration. If you are using an ES6 migration file, use option --es6' : + err.message; + throw err; + } + + if (!migrationFunctions[direction]) { + throw new Error(`The ${direction} export is not defined in ${migration.filename}.`.red); + } + + try { + await new Promise((resolve, reject) => { + const callPromise = migrationFunctions[direction].call( + this.connection.model.bind(this.connection), + function callback(err) { + if (err) return reject(err); + resolve(); + } + ); + + if (callPromise && typeof callPromise.then === 'function') { + callPromise.then(resolve).catch(reject); + } + }); + + this.log(`${direction.toUpperCase()}: `[direction == 'up' ? 'green' : 'red'] + ` ${migration.filename} `); + + await MigrationModel.where({name: migration.name}).update({$set: {state: direction}}); + migrationsRan.push(migration.toJSON()); + numMigrationsRan++; + } catch (err) { + this.log(`Failed to run migration ${migration.name} due to an error.`.red); + this.log(`Not continuing. Make sure your data is in consistent state`.red); + throw err instanceof (Error) ? err : new Error(err); + } + } + + if (migrationsToRun.length == numMigrationsRan) this.log('All migrations finished successfully.'.green); + return migrationsRan; + } + + /** + * Looks at the file system migrations and imports any migrations that are + * on the file system but missing in the database into the database + * + * This functionality is opposite of prune() + */ + async sync() { + try { + const filesInMigrationFolder = fs.readdirSync(this.migrationPath); + const migrationsInDatabase = await MigrationModel.find({}); + // Go over migrations in folder and delete any files not in DB + const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.(js|ts)$/.test(file)) + .map(filename => { + const fileCreatedAt = parseInt(filename.split('-')[0]); + const existsInDatabase = migrationsInDatabase.some(m => filename == m.filename); + return {createdAt: fileCreatedAt, filename, existsInDatabase}; + }); + + const filesNotInDb = _.filter(migrationsInFolder, {existsInDatabase: false}).map(f => f.filename); + let migrationsToImport = filesNotInDb; + this.log('Synchronizing database with file system migrations...'); + if (!this.autosync && migrationsToImport.length) { + const answers = await new Promise(function (resolve) { + ask.prompt({ + type : 'checkbox', + message: 'The following migrations exist in the migrations folder but not in the database. Select the ones you want to import into the database', + name : 'migrationsToImport', + choices: filesNotInDb + }, (answers) => { + resolve(answers); + }); + }); + + migrationsToImport = answers.migrationsToImport; + } + + return Promise.map(migrationsToImport, async (migrationToImport) => { + const filePath = path.join(this.migrationPath, migrationToImport), + timestampSeparatorIndex = migrationToImport.indexOf('-'), + timestamp = migrationToImport.slice(0, timestampSeparatorIndex), + migrationName = migrationToImport.slice(timestampSeparatorIndex + 1, migrationToImport.lastIndexOf('.')); + + this.log(`Adding migration ${filePath} into database from file system. State is ` + `DOWN`.red); + const createdMigration = await MigrationModel.create({ + name : migrationName, + createdAt: timestamp + }); + return createdMigration.toJSON(); + }); + } catch (error) { + this.log(`Could not synchronise migrations in the migrations folder up to the database.`.red); + throw error; + } + } + + /** + * Opposite of sync(). + * Removes files in migration directory which don't exist in database. + */ + async prune() { + try { + const filesInMigrationFolder = fs.readdirSync(this.migrationPath); + const migrationsInDatabase = await MigrationModel.find({}); + // Go over migrations in folder and delete any files not in DB + const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.(js|ts)/.test(file)) + .map(filename => { + const fileCreatedAt = parseInt(filename.split('-')[0]); + const existsInDatabase = migrationsInDatabase.some(m => filename == m.filename); + return {createdAt: fileCreatedAt, filename, existsInDatabase}; + }); + + const dbMigrationsNotOnFs = _.filter(migrationsInDatabase, m => { + return !_.find(migrationsInFolder, {filename: m.filename}) + }); + + let migrationsToDelete = dbMigrationsNotOnFs.map(m => m.name); + + if (!this.autosync && !!migrationsToDelete.length) { + const answers = await new Promise(function (resolve) { + ask.prompt({ + type : 'checkbox', + message: 'The following migrations exist in the database but not in the migrations folder. Select the ones you want to remove from the file system.', + name : 'migrationsToDelete', + choices: migrationsToDelete + }, (answers) => { + resolve(answers); + }); + }); + + migrationsToDelete = answers.migrationsToDelete; + } + + const migrationsToDeleteDocs = await MigrationModel + .find({ + name: {$in: migrationsToDelete} + }).lean(); + + if (migrationsToDelete.length) { + this.log(`Removing migration(s) `, `${migrationsToDelete.join(', ')}`.cyan, ` from database`); + await MigrationModel.remove({ + name: {$in: migrationsToDelete} + }); + } + + return migrationsToDeleteDocs; + } catch (error) { + this.log(`Could not prune extraneous migrations from database.`.red); + throw error; + } + } + + /** + * Lists the current migrations and their statuses + * @returns {Promise>} + * @example + * [ + * { name: 'my-migration', filename: '149213223424_my-migration.js', state: 'up' }, + * { name: 'add-cows', filename: '149213223453_add-cows.js', state: 'down' } + * ] + */ + async list() { + await this.sync(); + const migrations = await MigrationModel.find().sort({createdAt: 1}); + if (!migrations.length) this.log('There are no migrations to list.'.yellow); + return migrations.map((m) => { + this.log( + `${m.state == 'up' ? 'UP: \t' : 'DOWN:\t'}`[m.state == 'up' ? 'green' : 'red'] + + ` ${m.filename}` + ); + return m.toJSON(); + }); + } } - - function fileRequired(error) { - if (error && error.code == 'ENOENT') { - throw new ReferenceError(`Could not find any files at path '${error.path}'`); - } + if (error && error.code == 'ENOENT') { + throw new ReferenceError(`Could not find any files at path '${error.path}'`); + } } - module.exports = Migrator; From 08239ea75a780c1de0b0b5ce14ebbf347a2db408 Mon Sep 17 00:00:00 2001 From: Anand Date: Thu, 25 May 2017 23:22:50 +0530 Subject: [PATCH 2/5] reformatting --- src/lib.js | 76 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/src/lib.js b/src/lib.js index ec283fb..48c597d 100644 --- a/src/lib.js +++ b/src/lib.js @@ -49,6 +49,7 @@ exports.down = function down(done) { }; `; + export default class Migrator { constructor({ templatePath, @@ -61,7 +62,7 @@ export default class Migrator { cli = false, connection }) { - const defaultTemplate = typescript || es6Templates ? es6Template : es5Template; + const defaultTemplate = (typescript || es6Templates) ? es6Template : es5Template; this.template = templatePath ? fs.readFileSync(templatePath, 'utf-8') : defaultTemplate; this.migrationPath = path.resolve(migrationsPath); this.connection = connection || mongoose.createConnection(dbConnectionUri); @@ -70,10 +71,10 @@ export default class Migrator { this.collection = collectionName; this.autosync = autosync; this.cli = cli; - MigrationModel = MigrationModelFactory(collectionName, this.connection,{typescript}); + MigrationModel = MigrationModelFactory(collectionName, this.connection, {typescript}); } - log(logString, force = false) { + log (logString, force = false) { if (force || this.cli) { console.log(logString); } @@ -83,7 +84,7 @@ export default class Migrator { * Use your own Mongoose connection object (so you can use this('modelname') * @param {mongoose.connection} connection - Mongoose connection */ - setMongooseConnection(connection) { + setMongooseConnection (connection) { MigrationModel = MigrationModelFactory(this.collection, connection) } @@ -95,9 +96,14 @@ export default class Migrator { return this.connection ? this.connection.close() : Promise.resolve(); } - getMigrationFileName(rootName) { - if (this.typescript) return `${rootName}.ts`; - return `${rootName}.js`; + /** + * Generate name for migration file with right extension + * @param basename + * @return {string} + */ + getMigrationFileName(basename) { + if (this.typescript) return `${basename}.ts`; + return `${basename}.js`; } /** @@ -107,7 +113,7 @@ export default class Migrator { */ async create(migrationName) { try { - const existingMigration = await MigrationModel.findOne({name: migrationName}); + const existingMigration = await MigrationModel.findOne({ name: migrationName }); if (!!existingMigration) { throw new Error(`There is already a migration with name '${migrationName}' in the database`.red); } @@ -120,12 +126,12 @@ export default class Migrator { // create instance in db await this.connection; const migrationCreated = await MigrationModel.create({ - name : migrationName, + name: migrationName, createdAt: now }); this.log(`Created migration ${migrationName} in ${this.migrationPath}.`); return migrationCreated; - } catch (error) { + } catch(error){ this.log(error.stack); fileRequired(error); } @@ -151,16 +157,17 @@ export default class Migrator { let query = { createdAt: {$lte: untilMigration.createdAt}, - state : 'down' + state: 'down' }; if (direction == 'down') { query = { createdAt: {$gte: untilMigration.createdAt}, - state : 'up' + state: 'up' }; } + const sortDirection = direction == 'up' ? 1 : -1; const migrationsToRun = await MigrationModel.find(query) .sort({createdAt: sortDirection}); @@ -215,12 +222,12 @@ export default class Migrator { } if (!migrationFunctions[direction]) { - throw new Error(`The ${direction} export is not defined in ${migration.filename}.`.red); + throw new Error (`The ${direction} export is not defined in ${migration.filename}.`.red); } try { - await new Promise((resolve, reject) => { - const callPromise = migrationFunctions[direction].call( + await new Promise( (resolve, reject) => { + const callPromise = migrationFunctions[direction].call( this.connection.model.bind(this.connection), function callback(err) { if (err) return reject(err); @@ -233,15 +240,15 @@ export default class Migrator { } }); - this.log(`${direction.toUpperCase()}: `[direction == 'up' ? 'green' : 'red'] + ` ${migration.filename} `); + this.log(`${direction.toUpperCase()}: `[direction == 'up'? 'green' : 'red'] + ` ${migration.filename} `); await MigrationModel.where({name: migration.name}).update({$set: {state: direction}}); migrationsRan.push(migration.toJSON()); numMigrationsRan++; - } catch (err) { + } catch(err) { this.log(`Failed to run migration ${migration.name} due to an error.`.red); this.log(`Not continuing. Make sure your data is in consistent state`.red); - throw err instanceof (Error) ? err : new Error(err); + throw err instanceof(Error) ? err : new Error(err); } } @@ -273,9 +280,9 @@ export default class Migrator { if (!this.autosync && migrationsToImport.length) { const answers = await new Promise(function (resolve) { ask.prompt({ - type : 'checkbox', + type: 'checkbox', message: 'The following migrations exist in the migrations folder but not in the database. Select the ones you want to import into the database', - name : 'migrationsToImport', + name: 'migrationsToImport', choices: filesNotInDb }, (answers) => { resolve(answers); @@ -293,7 +300,7 @@ export default class Migrator { this.log(`Adding migration ${filePath} into database from file system. State is ` + `DOWN`.red); const createdMigration = await MigrationModel.create({ - name : migrationName, + name: migrationName, createdAt: timestamp }); return createdMigration.toJSON(); @@ -313,25 +320,26 @@ export default class Migrator { const filesInMigrationFolder = fs.readdirSync(this.migrationPath); const migrationsInDatabase = await MigrationModel.find({}); // Go over migrations in folder and delete any files not in DB - const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.(js|ts)/.test(file)) + const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.(js|ts)/.test(file) ) .map(filename => { const fileCreatedAt = parseInt(filename.split('-')[0]); const existsInDatabase = migrationsInDatabase.some(m => filename == m.filename); - return {createdAt: fileCreatedAt, filename, existsInDatabase}; + return { createdAt: fileCreatedAt, filename, existsInDatabase }; }); const dbMigrationsNotOnFs = _.filter(migrationsInDatabase, m => { - return !_.find(migrationsInFolder, {filename: m.filename}) + return !_.find(migrationsInFolder, { filename: m.filename }) }); - let migrationsToDelete = dbMigrationsNotOnFs.map(m => m.name); + + let migrationsToDelete = dbMigrationsNotOnFs.map( m => m.name ); if (!this.autosync && !!migrationsToDelete.length) { const answers = await new Promise(function (resolve) { ask.prompt({ - type : 'checkbox', + type: 'checkbox', message: 'The following migrations exist in the database but not in the migrations folder. Select the ones you want to remove from the file system.', - name : 'migrationsToDelete', + name: 'migrationsToDelete', choices: migrationsToDelete }, (answers) => { resolve(answers); @@ -343,18 +351,18 @@ export default class Migrator { const migrationsToDeleteDocs = await MigrationModel .find({ - name: {$in: migrationsToDelete} + name: { $in: migrationsToDelete } }).lean(); if (migrationsToDelete.length) { this.log(`Removing migration(s) `, `${migrationsToDelete.join(', ')}`.cyan, ` from database`); await MigrationModel.remove({ - name: {$in: migrationsToDelete} + name: { $in: migrationsToDelete } }); } return migrationsToDeleteDocs; - } catch (error) { + } catch(error) { this.log(`Could not prune extraneous migrations from database.`.red); throw error; } @@ -371,11 +379,11 @@ export default class Migrator { */ async list() { await this.sync(); - const migrations = await MigrationModel.find().sort({createdAt: 1}); + const migrations = await MigrationModel.find().sort({ createdAt: 1 }); if (!migrations.length) this.log('There are no migrations to list.'.yellow); return migrations.map((m) => { this.log( - `${m.state == 'up' ? 'UP: \t' : 'DOWN:\t'}`[m.state == 'up' ? 'green' : 'red'] + + `${m.state == 'up' ? 'UP: \t' : 'DOWN:\t'}`[m.state == 'up'? 'green' : 'red'] + ` ${m.filename}` ); return m.toJSON(); @@ -383,11 +391,13 @@ export default class Migrator { } } + + function fileRequired(error) { if (error && error.code == 'ENOENT') { throw new ReferenceError(`Could not find any files at path '${error.path}'`); } } -module.exports = Migrator; +module.exports = Migrator; From 1797555e9b22919e83840d9f0adab4e2edc26e84 Mon Sep 17 00:00:00 2001 From: Anand Date: Thu, 25 May 2017 23:36:14 +0530 Subject: [PATCH 3/5] reformatting --- src/lib.js | 665 +++++++++++++++++++++++++++-------------------------- 1 file changed, 333 insertions(+), 332 deletions(-) diff --git a/src/lib.js b/src/lib.js index 48c597d..04e2715 100644 --- a/src/lib.js +++ b/src/lib.js @@ -11,11 +11,11 @@ import MigrationModelFactory from './db'; let MigrationModel; Promise.config({ - warnings: false + warnings: false }); const es6Template = - ` +` /** * Make any changes you need to make to the database here */ @@ -32,7 +32,7 @@ export async function down () { `; const es5Template = - `'use strict'; +`'use strict'; /** * Make any changes you need to make to the database here @@ -51,352 +51,353 @@ exports.down = function down(done) { export default class Migrator { - constructor({ - templatePath, - migrationsPath = './migrations', - dbConnectionUri, - es6Templates = false, - typescript = false, - collectionName = 'migrations', - autosync = false, - cli = false, - connection - }) { - const defaultTemplate = (typescript || es6Templates) ? es6Template : es5Template; - this.template = templatePath ? fs.readFileSync(templatePath, 'utf-8') : defaultTemplate; - this.migrationPath = path.resolve(migrationsPath); - this.connection = connection || mongoose.createConnection(dbConnectionUri); - this.es6 = es6Templates; - this.typescript = typescript; - this.collection = collectionName; - this.autosync = autosync; - this.cli = cli; - MigrationModel = MigrationModelFactory(collectionName, this.connection, {typescript}); - } - - log (logString, force = false) { - if (force || this.cli) { - console.log(logString); - } - } - - /** - * Use your own Mongoose connection object (so you can use this('modelname') - * @param {mongoose.connection} connection - Mongoose connection - */ - setMongooseConnection (connection) { - MigrationModel = MigrationModelFactory(this.collection, connection) - } - - /** - * Close the underlying connection to mongo - * @returns {Promise} A promise that resolves when connection is closed - */ - close() { - return this.connection ? this.connection.close() : Promise.resolve(); - } - - /** - * Generate name for migration file with right extension - * @param basename - * @return {string} - */ + constructor({ + templatePath, + migrationsPath = './migrations', + dbConnectionUri, + es6Templates = false, + typescript = false, + collectionName = 'migrations', + autosync = false, + cli = false, + connection + }) { + const defaultTemplate = typescript || es6Templates ? es6Template : es5Template; + this.template = templatePath ? fs.readFileSync(templatePath, 'utf-8') : defaultTemplate; + this.migrationPath = path.resolve(migrationsPath); + this.connection = connection || mongoose.createConnection(dbConnectionUri); + this.es6 = es6Templates; + this.typescript = typescript; + this.collection = collectionName; + this.autosync = autosync; + this.cli = cli; + MigrationModel = MigrationModelFactory(collectionName, this.connection, {typescript}); + } + + log (logString, force = false) { + if (force || this.cli) { + console.log(logString); + } + } + + /** + * Use your own Mongoose connection object (so you can use this('modelname') + * @param {mongoose.connection} connection - Mongoose connection + */ + setMongooseConnection (connection) { + MigrationModel = MigrationModelFactory(this.collection, connection) + } + + /** + * Close the underlying connection to mongo + * @returns {Promise} A promise that resolves when connection is closed + */ + close() { + return this.connection ? this.connection.close() : Promise.resolve(); + } + + /** + * Generate name for migration file with right extension + * @param basename + * @return {string} + */ getMigrationFileName(basename) { if (this.typescript) return `${basename}.ts`; return `${basename}.js`; } - /** - * Create a new migration - * @param {string} migrationName - * @returns {Promise} A promise of the Migration created - */ - async create(migrationName) { - try { - const existingMigration = await MigrationModel.findOne({ name: migrationName }); - if (!!existingMigration) { - throw new Error(`There is already a migration with name '${migrationName}' in the database`.red); - } - await this.sync(); - const now = Date.now(); - const newMigrationFile = this.getMigrationFileName(`${now}-${migrationName}`); - mkdirp.sync(this.migrationPath); - fs.writeFileSync(path.join(this.migrationPath, newMigrationFile), this.template); - // create instance in db - await this.connection; - const migrationCreated = await MigrationModel.create({ - name: migrationName, - createdAt: now - }); - this.log(`Created migration ${migrationName} in ${this.migrationPath}.`); - return migrationCreated; - } catch(error){ - this.log(error.stack); - fileRequired(error); - } - } - - /** - * Runs migrations up to or down to a given migration name - * - * @param migrationName - * @param direction - */ - async run(direction = 'up', migrationName) { - await this.sync(); - - const untilMigration = migrationName ? - await MigrationModel.findOne({name: migrationName}) : - await MigrationModel.findOne().sort({createdAt: -1}); - - if (!untilMigration) { - if (migrationName) throw new ReferenceError("Could not find that migration in the database"); - else throw new Error("There are no pending migrations."); - } - - let query = { - createdAt: {$lte: untilMigration.createdAt}, - state: 'down' - }; - - if (direction == 'down') { - query = { - createdAt: {$gte: untilMigration.createdAt}, - state: 'up' - }; - } - - - const sortDirection = direction == 'up' ? 1 : -1; - const migrationsToRun = await MigrationModel.find(query) - .sort({createdAt: sortDirection}); - - if (!migrationsToRun.length) { - if (this.cli) { - this.log('There are no migrations to run'.yellow); - this.log(`Current Migrations' Statuses: `); - await this.list(); - } - throw new Error('There are no migrations to run'); - } - - let self = this; - let numMigrationsRan = 0; - let migrationsRan = []; - - for (const migration of migrationsToRun) { - const migrationFilePath = path.join(self.migrationPath, migration.filename); - if (this.es6) { - require('babel-register')({ - "presets": [require("babel-preset-latest")], - "plugins": [require("babel-plugin-transform-runtime")] - }); - - require('babel-polyfill'); - } + /** + * Create a new migration + * @param {string} migrationName + * @returns {Promise} A promise of the Migration created + */ + async create(migrationName) { + try { + const existingMigration = await MigrationModel.findOne({ name: migrationName }); + if (!!existingMigration) { + throw new Error(`There is already a migration with name '${migrationName}' in the database`.red); + } + + await this.sync(); + const now = Date.now(); + const newMigrationFile = this.getMigrationFileName(`${now}-${migrationName}`); + mkdirp.sync(this.migrationPath); + fs.writeFileSync(path.join(this.migrationPath, newMigrationFile), this.template); + // create instance in db + await this.connection; + const migrationCreated = await MigrationModel.create({ + name: migrationName, + createdAt: now + }); + this.log(`Created migration ${migrationName} in ${this.migrationPath}.`); + return migrationCreated; + } catch(error){ + this.log(error.stack); + fileRequired(error); + } + } + + /** + * Runs migrations up to or down to a given migration name + * + * @param migrationName + * @param direction + */ + async run(direction = 'up', migrationName) { + await this.sync(); + + const untilMigration = migrationName ? + await MigrationModel.findOne({name: migrationName}) : + await MigrationModel.findOne().sort({createdAt: -1}); + + if (!untilMigration) { + if (migrationName) throw new ReferenceError("Could not find that migration in the database"); + else throw new Error("There are no pending migrations."); + } + + let query = { + createdAt: {$lte: untilMigration.createdAt}, + state: 'down' + }; + + if (direction == 'down') { + query = { + createdAt: {$gte: untilMigration.createdAt}, + state: 'up' + }; + } + + + const sortDirection = direction == 'up' ? 1 : -1; + const migrationsToRun = await MigrationModel.find(query) + .sort({createdAt: sortDirection}); + + if (!migrationsToRun.length) { + if (this.cli) { + this.log('There are no migrations to run'.yellow); + this.log(`Current Migrations' Statuses: `); + await this.list(); + } + throw new Error('There are no migrations to run'); + } + + let self = this; + let numMigrationsRan = 0; + let migrationsRan = []; + + for (const migration of migrationsToRun) { + const migrationFilePath = path.join(self.migrationPath, migration.filename); + if (this.es6) { + require('babel-register')({ + "presets": [require("babel-preset-latest")], + "plugins": [require("babel-plugin-transform-runtime")] + }); + + require('babel-polyfill'); + } if (this.typescript) { require("ts-node").register({ - ignoreWarnings: [ - "2345", - "2346", - "2349", - "2350", - "2339", - "2459", - "1015", - "7027"] - }); - } - - let migrationFunctions; - - try { - migrationFunctions = require(migrationFilePath); - } catch (err) { - err.message = err.message && /Unexpected token/.test(err.message) ? - 'Unexpected Token when parsing migration. If you are using an ES6 migration file, use option --es6' : - err.message; - throw err; - } - - if (!migrationFunctions[direction]) { - throw new Error (`The ${direction} export is not defined in ${migration.filename}.`.red); - } - - try { - await new Promise( (resolve, reject) => { - const callPromise = migrationFunctions[direction].call( - this.connection.model.bind(this.connection), - function callback(err) { - if (err) return reject(err); - resolve(); - } - ); - - if (callPromise && typeof callPromise.then === 'function') { - callPromise.then(resolve).catch(reject); - } - }); - - this.log(`${direction.toUpperCase()}: `[direction == 'up'? 'green' : 'red'] + ` ${migration.filename} `); - - await MigrationModel.where({name: migration.name}).update({$set: {state: direction}}); - migrationsRan.push(migration.toJSON()); - numMigrationsRan++; - } catch(err) { - this.log(`Failed to run migration ${migration.name} due to an error.`.red); - this.log(`Not continuing. Make sure your data is in consistent state`.red); - throw err instanceof(Error) ? err : new Error(err); - } - } - - if (migrationsToRun.length == numMigrationsRan) this.log('All migrations finished successfully.'.green); - return migrationsRan; - } - - /** - * Looks at the file system migrations and imports any migrations that are - * on the file system but missing in the database into the database - * - * This functionality is opposite of prune() - */ - async sync() { - try { - const filesInMigrationFolder = fs.readdirSync(this.migrationPath); - const migrationsInDatabase = await MigrationModel.find({}); - // Go over migrations in folder and delete any files not in DB - const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.(js|ts)$/.test(file)) - .map(filename => { - const fileCreatedAt = parseInt(filename.split('-')[0]); - const existsInDatabase = migrationsInDatabase.some(m => filename == m.filename); - return {createdAt: fileCreatedAt, filename, existsInDatabase}; + ignoreWarnings: [ + "2345", + "2346", + "2349", + "2350", + "2339", + "2459", + "1015", + "7027"] }); - - const filesNotInDb = _.filter(migrationsInFolder, {existsInDatabase: false}).map(f => f.filename); - let migrationsToImport = filesNotInDb; - this.log('Synchronizing database with file system migrations...'); - if (!this.autosync && migrationsToImport.length) { - const answers = await new Promise(function (resolve) { - ask.prompt({ - type: 'checkbox', - message: 'The following migrations exist in the migrations folder but not in the database. Select the ones you want to import into the database', - name: 'migrationsToImport', - choices: filesNotInDb - }, (answers) => { - resolve(answers); - }); - }); - - migrationsToImport = answers.migrationsToImport; } - return Promise.map(migrationsToImport, async (migrationToImport) => { - const filePath = path.join(this.migrationPath, migrationToImport), - timestampSeparatorIndex = migrationToImport.indexOf('-'), - timestamp = migrationToImport.slice(0, timestampSeparatorIndex), - migrationName = migrationToImport.slice(timestampSeparatorIndex + 1, migrationToImport.lastIndexOf('.')); - - this.log(`Adding migration ${filePath} into database from file system. State is ` + `DOWN`.red); - const createdMigration = await MigrationModel.create({ - name: migrationName, - createdAt: timestamp - }); - return createdMigration.toJSON(); - }); - } catch (error) { - this.log(`Could not synchronise migrations in the migrations folder up to the database.`.red); - throw error; - } - } - - /** - * Opposite of sync(). - * Removes files in migration directory which don't exist in database. - */ - async prune() { - try { - const filesInMigrationFolder = fs.readdirSync(this.migrationPath); - const migrationsInDatabase = await MigrationModel.find({}); - // Go over migrations in folder and delete any files not in DB - const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.(js|ts)/.test(file) ) - .map(filename => { - const fileCreatedAt = parseInt(filename.split('-')[0]); - const existsInDatabase = migrationsInDatabase.some(m => filename == m.filename); - return { createdAt: fileCreatedAt, filename, existsInDatabase }; - }); - - const dbMigrationsNotOnFs = _.filter(migrationsInDatabase, m => { - return !_.find(migrationsInFolder, { filename: m.filename }) - }); - - - let migrationsToDelete = dbMigrationsNotOnFs.map( m => m.name ); - - if (!this.autosync && !!migrationsToDelete.length) { - const answers = await new Promise(function (resolve) { - ask.prompt({ - type: 'checkbox', - message: 'The following migrations exist in the database but not in the migrations folder. Select the ones you want to remove from the file system.', - name: 'migrationsToDelete', - choices: migrationsToDelete - }, (answers) => { - resolve(answers); - }); - }); - - migrationsToDelete = answers.migrationsToDelete; - } - - const migrationsToDeleteDocs = await MigrationModel - .find({ - name: { $in: migrationsToDelete } - }).lean(); - - if (migrationsToDelete.length) { - this.log(`Removing migration(s) `, `${migrationsToDelete.join(', ')}`.cyan, ` from database`); - await MigrationModel.remove({ - name: { $in: migrationsToDelete } - }); - } - - return migrationsToDeleteDocs; - } catch(error) { - this.log(`Could not prune extraneous migrations from database.`.red); - throw error; - } - } - - /** - * Lists the current migrations and their statuses - * @returns {Promise>} - * @example - * [ - * { name: 'my-migration', filename: '149213223424_my-migration.js', state: 'up' }, - * { name: 'add-cows', filename: '149213223453_add-cows.js', state: 'down' } - * ] - */ - async list() { - await this.sync(); - const migrations = await MigrationModel.find().sort({ createdAt: 1 }); - if (!migrations.length) this.log('There are no migrations to list.'.yellow); - return migrations.map((m) => { - this.log( - `${m.state == 'up' ? 'UP: \t' : 'DOWN:\t'}`[m.state == 'up'? 'green' : 'red'] + - ` ${m.filename}` - ); - return m.toJSON(); - }); - } + let migrationFunctions; + + try { + migrationFunctions = require(migrationFilePath); + } catch (err) { + err.message = err.message && /Unexpected token/.test(err.message) ? + 'Unexpected Token when parsing migration. If you are using an ES6 migration file, use option --es6' : + err.message; + throw err; + } + + if (!migrationFunctions[direction]) { + throw new Error (`The ${direction} export is not defined in ${migration.filename}.`.red); + } + + try { + await new Promise( (resolve, reject) => { + const callPromise = migrationFunctions[direction].call( + this.connection.model.bind(this.connection), + function callback(err) { + if (err) return reject(err); + resolve(); + } + ); + + if (callPromise && typeof callPromise.then === 'function') { + callPromise.then(resolve).catch(reject); + } + }); + + this.log(`${direction.toUpperCase()}: `[direction == 'up'? 'green' : 'red'] + ` ${migration.filename} `); + + await MigrationModel.where({name: migration.name}).update({$set: {state: direction}}); + migrationsRan.push(migration.toJSON()); + numMigrationsRan++; + } catch(err) { + this.log(`Failed to run migration ${migration.name} due to an error.`.red); + this.log(`Not continuing. Make sure your data is in consistent state`.red); + throw err instanceof(Error) ? err : new Error(err); + } + } + + if (migrationsToRun.length == numMigrationsRan) this.log('All migrations finished successfully.'.green); + return migrationsRan; + } + + /** + * Looks at the file system migrations and imports any migrations that are + * on the file system but missing in the database into the database + * + * This functionality is opposite of prune() + */ + async sync() { + try { + const filesInMigrationFolder = fs.readdirSync(this.migrationPath); + const migrationsInDatabase = await MigrationModel.find({}); + // Go over migrations in folder and delete any files not in DB + const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.(js|ts)$/.test(file)) + .map(filename => { + const fileCreatedAt = parseInt(filename.split('-')[0]); + const existsInDatabase = migrationsInDatabase.some(m => filename == m.filename); + return {createdAt: fileCreatedAt, filename, existsInDatabase}; + }); + + const filesNotInDb = _.filter(migrationsInFolder, {existsInDatabase: false}).map(f => f.filename); + let migrationsToImport = filesNotInDb; + this.log('Synchronizing database with file system migrations...'); + if (!this.autosync && migrationsToImport.length) { + const answers = await new Promise(function (resolve) { + ask.prompt({ + type: 'checkbox', + message: 'The following migrations exist in the migrations folder but not in the database. Select the ones you want to import into the database', + name: 'migrationsToImport', + choices: filesNotInDb + }, (answers) => { + resolve(answers); + }); + }); + + migrationsToImport = answers.migrationsToImport; + } + + return Promise.map(migrationsToImport, async (migrationToImport) => { + const filePath = path.join(this.migrationPath, migrationToImport), + timestampSeparatorIndex = migrationToImport.indexOf('-'), + timestamp = migrationToImport.slice(0, timestampSeparatorIndex), + migrationName = migrationToImport.slice(timestampSeparatorIndex + 1, migrationToImport.lastIndexOf('.')); + + this.log(`Adding migration ${filePath} into database from file system. State is ` + `DOWN`.red); + const createdMigration = await MigrationModel.create({ + name: migrationName, + createdAt: timestamp + }); + return createdMigration.toJSON(); + }); + } catch (error) { + this.log(`Could not synchronise migrations in the migrations folder up to the database.`.red); + throw error; + } + } + + /** + * Opposite of sync(). + * Removes files in migration directory which don't exist in database. + */ + async prune() { + try { + const filesInMigrationFolder = fs.readdirSync(this.migrationPath); + const migrationsInDatabase = await MigrationModel.find({}); + // Go over migrations in folder and delete any files not in DB + const migrationsInFolder = _.filter(filesInMigrationFolder, file => /\d{13,}\-.+.(js|ts)/.test(file) ) + .map(filename => { + const fileCreatedAt = parseInt(filename.split('-')[0]); + const existsInDatabase = migrationsInDatabase.some(m => filename == m.filename); + return { createdAt: fileCreatedAt, filename, existsInDatabase }; + }); + + const dbMigrationsNotOnFs = _.filter(migrationsInDatabase, m => { + return !_.find(migrationsInFolder, { filename: m.filename }) + }); + + + let migrationsToDelete = dbMigrationsNotOnFs.map( m => m.name ); + + if (!this.autosync && !!migrationsToDelete.length) { + const answers = await new Promise(function (resolve) { + ask.prompt({ + type: 'checkbox', + message: 'The following migrations exist in the database but not in the migrations folder. Select the ones you want to remove from the file system.', + name: 'migrationsToDelete', + choices: migrationsToDelete + }, (answers) => { + resolve(answers); + }); + }); + + migrationsToDelete = answers.migrationsToDelete; + } + + const migrationsToDeleteDocs = await MigrationModel + .find({ + name: { $in: migrationsToDelete } + }).lean(); + + if (migrationsToDelete.length) { + this.log(`Removing migration(s) `, `${migrationsToDelete.join(', ')}`.cyan, ` from database`); + await MigrationModel.remove({ + name: { $in: migrationsToDelete } + }); + } + + return migrationsToDeleteDocs; + } catch(error) { + this.log(`Could not prune extraneous migrations from database.`.red); + throw error; + } + } + + /** + * Lists the current migrations and their statuses + * @returns {Promise>} + * @example + * [ + * { name: 'my-migration', filename: '149213223424_my-migration.js', state: 'up' }, + * { name: 'add-cows', filename: '149213223453_add-cows.js', state: 'down' } + * ] + */ + async list() { + await this.sync(); + const migrations = await MigrationModel.find().sort({ createdAt: 1 }); + if (!migrations.length) this.log('There are no migrations to list.'.yellow); + return migrations.map((m) => { + this.log( + `${m.state == 'up' ? 'UP: \t' : 'DOWN:\t'}`[m.state == 'up'? 'green' : 'red'] + + ` ${m.filename}` + ); + return m.toJSON(); + }); + } } function fileRequired(error) { - if (error && error.code == 'ENOENT') { - throw new ReferenceError(`Could not find any files at path '${error.path}'`); - } + if (error && error.code == 'ENOENT') { + throw new ReferenceError(`Could not find any files at path '${error.path}'`); + } } From 8823e87751e979a4529af725bcebf8d8ed4746bc Mon Sep 17 00:00:00 2001 From: Anand Date: Fri, 26 May 2017 17:15:21 +0530 Subject: [PATCH 4/5] disable warnings --- src/lib.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/lib.js b/src/lib.js index 04e2715..8ccaff1 100644 --- a/src/lib.js +++ b/src/lib.js @@ -199,15 +199,7 @@ export default class Migrator { if (this.typescript) { require("ts-node").register({ - ignoreWarnings: [ - "2345", - "2346", - "2349", - "2350", - "2339", - "2459", - "1015", - "7027"] + disableWarnings: true }); } From 9ab92e51813a276e1539c38f5ced5482ed31841e Mon Sep 17 00:00:00 2001 From: Anand Date: Fri, 26 May 2017 17:18:20 +0530 Subject: [PATCH 5/5] correct indentation --- src/lib.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.js b/src/lib.js index 8ccaff1..0586321 100644 --- a/src/lib.js +++ b/src/lib.js @@ -197,11 +197,11 @@ export default class Migrator { require('babel-polyfill'); } - if (this.typescript) { - require("ts-node").register({ - disableWarnings: true - }); - } + if (this.typescript) { + require("ts-node").register({ + disableWarnings: true + }); + } let migrationFunctions;