diff --git a/bin/cli.js b/bin/cli.js index 08c2c178a9..5c7597dce2 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -18,9 +18,10 @@ const { success, checkLocalModule, getMigrationExtension, + getSeedExtension, getStubPath, } = require('./utils/cli-config-utils'); -const { DEFAULT_EXT } = require('./utils/constants'); + const { listMigrations } = require('./utils/migrationsLister'); function initKnex(env, opts) { @@ -157,7 +158,7 @@ function invoke(env) { const ext = getMigrationExtension(env, opts); const configOverrides = { extension: ext }; - const stub = getStubPath(env, opts); + const stub = getStubPath('migrations', env, opts); if (stub) { configOverrides.stub = stub; } @@ -292,17 +293,23 @@ function invoke(env) { `-x [${filetypes.join('|')}]`, 'Specify the stub extension (default js)' ) + .option( + `--stub [|]`, + 'Specify the seed stub to use. If using the file must be located in config.seeds.directory' + ) .action((name) => { const opts = commander.opts(); opts.client = opts.client || 'sqlite3'; // We don't really care about client when creating seeds const instance = initKnex(env, opts); - const ext = ( - argv.x || - env.configuration.ext || - DEFAULT_EXT - ).toLowerCase(); + const ext = getSeedExtension(env, opts); + const configOverrides = { extension: ext }; + const stub = getStubPath('seeds', env, opts); + if (stub) { + configOverrides.stub = stub; + } + pending = instance.seed - .make(name, { extension: ext }) + .make(name, configOverrides) .then((name) => { success(color.green(`Created seed file: ${name}`)); }) diff --git a/bin/utils/cli-config-utils.js b/bin/utils/cli-config-utils.js index 0635dcae2c..a00ef72a45 100644 --- a/bin/utils/cli-config-utils.js +++ b/bin/utils/cli-config-utils.js @@ -119,9 +119,23 @@ function getMigrationExtension(env, opts) { return ext.toLowerCase(); } -function getStubPath(env, opts) { +function getSeedExtension(env, opts) { const config = resolveEnvironmentConfig(opts, env.configuration); - const stubDirectory = config.migrations && config.migrations.directory; + + let ext = DEFAULT_EXT; + if (argv.x) { + ext = argv.x; + } else if (config.seeds && config.seeds.extension) { + ext = config.seeds.extension; + } else if (config.ext) { + ext = config.ext; + } + return ext.toLowerCase(); +} + +function getStubPath(configKey, env, opts) { + const config = resolveEnvironmentConfig(opts, env.configuration); + const stubDirectory = config[configKey] && config[configKey].directory; const { stub } = argv; if (!stub) { @@ -131,10 +145,10 @@ function getStubPath(env, opts) { return stub; } - // using stub must have config.migrations.directory defined + // using stub must have config[configKey].directory defined if (!stubDirectory) { console.log(color.red('Failed to load stub'), color.magenta(stub)); - exit('config.migrations.directory in knexfile must be defined'); + exit(`config.${configKey}.directory in knexfile must be defined`); } return path.join(stubDirectory, stub); @@ -147,6 +161,7 @@ module.exports = { exit, success, checkLocalModule, + getSeedExtension, getMigrationExtension, getStubPath, }; diff --git a/lib/migrate/MigrationGenerator.js b/lib/migrate/MigrationGenerator.js index 3235c70ac8..bd2426e27c 100644 --- a/lib/migrate/MigrationGenerator.js +++ b/lib/migrate/MigrationGenerator.js @@ -1,7 +1,7 @@ const fs = require('fs'); const path = require('path'); const mkdirp = require('mkdirp'); -const bluebird = require('bluebird'); +const {promisify} = require('util'); const { writeJsFileUsingTemplate } = require('../util/template'); const { getMergedConfig } = require('./configuration-merger'); @@ -18,8 +18,9 @@ class MigrationGenerator { new Error('A name must be specified for the generated migration') ); } - await this._ensureFolder(config); - await this._writeNewMigration(name, config); + await this._ensureFolder(); + const createdMigrationFilePath = await this._writeNewMigration(name); + return createdMigrationFilePath; } // Ensures a folder for the migrations exist, dependent on the migration @@ -28,9 +29,8 @@ class MigrationGenerator { const dirs = this._absoluteConfigDirs(); const promises = dirs.map((dir) => { - return bluebird - .promisify(fs.stat, { context: fs })(dir) - .catch(() => bluebird.promisify(mkdirp)(dir)); + return promisify(fs.stat)(dir) + .catch(() => promisify(mkdirp)(dir)); }); return Promise.all(promises); @@ -55,13 +55,15 @@ class MigrationGenerator { // 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), + async _writeNewMigration(name) { + const migrationPath = this._getNewMigrationPath(name); + await writeJsFileUsingTemplate( + migrationPath, this._getStubPath(), { variable: 'd' }, - config.variables || {} + this.config.variables || {} ); + return migrationPath; } _absoluteConfigDirs() { diff --git a/lib/seed/Seeder.js b/lib/seed/Seeder.js index 285e24166d..c94cb2da7f 100644 --- a/lib/seed/Seeder.js +++ b/lib/seed/Seeder.js @@ -42,7 +42,8 @@ class Seeder { if (!name) throw new Error('A name must be specified for the generated seed'); await this._ensureFolder(config); - await this._writeNewSeed(name); + const seedPath = await this._writeNewSeed(name); + return seedPath; } // Lists all available seed files as a sorted array. @@ -121,13 +122,15 @@ class Seeder { // Write a new seed to disk, using the config and generated filename, // passing any `variables` given in the config to the template. - _writeNewSeed(name) { - return writeJsFileUsingTemplate( - this._getNewStubFilePath(name), + async _writeNewSeed(name) { + const seedPath = this._getNewStubFilePath(name); + await writeJsFileUsingTemplate( + seedPath, this._getStubPath(), { variable: 'd' }, this.config.variables || {} ); + return seedPath; } // Runs a batch of seed files. diff --git a/test/cli/cli-test-utils.js b/test/cli/cli-test-utils.js index 7a1c81a98e..3345872c2c 100644 --- a/test/cli/cli-test-utils.js +++ b/test/cli/cli-test-utils.js @@ -29,15 +29,40 @@ function migrationStubOptionSetup(fileHelper) { return { migrationGlobPath }; } -function expectMigrationMatchesStub(stubPath, migrationGlobPath, fileHelper) { +function seedStubOptionSetup(fileHelper) { + const seedGlobPath = 'test/jake-util/knexfile_seeds/*.js'; + + fileHelper.registerGlobForCleanup(seedGlobPath); + fileHelper.createFile( + process.cwd() + '/knexfile.js', + ` + module.exports = { + development: { + client: 'sqlite3', + connection: { + filename: __dirname + '/test/jake-util/test.sqlite3', + }, + seeds: { + directory: __dirname + '/test/jake-util/knexfile_seeds', + }, + } + }; + `, + { isPathAbsolute: true } + ); + + return { seedGlobPath }; +} + +function expectContentMatchesStub(stubPath, globPath, fileHelper) { // accepts full or relative stub path const relativeStubPath = stubPath.replace('test/jake-util/', ''); const stubContent = fileHelper.getFileTextContent(relativeStubPath); - const [migrationContent] = fileHelper.getFileGlobTextContent( - migrationGlobPath + const [content] = fileHelper.getFileGlobTextContent( + globPath ); - expect(migrationContent).equals(stubContent); + expect(content).equals(stubContent); } function setupFileHelper() { @@ -51,7 +76,8 @@ function setupFileHelper() { } module.exports = { - expectMigrationMatchesStub, + expectContentMatchesStub, migrationStubOptionSetup, + seedStubOptionSetup, setupFileHelper, }; diff --git a/test/cli/migrate-make.spec.js b/test/cli/migrate-make.spec.js index 8557eeecd2..5b3f0e7a49 100644 --- a/test/cli/migrate-make.spec.js +++ b/test/cli/migrate-make.spec.js @@ -7,7 +7,7 @@ const { expect } = require('chai'); const KNEX = path.normalize(__dirname + '/../../bin/cli.js'); const { migrationStubOptionSetup, - expectMigrationMatchesStub, + expectContentMatchesStub, setupFileHelper, } = require('./cli-test-utils'); @@ -252,7 +252,7 @@ development: { 'test/jake-util/knexfile_migrations/*_somename.js' ); expect(fileCount).to.equal(1); - expectMigrationMatchesStub(stubPath, migrationGlobPath, fileHelper); + expectContentMatchesStub(stubPath, migrationGlobPath, fileHelper); }); it('Create a new migration with stub parameter in knexfile', async () => { @@ -273,7 +273,7 @@ development: { const stubName = 'table.stub'; const stubPath = `test/jake-util/knexfile-stubs/${stubName}`; expect(fileCount).to.equal(1); - expectMigrationMatchesStub(stubPath, migrationGlobPath, fileHelper); + expectContentMatchesStub(stubPath, migrationGlobPath, fileHelper); }); it('Create a new migration with --stub in config.migrations.directory', async () => { @@ -293,7 +293,7 @@ development: { 'test/jake-util/knexfile_migrations/*_somename.js' ); expect(fileCount).to.equal(1); - expectMigrationMatchesStub(stubPath, migrationGlobPath, fileHelper); + expectContentMatchesStub(stubPath, migrationGlobPath, fileHelper); }); it('Create a new migration with --stub when file does not exist', async () => { diff --git a/test/cli/seed-make.spec.js b/test/cli/seed-make.spec.js new file mode 100644 index 0000000000..996383eef4 --- /dev/null +++ b/test/cli/seed-make.spec.js @@ -0,0 +1,349 @@ +'use strict'; + +const path = require('path'); +const { execCommand } = require('cli-testlab'); +const { expect } = require('chai'); + +const KNEX = path.normalize(__dirname + '/../../bin/cli.js'); + +const NODE = 'node'; +// To enable debug change to: +// const NODE = 'node --inspect-brk'; + +const { + seedStubOptionSetup, + expectContentMatchesStub, + setupFileHelper, +} = require('./cli-test-utils'); + +describe('seed:make', () => { + describe('-x option: make seed using a specific extension', () => { + /** + * @type FileTestHelper + */ + let fileHelper; + beforeEach(() => { + fileHelper = setupFileHelper(); + }); + + afterEach(() => { + fileHelper.cleanup(); + }); + + before(() => { + process.env.KNEX_PATH = '../knex.js'; + }); + + it('Creates new seed with js knexfile passed', async () => { + fileHelper.registerGlobForCleanup( + 'test/jake-util/knexfile_seeds/somename.js' + ); + + await execCommand( + `${NODE} ${KNEX} seed:make somename --knexfile=test/jake-util/knexfile/knexfile.js --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile_seeds/somename.js' + ); + expect(fileCount).to.equal(1); + }); + + it('Creates new seed with ts knexfile passed', async () => { + fileHelper.registerGlobForCleanup( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + await execCommand( + `${NODE} ${KNEX} seed:make somename --knexfile=test/jake-util/knexfile-ts/knexfile.ts --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + expect(fileCount).to.equal(1); + }); + + it('Creates new seed with default knexfile', async () => { + fileHelper.registerGlobForCleanup( + 'test/jake-util/knexfile_seeds/somename.js' + ); + fileHelper.createFile( + process.cwd() + '/knexfile.js', + ` +module.exports = { + client: 'sqlite3', + connection: { + filename: __dirname + '/test/jake-util/test.sqlite3', + }, + seeds: { + directory: __dirname + '/test/jake-util/knexfile_seeds', + }, +}; + `, + { isPathAbsolute: true } + ); + await execCommand( + `${NODE} ${KNEX} seed:make somename --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile_seeds/somename.js' + ); + expect(fileCount).to.equal(1); + }); + + it('Creates new seed with default ts knexfile', async () => { + fileHelper.registerGlobForCleanup( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + fileHelper.createFile( + process.cwd() + '/knexfile.ts', + ` +module.exports = { + client: 'sqlite3', + connection: { + filename: __dirname + '/test/jake-util/test.sqlite3', + }, + seeds: { + directory: __dirname + '/test/jake-util/knexfile_seeds', + }, +}; + `, + { isPathAbsolute: true } + ); + await execCommand( + `${NODE} ${KNEX} seed:make somename --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + expect(fileCount).to.equal(1); + }); + + it('Creates new seed with ts extension using -x switch', async () => { + fileHelper.registerGlobForCleanup( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + await execCommand( + `${NODE} ${KNEX} seed:make somename -x ts --knexfile=test/jake-util/knexfile/knexfile.js --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + expect(fileCount).to.equal(1); + }); + + it('Creates new seed with ts extension using "extension" knexfile param', async () => { + fileHelper.registerGlobForCleanup( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + fileHelper.createFile( + process.cwd() + '/knexfile.js', + ` +module.exports = { + client: 'sqlite3', + connection: { + filename: __dirname + '/test/jake-util/test.sqlite3', + }, + seeds: { + extension: 'ts', + directory: __dirname + '/test/jake-util/knexfile_seeds', + }, +}; + `, + { isPathAbsolute: true } + ); + await execCommand( + `${NODE} ${KNEX} seed:make somename --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + expect(fileCount).to.equal(1); + }); + + it('Creates new seed with ts extension using environment "extension" knexfile param', async () => { + fileHelper.registerGlobForCleanup( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + fileHelper.createFile( + process.cwd() + '/knexfile.js', + ` +module.exports = { +development: { + client: 'sqlite3', + connection: { + filename: __dirname + '/test/jake-util/test.sqlite3', + }, + seeds: { + extension: 'ts', + directory: __dirname + '/test/jake-util/knexfile_seeds', + }, + } +}; + `, + { isPathAbsolute: true } + ); + await execCommand( + `${NODE} ${KNEX} seed:make somename --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile_seeds/somename.ts' + ); + expect(fileCount).to.equal(1); + }); + }); + + describe('--stub option: make seed using specific stub file', () => { + /** + * @type FileTestHelper + */ + let fileHelper; + beforeEach(() => { + fileHelper = setupFileHelper(); + }); + + afterEach(() => { + fileHelper.cleanup(); + }); + + before(() => { + process.env.KNEX_PATH = '../knex.js'; + }); + + it('Creates a new seed using --stub relative to the knexfile', async () => { + const { seedGlobPath } = seedStubOptionSetup(fileHelper); + const stubPath = 'test/jake-util/knexfile-stubs/seed.stub'; + + await execCommand( + `${NODE} ${KNEX} seed:make somename --stub ${stubPath} --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile_seeds/somename.js' + ); + expect(fileCount).to.equal(1); + expectContentMatchesStub(stubPath, seedGlobPath, fileHelper); + }); + + it('Creates a new seed with stub parameter in knexfile', async () => { + const seedGlobPath = 'test/jake-util/knexfile-stubs/somename.js'; + fileHelper.registerGlobForCleanup(seedGlobPath); + + await execCommand( + `${NODE} ${KNEX} seed:make somename --knexfile=test/jake-util/knexfile-stubs/knexfile.js --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile-stubs/somename.js' + ); + + const stubName = 'seed.stub'; + const stubPath = `test/jake-util/knexfile-stubs/${stubName}`; + expect(fileCount).to.equal(1); + expectContentMatchesStub(stubPath, seedGlobPath, fileHelper); + }); + + it('Creates a new seed with --stub in config.seeds.directory', async () => { + const { seedGlobPath } = seedStubOptionSetup(fileHelper); + + const stubName = 'seed2.stub'; + const stubPath = `test/jake-util/knexfile_seeds/${stubName}`; + + await execCommand( + `${NODE} ${KNEX} seed:make somename --stub ${stubName} --knexpath=../knex.js`, + { + expectedOutput: 'Created seed file', + } + ); + + const fileCount = fileHelper.fileGlobExists( + 'test/jake-util/knexfile_seeds/somename.js' + ); + expect(fileCount).to.equal(1); + expectContentMatchesStub(stubPath, seedGlobPath, fileHelper); + }); + + it('fails to create a new seed with --stub when file does not exist', async () => { + seedStubOptionSetup(fileHelper); + const stubName = 'non-existat.stub'; + await execCommand( + `${NODE} ${KNEX} seed:make somename --stub ${stubName} --knexpath=../knex.js`, + { + expectedErrorMessage: 'ENOENT:', + } + ); + }); + + it('fails to create a new seed with --stub when file does not exist', async () => { + seedStubOptionSetup(fileHelper); + const stubPath = '/path/non-existat.stub'; + await execCommand( + `${NODE} ${KNEX} seed:make somename --stub ${stubPath} --knexpath=../knex.js`, + { + expectedErrorMessage: 'ENOENT:', + } + ); + }); + + it('fails to create a seed with --stub when config.seeds.directory not defined', async () => { + fileHelper.createFile( + process.cwd() + '/knexfile.js', + ` + module.exports = { + development: { + client: 'sqlite3', + connection: { + filename: __dirname + '/test/jake-util/test.sqlite3', + }, + seeds: { + // directory not defined + }, + } + }; + `, + { isPathAbsolute: true } + ); + + const stubName = 'table-foreign.stub'; + await execCommand( + `${NODE} ${KNEX} seed:make somename --stub ${stubName} --knexpath=../knex.js`, + { + expectedErrorMessage: + 'config.seeds.directory in knexfile must be defined', + } + ); + }); + }); +}); diff --git a/test/index.js b/test/index.js index 0a9111719a..6f1a722632 100644 --- a/test/index.js +++ b/test/index.js @@ -86,5 +86,6 @@ describe('CLI tests', function() { this.timeout(process.env.KNEX_TEST_TIMEOUT || 5000); require('./cli/knexfile-test.spec'); require('./cli/migrate-make.spec'); + require('./cli/seed-make.spec'); require('./cli/version.spec'); }); diff --git a/test/jake-util/knexfile-stubs/knexfile.js b/test/jake-util/knexfile-stubs/knexfile.js index 872a2cf6d8..3b8cb7e16d 100644 --- a/test/jake-util/knexfile-stubs/knexfile.js +++ b/test/jake-util/knexfile-stubs/knexfile.js @@ -7,4 +7,8 @@ module.exports = { directory: __dirname + '/../knexfile-stubs', stub: 'table.stub', }, + seeds: { + directory: __dirname + '/../knexfile-stubs', + stub: 'seed.stub' + }, }; diff --git a/test/jake-util/knexfile-stubs/seed.stub b/test/jake-util/knexfile-stubs/seed.stub new file mode 100644 index 0000000000..0ac77c852e --- /dev/null +++ b/test/jake-util/knexfile-stubs/seed.stub @@ -0,0 +1,14 @@ +const tableName = 'table_name'; + +exports.seed = (knex) => { + // Deletes ALL existing entries + return knex(`tbl_${tableName}`).del() + .then(() => { + // Inserts seed entries + return knex(`tbl_${tableName}`).insert([ + {id: 1, colName: 'rowValue1'}, + {id: 2, colName: 'rowValue2'}, + {id: 3, colName: 'rowValue3'} + ]); + }); +}; diff --git a/test/jake-util/knexfile-ts/knexfile.ts b/test/jake-util/knexfile-ts/knexfile.ts index dcb3e598d8..5149c621c1 100644 --- a/test/jake-util/knexfile-ts/knexfile.ts +++ b/test/jake-util/knexfile-ts/knexfile.ts @@ -6,4 +6,7 @@ module.exports = { migrations: { directory: __dirname + '/../knexfile_migrations', }, + seeds: { + directory: __dirname + '/../knexfile_seeds', + }, }; diff --git a/test/jake-util/knexfile/knexfile.js b/test/jake-util/knexfile/knexfile.js index dcb3e598d8..b2157b952f 100644 --- a/test/jake-util/knexfile/knexfile.js +++ b/test/jake-util/knexfile/knexfile.js @@ -6,4 +6,7 @@ module.exports = { migrations: { directory: __dirname + '/../knexfile_migrations', }, + seeds: { + directory: __dirname + '/../knexfile_seeds', + } }; diff --git a/test/jake-util/knexfile_seeds/seed2.stub b/test/jake-util/knexfile_seeds/seed2.stub new file mode 100644 index 0000000000..943eb21d84 --- /dev/null +++ b/test/jake-util/knexfile_seeds/seed2.stub @@ -0,0 +1,6 @@ +const tableName = 'table_name'; + +exports.seed = (knex) => { + // Deletes ALL existing entries + return knex(`tbl_${tableName}`).del(); +};