knex/bin/cli.js
Aurélio A. Heckert bb5da4b200 Migrations with CLI and without knexfile (#2884)
* Migrations with CLI and without knexfile

Allows to create and run migrations.

closes #2819

* Apply tweaks to migrate without knexfile

Thanks to @kibertoad comments!
The bigger change was to remove the bash script to test knex's cli command, and replace it with a jake file. I believe this jake file may be used as a base for future cli tests.

* Replace `fs.promises` with `new Promise`
2018-11-06 11:03:18 +02:00

300 lines
8.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
/* eslint no-console:0, no-var:0 */
var Liftoff = require('liftoff');
var Promise = require('bluebird');
var interpret = require('interpret');
var path = require('path');
var chalk = require('chalk');
var tildify = require('tildify');
var commander = require('commander');
var argv = require('minimist')(process.argv.slice(2));
var fs = Promise.promisifyAll(require('fs'));
var cliPkg = require('../package');
var { isObject } = require('lodash');
function exit(text) {
if (text instanceof Error) {
console.error(chalk.red(text.stack));
} else {
console.error(chalk.red(text));
}
process.exit(1);
}
function success(text) {
console.log(text);
process.exit(0);
}
function checkLocalModule(env) {
if (!env.modulePath) {
console.log(
chalk.red('No local knex install found in:'),
chalk.magenta(tildify(env.cwd))
);
exit('Try running: npm install knex.');
}
}
function mkConfigObj(opts) {
let envName = opts.env || process.env.NODE_ENV || 'development';
let useNullAsDefault = opts.client === 'sqlite3';
return {
ext: 'js',
[envName]: {
useNullAsDefault,
client: opts.client,
connection: opts.connection,
migrations: {
directory: opts.migrationsDirectory,
},
},
};
}
function initKnex(env, opts) {
checkLocalModule(env);
if (!env.configuration) {
if (opts.client) env.configuration = mkConfigObj(opts);
else
exit(
'No knexfile found in this directory. Specify a path with --knexfile'
);
}
if (process.cwd() !== env.cwd) {
process.chdir(env.cwd);
console.log(
'Working directory changed to',
chalk.magenta(tildify(env.cwd))
);
}
var environment = opts.env || process.env.NODE_ENV;
var defaultEnv = 'development';
var config = isObject(env.configuration)
? env.configuration
: require(env.configuration);
if (!environment && typeof config[defaultEnv] === 'object') {
environment = defaultEnv;
}
if (environment) {
console.log('Using environment:', chalk.magenta(environment));
config = config[environment] || config;
}
if (!config) {
console.log(chalk.red('Warning: unable to read knexfile config'));
process.exit(1);
}
if (argv.debug !== undefined) config.debug = argv.debug;
var knex = require(env.modulePath);
return knex(config);
}
function invoke(env) {
env.modulePath = env.modulePath || process.env.KNEX_PATH;
var filetypes = ['js', 'coffee', 'ts', 'eg', 'ls'];
var pending = null;
commander
.version(
chalk.blue('Knex CLI version: ', chalk.green(cliPkg.version)) +
'\n' +
chalk.blue(
'Local Knex version: ',
chalk.green(env.modulePackage.version)
) +
'\n'
)
.option('--debug', 'Run with debugging.')
.option('--knexfile [path]', 'Specify the knexfile path.')
.option('--cwd [path]', 'Specify the working directory.')
.option('--client [name]', 'Set DB client without a knexfile.')
.option('--connection [address]', 'Set DB connection without a knexfile.')
.option(
'--migrations-directory [path]',
'Set migrations directory without a knexfile.'
)
.option(
'--env [name]',
'environment, default: process.env.NODE_ENV || development'
);
commander
.command('init')
.description(' Create a fresh knexfile.')
.option(
`-x [${filetypes.join('|')}]`,
'Specify the knexfile extension (default js)'
)
.action(function() {
var type = (argv.x || 'js').toLowerCase();
if (filetypes.indexOf(type) === -1) {
exit(`Invalid filetype specified: ${type}`);
}
if (env.configuration) {
exit(`Error: ${env.configuration} already exists`);
}
checkLocalModule(env);
var stubPath = `./knexfile.${type}`;
pending = fs
.readFileAsync(
path.dirname(env.modulePath) +
'/lib/migrate/stub/knexfile-' +
type +
'.stub'
)
.then(function(code) {
return fs.writeFileAsync(stubPath, code);
})
.then(function() {
success(chalk.green(`Created ${stubPath}`));
})
.catch(exit);
});
commander
.command('migrate:make <name>')
.description(' Create a named migration file.')
.option(
`-x [${filetypes.join('|')}]`,
'Specify the stub extension (default js)'
)
.action(function(name) {
var instance = initKnex(env, commander.opts());
var ext = (
argv.x ||
env.configuration.ext ||
env.configuration.split('.').pop()
).toLowerCase();
pending = instance.migrate
.make(name, { extension: ext })
.then(function(name) {
success(chalk.green(`Created Migration: ${name}`));
})
.catch(exit);
});
commander
.command('migrate:latest')
.description(' Run all migrations that have not yet been run.')
.action(function() {
pending = initKnex(env, commander.opts())
.migrate.latest()
.spread(function(batchNo, log) {
if (log.length === 0) {
success(chalk.cyan('Already up to date'));
}
success(
chalk.green(`Batch ${batchNo} run: ${log.length} migrations \n`) +
chalk.cyan(log.join('\n'))
);
})
.catch(exit);
});
commander
.command('migrate:rollback')
.description(' Rollback the last set of migrations performed.')
.action(function() {
pending = initKnex(env, commander.opts())
.migrate.rollback()
.spread(function(batchNo, log) {
if (log.length === 0) {
success(chalk.cyan('Already at the base migration'));
}
success(
chalk.green(
`Batch ${batchNo} rolled back: ${log.length} migrations \n`
) + chalk.cyan(log.join('\n'))
);
})
.catch(exit);
});
commander
.command('migrate:currentVersion')
.description(' View the current version for the migration.')
.action(function() {
pending = initKnex(env, commander.opts())
.migrate.currentVersion()
.then(function(version) {
success(chalk.green('Current Version: ') + chalk.blue(version));
})
.catch(exit);
});
commander
.command('seed:make <name>')
.description(' Create a named seed file.')
.option(
`-x [${filetypes.join('|')}]`,
'Specify the stub extension (default js)'
)
.action(function(name) {
var instance = initKnex(env, commander.opts());
var ext = (argv.x || env.configuration.split('.').pop()).toLowerCase();
pending = instance.seed
.make(name, { extension: ext })
.then(function(name) {
success(chalk.green(`Created seed file: ${name}`));
})
.catch(exit);
});
commander
.command('seed:run')
.description(' Run seed files.')
.action(function() {
pending = initKnex(env, commander.opts())
.seed.run()
.spread(function(log) {
if (log.length === 0) {
success(chalk.cyan('No seed files exist'));
}
success(
chalk.green(
`Ran ${log.length} seed files \n${chalk.cyan(log.join('\n'))}`
)
);
})
.catch(exit);
});
commander.parse(process.argv);
Promise.resolve(pending).then(function() {
commander.outputHelp();
exit('Unknown command-line options, exiting');
});
}
var cli = new Liftoff({
name: 'knex',
extensions: interpret.jsVariants,
v8flags: require('v8flags'),
});
cli.on('require', function(name) {
console.log('Requiring external module', chalk.magenta(name));
});
cli.on('requireFail', function(name) {
console.log(chalk.red('Failed to load external module'), chalk.magenta(name));
});
cli.launch(
{
cwd: argv.cwd,
configuration: argv.knexfile,
require: argv.require,
completion: argv.completion,
},
invoke
);