Permalink
Browse files

feat(cli): add advanced cli tools for wetland

  • Loading branch information...
RWOverdijk committed Nov 5, 2016
1 parent bf4b4e6 commit b0860b3c10419720e4a40d763fc184c5c66922c6
Showing with 445 additions and 3 deletions.
  1. +181 −0 bin/helpers.ts
  2. +194 −0 bin/wetland-migrator.ts
  3. +53 −0 bin/wetland-snapshot.ts
  4. +9 −0 bin/wetland.ts
  5. +8 −3 package.json
@@ -0,0 +1,181 @@
import {Wetland, Migrator} from '../src/index';
import * as program from 'commander';
import * as path from 'path';
import * as fs from 'fs';
import {exec} from 'child_process';
import {SnapshotManager} from '../src/SnapshotManager';
const colors = require('colors/safe');
export let checkmark = process.platform === 'win32' ? '\u221A' : '';
export let cross = process.platform === 'win32' ? '\u00D7' : '';
export function logExamples(command, examples) {
console.log(' Examples:');
console.log('');
examples.forEach(example => console.log(` $ wetland ${command} ${example}`));
console.log('');
}
export function getVersion() {
return require(__dirname + '/../../package.json').version;
}
export function showSuccess(message: string, exit: boolean = true) {
console.log(colors.green(`\n Success: ${message}!\n`));
if (exit) {
process.exit();
}
}
export function unexpectedError(error) {
showError('Unexpected error', error);
}
export function showError(message: string, error?: any, exit: boolean = true) {
console.log(colors.red(`\n Error: ${message}!\n`));
if (error) {
console.log(error);
}
if (exit) {
process.exit(1);
}
}
export function getWetland(options): Wetland {
let wetland;
try {
wetland = new Wetland(getConfig(options && options.parent ? options.parent.config : null));
} catch (error) {
console.error(`\n Caught: ${error}\n`);
process.exit(1);
}
return wetland;
}
export function getMigrator(provided: string): Migrator {
return getWetland(provided).getMigrator();
}
export function getSnapshot(provided: string): SnapshotManager {
return getWetland(provided).getSnapshotManager();
}
export function getConfig(provided: string): Object {
let tryPaths = [];
let config = null;
if (!provided) {
tryPaths = [
path.join(process.cwd(), 'wetland.js'),
path.join(process.cwd(), 'wetland.json')
];
} else {
tryPaths.push(provided[0] === '/' ? provided : path.join(process.cwd(), provided));
}
tryPaths.find(tryPath => {
try {
return config = require(tryPath);
} catch (e) {
}
});
if (!config) {
console.error('\n error: config file not found.\n');
process.exit(1);
}
return config;
}
export function getName(name): Promise<string> {
return new Promise((resolve, reject) => {
if (name) {
return resolve(name.replace(/\W+/g, "_"));
}
try {
fs.statSync(path.resolve(process.cwd(), '.git'));
} catch (error) {
showError(`No name provided, and no git repository found in cwd.`);
}
exec('git symbolic-ref --short HEAD', (error, stdout) => {
if (error) {
return reject(error);
}
let branchName = stdout.trim();
resolve(branchName.replace(/\W+/g, "_"));
});
});
}
export function migrate(options, method) {
let migrator = getMigrator(options);
if (options && options.parent.dumpSql) {
return migrator[method](Migrator.ACTION_GET_SQL).then(queries => {
if (!queries) {
console.log('\n-- No queries found\n');
} else {
console.log('\n-- Queries for next migration');
console.log(queries, '\n');
}
process.exit(0);
}).catch(unexpectedError);
}
if (options && options.parent.run) {
return migrator[method](Migrator.ACTION_RUN).then(migrations => {
if (!migrations) {
showSuccess('Success: No migrations to run');
} else {
showSuccess(`'${parseInt(migrations)}' migrations executed!`);
}
process.exit(0);
}).catch(unexpectedError);
}
showError('Missing action. Provide one of --run or --dump-sql');
}
export function bootstrap() {
try {
require(process.cwd() + '/package.json');
} catch (error) {
showError('Unable to locate package.json, refusing to run. Please use wetland from your project root.');
}
require('app-module-path').addPath(path.resolve(process.cwd(), 'node_modules'));
}
export function registerHelpHandler(command?, examples?) {
if (!process.argv.slice(2).length) {
program.outputHelp();
if (command && examples) {
logExamples(command, examples);
}
}
program.on('*', command => {
program.outputHelp();
if (command && examples) {
logExamples(command, examples);
}
});
program.on('--help', () => {
if (command && examples) {
logExamples(command, examples);
}
});
}
@@ -0,0 +1,194 @@
#!/usr/bin/env node
import * as program from 'commander';
import {
migrate,
getMigrator,
getName,
getWetland,
cross,
checkmark,
getVersion,
showSuccess,
registerHelpHandler,
bootstrap,
unexpectedError,
showError, getSnapshot
} from './helpers';
bootstrap();
const toDatetime = require('to-datetime');
const Table = require('cli-table');
const colors = require('colors/safe');
program
.version(getVersion())
.option('-c, --config <path>', 'defaults to $PWD/(wetland.js|wetland.json)')
.option('-r, --run', 'run the migration(s) on the database')
.option('-d, --dump-sql', 'dump the queries for the migration(s)')
.option('-b, --bare', 'create an empty migration file');
registerHelpHandler('migrator', [
'create create_user --bare',
'status',
'status -c config/wetland.js',
'up --run',
'down --dump-sql',
'latest -r',
'undo -d',
]);
program
.command('create [name]')
.description('Create a new migration file (name defaults to git branch name).')
.action((name, options) => {
let wetland = getWetland(options);
let snapshot = wetland.getSnapshotManager();
let migrator = wetland.getMigrator();
let schema = wetland.getSchemaManager();
let resolvedName;
getName(name)
.then(resolved => {
resolvedName = resolved;
if (options.parent.bare) {
return {up: null, down: null};
}
return snapshot.fetch(resolvedName, false).then(previous => {
return Promise.all([
schema.getCode(previous || {}),
schema.getCode(previous || {}, true)
]).then(code => {
return {up: code[0], down: code[1]};
});
});
})
.then(code => migrator.create(resolvedName, code))
.then(migrationName => showSuccess(`Migration file '${migrationName}' created successfully`))
.catch(unexpectedError);
});
program.command('dev').description('Diff against current mapping for dev migrations.').action(options => {
if (!options.parent.run && !options.parent.dumpSql) {
showError('Missing flag -d or -r');
}
let wetland = getWetland(options);
let snapshot = wetland.getSnapshotManager();
let schema = wetland.getSchemaManager();
snapshot.fetch()
.then(previous => {
if (options.parent.dumpSql) {
console.log('\n-- Queries for dev migrations:');
console.log((schema.getSQL(previous || {}) || '-- Nothing to do.') + '\n');
process.exit();
}
return schema.apply(previous || {});
})
.then(() => snapshot.create())
.then(() => showSuccess('Dev migrations applied'))
.catch(unexpectedError);
});
program.command('status').description('Overview of migrations and their status.').action(options => {
let migrator = getMigrator(options);
let table = new Table({
head: [
colors.green.bold('Migration'),
colors.green.bold('Run at'),
colors.green.bold('Run ID'),
colors.green.bold('Status')
]
});
Promise.all([migrator.allMigrations(), migrator.appliedMigrations()]).then(result => {
let runMap = {};
result[1].forEach(migration => runMap[migration.name] = migration);
result[0].map(migration => {
let applied = runMap[migration];
table.push([
migration,
applied ? toDatetime(applied.migration_time) : 'N/A',
applied ? applied.run : 'N/A',
applied ? `${colors.green(checkmark)} Applied` : `${colors.red(cross)} Not applied`
]);
});
console.log('');
console.log(table.toString());
console.log('');
process.exit();
}).catch(unexpectedError);
});
program.command('schema').description('Create the database schema.').action(options => {
let schema = getWetland(options).getSchemaManager();
if (options.parent.dumpSql) {
console.log('\n-- Queries for schema');
console.log(schema.getSQL() + '\n');
process.exit();
} else if (options.parent.run) {
return schema.create().then(() => {
showSuccess('Schema created successfully');
}).catch(unexpectedError);
}
showError('Missing flag -d or -r');
});
program
.command('revert [name]')
.description('Revert to snapshot (name defaults to git branch name).')
.action((name, options) => {
if (!options.parent.run && !options.parent.dumpSql) {
showError('Missing flag -d or -r');
}
let wetland = getWetland(options);
let snapshot = wetland.getSnapshotManager();
let schema = wetland.getSchemaManager();
let resolvedName;
getName(name)
.then(resolved => resolvedName = resolved)
.then(resolved => snapshot.fetch(resolved, false))
.then(resolved => {
if (options.parent.run) {
return schema.apply(resolved, true).then(() => snapshot.create());
}
return schema.getSQL(resolved, true);
})
.then(sql => {
if (options.parent.run) {
showSuccess(`Successfully reverted to '${resolvedName}'`)
}
console.log('\n-- Queries for revert');
console.log(sql + '\n');
process.exit();
})
.catch(unexpectedError);
});
program.command('forward').description('Update the dev snapshot without running.').action(options => {
getSnapshot(options).create()
.then(() => showSuccess(`Dev snapshot created`))
.catch(unexpectedError);
});
program.command('up').description('Run the next "up" migration.').action(options => migrate(options, 'up'));
program.command('down').description('Run the latest "down" migration.').action(options => migrate(options, 'down'));
program.command('latest').description('Run all un-run "up" migrations.').action(options => migrate(options, 'latest'));
program.command('undo').description('Undo the last run migration(s).').action(options => migrate(options, 'revert'));
program.parse(process.argv);
Oops, something went wrong.

0 comments on commit b0860b3

Please sign in to comment.