Skip to content

Commit

Permalink
feat: Officially support matrix builds
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The `publish` command was removed and merged with
the `release` command. To better support projects with multiple
builds (e.g. against multiple versions of node) the release parts
were split from the verification parts. `nlm verify` can be used
as a `posttest` script in those cases. This ensures that multiple
builds won't try to release the same version.
  • Loading branch information
Jan Krems committed Dec 18, 2015
1 parent 321ce61 commit 6f34b11
Show file tree
Hide file tree
Showing 17 changed files with 418 additions and 156 deletions.
13 changes: 10 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
language: node_js
node_js:
- "4.2"
- '0.10'
- '4.2'
env:
- 'DEBUG=nlm*,gofer*'
before_script:
before_deploy:
- git config --global user.email "opensource@groupon.com"
- git config --global user.name "CI"
after_success: "./bin/nlm.js publish"
deploy:
provider: script
script: './bin/nlm.js release'
skip_cleanup: true
on:
branch: master
node: '4.2'
47 changes: 22 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,6 @@ If you want to use `nlm` to publish, you'll have to add `NPM_TOKEN`:
travis encrypt NPM_TOKEN=your_npm_token --add env
```

Then add the following to your `.travis.yml`:

```yaml
after_success: "./node_modules/.bin/nlm publish"
```
#### DotCI

DotCI lacks native support for encrypted environment variables.
Expand Down Expand Up @@ -114,35 +108,38 @@ Parses an existing `package.json` and makes changes to support `nlm`.
1. Set `publishConfig.registry` (default: read from npm config).


### `nlm changelog`
### `nlm verify`

Preview the changelog that would be generated by the commits between the last version tag and the current `HEAD`.
If there are no unreleased commits, the output will be empty.
*Intended use: `posttest` script for matrix builds.*

Verify that the current state is valid and could be released.
Will also add license headers where they are missing.

1. Add missing license headers.
1. Verify that the checkout is clean.
1. Collect change log items and determine what kind of change it is.


### `nlm release`

*Intended use: `posttest` script.*
*Intended use: `deploy` script, or `posttest` script if not running matrix builds.*

Verify that the current state is valid and could be released.
Will also add license headers where they are missing.

1. Add missing license headers
1. Verify that the checkout is clean
1. Collect change log items and determine what kind of change it is

If this is ran as part of a CI build of the release branch (e.g. `master`),
it will create a new release based on the changes since the last version.
This includes creating and pushing a new git tag.
1. Everything `nlm verify` does.
1. If there are unreleased changes:
1. Create a new CHANGELOG entry and update `package.json#version`.
1. Commit and tag the release.
1. Push the tag and the release branch (e.g. master).
1. Create a Github release.
1. Publish the package to npm or update `dist-tag` if required.

By default `nlm release` will not do anything unless it's running on CI.
You can force a release by running `nlm release --commit`.

### `nlm publish`

*Intended use: Publish a released version.*

This is only for cases where you can't use Travis' native support
for publishing packages. It will create a temporary `.npmrc` file
and check if any npm interactions are required.
### `nlm changelog`

If it's running on CI (or started with `--commit`), it will then
proceed and actually publish to npm.
Preview the changelog that would be generated by the commits between the last version tag and the current `HEAD`.
If there are no unreleased commits, the output will be empty.
20 changes: 16 additions & 4 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ var rc = require('rc');
var COMMANDS = {
changelog: require('./commands/changelog'),
init: require('./commands/init'),
publish: require('./commands/publish'),
release: require('./commands/release'),
verify: require('./commands/verify'),
};

