diff --git a/package.json b/package.json index bd2db45a..7e7ca3ae 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,10 @@ } }, "dependencies": { - "@semantic-release/commit-analyzer": "^1.0.0", - "@semantic-release/condition-travis": "^3.0.0", + "@semantic-release/commit-analyzer": "^2.0.0", + "@semantic-release/condition-travis": "^4.0.0", "@semantic-release/error": "^1.0.0", - "@semantic-release/release-notes-generator": "^1.0.0", + "@semantic-release/release-notes-generator": "^2.0.0", "git-head": "^1.2.1", "github": "^0.2.4", "lodash": "^3.9.3", diff --git a/src/index.js b/src/index.js index fb6e1175..6ab60f1f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,27 +1,35 @@ const { readFileSync, writeFileSync } = require('fs') +const path = require('path') const _ = require('lodash') const log = require('npmlog') const nopt = require('nopt') const npmconf = require('npmconf') +const PREFIX = 'semantic-release' const env = process.env -const options = _.defaults(nopt({ +const pkg = JSON.parse(readFileSync('./package.json')) +const knownOptions = { + branch: String, debug: Boolean, 'github-token': String, - 'github-url': String -}, { - token: 'github-token', - dry: 'debug' -}), { - debug: !env.CI, - 'github-token': env.GH_TOKEN || env.GITHUB_TOKEN, - 'github-url': env.GH_URL -}) -const PREFIX = 'semantic-release' - -const pkg = JSON.parse(readFileSync('./package.json')) -const plugins = require('./lib/plugins')(pkg.release || {}) + 'github-url': String, + 'analyze-commits': [path, String], + 'generate-notes': [path, String], + 'verify-conditions': [path, String], + 'verify-release': [path, String] +} +const options = _.defaults( + _.mapKeys(nopt(knownOptions), (value, key) => _.camelCase(key)), + pkg.release, + { + branch: 'master', + debug: !env.CI, + githubToken: env.GH_TOKEN || env.GITHUB_TOKEN, + githubUrl: env.GH_URL + } +) +const plugins = require('./lib/plugins')(options) npmconf.load({}, (err, conf) => { if (err) { @@ -29,28 +37,41 @@ npmconf.load({}, (err, conf) => { process.exit(1) } - log.level = conf.get('loglevel') + let npm = { + auth: { + token: env.NPM_TOKEN + }, + loglevel: conf.get('loglevel'), + registry: conf.get('registry'), + tag: (pkg.publishConfig || {}).tag || conf.get('tag') || 'latest' + } - log.verbose(PREFIX, 'argv:', options) - log.verbose(PREFIX, 'options:', pkg.release || 'no options') - log.verbose(PREFIX, 'Verifying pkg, options and env.') + if (npm.registry[npm.registry.length - 1] !== '/') npm.registry += '/' - const errors = require('./lib/verify')(pkg, options, env) + log.level = npm.loglevel + + const config = {PREFIX, log, env, pkg, options, plugins, npm} + + log.verbose(PREFIX, 'options:', _.assign({ + githubToken: options.githubToken ? '***' : undefined + }), options) + log.verbose(PREFIX, 'Verifying config.') + + const errors = require('./lib/verify')(config) errors.forEach((err) => log.error(PREFIX, `${err.message} ${err.code}`)) if (errors.length) process.exit(1) - if (!options.argv.cooked.length || options.argv.cooked[0] === 'pre') { + if (options.argv.remain[0] === 'pre') { log.verbose(PREFIX, 'Running pre-script.') log.verbose(PREFIX, 'Veriying conditions.') - plugins.verifyConditions(pkg, options, env, (err) => { + plugins.verifyConditions(config, (err) => { if (err) { log[options.debug ? 'warn' : 'error'](PREFIX, err.message) if (!options.debug) process.exit(1) } - const registry = conf.get('registry') - const nerfDart = require('./lib/nerf-dart')(registry) + const nerfDart = require('./lib/nerf-dart')(npm.registry) let wroteNpmRc = false if (env.NPM_TOKEN) { @@ -69,19 +90,7 @@ npmconf.load({}, (err, conf) => { if (wroteNpmRc) log.verbose(PREFIX, 'Wrote authToken to .npmrc.') - const npmConfig = { - auth: { - token: env.NPM_TOKEN - }, - loglevel: log.level, - registry: registry + (registry[registry.length - 1] !== '/' ? '/' : ''), - tag: (pkg.publishConfig || {}).tag || conf.get('tag') || 'latest' - } - - require('./pre')(pkg, - npmConfig, - plugins, - (err, release) => { + require('./pre')(config, (err, release) => { if (err) { log.error(PREFIX, 'Failed to determine new version.') @@ -91,7 +100,7 @@ npmconf.load({}, (err, conf) => { process.exit(1) } - const message = `Determined version ${release.version} as "${npmConfig.tag}".` + const message = `Determined version ${release.version} as "${npm.tag}".` log.verbose(PREFIX, message) @@ -108,10 +117,10 @@ npmconf.load({}, (err, conf) => { }) }) }) - } else if (options.argv.cooked[0] === 'post') { + } else if (options.argv.remain[0] === 'post') { log.verbose(PREFIX, 'Running post-script.') - require('./post')(pkg, options, plugins, (err, published, release) => { + require('./post')(config, (err, published, release) => { if (err) { log.error(PREFIX, 'Failed to publish release notes.', err) process.exit(1) @@ -120,6 +129,6 @@ npmconf.load({}, (err, conf) => { log.verbose(PREFIX, `${published ? 'Published' : 'Generated'} release notes.`, release) }) } else { - log.error(PREFIX, `Command "${options.argv.cooked[0]}" not recognized. User either "pre" or "post"`) + log.error(PREFIX, `Command "${options.argv.remain[0]}" not recognized. User either "pre" or "post"`) } }) diff --git a/src/lib/commits.js b/src/lib/commits.js index b2a0a1ab..ac11d8d8 100644 --- a/src/lib/commits.js +++ b/src/lib/commits.js @@ -1,6 +1,6 @@ const { exec } = require('child_process') -module.exports = function (lastRelease, cb) { +module.exports = function ({lastRelease}, cb) { const from = lastRelease.gitHead const range = (from ? from + '..' : '') + 'HEAD' diff --git a/src/lib/last-release.js b/src/lib/last-release.js index 4326bb4c..90f127ba 100644 --- a/src/lib/last-release.js +++ b/src/lib/last-release.js @@ -3,24 +3,24 @@ const SemanticReleaseError = require('@semantic-release/error') const npmlog = require('npmlog') const RegClient = require('npm-registry-client') -module.exports = function (pkg, npmConfig, cb) { - npmlog.level = npmConfig.loglevel || 'error' +module.exports = function ({pkg, npm}, cb) { + npmlog.level = npm.loglevel || 'error' const client = new RegClient({log: npmlog}) - client.get(`${npmConfig.registry}${pkg.name}`, { - auth: npmConfig.auth + client.get(`${npm.registry}${pkg.name}`, { + auth: npm.auth }, (err, data) => { if (err && err.statusCode === 404) return cb(null, {}) if (err) return cb(err) - const version = data['dist-tags'][npmConfig.tag] + const version = data['dist-tags'][npm.tag] - if (!version) return cb(new SemanticReleaseError(`There is no release with the dist-tag "${npmConfig.tag}" yet. Tag a version first.`, 'ENODISTTAG')) + if (!version) return cb(new SemanticReleaseError(`There is no release with the dist-tag "${npm.tag}" yet. Tag a version first.`, 'ENODISTTAG')) cb(null, { version, gitHead: data.versions[version].gitHead, - tag: npmConfig.tag + tag: npm.tag }) }) } diff --git a/src/lib/plugin-noop.js b/src/lib/plugin-noop.js index 630aa073..0f28a72a 100644 --- a/src/lib/plugin-noop.js +++ b/src/lib/plugin-noop.js @@ -1,4 +1,4 @@ /* istanbul ignore next */ -module.exports = function (options, release, cb) { +module.exports = function (config, options, cb) { cb(null) } diff --git a/src/lib/plugins.js b/src/lib/plugins.js index 32868c7c..2740a0a9 100644 --- a/src/lib/plugins.js +++ b/src/lib/plugins.js @@ -1,20 +1,20 @@ const relative = require('require-relative') -let exports = module.exports = function (source) { +let exports = module.exports = function (options) { return { - analyzeCommits: exports.normalize(source.analyzeCommits, '@semantic-release/commit-analyzer'), - generateNotes: exports.normalize(source.generateNotes, '@semantic-release/release-notes-generator'), - verifyConditions: exports.normalize(source.verifyConditions, '@semantic-release/condition-travis'), - verifyRelease: exports.normalize(source.verifyRelease, './plugin-noop') + analyzeCommits: exports.normalize(options.analyzeCommits, '@semantic-release/commit-analyzer'), + generateNotes: exports.normalize(options.generateNotes, '@semantic-release/release-notes-generator'), + verifyConditions: exports.normalize(options.verifyConditions, '@semantic-release/condition-travis'), + verifyRelease: exports.normalize(options.verifyRelease, './plugin-noop') } } -exports.normalize = function (plugin, fallback) { - if (typeof plugin === 'string') return relative(plugin).bind(null, {}) +exports.normalize = function (pluginConfig, fallback) { + if (typeof pluginConfig === 'string') return relative(pluginConfig).bind(null, {}) - if (plugin && (typeof plugin.path === 'string')) { - return relative(plugin.path).bind(null, plugin) + if (pluginConfig && (typeof pluginConfig.path === 'string')) { + return relative(pluginConfig.path).bind(null, pluginConfig) } - return require(fallback).bind(null, plugin) + return require(fallback).bind(null, pluginConfig) } diff --git a/src/lib/type.js b/src/lib/type.js index 376f7d09..d22ffa73 100644 --- a/src/lib/type.js +++ b/src/lib/type.js @@ -1,7 +1,9 @@ const SemanticReleaseError = require('@semantic-release/error') -module.exports = function (plugins, commits, lastRelease, cb) { - plugins.analyzeCommits(commits, (err, type) => { +module.exports = function (config, cb) { + const { plugins, lastRelease } = config + + plugins.analyzeCommits(config, (err, type) => { if (err) return cb(err) if (!type) { diff --git a/src/lib/verify.js b/src/lib/verify.js index d8c68381..38c952ca 100644 --- a/src/lib/verify.js +++ b/src/lib/verify.js @@ -2,7 +2,7 @@ const parseSlug = require('parse-github-repo-url') const SemanticReleaseError = require('@semantic-release/error') -module.exports = function (pkg, options, env) { +module.exports = function ({pkg, options, env}) { let errors = [] if (!pkg.name) { @@ -26,7 +26,7 @@ module.exports = function (pkg, options, env) { if (options.debug) return errors - if (!options['github-token']) { + if (!options.githubToken) { errors.push(new SemanticReleaseError( 'No github token specified.', 'ENOGHTOKEN' diff --git a/src/post.js b/src/post.js index 42107af0..e4794db8 100644 --- a/src/post.js +++ b/src/post.js @@ -4,17 +4,18 @@ const gitHead = require('git-head') const GitHubApi = require('github') const parseSlug = require('parse-github-repo-url') -module.exports = function (pkg, argv, plugins, cb) { - const config = argv['github-url'] ? url.parse(argv['github-url']) : {} +module.exports = function (config, cb) { + const { pkg, options, plugins } = config + const ghConfig = options.githubUrl ? url.parse(options.githubUrl) : {} const github = new GitHubApi({ version: '3.0.0', - port: config.port, - protocol: (config.protocol || '').split(':')[0] || null, - host: config.hostname + port: ghConfig.port, + protocol: (ghConfig.protocol || '').split(':')[0] || null, + host: ghConfig.hostname }) - plugins.generateNotes(pkg, (err, log) => { + plugins.generateNotes(config, (err, log) => { if (err) return cb(err) gitHead((err, hash) => { @@ -27,17 +28,17 @@ module.exports = function (pkg, argv, plugins, cb) { name: `v${pkg.version}`, tag_name: `v${pkg.version}`, target_commitish: hash, - draft: !!argv.debug, + draft: !!options.debug, body: log } - if (argv.debug && !argv['github-token']) { + if (options.debug && !options.githubToken) { return cb(null, false, release) } github.authenticate({ type: 'oauth', - token: argv['github-token'] + token: options.githubToken }) github.releases.createRelease(release, (err) => { diff --git a/src/pre.js b/src/pre.js index 30464885..9190f2f7 100644 --- a/src/pre.js +++ b/src/pre.js @@ -1,3 +1,4 @@ +const _ = require('lodash') const auto = require('run-auto') const semver = require('semver') @@ -5,28 +6,38 @@ const getLastRelease = require('./lib/last-release') const getCommits = require('./lib/commits') const getType = require('./lib/type') -module.exports = function (pkg, npmConfig, plugins, cb) { +module.exports = function (config, cb) { + const {plugins} = config auto({ - lastRelease: getLastRelease.bind(null, pkg, npmConfig), + lastRelease: getLastRelease.bind(null, config), commits: ['lastRelease', (cb, results) => { - getCommits(results.lastRelease, cb) + getCommits(_.assign({ + lastRelease: results.lastRelease + }, config), + cb) }], type: ['commits', 'lastRelease', (cb, results) => { - getType(plugins, results.commits, results.lastRelease, cb) + getType(_.assign({ + commits: results.commits, + lastRelease: results.lastRelease + }, config), + cb) }] }, (err, results) => { if (err) return cb(err) const nextRelease = { type: results.type, - commits: results.commits, - lastVersion: results.lastRelease.version, version: results.type === 'initial' ? '1.0.0' : semver.inc(results.lastRelease.version, results.type) } - plugins.verifyRelease(nextRelease, (err) => { + plugins.verifyRelease(_.assign({ + commits: results.commits, + lastRelease: results.lastRelease, + nextRelease + }, config), (err) => { if (err) return cb(err) cb(null, nextRelease) }) diff --git a/test/specs/commits.js b/test/specs/commits.js index c1c11e5f..d3b160c0 100644 --- a/test/specs/commits.js +++ b/test/specs/commits.js @@ -7,7 +7,7 @@ const commits = proxyquire('../../dist/lib/commits', { test('commits since last release', (t) => { t.test('get all commits', (tt) => { - commits({}, (err, commits) => { + commits({lastRelease: {}}, (err, commits) => { tt.error(err) tt.is(commits.length, 2, 'all commits') tt.is(commits[0].hash, 'hash-one', 'parsed hash') @@ -18,7 +18,7 @@ test('commits since last release', (t) => { }) t.test('get commits since hash', (tt) => { - commits({gitHead: 'hash'}, (err, commits) => { + commits({lastRelease: {gitHead: 'hash'}}, (err, commits) => { tt.error(err) tt.is(commits.length, 1, 'specified commits') tt.is(commits[0].hash, 'hash-one', 'parsed hash') diff --git a/test/specs/last-release.js b/test/specs/last-release.js index 60c06383..55467056 100644 --- a/test/specs/last-release.js +++ b/test/specs/last-release.js @@ -4,7 +4,7 @@ const test = require('tap').test require('../mocks/registry') const lastRelease = require('../../dist/lib/last-release') -const npmConfig = { +const npm = { registry: 'http://registry.npmjs.org/', tag: 'latest' } @@ -14,10 +14,9 @@ test('last release from registry', (t) => { t.test('get release from package name', (tt) => { lastRelease({ - name: 'available' - }, - npmConfig, - (err, release) => { + pkg: {name: 'available'}, + npm + }, (err, release) => { tt.error(err) tt.is(release.version, '1.33.7', 'version') tt.is(release.gitHead, 'HEAD', 'gitHead') @@ -29,10 +28,9 @@ test('last release from registry', (t) => { t.test('get release from a tagged package\'s name', (tt) => { lastRelease({ - name: 'tagged' - }, - defaults({tag: 'foo'}, npmConfig), - (err, release) => { + pkg: {name: 'tagged'}, + npm: defaults({tag: 'foo'}, npm) + }, (err, release) => { tt.error(err) tt.is(release.version, '0.8.15', 'version') tt.is(release.gitHead, 'bar', 'gitHead') @@ -44,10 +42,9 @@ test('last release from registry', (t) => { t.test('get error from an untagged package\'s name', (tt) => { lastRelease({ - name: 'untagged' - }, - defaults({tag: 'bar'}, npmConfig), - (err) => { + pkg: {name: 'untagged'}, + npm: defaults({tag: 'bar'}, npm) + }, (err) => { tt.is(err.code, 'ENODISTTAG', 'error') tt.end() @@ -56,10 +53,9 @@ test('last release from registry', (t) => { t.test('get release from scoped package name', (tt) => { lastRelease({ - name: '@scoped/available' - }, - npmConfig, - (err, release) => { + pkg: {name: '@scoped/available'}, + npm + }, (err, release) => { tt.error(err) tt.is(release.version, '1.33.7', 'version') tt.is(release.gitHead, 'HEAD', 'gitHead') @@ -71,10 +67,9 @@ test('last release from registry', (t) => { t.test('get nothing from not yet published package name', (tt) => { lastRelease({ - name: 'unavailable' - }, - npmConfig, - (err, release) => { + pkg: {name: 'unavailable'}, + npm + }, (err, release) => { tt.error(err) tt.is(release.version, undefined, 'no version') diff --git a/test/specs/post.js b/test/specs/post.js index 887dfffb..f34aad0d 100644 --- a/test/specs/post.js +++ b/test/specs/post.js @@ -9,14 +9,10 @@ const post = proxyquire('../../dist/post', { const pkg = { version: '1.0.0', - repository: { - url: 'http://github.com/whats/up.git' - } + repository: {url: 'http://github.com/whats/up.git'} } -const plugins = { - generateNotes: (pkg, cb) => cb(null, 'the log') -} +const plugins = {generateNotes: (pkg, cb) => cb(null, 'the log')} const defaultRelease = { owner: 'whats', @@ -29,7 +25,11 @@ const defaultRelease = { test('full post run', (t) => { t.test('in debug mode w/o token', (tt) => { - post(pkg, {debug: true}, plugins, (err, published, release) => { + post({ + options: {debug: true}, + pkg, + plugins + }, (err, published, release) => { tt.error(err) tt.is(published, false) tt.match(release, defaults({draft: true}, defaultRelease)) @@ -38,8 +38,12 @@ test('full post run', (t) => { }) }) - t.test('in debug mode w token', (tt) => { - post(pkg, {debug: true, 'github-token': 'yo'}, plugins, (err, published, release) => { + t.test('in debug mode w/token', (tt) => { + post({ + options: {debug: true, githubToken: 'yo'}, + pkg, + plugins + }, (err, published, release) => { tt.error(err) tt.is(published, true) tt.match(release, defaults({draft: true}, defaultRelease)) @@ -49,7 +53,11 @@ test('full post run', (t) => { }) t.test('production', (tt) => { - post(pkg, {'github-token': 'yo'}, plugins, (err, published, release) => { + post({ + options: {githubToken: 'yo'}, + pkg, + plugins + }, (err, published, release) => { tt.error(err) tt.is(published, true) tt.match(release, defaultRelease) diff --git a/test/specs/pre.js b/test/specs/pre.js index c7b67b87..6f1a0d57 100644 --- a/test/specs/pre.js +++ b/test/specs/pre.js @@ -11,7 +11,7 @@ const plugins = { analyzeCommits: (commits, cb) => cb(null, 'major') } -const npmConfig = { +const npm = { registry: 'http://registry.npmjs.org/', tag: 'latest' @@ -22,11 +22,10 @@ test('full pre run', (t) => { tt.plan(3) pre({ - name: 'available' - }, - npmConfig, - plugins, - (err, release) => { + npm, + pkg: {name: 'available'}, + plugins + }, (err, release) => { tt.error(err) tt.is(release.type, 'major') tt.is(release.version, '2.0.0') @@ -37,11 +36,10 @@ test('full pre run', (t) => { tt.plan(3) pre({ - name: 'unavailable' - }, - npmConfig, - plugins, - (err, release) => { + npm, + pkg: {name: 'unavailable'}, + plugins + }, (err, release) => { tt.error(err) tt.is(release.type, 'initial') tt.is(release.version, '1.0.0') diff --git a/test/specs/type.js b/test/specs/type.js index 7d1f9180..399c2a91 100644 --- a/test/specs/type.js +++ b/test/specs/type.js @@ -7,12 +7,12 @@ test('get type from commits', (t) => { tt.plan(2) type({ - analyzeCommits: (commits, cb) => cb(null, 'major') - }, [{ - hash: '0', - message: 'a' - }], { - version: '1.0.0' + commits: [{ + hash: '0', + message: 'a' + }], + lastRelease: {version: '1.0.0'}, + plugins: {analyzeCommits: (config, cb) => cb(null, 'major')} }, (err, type) => { tt.error(err) tt.is(type, 'major') @@ -23,9 +23,10 @@ test('get type from commits', (t) => { tt.plan(1) type({ - analyzeCommits: (commits, cb) => cb(null, null) - }, [], {}, - (err) => { + commits: [], + lastRelease: {}, + plugins: {analyzeCommits: (config, cb) => cb(null, null)} + }, (err) => { tt.is(err.code, 'ENOCHANGE') }) }) @@ -34,9 +35,10 @@ test('get type from commits', (t) => { tt.plan(2) type({ - analyzeCommits: (commits, cb) => cb(null, 'major') - }, [], {}, - (err, type) => { + commits: [], + lastRelease: {}, + plugins: {analyzeCommits: (config, cb) => cb(null, 'major')} + }, (err, type) => { tt.error(err) tt.is(type, 'initial') }) diff --git a/test/specs/verify.js b/test/specs/verify.js index 8c4f34cd..03d4d291 100644 --- a/test/specs/verify.js +++ b/test/specs/verify.js @@ -5,32 +5,35 @@ const verify = require('../../dist/lib/verify') test('verify pkg, options and env', (t) => { t.test('dry run verification', (tt) => { const noErrors = verify({ - name: 'package', - repository: { - url: 'http://github.com/whats/up.git' + options: {debug: true}, + pkg: { + name: 'package', + repository: { + url: 'http://github.com/whats/up.git' + } } - }, { - debug: true - }, {}) + }) tt.is(noErrors.length, 0) - const errors = verify({}, { - debug: true - }, {}) + const errors = verify({ + options: {debug: true}, + pkg: {} + }) tt.is(errors.length, 2) tt.is(errors[0].code, 'ENOPKGNAME') tt.is(errors[1].code, 'ENOPKGREPO') const errors2 = verify({ - name: 'package', - repository: { - url: 'lol' + options: {debug: true}, + pkg: { + name: 'package', + repository: { + url: 'lol' + } } - }, { - debug: true - }, {}) + }) tt.is(errors2.length, 1) tt.is(errors2[0].code, 'EMALFORMEDPKGREPO') @@ -40,19 +43,19 @@ test('verify pkg, options and env', (t) => { t.test('publish verification', (tt) => { const noErrors = verify({ - name: 'package', - repository: { - url: 'http://github.com/whats/up.git' + env: {NPM_TOKEN: 'yo'}, + options: {githubToken: 'sup'}, + pkg: { + name: 'package', + repository: { + url: 'http://github.com/whats/up.git' + } } - }, { - 'github-token': 'sup' - }, { - NPM_TOKEN: 'yo' }) tt.is(noErrors.length, 0) - const errors = verify({}, {}, {}) + const errors = verify({env: {}, options: {}, pkg: {}}) tt.is(errors.length, 4) tt.is(errors[0].code, 'ENOPKGNAME')