diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e328f4376..1ae8d56231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Master (Unreleased) +# 0.16.3 - 19 Dec, 2018 + ### Bug fixes: - @babel/polyfill loaded multiple times #2955 diff --git a/bin/cli.js b/bin/cli.js index e6166f9400..fa4d85b7d2 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -4,10 +4,10 @@ const Liftoff = require('liftoff'); const Promise = require('bluebird'); const interpret = require('interpret'); const path = require('path'); -const chalk = require('chalk'); const tildify = require('tildify'); const commander = require('commander'); -const argv = require('minimist')(process.argv.slice(2)); +const color = require('colorette'); +const argv = require('getopts')(process.argv.slice(2)); const fs = Promise.promisifyAll(require('fs')); const cliPkg = require('../package'); const { @@ -18,9 +18,9 @@ const { DEFAULT_EXT } = require('./utils/constants'); function exit(text) { if (text instanceof Error) { - console.error(chalk.red(text.stack)); + console.error(color.red(text.stack)); } else { - console.error(chalk.red(text)); + console.error(color.red(text)); } process.exit(1); } @@ -33,8 +33,8 @@ function success(text) { function checkLocalModule(env) { if (!env.modulePath) { console.log( - chalk.red('No local knex install found in:'), - chalk.magenta(tildify(env.cwd)) + color.red('No local knex install found in:'), + color.magenta(tildify(env.cwd)) ); exit('Try running: npm install knex'); } @@ -44,10 +44,7 @@ function initKnex(env, opts) { checkLocalModule(env); if (process.cwd() !== env.cwd) { process.chdir(env.cwd); - console.log( - 'Working directory changed to', - chalk.magenta(tildify(env.cwd)) - ); + console.log('Working directory changed to', color.magenta(tildify(env.cwd))); } if (!opts.knexfile) { @@ -78,12 +75,12 @@ function initKnex(env, opts) { } if (environment) { - console.log('Using environment:', chalk.magenta(environment)); + console.log('Using environment:', color.magenta(environment)); config = config[environment] || config; } if (!config) { - console.log(chalk.red('Warning: unable to read knexfile config')); + console.log(color.red('Warning: unable to read knexfile config')); process.exit(1); } @@ -103,12 +100,9 @@ function invoke(env) { commander .version( - chalk.blue('Knex CLI version: ', chalk.green(cliPkg.version)) + + color.blue('Knex CLI version: ', color.green(cliPkg.version)) + '\n' + - chalk.blue( - 'Local Knex version: ', - chalk.green(env.modulePackage.version) - ) + + color.blue('Local Knex version: ', color.green(env.modulePackage.version)) + '\n' ) .option('--debug', 'Run with debugging.') @@ -154,7 +148,7 @@ function invoke(env) { return fs.writeFileAsync(stubPath, code); }) .then(() => { - success(chalk.green(`Created ${stubPath}`)); + success(color.green(`Created ${stubPath}`)); }) .catch(exit); }); @@ -178,7 +172,7 @@ function invoke(env) { pending = instance.migrate .make(name, { extension: ext }) .then((name) => { - success(chalk.green(`Created Migration: ${name}`)); + success(color.green(`Created Migration: ${name}`)); }) .catch(exit); }); @@ -186,16 +180,17 @@ function invoke(env) { commander .command('migrate:latest') .description(' Run all migrations that have not yet been run.') + .option('--verbose', 'verbose') .action(() => { pending = initKnex(env, commander.opts()) .migrate.latest() .spread((batchNo, log) => { if (log.length === 0) { - success(chalk.cyan('Already up to date')); + success(color.cyan('Already up to date')); } success( - chalk.green(`Batch ${batchNo} run: ${log.length} migrations \n`) + - chalk.cyan(log.join('\n')) + color.green(`Batch ${batchNo} run: ${log.length} migrations`) + + (argv.verbose ? `\n${color.cyan(log.join('\n'))}` : '') ); }) .catch(exit); @@ -204,17 +199,18 @@ function invoke(env) { commander .command('migrate:rollback') .description(' Rollback the last set of migrations performed.') + .option('--verbose', 'verbose') .action(() => { pending = initKnex(env, commander.opts()) .migrate.rollback() .spread((batchNo, log) => { if (log.length === 0) { - success(chalk.cyan('Already at the base migration')); + success(color.cyan('Already at the base migration')); } success( - chalk.green( - `Batch ${batchNo} rolled back: ${log.length} migrations \n` - ) + chalk.cyan(log.join('\n')) + color.green( + `Batch ${batchNo} rolled back: ${log.length} migrations` + ) + (argv.verbose ? `\n${color.cyan(log.join('\n'))}` : '') ); }) .catch(exit); @@ -227,7 +223,7 @@ function invoke(env) { pending = initKnex(env, commander.opts()) .migrate.currentVersion() .then((version) => { - success(chalk.green('Current Version: ') + chalk.blue(version)); + success(color.green('Current Version: ') + color.blue(version)); }) .catch(exit); }); @@ -251,7 +247,7 @@ function invoke(env) { pending = instance.seed .make(name, { extension: ext }) .then((name) => { - success(chalk.green(`Created seed file: ${name}`)); + success(color.green(`Created seed file: ${name}`)); }) .catch(exit); }); @@ -259,17 +255,17 @@ function invoke(env) { commander .command('seed:run') .description(' Run seed files.') + .option('--verbose', 'verbose') .action(() => { pending = initKnex(env, commander.opts()) .seed.run() .spread((log) => { if (log.length === 0) { - success(chalk.cyan('No seed files exist')); + success(color.cyan('No seed files exist')); } success( - chalk.green( - `Ran ${log.length} seed files \n${chalk.cyan(log.join('\n'))}` - ) + color.green(`Ran ${log.length} seed files`) + + (argv.verbose ? `\n${color.cyan(log.join('\n'))}` : '') ); }) .catch(exit); @@ -290,11 +286,11 @@ const cli = new Liftoff({ }); cli.on('require', function(name) { - console.log('Requiring external module', chalk.magenta(name)); + console.log('Requiring external module', color.magenta(name)); }); cli.on('requireFail', function(name) { - console.log(chalk.red('Failed to load external module'), chalk.magenta(name)); + console.log(color.red('Failed to load external module'), color.magenta(name)); }); cli.launch( diff --git a/package.json b/package.json index b5e5403f97..ba43217b43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "knex", - "version": "0.16.2", + "version": "0.16.3", "description": "A batteries-included SQL query & schema builder for Postgres, MySQL and SQLite3 and the Browser", "main": "knex.js", "types": "types/knex.d.ts", @@ -8,23 +8,23 @@ "node": ">=6" }, "dependencies": { - "@babel/polyfill": "^7.0.0", + "@babel/polyfill": "^7.2.5", "@types/bluebird": "^3.5.25", "bluebird": "^3.5.3", - "chalk": "2.4.1", + "colorette": "1.0.7", "commander": "^2.19.0", "debug": "4.1.0", "inherits": "~2.0.3", "interpret": "^1.1.0", "liftoff": "2.5.0", "lodash": "^4.17.11", - "minimist": "1.2.0", + "getopts": "2.2.3", "mkdirp": "^0.5.1", "pg-connection-string": "2.0.0", "tarn": "^1.1.4", "tildify": "1.2.0", "uuid": "^3.3.2", - "v8flags": "^3.1.1" + "v8flags": "^3.1.2" }, "lint-staged": { "*.{js,json}": [ @@ -33,9 +33,9 @@ ] }, "devDependencies": { - "@babel/cli": "^7.2.0", + "@babel/cli": "^7.2.3", "@babel/core": "^7.2.2", - "@babel/preset-env": "^7.2.0", + "@babel/preset-env": "^7.2.3", "@types/node": "*", "JSONStream": "^1.3.5", "async": "^2.6.1", @@ -96,7 +96,7 @@ "mssql:init": "docker-compose -f scripts/mssql-docker-compose.yml up --no-start && docker-compose -f scripts/mssql-docker-compose.yml start", "postmssql:init": "node scripts/wait-for-mssql-server.js && npm run mssql:logs || (npm run mssql:logs;false)", "mssql:logs": "docker-compose -f scripts/mssql-docker-compose.yml logs", - "mssql:test": "DB=mssql npm test", + "mssql:test": "DB=mssql npm run plaintest", "mssql:destroy": "docker-compose -f scripts/mssql-docker-compose.yml stop", "stress:init": "docker-compose -f scripts/stress-test/docker-compose.yml up --no-start && docker-compose -f scripts/stress-test/docker-compose.yml start", "stress:test": "node scripts/stress-test/knex-stress-test.js | grep -A 5 -B 60 -- '- STATS '", @@ -124,9 +124,9 @@ "web": "https://github.com/tgriesser" }, "browser": { - "./lib/migrate/index.js": "./lib/util/noop.js", + "./lib/migrate/Migrator.js": "./lib/util/noop.js", "./lib/bin/cli.js": "./lib/util/noop.js", - "./lib/seed/index.js": "./lib/util/noop.js", + "./lib/seed/Seeder.js": "./lib/util/noop.js", "mssql": false, "mssql/lib/base": false, "tedious": false, diff --git a/src/logger.js b/src/logger.js index dafbd1fd6a..7f55d83670 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,6 +1,6 @@ /* eslint no-console:0 */ -import chalk from 'chalk'; +import color from 'colorette'; import { isFunction, isNil } from 'lodash'; function log(message, userFn, colorFn) { @@ -31,17 +31,17 @@ class Logger { } warn(message) { - log(message, this._warn, chalk.yellow); + log(message, this._warn, color.yellow); } error(message) { - log(message, this._error, chalk.red); + log(message, this._error, color.red); } deprecate(method, alternative) { const message = `${method} is deprecated, please use ${alternative}`; - log(message, this._deprecate, chalk.yellow); + log(message, this._deprecate, color.yellow); } } diff --git a/src/migrate/Migrator.js b/src/migrate/Migrator.js index b6d61b9edf..f36ae6bce1 100644 --- a/src/migrate/Migrator.js +++ b/src/migrate/Migrator.js @@ -99,8 +99,8 @@ export default class Migrator { }); } - // Rollback the last "batch" of migrations that were run. - rollback(config) { + // Rollback the last "batch", or all, of migrations that were run. + rollback(config, all = false) { return Promise.try(() => { this.config = getMergedConfig(config, this.config); @@ -109,7 +109,7 @@ export default class Migrator { .tap((value) => validateMigrationList(this.config.migrationSource, value) ) - .then((val) => this._getLastBatch(val)) + .then((val) => (all ? val[0] : this._getLastBatch(val))) .then((migrations) => { return this._runBatch(migrations, 'down'); }); @@ -423,7 +423,8 @@ export function getMergedConfig(config, currentConfig) { if (!mergedConfig.migrationSource) { mergedConfig.migrationSource = new FsMigrations( mergedConfig.directory, - mergedConfig.sortDirsSeparately + mergedConfig.sortDirsSeparately, + mergedConfig.loadExtensions ); } diff --git a/src/seed/index.js b/src/seed/Seeder.js similarity index 92% rename from src/seed/index.js rename to src/seed/Seeder.js index fb055f762f..4875b2b742 100644 --- a/src/seed/index.js +++ b/src/seed/Seeder.js @@ -128,6 +128,20 @@ Seeder.prototype._waterfallBatch = function(seeds) { .then(() => seed.seed(knex, Promise)) .then(() => { log.push(name); + }) + .catch((originalError) => { + const error = new Error( + `Error while executing "${name}" seed: ${originalError.message}` + ); + error.original = originalError; + error.stack = + error.stack + .split('\n') + .slice(0, 2) + .join('\n') + + '\n' + + originalError.stack; + throw error; }); }); diff --git a/src/util/make-knex.js b/src/util/make-knex.js index 8ef3cc426e..d52d982a64 100644 --- a/src/util/make-knex.js +++ b/src/util/make-knex.js @@ -1,7 +1,7 @@ import { EventEmitter } from 'events'; import Migrator from '../migrate/Migrator'; -import Seeder from '../seed'; +import Seeder from '../seed/Seeder'; import FunctionHelper from '../functionhelper'; import QueryInterface from '../query/methods'; import { assign } from 'lodash'; diff --git a/test/integration/migrate/index.js b/test/integration/migrate/index.js index 5e149040bd..5f0fe73dc6 100644 --- a/test/integration/migrate/index.js +++ b/test/integration/migrate/index.js @@ -331,6 +331,53 @@ module.exports = function(knex) { }); }); + describe('knex.migrate.rollback - all', () => { + before(() => { + return knex.migrate + .latest({ + directory: ['test/integration/migrate/test'], + }) + .then(function() { + return knex.migrate.latest({ + directory: [ + 'test/integration/migrate/test', + 'test/integration/migrate/test2', + ], + }); + }); + }); + + it('should delete all batches from the migration log', () => { + return knex.migrate + .rollback( + { + directory: [ + 'test/integration/migrate/test', + 'test/integration/migrate/test2', + ], + }, + true + ) + .spread(function(batchNo, log) { + expect(batchNo).to.equal(2); + expect(log).to.have.length(4); + return knex('knex_migrations') + .select('*') + .then(function(data) { + expect(data.length).to.equal(0); + }); + }); + }); + + it('should drop tables as specified in the batch', () => { + return Promise.map(tables, function(table) { + return knex.schema.hasTable(table).then(function(exists) { + expect(!!exists).to.equal(false); + }); + }); + }); + }); + after(function() { rimraf.sync(path.join(__dirname, './migration')); }); diff --git a/test/jake-util/helpers/migration-test-helper.js b/test/jake-util/helpers/migration-test-helper.js index 7abaff9e24..8c5973fcec 100644 --- a/test/jake-util/helpers/migration-test-helper.js +++ b/test/jake-util/helpers/migration-test-helper.js @@ -9,12 +9,14 @@ function assertExec(cmd, desc) { desc = desc || 'Run ' + cmd; return new Promise((resolve, reject) => { let stderr = ''; - console.log(`Executing: ${cmd}`); + let stdout = ''; + // console.log(`Executing: ${cmd}`); const bin = jake.createExec([cmd]); bin.addListener('error', (msg, code) => reject(Error(desc + ' FAIL. ' + stderr)) ); - bin.addListener('cmdEnd', resolve); + bin.addListener('cmdEnd', (cmd) => resolve({ cmd, stdout, stderr })); + bin.addListener('stdout', (data) => (stdout += data.toString())); bin.addListener('stderr', (data) => (stderr += data.toString())); bin.run(); }); @@ -24,7 +26,7 @@ function assertExecError(cmd, desc) { desc = desc || 'Run ' + cmd; return new Promise((resolve, reject) => { let stderr = ''; - console.log(`Executing: ${cmd}`); + // console.log(`Executing: ${cmd}`); const bin = jake.createExec([cmd]); bin.addListener('error', (msg, code) => { resolve(stderr); diff --git a/test/jake-util/seeds-error-knexfile.js b/test/jake-util/seeds-error-knexfile.js new file mode 100644 index 0000000000..667c6477bf --- /dev/null +++ b/test/jake-util/seeds-error-knexfile.js @@ -0,0 +1,9 @@ +module.exports = { + client: 'sqlite3', + connection: { + filename: __dirname + '/../test.sqlite3', + }, + seeds: { + directory: __dirname + '/seeds-error', + }, +}; diff --git a/test/jake-util/seeds-error/seeds.js b/test/jake-util/seeds-error/seeds.js new file mode 100644 index 0000000000..03893efaa1 --- /dev/null +++ b/test/jake-util/seeds-error/seeds.js @@ -0,0 +1,3 @@ +exports.seed = (knex, Promise) => { + throw new Error('Boom'); +}; diff --git a/test/jake-util/seeds-knexfile.js b/test/jake-util/seeds-knexfile.js new file mode 100644 index 0000000000..bfaa56526e --- /dev/null +++ b/test/jake-util/seeds-knexfile.js @@ -0,0 +1,9 @@ +module.exports = { + client: 'sqlite3', + connection: { + filename: __dirname + '/../test.sqlite3', + }, + seeds: { + directory: __dirname + '/seeds', + }, +}; diff --git a/test/jake-util/seeds/first.js b/test/jake-util/seeds/first.js new file mode 100644 index 0000000000..81a219741f --- /dev/null +++ b/test/jake-util/seeds/first.js @@ -0,0 +1 @@ +exports.seed = (knex, Promise) => {}; diff --git a/test/jake-util/seeds/second.js b/test/jake-util/seeds/second.js new file mode 100644 index 0000000000..81a219741f --- /dev/null +++ b/test/jake-util/seeds/second.js @@ -0,0 +1 @@ +exports.seed = (knex, Promise) => {}; diff --git a/test/jake/Jakefile b/test/jake/Jakefile index ef3a2473df..16b112cfdc 100644 --- a/test/jake/Jakefile +++ b/test/jake/Jakefile @@ -5,10 +5,12 @@ const knexfileTests = require('./jakelib/knexfile-test').taskList; const migrateTests = require('./jakelib/migrate-test').taskList; +const seedTests = require('./jakelib/seed-test').taskList; const tests = [ ...knexfileTests, ...migrateTests, + ...seedTests ]; task('default', tests, () => { diff --git a/test/jake/jakelib/migrate-test.js b/test/jake/jakelib/migrate-test.js index 70d65f6743..c9caab0c59 100644 --- a/test/jake/jakelib/migrate-test.js +++ b/test/jake/jakelib/migrate-test.js @@ -9,25 +9,13 @@ const rimrafSync = require('rimraf').sync; const path = require('path'); const sqlite3 = require('sqlite3'); const { assert } = require('chai'); +const { assertExec } = require('../../jake-util/helpers/migration-test-helper'); +const knexfile = require('../../jake-util/knexfile/knexfile.js'); const KNEX = path.normalize(__dirname + '/../../../bin/cli.js'); /* * * HELPERS * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -function assertExec(cmd, desc) { - desc = desc || 'Run ' + cmd; - return new Promise((resolve, reject) => { - let stderr = ''; - const bin = jake.createExec([cmd]); - bin.addListener('error', (msg, code) => - reject(Error(desc + ' FAIL. ' + stderr)) - ); - bin.addListener('cmdEnd', resolve); - bin.addListener('stderr', (data) => (stderr += data.toString())); - bin.run(); - }); -} - const taskList = []; function test(description, func) { const tmpDirPath = os.tmpdir() + '/knex-test-'; @@ -122,6 +110,47 @@ test('Run migrations', (temp) => ) .then((row) => assert.equal(row.name, '000_create_rule_table.js'))); +test('migrate:latest prints non verbose logs', (temp) => { + const db = knexfile.connection.filename; + if (fs.existsSync(db)) { + fs.unlinkSync(db); + } + + return assertExec( + `node ${KNEX} migrate:latest --knexfile=test/jake-util/knexfile/knexfile.js --knexpath=../knex.js` + ).then(({ stdout }) => { + assert.include(stdout, 'Batch 1 run: 1 migrations'); + assert.notInclude(stdout, 'simple_migration.js'); + }); +}); + +test('migrate:rollback prints non verbose logs', (temp) => { + return assertExec( + `node ${KNEX} migrate:rollback --knexfile=test/jake-util/knexfile/knexfile.js --knexpath=../knex.js` + ).then(({ stdout }) => { + assert.include(stdout, 'Batch 1 rolled back: 1 migrations'); + assert.notInclude(stdout, 'simple_migration.js'); + }); +}); + +test('migrate:latest prints verbose logs', (temp) => { + return assertExec( + `node ${KNEX} migrate:latest --knexfile=test/jake-util/knexfile/knexfile.js --knexpath=../knex.js --verbose` + ).then(({ stdout }) => { + assert.include(stdout, 'Batch 1 run: 1 migrations'); + assert.include(stdout, 'simple_migration.js'); + }); +}); + +test('migrate:rollback prints verbose logs', (temp) => { + return assertExec( + `node ${KNEX} migrate:rollback --knexfile=test/jake-util/knexfile/knexfile.js --knexpath=../knex.js --verbose` + ).then(({ stdout }) => { + assert.include(stdout, 'Batch 1 rolled back: 1 migrations'); + assert.include(stdout, 'simple_migration.js'); + }); +}); + module.exports = { taskList, }; diff --git a/test/jake/jakelib/seed-test.js b/test/jake/jakelib/seed-test.js new file mode 100644 index 0000000000..be4cad38ad --- /dev/null +++ b/test/jake/jakelib/seed-test.js @@ -0,0 +1,50 @@ +#!/usr/bin/env jake +'use strict'; +/* eslint-disable no-undef */ + +const path = require('path'); +const { + assertExec, + assertExecError, + test, +} = require('../../jake-util/helpers/migration-test-helper'); +const { assert } = require('chai'); + +const KNEX = path.normalize(__dirname + '/../../../bin/cli.js'); + +const taskList = []; +/* * * TESTS * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +test(taskList, 'seed:run prints non verbose logs', (temp) => { + return assertExec( + `node ${KNEX} seed:run --knexfile=test/jake-util/seeds-knexfile.js --knexpath=../knex.js` + ).then(({ stdout }) => { + assert.include(stdout, 'Ran 2 seed files'); + assert.notInclude(stdout, 'first.js'); + assert.notInclude(stdout, 'second.js'); + }); +}); + +test(taskList, 'seed:run prints verbose logs', (temp) => { + return assertExec( + `node ${KNEX} seed:run --knexfile=test/jake-util/seeds-knexfile.js --knexpath=../knex.js --verbose` + ).then(({ stdout }) => { + assert.include(stdout, 'Ran 2 seed files'); + assert.include(stdout, 'first.js'); + assert.include(stdout, 'second.js'); + }); +}); + +test(taskList, 'Handles seeding errors correctly', (temp) => { + return assertExecError( + `node ${KNEX} seed:run --knexfile=test/jake-util/seeds-error-knexfile.js --knexpath=../knex.js` + ).then((err) => { + assert.include(err, 'Error while executing'); + assert.include(err, 'seeds.js'); + assert.include(err, 'Boom'); + }); +}); + +module.exports = { + taskList, +}; diff --git a/test/tape/seed.js b/test/tape/seed.js index 7b8ef8fbd8..39383c4143 100644 --- a/test/tape/seed.js +++ b/test/tape/seed.js @@ -1,7 +1,7 @@ 'use strict'; const tape = require('tape'); -const Seed = require('../../lib/seed/index.js'); +const Seed = require('../../lib/seed/Seeder'); tape('checks config.seeds for seed config', function(t) { t.plan(1); diff --git a/types/knex.d.ts b/types/knex.d.ts index 8a2fdf829b..b0d892ea28 100644 --- a/types/knex.d.ts +++ b/types/knex.d.ts @@ -830,7 +830,7 @@ declare namespace Knex { interface Migrator { make(name: string, config?: MigratorConfig): Bluebird; latest(config?: MigratorConfig): Bluebird; - rollback(config?: MigratorConfig): Bluebird; + rollback(config?: MigratorConfig, all?: boolean): Bluebird; status(config?: MigratorConfig): Bluebird; currentVersion(config?: MigratorConfig): Bluebird; }