Skip to content

Commit

Permalink
(ReleaseTag) Adding release scripts
Browse files Browse the repository at this point in the history
* bin/release -v <version> will tag a new version
* bin/package will copy release artifacts and tar them
* Performs as much validation as possible to catch bogus tags
  • Loading branch information
mrvisser committed Jul 26, 2013
1 parent 005a1b6 commit b951b14
Show file tree
Hide file tree
Showing 7 changed files with 629 additions and 27 deletions.
27 changes: 1 addition & 26 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,32 +208,7 @@ module.exports = function(grunt) {
return grunt.log.writeln('Please provide a path where the files should be copied to'.red);
}

// We will wind up deleting stuff out of this directory, so make sure it doesn't exist yet
var dest = path.resolve(outputDir);
if (shell.test('-d', dest)) {
return grunt.log.writeln('The output directory already exists, please delete it first'.red);
}

// Create the target directory
shell.mkdir('-p', dest);

// Copy the relevant files to the distribution directory
shell.cp('app.js', 'config.js', 'npm-shrinkwrap.json', 'package.json', 'README.md', 'LICENSE', dest);

// Using shell.exec here because shell.cp (and grunt--copy) does not copy the files in the same way, which results in
// (I think) issues with symlinks that result in phantomjs/webshot not functioning properly on the released binary
// package. If you change this, ensure you test "link" content items have previews generated properly on the resulting
// distribution.
shell.exec('cp -RLf node_modules ' + dest);

// Remove all orig and rej files as they are useless and trip up the debian packaging process
shell.exec('find ' + dest + ' -name "*.orig" -exec rm {} \\;');
shell.exec('find ' + dest + ' -name "*.rej" -exec rm {} \\;');

// Delete the node_modules/oae-*/tests directories
_.each(shell.ls(dest + '/node_modules/oae-*'), function(modulePath) {
shell.rm('-rf', modulePath + '/tests');
});
shell.exec('bin/package -so ' + outputDir);
});

// Default task.
Expand Down
126 changes: 126 additions & 0 deletions bin/lib/package/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*!
* Copyright 2013 Apereo Foundation (AF) Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

var _ = require('underscore');
var fs = require('fs');
var shell = require('shelljs');
var util = require('util');

var BinUtil = require('../util');

/**
* Before performing a packaging of release artifacts, this method ensures that the directories
* are in a state in which it is safe to package.
*
* @param {String} dest The destination directory in which the release artifacts will be stored
* @param {Number} [errCode] The process error code to return on failure. Default: 1
*/
var validatePackage = module.exports.validatePackage = function(dest, errCode) {
errCode = errCode || 1;
if (shell.test('-d', dest)) {
BinUtil.logFail('The output directory exists, please delete it first');
return process.exit(errCode);
}
};

/**
* Copy the release files from the tested root application directory to the distribution source directory.
*
* @param {String} dest The root distribution directory
* @param {Number} [errCode] The process error code to return on failure. Default: 1
*/
var copyReleaseFiles = module.exports.copyReleaseFiles = function(dest, errCode) {
errCode = errCode || 1;

// We will wind up deleting stuff out of this directory, so make sure it doesn't exist yet
validatePackage(dest, errCode);

var destSrc = _getDestSrc(dest);

BinUtil.logInfo('Starting to copy the release artifacts');

// Create the target source directory
shell.mkdir('-p', destSrc);

// Copy individual top-level files. They should all exist
BinUtil.exec('cp app.js ' + destSrc);
BinUtil.exec('cp config.js ' + destSrc);
BinUtil.exec('cp LICENSE ' + destSrc);
BinUtil.exec('cp npm-shrinkwrap.json ' + destSrc);
BinUtil.exec('cp package.json ' + destSrc);
BinUtil.exec('cp README.md ' + destSrc);

// Using shell.exec here because shell.cp does not copy the files in the same way, which results in
// (I think) issues with symlinks that result in phantomjs/webshot not functioning properly on the released binary
// package. If you change this, ensure you test "link" content items have previews generated properly on the resulting
// distribution.
BinUtil.exec('cp -RLf node_modules ' + destSrc);

// Remove all orig and rej files as they are useless and can trip up the debian packaging process
BinUtil.exec('find ' + destSrc + ' -name "*.orig" -exec rm {} \\;');
BinUtil.exec('find ' + destSrc + ' -name "*.rej" -exec rm {} \\;');

// Delete the node_modules/oae-*/tests directories
_.each(shell.ls(destSrc + '/node_modules/oae-*'), function(modulePath) {
shell.rm('-rf', modulePath + '/tests');
});

BinUtil.logSuccess('Successfully copied release artifacts to '.text + destSrc.white);
};

