Compare commits

...
12 Commits
23 changed files with 468 additions and 157 deletions
+1
View File
@@ -11,6 +11,7 @@ module.exports = async () => {
.description('Run automated package publishing')
.option('-b, --branch <branch>', 'Branch to release from')
.option('-r, --repository-url <repositoryUrl>', 'Git repository URL')
.option('-t, --tag-format <tagFormat>', `Git tag format`)
.option('-e, --extends <paths>', 'Comma separated list of shareable config paths or packages name', list)
.option(
'--verify-conditions <paths>',
+1 -9
View File
@@ -47,12 +47,7 @@ publish:
image: node:8
stage: release
script:
# Only for a local semantic-release installation
- npm run semantic-release
# Only for a global semantic-release installation
- npm install -g semantic-release
- semantic-release
- npx semantic-release
```
### `package.json` configuration
@@ -63,9 +58,6 @@ A `package.json` is required only for [local](../usage/installation.md#local-ins
{
"devDependencies": {
"semantic-release": "^12.0.0"
},
"scripts": {
"semantic-release": "semantic-release"
}
}
```
+3 -22
View File
@@ -20,8 +20,6 @@ It's recommended to run the `semantic-release` command in the [Travis `deploy` s
**Note**: It's not recommended to run the `semantic-release` command in the Travis `script` step as each script in this step will be executed regardless of the outcome of the previous one. See [travis-ci/travis-ci#1066](https://github.com/travis-ci/travis-ci/issues/1066).
**Note**: The`semantic-release` execution command varies depending if you are using a [local](../usage/installation.md#local-installation) or [global](../usage/installation.md#global-installation) semantic-release installation.
```yaml
language: node_js
@@ -35,10 +33,7 @@ deploy:
provider: script
skip_cleanup: true
script:
# Only for a local semantic-release installation
- npm run semantic-release
# Only for a global semantic-release installation
- npm install -g semantic-release && semantic-release
- npx semantic-release
```
### `package.json` configuration for single Node job
@@ -49,9 +44,6 @@ A `package.json` is required only for [local](../usage/installation.md#local-ins
{
"devDependencies": {
"semantic-release": "^11.0.0"
},
"scripts": {
"semantic-release": "semantic-release"
}
}
```
@@ -72,8 +64,6 @@ It's recommended to run the `semantic-release` command in the [Travis `deploy` s
**Note**: It's not recommended to run the `semantic-release` command in the Travis `script` step as each script in this step will be executed regardless of the outcome of the previous one. See [travis-ci/travis-ci#1066](https://github.com/travis-ci/travis-ci/issues/1066).
**Note**: The`semantic-release` execution command varies depending if you are using a [local](../usage/installation.md#local-installation) or [global](../usage/installation.md#global-installation) **semantic-release** installation.
```yaml
language: node_js
@@ -94,10 +84,7 @@ deploy:
provider: script
skip_cleanup: true
script:
# Only for a local semantic-release installation
- npm run travis-deploy-once "npm run semantic-release"
# Only for a global semantic-release installation
- npm install -g travis-deploy-once semantic-release && travis-deploy-once "semantic-release"
- npx travis-deploy-once "npx semantic-release"
```
**Note**: See the `travis-deploy-once` [`pro`](https://github.com/semantic-release/travis-deploy-once#-p---pro) and [`travis-url`](https://github.com/semantic-release/travis-deploy-once#-u---travis-url) options for using with [Travis Pro](https://docs.travis-ci.com/user/travis-pro) and [Travis Enterprise](https://enterprise.travis-ci.com).
@@ -111,10 +98,6 @@ A `package.json` is required only for [local](../usage/installation.md#local-ins
"devDependencies": {
"semantic-release": "^12.0.0",
"travis-deploy-once": "^4.0.0"
},
"scripts": {
"semantic-release": "semantic-release",
"travis-deploy-once": "travis-deploy-once"
}
}
```
@@ -156,10 +139,8 @@ deploy:
script:
# Use nvm to install and use the Node LTS version (nvm is installed on all Travis images)
- nvm install lts/*
# Install travis-deploy-once and semantic-release
- npm install -g travis-deploy-once semantic-release
# Run semantic-release only on job, after all other are successful
- travis-deploy-once "semantic-release"
- npx travis-deploy-once "npx semantic-release"
```
**Note**: See the `travis-deploy-once` [`pro`](https://github.com/semantic-release/travis-deploy-once#-p---pro) and [`travis-url`](https://github.com/semantic-release/travis-deploy-once#-u---travis-url) options for using with [Travis Pro](https://docs.travis-ci.com/user/travis-pro) and [Travis Enterprise](https://enterprise.travis-ci.com).
+16 -2
View File
@@ -35,11 +35,19 @@ Alternatively you can use a [global](../usage/installation.md#global-installatio
If your CI environment provides [nvm](https://github.com/creationix/nvm) you can switch to Node 8 before installing and running the `semantic-release` command:
```bash
$ nvm install 8 && yarn install -g semantic-release && semantic-release
$ nvm install 8 && yarn global add semantic-release && semantic-release
```
See the [CI configuration recipes](../recipes/README.md#ci-configurations) for more details on specific CI environments.
As `semantic-release` is recommended to be executed with [`npx`](https://www.npmjs.com/package/npx) an alternative is required for usage with Yarn. Even though it is possible to install npx with Yarn, it's not recommended. Yarn and npx would be using different cache locations.
For [local installation](../usage/installation.md#local-installation) replace
`npx semantic-release` with `yarn run semantic-release`.
For [global installation](../usage/installation.md#global-installation) replace
`npx semantic-release` with `yarn global add semantic-release && semantic-release`.
## Can I use semantic-release to publish non-JavaScript packages?
Yes, **semantic-release** is a Node CLI application but it can be used to publish any type of packages.
@@ -64,7 +72,7 @@ See the [CI configuration recipes](../usage/ci-configuration.md) for more detail
Yes, you can by explicitly setting the [`--no-ci` CLI option](../usage/configuration.md#options) option. You will also have to set the required [authentication](../usage/ci-configuration.md#authentication) via environment variables on your local machine, for example:
```bash
$ NPM_TOKEN=<your_npm_token> GH_TOKEN=<your_github_token> npm run semantic-release --no-ci
$ NPM_TOKEN=<your_npm_token> GH_TOKEN=<your_github_token> npx semantic-release --no-ci
```
However this is not the recommended approach, as running unit and integration tests on an independent machine before publishing software is a crucial part of the release workflow.
@@ -156,3 +164,9 @@ In addition the [verify conditions step](../../README.md#release-steps) verifies
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which **requires Node version 8.3 or higher**.
See [Node version requirement](../support/node-version.md#node-version-requirement) for more details and solutions.
# What is npx?
[`npx`](https://www.npmjs.com/package/npx) short for "npm exec" is a CLI to find and execute npm binaries within the local `node_modules` folder or in the $PATH. If a binary can't be located npx will download the required package and execute it from its cache location.
The tool is bundled with [npm](https://www.npmjs.com/package/npm) >= 5.2, or can be installed via `npm install -g npx`.
For more details and motivation read the [introductory blog post](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) by [@zkat](https://github.com/zkat).
+4 -21
View File
@@ -18,35 +18,18 @@ See [CI configuration](../usage/ci-configuration.md) and [CI configuration recip
### Use `npx`
[`npx`](https://github.com/zkat/npx) is a CLI to execute npm binaries. It is bundled with [npm](https://www.npmjs.com/package/npm) >= 5.4, or can be installed via `npm install -g npx`.
`npx` can be used to download the latest [Node 8 package published on npm](https://www.npmjs.com/package/node) and use it to execute the `semantic-release` command.
If you are using a [local](../usage/installation.md#local-installation) **semantic-release** installation:
`npx` is included with npm >= 5.2 and can be used to download the latest [Node 8 package published on npm](https://www.npmjs.com/package/node). Use it to execute the `semantic-release` command.
```bash
$ npm install -g npx && npx -p node@8 -c "npm run semantic-release"
$ npx -p node@8 -c "npx semantic-release"
```
If you are using a [global](../usage/installation.md#global-installation) **semantic-release** installation:
```bash
# For global semantic-release install
$ npm install -g semantic-release npx && npx -p node@8 -c "semantic-release"
```
**Note:**: See [What is npx](./FAQ.md#what-is-npx) for more details.
### Use `nvm`
If your CI environment provides [nvm](https://github.com/creationix/nvm) you can use it to switch to Node 8 before running the `semantic-release` command.
If you are using a [local](../usage/installation.md#local-installation) **semantic-release** installation:
```bash
$ nvm install 8 && npm run semantic-release
```
If you are using a [global](../usage/installation.md#global-installation) **semantic-release** installation:
```bash
$ nvm install 8 && npm install -g semantic-release && semantic-release
$ nvm install 8 && npx semantic-release
```
+1 -1
View File
@@ -44,6 +44,6 @@ cd your-module
semantic-release-cli setup
```
![dialogue](media/semantic-release-cli.png)
![dialogue](../../media/semantic-release-cli.png)
See the [semantic-release-cli](https://github.com/semantic-release/cli#what-it-does) documentation for more details.
+12
View File
@@ -65,6 +65,18 @@ Any valid git url format is supported (See [Git protocols](https://git-scm.com/b
**Note**: If the [Github plugin](https://github.com/semantic-release/github) is used the URL must be a valid Github URL that include the `owner`, the `repository` name and the `host`. **The Github shorthand URL is not supported.**
### tagFormat
Type: `String`
Default: `v${version}`
CLI arguments: `-t`, `--tag-format`
The [Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) format used by **semantic-release** to identify releases. The tag name is generated with [Lodash template](https://lodash.com/docs#template) and will be compiled with the `version` variable.
**Note**: The `tagFormat` must contain the `version` variable and compile to a [valid Git reference](https://git-scm.com/docs/git-check-ref-format#_description).
### dryRun
Type: `Boolean`
+10 -14
View File
@@ -2,32 +2,28 @@
## Local installation
For [Node modules projects](https://docs.npmjs.com/getting-started/creating-node-modules) we recommend to install **semantic-release** locally and to run the `semantic-release` command with a [npm script](https://docs.npmjs.com/misc/scripts):
For [Node modules projects](https://docs.npmjs.com/getting-started/creating-node-modules) we recommend to install **semantic-release** locally and to run the `semantic-release` command with [npx](https://www.npmjs.com/package/npx):
```bash
$ npm install --save-dev semantic-release
```
In your `package.json`:
```json
"scripts": {
"semantic-release": "semantic-release"
}
```
Then in the CI environment:
```bash
$ npm run semantic-release
$ npx semantic-release
```
**Note:**: `npx` is a tool bundled with `npm@>=5.2.0`. It is used to conveniently find the semantic-release binary and to execute it. See [What is npx](../support/FAQ.md#what-is-npx) for more details.
## Global installation
For other type of projects we recommend to install **semantic-release** globally directly in the CI environment:
For other type of projects we recommend to install **semantic-release** directly in the CI environment, also with [npx](https://www.npmjs.com/package/npx):
```bash
$ npm install -g semantic-release
$ semantic-release
$ npx semantic-release
```
**Note:**: For a global installation, it's recommended to specify the major **semantic-release** version to install (for example with with `npm install -g semantic-release@12`). This way your build will not automatically use the next major **semantic-release** release that could possibly break your build. You will have to upgrade manually when a new major version is released.
**Note:**: For a global installation, it's recommended to specify the major **semantic-release** version to install (for example with with `npx semantic-release@12`, or `npm install -g semantic-release@12`). This way your build will not automatically use the next major **semantic-release** release that could possibly break your build. You will have to upgrade manually when a new major version is released.
**Note:**: `npx` is a tool bundled with `npm@>=5.2.0`. It is used to conveniently install the semantic-release binary and to execute it. See [What is npx](../support/FAQ.md#what-is-npx) for more details.
+3 -2
View File
@@ -1,3 +1,4 @@
const {template} = require('lodash');
const marked = require('marked');
const TerminalRenderer = require('marked-terminal');
const envCi = require('env-ci');
@@ -38,7 +39,7 @@ async function run(opts) {
// Unshallow the repo in order to get all the tags
await unshallow();
const lastRelease = await getLastRelease(logger);
const lastRelease = await getLastRelease(options.tagFormat, logger);
const commits = await getCommits(lastRelease.gitHead, options.branch, logger);
logger.log('Call plugin %s', 'analyze-commits');
@@ -53,7 +54,7 @@ async function run(opts) {
return;
}
const version = getNextVersion(type, lastRelease, logger);
const nextRelease = {type, version, gitHead: await getGitHead(), gitTag: `v${version}`};
const nextRelease = {type, version, gitHead: await getGitHead(), gitTag: template(options.tagFormat)({version})};
logger.log('Call plugin %s', 'verify-release');
await plugins.verifyRelease({options, logger, lastRelease, commits, nextRelease}, true);
+6 -18
View File
@@ -1,8 +1,7 @@
const readPkgUp = require('read-pkg-up');
const {castArray, pickBy, isUndefined, isNull, isString, isPlainObject} = require('lodash');
const readPkgUp = require('read-pkg-up');
const cosmiconfig = require('cosmiconfig');
const resolveFrom = require('resolve-from');
const SemanticReleaseError = require('@semantic-release/error');
const debug = require('debug')('semantic-release:config');
const {repoUrl} = require('./git');
const PLUGINS_DEFINITION = require('./plugins/definitions');
@@ -43,29 +42,18 @@ module.exports = async (opts, logger) => {
};
}
const repositoryUrl = (await pkgRepoUrl()) || (await repoUrl());
// Set default options values if not defined yet
options = {
branch: 'master',
repositoryUrl: (await pkgRepoUrl()) || (await repoUrl()),
repositoryUrl: repositoryUrl ? getGitAuthUrl(repositoryUrl) : repositoryUrl,
tagFormat: `v\${version}`,
// Remove `null` and `undefined` options so they can be replaced with default ones
...pickBy(options, option => !isUndefined(option) && !isNull(option)),
};
debug('options values: %O', Object.keys(options));
debug('name: %O', options.name);
debug('branch: %O', options.branch);
debug('repositoryUrl: %O', options.repositoryUrl);
debug('analyzeCommits: %O', options.analyzeCommits);
debug('generateNotes: %O', options.generateNotes);
debug('verifyConditions: %O', options.verifyConditions);
debug('verifyRelease: %O', options.verifyRelease);
debug('publish: %O', options.publish);
if (!options.repositoryUrl) {
throw new SemanticReleaseError('The repositoryUrl option is required', 'ENOREPOURL');
}
options.repositoryUrl = getGitAuthUrl(options.repositoryUrl);
debug('options values: %O', options);
return {options, plugins: await plugins(options, pluginsPath, logger)};
};
+5 -4
View File
@@ -19,11 +19,12 @@ module.exports = repositoryUrl => {
? `gitlab-ci-token:${process.env[envVar]}`
: process.env[envVar];
if (!gitCredentials) {
return repositoryUrl;
}
const {protocols} = gitUrlParse(repositoryUrl);
const protocol = protocols.includes('https') ? 'https' : protocols.includes('http') ? 'http' : 'https';
if (!gitCredentials) {
return protocols.includes('https') ? `${gitUrlParse(repositoryUrl).toString(protocol)}.git` : repositoryUrl;
}
return format({...parse(`${gitUrlParse(repositoryUrl).toString(protocol)}.git`), ...{auth: gitCredentials}});
};
+24 -8
View File
@@ -1,3 +1,4 @@
const {escapeRegExp, template} = require('lodash');
const semver = require('semver');
const pLocate = require('p-locate');
const debug = require('debug')('semantic-release:get-last-release');
@@ -15,21 +16,36 @@ const {gitTags, isRefInHistory, gitTagHead} = require('./git');
* Determine the Git tag and version of the last tagged release.
*
* - Obtain all the tags referencing commits in the current branch history
* - Filter out the ones that are not valid semantic version
* - Sort the tags
* - Retrive the highest tag
* - Filter out the ones that are not valid semantic version or doesn't match the `tagFormat`
* - Sort the versions
* - Retrive the highest version
*
* @param {Object} logger Global logger.
* @return {Promise<LastRelease>} The last tagged release or `undefined` if none is found.
*/
module.exports = async logger => {
const tags = (await gitTags()).filter(tag => semver.valid(semver.clean(tag))).sort(semver.rcompare);
module.exports = async (tagFormat, logger) => {
// Generate a regex to parse tags formatted with `tagFormat`
// by replacing the `version` variable in the template by `(.+)`.
// The `tagFormat` is compiled with space as the `version` as it's an invalid tag character,
// so it's guaranteed to no be present in the `tagFormat`.
const tagRegexp = escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.+)');
const tags = (await gitTags())
.map(tag => {
return {gitTag: tag, version: (tag.match(tagRegexp) || new Array(2))[1]};
})
.filter(tag => tag.version && semver.valid(semver.clean(tag.version)))
.sort((a, b) => semver.rcompare(a.version, b.version));
debug('found tags: %o', tags);
if (tags.length > 0) {
const gitTag = await pLocate(tags, tag => isRefInHistory(tag), {concurrency: 1, preserveOrder: true});
logger.log('Found git tag version %s', gitTag);
return {gitTag, gitHead: await gitTagHead(gitTag), version: semver.valid(semver.clean(gitTag))};
const {gitTag, version} = await pLocate(tags, tag => isRefInHistory(tag.gitTag), {
concurrency: 1,
preserveOrder: true,
});
logger.log('Found git tag %s associated with version %s', gitTag, version);
return {gitHead: await gitTagHead(gitTag), gitTag, version};
}
logger.log('No git tag version found');
+45 -9
View File
@@ -9,7 +9,11 @@ const debug = require('debug')('semantic-release:get-version-head');
* @return {string} The commit sha of the tag in parameter or `null`.
*/
async function gitTagHead(tagName) {
return execa.stdout('git', ['rev-list', '-1', tagName], {reject: false});
try {
return await execa.stdout('git', ['rev-list', '-1', tagName]);
} catch (err) {
debug(err);
}
}
/**
@@ -28,10 +32,14 @@ async function gitTags() {
*
* @param {string} ref The reference to look for.
*
* @return {boolean} `true` if the reference is in the history of the current branch, `false` otherwise.
* @return {boolean} `true` if the reference is in the history of the current branch, falsy otherwise.
*/
async function isRefInHistory(ref) {
return (await execa('git', ['merge-base', '--is-ancestor', ref, 'HEAD'], {reject: false})).code === 0;
try {
return (await execa('git', ['merge-base', '--is-ancestor', ref, 'HEAD'])).code === 0;
} catch (err) {
debug(err);
}
}
/**
@@ -52,14 +60,22 @@ async function gitHead() {
* @return {string} The value of the remote git URL.
*/
async function repoUrl() {
return execa.stdout('git', ['remote', 'get-url', 'origin'], {reject: false});
try {
return await execa.stdout('git', ['remote', 'get-url', 'origin']);
} catch (err) {
debug(err);
}
}
/**
* @return {Boolean} `true` if the current working directory is in a git repository, `false` otherwise.
* @return {Boolean} `true` if the current working directory is in a git repository, falsy otherwise.
*/
async function isGitRepo() {
return (await execa('git', ['rev-parse', '--git-dir'], {reject: false})).code === 0;
try {
return (await execa('git', ['rev-parse', '--git-dir'])).code === 0;
} catch (err) {
debug(err);
}
}
/**
@@ -68,10 +84,14 @@ async function isGitRepo() {
* @param {String} origin The remote repository URL.
* @param {String} branch The repositoru branch for which to verify write access.
*
* @return {Boolean} `true` is authorized to push, `false` otherwise.
* @return {Boolean} `true` is authorized to push, falsy otherwise.
*/
async function verifyAuth(origin, branch) {
return (await execa('git', ['push', '--dry-run', origin, `HEAD:${branch}`], {reject: false})).code === 0;
try {
return (await execa('git', ['push', '--dry-run', origin, `HEAD:${branch}`])).code === 0;
} catch (err) {
debug(err);
}
}
/**
@@ -108,10 +128,25 @@ async function deleteTag(origin, tagName) {
debug('delete local tag', shell);
// Delete the tag remotely
shell = await execa('git', ['push', '-d', origin, tagName], {reject: false});
shell = await execa('git', ['push', '--delete', origin, tagName], {reject: false});
debug('delete remote tag', shell);
}
/**
* Verify a tag name is a valid Git reference.
*
* @method verifyTagName
* @param {string} tagName the tag name to verify.
* @return {boolean} `true` if valid, falsy otherwise.
*/
async function verifyTagName(tagName) {
try {
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`])).code === 0;
} catch (err) {
debug(err);
}
}
module.exports = {
gitTagHead,
gitTags,
@@ -124,4 +159,5 @@ module.exports = {
tag,
push,
deleteTag,
verifyTagName,
};
+11 -3
View File
@@ -1,11 +1,13 @@
const {isArray, isObject, omit} = require('lodash');
const AggregateError = require('aggregate-error');
const SemanticReleaseError = require('@semantic-release/error');
const PLUGINS_DEFINITION = require('./definitions');
const pipeline = require('./pipeline');
const normalize = require('./normalize');
module.exports = (options, pluginsPath, logger) =>
Object.keys(PLUGINS_DEFINITION).reduce((plugins, pluginType) => {
module.exports = (options, pluginsPath, logger) => {
const errors = [];
const plugins = Object.keys(PLUGINS_DEFINITION).reduce((plugins, pluginType) => {
const {config, output, default: def} = PLUGINS_DEFINITION[pluginType];
let pluginConfs;
if (options[pluginType]) {
@@ -14,7 +16,8 @@ module.exports = (options, pluginsPath, logger) =>
options[pluginType].path = def;
}
if (config && !config.validator(options[pluginType])) {
throw new SemanticReleaseError(config.message, 'EPLUGINCONF');
errors.push(new SemanticReleaseError(config.message, 'EPLUGINCONF'));
return plugins;
}
pluginConfs = options[pluginType];
} else {
@@ -29,3 +32,8 @@ module.exports = (options, pluginsPath, logger) =>
return plugins;
}, {});
if (errors.length > 0) {
throw new AggregateError(errors);
}
return plugins;
};
+36 -5
View File
@@ -1,19 +1,50 @@
const {template} = require('lodash');
const SemanticReleaseError = require('@semantic-release/error');
const {isGitRepo, verifyAuth} = require('./git');
const AggregateError = require('aggregate-error');
const {isGitRepo, verifyAuth, verifyTagName} = require('./git');
module.exports = async (options, branch, logger) => {
const errors = [];
if (!await isGitRepo()) {
logger.error('Semantic-release must run from a git repository.');
return false;
}
if (!await verifyAuth(options.repositoryUrl, options.branch)) {
throw new SemanticReleaseError(
`The git credentials doesn't allow to push on the branch ${options.branch}.`,
'EGITNOPERMISSION'
if (!options.repositoryUrl) {
errors.push(new SemanticReleaseError('The repositoryUrl option is required', 'ENOREPOURL'));
} else if (!await verifyAuth(options.repositoryUrl, options.branch)) {
errors.push(
new SemanticReleaseError(
`The git credentials doesn't allow to push on the branch ${options.branch}.`,
'EGITNOPERMISSION'
)
);
}
// Verify that compiling the `tagFormat` produce a valid Git tag
if (!await verifyTagName(template(options.tagFormat)({version: '0.0.0'}))) {
errors.push(
new SemanticReleaseError('The tagFormat template must compile to a valid Git tag format', 'EINVALIDTAGFORMAT')
);
}
// Verify the `tagFormat` contains the variable `version` by compiling the `tagFormat` template
// with a space as the `version` value and verify the result contains the space.
// The space is used as it's an invalid tag character, so it's guaranteed to no be present in the `tagFormat`.
if ((template(options.tagFormat)({version: ' '}).match(/ /g) || []).length !== 1) {
errors.push(
new SemanticReleaseError(
`The tagFormat template must contain the variable "\${version}" exactly once`,
'ETAGNOVERSION'
)
);
}
if (errors.length > 0) {
throw new AggregateError(errors);
}
if (branch !== options.branch) {
logger.log(
`This test run was triggered on the branch ${branch}, while semantic-release is configured to only publish from ${
+2 -2
View File
@@ -21,7 +21,7 @@
"dependencies": {
"@semantic-release/commit-analyzer": "^5.0.0",
"@semantic-release/error": "^2.1.0",
"@semantic-release/github": "^4.0.0",
"@semantic-release/github": "^4.0.2",
"@semantic-release/npm": "^3.0.0",
"@semantic-release/release-notes-generator": "^6.0.0",
"aggregate-error": "^1.0.0",
@@ -33,7 +33,7 @@
"execa": "^0.9.0",
"get-stream": "^3.0.0",
"git-log-parser": "^1.2.0",
"git-url-parse": "^8.0.0",
"git-url-parse": "^8.1.0",
"hook-std": "^0.4.0",
"lodash": "^4.17.4",
"marked": "^0.3.9",
+31 -15
View File
@@ -13,12 +13,12 @@ const envBackup = Object.assign({}, process.env);
const cwd = process.cwd();
test.beforeEach(t => {
// Delete environment variables that could have been set on the machine running the tests
delete process.env.GIT_CREDENTIALS;
delete process.env.GH_TOKEN;
delete process.env.GITHUB_TOKEN;
delete process.env.GL_TOKEN;
delete process.env.GITLAB_TOKEN;
// Delete environment variables that could have been set on the machine running the tests
t.context.plugins = stub().returns({});
t.context.getConfig = proxyquire('../lib/get-config', {'./plugins': t.context.plugins});
});
@@ -45,6 +45,7 @@ test.serial('Default values, reading repositoryUrl from package.json', async t =
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, 'git@package.com:owner/module.git');
t.is(options.tagFormat, `v\${version}`);
});
test.serial('Default values, reading repositoryUrl from repo if not set in package.json', async t => {
@@ -58,10 +59,11 @@ test.serial('Default values, reading repositoryUrl from repo if not set in packa
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, 'git@repo.com:owner/module.git');
t.is(options.tagFormat, `v\${version}`);
});
test.serial('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async t => {
const pkg = {repository: 'git+https://hostname.com/owner/module.git'};
const pkg = {repository: 'https://hostname.com/owner/module.git'};
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Create package.json in repository root
@@ -72,6 +74,7 @@ test.serial('Default values, reading repositoryUrl (http url) from package.json
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, pkg.repository);
t.is(options.tagFormat, `v\${version}`);
});
test.serial('Read options from package.json', async t => {
@@ -79,7 +82,8 @@ test.serial('Read options from package.json', async t => {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
generateNotes: 'generateNotes',
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -99,7 +103,8 @@ test.serial('Read options from .releaserc.yml', async t => {
const release = {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -119,7 +124,8 @@ test.serial('Read options from .releaserc.json', async t => {
const release = {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -139,7 +145,8 @@ test.serial('Read options from .releaserc.js', async t => {
const release = {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -159,7 +166,8 @@ test.serial('Read options from release.config.js', async t => {
const release = {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -184,6 +192,7 @@ test.serial('Prioritise CLI/API parameters over file configuration and git repo'
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_cli'},
branch: 'branch_cli',
repositoryUrl: 'http://cli-url.com/owner/package',
tagFormat: `cli\${version}`,
};
const pkg = {release, repository: 'git@hostname.com:owner/module.git'};
// Create a git repository, set the current working directory at the root of the repo
@@ -208,7 +217,8 @@ test.serial('Read configuration from file path in "extends"', async t => {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
generateNotes: 'generateNotes',
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -235,7 +245,8 @@ test.serial('Read configuration from module path in "extends"', async t => {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
generateNotes: 'generateNotes',
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -262,7 +273,7 @@ test.serial('Read configuration from an array of paths in "extends"', async t =>
verifyRelease: 'verifyRelease1',
analyzeCommits: {path: 'analyzeCommits1', param: 'analyzeCommits_param1'},
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
};
const shareable2 = {
@@ -270,6 +281,7 @@ test.serial('Read configuration from an array of paths in "extends"', async t =>
generateNotes: 'generateNotes2',
analyzeCommits: {path: 'analyzeCommits2', param: 'analyzeCommits_param2'},
branch: 'test_branch',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -306,7 +318,8 @@ test.serial('Prioritize configuration from config file over "extends"', async t
generateNotes: 'generateNotesShareable',
publish: [{path: 'publishShareable', param: 'publishShareable_param'}],
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -333,7 +346,7 @@ test.serial('Prioritize configuration from cli/API options over "extends"', asyn
extends: './shareable2.json',
branch: 'branch_opts',
publish: [{path: 'publishOpts', param: 'publishOpts_param'}],
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
};
const release = {
extends: './shareable1.json',
@@ -346,12 +359,13 @@ test.serial('Prioritize configuration from cli/API options over "extends"', asyn
generateNotes: 'generateNotesShareable1',
publish: [{path: 'publishShareable', param: 'publishShareable_param1'}],
branch: 'test_branch1',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
};
const shareable2 = {
analyzeCommits: 'analyzeCommits2',
publish: [{path: 'publishShareable', param: 'publishShareable_param2'}],
branch: 'test_branch2',
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -374,11 +388,12 @@ test.serial('Allow to unset properties defined in shareable config with "null"',
extends: './shareable.json',
analyzeCommits: null,
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
};
const shareable = {
generateNotes: 'generateNotes',
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
@@ -407,11 +422,12 @@ test.serial('Allow to unset properties defined in shareable config with "undefin
extends: './shareable.json',
analyzeCommits: undefined,
branch: 'test_branch',
repositoryUrl: 'git+https://hostname.com/owner/module.git',
repositoryUrl: 'https://hostname.com/owner/module.git',
};
const shareable = {
generateNotes: 'generateNotes',
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
tagFormat: `v\${version}`,
};
// Create a git repository, set the current working directory at the root of the repo
+36 -1
View File
@@ -14,10 +14,29 @@ test.afterEach.always(() => {
process.env = envBackup;
});
test.serial('Return the same "repositoryUrl" is no "gitCredentials" is defined', t => {
test.serial('Return the same "git" formatted URL if "gitCredentials" is not defined', t => {
t.is(getAuthUrl('git@host.com:owner/repo.git'), 'git@host.com:owner/repo.git');
});
test.serial('Return the same "https" formatted URL if "gitCredentials" is not defined', t => {
t.is(getAuthUrl('https://host.com/owner/repo.git'), 'https://host.com/owner/repo.git');
});
test.serial(
'Return the "https" formatted URL if "gitCredentials" is not defined and repositoryUrl is a "git+https" URL',
t => {
t.is(getAuthUrl('git+https://host.com/owner/repo.git'), 'https://host.com/owner/repo.git');
}
);
test.serial('Handle "https" URL with group and subgroup', t => {
t.is(getAuthUrl('https://host.com/group/subgroup/owner/repo.git'), 'https://host.com/group/subgroup/owner/repo.git');
});
test.serial('Handle "git" URL with group and subgroup', t => {
t.is(getAuthUrl('git@host.com:group/subgroup/owner/repo.git'), 'git@host.com:group/subgroup/owner/repo.git');
});
test.serial('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL', t => {
process.env.GIT_CREDENTIALS = 'user:pass';
t.is(getAuthUrl('git@host.com:owner/repo.git'), 'https://user:pass@host.com/owner/repo.git');
@@ -68,3 +87,19 @@ test.serial('Return the "https" formatted URL if "gitCredentials" is defined wit
process.env.GITLAB_TOKEN = 'token';
t.is(getAuthUrl('git@host.com:owner/repo.git'), 'https://gitlab-ci-token:token@host.com/owner/repo.git');
});
test.serial('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', t => {
process.env.GIT_CREDENTIALS = 'user:pass';
t.is(
getAuthUrl('https://host.com/group/subgroup/owner/repo.git'),
'https://user:pass@host.com/group/subgroup/owner/repo.git'
);
});
test.serial('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', t => {
process.env.GIT_CREDENTIALS = 'user:pass';
t.is(
getAuthUrl('git@host.com:group/subgroup/owner/repo.git'),
'https://user:pass@host.com/group/subgroup/owner/repo.git'
);
});
+46 -4
View File
@@ -30,10 +30,10 @@ test.serial('Get the highest valid tag', async t => {
await gitCommits(['Fourth']);
await gitTagVersion('v3.0');
const result = await getLastRelease(t.context.logger);
const result = await getLastRelease(`v\${version}`, t.context.logger);
t.deepEqual(result, {gitHead: commits[0].hash, gitTag: 'v2.0.0', version: '2.0.0'});
t.deepEqual(t.context.log.args[0], ['Found git tag version %s', 'v2.0.0']);
t.deepEqual(t.context.log.args[0], ['Found git tag %s associated with version %s', 'v2.0.0', '2.0.0']);
});
test.serial('Get the highest tag in the history of the current branch', async t => {
@@ -55,7 +55,7 @@ test.serial('Get the highest tag in the history of the current branch', async t
// Create the tag corresponding to version 2.0.0
await gitTagVersion('v2.0.0');
const result = await getLastRelease(t.context.logger);
const result = await getLastRelease(`v\${version}`, t.context.logger);
t.deepEqual(result, {gitHead: commits[0].hash, gitTag: 'v2.0.0', version: '2.0.0'});
});
@@ -71,8 +71,50 @@ test.serial('Return empty object if no valid tag is found', async t => {
await gitCommits(['Third']);
await gitTagVersion('v3.0');
const result = await getLastRelease(t.context.logger);
const result = await getLastRelease(`v\${version}`, t.context.logger);
t.deepEqual(result, {});
t.is(t.context.log.args[0][0], 'No git tag version found');
});
test.serial('Get the highest valid tag corresponding to the "tagFormat"', async t => {
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Create some commits and tags
const [{hash: gitHead}] = await gitCommits(['First']);
await gitTagVersion('1.0.0');
t.deepEqual(await getLastRelease(`\${version}`, t.context.logger), {
gitHead,
gitTag: '1.0.0',
version: '1.0.0',
});
await gitTagVersion('foo-1.0.0-bar');
t.deepEqual(await getLastRelease(`foo-\${version}-bar`, t.context.logger), {
gitHead,
gitTag: 'foo-1.0.0-bar',
version: '1.0.0',
});
await gitTagVersion('foo-v1.0.0-bar');
t.deepEqual(await getLastRelease(`foo-v\${version}-bar`, t.context.logger), {
gitHead,
gitTag: 'foo-v1.0.0-bar',
version: '1.0.0',
});
await gitTagVersion('(.+)/1.0.0/(a-z)');
t.deepEqual(await getLastRelease(`(.+)/\${version}/(a-z)`, t.context.logger), {
gitHead,
gitTag: '(.+)/1.0.0/(a-z)',
version: '1.0.0',
});
await gitTagVersion('2.0.0-1.0.0-bar.1');
t.deepEqual(await getLastRelease(`2.0.0-\${version}-bar.1`, t.context.logger), {
gitHead,
gitTag: '2.0.0-1.0.0-bar.1',
version: '1.0.0',
});
});
+18 -3
View File
@@ -11,6 +11,7 @@ import {
gitTags,
isGitRepo,
deleteTag,
verifyTagName,
} from '../lib/git';
import {
gitRepo,
@@ -87,7 +88,7 @@ test.serial('Verify if the commit `sha` is in the direct history of the current
await gitCheckout('master', false);
t.true(await isRefInHistory(commits[0].hash));
t.false(await isRefInHistory(otherCommits[0].hash));
t.falsy(await isRefInHistory(otherCommits[0].hash));
});
test.serial('Get the commit sha for a given tag or falsy if the tag does not exists', async t => {
@@ -168,11 +169,25 @@ test.serial('Return "true" if in a Git repository', async t => {
t.true(await isGitRepo());
});
test.serial('Return "false" if not in a Git repository', async t => {
test.serial('Return falsy if not in a Git repository', async t => {
const dir = tempy.directory();
process.chdir(dir);
t.false(await isGitRepo());
t.falsy(await isGitRepo());
});
test.serial('Return "true" for valid tag names', async t => {
t.true(await verifyTagName('1.0.0'));
t.true(await verifyTagName('v1.0.0'));
t.true(await verifyTagName('tag_name'));
t.true(await verifyTagName('tag/name'));
});
test.serial('Return falsy for invalid tag names', async t => {
t.falsy(await verifyTagName('?1.0.0'));
t.falsy(await verifyTagName('*1.0.0'));
t.falsy(await verifyTagName('[1.0.0]'));
t.falsy(await verifyTagName('1.0.0..'));
});
test.serial('Throws error if obtaining the tags fails', async t => {
+39 -4
View File
@@ -68,7 +68,7 @@ test.serial('Plugins are called with expected values', async t => {
const generateNotes = stub().resolves(notes);
const publish = stub().resolves();
const config = {branch: 'master', repositoryUrl, globalOpt: 'global'};
const config = {branch: 'master', repositoryUrl, globalOpt: 'global', tagFormat: `v\${version}`};
const options = {
...config,
verifyConditions: [verifyConditions1, verifyConditions2],
@@ -130,6 +130,41 @@ test.serial('Plugins are called with expected values', async t => {
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease.gitTag), nextRelease.gitHead);
});
test.serial('Use custom tag format', async t => {
const repositoryUrl = await gitRepo(true);
await gitCommits(['First']);
await gitTagVersion('test-1.0.0');
await gitCommits(['Second']);
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'test-2.0.0'};
const notes = 'Release notes';
const verifyConditions = stub().resolves();
const analyzeCommits = stub().resolves(nextRelease.type);
const verifyRelease = stub().resolves();
const generateNotes = stub().resolves(notes);
const publish = stub().resolves();
const config = {branch: 'master', repositoryUrl, globalOpt: 'global', tagFormat: `test-\${version}`};
const options = {
...config,
verifyConditions,
analyzeCommits,
verifyRelease,
generateNotes,
publish,
};
const semanticRelease = proxyquire('..', {
'./lib/logger': t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
t.truthy(await semanticRelease(options));
// Verify the tag has been created on the local and remote repo and reference the gitHead
t.is(await gitTagHead(nextRelease.gitTag), nextRelease.gitHead);
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease.gitTag), nextRelease.gitHead);
});
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
const repositoryUrl = await gitRepo(true);
@@ -601,11 +636,11 @@ test.serial('Throw SemanticReleaseError if repositoryUrl is not set and cannot b
'./lib/logger': t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
const error = await t.throws(semanticRelease());
const errors = Array.from(await t.throws(semanticRelease()));
// Verify error code and type
t.is(error.code, 'ENOREPOURL');
t.is(error.name, 'SemanticReleaseError');
t.is(errors[0].code, 'ENOREPOURL');
t.is(errors[0].name, 'SemanticReleaseError');
});
test.serial('Throw an Error if plugin returns an unexpected value', async t => {
+18 -10
View File
@@ -127,26 +127,34 @@ test('Merge global options with plugin options', async t => {
t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'});
});
test('Throw an error if plugin configuration is missing a path for plugin pipeline', t => {
const error = t.throws(() => getPlugins({verifyConditions: {}}, {}, t.context.logger));
test('Throw an error if plugins configuration are missing a path for plugin pipeline', t => {
const errors = Array.from(
t.throws(() => getPlugins({verifyConditions: {}, verifyRelease: {}}, {}, t.context.logger))
);
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, 'EPLUGINCONF');
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINCONF');
t.is(
error.message,
errors[0].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.'
);
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EPLUGINCONF');
t.is(
errors[1].message,
'The "verifyRelease" 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.'
);
});
test('Throw an error if an array of plugin configuration is missing a path for plugin pipeline', t => {
const error = t.throws(() =>
getPlugins({verifyConditions: [{path: '@semantic-release/npm'}, {}]}, {}, t.context.logger)
const errors = Array.from(
t.throws(() => getPlugins({verifyConditions: [{path: '@semantic-release/npm'}, {}]}, {}, t.context.logger))
);
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, 'EPLUGINCONF');
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINCONF');
t.is(
error.message,
errors[0].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.'
);
});
+100
View File
@@ -0,0 +1,100 @@
import test from 'ava';
import {stub} from 'sinon';
import tempy from 'tempy';
import verify from '../lib/verify';
import {gitRepo} from './helpers/git-utils';
// Save the current process.env
const envBackup = Object.assign({}, process.env);
// Save the current working diretory
const cwd = process.cwd();
test.beforeEach(t => {
// Delete environment variables that could have been set on the machine running the tests
delete process.env.GIT_CREDENTIALS;
delete process.env.GH_TOKEN;
delete process.env.GITHUB_TOKEN;
delete process.env.GL_TOKEN;
delete process.env.GITLAB_TOKEN;
// Stub the logger functions
t.context.log = stub();
t.context.error = stub();
t.context.logger = {log: t.context.log, error: t.context.error};
});
test.afterEach.always(() => {
// Restore process.env
process.env = envBackup;
// Restore the current working directory
process.chdir(cwd);
});
test.serial('Return "false" if does not run on a git repository', async t => {
const dir = tempy.directory();
process.chdir(dir);
t.false(await verify({}, 'master', t.context.logger));
});
test.serial('Throw a AggregateError', async t => {
await gitRepo();
const errors = Array.from(await t.throws(verify({}, 'master', t.context.logger)));
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].message, 'The repositoryUrl option is required');
t.is(errors[0].code, 'ENOREPOURL');
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].message, 'The tagFormat template must compile to a valid Git tag format');
t.is(errors[1].code, 'EINVALIDTAGFORMAT');
t.is(errors[2].name, 'SemanticReleaseError');
t.is(errors[2].message, `The tagFormat template must contain the variable "\${version}" exactly once`);
t.is(errors[2].code, 'ETAGNOVERSION');
});
test.serial('Throw a SemanticReleaseError if the "tagFormat" is not valid', async t => {
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `?\${version}`};
const errors = Array.from(await t.throws(verify(options, 'master', t.context.logger)));
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].message, 'The tagFormat template must compile to a valid Git tag format');
t.is(errors[0].code, 'EINVALIDTAGFORMAT');
});
test.serial('Throw a SemanticReleaseError if the "tagFormat" does not contains the "version" variable', async t => {
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: 'test'};
const errors = Array.from(await t.throws(verify(options, 'master', t.context.logger)));
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].message, `The tagFormat template must contain the variable "\${version}" exactly once`);
t.is(errors[0].code, 'ETAGNOVERSION');
});
test.serial('Throw a SemanticReleaseError if the "tagFormat" contains multiple "version" variables', async t => {
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `\${version}v\${version}`};
const errors = Array.from(await t.throws(verify(options, 'master', t.context.logger)));
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].message, `The tagFormat template must contain the variable "\${version}" exactly once`);
t.is(errors[0].code, 'ETAGNOVERSION');
});
test.serial('Return "false" if the current branch is not the once configured', async t => {
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `v\${version}`, branch: 'master'};
t.false(await verify(options, 'other', t.context.logger));
});
test.serial('Return "true" if all verification pass', async t => {
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `v\${version}`, branch: 'master'};
t.true(await verify(options, 'master', t.context.logger));
});