Compare commits
+3
-9
@@ -1,9 +1,5 @@
|
||||
# Summary
|
||||
|
||||
## About
|
||||
- [Highlights](README.md#highlights)
|
||||
- [How does it work?](README.md#how-does-it-work)
|
||||
|
||||
## Usage
|
||||
- [Installation](docs/usage/installation.md#installation)
|
||||
- [CI Configuration](docs/usage/ci-configuration.md#ci-configuration)
|
||||
@@ -21,18 +17,16 @@
|
||||
- [Travis CI](docs/recipes/travis.md)
|
||||
- [Travis CI with build stages](docs/recipes/travis-build-stages.md)
|
||||
- [GitLab CI](docs/recipes/gitlab-ci.md)
|
||||
- [Git hosted services](docs/recipes/README.md)
|
||||
- [Git authentication with SSH keys](docs/recipes/git-auth-ssh-keys.md)
|
||||
- [Package managers and languages](docs/recipes/README.md)
|
||||
|
||||
## Developer guide
|
||||
- [Plugin](docs/developer-guide/plugin.md)
|
||||
- [Shareable configuration](docs/developer-guide/shareable-configuration.md)
|
||||
|
||||
## Resources
|
||||
- [Videos](docs/resources.md#videos)
|
||||
- [Articles](docs/resources.md#articles)
|
||||
- [Tutorials](docs/resources.md#tutorials)
|
||||
|
||||
## Support
|
||||
- [Resources](docs/support/resources.md)
|
||||
- [Frequently Asked Questions](docs/support/FAQ.md)
|
||||
- [Troubleshooting](docs/support/troubleshooting.md)
|
||||
- [Node version requirement](docs/support/node-version.md)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"plugins" : ["github", "anchors"],
|
||||
"pluginsConfig": {
|
||||
"github": {
|
||||
"url": "https://github.com/semantic-release/semantic-release"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
const {pickBy, isUndefined} = require('lodash');
|
||||
|
||||
const stringList = {
|
||||
type: 'string',
|
||||
array: true,
|
||||
@@ -13,6 +11,7 @@ module.exports = async () => {
|
||||
const cli = require('yargs')
|
||||
.command('$0', 'Run automated package publishing', yargs => {
|
||||
yargs.demandCommand(0, 0).usage(`Run automated package publishing
|
||||
|
||||
Usage:
|
||||
semantic-release [options] [plugins]`);
|
||||
})
|
||||
@@ -20,7 +19,7 @@ Usage:
|
||||
.option('r', {alias: 'repository-url', describe: 'Git repository URL', type: 'string', group: 'Options'})
|
||||
.option('t', {alias: 'tag-format', describe: 'Git tag format', type: 'string', group: 'Options'})
|
||||
.option('e', {alias: 'extends', describe: 'Shareable configurations', ...stringList, group: 'Options'})
|
||||
.option('ci', {describe: 'Toggle CI verifications', default: undefined, type: 'boolean', group: 'Options'})
|
||||
.option('ci', {describe: 'Toggle CI verifications', type: 'boolean', group: 'Options'})
|
||||
.option('verify-conditions', {...stringList, group: 'Plugins'})
|
||||
.option('analyze-commits', {type: 'string', group: 'Plugins'})
|
||||
.option('verify-release', {...stringList, group: 'Plugins'})
|
||||
@@ -29,16 +28,15 @@ Usage:
|
||||
.option('publish', {...stringList, group: 'Plugins'})
|
||||
.option('success', {...stringList, group: 'Plugins'})
|
||||
.option('fail', {...stringList, group: 'Plugins'})
|
||||
.option('debug', {describe: 'Output debugging information', default: undefined, type: 'boolean', group: 'Options'})
|
||||
.option('d', {alias: 'dry-run', describe: 'Skip publishing', default: undefined, type: 'boolean', group: 'Options'})
|
||||
.option('h', {alias: 'help', default: undefined, group: 'Options'})
|
||||
.option('v', {alias: 'version', default: undefined, group: 'Options'})
|
||||
.option('debug', {describe: 'Output debugging information', type: 'boolean', group: 'Options'})
|
||||
.option('d', {alias: 'dry-run', describe: 'Skip publishing', type: 'boolean', group: 'Options'})
|
||||
.option('h', {alias: 'help', group: 'Options'})
|
||||
.option('v', {alias: 'version', group: 'Options'})
|
||||
.strict(false)
|
||||
.exitProcess(false);
|
||||
|
||||
try {
|
||||
// Remove option with undefined values, as yargs sets non defined options as `undefined`
|
||||
const {help, version, ...opts} = pickBy(cli.argv, value => !isUndefined(value));
|
||||
const {help, version, ...opts} = cli.argv;
|
||||
|
||||
if (Boolean(help) || Boolean(version)) {
|
||||
process.exitCode = 0;
|
||||
|
||||
@@ -4,5 +4,4 @@
|
||||
- [Extending](extending/README.md)- Extending **semantic-release** with plugins and shareable configurations
|
||||
- [Recipes](recipes/README.md) - Community written recipes for common **semantic-release** use-cases
|
||||
- [Developer Guide](developer-guide/README.md) - The essentials of writing a **semantic-release** plugin or shareable configurations
|
||||
- [Resources](resources.md) - Videos, articles and tutorials
|
||||
- [Support](support/README.md) - FAQ and troubleshooting
|
||||
|
||||
@@ -59,7 +59,7 @@ A `package.json` is required only for [local](../usage/installation.md#local-ins
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"semantic-release": "^12.0.0"
|
||||
"semantic-release": "^15.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ A `package.json` is required only for [local](../usage/installation.md#local-ins
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"semantic-release": "^12.0.0"
|
||||
"semantic-release": "^15.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -48,7 +48,7 @@ A `package.json` is required only for [local](../usage/installation.md#local-ins
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"semantic-release": "^12.0.0"
|
||||
"semantic-release": "^15.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -83,8 +83,8 @@ A `package.json` is required only for [local](../usage/installation.md#local-ins
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"semantic-release": "^12.0.0",
|
||||
"travis-deploy-once": "^4.0.0"
|
||||
"semantic-release": "^15.0.0",
|
||||
"travis-deploy-once": "^5.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -124,6 +124,20 @@ Yes, the publishing to the npm registry can be disabled with the [`npmPublish`](
|
||||
|
||||
See the [`@semantic-release/npm`](https://github.com/semantic-release/npm#semantic-releasenpm) plugin documentation for more details.
|
||||
|
||||
## How can I revert a release?
|
||||
|
||||
If you have introduced a breaking bug in a release you have 2 options:
|
||||
- If you have a fix immediately ready, commit and push it (or merge it via a pull request) to the release branch
|
||||
- Otherwise [revert the commit](https://git-scm.com/docs/git-revert) that introduced the bug and push the revert commit (or merge it via a pull request) to the release branch
|
||||
|
||||
In both cases **semantic-release** will publish a new release, so your package users' will get the fixed/reverted version.
|
||||
|
||||
Depending on the package manager you are using, you might be able to un-publish or deprecate a release, in order to prevent users to download it by accident. For example npm allows you to [un-publish](https://docs.npmjs.com/cli/unpublish) in [next 72 hours](https://www.npmjs.com/policies/unpublish) after releasing or to [deprecate](https://docs.npmjs.com/cli/deprecate) a release.
|
||||
|
||||
In any case **do not remove the Git tag associated with the buggy version**, otherwise **semantic-release** will later try to republish that version. Publishing a version after un-publishing is not supported by most package managers.
|
||||
|
||||
**Note**: If you are using the default [Angular Commit Message Conventions](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) be aware that it uses a different revert commit format than the standard one created by [git revert](https://git-scm.com/docs/git-revert), contrary to what is [claimed in the convention](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#revert). Therefore, if you revert a commit with [`git revert`](https://git-scm.com/docs/git-revert), use the [`--edit` option](https://git-scm.com/docs/git-revert#git-revert---edit) to format the message according to the [Angular revert commit message format](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#revert). See [conventional-changelog/conventional-changelog#348](https://github.com/conventional-changelog/conventional-changelog/issues/348) for more details.
|
||||
|
||||
## Can I use `.npmrc` options?
|
||||
|
||||
Yes, all the [npm configuration options](https://docs.npmjs.com/misc/config) are supported via the [`.npmrc`](https://docs.npmjs.com/files/npmrc) file at the root of your repository.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Support
|
||||
|
||||
- [Resources](resources.md) - Videos, articles and tutorials
|
||||
- [Frequently Asked Questions](FAQ.md)
|
||||
- [Troubleshooting](troubleshooting.md)
|
||||
- [Node version requirement](node-version.md)
|
||||
|
||||
@@ -44,3 +44,13 @@ npm ERR! You do not have permission to publish "<package-name>". Are you logged
|
||||
This message is a little unclear, and might not have anything to with your `NPM_TOKEN` or authentication method. It might instead be related to the package name itself. If there is already a package with the same name as yours or, there is a very close match, it could trigger this error.
|
||||
|
||||
Best way to be sure, is to search [npmjs.org](https://www.npmjs.com/)) using your package name. If there is a name conflict, rename your package in your `package.json`
|
||||
|
||||
## Squashed commits are ignored by **semantic-release**
|
||||
|
||||
**semantic-release** parses commits according to a [commit message convention](https://github.com/semantic-release/semantic-release#commit-message-format) to figure out how they affect the codebase. Commits that doesn't follow the project's commit message convention are simply ignored.
|
||||
|
||||
When [squashing commits](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#_squashing) most Git tools will by default generate a new commit message with a summary of the squashed commits. This commit message will most likely not be compliant with the project's commit message convention and therefore will be ignored by **semantic-release**.
|
||||
|
||||
When squashing commits make sure to rewrite the resulting commit message to be compliant with the project's commit message convention.
|
||||
|
||||
**Note**: if the resulting squashed commit would encompasses multiple changes (for example multiple unrelated features or fixes) then it's probably not a good idea to squash those commits together. A commit should contain exactly one self-contained functional change and a functional change should be contained in exactly one commit. See [atomic commits](https://en.wikipedia.org/wiki/Atomic_commit).
|
||||
|
||||
@@ -145,7 +145,7 @@ See [Plugins configuration](plugins.md#configuration) for more details.
|
||||
|
||||
Type: `String`, `Object`
|
||||
|
||||
Default: `['@semantic-release/commit-analyzer']`
|
||||
Default: `'@semantic-release/commit-analyzer'`
|
||||
|
||||
CLI argument: `--analyze-commits`
|
||||
|
||||
@@ -167,13 +167,13 @@ See [Plugins configuration](plugins.md#configuration) for more details.
|
||||
|
||||
### generateNotes
|
||||
|
||||
Type: `String`, `Object`
|
||||
Type: `Array`, `String`, `Object`
|
||||
|
||||
Default: `['@semantic-release/release-notes-generator']`
|
||||
|
||||
CLI argument: `--generate-notes`
|
||||
|
||||
Define the [generate notes plugin](plugins.md#generatenotes-plugin).
|
||||
Define the [generate notes plugins](plugins.md#generatenotes-plugin).
|
||||
|
||||
See [Plugins configuration](plugins.md#configuration) for more details.
|
||||
|
||||
|
||||
@@ -24,6 +24,6 @@ For other type of projects we recommend installing **semantic-release** directly
|
||||
$ npx semantic-release
|
||||
```
|
||||
|
||||
**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:** For a global installation, it's recommended to specify the major **semantic-release** version to install (for example with with `npx semantic-release@15`, or `npm install -g semantic-release@15`). 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.
|
||||
**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.
|
||||
|
||||
@@ -20,13 +20,13 @@ Default implementation: [@semantic-release/commit-analyzer](https://github.com/s
|
||||
|
||||
### verifyRelease plugin
|
||||
|
||||
Responsible for verifying the parameters (version, type, dist-tag etc...) of the release that is about to be published match certain expectations. For example the [cracks plugin](https://github.com/semantic-release/cracks) is able to verify that if a release contains breaking changes, its type must be `major`.
|
||||
Responsible for verifying the parameters (version, type, dist-tag etc...) of the release that is about to be published. For example the [cracks plugin](https://github.com/semantic-release/cracks) is able to verify that if a release contains breaking changes, its type must be `major`.
|
||||
|
||||
Default implementation: none.
|
||||
|
||||
### generateNotes plugin
|
||||
|
||||
Responsible for generating release notes.
|
||||
Responsible for generating release notes. If multiple `generateNotes` plugins are defined, the release notes will be the result of the concatenation of plugin output.
|
||||
|
||||
Default implementation: [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator).
|
||||
|
||||
@@ -91,7 +91,7 @@ For example:
|
||||
With this configuration:
|
||||
- the `custom-plugin` npm module will be used to [analyze commits](#analyzecommits-plugin)
|
||||
- the `./build/my-plugin.js` script will be used to [generate release notes](#generatenotes-plugin)
|
||||
- the [`@semantic-release/exec`](https://github.com/semantic-release/exec), [`@semantic-release/npm`](https://github.com/semantic-release/npm) and [`@semantic-release/exec`](https://github.com/semantic-release/exec) plugins will be used to [verify conditions](#verifyconditions-plugin)
|
||||
- the [`@semantic-release/exec`](https://github.com/semantic-release/exec), [`@semantic-release/npm`](https://github.com/semantic-release/npm) and [`@semantic-release/github`](https://github.com/semantic-release/github) plugins will be used to [verify conditions](#verifyconditions-plugin)
|
||||
- the [`@semantic-release/exec`](https://github.com/semantic-release/exec) plugin will be used to [verify the release](#verifyrelease-plugin)
|
||||
- the `cmd` option will be set to `verify-conditions.sh` only for the [`@semantic-release/exec`](https://github.com/semantic-release/exec) plugin used to [verify conditions](#verifyconditions-plugin)
|
||||
- the `cmd` option will be set to `verify-release.sh` only for the [`@semantic-release/exec`](https://github.com/semantic-release/exec) plugin used to [verify the release](#verifyrelease-plugin)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {template, isPlainObject, castArray} = require('lodash');
|
||||
const {template} = require('lodash');
|
||||
const marked = require('marked');
|
||||
const TerminalRenderer = require('marked-terminal');
|
||||
const envCi = require('env-ci');
|
||||
@@ -13,7 +13,7 @@ const getLastRelease = require('./lib/get-last-release');
|
||||
const {extractErrors} = require('./lib/utils');
|
||||
const getGitAuthUrl = require('./lib/get-git-auth-url');
|
||||
const logger = require('./lib/logger');
|
||||
const {unshallow, verifyAuth, isBranchUpToDate, gitHead: getGitHead, tag, push} = require('./lib/git');
|
||||
const {fetch, verifyAuth, isBranchUpToDate, gitHead: getGitHead, tag, push} = require('./lib/git');
|
||||
const getError = require('./lib/get-error');
|
||||
const {COMMIT_NAME, COMMIT_EMAIL} = require('./lib/definitions/constants');
|
||||
|
||||
@@ -54,42 +54,32 @@ async function run(options, plugins) {
|
||||
|
||||
await verify(options);
|
||||
|
||||
const {repositoryUrl} = options;
|
||||
options.repositoryUrl = await getGitAuthUrl(options);
|
||||
|
||||
if (!(await isBranchUpToDate(options.branch))) {
|
||||
logger.log(
|
||||
"The local branch %s is behind the remote one, therefore a new version won't be published.",
|
||||
options.branch
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await verifyAuth(options.repositoryUrl, options.branch);
|
||||
} catch (err) {
|
||||
if (!(await isBranchUpToDate(options.branch))) {
|
||||
logger.log(
|
||||
"The local branch %s is behind the remote one, therefore a new version won't be published.",
|
||||
options.branch
|
||||
);
|
||||
return false;
|
||||
}
|
||||
logger.error(`The command "${err.cmd}" failed with the error message %s.`, err.stderr);
|
||||
throw getError('EGITNOPERMISSION', {options, repositoryUrl});
|
||||
throw getError('EGITNOPERMISSION', {options});
|
||||
}
|
||||
|
||||
logger.log('Run automated release from branch %s', options.branch);
|
||||
|
||||
logger.log('Call plugin %s', 'verify-conditions');
|
||||
await plugins.verifyConditions({options, logger}, {settleAll: true});
|
||||
await plugins.verifyConditions({options, logger});
|
||||
|
||||
// Unshallow the repo in order to get all the tags
|
||||
await unshallow(options.repositoryUrl);
|
||||
await fetch(options.repositoryUrl);
|
||||
|
||||
const lastRelease = await getLastRelease(options.tagFormat, logger);
|
||||
const commits = await getCommits(lastRelease.gitHead, options.branch, logger);
|
||||
|
||||
logger.log('Call plugin %s', 'analyze-commits');
|
||||
const type = await plugins.analyzeCommits({
|
||||
options,
|
||||
logger,
|
||||
lastRelease,
|
||||
commits: commits.filter(commit => !/\[skip\s+release\]|\[release\s+skip\]/i.test(commit.message)),
|
||||
});
|
||||
const type = await plugins.analyzeCommits({options, logger, lastRelease, commits});
|
||||
if (!type) {
|
||||
logger.log('There are no relevant changes, so no new version is released.');
|
||||
return;
|
||||
@@ -97,55 +87,28 @@ async function run(options, plugins) {
|
||||
const version = getNextVersion(type, lastRelease, logger);
|
||||
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}, {settleAll: true});
|
||||
await plugins.verifyRelease({options, logger, lastRelease, commits, nextRelease});
|
||||
|
||||
const generateNotesParam = {options, logger, lastRelease, commits, nextRelease};
|
||||
|
||||
if (options.dryRun) {
|
||||
logger.log('Call plugin %s', 'generate-notes');
|
||||
const notes = await plugins.generateNotes(generateNotesParam);
|
||||
logger.log('Release note for version %s:\n', nextRelease.version);
|
||||
process.stdout.write(`${marked(notes)}\n`);
|
||||
if (notes) {
|
||||
process.stdout.write(`${marked(notes)}\n`);
|
||||
}
|
||||
} else {
|
||||
logger.log('Call plugin %s', 'generateNotes');
|
||||
nextRelease.notes = await plugins.generateNotes(generateNotesParam);
|
||||
|
||||
logger.log('Call plugin %s', 'prepare');
|
||||
await plugins.prepare(
|
||||
{options, logger, lastRelease, commits, nextRelease},
|
||||
{
|
||||
getNextInput: async lastResult => {
|
||||
const newGitHead = await getGitHead();
|
||||
// If previous prepare plugin has created a commit (gitHead changed)
|
||||
if (lastResult.nextRelease.gitHead !== newGitHead) {
|
||||
nextRelease.gitHead = newGitHead;
|
||||
// Regenerate the release notes
|
||||
logger.log('Call plugin %s', 'generateNotes');
|
||||
nextRelease.notes = await plugins.generateNotes(generateNotesParam);
|
||||
}
|
||||
// Call the next publish plugin with the updated `nextRelease`
|
||||
return {options, logger, lastRelease, commits, nextRelease};
|
||||
},
|
||||
}
|
||||
);
|
||||
await plugins.prepare({options, logger, lastRelease, commits, nextRelease});
|
||||
|
||||
// Create the tag before calling the publish plugins as some require the tag to exists
|
||||
logger.log('Create tag %s', nextRelease.gitTag);
|
||||
await tag(nextRelease.gitTag);
|
||||
await push(options.repositoryUrl, branch);
|
||||
|
||||
logger.log('Call plugin %s', 'publish');
|
||||
const releases = await plugins.publish(
|
||||
{options, logger, lastRelease, commits, nextRelease},
|
||||
// Add nextRelease and plugin properties to published release
|
||||
{transform: (release, step) => ({...(isPlainObject(release) ? release : {}), ...nextRelease, ...step})}
|
||||
);
|
||||
const releases = await plugins.publish({options, logger, lastRelease, commits, nextRelease});
|
||||
|
||||
await plugins.success(
|
||||
{options, logger, lastRelease, commits, nextRelease, releases: castArray(releases)},
|
||||
{settleAll: true}
|
||||
);
|
||||
await plugins.success({options, logger, lastRelease, commits, nextRelease, releases});
|
||||
|
||||
logger.log('Published release: %s', nextRelease.version);
|
||||
}
|
||||
@@ -170,7 +133,7 @@ async function callFail(plugins, options, error) {
|
||||
const errors = extractErrors(error).filter(error => error.semanticRelease);
|
||||
if (errors.length > 0) {
|
||||
try {
|
||||
await plugins.fail({options, logger, errors}, {settleAll: true});
|
||||
await plugins.fail({options, logger, errors});
|
||||
} catch (err) {
|
||||
logErrors(err);
|
||||
}
|
||||
|
||||
@@ -6,4 +6,6 @@ const COMMIT_NAME = 'semantic-release-bot';
|
||||
|
||||
const COMMIT_EMAIL = 'semantic-release-bot@martynus.net';
|
||||
|
||||
module.exports = {RELEASE_TYPE, FIRST_RELEASE, COMMIT_NAME, COMMIT_EMAIL};
|
||||
const RELEASE_NOTES_SEPARATOR = '\n\n';
|
||||
|
||||
module.exports = {RELEASE_TYPE, FIRST_RELEASE, COMMIT_NAME, COMMIT_EMAIL, RELEASE_NOTES_SEPARATOR};
|
||||
|
||||
+14
-14
@@ -4,7 +4,7 @@ const {toLower, isString} = require('lodash');
|
||||
const pkg = require('../../package.json');
|
||||
const {RELEASE_TYPE} = require('./constants');
|
||||
|
||||
const homepage = url.format({...url.parse(pkg.homepage), ...{hash: null}});
|
||||
const homepage = url.format({...url.parse(pkg.homepage), hash: null});
|
||||
const stringify = obj => (isString(obj) ? obj : inspect(obj, {breakLength: Infinity, depth: 2, maxArrayLength: 5}));
|
||||
const linkify = file => `${homepage}/blob/caribou/${file}`;
|
||||
|
||||
@@ -27,11 +27,11 @@ Please make sure to add the \`repositoryUrl\` to the [semantic-release configura
|
||||
'docs/usage/configuration.md'
|
||||
)}).`,
|
||||
}),
|
||||
EGITNOPERMISSION: ({options, repositoryUrl}) => ({
|
||||
EGITNOPERMISSION: ({options}) => ({
|
||||
message: 'The push permission to the Git repository is required.',
|
||||
details: `**semantic-release** cannot push the version tag to the branch \`${
|
||||
options.branch
|
||||
}\` on remote Git repository with URL \`${repositoryUrl}\`.
|
||||
}\` on remote Git repository with URL \`${options.repositoryUrl}\`.
|
||||
|
||||
Please refer to the [authentication configuration documentation](${linkify(
|
||||
'docs/usage/ci-configuration.md#authentication'
|
||||
@@ -55,25 +55,25 @@ Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`
|
||||
|
||||
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
|
||||
}),
|
||||
EPLUGINCONF: ({pluginType, pluginConf}) => ({
|
||||
message: `The \`${pluginType}\` plugin configuration is invalid.`,
|
||||
details: `The [${pluginType} plugin configuration](${linkify(
|
||||
`docs/usage/plugins.md#${toLower(pluginType)}-plugin`
|
||||
EPLUGINCONF: ({type, pluginConf}) => ({
|
||||
message: `The \`${type}\` plugin configuration is invalid.`,
|
||||
details: `The [${type} plugin configuration](${linkify(
|
||||
`docs/usage/plugins.md#${toLower(type)}-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.
|
||||
|
||||
Your configuration for the \`${pluginType}\` plugin is \`${stringify(pluginConf)}\`.`,
|
||||
Your configuration for the \`${type}\` plugin is \`${stringify(pluginConf)}\`.`,
|
||||
}),
|
||||
EPLUGIN: ({pluginName, pluginType}) => ({
|
||||
message: `A plugin configured in the step ${pluginType} is not a valid semantic-release plugin.`,
|
||||
details: `A valid \`${pluginType}\` **semantic-release** plugin must be a function or an object with a function in the property \`${pluginType}\`.
|
||||
EPLUGIN: ({pluginName, type}) => ({
|
||||
message: `A plugin configured in the step ${type} is not a valid semantic-release plugin.`,
|
||||
details: `A valid \`${type}\` **semantic-release** plugin must be a function or an object with a function in the property \`${type}\`.
|
||||
|
||||
The plugin \`${pluginName}\` doesn't have the property \`${pluginType}\` and cannot be used for the \`${pluginType}\` step.
|
||||
The plugin \`${pluginName}\` doesn't have the property \`${type}\` and cannot be used for the \`${type}\` step.
|
||||
|
||||
Please refer to the \`${pluginName}\` and [semantic-release plugins configuration](${linkify(
|
||||
'docs/usage/plugins.md'
|
||||
)}) documentation for more details.`,
|
||||
}),
|
||||
EANALYZEOUTPUT: ({result, pluginName}) => ({
|
||||
EANALYZECOMMITSOUTPUT: ({result, pluginName}) => ({
|
||||
message: 'The `analyzeCommits` plugin returned an invalid value. It must return a valid semver release type.',
|
||||
details: `The \`analyzeCommits\` plugin must return a valid [semver](https://semver.org) release type. The valid values are: ${RELEASE_TYPE.map(
|
||||
type => `\`${type}\``
|
||||
@@ -89,7 +89,7 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
|
||||
'docs/developer-guide/plugin.md'
|
||||
)})`,
|
||||
}),
|
||||
ERELEASENOTESOUTPUT: ({result, pluginName}) => ({
|
||||
EGENERATENOTESOUTPUT: ({result, pluginName}) => ({
|
||||
message: 'The `generateNotes` plugin returned an invalid value. It must return a `String`.',
|
||||
details: `The \`generateNotes\` plugin must return a \`String\`.
|
||||
|
||||
|
||||
+55
-38
@@ -1,67 +1,84 @@
|
||||
const {isString, isFunction, isArray, isPlainObject} = require('lodash');
|
||||
const {RELEASE_TYPE} = require('./constants');
|
||||
const {gitHead} = require('../git');
|
||||
const {RELEASE_TYPE, RELEASE_NOTES_SEPARATOR} = require('./constants');
|
||||
|
||||
const validatePluginConfig = conf => isString(conf) || isString(conf.path) || isFunction(conf);
|
||||
|
||||
module.exports = {
|
||||
verifyConditions: {
|
||||
default: ['@semantic-release/npm', '@semantic-release/github'],
|
||||
config: {
|
||||
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
},
|
||||
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
pipelineConfig: () => ({settleAll: true}),
|
||||
},
|
||||
analyzeCommits: {
|
||||
default: '@semantic-release/commit-analyzer',
|
||||
config: {
|
||||
validator: conf => Boolean(conf) && validatePluginConfig(conf),
|
||||
},
|
||||
output: {
|
||||
validator: output => !output || RELEASE_TYPE.includes(output),
|
||||
error: 'EANALYZEOUTPUT',
|
||||
},
|
||||
configValidator: conf => Boolean(conf) && validatePluginConfig(conf),
|
||||
outputValidator: output => !output || RELEASE_TYPE.includes(output),
|
||||
preprocess: ({commits, ...inputs}) => ({
|
||||
...inputs,
|
||||
commits: commits.filter(commit => !/\[skip\s+release\]|\[release\s+skip\]/i.test(commit.message)),
|
||||
}),
|
||||
postprocess: ([result]) => result,
|
||||
},
|
||||
verifyRelease: {
|
||||
default: false,
|
||||
config: {
|
||||
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
},
|
||||
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
pipelineConfig: () => ({settleAll: true}),
|
||||
},
|
||||
generateNotes: {
|
||||
default: '@semantic-release/release-notes-generator',
|
||||
config: {
|
||||
validator: conf => !conf || validatePluginConfig(conf),
|
||||
},
|
||||
output: {
|
||||
validator: output => !output || isString(output),
|
||||
error: 'ERELEASENOTESOUTPUT',
|
||||
},
|
||||
default: ['@semantic-release/release-notes-generator'],
|
||||
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
outputValidator: output => !output || isString(output),
|
||||
pipelineConfig: () => ({
|
||||
getNextInput: ({nextRelease, ...generateNotesParam}, notes) => ({
|
||||
...generateNotesParam,
|
||||
nextRelease: {
|
||||
...nextRelease,
|
||||
notes: `${nextRelease.notes ? `${nextRelease.notes}${RELEASE_NOTES_SEPARATOR}` : ''}${notes}`,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
postprocess: results => results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR),
|
||||
},
|
||||
prepare: {
|
||||
default: ['@semantic-release/npm'],
|
||||
config: {
|
||||
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
},
|
||||
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
pipelineConfig: ({generateNotes}, logger) => ({
|
||||
getNextInput: async ({nextRelease, ...prepareParam}) => {
|
||||
const newGitHead = await gitHead();
|
||||
// If previous prepare plugin has created a commit (gitHead changed)
|
||||
if (nextRelease.gitHead !== newGitHead) {
|
||||
nextRelease.gitHead = newGitHead;
|
||||
// Regenerate the release notes
|
||||
logger.log('Call plugin %s', 'generateNotes');
|
||||
nextRelease.notes = await generateNotes({nextRelease, ...prepareParam});
|
||||
}
|
||||
// Call the next publish plugin with the updated `nextRelease`
|
||||
return {...prepareParam, nextRelease};
|
||||
},
|
||||
}),
|
||||
},
|
||||
publish: {
|
||||
default: ['@semantic-release/npm', '@semantic-release/github'],
|
||||
config: {
|
||||
validator: conf => Boolean(conf) && (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
},
|
||||
output: {
|
||||
validator: output => !output || isPlainObject(output),
|
||||
error: 'EPUBLISHOUTPUT',
|
||||
},
|
||||
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
outputValidator: output => !output || isPlainObject(output),
|
||||
pipelineConfig: () => ({
|
||||
// Add `nextRelease` and plugin properties to published release
|
||||
transform: (release, step, {nextRelease}) => ({
|
||||
...(isPlainObject(release) ? release : {}),
|
||||
...nextRelease,
|
||||
...step,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
success: {
|
||||
default: ['@semantic-release/github'],
|
||||
config: {
|
||||
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
},
|
||||
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
pipelineConfig: () => ({settleAll: true}),
|
||||
},
|
||||
fail: {
|
||||
default: ['@semantic-release/github'],
|
||||
config: {
|
||||
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
},
|
||||
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
pipelineConfig: () => ({settleAll: true}),
|
||||
},
|
||||
};
|
||||
|
||||
+2
-2
@@ -34,9 +34,9 @@ module.exports = async (opts, logger) => {
|
||||
|
||||
// For each plugin defined in a shareable config, save in `pluginsPath` the extendable config path,
|
||||
// so those plugin will be loaded relatively to the config file
|
||||
Object.keys(extendsOpts).reduce((pluginsPath, option) => {
|
||||
Object.entries(extendsOpts).reduce((pluginsPath, [option, value]) => {
|
||||
if (PLUGINS_DEFINITIONS[option]) {
|
||||
castArray(extendsOpts[option])
|
||||
castArray(value)
|
||||
.filter(plugin => isString(plugin) || (isPlainObject(plugin) && isString(plugin.path)))
|
||||
.map(plugin => (isString(plugin) ? plugin : plugin.path))
|
||||
.forEach(plugin => {
|
||||
|
||||
@@ -35,10 +35,7 @@ module.exports = async ({repositoryUrl, branch}) => {
|
||||
|
||||
// Replace `git+https` and `git+http` with `https` or `http`
|
||||
if (protocols.includes('http') || protocols.includes('https')) {
|
||||
repositoryUrl = format({
|
||||
...parse(repositoryUrl),
|
||||
...{protocol: protocols.includes('https') ? 'https' : 'http'},
|
||||
});
|
||||
repositoryUrl = format({...parse(repositoryUrl), protocol: protocols.includes('https') ? 'https' : 'http'});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-5
@@ -49,12 +49,16 @@ async function isRefInHistory(ref) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshallow the git repository (retriving every commits and tags).
|
||||
* Unshallow the git repository if necessary and fetch all the tags.
|
||||
*
|
||||
* @param {String} repositoryUrl The remote repository URL.
|
||||
*/
|
||||
async function unshallow(repositoryUrl) {
|
||||
await execa('git', ['fetch', '--unshallow', '--tags', repositoryUrl], {reject: false});
|
||||
async function fetch(repositoryUrl) {
|
||||
try {
|
||||
await execa('git', ['fetch', '--unshallow', '--tags', repositoryUrl]);
|
||||
} catch (err) {
|
||||
await execa('git', ['fetch', '--tags', repositoryUrl]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,14 +150,20 @@ async function verifyTagName(tagName) {
|
||||
* @return {Boolean} `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, falsy otherwise.
|
||||
*/
|
||||
async function isBranchUpToDate(branch) {
|
||||
return isRefInHistory(await execa.stdout('git', ['rev-parse', `origin/${branch}`]));
|
||||
try {
|
||||
return await isRefInHistory(
|
||||
(await execa.stdout('git', ['ls-remote', '--heads', 'origin', branch])).match(/^(\w+)?/)[1]
|
||||
);
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
gitTagHead,
|
||||
gitTags,
|
||||
isRefInHistory,
|
||||
unshallow,
|
||||
fetch,
|
||||
gitHead,
|
||||
repoUrl,
|
||||
isGitRepo,
|
||||
|
||||
+28
-22
@@ -1,4 +1,4 @@
|
||||
const {isArray, isObject, omit, castArray, isUndefined} = require('lodash');
|
||||
const {identity, isPlainObject, omit, castArray, isUndefined} = require('lodash');
|
||||
const AggregateError = require('aggregate-error');
|
||||
const getError = require('../get-error');
|
||||
const PLUGINS_DEFINITIONS = require('../definitions/plugins');
|
||||
@@ -7,32 +7,38 @@ const normalize = require('./normalize');
|
||||
|
||||
module.exports = (options, pluginsPath, logger) => {
|
||||
const errors = [];
|
||||
const plugins = Object.keys(PLUGINS_DEFINITIONS).reduce((plugins, pluginType) => {
|
||||
const {config, default: def} = PLUGINS_DEFINITIONS[pluginType];
|
||||
let pluginConfs;
|
||||
const plugins = Object.entries(PLUGINS_DEFINITIONS).reduce(
|
||||
(
|
||||
plugins,
|
||||
[type, {configValidator, default: def, pipelineConfig, postprocess = identity, preprocess = identity}]
|
||||
) => {
|
||||
let pluginConfs;
|
||||
|
||||
if (isUndefined(options[pluginType])) {
|
||||
pluginConfs = def;
|
||||
} else {
|
||||
// If an object is passed and the path is missing, set the default one for single plugins
|
||||
if (isObject(options[pluginType]) && !options[pluginType].path && !isArray(def)) {
|
||||
options[pluginType].path = def;
|
||||
if (isUndefined(options[type])) {
|
||||
pluginConfs = def;
|
||||
} else {
|
||||
const defaultPaths = castArray(def);
|
||||
// If an object is passed and the path is missing, set the default one for single plugins
|
||||
if (isPlainObject(options[type]) && !options[type].path && defaultPaths.length === 1) {
|
||||
[options[type].path] = defaultPaths;
|
||||
}
|
||||
if (configValidator && !configValidator(options[type])) {
|
||||
errors.push(getError('EPLUGINCONF', {type, pluginConf: options[type]}));
|
||||
return plugins;
|
||||
}
|
||||
pluginConfs = options[type];
|
||||
}
|
||||
if (config && !config.validator(options[pluginType])) {
|
||||
errors.push(getError('EPLUGINCONF', {pluginType, pluginConf: options[pluginType]}));
|
||||
return plugins;
|
||||
}
|
||||
pluginConfs = options[pluginType];
|
||||
}
|
||||
|
||||
const globalOpts = omit(options, Object.keys(PLUGINS_DEFINITIONS));
|
||||
const globalOpts = omit(options, Object.keys(PLUGINS_DEFINITIONS));
|
||||
const steps = castArray(pluginConfs).map(conf => normalize(type, pluginsPath, globalOpts, conf, logger));
|
||||
|
||||
plugins[pluginType] = pipeline(
|
||||
castArray(pluginConfs).map(conf => normalize(pluginType, pluginsPath, globalOpts, conf, logger))
|
||||
);
|
||||
plugins[type] = async input =>
|
||||
postprocess(await pipeline(steps, pipelineConfig && pipelineConfig(plugins, logger))(await preprocess(input)));
|
||||
|
||||
return plugins;
|
||||
}, {});
|
||||
return plugins;
|
||||
},
|
||||
{}
|
||||
);
|
||||
if (errors.length > 0) {
|
||||
throw new AggregateError(errors);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ const getError = require('../get-error');
|
||||
const {extractErrors} = require('../utils');
|
||||
const PLUGINS_DEFINITIONS = require('../definitions/plugins');
|
||||
|
||||
module.exports = (pluginType, pluginsPath, globalOpts, pluginOpts, logger) => {
|
||||
/* eslint max-params: ["error", 5] */
|
||||
|
||||
module.exports = (type, pluginsPath, globalOpts, pluginOpts, logger) => {
|
||||
if (!pluginOpts) {
|
||||
return noop;
|
||||
}
|
||||
@@ -15,9 +17,9 @@ module.exports = (pluginType, pluginsPath, globalOpts, pluginOpts, logger) => {
|
||||
|
||||
if (!isFunction(pluginOpts)) {
|
||||
if (pluginsPath[path]) {
|
||||
logger.log('Load plugin "%s" from %s in shareable config %s', pluginType, path, pluginsPath[path]);
|
||||
logger.log('Load plugin "%s" from %s in shareable config %s', type, path, pluginsPath[path]);
|
||||
} else {
|
||||
logger.log('Load plugin "%s" from %s', pluginType, path);
|
||||
logger.log('Load plugin "%s" from %s', type, path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,18 +33,19 @@ module.exports = (pluginType, pluginsPath, globalOpts, pluginOpts, logger) => {
|
||||
let func;
|
||||
if (isFunction(plugin)) {
|
||||
func = plugin.bind(null, cloneDeep({...globalOpts, ...config}));
|
||||
} else if (isPlainObject(plugin) && plugin[pluginType] && isFunction(plugin[pluginType])) {
|
||||
func = plugin[pluginType].bind(null, cloneDeep({...globalOpts, ...config}));
|
||||
} else if (isPlainObject(plugin) && plugin[type] && isFunction(plugin[type])) {
|
||||
func = plugin[type].bind(null, cloneDeep({...globalOpts, ...config}));
|
||||
} else {
|
||||
throw getError('EPLUGIN', {pluginType, pluginName});
|
||||
throw getError('EPLUGIN', {type, pluginName});
|
||||
}
|
||||
|
||||
const validator = async input => {
|
||||
const definition = PLUGINS_DEFINITIONS[pluginType];
|
||||
const {outputValidator} = PLUGINS_DEFINITIONS[type] || {};
|
||||
try {
|
||||
logger.log('Call plugin "%s"', type);
|
||||
const result = await func(cloneDeep(input));
|
||||
if (definition && definition.output && !definition.output.validator(result)) {
|
||||
throw getError(PLUGINS_DEFINITIONS[pluginType].output.error, {result, pluginName});
|
||||
if (outputValidator && !outputValidator(result)) {
|
||||
throw getError(`E${type.toUpperCase()}OUTPUT`, {result, pluginName});
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
|
||||
+12
-11
@@ -8,10 +8,6 @@ const {extractErrors} = require('../utils');
|
||||
*
|
||||
* @typedef {Function} Pipeline
|
||||
* @param {Any} input Argument to pass to the first step in the pipeline.
|
||||
* @param {Object} options Pipeline options.
|
||||
* @param {Boolean} [options.settleAll=false] If `true` all the steps in the pipeline are executed, even if one rejects, if `false` the execution stops after a steps rejects.
|
||||
* @param {Function} [options.getNextInput=identity] Function called after each step is executed, with the last and current step results; the returned value will be used as the argument of the next step.
|
||||
* @param {Function} [options.transform=identity] Function called after each step is executed, with the current step result and the step function; the returned value will be saved in the pipeline results.
|
||||
*
|
||||
* @return {Array<*>|*} An Array with the result of each step in the pipeline; if there is only 1 step in the pipeline, the result of this step is returned directly.
|
||||
*
|
||||
@@ -22,18 +18,23 @@ const {extractErrors} = require('../utils');
|
||||
* Create a Pipeline with a list of Functions.
|
||||
*
|
||||
* @param {Array<Function>} steps The list of Function to execute.
|
||||
* @param {Object} options Pipeline options.
|
||||
* @param {Boolean} [options.settleAll=false] If `true` all the steps in the pipeline are executed, even if one rejects, if `false` the execution stops after a steps rejects.
|
||||
* @param {Function} [options.getNextInput=identity] Function called after each step is executed, with the last step input and the current current step result; the returned value will be used as the input of the next step.
|
||||
* @param {Function} [options.transform=identity] Function called after each step is executed, with the current step result, the step function and the last step input; the returned value will be saved in the pipeline results.
|
||||
*
|
||||
* @return {Pipeline} A Function that execute the `steps` sequencially
|
||||
*/
|
||||
module.exports = steps => async (input, {settleAll = false, getNextInput = identity, transform = identity} = {}) => {
|
||||
module.exports = (steps, {settleAll = false, getNextInput = identity, transform = identity} = {}) => async input => {
|
||||
const results = [];
|
||||
const errors = [];
|
||||
await pReduce(
|
||||
steps,
|
||||
async (lastResult, step) => {
|
||||
async (lastInput, step) => {
|
||||
let result;
|
||||
try {
|
||||
// Call the step with the input computed at the end of the previous iteration and save intermediary result
|
||||
result = await transform(await step(lastResult), step);
|
||||
result = await transform(await step(lastInput), step, lastInput);
|
||||
results.push(result);
|
||||
} catch (err) {
|
||||
if (settleAll) {
|
||||
@@ -43,13 +44,13 @@ module.exports = steps => async (input, {settleAll = false, getNextInput = ident
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
// Prepare input for the next step, passing the result of the last iteration (or initial parameter for the first iteration) and the current one
|
||||
return getNextInput(lastResult, result);
|
||||
// Prepare input for the next step, passing the input of the last iteration (or initial parameter for the first iteration) and the result of the current one
|
||||
return getNextInput(lastInput, result);
|
||||
},
|
||||
input
|
||||
);
|
||||
if (errors.length > 0) {
|
||||
throw errors.length === 1 ? errors[0] : new AggregateError(errors);
|
||||
throw new AggregateError(errors);
|
||||
}
|
||||
return results.length <= 1 ? results[0] : results;
|
||||
return results;
|
||||
};
|
||||
|
||||
+7
-7
@@ -33,22 +33,22 @@
|
||||
"find-versions": "^2.0.0",
|
||||
"get-stream": "^3.0.0",
|
||||
"git-log-parser": "^1.2.0",
|
||||
"git-url-parse": "^9.0.0",
|
||||
"git-url-parse": "^10.0.1",
|
||||
"hook-std": "^1.0.1",
|
||||
"hosted-git-info": "^2.6.0",
|
||||
"hosted-git-info": "^2.7.1",
|
||||
"lodash": "^4.17.4",
|
||||
"marked": "^0.4.0",
|
||||
"marked-terminal": "^3.0.0",
|
||||
"p-locate": "^2.0.0",
|
||||
"p-locate": "^3.0.0",
|
||||
"p-reduce": "^1.0.0",
|
||||
"read-pkg-up": "^3.0.0",
|
||||
"read-pkg-up": "^4.0.0",
|
||||
"resolve-from": "^4.0.0",
|
||||
"semver": "^5.4.1",
|
||||
"yargs": "^11.0.0"
|
||||
"yargs": "^12.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^0.25.0",
|
||||
"clear-module": "^2.1.0",
|
||||
"clear-module": "^3.0.0",
|
||||
"codecov": "^3.0.0",
|
||||
"commitizen": "^2.9.6",
|
||||
"cz-conventional-changelog": "^2.0.0",
|
||||
@@ -63,7 +63,7 @@
|
||||
"nyc": "^12.0.1",
|
||||
"p-retry": "^2.0.0",
|
||||
"proxyquire": "^2.0.0",
|
||||
"sinon": "^5.0.1",
|
||||
"sinon": "^6.0.0",
|
||||
"tempy": "^0.2.1",
|
||||
"xo": "^0.21.0"
|
||||
},
|
||||
|
||||
@@ -1,133 +1,129 @@
|
||||
import test from 'ava';
|
||||
import plugins from '../../lib/definitions/plugins';
|
||||
import errors from '../../lib/definitions/errors';
|
||||
import {RELEASE_NOTES_SEPARATOR} from '../../lib/definitions/constants';
|
||||
|
||||
test('The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition', t => {
|
||||
t.false(plugins.verifyConditions.config.validator({}));
|
||||
t.false(plugins.verifyConditions.config.validator({path: null}));
|
||||
t.false(plugins.verifyConditions.configValidator({}));
|
||||
t.false(plugins.verifyConditions.configValidator({path: null}));
|
||||
|
||||
t.true(plugins.verifyConditions.config.validator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.verifyConditions.config.validator());
|
||||
t.true(plugins.verifyConditions.config.validator('plugin-path.js'));
|
||||
t.true(plugins.verifyConditions.config.validator(() => {}));
|
||||
t.true(plugins.verifyConditions.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
t.true(plugins.verifyConditions.configValidator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.verifyConditions.configValidator());
|
||||
t.true(plugins.verifyConditions.configValidator('plugin-path.js'));
|
||||
t.true(plugins.verifyConditions.configValidator(() => {}));
|
||||
t.true(plugins.verifyConditions.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
});
|
||||
|
||||
test('The "analyzeCommits" plugin is mandatory, and must be a single plugin definition', t => {
|
||||
t.false(plugins.analyzeCommits.config.validator({}));
|
||||
t.false(plugins.analyzeCommits.config.validator({path: null}));
|
||||
t.false(plugins.analyzeCommits.config.validator([]));
|
||||
t.false(plugins.analyzeCommits.config.validator());
|
||||
t.false(plugins.analyzeCommits.configValidator({}));
|
||||
t.false(plugins.analyzeCommits.configValidator({path: null}));
|
||||
t.false(plugins.analyzeCommits.configValidator([]));
|
||||
t.false(plugins.analyzeCommits.configValidator());
|
||||
|
||||
t.true(plugins.analyzeCommits.config.validator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.analyzeCommits.config.validator('plugin-path.js'));
|
||||
t.true(plugins.analyzeCommits.config.validator(() => {}));
|
||||
t.true(plugins.analyzeCommits.configValidator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.analyzeCommits.configValidator('plugin-path.js'));
|
||||
t.true(plugins.analyzeCommits.configValidator(() => {}));
|
||||
});
|
||||
|
||||
test('The "verifyRelease" plugin, if defined, must be a single or an array of plugins definition', t => {
|
||||
t.false(plugins.verifyRelease.config.validator({}));
|
||||
t.false(plugins.verifyRelease.config.validator({path: null}));
|
||||
t.false(plugins.verifyRelease.configValidator({}));
|
||||
t.false(plugins.verifyRelease.configValidator({path: null}));
|
||||
|
||||
t.true(plugins.verifyRelease.config.validator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.verifyRelease.config.validator());
|
||||
t.true(plugins.verifyRelease.config.validator('plugin-path.js'));
|
||||
t.true(plugins.verifyRelease.config.validator(() => {}));
|
||||
t.true(plugins.verifyRelease.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
t.true(plugins.verifyRelease.configValidator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.verifyRelease.configValidator());
|
||||
t.true(plugins.verifyRelease.configValidator('plugin-path.js'));
|
||||
t.true(plugins.verifyRelease.configValidator(() => {}));
|
||||
t.true(plugins.verifyRelease.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
});
|
||||
|
||||
test('The "generateNotes" plugin, if defined, must be a single plugin definition', t => {
|
||||
t.false(plugins.generateNotes.config.validator({}));
|
||||
t.false(plugins.generateNotes.config.validator({path: null}));
|
||||
t.false(plugins.generateNotes.config.validator([]));
|
||||
test('The "generateNotes" plugin, if defined, must be a single or an array of plugins definition', t => {
|
||||
t.false(plugins.generateNotes.configValidator({}));
|
||||
t.false(plugins.generateNotes.configValidator({path: null}));
|
||||
|
||||
t.true(plugins.generateNotes.config.validator());
|
||||
t.true(plugins.generateNotes.config.validator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.generateNotes.config.validator('plugin-path.js'));
|
||||
t.true(plugins.generateNotes.config.validator(() => {}));
|
||||
t.true(plugins.generateNotes.configValidator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.generateNotes.configValidator());
|
||||
t.true(plugins.generateNotes.configValidator('plugin-path.js'));
|
||||
t.true(plugins.generateNotes.configValidator(() => {}));
|
||||
t.true(plugins.generateNotes.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
});
|
||||
|
||||
test('The "prepare" plugin, if defined, must be a single or an array of plugins definition', t => {
|
||||
t.false(plugins.verifyRelease.config.validator({}));
|
||||
t.false(plugins.verifyRelease.config.validator({path: null}));
|
||||
t.false(plugins.verifyRelease.configValidator({}));
|
||||
t.false(plugins.verifyRelease.configValidator({path: null}));
|
||||
|
||||
t.true(plugins.verifyRelease.config.validator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.verifyRelease.config.validator());
|
||||
t.true(plugins.verifyRelease.config.validator('plugin-path.js'));
|
||||
t.true(plugins.verifyRelease.config.validator(() => {}));
|
||||
t.true(plugins.verifyRelease.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
t.true(plugins.verifyRelease.configValidator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.verifyRelease.configValidator());
|
||||
t.true(plugins.verifyRelease.configValidator('plugin-path.js'));
|
||||
t.true(plugins.verifyRelease.configValidator(() => {}));
|
||||
t.true(plugins.verifyRelease.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
});
|
||||
|
||||
test('The "publish" plugin is mandatory, and must be a single or an array of plugins definition', t => {
|
||||
t.false(plugins.publish.config.validator({}));
|
||||
t.false(plugins.publish.config.validator({path: null}));
|
||||
t.false(plugins.publish.config.validator());
|
||||
t.false(plugins.publish.configValidator({}));
|
||||
t.false(plugins.publish.configValidator({path: null}));
|
||||
|
||||
t.true(plugins.publish.config.validator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.publish.config.validator('plugin-path.js'));
|
||||
t.true(plugins.publish.config.validator(() => {}));
|
||||
t.true(plugins.publish.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
t.true(plugins.publish.configValidator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.publish.configValidator());
|
||||
t.true(plugins.publish.configValidator('plugin-path.js'));
|
||||
t.true(plugins.publish.configValidator(() => {}));
|
||||
t.true(plugins.publish.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
});
|
||||
|
||||
test('The "success" plugin, if defined, must be a single or an array of plugins definition', t => {
|
||||
t.false(plugins.success.config.validator({}));
|
||||
t.false(plugins.success.config.validator({path: null}));
|
||||
t.false(plugins.success.configValidator({}));
|
||||
t.false(plugins.success.configValidator({path: null}));
|
||||
|
||||
t.true(plugins.success.config.validator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.success.config.validator());
|
||||
t.true(plugins.success.config.validator('plugin-path.js'));
|
||||
t.true(plugins.success.config.validator(() => {}));
|
||||
t.true(plugins.success.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
t.true(plugins.success.configValidator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.success.configValidator());
|
||||
t.true(plugins.success.configValidator('plugin-path.js'));
|
||||
t.true(plugins.success.configValidator(() => {}));
|
||||
t.true(plugins.success.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
});
|
||||
|
||||
test('The "fail" plugin, if defined, must be a single or an array of plugins definition', t => {
|
||||
t.false(plugins.fail.config.validator({}));
|
||||
t.false(plugins.fail.config.validator({path: null}));
|
||||
t.false(plugins.fail.configValidator({}));
|
||||
t.false(plugins.fail.configValidator({path: null}));
|
||||
|
||||
t.true(plugins.fail.config.validator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.fail.config.validator());
|
||||
t.true(plugins.fail.config.validator('plugin-path.js'));
|
||||
t.true(plugins.fail.config.validator(() => {}));
|
||||
t.true(plugins.fail.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
t.true(plugins.fail.configValidator({path: 'plugin-path.js'}));
|
||||
t.true(plugins.fail.configValidator());
|
||||
t.true(plugins.fail.configValidator('plugin-path.js'));
|
||||
t.true(plugins.fail.configValidator(() => {}));
|
||||
t.true(plugins.fail.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
|
||||
});
|
||||
|
||||
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', t => {
|
||||
t.false(plugins.analyzeCommits.output.validator('invalid'));
|
||||
t.false(plugins.analyzeCommits.output.validator(1));
|
||||
t.false(plugins.analyzeCommits.output.validator({}));
|
||||
t.false(plugins.analyzeCommits.outputValidator('invalid'));
|
||||
t.false(plugins.analyzeCommits.outputValidator(1));
|
||||
t.false(plugins.analyzeCommits.outputValidator({}));
|
||||
|
||||
t.true(plugins.analyzeCommits.output.validator());
|
||||
t.true(plugins.analyzeCommits.output.validator(null));
|
||||
t.true(plugins.analyzeCommits.output.validator('major'));
|
||||
t.true(plugins.analyzeCommits.outputValidator());
|
||||
t.true(plugins.analyzeCommits.outputValidator(null));
|
||||
t.true(plugins.analyzeCommits.outputValidator('major'));
|
||||
});
|
||||
|
||||
test('The "generateNotes" plugin output, if defined, must be a string', t => {
|
||||
t.false(plugins.generateNotes.output.validator(1));
|
||||
t.false(plugins.generateNotes.output.validator({}));
|
||||
t.false(plugins.generateNotes.outputValidator(1));
|
||||
t.false(plugins.generateNotes.outputValidator({}));
|
||||
|
||||
t.true(plugins.generateNotes.output.validator());
|
||||
t.true(plugins.generateNotes.output.validator(null));
|
||||
t.true(plugins.generateNotes.output.validator(''));
|
||||
t.true(plugins.generateNotes.output.validator('string'));
|
||||
t.true(plugins.generateNotes.outputValidator());
|
||||
t.true(plugins.generateNotes.outputValidator(null));
|
||||
t.true(plugins.generateNotes.outputValidator(''));
|
||||
t.true(plugins.generateNotes.outputValidator('string'));
|
||||
});
|
||||
|
||||
test('The "publish" plugin output, if defined, must be an object', t => {
|
||||
t.false(plugins.publish.output.validator(1));
|
||||
t.false(plugins.publish.output.validator('string'));
|
||||
t.false(plugins.publish.outputValidator(1));
|
||||
t.false(plugins.publish.outputValidator('string'));
|
||||
|
||||
t.true(plugins.publish.output.validator({}));
|
||||
t.true(plugins.publish.output.validator());
|
||||
t.true(plugins.publish.output.validator(null));
|
||||
t.true(plugins.publish.output.validator(''));
|
||||
t.true(plugins.publish.outputValidator({}));
|
||||
t.true(plugins.publish.outputValidator());
|
||||
t.true(plugins.publish.outputValidator(null));
|
||||
t.true(plugins.publish.outputValidator(''));
|
||||
});
|
||||
|
||||
test('The "analyzeCommits" plugin output definition return an existing error code', t => {
|
||||
t.true(Object.keys(errors).includes(plugins.analyzeCommits.output.error));
|
||||
});
|
||||
|
||||
test('The "generateNotes" plugin output definition return an existing error code', t => {
|
||||
t.true(Object.keys(errors).includes(plugins.generateNotes.output.error));
|
||||
});
|
||||
|
||||
test('The "publish" plugin output definition return an existing error code', t => {
|
||||
t.true(Object.keys(errors).includes(plugins.publish.output.error));
|
||||
test('The "generateNotes" plugins output are concatenated with separator', t => {
|
||||
t.is(plugins.generateNotes.postprocess(['note 1', 'note 2']), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
||||
t.is(plugins.generateNotes.postprocess(['', 'note']), 'note');
|
||||
t.is(plugins.generateNotes.postprocess([undefined, 'note']), 'note');
|
||||
t.is(plugins.generateNotes.postprocess(['note 1', '', 'note 2']), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
||||
t.is(plugins.generateNotes.postprocess(['note 1', undefined, 'note 2']), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
||||
});
|
||||
|
||||
+32
-11
@@ -3,7 +3,7 @@ import tempy from 'tempy';
|
||||
import {
|
||||
gitTagHead,
|
||||
isRefInHistory,
|
||||
unshallow,
|
||||
fetch,
|
||||
gitHead,
|
||||
repoUrl,
|
||||
tag,
|
||||
@@ -23,8 +23,8 @@ import {
|
||||
gitAddConfig,
|
||||
gitCommitTag,
|
||||
gitRemoteTagHead,
|
||||
push as pushUtil,
|
||||
reset,
|
||||
gitPush,
|
||||
gitDetachedHead,
|
||||
} from './helpers/git-utils';
|
||||
|
||||
// Save the current working diretory
|
||||
@@ -53,7 +53,7 @@ test.serial('Throw error if the last commit sha cannot be found', async t => {
|
||||
await t.throws(gitHead(), Error);
|
||||
});
|
||||
|
||||
test.serial('Unshallow repository', async t => {
|
||||
test.serial('Unshallow and fetch 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
|
||||
@@ -64,7 +64,7 @@ test.serial('Unshallow repository', async t => {
|
||||
// Verify the shallow clone contains only one commit
|
||||
t.is((await gitGetCommits()).length, 1);
|
||||
|
||||
await unshallow(repo);
|
||||
await fetch(repo);
|
||||
|
||||
// Verify the shallow clone contains all the commits
|
||||
t.is((await gitGetCommits()).length, 2);
|
||||
@@ -75,7 +75,24 @@ test.serial('Do not throw error when unshallow a complete repository', async t =
|
||||
const repo = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First']);
|
||||
await t.notThrows(unshallow(repo));
|
||||
await t.notThrows(fetch(repo));
|
||||
});
|
||||
|
||||
test.serial('Fetch all tags on a detached head repository', async t => {
|
||||
const repo = await gitRepo(true);
|
||||
|
||||
await gitCommits(['First']);
|
||||
await gitTagVersion('v1.0.0');
|
||||
await gitCommits(['Second']);
|
||||
await gitTagVersion('v1.0.1');
|
||||
const [commit] = await gitCommits(['Third']);
|
||||
await gitTagVersion('v1.1.0');
|
||||
await gitPush();
|
||||
await gitDetachedHead(repo, commit.hash);
|
||||
|
||||
await fetch(repo);
|
||||
|
||||
t.deepEqual((await gitTags()).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort());
|
||||
});
|
||||
|
||||
test.serial('Verify if the commit `sha` is in the direct history of the current branch', async t => {
|
||||
@@ -191,20 +208,24 @@ test.serial('Throws error if obtaining the tags fails', async t => {
|
||||
test.serial('Return "true" if repository is up to date', async t => {
|
||||
await gitRepo(true);
|
||||
await gitCommits(['First']);
|
||||
await pushUtil();
|
||||
await gitPush();
|
||||
|
||||
t.true(await isBranchUpToDate('master'));
|
||||
});
|
||||
|
||||
test.serial('Return falsy if repository is not up to date', async t => {
|
||||
await gitRepo(true);
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
const repoDir = process.cwd();
|
||||
await gitCommits(['First']);
|
||||
await gitCommits(['Second']);
|
||||
await pushUtil();
|
||||
await gitPush();
|
||||
|
||||
t.true(await isBranchUpToDate('master'));
|
||||
|
||||
await reset();
|
||||
await gitShallowClone(repositoryUrl);
|
||||
await gitCommits(['Third']);
|
||||
await gitPush();
|
||||
process.chdir(repoDir);
|
||||
|
||||
t.falsy(await isBranchUpToDate('master'));
|
||||
});
|
||||
@@ -212,7 +233,7 @@ test.serial('Return falsy if repository is not up to date', async t => {
|
||||
test.serial('Return "true" if local repository is ahead', async t => {
|
||||
await gitRepo(true);
|
||||
await gitCommits(['First']);
|
||||
await pushUtil();
|
||||
await gitPush();
|
||||
await gitCommits(['Second']);
|
||||
|
||||
t.true(await isBranchUpToDate('master'));
|
||||
|
||||
@@ -151,7 +151,7 @@ export async function gitDetachedHead(repositoryUrl, head) {
|
||||
process.chdir(dir);
|
||||
await execa('git', ['init']);
|
||||
await execa('git', ['remote', 'add', 'origin', repositoryUrl]);
|
||||
await execa('git', ['fetch']);
|
||||
await execa('git', ['fetch', repositoryUrl]);
|
||||
await execa('git', ['checkout', head]);
|
||||
return dir;
|
||||
}
|
||||
@@ -209,15 +209,6 @@ export async function gitCommitTag(gitHead) {
|
||||
* @param {String} branch The branch to push.
|
||||
* @throws {Error} if the push failed.
|
||||
*/
|
||||
export async function push(repositoryUrl = 'origin', branch = 'master') {
|
||||
export async function gitPush(repositoryUrl = 'origin', branch = 'master') {
|
||||
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset repository to a commit.
|
||||
*
|
||||
* @param {String} [commit='HEAD~1'] Commit reference to reset the repo to.
|
||||
*/
|
||||
export async function reset(commit = 'HEAD~1') {
|
||||
await execa('git', ['reset', commit]);
|
||||
}
|
||||
|
||||
+113
-53
@@ -4,7 +4,6 @@ import {spy, stub} from 'sinon';
|
||||
import clearModule from 'clear-module';
|
||||
import AggregateError from 'aggregate-error';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
import DEFINITIONS from '../lib/definitions/plugins';
|
||||
import {COMMIT_NAME, COMMIT_EMAIL} from '../lib/definitions/constants';
|
||||
import {
|
||||
gitHead as getGitHead,
|
||||
@@ -13,9 +12,8 @@ import {
|
||||
gitCommits,
|
||||
gitTagVersion,
|
||||
gitRemoteTagHead,
|
||||
push,
|
||||
gitPush,
|
||||
gitShallowClone,
|
||||
reset,
|
||||
} from './helpers/git-utils';
|
||||
|
||||
// Save the current process.env
|
||||
@@ -59,16 +57,20 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second'])).concat(commits);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
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 notes1 = 'Release notes 1';
|
||||
const notes2 = 'Release notes 2';
|
||||
const notes3 = 'Release notes 3';
|
||||
const verifyConditions1 = stub().resolves();
|
||||
const verifyConditions2 = stub().resolves();
|
||||
const analyzeCommits = stub().resolves(nextRelease.type);
|
||||
const verifyRelease = stub().resolves();
|
||||
const generateNotes = stub().resolves(notes);
|
||||
const generateNotes1 = stub().resolves(notes1);
|
||||
const generateNotes2 = stub().resolves(notes2);
|
||||
const generateNotes3 = stub().resolves(notes3);
|
||||
const release1 = {name: 'Release 1', url: 'https://release1.com'};
|
||||
const prepare = stub().resolves();
|
||||
const publish1 = stub().resolves(release1);
|
||||
@@ -80,7 +82,7 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
verifyConditions: [verifyConditions1, verifyConditions2],
|
||||
analyzeCommits,
|
||||
verifyRelease,
|
||||
generateNotes,
|
||||
generateNotes: [generateNotes1, generateNotes2, generateNotes3],
|
||||
prepare,
|
||||
publish: [publish1, pluginNoop],
|
||||
success,
|
||||
@@ -115,14 +117,32 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(verifyRelease.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(verifyRelease.args[0][1].nextRelease, nextRelease);
|
||||
|
||||
t.is(generateNotes.callCount, 1);
|
||||
t.deepEqual(generateNotes.args[0][0], config);
|
||||
t.deepEqual(generateNotes.args[0][1].options, options);
|
||||
t.deepEqual(generateNotes.args[0][1].logger, t.context.logger);
|
||||
t.deepEqual(generateNotes.args[0][1].lastRelease, lastRelease);
|
||||
t.deepEqual(generateNotes.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(generateNotes.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease);
|
||||
t.is(generateNotes1.callCount, 1);
|
||||
t.deepEqual(generateNotes1.args[0][0], config);
|
||||
t.deepEqual(generateNotes1.args[0][1].options, options);
|
||||
t.deepEqual(generateNotes1.args[0][1].logger, t.context.logger);
|
||||
t.deepEqual(generateNotes1.args[0][1].lastRelease, lastRelease);
|
||||
t.deepEqual(generateNotes1.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(generateNotes1.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(generateNotes1.args[0][1].nextRelease, nextRelease);
|
||||
|
||||
t.is(generateNotes2.callCount, 1);
|
||||
t.deepEqual(generateNotes2.args[0][0], config);
|
||||
t.deepEqual(generateNotes2.args[0][1].options, options);
|
||||
t.deepEqual(generateNotes2.args[0][1].logger, t.context.logger);
|
||||
t.deepEqual(generateNotes2.args[0][1].lastRelease, lastRelease);
|
||||
t.deepEqual(generateNotes2.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(generateNotes2.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(generateNotes2.args[0][1].nextRelease, {...nextRelease, notes: notes1});
|
||||
|
||||
t.is(generateNotes3.callCount, 1);
|
||||
t.deepEqual(generateNotes3.args[0][0], config);
|
||||
t.deepEqual(generateNotes3.args[0][1].options, options);
|
||||
t.deepEqual(generateNotes3.args[0][1].logger, t.context.logger);
|
||||
t.deepEqual(generateNotes3.args[0][1].lastRelease, lastRelease);
|
||||
t.deepEqual(generateNotes3.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(generateNotes3.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(generateNotes3.args[0][1].nextRelease, {...nextRelease, notes: `${notes1}\n\n${notes2}`});
|
||||
|
||||
t.is(prepare.callCount, 1);
|
||||
t.deepEqual(prepare.args[0][0], config);
|
||||
@@ -131,7 +151,7 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(prepare.args[0][1].lastRelease, lastRelease);
|
||||
t.deepEqual(prepare.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(prepare.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(prepare.args[0][1].nextRelease, {...nextRelease, ...{notes}});
|
||||
t.deepEqual(prepare.args[0][1].nextRelease, {...nextRelease, notes: `${notes1}\n\n${notes2}\n\n${notes3}`});
|
||||
|
||||
t.is(publish1.callCount, 1);
|
||||
t.deepEqual(publish1.args[0][0], config);
|
||||
@@ -140,7 +160,7 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(publish1.args[0][1].lastRelease, lastRelease);
|
||||
t.deepEqual(publish1.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(publish1.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(publish1.args[0][1].nextRelease, {...nextRelease, ...{notes}});
|
||||
t.deepEqual(publish1.args[0][1].nextRelease, {...nextRelease, notes: `${notes1}\n\n${notes2}\n\n${notes3}`});
|
||||
|
||||
t.is(success.callCount, 1);
|
||||
t.deepEqual(success.args[0][0], config);
|
||||
@@ -149,10 +169,10 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(success.args[0][1].lastRelease, lastRelease);
|
||||
t.deepEqual(success.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(success.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(success.args[0][1].nextRelease, {...nextRelease, ...{notes}});
|
||||
t.deepEqual(success.args[0][1].nextRelease, {...nextRelease, notes: `${notes1}\n\n${notes2}\n\n${notes3}`});
|
||||
t.deepEqual(success.args[0][1].releases, [
|
||||
{...release1, ...nextRelease, ...{notes}, ...{pluginName: '[Function: proxy]'}},
|
||||
{...nextRelease, ...{notes}, ...{pluginName: pluginNoop}},
|
||||
{...release1, ...nextRelease, notes: `${notes1}\n\n${notes2}\n\n${notes3}`, pluginName: '[Function: proxy]'},
|
||||
{...nextRelease, notes: `${notes1}\n\n${notes2}\n\n${notes3}`, pluginName: pluginNoop},
|
||||
]);
|
||||
|
||||
// Verify the tag has been created on the local and remote repo and reference the gitHead
|
||||
@@ -171,7 +191,7 @@ test.serial('Use custom tag format', async t => {
|
||||
await gitCommits(['First']);
|
||||
await gitTagVersion('test-1.0.0');
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'test-2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -208,7 +228,7 @@ test.serial('Use new gitHead, and recreate release notes if a prepare plugin cre
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second'])).concat(commits);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -243,16 +263,16 @@ test.serial('Use new gitHead, and recreate release notes if a prepare plugin cre
|
||||
t.is(generateNotes.callCount, 2);
|
||||
t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease);
|
||||
t.is(prepare1.callCount, 1);
|
||||
t.deepEqual(prepare1.args[0][1].nextRelease, {...nextRelease, ...{notes}});
|
||||
t.deepEqual(prepare1.args[0][1].nextRelease, {...nextRelease, notes});
|
||||
|
||||
nextRelease.gitHead = await getGitHead();
|
||||
|
||||
t.deepEqual(generateNotes.args[1][1].nextRelease, {...nextRelease, ...{notes}});
|
||||
t.deepEqual(generateNotes.args[1][1].nextRelease, {...nextRelease, notes});
|
||||
t.is(prepare2.callCount, 1);
|
||||
t.deepEqual(prepare2.args[0][1].nextRelease, {...nextRelease, ...{notes}});
|
||||
t.deepEqual(prepare2.args[0][1].nextRelease, {...nextRelease, notes});
|
||||
|
||||
t.is(publish.callCount, 1);
|
||||
t.deepEqual(publish.args[0][1].nextRelease, {...nextRelease, ...{notes}});
|
||||
t.deepEqual(publish.args[0][1].nextRelease, {...nextRelease, notes});
|
||||
|
||||
// Verify the tag has been created on the local and remote repo and reference the last gitHead
|
||||
t.is(await gitTagHead(nextRelease.gitTag), commits[0].hash);
|
||||
@@ -268,7 +288,7 @@ test.serial('Call all "success" plugins even if one errors out', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -301,14 +321,10 @@ test.serial('Call all "success" plugins even if one errors out', async t => {
|
||||
|
||||
t.is(success1.callCount, 1);
|
||||
t.deepEqual(success1.args[0][0], config);
|
||||
t.deepEqual(success1.args[0][1].releases, [
|
||||
{...release, ...nextRelease, ...{notes}, ...{pluginName: '[Function: proxy]'}},
|
||||
]);
|
||||
t.deepEqual(success1.args[0][1].releases, [{...release, ...nextRelease, notes, pluginName: '[Function: proxy]'}]);
|
||||
|
||||
t.is(success2.callCount, 1);
|
||||
t.deepEqual(success2.args[0][1].releases, [
|
||||
{...release, ...nextRelease, ...{notes}, ...{pluginName: '[Function: proxy]'}},
|
||||
]);
|
||||
t.deepEqual(success2.args[0][1].releases, [{...release, ...nextRelease, notes, pluginName: '[Function: proxy]'}]);
|
||||
});
|
||||
|
||||
test.serial('Log all "verifyConditions" errors', async t => {
|
||||
@@ -316,7 +332,7 @@ test.serial('Log all "verifyConditions" errors', async t => {
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const error1 = new Error('error 1');
|
||||
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
||||
@@ -359,7 +375,7 @@ test.serial('Log all "verifyRelease" errors', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const error1 = new SemanticReleaseError('error 1', 'ERR1');
|
||||
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
||||
@@ -396,7 +412,7 @@ test.serial('Dry-run skips publish and success', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -445,7 +461,7 @@ test.serial('Dry-run skips fail', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const error1 = new SemanticReleaseError('error 1', 'ERR1');
|
||||
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
||||
@@ -480,7 +496,7 @@ test.serial('Force a dry-run if not on a CI and "noCi" is not explicitly set', a
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -521,6 +537,43 @@ test.serial('Force a dry-run if not on a CI and "noCi" is not explicitly set', a
|
||||
t.is(success.callCount, 0);
|
||||
});
|
||||
|
||||
test.serial('Dry-run does not print changelog if "generateNotes" return "undefined"', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await gitPush();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const analyzeCommits = stub().resolves(nextRelease.type);
|
||||
const generateNotes = stub().resolves();
|
||||
|
||||
const options = {
|
||||
dryRun: true,
|
||||
branch: 'master',
|
||||
repositoryUrl,
|
||||
verifyConditions: false,
|
||||
analyzeCommits,
|
||||
verifyRelease: false,
|
||||
generateNotes,
|
||||
prepare: false,
|
||||
publish: false,
|
||||
success: false,
|
||||
};
|
||||
|
||||
const semanticRelease = proxyquire('..', {
|
||||
'./lib/logger': t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
t.truthy(await semanticRelease(options));
|
||||
|
||||
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['Release note for version %s:\n', '2.0.0']);
|
||||
});
|
||||
|
||||
test.serial('Allow local releases with "noCi" option', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
@@ -530,7 +583,7 @@ test.serial('Allow local releases with "noCi" option', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -584,13 +637,15 @@ test.serial('Accept "undefined" value returned by the "generateNotes" plugins',
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second'])).concat(commits);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
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 analyzeCommits = stub().resolves(nextRelease.type);
|
||||
const verifyRelease = stub().resolves();
|
||||
const generateNotes = stub().resolves();
|
||||
const generateNotes1 = stub().resolves();
|
||||
const notes2 = 'Release notes 2';
|
||||
const generateNotes2 = stub().resolves(notes2);
|
||||
const publish = stub().resolves();
|
||||
|
||||
const options = {
|
||||
@@ -599,7 +654,7 @@ test.serial('Accept "undefined" value returned by the "generateNotes" plugins',
|
||||
verifyConditions: stub().resolves(),
|
||||
analyzeCommits,
|
||||
verifyRelease,
|
||||
generateNotes,
|
||||
generateNotes: [generateNotes1, generateNotes2],
|
||||
prepare: stub().resolves(),
|
||||
publish,
|
||||
success: stub().resolves(),
|
||||
@@ -618,12 +673,15 @@ test.serial('Accept "undefined" value returned by the "generateNotes" plugins',
|
||||
t.is(verifyRelease.callCount, 1);
|
||||
t.deepEqual(verifyRelease.args[0][1].lastRelease, lastRelease);
|
||||
|
||||
t.is(generateNotes.callCount, 1);
|
||||
t.deepEqual(generateNotes.args[0][1].lastRelease, lastRelease);
|
||||
t.is(generateNotes1.callCount, 1);
|
||||
t.deepEqual(generateNotes1.args[0][1].lastRelease, lastRelease);
|
||||
|
||||
t.is(generateNotes2.callCount, 1);
|
||||
t.deepEqual(generateNotes2.args[0][1].lastRelease, lastRelease);
|
||||
|
||||
t.is(publish.callCount, 1);
|
||||
t.deepEqual(publish.args[0][1].lastRelease, lastRelease);
|
||||
t.falsy(publish.args[0][1].nextRelease.notes);
|
||||
t.is(publish.args[0][1].nextRelease.notes, notes2);
|
||||
});
|
||||
|
||||
test.serial('Returns falsy value if triggered by a PR', async t => {
|
||||
@@ -645,11 +703,15 @@ test.serial('Returns falsy value if triggered by a PR', async t => {
|
||||
test.serial('Returns falsy value if triggered on an outdated clone', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
const repoDir = process.cwd();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First']);
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await reset();
|
||||
await gitPush();
|
||||
await gitShallowClone(repositoryUrl);
|
||||
await gitCommits(['Third']);
|
||||
await gitPush();
|
||||
process.chdir(repoDir);
|
||||
|
||||
const semanticRelease = proxyquire('..', {
|
||||
'./lib/logger': t.context.logger,
|
||||
@@ -696,7 +758,7 @@ test.serial('Returns falsy value if there is no relevant changes', async t => {
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const analyzeCommits = stub().resolves();
|
||||
const verifyRelease = stub().resolves();
|
||||
@@ -746,7 +808,7 @@ test.serial('Exclude commits with [skip release] or [release skip] from analysis
|
||||
'Test commit\n\n commit body\n[skip release]',
|
||||
'Test commit\n\n commit body\n[release skip]',
|
||||
]);
|
||||
await push();
|
||||
await gitPush();
|
||||
const analyzeCommits = stub().resolves();
|
||||
const config = {branch: 'master', repositoryUrl, globalOpt: 'global'};
|
||||
const options = {
|
||||
@@ -871,7 +933,7 @@ test.serial('Throw an Error if plugin returns an unexpected value', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await gitPush();
|
||||
|
||||
const verifyConditions = stub().resolves();
|
||||
const analyzeCommits = stub().resolves('string');
|
||||
@@ -891,8 +953,6 @@ test.serial('Throw an Error if plugin returns an unexpected value', async t => {
|
||||
});
|
||||
const error = await t.throws(semanticRelease(options), Error);
|
||||
|
||||
// Verify error message
|
||||
t.regex(error.message, new RegExp(DEFINITIONS.analyzeCommits.output.message));
|
||||
t.regex(error.details, /string/);
|
||||
});
|
||||
|
||||
@@ -900,7 +960,7 @@ test.serial('Get all commits including the ones not in the shallow clone', async
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
await gitTagVersion('v1.0.0');
|
||||
await gitCommits(['First', 'Second', 'Third']);
|
||||
await push(repositoryUrl, 'master');
|
||||
await gitPush(repositoryUrl, 'master');
|
||||
|
||||
await gitShallowClone(repositoryUrl);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import test from 'ava';
|
||||
import {writeJson, readJson} from 'fs-extra';
|
||||
import {stub} from 'sinon';
|
||||
import execa from 'execa';
|
||||
import {gitHead as getGitHead, gitTagHead, gitRepo, gitCommits, gitRemoteTagHead} from './helpers/git-utils';
|
||||
import {gitHead as getGitHead, gitTagHead, gitRepo, gitCommits, gitRemoteTagHead, gitPush} from './helpers/git-utils';
|
||||
import gitbox from './helpers/gitbox';
|
||||
import mockServer from './helpers/mockserver';
|
||||
import npmRegistry from './helpers/npm-registry';
|
||||
@@ -609,11 +609,12 @@ test.serial('Exit with 1 if missing permission to push to the remote repository'
|
||||
/* Initial release */
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
await gitPush();
|
||||
t.log('$ semantic-release');
|
||||
const {stdout, code} = await execa(
|
||||
cli,
|
||||
['--repository-url', 'http://user:wrong_pass@localhost:2080/git/unauthorized.git'],
|
||||
{env: {...env, ...{GH_TOKEN: 'user:wrong_pass'}}, reject: false}
|
||||
{env: {...env, GH_TOKEN: 'user:wrong_pass'}, reject: false}
|
||||
);
|
||||
// Verify the type and message are logged
|
||||
t.regex(stdout, /EGITNOPERMISSION/);
|
||||
|
||||
@@ -94,8 +94,10 @@ test('Wrap "analyzeCommits" plugin in a function that validate the output of the
|
||||
|
||||
const error = await t.throws(plugin());
|
||||
|
||||
t.is(error.code, 'EANALYZEOUTPUT');
|
||||
t.is(error.code, 'EANALYZECOMMITSOUTPUT');
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
t.truthy(error.message);
|
||||
t.truthy(error.details);
|
||||
t.regex(error.details, /2/);
|
||||
});
|
||||
|
||||
@@ -105,8 +107,10 @@ test('Wrap "generateNotes" plugin in a function that validate the output of the
|
||||
|
||||
const error = await t.throws(plugin());
|
||||
|
||||
t.is(error.code, 'ERELEASENOTESOUTPUT');
|
||||
t.is(error.code, 'EGENERATENOTESOUTPUT');
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
t.truthy(error.message);
|
||||
t.truthy(error.details);
|
||||
t.regex(error.details, /2/);
|
||||
});
|
||||
|
||||
@@ -123,6 +127,8 @@ test('Wrap "publish" plugin in a function that validate the output of the plugin
|
||||
|
||||
t.is(error.code, 'EPUBLISHOUTPUT');
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
t.truthy(error.message);
|
||||
t.truthy(error.details);
|
||||
t.regex(error.details, /2/);
|
||||
});
|
||||
|
||||
@@ -187,6 +193,8 @@ test('Throws an error if the plugin return an object without the expected plugin
|
||||
|
||||
t.is(error.code, 'EPLUGIN');
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
t.truthy(error.message);
|
||||
t.truthy(error.details);
|
||||
});
|
||||
|
||||
test('Throws an error if the plugin is not found', t => {
|
||||
|
||||
@@ -18,24 +18,6 @@ test('Execute each function in series passing the same input', async t => {
|
||||
t.true(step2.calledBefore(step3));
|
||||
});
|
||||
|
||||
test('With one step, returns the step values rather than an Array ', async t => {
|
||||
const step1 = stub().resolves(1);
|
||||
|
||||
const result = await pipeline([step1])(0);
|
||||
|
||||
t.deepEqual(result, 1);
|
||||
t.true(step1.calledWith(0));
|
||||
});
|
||||
|
||||
test('With one step, throws the error rather than an AggregateError ', async t => {
|
||||
const error = new Error('test error 1');
|
||||
const step1 = stub().rejects(error);
|
||||
|
||||
const thrown = await t.throws(pipeline([step1])(0));
|
||||
|
||||
t.is(error, thrown);
|
||||
});
|
||||
|
||||
test('Execute each function in series passing a transformed input from "getNextInput"', async t => {
|
||||
const step1 = stub().resolves(1);
|
||||
const step2 = stub().resolves(2);
|
||||
@@ -43,7 +25,7 @@ test('Execute each function in series passing a transformed input from "getNextI
|
||||
const step4 = stub().resolves(4);
|
||||
const getNextInput = (lastResult, result) => lastResult + result;
|
||||
|
||||
const result = await pipeline([step1, step2, step3, step4])(0, {settleAll: false, getNextInput});
|
||||
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(0);
|
||||
|
||||
t.deepEqual(result, [1, 2, 3, 4]);
|
||||
t.true(step1.calledWith(0));
|
||||
@@ -62,7 +44,7 @@ test('Execute each function in series passing the "lastResult" and "result" to "
|
||||
const step4 = stub().resolves(4);
|
||||
const getNextInput = stub().returnsArg(0);
|
||||
|
||||
const result = await pipeline([step1, step2, step3, step4])(5, {settleAll: false, getNextInput});
|
||||
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(5);
|
||||
|
||||
t.deepEqual(result, [1, 2, 3, 4]);
|
||||
t.deepEqual(getNextInput.args, [[5, 1], [5, 2], [5, 3], [5, 4]]);
|
||||
@@ -76,7 +58,7 @@ test('Execute each function in series calling "transform" to modify the results'
|
||||
const getNextInput = stub().returnsArg(0);
|
||||
const transform = stub().callsFake(result => result + 1);
|
||||
|
||||
const result = await pipeline([step1, step2, step3, step4])(5, {getNextInput, transform});
|
||||
const result = await pipeline([step1, step2, step3, step4], {getNextInput, transform})(5);
|
||||
|
||||
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
|
||||
t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]);
|
||||
@@ -90,13 +72,13 @@ test('Execute each function in series calling "transform" to modify the results
|
||||
const getNextInput = stub().returnsArg(0);
|
||||
const transform = stub().callsFake(result => result + 1);
|
||||
|
||||
const result = await pipeline([step1, step2, step3, step4])(5, {settleAll: true, getNextInput, transform});
|
||||
const result = await pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput, transform})(5);
|
||||
|
||||
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
|
||||
t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]);
|
||||
});
|
||||
|
||||
test('Stop execution and throw error is a step rejects', async t => {
|
||||
test('Stop execution and throw error if a step rejects', async t => {
|
||||
const step1 = stub().resolves(1);
|
||||
const step2 = stub().rejects(new Error('test error'));
|
||||
const step3 = stub().resolves(3);
|
||||
@@ -131,7 +113,7 @@ test('Execute all even if a Promise rejects', async t => {
|
||||
const step2 = stub().rejects(error1);
|
||||
const step3 = stub().rejects(error2);
|
||||
|
||||
const errors = await t.throws(pipeline([step1, step2, step3])(0, {settleAll: true}));
|
||||
const errors = await t.throws(pipeline([step1, step2, step3], {settleAll: true})(0));
|
||||
|
||||
t.deepEqual([...errors], [error1, error2]);
|
||||
t.true(step1.calledWith(0));
|
||||
@@ -147,7 +129,7 @@ test('Throw all errors from all steps throwing an AggregateError', async t => {
|
||||
const step1 = stub().rejects(new AggregateError([error1, error2]));
|
||||
const step2 = stub().rejects(new AggregateError([error3, error4]));
|
||||
|
||||
const errors = await t.throws(pipeline([step1, step2])(0, {settleAll: true}));
|
||||
const errors = await t.throws(pipeline([step1, step2], {settleAll: true})(0));
|
||||
|
||||
t.deepEqual([...errors], [error1, error2, error3, error4]);
|
||||
t.true(step1.calledWith(0));
|
||||
@@ -163,7 +145,7 @@ test('Execute each function in series passing a transformed input even if a step
|
||||
const step4 = stub().resolves(4);
|
||||
const getNextInput = (prevResult, result) => prevResult + result;
|
||||
|
||||
const errors = await t.throws(pipeline([step1, step2, step3, step4])(0, {settleAll: true, getNextInput}));
|
||||
const errors = await t.throws(pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput})(0));
|
||||
|
||||
t.deepEqual([...errors], [error2, error3]);
|
||||
t.true(step1.calledWith(0));
|
||||
|
||||
@@ -116,11 +116,22 @@ test.serial('Export plugins loaded from the dependency of a shareable config fil
|
||||
});
|
||||
|
||||
test('Use default when only options are passed for a single plugin', t => {
|
||||
const plugins = getPlugins({generateNotes: {}, analyzeCommits: {}}, {}, t.context.logger);
|
||||
const analyzeCommits = {};
|
||||
const generateNotes = {};
|
||||
const success = () => {};
|
||||
const fail = [() => {}];
|
||||
|
||||
const plugins = getPlugins({analyzeCommits, generateNotes, success, fail}, {}, t.context.logger);
|
||||
|
||||
// Verify the module returns a function for each plugin
|
||||
t.is(typeof plugins.generateNotes, 'function');
|
||||
t.is(typeof plugins.analyzeCommits, 'function');
|
||||
t.is(typeof plugins.generateNotes, 'function');
|
||||
t.is(typeof plugins.success, 'function');
|
||||
t.is(typeof plugins.fail, 'function');
|
||||
|
||||
// Verify only the plugins defined as an object with no `path` are set to the default value
|
||||
t.falsy(success.path);
|
||||
t.falsy(fail.path);
|
||||
});
|
||||
|
||||
test('Merge global options with plugin options', async t => {
|
||||
@@ -134,7 +145,7 @@ test('Merge global options with plugin options', async t => {
|
||||
t.context.logger
|
||||
);
|
||||
|
||||
const result = await plugins.verifyRelease();
|
||||
const [result] = await plugins.verifyRelease();
|
||||
|
||||
t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user