/**
* Save the build info to the `build-info.json` file in the target distribution directory.
*
* @param {String} dest The root distribution directory
* @param {String} hilaryVersion The version of hilary being released
* @param {Object} systemInfo The system information, from `BinUtil.getSystemInfo()`
* @param {Number} [errCode] The process error code to return on failure. Default: 1
*/
var saveBuildInfo = module.exports.saveBuildInfo = function(dest, hilaryVersion, systemInfo, errCode) {
errCode = errCode || 1;

var targetInfoPath = util.format('%s/build-info.json', _getDestSrc(dest));
var buildInfo = _.extend({}, systemInfo, {'version': hilaryVersion});
fs.writeFileSync(targetInfoPath, JSON.stringify(buildInfo, null, 4) + '\n');
BinUtil.logSuccess('Sucessfully wrote system and version information to '.text + targetInfoPath.white);
};

/**
* Package the artifacts copied by `copyReleaseFiles` into a tar.gz file for distribution.
*
* @param {String} dest The root distribution directory
* @param {String} filename The filename (without the extention) of the distribution tar to create
* @param {Number} [errCode] The process error code to return on failure. Default: 1
*/
var packageRelease = module.exports.packageRelease = function(dest, filename, errCode) {
errCode = errCode || 1;
var destSrc = _getDestSrc(dest);

BinUtil.logInfo('Starting to package the release artifacts (tar.gz)');
BinUtil.exec(util.format('tar -czvf %s/%s.tar.gz -C %s .', dest, filename, destSrc), 'Error creating the distribution tar.gz file', errCode);

var result = {'tarball': util.format('%s/%s.tar.gz', dest, filename)};
BinUtil.logSuccess('Successfully created release tarball at '.text + result.tarball.white);
return result;
};

/**
* Given a root distribution directory, get the location of the application source files to be packaged
*
* @param {String} dest The root distribution directory
*/
var _getDestSrc = function(dest) {
return util.format('%s/src', dest);
};
146 changes: 146 additions & 0 deletions bin/lib/release/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*!
* Copyright 2013 Apereo Foundation (AF) Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

var semver = require('semver');
var shell = require('shelljs');
var util = require('util');

var BinUtil = require('../util');

/**
* Verify that the release process can begin with the current state of the repository.
*
* @param {Number} [errCode] The process error code to return on failure. Default: 1
*/
var validateRelease = module.exports.validateRelease = function(errCode) {
errCode = errCode || 1;


// This refreshes the index state, somehow, for the following validations
BinUtil.exec('git status', 'Error refreshing git cache with a git status', errCode);

// Ensure there are no unstaged changes in the repository
BinUtil.exec('git diff-files --quiet', 'It appears you may have unstaged changes in your repository', errCode);
BinUtil.exec('git diff-index --quiet --cached HEAD', 'It appears you may have uncommitted changes in your repository', errCode);
BinUtil.logSuccess('Repository state is clean for a release');
};

/**
* Verify that the specified target version is a valid semver version and is greater than the current version.
*
* @param {Object} packageJson The parsed hilary package.json
* @param {String} toVersion The target version to validate
* @param {Number} errCode The process error code to return on failure
*/
var validateTargetVersion = module.exports.validateTargetVersion = function(packageJson, toVersion, errCode) {

// Ensure the target version is a valid semver version
if (!semver.valid(toVersion)) {
BinUtil.logFail('The target version of '.text + toVersion.error + ' is not a valid semver version'.text);
return process.exit(errCode);
}

// Ensure that the new version number is greater than the old
if (!semver.gt(toVersion, packageJson.version)) {
BinUtil.logFail('The target version of '.text + toVersion.error + ' should be greater than the current version '.text + packageJson.version.error);
return process.exit(errCode);
}

BinUtil.logSuccess(util.format('Validated the target release version %s', toVersion));
};

