var semver = require('semver'); var which = require('which'); var fs = require('../util/fs'); var path = require('path'); var Q = require('q'); var execFile = require('child_process').execFile; var defaultConfig = require('../config'); var createError = require('../util/createError'); function version(logger, versionArg, options, config) { options = options || {}; config = defaultConfig(config); return bump(logger, config, versionArg, options.message); } function bump(logger, config, versionArg, message) { var cwd = config.cwd || process.cwd(); var newVersion; if (!versionArg) { throw createError('No agrument provided', 'EREADOPTIONS'); } return driver.check(cwd) .then(function () { return Q.all([driver.versions(cwd), driver.currentVersion(cwd)]); }) .spread(function (versions, currentVersion) { currentVersion = currentVersion || '0.0.0'; if (semver.valid(versionArg)) { newVersion = semver.valid(versionArg); } else { newVersion = semver.inc(currentVersion, versionArg); if (!newVersion) { throw createError('Invalid argument: ' + versionArg, 'EINVALIDVERSION', { version: versionArg }); } } newVersion = (currentVersion[0] === 'v') ? 'v' + newVersion : newVersion; if (versions) { versions.forEach(function (version) { if (semver.eq(version, newVersion)) { throw createError('Version exists: ' + newVersion, 'EVERSIONEXISTS', { versions: versions, newVersion: newVersion }); } }); } return driver.bump(cwd, newVersion, message).then(function () { return { oldVersion: currentVersion, newVersion: newVersion } }); }) .then(function (result) { logger.info('version', 'Bumped package version from ' + result.oldVersion + ' to ' + result.newVersion, result); return result.newVersion; }); } var driver = { check: function (cwd) { function checkGit(cwd) { var gitDir = path.join(cwd, '.git'); return Q.nfcall(fs.stat, gitDir) .then(function (stat) { if (stat.isDirectory()) { return checkGitStatus(cwd); } return false; }, function () { //Ignore not found .git directory return false; }); } function checkGitStatus(cwd) { return Q.nfcall(which, 'git') .fail(function (err) { err.code = 'ENOGIT'; throw err; }) .then(function () { return Q.nfcall(execFile, 'git', ['status', '--porcelain'], {env: process.env, cwd: cwd}); }) .then(function (value) { var stdout = value[0]; var lines = filterModifiedStatusLines(stdout); if (lines.length) { throw createError('Version bump requires clean working directory', 'EWORKINGDIRECTORYDIRTY'); } return true; }); } function filterModifiedStatusLines(stdout) { return stdout.trim().split('\n') .filter(function (line) { return line.trim() && !line.match(/^\?\? /); }).map(function (line) { return line.trim(); }); } return checkGit(cwd).then(function (hasGit) { if (!hasGit) { throw createError('Version bump currently supports only git repositories', 'ENOTGITREPOSITORY'); } }); }, versions: function (cwd) { return Q.nfcall(execFile, 'git', ['tag'], {env: process.env, cwd: cwd}) .then(function (res) { var versions = res[0] .split(/\r?\n/) .filter(semver.valid); return versions; }, function () { return []; }); }, currentVersion: function (cwd) { return Q.nfcall(execFile, 'git', ['describe', '--abbrev=0', '--tags'], {env: process.env, cwd: cwd}) .then(function (res) { var version = res[0] .split(/\r?\n/) .filter(semver.valid)[0]; return version; }, function () { return undefined; }); }, bump: function (cwd, tag, message) { message = message || tag; message = message.replace(/%s/g, tag); return Q.nfcall(execFile, 'git', ['commit', '-m', message, '--allow-empty'], {env: process.env, cwd: cwd}) .then(function () { return Q.nfcall(execFile, 'git', ['tag', tag, '-am', message], {env: process.env, cwd: cwd}); }); } } version.readOptions = function (argv) { var cli = require('../util/cli'); var options = cli.readOptions({ 'message': { type: String, shorthand: 'm'} }, argv); return [options.argv.remain[1], options]; }; module.exports = version;