Skip to content

Commit

Permalink
Prevent lodash.template from interfering with javascript string inter…
Browse files Browse the repository at this point in the history
…polation

Closes #3468
  • Loading branch information
lorefnon committed Oct 12, 2019
1 parent ce81185 commit 181b139
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 42 deletions.
50 changes: 23 additions & 27 deletions lib/migrate/MigrationGenerator.js
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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() {
Expand Down
37 changes: 22 additions & 15 deletions lib/seed/Seeder.js
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down
51 changes: 51 additions & 0 deletions 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
};

0 comments on commit 181b139

Please sign in to comment.