var USAGE = [
'Usage: nlm init # Set up a project to use nlm',
' nlm changelog # Preview the changelog for the next release',
' nlm release # Create a release & push to github',
' nlm publish # Publish a released version to npm',
' nlm release # Create a release, push to github & npm',
' nlm verify # Check that the current state could be released',
'',
'Options:',
' -y, --yes Don\'t ask for permission. Just do it.',
Expand All @@ -75,6 +75,16 @@ var argv = rc('nlm', {

var command = COMMANDS[argv._.shift()];

function prettyPrintErrorAndExit(error) {
/* eslint no-console:0 */
if (error.body && error.statusCode) {
console.error('Response %j: %j', error.statusCode, error.body);
}
var errorMessage = error.message.split('\n').join('\n! ');
console.error('\n!\n! ' + errorMessage + '\n!\n! NOT OK');
process.exit(1);
}

if (argv.version) {
process.stdout.write(require('../package.json').version + '\n');
process.exit(0);
Expand All @@ -85,5 +95,7 @@ if (argv.version) {
var cwd = process.cwd();
var packageJsonFile = path.join(cwd, 'package.json');
var pkg = require(packageJsonFile);
command(cwd, pkg, pkg.nlm ? _.merge({}, pkg.nlm, argv) : argv);
command(cwd, pkg, pkg.nlm ? _.merge({}, pkg.nlm, argv) : argv)
.catch(prettyPrintErrorAndExit)
.done();
}
70 changes: 5 additions & 65 deletions lib/commands/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,13 @@ var debug = require('debug')('nlm:command:release');
var _ = require('lodash');
var semver = require('semver');

var addLicenseHeaders = require('../license');

var verifyClean = require('../git/verify-clean');
var ensureTag = require('../git/ensure-tag');

var determineReleaseInfo = require('../steps/release-info');
var tagPullRequest = require('../steps/tag-pr');
var generateChangeLog = require('../steps/changelog');
var createVersionCommit = require('../steps/version-commit');
var pushReleaseToRemote = require('../steps/push-to-remote');
var createGithubRelease = require('../steps/github-release');
var getPendingChanges = require('../steps/pending-changes');
var detectBranch = require('../steps/detect-branch');
var publishToNpm = require('../steps/publish-to-npm');

function verifyLicenseHeaders(cwd, pkg, options) {
var whitelist = options.license && options.license.files || pkg.files;
var exclude = options.license && options.license.exclude;
return addLicenseHeaders(cwd, whitelist, exclude);
}
var runVerify = require('./verify');

function bumpVersion(version, type) {
if (type === 'none') {
Expand All @@ -66,43 +54,9 @@ function bumpVersion(version, type) {
return semver.inc(version, type);
}

function getPullRequestId() {
var travisId = process.env.TRAVIS_PULL_REQUEST;
if (travisId && travisId !== 'false') {
return travisId;
}
var dotciId = process.env.DOTCI_PULL_REQUEST;
if (dotciId && dotciId !== 'false') {
return dotciId;
}
return '';
}

function release(cwd, pkg, options) {
// Not making this configurable to prevent some possible abuse
options.pr = getPullRequestId();

function ensureLastVersionTag() {
return ensureTag(cwd, 'v' + pkg.version);
}

function setReleaseType() {
/* eslint no-console:0 */
options.releaseType = determineReleaseInfo(options.commits);
console.log('[nlm] Changes are %j', options.releaseType);
}

var verifyTasks = [
ensureLastVersionTag,
getPendingChanges,
setReleaseType,
verifyLicenseHeaders,
verifyClean,
detectBranch,
tagPullRequest,
];

function setNextVersion() {
/* eslint no-console: 0 */
options.nextVersion = bumpVersion(pkg.version, options.releaseType);
console.log('[nlm] Publishing %j from %j as %j',
options.nextVersion, options.currentBranch, options.distTag);
Expand All @@ -120,10 +74,6 @@ function release(cwd, pkg, options) {
return task(cwd, pkg, options);
}

function runVerifyTasks() {
return Bluebird.each(verifyTasks, runTask);
}

function runPublishTasks() {
if (options.releaseType === 'none') {
debug('Nothing to release');
Expand All @@ -144,18 +94,8 @@ function release(cwd, pkg, options) {
return Bluebird.each(publishTasks, runTask);
}

function prettyPrintErrorAndExit(error) {
/* eslint no-console:0 */
if (error.body && error.statusCode) {
console.error('Response %j: %j', error.statusCode, error.body);
}
var errorMessage = error.message.split('\n').join('\n! ');
console.error('\n!\n! ' + errorMessage + '\n!\n! NOT OK');
process.exit(1);
}

return runVerifyTasks()
return runVerify()
.then(options.commit ? runPublishTasks : _.noop)
.catch(prettyPrintErrorAndExit);
.then(options.commit ? publishToNpm : _.noop);
}
module.exports = release;
98 changes: 98 additions & 0 deletions lib/commands/verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2015, Groupon, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of GROUPON nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
'use strict';

var Bluebird = require('bluebird');

var addLicenseHeaders = require('../license');

var verifyClean = require('../git/verify-clean');
var ensureTag = require('../git/ensure-tag');

var determineReleaseInfo = require('../steps/release-info');
var tagPullRequest = require('../steps/tag-pr');
var getPendingChanges = require('../steps/pending-changes');
var detectBranch = require('../steps/detect-branch');

function verifyLicenseHeaders(cwd, pkg, options) {
var whitelist = options.license && options.license.files || pkg.files;
var exclude = options.license && options.license.exclude;
return addLicenseHeaders(cwd, whitelist, exclude);
}

function getPullRequestId() {
var travisId = process.env.TRAVIS_PULL_REQUEST;
if (travisId && travisId !== 'false') {
return travisId;
}
var dotciId = process.env.DOTCI_PULL_REQUEST;
if (dotciId && dotciId !== 'false') {
return dotciId;
}
return '';
}

function verify(cwd, pkg, options) {
// Not making this configurable to prevent some possible abuse
options.pr = getPullRequestId();

function ensureLastVersionTag() {
return ensureTag(cwd, 'v' + pkg.version);
}

function setReleaseType() {
/* eslint no-console:0 */
options.releaseType = determineReleaseInfo(options.commits);
console.log('[nlm] Changes are %j', options.releaseType);
}

var verifyTasks = [
ensureLastVersionTag,
getPendingChanges,
setReleaseType,
verifyLicenseHeaders,
verifyClean,
detectBranch,
tagPullRequest,
];

function runTask(task) {
return task(cwd, pkg, options);
}

function runVerifyTasks() {
return Bluebird.each(verifyTasks, runTask);
}

return runVerifyTasks();
}
module.exports = verify;
7 changes: 2 additions & 5 deletions lib/git/commits.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,11 @@
*/
'use strict';

var childProcess = require('child_process');

var Bluebird = require('bluebird');
var commitParser = require('conventional-commits-parser');
var debug = require('debug')('nlm:git:commits');
var _ = require('lodash');

var execFileAsync = Bluebird.promisify(childProcess.execFile);
var run = require('../run');

var SEPARATOR = '---nlm-split---';
var GIT_LOG_FORMAT = '--format=%H %P\n%B' + SEPARATOR;
Expand Down Expand Up @@ -103,7 +100,7 @@ function createRange(fromRevision) {
}

function getCommits(directory, fromRevision) {
return execFileAsync('git', [
return run('git', [
'log', '--reverse', '--topo-order', GIT_LOG_FORMAT,
].concat(createRange(fromRevision)), {
cwd: directory,
Expand Down
7 changes: 2 additions & 5 deletions lib/git/current-branch.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,12 @@
*/
'use strict';

var childProcess = require('child_process');

var Bluebird = require('bluebird');
var _ = require('lodash');

var execFileAsync = Bluebird.promisify(childProcess.execFile);
var run = require('../run');

function getCurrentBranch(directory) {
return execFileAsync('git', [
return run('git', [
'rev-parse', '--abbrev-ref', 'HEAD',
], {
cwd: directory,
Expand Down
Loading

0 comments on commit 6f34b11

Please sign in to comment.