Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make bower version behavior consistent with spec #2232

Merged
merged 1 commit into from Apr 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
203 changes: 118 additions & 85 deletions lib/commands/version.js
Expand Up @@ -4,116 +4,149 @@ var fs = require('../util/fs');
var path = require('path');
var Q = require('q');
var execFile = require('child_process').execFile;
var Project = require('../core/Project');
var defaultConfig = require('../config');
var createError = require('../util/createError');

function version(logger, versionArg, options, config) {
var project;

options = options || {};

config = defaultConfig(config);
project = new Project(config, logger);

return bump(project, versionArg, options.message);
return bump(logger, config, versionArg, options.message);
}

function bump(project, versionArg, message) {
var cwd = project._config.cwd || process.cwd();
function bump(logger, config, versionArg, message) {
var cwd = config.cwd || process.cwd();
var newVersion;
var doGitCommit = false;

return checkGit(cwd)
.then(function (hasGit) {
doGitCommit = hasGit;
})
.then(project.getJson.bind(project))
.then(function (json) {
newVersion = getNewVersion(json.version, versionArg);
json.version = newVersion;
})
.then(project.saveJson.bind(project))
if (!versionArg) {
throw createError('No <version> agrument provided', 'EREADOPTIONS');
}

return driver.check(cwd)
.then(function () {
if (doGitCommit) {
return gitCommitAndTag(cwd, newVersion, message);
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 <version> 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 () {
console.log('v' + newVersion);
return newVersion;
.then(function (result) {
logger.info('version', 'Bumped package version from ' + result.oldVersion + ' to ' + result.newVersion, result);

return result.newVersion;
});
}

function getNewVersion(currentVersion, versionArg) {
var newVersion = semver.valid(versionArg);
if (!newVersion) {
newVersion = semver.inc(currentVersion, versionArg);
}
if (!newVersion) {
throw createError('Invalid version argument: `' + versionArg + '`. Usage: `bower version [<newversion> | major | minor | patch]`', 'EINVALIDVERSION');
}
if (currentVersion === newVersion) {
throw createError('Version not changed', 'EVERSIONNOTCHANGED');
}
return 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 checkGit(cwd) {
var gitDir = path.join(cwd, '.git');
return Q.nfcall(fs.stat, gitDir)
.then(function (stat) {
if (stat.isDirectory()) {
return checkGitStatus(cwd);
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;
});
}
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('Git working directory not clean.\n' + lines.join('\n'), 'EWORKINGDIRECTORYDIRTY');
function filterModifiedStatusLines(stdout) {
return stdout.trim().split('\n')
.filter(function (line) {
return line.trim() && !line.match(/^\?\? /);
}).map(function (line) {
return line.trim();
});
}
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);

function gitCommitAndTag(cwd, newVersion, message) {
var tag = 'v' + newVersion;
message = message || tag;
message = message.replace(/%s/g, newVersion);
return Q.nfcall(execFile, 'git', ['add', 'bower.json'], {env: process.env, cwd: cwd})
.then(function () {
return Q.nfcall(execFile, 'git', ['commit', '-m', message], {env: process.env, cwd: cwd});
})
.then(function () {
return Q.nfcall(execFile, 'git', ['tag', tag, '-am', message], {env: process.env, cwd: cwd});
});
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 () {
Q.nfcall(execFile, 'git', ['tag', tag, '-am', message], {env: process.env, cwd: cwd});
});
}
}

// -------------------

version.readOptions = function (argv) {
var cli = require('../util/cli');
Expand Down
4 changes: 2 additions & 2 deletions lib/templates/json/help-version.json
@@ -1,8 +1,8 @@
{
"command": "version",
"description": "Run this in a package directory to bump the version and write the new data back to the bower.json file.\n\nThe newversion argument should be a valid semver string, or a valid second argument to semver.inc (one of \"build\", \"patch\", \"minor\", or \"major\"). In the second case, the existing version will be incremented\nby 1 in the specified field.\n\nIf run in a git repo, it will also create a version commit and tag, and fail if the repo is not clean.\n\nIf supplied with --message (shorthand: -m) config option, bower will use it as a commit message when creating a version commit. If the message config contains %s then that will be replaced with the resulting\nversion number. For example:\n\n bower version patch -m \"Upgrade to %s for reasons\"",
"description": "Creates an empty version commit and tag, and fail if the repo is not clean.\n\nThe <version> argument should be a valid semver string, or one of following:\nbuild, patch, minor, major.\n\nIf supplied with --message (shorthand: -m) config option, bower will use it\nas a commit message when creating a version commit. If the message config\ncontains %s then that will be replaced with the resulting version number.\n\nFor example:\n\n bower version patch -m \"Upgrade to %s for reasons\"",
"usage": [
"version [<newversion> | major | minor | patch]"
"version [<version> | major | minor | patch]"
],
"options": [
{
Expand Down