intern-cli
Version:
The command line interface for Intern
257 lines (231 loc) • 7.97 kB
JavaScript
/* global Promise */
var exec = require('child_process').exec;
var fs = require('fs');
var path = require('path');
var packageJson = require('../package.json');
var util = require('util');
var rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
function cleanup() {
// Cleanup
function rm(filename) {
if (fs.statSync(filename).isFile()) {
fs.unlinkSync(filename);
}
else {
fs.readdirSync(filename).forEach(function (entry) {
rm(path.join(filename, entry));
});
fs.rmdirSync(filename);
}
}
print('\nCleaning up...\n');
process.chdir(rootDir);
rm(buildDir);
}
function print() {
rl.write.apply(rl, arguments);
}
function printUsage() {
print('Usage: ' + process.argv[1] + ' [options] [branch] [version]\n');
print('\n');
print('Branch defaults to "master".\n');
print('Version defaults to what is listed in package.json in the branch.\n');
print('Version should only be specified for pre-releases.\n');
}
function prompt() {
var question = util.format.apply(util, arguments);
return new Promise(function (resolve) {
rl.question(question, resolve);
});
}
function run(cmd) {
return new Promise(function (resolve, reject) {
if (shouldRun) {
exec(cmd, function (error, stdout) {
if (error) {
reject(error);
}
else {
resolve(stdout);
}
});
}
else {
print(cmd + '\n');
resolve('');
}
});
}
function updatePackageVersion(version) {
packageJson.version = version;
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, ' '));
}
var args = process.argv.slice(2);
var shouldRun = true;
if (args[0] === '--help') {
printUsage();
process.exit(0);
}
if (args[0] === '-n') {
shouldRun = false;
args.shift();
}
var rootDir = path.dirname(__dirname);
var buildDir = path.join(rootDir, '_build');
var branch = args[0] || 'master';
var pushBranches = [ branch ];
var npmTag = 'latest';
var version;
var releaseTag;
var makeBranch;
var preVersion;
var branchVersion;
var tagVersion;
if (args[1]) {
version = args[1];
npmTag = 'beta';
}
print('This is an internal release script!\n');
var question = 'Enter "y" to create a new release from branch ' + branch +
(version ? (' with version override ' + version) : '.') +
'\n(You can abort pushing upstream later on if something goes wrong.)\n';
prompt(question).then(function (answer) {
if (answer !== 'y') {
throw new Error('Aborted');
}
}).then(function () {
try {
fs.statSync(buildDir);
throw new Error('Existing build directory detected at ' + buildDir);
}
catch (error) {
// ignore
}
}).then(function () {
return run('git config receive.denyCurrentBranch').then(function (stdout) {
if (stdout.indexOf('updateInstead') !== 0) {
throw new Error('Repository should have receive.denyCurrentBranch set to "updateInstead"');
}
});
}).then(function () {
// Create a package build directory and clone this repo into it
process.chdir(rootDir);
fs.mkdirSync(buildDir);
return run('git clone --recursive . ' + buildDir);
}).then(function () {
// Cd into the build dir and checkout the branch that's being released
process.chdir(buildDir);
print('\nBuilding branch "' + branch + '"...\n');
return run('git checkout ' + branch);
}).then(function () {
// Determine the proper version numbers for release and for repo post-release
if (!version) {
version = packageJson.version.replace('-pre', '');
preVersion = version.split('.').map(Number);
// If the last digit is a 0, this is a new major/minor release
if (preVersion[2] === '0') {
// We'll be creating a new minor release branch for this version for any future patch releases
// e.g., current is 2.1.0, branch will be 2.1.1-pre
branchVersion = preVersion[0] + '.' + preVersion[1] + '.' + (preVersion[2] + 1) + '-pre';
// makeBranch is the new branch we'll be making for this major/minor release
makeBranch = preVersion[0] + '.' + preVersion[1];
// The next release is usually going to be a minor release; if the next version is to be a major release,
// the package version will need to be manually updated in Git before release
// e.g., current is 2.1.0, pre will be 2.2.0-pre
preVersion = preVersion[0] + '.' + (preVersion[1] + 1) + '.0-pre';
}
// If the last digit isn't a 0, this is a new patch release
else {
// Patch releases do not get a branch, and the next release version will always be another patch version
// e.g., current is 2.1.0, pre will be 2.1.1-pre
preVersion = preVersion[0] + '.' + preVersion[1] + '.' + (preVersion[2] + 1) + '-pre';
}
}
else {
preVersion = packageJson.version + '-pre';
}
releaseTag = tagVersion = version;
// At this point:
// `version` is the version of the package that is being released;
// `tagVersion` is the name that will be used for the Git tag for the release
// `preVersion` is the next pre-release version that will be set on the original branch after tagging
// `makeBranch` is the name of the new release branch that should be created if this is not a patch release
// `branchVersion` is the pre-release version that will be set on the minor release branch
return run('git tag').then(function (tags) {
tags.split('\n').forEach(function (tag) {
if (tag === tagVersion) {
throw new Error('Version ' + tag + ' has already been tagged');
}
});
});
}).then(function () {
// Set the package version to release version and commit the new release
updatePackageVersion(version);
return run('git commit -m "Updating metadata for ' + version + '" package.json').then(function () {
return run('git tag -a -m "Release ' + version + '" ' + tagVersion);
});
}).then(function () {
// Check out the previous package.json
return run('git checkout HEAD^ package.json').then(function () {
return run('git reset package.json');
});
}).then(function () {
// Set the package version to next pre-release version and commit the pre-release
updatePackageVersion(preVersion);
return run('git commit -m "Updating source version to ' + preVersion + '" package.json');
}).then(function () {
// If this is a major/minor release, we also create a new branch for it
if (makeBranch) {
// Create the new branch starting at the tagged release version
return run('git checkout -b ' + makeBranch + ' ' + tagVersion).then(function () {
// Set the package version to the next patch pre-release version and commit the pre-release
updatePackageVersion(branchVersion);
return run('git commit -m "Updating source version to ' + branchVersion + '" package.json');
}).then(function () {
// Store the branch as one that needs to be pushed when we are ready to deploy the release
pushBranches.push(makeBranch);
});
}
}).then(function () {
// Checkout the new release in preparation for publishing
return run('git checkout ' + releaseTag);
}).then(function () {
// Give the user a chance to verify everything is good before making any updates
print('\nDone!\n\n');
question = 'Please confirm packaging success, then enter "y" to publish to npm\n' +
npmTag + ', push tags ' + releaseTag + ', and upload. Enter any other key to bail.\n' +
'> ';
return new Promise(function (resolve) {
rl.question(question, function (answer) {
resolve(answer);
});
}).then(function (answer) {
if (answer !== 'y') {
cleanup();
process.exit(0);
}
});
}).then(function () {
// Publish the package
return run('npm publish --tag ' + npmTag);
}).then(function () {
// Update the original repo with the new branch and tag pointers
return Promise.all(pushBranches.map(function (branch) {
return run('git push origin ' + branch);
})).then(function () {
return run('git push origin --tags');
});
}).then(function () {
cleanup();
print('\nAll done! Yay!\n');
process.exit(0);
}).catch(function (error) {
// Something broke -- display an error
print(error.message + '\n');
print('Aborted.\n');
process.exit(1);
});