diff --git a/README.md b/README.md index 2469b8ea..3e3c4c75 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,14 @@ const umzug = new Umzug({ // read raw sql etc. // See https://github.com/sequelize/umzug/tree/master/test/fixtures // for examples. - customResolver: function (sqlPath) { - return { up: () => sequelize.query(require('fs').readFileSync(sqlPath, 'utf8')) } + customResolver: function (sqlPath) { + return { up: () => sequelize.query(require('fs').readFileSync(sqlPath, 'utf8')) }; + } + + // A function that receives the file path of the migration and returns the name of the + // migration. This can be used to remove file extensions for example. + nameFormatter: function (filePath) { + return path.parse(filePath).name; } } }) diff --git a/src/index.js b/src/index.js index 42ec76dc..c9e34940 100644 --- a/src/index.js +++ b/src/index.js @@ -45,6 +45,9 @@ module.exports = class Umzug extends EventEmitter { * function that specifies how to get a migration object from a path. This * should return an object of the form { up: Function, down: Function }. * Without this defined, a regular javascript import will be performed. + * @param {Migration~nameFormatter} [options.migrations.nameFormatter] - A + * function that receives the file path of the migration and returns the name + * of the migration. This can be used to remove file extensions for example. * @constructs Umzug */ constructor (options = {}) { @@ -155,7 +158,7 @@ module.exports = class Umzug extends EventEmitter { * @returns {Promise.} */ executed () { - return Bluebird.resolve(this.storage.executed()).bind(this).map((file) => new Migration(file)); + return Bluebird.resolve(this.storage.executed()).bind(this).map((file) => new Migration(file, this.options)); } /** diff --git a/src/migration.js b/src/migration.js index bff190ed..89ea4d29 100644 --- a/src/migration.js +++ b/src/migration.js @@ -29,12 +29,25 @@ module.exports = class Migration { * function that specifies how to get a migration object from a path. This * should return an object of the form { up: Function, down: Function }. * Without this defined, a regular javascript import will be performed. + * @param {Migration~nameFormatter} [options.migrations.nameFormatter] - A + * function that receives the file path of the migration and returns the name + * of the migration. This can be used to remove file extensions for example. * @constructs Migration */ - constructor (path, options) { + constructor (path, options = {}) { this.path = _path.resolve(path); - this.file = _path.basename(this.path); - this.options = options; + this.options = { + ...options, + migrations: { + nameFormatter: (path) => _path.basename(path), + ...options.migrations, + }, + }; + + this.file = this.options.migrations.nameFormatter(this.path); + if (typeof this.file !== 'string') { + throw new Error(`Unexpected migration formatter result for '${this.path}': expected string, got ${typeof this.file}`); + } } /** @@ -92,7 +105,8 @@ module.exports = class Migration { * @returns {boolean} */ testFileName (needle) { - return this.file.indexOf(needle) === 0; + const formattedNeedle = this.options.migrations.nameFormatter(needle); + return this.file.indexOf(formattedNeedle) === 0; } /** diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 44349362..d14efa1f 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -1,5 +1,5 @@ import { readFileSync } from 'fs'; -import { resolve, dirname, join } from 'path'; +import { resolve, dirname, join, parse } from 'path'; import { expect } from 'chai'; import Sequelize from 'sequelize'; import typescript from 'typescript'; @@ -32,6 +32,7 @@ describe('custom resolver', () => { ], pattern: this.pattern, customResolver: this.customResolver, + nameFormatter: (path) => parse(path).name, }, storage: 'sequelize', storageOptions: { @@ -49,6 +50,11 @@ describe('custom resolver', () => { expect(tables.sort()).to.deep.equal(['SequelizeMeta', 'thing', 'user']); }; + this.verifyMeta = async () => { + const [meta] = await this.sequelize.query('select * from `SequelizeMeta`'); + + expect(meta).to.deep.equal([ { name: '1.users' }, { name: '2.things' } ]); + }; }); it('resolves javascript files if no custom resolver is defined', async function () { @@ -59,6 +65,7 @@ describe('custom resolver', () => { await this.umzug().up(); await this.verifyTables(); + await this.verifyMeta(); }); it('an array of migrations created manually can be passed in', async function () { @@ -69,6 +76,7 @@ describe('custom resolver', () => { downName: 'down', migrations: { wrap: fn => () => fn(this.sequelize.getQueryInterface(), this.sequelize.constructor), + nameFormatter: (path) => parse(path).name, }, }), new Migration(require.resolve('./javascript/2.things'), { @@ -76,6 +84,7 @@ describe('custom resolver', () => { downName: 'down', migrations: { wrap: fn => () => fn(this.sequelize.getQueryInterface(), this.sequelize.constructor), + nameFormatter: (path) => parse(path).name, }, }), ], @@ -89,6 +98,7 @@ describe('custom resolver', () => { await umzug.up(); await this.verifyTables(); + await this.verifyMeta(); }); it('can resolve sql files', async function () { @@ -101,6 +111,7 @@ describe('custom resolver', () => { await this.umzug().up(); await this.verifyTables(); + await this.verifyMeta(); }); it('can resolve typescript files', async function () { @@ -120,6 +131,7 @@ describe('custom resolver', () => { await this.umzug().up(); await this.verifyTables(); + await this.verifyMeta(); }); it('can resolve coffeescript files', async function () { @@ -139,5 +151,6 @@ describe('custom resolver', () => { await this.umzug().up(); await this.verifyTables(); + await this.verifyMeta(); }); });