From 6f34b11cf983db13e87945769b74afba37c6e1c7 Mon Sep 17 00:00:00 2001 From: Jan Krems Date: Thu, 17 Dec 2015 16:42:58 -0800 Subject: [PATCH] feat: Officially support matrix builds 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. --- .travis.yml | 13 +- README.md | 47 +++---- lib/cli.js | 20 ++- lib/commands/release.js | 70 +--------- lib/commands/verify.js | 98 +++++++++++++ lib/git/commits.js | 7 +- lib/git/current-branch.js | 7 +- lib/git/ensure-tag.js | 7 +- lib/git/push.js | 7 +- lib/git/verify-clean.js | 8 +- lib/run.js | 56 ++++++++ .../publish.js => steps/publish-to-npm.js} | 73 ++++++---- lib/steps/version-commit.js | 10 +- package.json | 2 +- test/fixture.js | 6 +- test/fixtures/released | 13 ++ test/steps/publish-to-npm.js | 130 ++++++++++++++++++ 17 files changed, 418 insertions(+), 156 deletions(-) create mode 100644 lib/commands/verify.js create mode 100644 lib/run.js rename lib/{commands/publish.js => steps/publish-to-npm.js} (75%) create mode 100755 test/fixtures/released create mode 100644 test/steps/publish-to-npm.js diff --git a/.travis.yml b/.travis.yml index fef85b4..7e94988 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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' diff --git a/README.md b/README.md index 6206608..66cd15f 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. diff --git a/lib/cli.js b/lib/cli.js index 57f02f8..626288c 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -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.', @@ -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); @@ -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(); } diff --git a/lib/commands/release.js b/lib/commands/release.js index 0aca638..108ac94 100644 --- a/lib/commands/release.js +++ b/lib/commands/release.js @@ -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') { @@ -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); @@ -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'); @@ -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; diff --git a/lib/commands/verify.js b/lib/commands/verify.js new file mode 100644 index 0000000..2025712 --- /dev/null +++ b/lib/commands/verify.js @@ -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; diff --git a/lib/git/commits.js b/lib/git/commits.js index 878479f..2a76acc 100644 --- a/lib/git/commits.js +++ b/lib/git/commits.js @@ -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; @@ -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, diff --git a/lib/git/current-branch.js b/lib/git/current-branch.js index 5740949..5243a12 100644 --- a/lib/git/current-branch.js +++ b/lib/git/current-branch.js @@ -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, diff --git a/lib/git/ensure-tag.js b/lib/git/ensure-tag.js index e20e18d..4abfe6b 100644 --- a/lib/git/ensure-tag.js +++ b/lib/git/ensure-tag.js @@ -31,16 +31,13 @@ */ 'use strict'; -var childProcess = require('child_process'); var path = require('path'); var fs = require('fs'); -var Bluebird = require('bluebird'); - -var execFileAsync = Bluebird.promisify(childProcess.execFile); +var run = require('../run'); function fetchTag(cwd, tag) { - return execFileAsync('git', [ + return run('git', [ 'fetch', 'origin', 'tag', tag, ], { cwd: cwd }); } diff --git a/lib/git/push.js b/lib/git/push.js index 5961ebe..3d7ce3d 100644 --- a/lib/git/push.js +++ b/lib/git/push.js @@ -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 gitPush(directory, origin, refs) { - return execFileAsync('git', [ + return run('git', [ 'push', origin, ].concat(refs), { cwd: directory, diff --git a/lib/git/verify-clean.js b/lib/git/verify-clean.js index ecd2ed4..b50ce16 100644 --- a/lib/git/verify-clean.js +++ b/lib/git/verify-clean.js @@ -31,11 +31,7 @@ */ 'use strict'; -var childProcess = require('child_process'); - -var Bluebird = require('bluebird'); - -var execFileAsync = Bluebird.promisify(childProcess.execFile); +var run = require('../run'); function parseStatusOutput(output) { if (output.trim().length) { @@ -45,7 +41,7 @@ function parseStatusOutput(output) { } function verifyClean(directory) { - return execFileAsync('git', [ + return run('git', [ 'status', '--short', ], { cwd: directory, diff --git a/lib/run.js b/lib/run.js new file mode 100644 index 0000000..94f59d7 --- /dev/null +++ b/lib/run.js @@ -0,0 +1,56 @@ +/* + * 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 childProcess = require('child_process'); + +var Bluebird = require('bluebird'); +var debug = require('debug')('nlm:run'); +var _ = require('lodash'); + +module.exports = function run(command, args, options) { + debug(command, args, _.omit(options, 'env')); + return new Bluebird(function resolveExec(resolve, reject) { + function onExecDone(error, stdout) { + if (error) return reject(error); + resolve(stdout); + } + + var child = childProcess.execFile(command, args, options, onExecDone); + child.stdout.on('data', function forwardStdOut(chunk) { + debug('stdout', chunk.toString().trim()); + }); + child.stderr.on('data', function forwardStdErr(chunk) { + debug('stderr', chunk.toString().trim()); + }); + }); +}; diff --git a/lib/commands/publish.js b/lib/steps/publish-to-npm.js similarity index 75% rename from lib/commands/publish.js rename to lib/steps/publish-to-npm.js index fffc668..40b79bd 100644 --- a/lib/commands/publish.js +++ b/lib/steps/publish-to-npm.js @@ -31,16 +31,14 @@ */ 'use strict'; /* eslint no-console:0 */ -var childProcess = require('child_process'); var fs = require('fs'); var path = require('path'); var Bluebird = require('bluebird'); +var debug = require('debug')('nlm:publish-to-npm'); var _ = require('lodash'); -var detectBranch = require('../steps/detect-branch'); - -var execFileAsync = Bluebird.promisify(childProcess.execFile); +var run = require('../run'); function generateNpmrc(registryUrl) { var configUrl = registryUrl @@ -69,8 +67,14 @@ function checkPublishRequired(cwd, pkg, options) { return 'wrong-branch'; } - return execFileAsync('npm', ['show', pkg.name, '--json'], { cwd: cwd }) + return run('npm', [ + 'show', pkg.name, '--json', + ], { cwd: cwd, env: options.npmEnv }) .then(function parseNpmList(content) { + // If we get an empty response, we'll assume it was not found. + if (content.trim() === '') { + return 'publish'; + } var registryState = JSON.parse(content); if (!_.includes(registryState.versions, pkg.version)) { return 'publish'; @@ -90,7 +94,7 @@ function checkPublishRequired(cwd, pkg, options) { } function getCurrentCommit(cwd) { - return execFileAsync('git', [ + return run('git', [ 'log', '--format=%s', '--max-count=1', @@ -108,9 +112,9 @@ function doPublish(cwd, pkg, options) { console.log('[nlm] Version %s needs publishing', pkg.version); return null; } - return execFileAsync('npm', [ + return run('npm', [ 'publish', '--tag', options.distTag, - ], { cwd: cwd }).then(forwardToStdout); + ], { cwd: cwd, env: options.npmEnv }).then(forwardToStdout); } function updateDistTag(cwd, pkg, options) { @@ -118,28 +122,52 @@ function updateDistTag(cwd, pkg, options) { console.log('[nlm] Set dist-tag %s to %s', options.distTag, pkg.version); return null; } - return execFileAsync('npm', [ + return run('npm', [ 'dist-tag', 'add', pkg.name + '@' + pkg.version, options.distTag, - ], { cwd: cwd }).then(forwardToStdout); + ], { cwd: cwd, env: options.npmEnv }).then(forwardToStdout); +} + +function hasTokenAuth(env) { + return !!env.NPM_TOKEN; +} + +function hasBasicAuth(env) { + return !!env.NPM_USERNAME && !!env.NPM_EMAIL && !!env.NPM_PASSWORD_BASE64; } function publishToNPM(cwd, pkg, options) { - process.env.NPM_TOKEN = process.env.NPM_TOKEN || ''; - process.env.NPM_PASSWORD_BASE64 = process.env.NPM_PASSWORD_BASE64 || ''; - process.env.NPM_USERNAME = process.env.NPM_USERNAME || ''; - process.env.NPM_EMAIL = process.env.NPM_EMAIL || ''; + _.defaults(options, { + npmToken: process.env.NPM_TOKEN || '', + npmPasswordBase64: process.env.NPM_PASSWORD_BASE64 || '', + npmUsername: process.env.NPM_USERNAME || '', + npmEmail: process.env.NPM_EMAIL || '', + }); + options.npmEnv = _.defaults({ + NPM_TOKEN: options.npmToken, + NPM_PASSWORD_BASE64: options.npmPasswordBase64, + NPM_USERNAME: options.npmUsername, + NPM_EMAIL: options.npmEmail, + }, process.env); + + if (!hasTokenAuth(options.npmEnv) && !hasBasicAuth(options.npmEnv)) { + debug('Skipping publish, no npm auth'); + return Bluebird.resolve(); + } var rcFile = path.join(cwd, '.npmrc'); var rcContent = generateNpmrc(pkg.publishConfig.registry); fs.writeFileSync(rcFile, rcContent); - return detectBranch(cwd, pkg, options) - .then(function preparePublish() { - return Bluebird.all([ - checkPublishRequired(cwd, pkg, options), - getCurrentCommit(cwd), - ]); - }) + if (!options.commit) { + debug('Skipping publish, no --commit'); + return Bluebird.resolve(); + } + + return Bluebird + .all([ + checkPublishRequired(cwd, pkg, options), + getCurrentCommit(cwd), + ]) .spread(function checkAndPublish(publishRequired, currentCommit) { if (currentCommit !== 'v' + pkg.version) { console.log('[nlm] Skipping publish, not a version commit:', currentCommit); @@ -165,7 +193,6 @@ function publishToNPM(cwd, pkg, options) { }) .finally(function removeTmpRcFile() { fs.unlinkSync(rcFile); - }) - .done(); + }); } module.exports = publishToNPM; diff --git a/lib/steps/version-commit.js b/lib/steps/version-commit.js index 1fe7d34..adf3e53 100644 --- a/lib/steps/version-commit.js +++ b/lib/steps/version-commit.js @@ -31,21 +31,19 @@ */ 'use strict'; -var childProcess = require('child_process'); var fs = require('fs'); var path = require('path'); -var Bluebird = require('bluebird'); var _ = require('lodash'); -var execFileAsync = Bluebird.promisify(childProcess.execFile); +var run = require('../run'); function addFiles(cwd) { - return execFileAsync('git', ['add', 'CHANGELOG.md', 'package.json'], { cwd: cwd }); + return run('git', ['add', 'CHANGELOG.md', 'package.json'], { cwd: cwd }); } function commit(cwd, message) { - return execFileAsync('git', ['commit', '-m', message], { cwd: cwd }); + return run('git', ['commit', '-m', message], { cwd: cwd }); } function createVersionCommit(cwd, pkg, options) { @@ -68,7 +66,7 @@ function createVersionCommit(cwd, pkg, options) { return addFiles(cwd) .then(_.partial(commit, cwd, 'v' + pkg.version)) - .then(_.partial(execFileAsync, 'git', ['rev-parse', 'HEAD'])) + .then(_.partial(run, 'git', ['rev-parse', 'HEAD'])) .then(function setVersionCommitSha(output) { options.versionCommitSha = output.trim(); return options.versionCommitSha; diff --git a/package.json b/package.json index 30ba8ac..89f06a3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "scripts": { "postinstall": "./bin/post-install.js", "test": "eslint lib test && mocha", - "posttest": "./bin/nlm.js release" + "posttest": "./bin/nlm.js verify" }, "repository": { "type": "git", diff --git a/test/fixture.js b/test/fixture.js index 576fcf1..49a0d91 100644 --- a/test/fixture.js +++ b/test/fixture.js @@ -59,9 +59,9 @@ function withFixture(name) { }); }); - after('remove fixture directory', function removeFixture(done) { - execFile('rm', ['-rf', dirname], done); - }); + // after('remove fixture directory', function removeFixture(done) { + // execFile('rm', ['-rf', dirname], done); + // }); return dirname; } diff --git a/test/fixtures/released b/test/fixtures/released new file mode 100755 index 0000000..7a53572 --- /dev/null +++ b/test/fixtures/released @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e +bash $(dirname $0)"/multiple-commits" + +echo '{ + "name": "nlm-test-pkg", + "version": "1.0.0", + "publishConfig": { + "registry": "http://127.0.0.1:3000" + } +}' >package.json +git add package.json +git commit -m 'v1.0.0' diff --git a/test/steps/publish-to-npm.js b/test/steps/publish-to-npm.js new file mode 100644 index 0000000..169a863 --- /dev/null +++ b/test/steps/publish-to-npm.js @@ -0,0 +1,130 @@ +/* + * 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 assert = require('assertive'); + +var publishToNpm = require('../../lib/steps/publish-to-npm'); + +var withFixture = require('../fixture'); + +function withFakeRegistry() { + var httpCalls = []; + var server; + + before(function (done) { + server = require('http').createServer(function (req, res) { + httpCalls.push({ + method: req.method, + url: req.url, + auth: req.headers.authorization, + }); + if (req.method === 'GET' && req.url === '/nlm-test-pkg') { + res.statusCode = 404; + return res.end('{}'); + } + res.statusCode = 200; + res.end('{"ok":true}'); + }); + server.listen(3000, done); + }); + + after(function (done) { + server.close(done); + }); + + return httpCalls; +} + +describe('publishToNpm', function () { + describe('without --commmit', function () { + var dirname = withFixture('released'); + var httpCalls = withFakeRegistry(); + + it('makes no http calls', function () { + return publishToNpm(dirname, require(dirname + '/package.json'), { + currentBranch: 'master', + distTag: 'latest', + }).then(function () { + assert.deepEqual([], httpCalls); + }); + }); + }); + + describe('with NPM_USERNAME etc.', function () { + var dirname = withFixture('released'); + var httpCalls = withFakeRegistry(); + + it('sends basic auth headers', function () { + this.timeout(4000); + return publishToNpm(dirname, require(dirname + '/package.json'), { + currentBranch: 'master', + distTag: 'latest', + commit: true, + npmUsername: 'robin', + npmPasswordBase64: new Buffer('passw0rd').toString('base64'), + npmEmail: 'robin@example.com', + npmToken: '', + }).then(function () { + assert.deepEqual({ + method: 'PUT', + url: '/nlm-test-pkg', + auth: 'Basic ' + new Buffer('robin:passw0rd').toString('base64'), + }, httpCalls.pop()); + }); + }); + }); + + describe('with NPM_TOKEN etc.', function () { + var dirname = withFixture('released'); + var httpCalls = withFakeRegistry(); + + it('uses a bearer token', function () { + this.timeout(4000); + return publishToNpm(dirname, require(dirname + '/package.json'), { + currentBranch: 'master', + distTag: 'latest', + commit: true, + npmUsername: '', + npmPasswordBase64: '', + npmEmail: '', + npmToken: 'some-access-token', + }).then(function () { + assert.deepEqual({ + method: 'PUT', + url: '/nlm-test-pkg', + auth: 'Bearer some-access-token', + }, httpCalls.pop()); + }); + }); + }); +});