feat: Extract npm and github publish to plugins

- Add a new plugin type: `publish`
- Add support for multi-plugin. A plugin module can now return an object with a property for each plugin type
- Uses by default [npm](https://github.com/semantic-release/npm) and [github](https://github.com/semantic-release/github) in addition of Travis for the verify condition plugin
- Uses by default [npm](https://github.com/semantic-release/npm) and [github](https://github.com/semantic-release/github) for the publish plugin
- `gitTag` if one can be found is passed to `generateNotes` for both `lastRelease` and `nextRelease`
- `semantic-release` now verifies the plugin configuration (in the `release` property of `package.json`) and throws an error if it's invalid
- `semantic-release` now verifies each plugin output and will throw an error if a plugin returns an unexpected value.

BREAKING CHANGE: `githubToken`, `githubUrl` and `githubApiPathPrefix` have to be set at the [github](https://github.com/semantic-release/github) plugin level. They can be set via `GH_TOKEN`, `GH_URL` and `GH_PREFIX` environment variables.

BREAKING CHANGE: the `npm` parameter is not passed to any plugin anymore. Each plugin have to read `.npmrc` if they needs to (with https://github.com/kevva/npm-conf for example).
This commit is contained in:
Pierre Vanduynslager
2017-11-21 16:41:04 -05:00
parent 991a7b5f97
commit d548edcf37
42 changed files with 1111 additions and 1122 deletions
+21
View File
@@ -0,0 +1,21 @@
module.exports = {
verifyConditions(config, options, cb) {
cb();
},
getLastRelease(config, options, cb) {
cb();
},
analyzeCommits(config, options, cb) {
cb();
},
verifyRelease(config, options, cb) {
cb();
},
generateNotes(config, options, cb) {
cb();
},
publish(config, options, cb) {
cb();
},
};
-3
View File
@@ -1,3 +0,0 @@
module.exports = function(config, options, cb) {
cb(new Error('b'));
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = (config, options, cb) => {
cb(null);
};
-3
View File
@@ -1,3 +0,0 @@
module.exports = function(config, options, cb) {
cb(null, 'a');
};
-3
View File
@@ -1,3 +0,0 @@
module.exports = function(config, options, cb) {
cb(null, 'b');
};
+39 -22
View File
@@ -1,5 +1,4 @@
import test from 'ava';
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import SemanticReleaseError from '@semantic-release/error';
import {
@@ -12,6 +11,7 @@ import {
gitLog,
gitDetachedHead,
} from './helpers/git-utils';
import getCommits from '../lib/get-commits';
test.beforeEach(t => {
// Save the current working diretory
@@ -19,9 +19,7 @@ test.beforeEach(t => {
// Stub the logger functions
t.context.log = stub();
t.context.error = stub();
t.context.getCommits = proxyquire('../lib/get-commits', {
'./logger': {log: t.context.log, error: t.context.error},
});
t.context.logger = {log: t.context.log, error: t.context.error};
});
test.afterEach.always(t => {
@@ -36,7 +34,7 @@ test.serial('Get all commits when there is no last release', async t => {
const commits = await gitCommits(['First', 'Second']);
// Retrieve the commits with the commits module
const result = await t.context.getCommits({}, 'master');
const result = await getCommits({}, 'master', t.context.logger);
// Verify the commits created and retrieved by the module are identical
t.is(result.commits.length, 2);
@@ -54,6 +52,7 @@ test.serial('Get all commits when there is no last release', async t => {
t.truthy(result.lastRelease);
t.falsy(result.lastRelease.gitHead);
t.falsy(result.lastRelease.version);
t.falsy(result.lastRelease.gitTag);
});
test.serial('Get all commits with gitTags', async t => {
@@ -67,7 +66,7 @@ test.serial('Get all commits with gitTags', async t => {
commits = (await gitCommits(['Second'])).concat(commits);
// Retrieve the commits with the commits module
const result = await t.context.getCommits({}, 'master');
const result = await getCommits({}, 'master', t.context.logger);
// Verify the commits created and retrieved by the module are identical
t.is(result.commits.length, 2);
t.is(result.commits[0].hash.substring(0, 7), commits[0].hash);
@@ -96,7 +95,7 @@ test.serial('Get all commits when there is no last release, including the ones n
t.is((await gitLog()).length, 1);
// Retrieve the commits with the commits module
const result = await t.context.getCommits({}, 'master');
const result = await getCommits({}, 'master', t.context.logger);
// Verify the commits created and retrieved by the module are identical
t.is(result.commits.length, 2);
@@ -114,6 +113,7 @@ test.serial('Get all commits when there is no last release, including the ones n
t.truthy(result.lastRelease);
t.falsy(result.lastRelease.gitHead);
t.falsy(result.lastRelease.version);
t.falsy(result.lastRelease.gitTag);
});
test.serial('Get all commits since gitHead (from lastRelease)', async t => {
@@ -123,7 +123,7 @@ test.serial('Get all commits since gitHead (from lastRelease)', async t => {
const commits = await gitCommits(['First', 'Second', 'Third']);
// Retrieve the commits with the commits module, since commit 'First'
const result = await t.context.getCommits({gitHead: commits[commits.length - 1].hash}, 'master');
const result = await getCommits({gitHead: commits[commits.length - 1].hash}, 'master', t.context.logger);
// Verify the commits created and retrieved by the module are identical
t.is(result.commits.length, 2);
@@ -141,6 +141,7 @@ test.serial('Get all commits since gitHead (from lastRelease)', async t => {
t.truthy(result.lastRelease);
t.is(result.lastRelease.gitHead, commits[commits.length - 1].hash);
t.falsy(result.lastRelease.version);
t.falsy(result.lastRelease.gitTag);
});
test.serial('Get all commits since gitHead (from lastRelease) on a detached head repo', async t => {
@@ -152,7 +153,7 @@ test.serial('Get all commits since gitHead (from lastRelease) on a detached head
await gitDetachedHead(repo, commits[1].hash);
// Retrieve the commits with the commits module, since commit 'First'
const result = await t.context.getCommits({gitHead: commits[commits.length - 1].hash}, 'master');
const result = await getCommits({gitHead: commits[commits.length - 1].hash}, 'master', t.context.logger);
// Verify the module retrieved only the commit 'feat: Second' (included in the detached and after 'fix: First')
t.is(result.commits.length, 1);
@@ -165,6 +166,7 @@ test.serial('Get all commits since gitHead (from lastRelease) on a detached head
t.truthy(result.lastRelease);
t.is(result.lastRelease.gitHead, commits[commits.length - 1].hash);
t.falsy(result.lastRelease.version);
t.falsy(result.lastRelease.gitTag);
});
test.serial('Get all commits since gitHead (from tag) ', async t => {
@@ -178,7 +180,7 @@ test.serial('Get all commits since gitHead (from tag) ', async t => {
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
const result = await t.context.getCommits({version: '1.0.0'}, 'master');
const result = await getCommits({version: '1.0.0'}, 'master', t.context.logger);
// Verify the commits created and retrieved by the module are identical
t.is(result.commits.length, 2);
@@ -195,6 +197,7 @@ test.serial('Get all commits since gitHead (from tag) ', async t => {
// Verify the last release is returned and updated
t.truthy(result.lastRelease);
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
t.is(result.lastRelease.gitTag, '1.0.0');
t.is(result.lastRelease.version, '1.0.0');
});
@@ -211,7 +214,7 @@ test.serial('Get all commits since gitHead (from tag) on a detached head repo',
await gitDetachedHead(repo, commits[1].hash);
// Retrieve the commits with the commits module, since commit 'First' (associated with tag 1.0.0)
const result = await t.context.getCommits({version: '1.0.0'}, 'master');
const result = await getCommits({version: '1.0.0'}, 'master', t.context.logger);
// Verify the module retrieved only the commit 'feat: Second' (included in the detached and after 'fix: First')
t.is(result.commits.length, 1);
@@ -223,6 +226,7 @@ test.serial('Get all commits since gitHead (from tag) on a detached head repo',
// Verify the last release is returned and updated
t.truthy(result.lastRelease);
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
t.is(result.lastRelease.gitTag, '1.0.0');
t.is(result.lastRelease.version, '1.0.0');
});
@@ -237,7 +241,7 @@ test.serial('Get all commits since gitHead (from tag formatted like v<version>)
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
const result = await t.context.getCommits({version: '1.0.0'}, 'master');
const result = await getCommits({version: '1.0.0'}, 'master', t.context.logger);
// Verify the commits created and retrieved by the module are identical
t.is(result.commits.length, 2);
@@ -254,6 +258,7 @@ test.serial('Get all commits since gitHead (from tag formatted like v<version>)
// Verify the last release is returned and updated
t.truthy(result.lastRelease);
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
t.is(result.lastRelease.gitTag, 'v1.0.0');
t.is(result.lastRelease.version, '1.0.0');
});
@@ -268,7 +273,7 @@ test.serial('Get commits when last release gitHead is missing but a tag match th
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
const result = await t.context.getCommits({version: '1.0.0', gitHead: 'missing'}, 'master');
const result = await getCommits({version: '1.0.0', gitHead: 'missing'}, 'master', t.context.logger);
// Verify the commits created and retrieved by the module are identical
t.is(result.commits.length, 2);
@@ -285,6 +290,7 @@ test.serial('Get commits when last release gitHead is missing but a tag match th
// Verify the last release is returned and updated
t.truthy(result.lastRelease);
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
t.is(result.lastRelease.gitTag, 'v1.0.0');
t.is(result.lastRelease.version, '1.0.0');
});
@@ -297,7 +303,11 @@ test.serial('Get all commits since gitHead, when gitHead are mising from the sha
await gitShallowClone(repo);
// Retrieve the commits with the commits module, since commit 'First'
const result = await t.context.getCommits({version: '1.0.0', gitHead: commits[commits.length - 1].hash}, 'master');
const result = await getCommits(
{version: '1.0.0', gitHead: commits[commits.length - 1].hash},
'master',
t.context.logger
);
// Verify the commits created and retrieved by the module are identical
t.is(result.commits.length, 2);
@@ -315,6 +325,7 @@ test.serial('Get all commits since gitHead, when gitHead are mising from the sha
t.truthy(result.lastRelease);
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
t.is(result.lastRelease.version, '1.0.0');
t.falsy(result.lastRelease.gitTag);
});
test.serial('Get all commits since gitHead from tag, when tags are mising from the shallow clone', async t => {
@@ -333,7 +344,7 @@ test.serial('Get all commits since gitHead from tag, when tags are mising from t
t.is((await gitTags()).length, 0);
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
const result = await t.context.getCommits({version: '1.0.0'}, 'master');
const result = await getCommits({version: '1.0.0'}, 'master', t.context.logger);
// Verify the commits created and retrieved by the module are identical
t.is(result.commits.length, 2);
@@ -350,6 +361,7 @@ test.serial('Get all commits since gitHead from tag, when tags are mising from t
// Verify the last release is returned and updated
t.truthy(result.lastRelease);
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
t.is(result.lastRelease.gitTag, 'v1.0.0');
t.is(result.lastRelease.version, '1.0.0');
});
@@ -360,7 +372,7 @@ test.serial('Return empty array if lastRelease.gitHead is the last commit', asyn
const commits = await gitCommits(['First', 'Second']);
// Retrieve the commits with the commits module, since commit 'Second' (therefore none)
const result = await t.context.getCommits({gitHead: commits[0].hash, version: '1.0.0'}, 'master');
const result = await getCommits({gitHead: commits[0].hash, version: '1.0.0'}, 'master', t.context.logger);
// Verify no commit is retrieved
t.deepEqual(result.commits, []);
@@ -368,6 +380,7 @@ test.serial('Return empty array if lastRelease.gitHead is the last commit', asyn
t.truthy(result.lastRelease);
t.is(result.lastRelease.gitHead.substring(0, 7), commits[0].hash);
t.is(result.lastRelease.version, '1.0.0');
t.falsy(result.lastRelease.gitTag);
});
test.serial('Return empty array if there is no commits', async t => {
@@ -375,7 +388,7 @@ test.serial('Return empty array if there is no commits', async t => {
await gitRepo();
// Retrieve the commits with the commits module
const result = await t.context.getCommits({}, 'master');
const result = await getCommits({}, 'master', t.context.logger);
// Verify no commit is retrieved
t.deepEqual(result.commits, []);
@@ -392,7 +405,7 @@ test.serial('Throws ENOGITHEAD error if the gitHead of the last release cannot b
await gitCommits(['First', 'Second']);
// Retrieve the commits with the commits module
const error = await t.throws(t.context.getCommits({version: '1.0.0'}, 'master'));
const error = await t.throws(getCommits({version: '1.0.0'}, 'master', t.context.logger));
// Verify error code and type
t.is(error.code, 'ENOGITHEAD');
@@ -411,7 +424,7 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in history', async t =
await gitCommits(['First', 'Second']);
// Retrieve the commits with the commits module
const error = await t.throws(t.context.getCommits({gitHead: 'notinhistory'}, 'master'));
const error = await t.throws(getCommits({gitHead: 'notinhistory'}, 'master', t.context.logger));
// Verify error code and type
t.is(error.code, 'ENOTINHISTORY');
@@ -434,7 +447,9 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in branch history but
await gitCheckout('master', false);
// Retrieve the commits with the commits module
const error = await t.throws(t.context.getCommits({version: '1.0.1', gitHead: commitsBranch[0].hash}, 'master'));
const error = await t.throws(
getCommits({version: '1.0.1', gitHead: commitsBranch[0].hash}, 'master', t.context.logger)
);
// Verify error code and type
t.is(error.code, 'ENOTINHISTORY');
@@ -461,7 +476,9 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in detached head but p
await gitDetachedHead(repo, commitsMaster[0].hash);
// Retrieve the commits with the commits module, since commit 'Second'
const error = await t.throws(t.context.getCommits({version: '1.0.1', gitHead: commitsBranch[0].hash}, 'master'));
const error = await t.throws(
getCommits({version: '1.0.1', gitHead: commitsBranch[0].hash}, 'master', t.context.logger)
);
// Verify error code and type
t.is(error.code, 'ENOTINHISTORY');
@@ -488,7 +505,7 @@ test.serial('Throws ENOTINHISTORY error when a tag is not in branch history but
await gitCommits(['Forth']);
// Retrieve the commits with the commits module
const error = await t.throws(t.context.getCommits({version: '1.0.0'}, 'master'));
const error = await t.throws(getCommits({version: '1.0.0'}, 'master', t.context.logger));
// Verify error code and type
t.is(error.code, 'ENOTINHISTORY');
t.true(error instanceof SemanticReleaseError);
+2 -51
View File
@@ -1,6 +1,5 @@
import url from 'url';
import test from 'ava';
import {writeJson, writeFile} from 'fs-extra';
import {writeJson} from 'fs-extra';
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import normalizeData from 'normalize-package-data';
@@ -28,7 +27,6 @@ test.serial('Default values', async t => {
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', pkg);
process.env.GH_TOKEN = 'GH_TOKEN';
const result = await t.context.getConfig();
@@ -37,12 +35,6 @@ test.serial('Default values', async t => {
t.deepEqual(result.pkg, pkg);
// Verify the default options are set
t.is(result.options.branch, 'master');
t.is(result.options.githubToken, process.env.GH_TOKEN);
// Verify the default npm options are set
t.is(result.npm.tag, 'latest');
// Use the environment variable npm_config_registry as the default so the test works on both npm and yarn
t.is(result.npm.registry, url.format(url.parse(process.env.npm_config_registry || 'https://registry.npmjs.org/')));
});
test.serial('Read package.json configuration', async t => {
@@ -61,8 +53,6 @@ test.serial('Read package.json configuration', async t => {
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', pkg);
delete process.env.GH_TOKEN;
process.env.GITHUB_TOKEN = 'GITHUB_TOKEN';
const result = await t.context.getConfig();
@@ -77,11 +67,9 @@ test.serial('Read package.json configuration', async t => {
t.is(t.context.plugins.firstCall.args[0].generateNotes, release.generateNotes);
t.deepEqual(t.context.plugins.firstCall.args[0].getLastRelease, release.getLastRelease);
t.is(t.context.plugins.firstCall.args[0].branch, release.branch);
t.is(result.options.githubToken, process.env.GITHUB_TOKEN);
});
test.serial('Priority cli parameters over package.json configuration', async t => {
test.serial('Prioritise cli parameters over package.json configuration', async t => {
const release = {
analyzeCommits: 'analyzeCommits',
generateNotes: 'generateNotes',
@@ -104,8 +92,6 @@ test.serial('Priority cli parameters over package.json configuration', async t =
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', pkg);
delete process.env.GH_TOKEN;
process.env.GITHUB_TOKEN = 'GITHUB_TOKEN';
const result = await t.context.getConfig(options);
@@ -117,38 +103,3 @@ test.serial('Priority cli parameters over package.json configuration', async t =
t.deepEqual(t.context.plugins.firstCall.args[0].getLastRelease, options.getLastRelease);
t.is(t.context.plugins.firstCall.args[0].branch, options.branch);
});
test.serial('Get tag from .npmrc', async t => {
const pkg = {name: 'package_name'};
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', pkg);
// Create local .npmrc
await writeFile('.npmrc', 'tag=npmrc_tag');
// Make sure to not use the environment variable set by npm when running tests with npm run test
delete process.env.npm_config_tag;
const result = await t.context.getConfig();
// Verify the tag used in the one in .npmrc
t.is(result.npm.tag, 'npmrc_tag');
});
test.serial('Get tag from package.json, even if defined in .npmrc', async t => {
const publishConfig = {tag: 'pkg_tag'};
const pkg = {name: 'package_name', publishConfig};
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', pkg);
// Create local .npmrc
await writeFile('.npmrc', 'tag=npmrc_tag');
const result = await t.context.getConfig();
// Verify the tag used in the one in package.json
t.is(result.npm.tag, 'pkg_tag');
});
+7 -7
View File
@@ -1,36 +1,36 @@
import test from 'ava';
import {stub} from 'sinon';
import proxyquire from 'proxyquire';
import SemanticReleaseError from '@semantic-release/error';
import getNextVersion from '../lib/get-next-version';
test.beforeEach(t => {
// Stub the logger functions
t.context.log = stub();
t.context.getNextVersion = proxyquire('../lib/get-next-version', {'./logger': {log: t.context.log}});
t.context.logger = {log: t.context.log};
});
test('Increase version for patch release', t => {
const version = t.context.getNextVersion('patch', {version: '1.0.0'});
const version = getNextVersion('patch', {version: '1.0.0'}, t.context.logger);
t.is(version, '1.0.1');
});
test('Increase version for minor release', t => {
const version = t.context.getNextVersion('minor', {version: '1.0.0'});
const version = getNextVersion('minor', {version: '1.0.0'}, t.context.logger);
t.is(version, '1.1.0');
});
test('Increase version for major release', t => {
const version = t.context.getNextVersion('major', {version: '1.0.0'});
const version = getNextVersion('major', {version: '1.0.0'}, t.context.logger);
t.is(version, '2.0.0');
});
test('Return 1.0.0 if there is no previous release', t => {
const version = t.context.getNextVersion('minor', {});
const version = getNextVersion('minor', {}, t.context.logger);
t.is(version, '1.0.0');
});
test('Return an error if the release type is invalid', t => {
const error = t.throws(() => t.context.getNextVersion('invalid', {version: '1.0.0'}));
const error = t.throws(() => getNextVersion('invalid', {version: '1.0.0'}, t.context.logger));
t.is(error.code, 'EINVALIDTYPE');
t.true(error instanceof SemanticReleaseError);
});
-82
View File
@@ -1,82 +0,0 @@
import test from 'ava';
import {stub} from 'sinon';
const getRegistry = require('../lib/get-registry');
test('Get registry from package.json', t => {
// Retrieve the registry with the get-registry module and verify it returns the one from the package.json in parameter
t.is(getRegistry({name: 'publish-config', publishConfig: {registry: 'a'}}, {}), 'a');
});
test('Prioritize the package.json registry config', t => {
// Stub the npmconf object
const get = stub();
// Retrieve the registry with the get-registry module and verify it returns the one from the package.json in parameter
t.is(getRegistry({name: 'publish-config', publishConfig: {registry: 'b'}}, {get}), 'b');
// Verify the registry has been retrieved from the package.json without trying the stubbed npmconf
t.true(get.notCalled);
});
test('Get registry for regular package name', t => {
// Stub the npmconf object returns 'b' for 'registry' property
const get = stub()
.withArgs('registry')
.returns('b');
// Retrieve the registry with the get-registry module and verify it returns the one configured in the stubbed npmconf
t.is(getRegistry({name: 'normal'}, {get}), 'b');
// Verify the registry has been retrieved by calling the stubbed npmconf
t.true(get.calledWithExactly('registry'));
});
test('Get default registry', t => {
// Stub the npmconf object, returns 'null'
const get = stub().returns(null);
// Retrieve the registry with the get-registry module and verify it returns default one
t.is(getRegistry({name: 'normal'}, {get}), 'https://registry.npmjs.org/');
// Verify the module tried first to retrieve the registry by calling the stubbed npmconf
t.true(get.calledWithExactly('registry'));
});
test('Get registry for scoped package name', t => {
// Stub the npmconf object, returns 'c' for '@scoped/registry' property
const get = stub()
.withArgs('@scoped/registry')
.returns('c');
// Retrieve the registry with the get-registry module and verify it returns the one configured in the stubbed npmconf
t.is(getRegistry({name: '@scoped/foo'}, {get}), 'c');
// Verify the registry for the scope '@scoped' has been retrieved by calling the stubbed npmconf
t.true(get.calledWithExactly('@scoped/registry'));
});
test('Get regular registry for scoped package name', t => {
// Stub the npmconf object, returns 'd' for 'registry' property
const get = stub()
.withArgs('registry')
.returns('d');
// Retrieve the registry with the get-registry module and verify it returns the regular default one for `@scoped` packages
t.is(getRegistry({name: '@scoped/baz'}, {get}), 'd');
// Verify the module tried to retrieve the @scoped registry by calling the stubbed npmconf
t.true(get.calledWithExactly('@scoped/registry'));
});
test('Get default registry for scoped package name', t => {
// Stub the npmconf object, returns 'd' for 'registry' property
const get = stub().returns(null);
// Retrieve the registry with the get-registry module and verify it returns default one for `@scoped` packages
t.is(getRegistry({name: '@scoped/baz'}, {get}), 'https://registry.npmjs.org/');
// Verify the module tried to retrieve the @scoped registry by calling the stubbed npmconf
t.true(get.calledWithExactly('@scoped/registry'));
// Verify the module tried to retrieve the regular registry by calling the stubbed npmconf
t.true(get.calledWithExactly('registry'));
});
+95
View File
@@ -0,0 +1,95 @@
import test from 'ava';
import {gitRepo, gitCommits, gitCheckout, gitTagVersion, gitShallowClone, gitLog} from './helpers/git-utils';
import {gitTagHead, gitCommitTag, isCommitInHistory, unshallow, gitHead} from '../lib/git';
test.beforeEach(t => {
// Save the current working diretory
t.context.cwd = process.cwd();
});
test.afterEach.always(t => {
// Restore the current working directory
process.chdir(t.context.cwd);
});
test.serial('Get the last commit sha', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
const commits = await gitCommits(['First']);
const result = await gitHead();
t.is(result.substring(0, 7), commits[0].hash);
});
test.serial('Throw error if the last commit sha cannot be found', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
await t.throws(gitHead());
});
test.serial('Unshallow repository', async t => {
// Create a git repository, set the current working directory at the root of the repo
const repo = await gitRepo();
// Add commits to the master branch
await gitCommits(['First', 'Second']);
// Create a shallow clone with only 1 commit
await gitShallowClone(repo);
// Verify the shallow clone contains only one commit
t.is((await gitLog()).length, 1);
await unshallow();
// Verify the shallow clone contains all the commits
t.is((await gitLog()).length, 2);
});
test.serial('Do not throw error when unshallow a complete repository', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
await gitCommits(['First']);
await t.notThrows(unshallow());
});
test.serial('Verify if the commit `sha` is in the direct history of the current branch', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
const commits = await gitCommits(['First']);
// Create the new branch 'other-branch' from master
await gitCheckout('other-branch');
// Add commits to the 'other-branch' branch
const otherCommits = await gitCommits(['Second']);
await gitCheckout('master', false);
t.true(await isCommitInHistory(commits[0].hash));
t.false(await isCommitInHistory(otherCommits[0].hash));
});
test.serial('Get the tag associated with a commit sha or "null" if the commit does not exists', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
let commits = await gitCommits(['First']);
// Create the tag corresponding to version 1.0.0
await gitTagVersion('v1.0.0');
t.is(await gitCommitTag(commits[0].hash), 'v1.0.0');
t.falsy(await gitCommitTag('missing_sha'));
});
test.serial('Get the commit sha for a given tag or "null" if the tag does not exists', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
let commits = await gitCommits(['First']);
// Create the tag corresponding to version 1.0.0
await gitTagVersion('v1.0.0');
t.is((await gitTagHead('v1.0.0')).substring(0, 7), commits[0].hash);
t.falsy(await gitTagHead('missing_tag'));
});
-91
View File
@@ -1,91 +0,0 @@
import test from 'ava';
import {gitRepo, gitCommits, gitHead} from './helpers/git-utils';
import nock from 'nock';
import {authenticate} from './helpers/mock-github';
import githubRelease from '../lib/github-release';
test.beforeEach(t => {
// Save the current working diretory
t.context.cwd = process.cwd();
});
test.afterEach.always(t => {
// Restore the current working directory
process.chdir(t.context.cwd);
// Reset nock
nock.cleanAll();
});
test.serial('Github release with default url', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
await gitCommits(['fix: First fix', 'feat: Second feature']);
const sha = await gitHead();
const owner = 'test_user';
const repo = 'test_repo';
const githubToken = 'github_token';
const notes = 'Test release note body';
const version = '1.0.0';
const branch = 'master';
const tagName = `v${version}`;
const options = {branch, githubToken};
const pkg = {version, repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}};
const releaseUrl = `https://othertesturl.com/${owner}/${repo}/releases/${version}`;
// Mock github API for releases and git/refs endpoints
const github = authenticate({githubToken})
.post(`/repos/${owner}/${repo}/releases`, {
tag_name: tagName,
target_commitish: branch,
name: tagName,
body: notes,
})
.reply(200, {html_url: releaseUrl})
.post(`/repos/${owner}/${repo}/git/refs`, {ref: `refs/tags/${tagName}`, sha})
.reply({});
// Call the post module
t.is(releaseUrl, await githubRelease(pkg, notes, version, options));
// Verify the releases and git/refs endpoint have been call with expected requests
t.true(github.isDone());
});
test.serial('Github release with custom url', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
await gitCommits(['fix: First fix', 'feat: Second feature']);
const sha = await gitHead();
const owner = 'test_user';
const repo = 'test_repo';
const githubUrl = 'https://testurl.com:443';
const githubToken = 'github_token';
const githubApiPathPrefix = 'prefix';
const notes = 'Test release note body';
const version = '1.0.0';
const branch = 'master';
const tagName = `v${version}`;
const options = {branch, githubUrl, githubToken, githubApiPathPrefix};
const pkg = {version, repository: {url: `git@othertesturl.com:${owner}/${repo}.git`}};
const releaseUrl = `https://othertesturl.com/${owner}/${repo}/releases/${version}`;
// Mock github API for releases and git/refs endpoints
const github = authenticate({githubUrl, githubToken, githubApiPathPrefix})
.post(`/repos/${owner}/${repo}/releases`, {
tag_name: tagName,
target_commitish: branch,
name: tagName,
body: notes,
})
.reply(200, {html_url: releaseUrl})
.post(`/repos/${owner}/${repo}/git/refs`, {ref: `refs/tags/${tagName}`, sha})
.reply({});
// Call the post module
t.is(releaseUrl, await githubRelease(pkg, notes, version, options));
// Verify the releases and git/refs endpoint have been call with expected requests
t.true(github.isDone());
});
+1 -6
View File
@@ -1,4 +1,3 @@
import {mkdir} from 'fs-extra';
import tempy from 'tempy';
import execa from 'execa';
import pMapSeries from 'p-map-series';
@@ -15,17 +14,13 @@ import pMapSeries from 'p-map-series';
/**
* Create a temporary git repository and change the current working directory to the repository root.
*
* @method gitCommits
* @param {Array<Commit>} commits the created commits.
*
* @return {string} The path of the repository.
*/
export async function gitRepo() {
const dir = tempy.directory();
process.chdir(dir);
await mkdir('git-templates');
await execa('git', ['init', '--template=./git-templates']);
await execa('git', ['init']);
await gitCheckout('master');
return dir;
}
+6 -4
View File
@@ -53,13 +53,13 @@ const url = `http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}`;
* @param {Object} response.body The JSON object to respond in the response body.
* @return {Object} An object representation the expectation. Pass to the `verify` function to validate the `mockserver` has been called with a `request` matching the expectations.
*/
function mock(
async function mock(
path,
{body: requestBody, headers: requestHeaders},
{method = 'POST', statusCode = 200, body: responseBody}
) {
client.mockAnyResponse({
httpRequest: {path},
await client.mockAnyResponse({
httpRequest: {path, method},
httpResponse: {
statusCode,
headers: [{name: 'Content-Type', values: ['application/json; charset=utf-8']}],
@@ -72,7 +72,9 @@ function mock(
method,
path,
headers: requestHeaders,
body: {type: 'JSON', json: JSON.stringify(requestBody), matchType: 'ONLY_MATCHING_FIELDS'},
body: requestBody
? {type: 'JSON', json: JSON.stringify(requestBody), matchType: 'ONLY_MATCHING_FIELDS'}
: undefined,
};
}
+163 -230
View File
@@ -1,278 +1,211 @@
import {callbackify} from 'util';
import test from 'ava';
import {writeJson} from 'fs-extra';
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import SemanticReleaseError from '@semantic-release/error';
const consoleLog = stub(console, 'log');
import normalizeData from 'normalize-package-data';
import {gitHead as getGitHead} from '../lib/git';
import {gitRepo, gitCommits, gitTagVersion} from './helpers/git-utils';
test.beforeEach(t => {
// Save the current process.env
t.context.env = Object.assign({}, process.env);
// Save the current working diretory
t.context.cwd = process.cwd();
// Stub the logger functions
t.context.log = stub();
t.context.error = stub();
t.context.logger = {log: t.context.log, error: t.context.error};
t.context.semanticRelease = proxyquire('../index', {'./lib/logger': t.context.logger});
t.context.stdout = stub(process.stdout, 'write');
t.context.stderr = stub(process.stderr, 'write');
});
test.afterEach.always(t => {
// Restore the current working directory
process.chdir(t.context.cwd);
// Restore process.env
process.env = Object.assign({}, t.context.env);
// Restore the current working directory
process.chdir(t.context.cwd);
t.context.stdout.restore();
t.context.stderr.restore();
});
test.after.always(t => {
consoleLog.restore();
});
test.serial('Plugins are called with expected values', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
let commits = await gitCommits(['First']);
// Create the tag corresponding to version 1.0.0
await gitTagVersion('v1.0.0');
// Add new commits to the master branch
commits = (await gitCommits(['Second'])).concat(commits);
test('Plugins are called with expected values', async t => {
const env = {NPM_TOKEN: 'NPM_TOKEN'};
const pkgOptions = {branch: 'master'};
const cliOptions = {githubToken: 'GH_TOKEN'};
const options = Object.assign({}, pkgOptions, cliOptions);
const pkg = {name: 'available', release: options, repository: {url: 'http://github.com/whats/up.git'}};
const npm = {registry: 'http://test.registry.com'};
const lastRelease = {version: '1.0.0', gitHead: 'test_commit_head'};
const commitsLastRelease = {version: '1.0.0', gitHead: 'tag_head'};
const commits = [{hash: '1', message: 'fix: First fix'}, {hash: '2', message: 'feat: First feature'}];
const nextRelease = {type: 'major', version: '2.0.0'};
const name = 'package-name';
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
const notes = 'Release notes';
// Stub modules
const log = stub();
const error = stub();
const logger = {log, error};
const verifyAuth = stub().returns();
const publishNpm = stub().resolves();
const githubRelease = stub().resolves();
const getCommits = stub().resolves({commits, lastRelease: commitsLastRelease});
const getNextVersion = stub().returns(nextRelease.version);
// Stub plugins
const verifyConditions = stub().resolves();
const verifyConditions1 = stub().resolves();
const verifyConditions2 = stub().resolves();
const getLastRelease = stub().resolves(lastRelease);
const analyzeCommits = stub().resolves(nextRelease.type);
const verifyRelease = stub().resolves();
const generateNotes = stub().resolves(notes);
const getConfig = stub().resolves({
plugins: {getLastRelease, analyzeCommits, verifyRelease, verifyConditions, generateNotes},
env,
options,
pkg,
npm,
});
const publish = stub().resolves();
const semanticRelease = proxyquire('../index', {
'./lib/logger': logger,
'./lib/verify-auth': verifyAuth,
'./lib/get-config': getConfig,
'./lib/get-commits': getCommits,
'./lib/publish-npm': publishNpm,
'./lib/github-release': githubRelease,
'./lib/get-next-version': getNextVersion,
});
const options = {
branch: 'master',
verifyConditions: [callbackify(verifyConditions1), callbackify(verifyConditions2)],
getLastRelease: callbackify(getLastRelease),
analyzeCommits: callbackify(analyzeCommits),
verifyRelease: callbackify(verifyRelease),
generateNotes: callbackify(generateNotes),
publish: callbackify(publish),
};
const pkg = {name, version: '0.0.0-dev'};
normalizeData(pkg);
// Call the index module
await semanticRelease(cliOptions);
await writeJson('./package.json', pkg);
await t.context.semanticRelease(options);
t.true(verifyConditions1.calledOnce);
t.deepEqual(verifyConditions1.firstCall.args[1], {env: process.env, options, pkg, logger: t.context.logger});
t.true(verifyConditions2.calledOnce);
t.deepEqual(verifyConditions2.firstCall.args[1], {env: process.env, options, pkg, logger: t.context.logger});
// Verify the sub-modules have been called with expected parameters
t.true(getConfig.calledOnce);
t.true(getConfig.calledWithExactly(cliOptions));
t.true(verifyAuth.calledOnce);
t.true(verifyAuth.calledWithExactly(options, env));
t.true(publishNpm.calledOnce);
t.true(publishNpm.calledWithExactly(pkg, npm, nextRelease));
t.true(githubRelease.calledOnce);
t.true(githubRelease.calledWithExactly(pkg, notes, nextRelease.version, options));
// Verify plugins have been called with expected parameters
t.true(verifyConditions.calledOnce);
t.true(verifyConditions.calledWithExactly({env, options, pkg, npm, logger}));
t.true(getLastRelease.calledOnce);
t.true(getLastRelease.calledWithExactly({env, options, pkg, npm, logger}));
t.deepEqual(getLastRelease.firstCall.args[1], {env: process.env, options, pkg, logger: t.context.logger});
t.true(analyzeCommits.calledOnce);
t.true(analyzeCommits.calledWithExactly({env, options, pkg, npm, logger, lastRelease: commitsLastRelease, commits}));
t.deepEqual(analyzeCommits.firstCall.args[1].env, process.env);
t.deepEqual(analyzeCommits.firstCall.args[1].options, options);
t.deepEqual(analyzeCommits.firstCall.args[1].pkg, pkg);
t.deepEqual(analyzeCommits.firstCall.args[1].logger, t.context.logger);
t.deepEqual(analyzeCommits.firstCall.args[1].lastRelease, lastRelease);
t.deepEqual(analyzeCommits.firstCall.args[1].commits[0].hash.substring(0, 7), commits[0].hash);
t.deepEqual(analyzeCommits.firstCall.args[1].commits[0].message, commits[0].message);
t.true(verifyRelease.calledOnce);
t.true(
verifyRelease.calledWithExactly({
env,
options,
pkg,
npm,
logger,
lastRelease: commitsLastRelease,
commits,
nextRelease,
})
);
t.deepEqual(verifyRelease.firstCall.args[1].env, process.env);
t.deepEqual(verifyRelease.firstCall.args[1].options, options);
t.deepEqual(verifyRelease.firstCall.args[1].pkg, pkg);
t.deepEqual(verifyRelease.firstCall.args[1].logger, t.context.logger);
t.deepEqual(verifyRelease.firstCall.args[1].lastRelease, lastRelease);
t.deepEqual(verifyRelease.firstCall.args[1].commits[0].hash.substring(0, 7), commits[0].hash);
t.deepEqual(verifyRelease.firstCall.args[1].commits[0].message, commits[0].message);
t.deepEqual(verifyRelease.firstCall.args[1].nextRelease, nextRelease);
t.true(generateNotes.calledOnce);
t.true(
generateNotes.calledWithExactly({
env,
options,
pkg,
npm,
logger,
lastRelease: commitsLastRelease,
commits,
nextRelease,
})
);
t.deepEqual(generateNotes.firstCall.args[1].env, process.env);
t.deepEqual(generateNotes.firstCall.args[1].options, options);
t.deepEqual(generateNotes.firstCall.args[1].pkg, pkg);
t.deepEqual(generateNotes.firstCall.args[1].logger, t.context.logger);
t.deepEqual(generateNotes.firstCall.args[1].lastRelease, lastRelease);
t.deepEqual(generateNotes.firstCall.args[1].commits[0].hash.substring(0, 7), commits[0].hash);
t.deepEqual(generateNotes.firstCall.args[1].commits[0].message, commits[0].message);
t.deepEqual(generateNotes.firstCall.args[1].nextRelease, nextRelease);
t.true(publish.calledOnce);
t.deepEqual(publish.firstCall.args[1].options, options);
t.deepEqual(publish.firstCall.args[1].pkg, pkg);
t.deepEqual(publish.firstCall.args[1].logger, t.context.logger);
t.deepEqual(publish.firstCall.args[1].lastRelease, lastRelease);
t.deepEqual(publish.firstCall.args[1].commits[0].hash.substring(0, 7), commits[0].hash);
t.deepEqual(publish.firstCall.args[1].commits[0].message, commits[0].message);
t.deepEqual(publish.firstCall.args[1].nextRelease, Object.assign({}, nextRelease, {notes}));
});
test('Dry-run skips verifyAuth, verifyConditions, publishNpm and githubRelease', async t => {
const env = {NPM_TOKEN: 'NPM_TOKEN'};
const pkgOptions = {branch: 'master'};
const cliOptions = {githubToken: 'GH_TOKEN', dryRun: true};
const options = Object.assign({}, pkgOptions, cliOptions);
const pkg = {name: 'available', release: options, repository: {url: 'http://github.com/whats/up.git'}};
const npm = {registry: 'http://test.registry.com'};
const lastRelease = {version: '1.0.0', gitHead: 'test_commit_head'};
const commitsLastRelease = {version: '1.0.0', gitHead: 'tag_head'};
const commits = [{hash: '1', message: 'fix: First fix'}, {hash: '2', message: 'feat: First feature'}];
const nextRelease = {type: 'major', version: '2.0.0'};
test.serial('Use new gitHead, and recreate release notes if a publish plugin create a commit', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
let commits = await gitCommits(['First']);
// Create the tag corresponding to version 1.0.0
await gitTagVersion('v1.0.0');
// Add new commits to the master branch
commits = (await gitCommits(['Second'])).concat(commits);
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
const notes = 'Release notes';
const generateNotes = stub().resolves(notes);
const publish1 = stub().callsFake(async () => {
await gitCommits(['Third']);
});
const publish2 = stub().resolves();
const options = {
branch: 'master',
verifyConditions: callbackify(stub().resolves()),
getLastRelease: callbackify(stub().resolves(lastRelease)),
analyzeCommits: callbackify(stub().resolves(nextRelease.type)),
verifyRelease: callbackify(stub().resolves()),
generateNotes: callbackify(generateNotes),
publish: [callbackify(publish1), callbackify(publish2)],
};
await writeJson('./package.json', {});
await t.context.semanticRelease(options);
t.true(generateNotes.calledTwice);
t.deepEqual(generateNotes.firstCall.args[1].nextRelease, nextRelease);
t.true(publish1.calledOnce);
t.deepEqual(publish1.firstCall.args[1].nextRelease, Object.assign({}, nextRelease, {notes}));
nextRelease.gitHead = await getGitHead();
t.deepEqual(generateNotes.secondCall.args[1].nextRelease, Object.assign({}, nextRelease, {notes}));
t.true(publish2.calledOnce);
t.deepEqual(publish2.firstCall.args[1].nextRelease, Object.assign({}, nextRelease, {notes}));
});
test.serial('Dry-run skips verifyConditions and publish', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add commits to the master branch
let commits = await gitCommits(['First']);
// Create the tag corresponding to version 1.0.0
await gitTagVersion('v1.0.0');
// Add new commits to the master branch
commits = (await gitCommits(['Second'])).concat(commits);
const name = 'package-name';
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
const notes = 'Release notes';
// Stub modules
const log = stub();
const error = stub();
const logger = {log, error};
const verifyAuth = stub().returns();
const publishNpm = stub().resolves();
const githubRelease = stub().resolves();
const getCommits = stub().resolves({commits, lastRelease: commitsLastRelease});
const getNextVersion = stub().returns(nextRelease.version);
// Stub plugins
const verifyConditions = stub().resolves();
const getLastRelease = stub().resolves(lastRelease);
const analyzeCommits = stub().resolves(nextRelease.type);
const verifyRelease = stub().resolves();
const generateNotes = stub().resolves(notes);
const getConfig = stub().resolves({
plugins: {getLastRelease, analyzeCommits, verifyRelease, verifyConditions, generateNotes},
env,
options,
pkg,
npm,
});
const publish = stub().resolves();
const semanticRelease = proxyquire('../index', {
'./lib/logger': logger,
'./lib/verify-auth': verifyAuth,
'./lib/get-config': getConfig,
'./lib/get-commits': getCommits,
'./lib/publish-npm': publishNpm,
'./lib/github-release': githubRelease,
'./lib/get-next-version': getNextVersion,
});
const options = {
dryRun: true,
branch: 'master',
verifyConditions: callbackify(verifyConditions),
getLastRelease: callbackify(getLastRelease),
analyzeCommits: callbackify(analyzeCommits),
verifyRelease: callbackify(verifyRelease),
generateNotes: callbackify(generateNotes),
publish: callbackify(publish),
};
const pkg = {name, version: '0.0.0-dev'};
normalizeData(pkg);
// Call the index module
await semanticRelease(cliOptions);
await writeJson('./package.json', pkg);
await t.context.semanticRelease(options);
// Verify that publishNpm, githubRelease, verifyAuth, verifyConditions have not been called in a dry run
t.true(publishNpm.notCalled);
t.true(githubRelease.notCalled);
t.true(verifyAuth.notCalled);
t.true(verifyConditions.notCalled);
// Verify the release notes are logged
t.true(consoleLog.calledWithMatch(notes));
// Verify the sub-modules have been called with expected parameters
t.true(getConfig.calledOnce);
t.true(getConfig.calledWithExactly(cliOptions));
// Verify plugins have been called with expected parameters
t.true(getLastRelease.calledOnce);
t.true(getLastRelease.calledWithExactly({env, options, pkg, npm, logger}));
t.true(analyzeCommits.calledOnce);
t.true(analyzeCommits.calledWithExactly({env, options, pkg, npm, logger, lastRelease: commitsLastRelease, commits}));
t.true(verifyRelease.calledOnce);
t.true(
verifyRelease.calledWithExactly({
env,
options,
pkg,
npm,
logger,
lastRelease: commitsLastRelease,
commits,
nextRelease,
})
);
t.true(generateNotes.calledOnce);
t.true(
generateNotes.calledWithExactly({
env,
options,
pkg,
npm,
logger,
lastRelease: commitsLastRelease,
commits,
nextRelease,
})
);
});
test('Throw SemanticReleaseError if there is no release to be done', async t => {
const env = {NPM_TOKEN: 'NPM_TOKEN'};
const pkgOptions = {branch: 'master'};
const cliOptions = {githubToken: 'GH_TOKEN'};
const options = Object.assign({}, pkgOptions, cliOptions);
const pkg = {name: 'available', release: options, repository: {url: 'http://github.com/whats/up.git'}};
const npm = {registry: 'http://test.registry.com'};
const lastRelease = {version: '1.0.0', gitHead: 'test_commit_head'};
const commitsLastRelease = {version: '1.0.0', gitHead: 'tag_head'};
const commits = [{hash: '1', message: 'fix: First fix'}, {hash: '2', message: 'feat: First feature'}];
const nextRelease = {type: undefined};
// Stub modules
const log = stub();
const error = stub();
const logger = {log, error};
const verifyAuth = stub().returns();
const publishNpm = stub().resolves();
const githubRelease = stub().resolves();
const getCommits = stub().resolves({commits, lastRelease: commitsLastRelease});
const getNextVersion = stub().returns(null);
// Stub plugins
const verifyConditions = stub().resolves();
const getLastRelease = stub().resolves(lastRelease);
const analyzeCommits = stub().resolves(nextRelease.type);
const verifyRelease = stub().resolves();
const generateNotes = stub().resolves();
const getConfig = stub().resolves({
plugins: {getLastRelease, analyzeCommits, verifyRelease, verifyConditions, generateNotes},
env,
options,
pkg,
npm,
});
const semanticRelease = proxyquire('../index', {
'./lib/logger': logger,
'./lib/verify-auth': verifyAuth,
'./lib/get-config': getConfig,
'./lib/get-commits': getCommits,
'./lib/publish-npm': publishNpm,
'./lib/github-release': githubRelease,
'./lib/get-next-version': getNextVersion,
});
// Call the index module
const err = await t.throws(semanticRelease(cliOptions));
// Verify error code and type
t.is(err.code, 'ENOCHANGE');
t.true(err instanceof SemanticReleaseError);
// Verify the sub-modules have been called with expected parameters
t.true(getConfig.calledOnce);
t.true(getConfig.calledWithExactly(cliOptions));
t.true(verifyAuth.calledOnce);
t.true(verifyAuth.calledWithExactly(options, env));
// Verify plugins have been called with expected parameters
t.true(verifyConditions.calledOnce);
t.true(verifyConditions.calledWithExactly({env, options, pkg, npm, logger}));
t.true(getLastRelease.calledOnce);
t.true(getLastRelease.calledWithExactly({env, options, pkg, npm, logger}));
t.true(analyzeCommits.calledOnce);
t.true(analyzeCommits.calledWithExactly({env, options, pkg, npm, logger, lastRelease: commitsLastRelease, commits}));
// Verify that verifyRelease, publishNpm, generateNotes, githubRelease have not been called when no release is done
t.true(verifyRelease.notCalled);
t.true(generateNotes.notCalled);
t.true(publishNpm.notCalled);
t.true(githubRelease.notCalled);
t.true(publish.notCalled);
});
+165 -134
View File
@@ -11,15 +11,17 @@ import semanticRelease from '..';
const env = {
npm_config_registry: registry.uri,
GH_TOKEN: 'github_token',
NPM_OLD_TOKEN: 'aW50ZWdyYXRpb246c3VjaHNlY3VyZQ==',
GITHUB_URL: mockServer.url,
NPM_EMAIL: 'integration@test.com',
NPM_USERNAME: 'integration',
NPM_PASSWORD: 'suchsecure',
};
const cli = require.resolve('../bin/semantic-release');
const noop = require.resolve('../lib/plugin-noop');
const pluginError = require.resolve('./fixtures/plugin-error-a');
const pluginError = require.resolve('./fixtures/plugin-error');
const pluginInheritedError = require.resolve('./fixtures/plugin-error-inherited');
test.before(async t => {
// Start Mock Server
await mockServer.start();
// Start the local NPM registry
await registry.start();
@@ -33,6 +35,8 @@ test.beforeEach(async t => {
t.context.log = stub(console, 'log');
t.context.error = stub(console, 'error');
t.context.stdout = stub(process.stdout, 'write');
t.context.stderr = stub(process.stderr, 'write');
});
test.afterEach.always(async t => {
@@ -43,6 +47,8 @@ test.afterEach.always(async t => {
t.context.log.restore();
t.context.error.restore();
t.context.stdout.restore();
t.context.stderr.restore();
});
test.after.always(async t => {
@@ -52,8 +58,8 @@ test.after.always(async t => {
});
test.serial('Release patch, minor and major versions', async t => {
const packageName = 'test-module';
const repo = 'test-repo';
const packageName = 'test-release';
const owner = 'test-owner';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
@@ -61,14 +67,20 @@ test.serial('Release patch, minor and major versions', async t => {
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${repo}/${packageName}`},
release: {verifyConditions: noop, githubUrl: mockServer.url},
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {verifyConditions: ['@semantic-release/github', '@semantic-release/npm']},
publishConfig: {registry: registry.uri},
});
// Create a npm-shrinkwrap.json file
await execa('npm', ['shrinkwrap'], {env});
/** No release **/
let verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
t.log('Commit a chore');
await gitCommits(['chore: Init repository']);
t.log('$ semantic-release');
@@ -78,13 +90,18 @@ test.serial('Release patch, minor and major versions', async t => {
/** Initial release **/
let version = '1.0.0';
let createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
let createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
let createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
let createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
@@ -97,7 +114,7 @@ test.serial('Release patch, minor and major versions', async t => {
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
@@ -112,18 +129,24 @@ test.serial('Release patch, minor and major versions', async t => {
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
/** Patch release **/
version = '1.0.1';
createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
@@ -136,7 +159,7 @@ test.serial('Release patch, minor and major versions', async t => {
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
@@ -145,24 +168,30 @@ test.serial('Release patch, minor and major versions', async t => {
// Retrieve the published package from the registry and check version and gitHead
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
);
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
/** Minor release **/
version = '1.1.0';
createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
@@ -175,7 +204,7 @@ test.serial('Release patch, minor and major versions', async t => {
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
@@ -184,24 +213,30 @@ test.serial('Release patch, minor and major versions', async t => {
// Retrieve the published package from the registry and check version and gitHead
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
);
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
/** Major release **/
version = '2.0.0';
createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
@@ -214,7 +249,7 @@ test.serial('Release patch, minor and major versions', async t => {
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
@@ -223,19 +258,20 @@ test.serial('Release patch, minor and major versions', async t => {
// Retrieve the published package from the registry and check version and gitHead
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
);
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
});
test.serial('Release versions from a packed git repository, using tags to determine last release gitHead', async t => {
const packageName = 'test-module-2';
const repo = 'test-repo';
const packageName = 'test-git-packaed';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
await gitRepo();
@@ -244,19 +280,25 @@ test.serial('Release versions from a packed git repository, using tags to determ
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git@github.com:${repo}/${packageName}.git`},
release: {verifyConditions: noop, githubUrl: mockServer.url},
repository: {url: `git@github.com:${owner}/${packageName}.git`},
release: {verifyConditions: ['@semantic-release/github', '@semantic-release/npm']},
publishConfig: {registry: registry.uri},
});
/** Initial release **/
let version = '1.0.0';
let createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
let verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
let createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
let createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
let createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
@@ -270,7 +312,7 @@ test.serial('Release versions from a packed git repository, using tags to determ
t.log('$ semantic-release');
let {stdout, code} = await execa(cli, [], {env});
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json has been updated
t.is((await readJson('./package.json')).version, version);
@@ -278,6 +320,7 @@ test.serial('Release versions from a packed git repository, using tags to determ
let releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env})).stdout;
t.is(releasedVersion, version);
t.log(`+ released ${releasedVersion}`);
await mockServer.verify(verifyMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
// Create a tag version so the tag can be used later to determine the commit associated with the version
@@ -286,13 +329,18 @@ test.serial('Release versions from a packed git repository, using tags to determ
/** Patch release **/
version = '1.0.1';
createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
@@ -304,7 +352,7 @@ test.serial('Release versions from a packed git repository, using tags to determ
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json has been updated
t.is((await readJson('./package.json')).version, version);
@@ -313,21 +361,21 @@ test.serial('Release versions from a packed git repository, using tags to determ
releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env})).stdout;
t.is(releasedVersion, version);
t.log(`+ released ${releasedVersion}`);
await mockServer.verify(verifyMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
});
test.serial('Exit with 1 if a plugin is not found', async t => {
const packageName = 'test-module-3';
const repo = 'test-repo';
const packageName = 'test-plugin-not-found';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
await gitRepo();
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${repo}/${packageName}`},
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {analyzeCommits: 'non-existing-path'},
});
@@ -337,8 +385,8 @@ test.serial('Exit with 1 if a plugin is not found', async t => {
});
test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', async t => {
const packageName = 'test-module-4';
const repo = 'test-repo';
const packageName = 'test-recovery';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
await gitRepo();
@@ -347,19 +395,25 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${repo}/${packageName}`},
release: {verifyConditions: noop, githubUrl: mockServer.url},
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {verifyConditions: ['@semantic-release/github', '@semantic-release/npm']},
publishConfig: {registry: registry.uri},
});
/** Initial release **/
let version = '1.0.0';
let createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
let verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
let createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
let createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
let createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
@@ -371,7 +425,7 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
t.log('$ semantic-release');
let {stderr, stdout, code} = await execa(cli, [], {env});
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json has been updated
t.is((await readJson('./package.json')).version, version);
@@ -384,6 +438,7 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
t.is(releasedGitHead, head);
t.is(releasedVersion, version);
t.log(`+ released ${releasedVersion}`);
await mockServer.verify(verifyMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
@@ -397,7 +452,11 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
const {hash} = await gitAmmendCommit('feat: Initial commit');
/** Patch release **/
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
t.log('Commit a fix');
await gitCommits(['fix: bar']);
t.log('$ semantic-release');
@@ -420,13 +479,18 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
await gitTagVersion(`v${version}`, hash);
version = '1.0.1';
createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
@@ -437,7 +501,7 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
t.log('$ semantic-release');
({stderr, stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json has been updated
t.is((await readJson('./package.json')).version, version);
@@ -446,13 +510,14 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env})).stdout;
t.is(releasedVersion, version);
t.log(`+ released ${releasedVersion}`);
await mockServer.verify(verifyMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
});
test.serial('Dry-run', async t => {
const packageName = 'test-module-5';
const repo = 'test-repo';
const packageName = 'test-dry-run';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
@@ -460,8 +525,7 @@ test.serial('Dry-run', async t => {
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${repo}/${packageName}`},
release: {githubUrl: mockServer.url},
repository: {url: `git+https://github.com/${owner}/${packageName}`},
});
/** Initial release **/
@@ -480,9 +544,8 @@ test.serial('Dry-run', async t => {
});
test.serial('Pass options via CLI arguments', async t => {
const packageName = 'test-module-6';
const repo = 'test-repo';
const githubToken = 'OTHER_TOKEN';
const packageName = 'test-cli';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
@@ -490,36 +553,21 @@ test.serial('Pass options via CLI arguments', async t => {
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${repo}/${packageName}`},
release: {githubUrl: mockServer.url},
repository: {url: `git+https://github.com/${owner}/${packageName}`},
publishConfig: {registry: registry.uri},
});
/** Initial release **/
let version = '1.0.0';
let createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${githubToken}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
let createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${githubToken}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ semantic-release');
let {stdout, code} = await execa(
cli,
['--github-token', githubToken, '--verify-conditions', `${noop}, ${noop}`, '--debug'],
['--verify-conditions', '@semantic-release/npm', '--publish', '@semantic-release/npm', '--debug'],
{env}
);
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and has been updated
@@ -532,14 +580,11 @@ test.serial('Pass options via CLI arguments', async t => {
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
});
test.serial('Run via JS API', async t => {
const packageName = 'test-module-7';
const repo = 'test-repo';
const packageName = 'test-js-api';
const owner = 'test-repo';
const githubToken = 'OTHER_TOKEN';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
@@ -548,19 +593,24 @@ test.serial('Run via JS API', async t => {
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${repo}/${packageName}`},
release: {githubUrl: mockServer.url},
repository: {url: `git+https://github.com/${owner}/${packageName}`},
publishConfig: {registry: registry.uri},
});
/** Initial release **/
let version = '1.0.0';
let createRefMock = mockServer.mock(
`/repos/${repo}/${packageName}/git/refs`,
let verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${githubToken}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
let createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${githubToken}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
let createReleaseMock = mockServer.mock(
`/repos/${repo}/${packageName}/releases`,
let createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${githubToken}`]}],
@@ -573,10 +623,14 @@ test.serial('Run via JS API', async t => {
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ Call semantic-release via API');
await semanticRelease({githubToken, verifyConditions: [noop, noop], debug: true});
await semanticRelease({
verifyConditions: [{path: '@semantic-release/github', githubToken}, '@semantic-release/npm'],
publish: [{path: '@semantic-release/github', githubToken}, '@semantic-release/npm'],
debug: true,
});
t.true(t.context.log.calledWithMatch(/Published Github release: /, new RegExp(`release-url/${version}`)));
t.true(t.context.log.calledWithMatch(/Publishing version .* to npm registry/, version, registry.uri));
t.true(t.context.log.calledWithMatch(/Publishing version .* to npm registry/, version));
// Verify package.json and has been updated
t.is((await readJson('./package.json')).version, version);
@@ -589,37 +643,14 @@ test.serial('Run via JS API', async t => {
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
});
test.serial('Returns and error code if NPM token is invalid', async t => {
const env = {npm_config_registry: registry.uri, GH_TOKEN: 'github_token', NPM_TOKEN: 'wrong_token'};
const packageName = 'test-module-8';
const repo = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${repo}/${packageName}`},
release: {verifyConditions: noop, githubUrl: mockServer.url},
});
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ semantic-release');
let {stderr, code} = await execa(cli, [], {env, reject: false});
t.regex(stderr, /forbidden Please log in before writing to the db/);
t.is(code, 1);
});
test.serial('Log unexpected errors from plugins and exit with 1', async t => {
const packageName = 'test-module-9';
const repo = 'test-repo';
const packageName = 'test-unexpected-error';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
@@ -627,8 +658,8 @@ test.serial('Log unexpected errors from plugins and exit with 1', async t => {
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${repo}/${packageName}`},
release: {githubUrl: mockServer.url, verifyConditions: pluginError},
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {verifyConditions: pluginError},
});
/** Initial release **/
@@ -646,8 +677,8 @@ test.serial('Log unexpected errors from plugins and exit with 1', async t => {
});
test.serial('Log errors inheriting SemanticReleaseError and exit with 0', async t => {
const packageName = 'test-module-10';
const repo = 'test-repo';
const packageName = 'test-inherited-error';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
@@ -655,8 +686,8 @@ test.serial('Log errors inheriting SemanticReleaseError and exit with 0', async
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${repo}/${packageName}`},
release: {githubUrl: mockServer.url, verifyConditions: pluginInheritedError},
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {verifyConditions: pluginInheritedError},
});
/** Initial release **/
-123
View File
@@ -1,123 +0,0 @@
import {promisify} from 'util';
import test from 'ava';
import proxyquire from 'proxyquire';
import {stub, match} from 'sinon';
test.beforeEach(t => {
// Stub the logger functions
t.context.log = stub();
t.context.plugins = proxyquire('../lib/plugins', {'./logger': {log: t.context.log}});
});
test('Export plugins', t => {
// Call the plugin module
const defaultPlugins = t.context.plugins({});
// Verify the module returns a function for each plugin
t.is(typeof defaultPlugins.analyzeCommits, 'function');
t.is(typeof defaultPlugins.generateNotes, 'function');
t.is(typeof defaultPlugins.verifyConditions, 'function');
t.is(typeof defaultPlugins.verifyRelease, 'function');
t.is(typeof defaultPlugins.getLastRelease, 'function');
});
test('Pipeline - Get all results', async t => {
// Call the plugin module with a verifyRelease plugin pipeline
const pipelinePlugins = t.context.plugins({
verifyRelease: ['./lib/plugin-noop', './test/fixtures/plugin-result-a', './test/fixtures/plugin-result-b'],
});
// Call the verifyRelease pipeline
const results = await pipelinePlugins.verifyRelease({});
// Verify the pipeline return the expected result for each plugin, in order
t.deepEqual(results, [undefined, 'a', 'b']);
// Verify the logger has been called with the plugins path
t.true(t.context.log.calledWith(match.string, './lib/plugin-noop'));
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-result-a'));
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-result-b'));
});
test('Pipeline - Pass pluginConfig and options to each plugins', async t => {
// Plugin configuration with options (plugin-result-config is a mock plugin returning its pluginConfig and options parameters)
const pluginConfig = {path: './test/fixtures/plugin-result-config', pluginParam: 'param1'};
// Semantic-release global options
const options = {semanticReleaseParam: 'param2'};
// Call the plugin module with a verifyRelease plugin pipeline
const pipelinePlugins = t.context.plugins({
verifyRelease: [pluginConfig, './test/fixtures/plugin-result-config'],
});
// Call the verifyRelease pipeline
const results = await pipelinePlugins.verifyRelease(options);
// Verify the pipeline first result is the pluginConfig and options parameters (to verify the plugin was called with the defined pluginConfig and options parameters)
t.deepEqual(results, [{pluginConfig, options}, {pluginConfig: {}, options}]);
// Verify the logger has been called with the plugins path
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-result-config'));
});
test('Pipeline - Get first error', async t => {
// Call the plugin module with a verifyRelease plugin pipeline
const pipelinePlugins = t.context.plugins({
verifyRelease: ['./lib/plugin-noop', './test/fixtures/plugin-error-a', './test/fixtures/plugin-error-b'],
});
// Call the verifyRelease pipeline and verify it returns the error thrown by './test/fixtures/plugin-error-a'
await t.throws(pipelinePlugins.verifyRelease({}), 'a');
// Verify the logger has been called with the plugins path
t.true(t.context.log.calledWith(match.string, './lib/plugin-noop'));
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-error-a'));
});
test('Normalize and load plugin from string', t => {
// Call the normalize function with a path
const plugin = t.context.plugins.normalize('./lib/plugin-noop');
// Verify the plugin is loaded
t.is(typeof plugin, 'function');
// Verify the logger has been called with the plugins path
t.true(t.context.log.calledWith(match.string, './lib/plugin-noop'));
});
test('Normalize and load plugin from object', t => {
// Call the normalize function with an object (with path property)
const plugin = t.context.plugins.normalize({path: './lib/plugin-noop'});
// Verify the plugin is loaded
t.is(typeof plugin, 'function');
// Verify the logger has been called with the plugins path
t.true(t.context.log.calledWith(match.string, './lib/plugin-noop'));
});
test('Load from fallback', t => {
// Call the normalize function with a fallback
const plugin = t.context.plugins.normalize(null, '../lib/plugin-noop');
// Verify the fallback plugin is loaded
t.is(typeof plugin, 'function');
});
test('Always pass a defined "pluginConfig" for plugin defined with string', async t => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = t.context.plugins.normalize('./test/fixtures/plugin-result-config');
const pluginResult = await promisify(plugin)({});
t.deepEqual(pluginResult.pluginConfig, {});
});
test('Always pass a defined "pluginConfig" for plugin defined with path', async t => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = t.context.plugins.normalize({path: './test/fixtures/plugin-result-config'});
const pluginResult = await promisify(plugin)({});
t.deepEqual(pluginResult.pluginConfig, {path: './test/fixtures/plugin-result-config'});
});
test('Always pass a defined "pluginConfig" for fallback plugin', async t => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = t.context.plugins.normalize(null, '../test/fixtures/plugin-result-config');
const pluginResult = await promisify(plugin)({});
t.deepEqual(pluginResult.pluginConfig, {});
});
+100
View File
@@ -0,0 +1,100 @@
import test from 'ava';
import definitions from '../../lib/plugins/definitions';
test('The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition', t => {
t.false(definitions.verifyConditions.config.validator({}));
t.false(definitions.verifyConditions.config.validator({path: null}));
t.true(definitions.verifyConditions.config.validator({path: 'plugin-path.js'}));
t.true(definitions.verifyConditions.config.validator());
t.true(definitions.verifyConditions.config.validator('plugin-path.js'));
t.true(definitions.verifyConditions.config.validator(() => {}));
t.true(definitions.verifyConditions.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "getLastRelease" plugin is mandatory, and must be a single plugin definition', t => {
t.false(definitions.getLastRelease.config.validator({}));
t.false(definitions.getLastRelease.config.validator({path: null}));
t.false(definitions.getLastRelease.config.validator([]));
t.false(definitions.getLastRelease.config.validator());
t.true(definitions.getLastRelease.config.validator({path: 'plugin-path.js'}));
t.true(definitions.getLastRelease.config.validator('plugin-path.js'));
t.true(definitions.getLastRelease.config.validator(() => {}));
});
test('The "analyzeCommits" plugin is mandatory, and must be a single plugin definition', t => {
t.false(definitions.analyzeCommits.config.validator({}));
t.false(definitions.analyzeCommits.config.validator({path: null}));
t.false(definitions.analyzeCommits.config.validator([]));
t.false(definitions.analyzeCommits.config.validator());
t.true(definitions.analyzeCommits.config.validator({path: 'plugin-path.js'}));
t.true(definitions.analyzeCommits.config.validator('plugin-path.js'));
t.true(definitions.analyzeCommits.config.validator(() => {}));
});
test('The "verifyRelease" plugin, if defined, must be a single or an array of plugins definition', t => {
t.false(definitions.verifyRelease.config.validator({}));
t.false(definitions.verifyRelease.config.validator({path: null}));
t.true(definitions.verifyRelease.config.validator({path: 'plugin-path.js'}));
t.true(definitions.verifyRelease.config.validator());
t.true(definitions.verifyRelease.config.validator('plugin-path.js'));
t.true(definitions.verifyRelease.config.validator(() => {}));
t.true(definitions.verifyRelease.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "generateNotes" plugin, if defined, must be a single plugin definition', t => {
t.false(definitions.generateNotes.config.validator({}));
t.false(definitions.generateNotes.config.validator({path: null}));
t.false(definitions.generateNotes.config.validator([]));
t.true(definitions.generateNotes.config.validator());
t.true(definitions.generateNotes.config.validator({path: 'plugin-path.js'}));
t.true(definitions.generateNotes.config.validator('plugin-path.js'));
t.true(definitions.generateNotes.config.validator(() => {}));
});
test('The "publish" plugin is mandatory, and must be a single or an array of plugins definition', t => {
t.false(definitions.publish.config.validator({}));
t.false(definitions.publish.config.validator({path: null}));
t.false(definitions.publish.config.validator());
t.true(definitions.publish.config.validator({path: 'plugin-path.js'}));
t.true(definitions.publish.config.validator('plugin-path.js'));
t.true(definitions.publish.config.validator(() => {}));
t.true(definitions.publish.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "getLastRelease" plugin output if defined, must be an object with an optionnal valid semver version in the "version" property', t => {
t.false(definitions.getLastRelease.output.validator('string'));
t.false(definitions.getLastRelease.output.validator(1));
t.false(definitions.getLastRelease.output.validator({version: 'invalid'}));
t.true(definitions.getLastRelease.output.validator());
t.true(definitions.getLastRelease.output.validator({}));
t.true(definitions.getLastRelease.output.validator({version: 'v1.0.0'}));
t.true(definitions.getLastRelease.output.validator({version: '1.0.0'}));
t.true(definitions.getLastRelease.output.validator({version: null}));
});
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', t => {
t.false(definitions.analyzeCommits.output.validator('invalid'));
t.false(definitions.analyzeCommits.output.validator(1));
t.false(definitions.analyzeCommits.output.validator({}));
t.true(definitions.analyzeCommits.output.validator());
t.true(definitions.analyzeCommits.output.validator(null));
t.true(definitions.analyzeCommits.output.validator('major'));
});
test('The "generateNotes" plugin output must be a string', t => {
t.false(definitions.generateNotes.output.validator());
t.false(definitions.generateNotes.output.validator(null));
t.false(definitions.generateNotes.output.validator(1));
t.false(definitions.generateNotes.output.validator({}));
t.true(definitions.generateNotes.output.validator(''));
t.true(definitions.generateNotes.output.validator('string'));
});
+116
View File
@@ -0,0 +1,116 @@
import {callbackify} from 'util';
import test from 'ava';
import {noop} from 'lodash';
import {stub, match} from 'sinon';
import normalize from '../../lib/plugins/normalize';
test.beforeEach(t => {
// Stub the logger functions
t.context.log = stub();
t.context.logger = {log: t.context.log};
});
test('Normalize and load plugin from string', t => {
const plugin = normalize('', './test/fixtures/plugin-noop', t.context.logger);
t.is(typeof plugin, 'function');
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-noop'));
});
test('Normalize and load plugin from object', t => {
const plugin = normalize('', {path: './test/fixtures/plugin-noop'}, t.context.logger);
t.is(typeof plugin, 'function');
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-noop'));
});
test('Normalize and load plugin from function', t => {
const plugin = normalize('', () => {}, t.context.logger);
t.is(typeof plugin, 'function');
});
test('Normalize and load plugin that retuns multiple functions', t => {
const plugin = normalize('verifyConditions', './test/fixtures/multi-plugin', t.context.logger);
t.is(typeof plugin, 'function');
t.true(t.context.log.calledWith(match.string, './test/fixtures/multi-plugin'));
});
test('Wrap plugin in a function that validate the output of the plugin', async t => {
const pluginFunction = stub().resolves(1);
const plugin = normalize('', callbackify(pluginFunction), t.context.logger, {
validator: output => output === 1,
message: 'The output must be 1',
});
await t.notThrows(plugin());
pluginFunction.resolves(2);
const error = await t.throws(plugin());
t.is(error.message, 'The output must be 1. Received: 2');
});
test('Plugin is called with "pluginConfig" (omitting "path") and input', async t => {
const pluginFunction = stub().resolves();
const conf = {path: callbackify(pluginFunction), conf: 'confValue'};
const plugin = normalize('', conf, t.context.logger);
await plugin('param');
t.true(pluginFunction.calledWith({conf: 'confValue'}, 'param'));
});
test('Prevent plugins to modify "pluginConfig"', async t => {
const pluginFunction = stub().callsFake((pluginConfig, options, cb) => {
pluginConfig.conf.subConf = 'otherConf';
cb();
});
const conf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
const plugin = normalize('', conf, t.context.logger);
await plugin();
t.is(conf.conf.subConf, 'originalConf');
});
test('Prevent plugins to modify its input', async t => {
const pluginFunction = stub().callsFake((pluginConfig, options, cb) => {
options.param.subParam = 'otherParam';
cb();
});
const input = {param: {subParam: 'originalSubParam'}};
const plugin = normalize('', pluginFunction, t.context.logger);
await plugin(input);
t.is(input.param.subParam, 'originalSubParam');
});
test('Return noop if the plugin is not defined', async t => {
const plugin = normalize();
t.is(plugin, noop);
});
test('Always pass a defined "pluginConfig" for plugin defined with string', async t => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = normalize('', './test/fixtures/plugin-result-config', t.context.logger);
const pluginResult = await plugin();
t.deepEqual(pluginResult.pluginConfig, {});
});
test('Always pass a defined "pluginConfig" for plugin defined with path', async t => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = normalize('', {path: './test/fixtures/plugin-result-config'}, t.context.logger);
const pluginResult = await plugin();
t.deepEqual(pluginResult.pluginConfig, {});
});
test('Throws an error if the plugin return an object without the expected plugin function', async t => {
const error = t.throws(() => normalize('inexistantPlugin', './test/fixtures/multi-plugin', t.context.logger));
t.is(
error.message,
'The inexistantPlugin plugin must be a function, or an object with a function in the property inexistantPlugin.'
);
});
+40
View File
@@ -0,0 +1,40 @@
import test from 'ava';
import {stub} from 'sinon';
import pipeline from '../../lib/plugins/pipeline';
test('Execute each function in series passing the same input', async t => {
const step1 = stub().resolves(1);
const step2 = stub().resolves(2);
const step3 = stub().resolves(3);
const result = await pipeline([step1, step2, step3])(0);
t.deepEqual(result, [1, 2, 3]);
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
t.true(step3.calledWith(0));
});
test('Execute each function in series passing a transformed input', async t => {
const step1 = stub().resolves(1);
const step2 = stub().resolves(2);
const step3 = stub().resolves(3);
const result = await pipeline([step1, step2, step3])(0, (prevResult, result) => prevResult + result);
t.deepEqual(result, [1, 2, 3]);
t.true(step1.calledWith(0));
t.true(step2.calledWith(1));
t.true(step3.calledWith(3));
});
test('Stop execution and throw error is a step rejects', async t => {
const step1 = stub().resolves(1);
const step2 = stub().throws(new Error('test error'));
const step3 = stub().resolves(3);
const error = await t.throws(pipeline([step1, step2, step3])(0));
t.is(error.message, 'test error');
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
t.true(step3.notCalled);
});
+49
View File
@@ -0,0 +1,49 @@
import test from 'ava';
import {stub} from 'sinon';
import getPlugins from '../../lib/plugins';
test.beforeEach(t => {
// Stub the logger functions
t.context.log = stub();
t.context.logger = {log: t.context.log};
});
test('Export default plugins', t => {
// Call the plugin module
const plugins = getPlugins({}, t.context.logger);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.getLastRelease, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.publish, 'function');
});
test('Export plugins based on config', t => {
// Call the plugin module
const plugins = getPlugins(
{
verifyConditions: ['./test/fixtures/plugin-noop', {path: './test/fixtures/plugin-noop'}],
getLastRelease: './test/fixtures/plugin-noop',
analyzeCommits: {path: './test/fixtures/plugin-noop'},
verifyRelease: () => {},
},
t.context.logger
);
// Verify the module returns a function for each plugin
t.is(typeof plugins.verifyConditions, 'function');
t.is(typeof plugins.getLastRelease, 'function');
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.publish, 'function');
});
test('Throw an error if plugin configuration is invalid', t => {
const error = t.throws(() => getPlugins({verifyConditions: {}}, t.context.logger));
t.is(
error.message,
'The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition. A plugin definition is either a string or an object with a path property.'
);
});
-45
View File
@@ -1,45 +0,0 @@
import test from 'ava';
import SemanticReleaseError from '@semantic-release/error';
import verify from '../lib/verify-auth';
test('Verify npm and github auth', t => {
// Call the verify module with options and env
t.notThrows(() => verify({githubToken: 'sup'}, {NPM_TOKEN: 'yo'}));
});
test('Verify npm (old token and mail) and github auth', t => {
// Call the verify module with options and env
t.notThrows(() => verify({githubToken: 'sup'}, {NPM_OLD_TOKEN: 'yo', NPM_EMAIL: 'test@email.com'}));
});
test('Return error for missing github token', t => {
// Call the verify module with options and env
const error = t.throws(() => verify({}, {NPM_TOKEN: 'yo'}));
// Verify error code and type
t.is(error.code, 'ENOGHTOKEN');
t.true(error instanceof SemanticReleaseError);
});
test('Return error for missing npm token', t => {
// Call the verify module with options and env
const error = t.throws(() => verify({githubToken: 'sup'}, {}));
// Verify error code and type
t.is(error.code, 'ENONPMTOKEN');
t.true(error instanceof SemanticReleaseError);
});
test('Return error for missing old npm token', t => {
// Call the verify module with options and env
const error = t.throws(() => verify({githubToken: 'sup'}, {NPM_EMAIL: 'test@email.com'}));
// Verify error code and type
t.is(error.code, 'ENONPMTOKEN');
t.true(error instanceof SemanticReleaseError);
});
test('Return error for missing npm email', t => {
// Call the verify module with options and env
const error = t.throws(() => verify({githubToken: 'sup'}, {NPM_OLD_TOKEN: 'yo'}));
// Verify error code and type
t.is(error.code, 'ENONPMTOKEN');
t.true(error instanceof SemanticReleaseError);
});
-32
View File
@@ -1,32 +0,0 @@
import test from 'ava';
import SemanticReleaseError from '@semantic-release/error';
import verify from '../lib/verify-pkg';
test('Verify name and repository', t => {
// Call the verify module with package
t.notThrows(() => verify({name: 'package', repository: {url: 'http://github.com/whats/up.git'}}));
});
test('Return error for missing package name', t => {
// Call the verify module with package
const error = t.throws(() => verify({repository: {url: 'http://github.com/whats/up.git'}}));
// Verify error code and type
t.is(error.code, 'ENOPKGNAME');
t.true(error instanceof SemanticReleaseError);
});
test('Return error for missing repository', t => {
// Call the verify module with package
const error = t.throws(() => verify({name: 'package'}));
// Verify error code and type
t.is(error.code, 'ENOPKGREPO');
t.true(error instanceof SemanticReleaseError);
});
test('Return error for missing repository url', t => {
// Call the verify module with package
const error = t.throws(() => verify({name: 'package', repository: {}}));
// Verify error code and type
t.is(error.code, 'ENOPKGREPO');
t.true(error instanceof SemanticReleaseError);
});