/**
* Update the hilary package.json file to have the new target version.
*
* @param {String} packageJsonPath The path to the hilary package.json to update
* @param {String} fromVersion The expected previous version in the package.json file
* @param {String} toVersion The target version to update the package.json to
* @param {Number} errCode The process error code to return on failure
*/
var bumpPackageJsonVersion = module.exports.bumpPackageJsonVersion = function(packageJsonPath, fromVersion, toVersion, errCode) {

var replaceSource = util.format('\n "version": "%s",\n', fromVersion);
var replaceWith = util.format('\n "version": "%s",\n', toVersion);

// Perform a bogus replace so we can get the content back from sed's output
var contentBefore = shell.sed('-i', '}"', '}"', packageJsonPath);
var contentAfter = shell.sed('-i', replaceSource, replaceWith, packageJsonPath);

if (contentBefore === contentAfter) {
// We didn't replace anything
BinUtil.logFail('Replacing regexp '.text + replaceSource.trim().error + ' with '.text + replaceWith.trim().error + ' in package.json resulted in no changes'.text);
return process.exit(errCode);
}

if (shell.cat(packageJsonPath).indexOf(replaceWith) === -1) {
BinUtil.logFail('Resulting package.json file did not contain the text ' + replaceWith.trim().error);
return process.exit(errCode);
}
};

/**
* Shrinkwrap the current set of hilary dependencies. It's important that unit tests are run and testing has been done with
* this set of dependencies before shrinkwrapping them into a release.
*
* @param {Number} errCode The process error code to return on failure. Default: 1
*/
var shrinkwrap = module.exports.shrinkwrap = function(errCode) {
errCode = errCode || 1;
BinUtil.logInfo('Starting to run npm shrinkwrap');
BinUtil.exec('npm shrinkwrap', 'Failed to shrinkwrap dependencies', errCode);
BinUtil.logSuccess('Successfully shrinkwrapped dependencies');
};

/**
* Commit the npm-shrinkwrap.json file and tag the commit as the target version.
*
* @param {String} tagVersion The validated target version
* @param {Number} [errCode] The process error code to return on failure. Default: 1
*/
var gitCommitShrinkwrapAndTag = module.exports.gitCommitShrinkwrapAndTag = function(tagVersion, errCode) {
errCode = errCode || 1;
var commitMessage = util.format('(Release %s) Add shrinkwrap, bump version', tagVersion);

BinUtil.logInfo('Commiting npm shrinkwrap and tagging release');

// Stage changes
BinUtil.exec('git add npm-shrinkwrap.json', 'Error adding npm-shrinkwrap.json to git index', errCode);
BinUtil.exec('git add package.json', 'Error adding package.json to git index', errCode);

// Commit and tag
BinUtil.exec(util.format('git commit -m "%s"', commitMessage), 'Error committng shrinkwrap to git', errCode);
BinUtil.exec(util.format('git tag -a %s -m v%s', tagVersion, tagVersion), 'Error creating tag for release', errCode);

BinUtil.logSuccess('Created tag '.text + tagVersion.white + ' and '.text + '1 commit (shrinkwrap)'.white);
};

/**
* Remove the shrinkwrap from the hilary repository and commit it after the release tag.
*
* @param {String} tagVersion The validated target version
* @param {Number} [errCode] The process error code to return on failure. Default: 1
*/
var gitRemoveShrinkwrapAndCommit = module.exports.gitRemoveShrinkwrapAndCommit = function(tagVersion, errCode) {
errCode = errCode || 1;
var commitMessage = util.format('(Release %s) Remove shrinkwrap', tagVersion);

BinUtil.logInfo('Removing shrinkwrap after tag');

// Remove npm-shrinkwrap
BinUtil.exec('git rm npm-shrinkwrap.json', 'Error removing npm-shrinkwrap.json from git index', errCode);
BinUtil.exec(util.format('git commit -m "%s"', commitMessage), 'Error committing shrinkwrap removal to git', errCode);

BinUtil.logSuccess('Removed shrinkwrap with '.text + '1 comment'.white);
};
Loading

0 comments on commit b951b14

Please sign in to comment.