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:
Vendored
+21
@@ -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();
|
||||
},
|
||||
};
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
module.exports = function(config, options, cb) {
|
||||
cb(new Error('b'));
|
||||
};
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = (config, options, cb) => {
|
||||
cb(null);
|
||||
};
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
module.exports = function(config, options, cb) {
|
||||
cb(null, 'a');
|
||||
};
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
module.exports = function(config, options, cb) {
|
||||
cb(null, 'b');
|
||||
};
|
||||
+39
-22
@@ -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
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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'));
|
||||
});
|
||||
@@ -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'));
|
||||
});
|
||||
@@ -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,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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 **/
|
||||
|
||||
@@ -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, {});
|
||||
});
|
||||
@@ -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'));
|
||||
});
|
||||
@@ -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.'
|
||||
);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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.'
|
||||
);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user