diff --git a/lib/migrate/MigrationGenerator.js b/lib/migrate/MigrationGenerator.js index fc9a44a798..3235c70ac8 100644 --- a/lib/migrate/MigrationGenerator.js +++ b/lib/migrate/MigrationGenerator.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const mkdirp = require('mkdirp'); const bluebird = require('bluebird'); -const { template } = require('lodash'); +const { writeJsFileUsingTemplate } = require('../util/template'); const { getMergedConfig } = require('./configuration-merger'); class MigrationGenerator { @@ -11,17 +11,15 @@ class MigrationGenerator { } // Creates a new migration, with a given name. - make(name, config) { + async make(name, config) { this.config = getMergedConfig(config, this.config); if (!name) { return Promise.reject( new Error('A name must be specified for the generated migration') ); } - - return this._ensureFolder(config) - .then((val) => this._generateStubTemplate(val)) - .then((val) => this._writeNewMigration(name, val)); + await this._ensureFolder(config); + await this._writeNewMigration(name, config); } // Ensures a folder for the migrations exist, dependent on the migration @@ -38,34 +36,32 @@ class MigrationGenerator { return Promise.all(promises); } - // Generates the stub template for the current migration, returning a compiled - // template. - _generateStubTemplate() { - const stubPath = - this.config.stub || + _getStubPath() { + return this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub'); + } - return bluebird - .promisify(fs.readFile, { context: fs })(stubPath) - .then((stub) => template(stub.toString(), { variable: 'd' })); + _getNewMigrationName(name) { + if (name[0] === '-') name = name.slice(1); + return yyyymmddhhmmss() + '_' + name + '.' + this.config.extension; } - // Write a new migration to disk, using the config and generated filename, - // passing any `variables` given in the config to the template. - _writeNewMigration(name, tmpl) { - const { config } = this; + _getNewMigrationPath(name) { + const fileName = this._getNewMigrationName(name); const dirs = this._absoluteConfigDirs(); const dir = dirs.slice(-1)[0]; // Get last specified directory + return path.join(dir, fileName); + } - if (name[0] === '-') name = name.slice(1); - const filename = yyyymmddhhmmss() + '_' + name + '.' + config.extension; - - return bluebird - .promisify(fs.writeFile, { context: fs })( - path.join(dir, filename), - tmpl(config.variables || {}) - ) - .return(path.join(dir, filename)); + // Write a new migration to disk, using the config and generated filename, + // passing any `variables` given in the config to the template. + _writeNewMigration(name, config) { + return writeJsFileUsingTemplate( + this._getNewMigrationPath(name), + this._getStubPath(), + { variable: 'd' }, + config.variables || {} + ); } _absoluteConfigDirs() { diff --git a/lib/seed/Seeder.js b/lib/seed/Seeder.js index 046b4377d2..285e24166d 100644 --- a/lib/seed/Seeder.js +++ b/lib/seed/Seeder.js @@ -14,6 +14,7 @@ const { each, extend, } = require('lodash'); +const { writeJsFileUsingTemplate } = require('../util/template'); // The new seeds we're performing, typically called from the `knex.seed` // interface on the main `knex` object. Passes the `knex` instance performing @@ -39,10 +40,9 @@ class Seeder { async make(name, config) { this.config = this.setConfig(config); if (!name) - throw new Error('A name must be specified for the generated seed') + throw new Error('A name must be specified for the generated seed'); await this._ensureFolder(config); - const tmpl = await this._generateStubTemplate(); - await this._writeNewSeed(name)(tmpl); + await this._writeNewSeed(name); } // Lists all available seed files as a sorted array. @@ -96,8 +96,10 @@ class Seeder { } _getStubPath() { - return this.config.stub || - path.join(__dirname, 'stub', this.config.extension + '.stub'); + return ( + this.config.stub || + path.join(__dirname, 'stub', this.config.extension + '.stub') + ); } // Generates the stub template for the current seed file, returning a compiled template. @@ -108,19 +110,24 @@ class Seeder { ); } + _getNewStubFileName(name) { + if (name[0] === '-') name = name.slice(1); + return name + '.' + this.config.extension; + } + + _getNewStubFilePath(name) { + return path.join(this._absoluteConfigDir(), this._getNewStubFileName(name)); + } + // Write a new seed to disk, using the config and generated filename, // passing any `variables` given in the config to the template. _writeNewSeed(name) { - const { config } = this; - const dir = this._absoluteConfigDir(); - return function(tmpl) { - if (name[0] === '-') name = name.slice(1); - const filename = name + '.' + config.extension; - return Bluebird.promisify(fs.writeFile, { context: fs })( - path.join(dir, filename), - tmpl(config.variables || {}) - ).return(path.join(dir, filename)); - }; + return writeJsFileUsingTemplate( + this._getNewStubFilePath(name), + this._getStubPath(), + { variable: 'd' }, + this.config.variables || {} + ); } // Runs a batch of seed files. diff --git a/lib/util/template.js b/lib/util/template.js new file mode 100644 index 0000000000..677217a049 --- /dev/null +++ b/lib/util/template.js @@ -0,0 +1,51 @@ +const { template } = require('lodash'); +const { promisify } = require('util'); +const fs = require('fs'); + +/** + * Light wrapper over lodash templates making it safer to be used with javascript source code. + * + * In particular, doesn't interfere with use of interpolated strings in javascript. + * + * @param {string} content Template source + * @param {_.TemplateOptions} options Template options + */ +const jsSourceTemplate = (content, options) => + template(content, { + interpolate: /<%=([\s\S]+?)%>/g, + ...options, + }); + +const readFile = promisify(fs.readFile, { context: fs }); +const writeFile = promisify(fs.writeFile, { context: fs }); + +/** + * Compile the contents of specified (javascript) file as a lodash template + * + * @param {string} filePath Path of file to be used as template + * @param {_.TemplateOptions} options Lodash template options + */ +const jsFileTemplate = async (filePath, options) => { + const contentBuffer = await readFile(filePath); + return jsSourceTemplate(contentBuffer.toString(), options); +}; + +/** + * Write a javascript file using another file as a (lodash) template + * + * @param {string} targetFilePath + * @param {string} sourceFilePath + * @param {_.TemplateOptions} options options passed to lodash templates + */ +const writeJsFileUsingTemplate = async ( + targetFilePath, + sourceFilePath, + options, + variables +) => writeFile(targetFilePath, (await jsFileTemplate(sourceFilePath, options))(variables)); + +module.exports = { + jsSourceTemplate, + jsFileTemplate, + writeJsFileUsingTemplate +};