Compare commits

...
54 Commits
Author SHA1 Message Date
Dominykas BlyžėandGitHub 21c151f167 fix: normalize ci: false into noCi: true after configs get merged (#1732) thanks @dominykas
This makes sure that options.ci is respected even when set inside a shareable config
2021-01-13 07:38:57 -08:00
Max DuvalandGitHub da75a9c60a docs: clean and strip SVG logo (#1723) thanks @mxdvl 2021-01-04 10:48:15 -08:00
cherryblossom000andGitHub e211499735 docs(plugins): document addChannel step (#1721)
Document the `addChannel` step defined in
https://github.com/semantic-release/semantic-release/blob/e35e5bb73f3628906553709ac0f5beb8f565c61a/lib/definitions/plugins.js#L83-L84,
which is used in @semantic-release/npm.
2020-12-29 12:33:12 -08:00
Ryan LingandGitHub 52238cbccc fix(deps): Require find-versions ^4.0.0 (#1722)
This new version includes a fix for a ReDoS vulnerability in
`semver-regex` that is flagged by some source composition analysis tools
like Snyk:

https://snyk.io/vuln/SNYK-JS-SEMVERREGEX-1047770

It's a major version because it drops support for Node.js 6.x. This
doesn't affect us since we already require Node.js >=10.18.
2020-12-29 12:15:11 -08:00
Jonas BromsandGitHub af596a9443 docs: semantic-release SVG logo (#1715) thanks @bromso
I recreated the logo and used this image as a reference: https://gblobscdn.gitbook.com/spaces%2F-LGsE7zdvzHI5cG-XV6p%2Favatar.png
2020-12-16 14:00:30 -08:00
Mihai PlasoianuandGitHub 6c7e4be68a docs: add semantic-release-helm plugin (#1713) 2020-12-09 09:22:32 -08:00
abichingerandGitHub c177d4b15a docs: add semantic-release-pypi plugin (#1707) 2020-12-02 09:43:00 -08:00
Geoffroy EmpainandGitHub eb708236df docs: add semantic-release-license-plugin (#1701)
This adds a semantic-release-plugin which we use to update our license files (copyright dates and more) based on their type.
2020-11-25 09:25:48 -08:00
AlmenonandGitHub 885d87ac0f feat(docs): note that publish token is required (#1700)
see https://github.com/semantic-release/npm/issues/277
2020-11-22 10:38:15 -08:00
Dmitriy DekhanovandGitHub f8f8fbcac4 fix: escape uri encoded symbols (#1697) 2020-11-19 11:55:09 -08:00
Matt TraviandGitHub c8d38b6258 style: removed line breaks to align with xo rule (#1689) 2020-11-16 11:32:04 -08:00
Matt TraviandMatt Travi ca90b34c4a fix: mask secrets when characters get uri encoded 2020-11-16 10:36:40 -06:00
Amanda MitchellandGitHub 63fa143023 docs(plugins): add listing for new plugin (#1686) 2020-11-12 14:16:15 -08:00
Arthur ChaloinandGitHub 2bf377194e fix: use valid git credentials when multiple are provided (#1669) 2020-10-29 11:09:19 -07:00
Arthur ChaloinandGitHub 77a75f072b fix: don't parse port as part of the path in repository URLs (#1671) 2020-10-28 15:53:03 -07:00
Guillaume GautreauandGitHub d74ffef788 docs: add npm-deprecate-old-versions in plugins list (#1667) 2020-10-23 14:26:20 -07:00
Matt Travi 3abcbaf256 Revert "feat: throw an Error if package.json has duplicate "repository" key (#1656)"
This reverts commit b8fb35c7e1.

fixes #1657
2020-10-12 08:21:24 -05:00
Gregor MartynusandGitHub b8fb35c7e1 feat: throw an Error if package.json has duplicate "repository" key (#1656) 2020-10-11 14:59:51 -07:00
Graeme CasselsandGitHub 18e35b28ad docs: reorder default plugins list (#1650)
Reorder the list default plugins from alphabetical to order of execution.
2020-10-04 11:52:17 -07:00
Micael JarniacandGitHub e35e5bb73f docs(contributing): fix commit message examples (#1648)
It had the backticks (`\``) around the messages, but I think they were unintentional, probably a leftover from copy-pasting.
2020-10-02 15:12:20 -07:00
Gregor MartynusandGitHub 311c46580f docs(README): welcome @travi, add alumni section 2020-09-25 15:31:11 -07:00
b4c5d0a436 fix: add logging for when ssh falls back to http (#1639)
Co-authored-by: thomas williams <thomas.williams@thetradedesk.com>
2020-09-17 10:15:04 -07:00
Micael JarniacandGitHub c982249f87 docs(contributing): typo fix (#1638) 2020-09-16 11:39:04 -07:00
Joep KockelkornandGitHub 9635f50da6 docs: improve github actions recipe on git plugin (#1626) 2020-09-06 10:38:29 -07:00
ylemkimonandGitHub d036a899d5 ci(docs): use actions/checkout@v2 (#1620) 2020-08-17 16:26:13 -07:00
AbdelRahman WahdanandGitHub 9303d1dba0 docs(resources.md): added more sematnic release article (#1610) 2020-07-30 17:17:36 -07:00
kopalandGitHub b72cdb331b docs(configuration.md): Updated documentation for dry-run feature of semantic Release (#1607) 2020-07-29 15:25:32 -07:00
Ahmed HassaneinandGitHub ee44ee80c8 docs(github-actions): suggest action_dispatch as trigger (#1605) 2020-07-28 06:57:05 -07:00
Rylan CollinsandGitHub b24d2474b1 docs: add semantic-release-rubygem to community plugins (#1602) 2020-07-22 16:35:05 -07:00
Gavin StaniforthandGitHub 6d118c6c14 docs: be clear about what module of semantic-release handles updating the package.json (#1601) 2020-07-12 12:31:40 -07:00
Shun KakinokiandMatt Travi b5c9dea215 docs: update github documentation to docs.github.com 2020-07-09 22:44:15 -05:00
kopalandGitHub 1405b94296 docs: added recipe for Jenkins CI configuration (#1) (#1591) 2020-06-30 14:54:33 -07:00
Emmanuel OgbiziandGitHub 0f0c650b41 fix: use correct ci branch context (#1521) 2020-06-25 09:30:12 -07:00
Sven LiebigandGitHub a4658016d9 feat(bitbucket-basic-auth): support for bitbucket server basic auth (#1578) 2020-06-22 11:26:24 -07:00
Daniel WirtzandGitHub 6d48663a77 build(gitattributes): eol=lf for Windows users (#1575) 2020-06-15 09:16:33 -07:00
Nicholas ShineandGitHub eed1d3c8cb fix: prevent false positive secret replacement for Golang projects (#1562) 2020-05-24 11:53:00 -07:00
Daniel TschinderandGitHub 5f3a8bb14e docs: Recommend using npx instead of installing globally (#1563) 2020-05-24 10:20:58 -07:00
Lucas ShadlerandGitHub 0ef52e7a5f docs: adjust minor typos (#1554) 2020-05-19 14:15:26 -07:00
Aaron BatiloandGitHub a62bc7cd85 docs(GitHub Actions): Add alternative trigger options (#1471)
These options work the same as the curl command, but use a GitHub App so there's no need to provision and manage a Personal Access Token in order to trigger the Semantic Release action.
2020-05-06 11:50:31 -07:00
solidnerdandGregor Martynus 22321cff1c docs: update recipe for gitlab-ci with Node.js >=10.18
Signed-off-by: solidnerd <niclas@mietz.io>
2020-04-22 15:15:05 -07:00
greenkeeper[bot]andGitHub d64db31e76 fix(package): update marked to version 1.0.0 (#1534) 2020-04-21 13:04:21 -07:00
greenkeeper[bot]andGitHub 2322a7028f build(deps): update got to version 11.0.0 (#1533) 2020-04-20 10:17:39 -07:00
Gregor MartynusandGitHub 431d571a7b fix: adapt for semver to version 7.3.2 (part II) (#1530) 2020-04-16 14:42:25 -07:00
Gregor Martynus 0363790b8a fix: adapt for semver to version 7.3.2
see https://github.com/npm/node-semver/issues/322
2020-04-16 13:54:47 -07:00
greenkeeper[bot]andGitHub 93cb76653d chore(package): update semver to version 7.3.2 2020-04-14 17:46:03 +00:00
Gregor Martynus caa3526caa style: xo 2020-04-08 15:13:33 -07:00
greenkeeper[bot]andGitHub ed5f26d10b chore(package): update xo to version 0.29.0 2020-04-08 21:14:42 +00:00
Justin PoehneltandGitHub ff1d53df58 docs: add @google/semantic-release-replace-plugin to plugin list (#1509) 2020-04-02 16:23:31 -07:00
greenkeeper[bot]andPierre Vanduynslager f899fa9a7c chore(package): update xo to version 0.28.1
Closes #1488
2020-03-26 23:55:26 -04:00
greenkeeper[bot]andGregor Martynus b5488ae7a5 chore(package): update fs-extra to version 9.0.0 2020-03-19 11:31:14 -07:00
greenkeeper[bot]andPierre Vanduynslager da5d1cf9c6 chore(package): update tempy to version 0.5.0 2020-03-09 11:22:55 -04:00
Gaurav NelsonandGitHub da5d06cf78 docs: fix formatting for GitBook 2020-02-27 02:52:32 -05:00
greenkeeper[bot]andPierre Vanduynslager b85adbdb8f chore(package): update xo to version 0.27.0 2020-02-25 17:55:12 -05:00
greenkeeper[bot]andPierre Vanduynslager bce633a41e chore(package): update sinon to version 9.0.0 2020-02-19 18:08:22 -05:00
63 changed files with 900 additions and 522 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+5 -5
View File
@@ -107,11 +107,11 @@ To ensure consistency and quality, all documentation modifications must:
- a third-party product/brand/service, i.e. Integrate with [GitHub](https://github.com)
- an external concept or feature, i.e. Create a [GitHub release](https://help.github.com/articles/creating-releases)
- a package or module, i.e. The [`@semantic-release/github`](https://github.com/semantic-release/github) module
- Use the the [single backtick `code` quoting](https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code) for:
- Use the [single backtick `code` quoting](https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code) for:
- commands inside sentences, i.e. the `semantic-release` command
- programming language keywords, i.e. `function`, `async`, `String`
- packages or modules, i.e. The [`@semantic-release/github`](https://github.com/semantic-release/github) module
- Use the the [triple backtick `code` formatting](https://help.github.com/articles/creating-and-highlighting-code-blocks) for:
- Use the [triple backtick `code` formatting](https://help.github.com/articles/creating-and-highlighting-code-blocks) for:
- code examples
- configuration examples
- sequence of command lines
@@ -183,17 +183,17 @@ The footer should contain any information about **Breaking Changes** and is also
#### Examples
```commit
`fix(pencil): stop graphite breaking when too much pressure applied`
fix(pencil): stop graphite breaking when too much pressure applied
```
```commit
`feat(pencil): add 'graphiteWidth' option`
feat(pencil): add 'graphiteWidth' option
Fix #42
```
```commit
perf(pencil): remove graphiteWidth option`
perf(pencil): remove graphiteWidth option
BREAKING CHANGE: The graphiteWidth option has been removed.
+10 -3
View File
@@ -150,9 +150,16 @@ Let people know that your package is published using **semantic-release** by inc
## Team
| [![Stephan Bönnemann](https://github.com/boennemann.png?size=100)](https://github.com/boennemann) | [![Rolf Erik Lekang](https://github.com/relekang.png?size=100)](https://github.com/relekang) | [![Johannes Jörg Schmidt](https://github.com/jo.png?size=100)](https://github.com/jo) | [![Gregor Martynus](https://github.com/gr2m.png?size=100)](https://github.com/gr2m) | [![Pierre Vanduynslager](https://github.com/finnp.png?size=100)](https://github.com/finnp) | [![Pierre Vanduynslager](https://github.com/pvdlg.png?size=100)](https://github.com/pvdlg) | [![Christoph Witzko](https://github.com/christophwitzko.png?size=100)](https://github.com/christophwitzko) |
|---------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|
| [Stephan Bönnemann](https://github.com/boennemann) | [Rolf Erik Lekang](https://github.com/relekang) | [Johannes Jörg Schmidt](https://github.com/jo) | [Gregor Martynus](https://github.com/gr2m) | [Finn Pauls](https://github.com/finnp) | [Pierre Vanduynslager](https://github.com/pvdlg) | [Christoph Witzko](https://github.com/christophwitzko) |
| [![Gregor Martynus](https://github.com/gr2m.png?size=100)](https://github.com/gr2m) | [![Pierre Vanduynslager](https://github.com/pvdlg.png?size=100)](https://github.com/pvdlg) | [![Matt Travi](https://github.com/travi.png?size=100)](https://github.com/travi) |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| [Gregor Martynus](https://github.com/gr2m) | [Pierre Vanduynslager](https://github.com/pvdlg) | [Matt Travi](https://github.com/travi) |
## Alumni
| [![Stephan Bönnemann](https://github.com/boennemann.png?size=100)](https://github.com/boennemann) | [![Rolf Erik Lekang](https://github.com/relekang.png?size=100)](https://github.com/relekang) | [![Johannes Jörg Schmidt](https://github.com/jo.png?size=100)](https://github.com/jo) | [![Finn Pauls](https://github.com/finnp.png?size=100)](https://github.com/finnp) | [![Christoph Witzko](https://github.com/christophwitzko.png?size=100)](https://github.com/christophwitzko) |
| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| [Stephan Bönnemann](https://github.com/boennemann) | [Rolf Erik Lekang](https://github.com/relekang) | [Johannes Jörg Schmidt](https://github.com/jo) | [Finn Pauls](https://github.com/finnp) | [Christoph Witzko](https://github.com/christophwitzko) |
<p align="center">
<img alt="Kill all humans" src="media/bender.png">
+2 -2
View File
@@ -29,7 +29,7 @@ execa('git', ['--version'])
process.exit(1);
}
})
.catch(error => {
.catch((error) => {
console.error(`[semantic-release]: Git version ${MIN_GIT_VERSION} is required. No git binary found.`);
console.error(error);
process.exit(1);
@@ -37,7 +37,7 @@ execa('git', ['--version'])
// Node 10+ from this point on
require('../cli')()
.then(exitCode => {
.then((exitCode) => {
process.exitCode = exitCode;
})
.catch(() => {
+6 -6
View File
@@ -5,15 +5,15 @@ const hideSensitive = require('./lib/hide-sensitive');
const stringList = {
type: 'string',
array: true,
coerce: values =>
coerce: (values) =>
values.length === 1 && values[0].trim() === 'false'
? []
: values.reduce((values, value) => values.concat(value.split(',').map(value => value.trim())), []),
: values.reduce((values, value) => values.concat(value.split(',').map((value) => value.trim())), []),
};
module.exports = async () => {
const cli = require('yargs')
.command('$0', 'Run automated package publishing', yargs => {
.command('$0', 'Run automated package publishing', (yargs) => {
yargs.demandCommand(0, 0).usage(`Run automated package publishing
Usage:
@@ -41,18 +41,18 @@ Usage:
.exitProcess(false);
try {
const {help, version, ...opts} = cli.parse(argv.slice(2));
const {help, version, ...options} = cli.parse(argv.slice(2));
if (Boolean(help) || Boolean(version)) {
return 0;
}
if (opts.debug) {
if (options.debug) {
// Debug must be enabled before other requires in order to work
require('debug').enable('semantic-release:*');
}
await require('.')(opts);
await require('.')(options);
return 0;
} catch (error) {
if (error.name !== 'YError') {
+25
View File
@@ -99,3 +99,28 @@
- `verifyConditions`: Verify the presence and the validity of the authentication and the assets option configuration.
- `publish`: Publish a Gitea release, optionally uploading file assets.
- `addChannel`: Update a Gitea release's pre-release field.
- [@google/semantic-release-replace-plugin](https://github.com/google/semantic-release-replace-plugin)
- `prepare`: Replace version strings in files using regex and glob.
- [semantic-release-rubygem](https://github.com/Gusto/semantic-release-rubygem)
- `verifyConditions`: Locate and validate a `.gemspec` file, locate and validate a `lib/**/version.rb` file, verify the presence of the `GEM_HOST_API_KEY` environment variable, and create a credentials file with the API key.
- `prepare`: Update the version in the `lib/**/version.rb` version file and [build](https://guides.rubygems.org/command-reference/#gem-build) the gem.
- `publish`: [Push the Ruby gem](https://guides.rubygems.org/command-reference/#gem-push) to the gem server.
- [semantic-release-npm-deprecate-old-versions](https://github.com/ghusse/semantic-release-npm-deprecate-old-versions)
- `verifyConditions`: Validates configuration.
- `publish`: Deprecates old versions, based on the declaration of supported versions in the config.
- [amanda-mitchell/semantic-release-npm-multiple](https://github.com/amanda-mitchell/semantic-release-npm-multiple)
- **Note**: this is a thin wrapper around the built-in npm plugin that can target multiple registries
- `verifyConditions`: Verify the presence and the validity of the npm authentication and release configuration for multiple registries
- `prepare`: Update the package.json version and create the npm package tarball
- `publish`: Publish the package on the npm registry for multiple registries
- [semantic-release-license](https://github.com/cbhq/semantic-release-license) Automatically update dates and more in your license file for new releases.
- `verifyConditions`: Verify the presence of a license file
- `prepare`: Update the license file based on its type
- [semantic-release-pypi](https://github.com/abichinger/semantic-release-pypi)
- `verifyConditions`: Verify the environment variable ```PYPI_TOKEN``` and installation of build tools
- `prepare`: Update the version in ```setup.cfg``` and create the distribution packages
- `publish`: Publish the python package to a repository (default: pypi)
- [semantic-release-helm](https://github.com/m1pl/semantic-release-helm)
- `verifyConditions`: Validate configuration and (if present) credentials
- `prepare`: Update version and appVersion in ```Chart.yaml```
- `publish`: Publish the chart to a registry (if configured)
+1
View File
@@ -5,6 +5,7 @@
- [Travis CI](travis.md)
- [GitLab CI](gitlab-ci.md)
- [GitHub Actions](github-actions.md)
- [Jenkins CI](jenkins-ci.md)
## Git hosted services
- [Git authentication with SSH keys](git-auth-ssh-keys.md)
+25 -3
View File
@@ -4,7 +4,7 @@
The [Authentication](../usage/ci-configuration.md#authentication) environment variables can be configured with [Secret Variables](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables).
In this example an [`NPM_TOKEN`](https://docs.npmjs.com/creating-and-viewing-authentication-tokens) is required to publish a package to the npm registry. GitHub Actions [automatically populate](https://help.github.com/en/articles/virtual-environments-for-github-actions#github_token-secret) a [`GITHUB_TOKEN`](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) environment variable which can be used in Workflows.
In this example a publish type [`NPM_TOKEN`](https://docs.npmjs.com/creating-and-viewing-authentication-tokens) is required to publish a package to the npm registry. GitHub Actions [automatically populate](https://help.github.com/en/articles/virtual-environments-for-github-actions#github_token-secret) a [`GITHUB_TOKEN`](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) environment variable which can be used in Workflows.
## Node project configuration
@@ -28,7 +28,9 @@ jobs:
runs-on: ubuntu-18.04
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v1
with:
@@ -48,9 +50,23 @@ To keep `package.json` updated in the `master` branch, [`@semantic-release/git`]
**Note**: Automatically populated `GITHUB_TOKEN` cannot be used if branch protection is enabled for the target branch. It is **not** advised to mitigate this limitation by overriding an automatically populated `GITHUB_TOKEN` variable with a [Personal Access Tokens](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line), as it poses a security risk. Since Secret Variables are available for Workflows triggered by any branch, it becomes a potential vector of attack, where a Workflow triggered from a non-protected branch can expose and use a token with elevated permissions, yielding branch protection insignificant. One can use Personal Access Tokens in trusted environments, where all developers should have the ability to perform administrative actions in the given repository and branch protection is enabled solely for convenience purposes, to remind about required reviews or CI checks.
If the risk is acceptible, some extra configuration is needed. The [actions/checkout `persist-credentials`](https://github.com/marketplace/actions/checkout#usage) option needs to be `false`, otherwise the generated `GITHUB_TOKEN` will interfere with the custom one. Example:
```yaml
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
persist-credentials: false # <--- this
```
## Trigger semantic-release on demand
There is a way to trigger semantic-relase on demand. Use [`repository_dispatch`](https://help.github.com/en/articles/events-that-trigger-workflows#external-events-repository_dispatch) event to have control on when to generate a release by making an HTTP request, e.g.:
### Using GUI:
You can use [Manual Triggers](https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/) for GitHub Actions.
### Using HTTP:
Use [`repository_dispatch`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#repository_dispatch) event to have control on when to generate a release by making an HTTP request, e.g.:
```yaml
name: Release
@@ -66,3 +82,9 @@ To trigger a release, call (with a [Personal Access Tokens](https://help.github.
```
$ curl -v -H "Accept: application/vnd.github.everest-preview+json" -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/[org-name-or-username]/[repository]/dispatches -d '{ "event_type": "semantic-release" }'
```
### Using 3rd party apps:
If you'd like to use a GitHub app to manage this instead of creating a personal access token, you could consider using a project like:
* [Actions Panel](https://www.actionspanel.app/) - A declaratively configured way for triggering GitHub Actions
* [Action Button](https://github-action-button.web.app/#details) - A simple badge based mechanism for triggering GitHub Actions
+41 -6
View File
@@ -14,7 +14,7 @@ GitLab CI supports [Pipelines](https://docs.gitlab.com/ee/ci/pipelines.html) all
### `.gitlab-ci.yml` configuration for Node projects
This example is a minimal configuration for **semantic-release** with a build running Node 6 and 8. See [GitLab CI - Configuration of your jobs with .gitlab-ci.yml](https://docs.gitlab.com/ee/ci/yaml/README.html) for additional configuration options.
This example is a minimal configuration for **semantic-release** with a build running Node 10 and 12. See [GitLab CI - Configuration of your jobs with .gitlab-ci.yml](https://docs.gitlab.com/ee/ci/yaml/README.html) for additional configuration options.
**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.
@@ -27,25 +27,60 @@ stages:
before_script:
- npm install
node:6:
image: node:6
node:10:
image: node:10
stage: test
script:
- npm test
node:8:
image: node:8
node:12:
image: node:12
stage: test
script:
- npm test
publish:
image: node:8
image: node:12
stage: release
script:
- npx semantic-release
```
### `.gitlab-ci.yml` configuration for all projects
This example is a minimal configuration for **semantic-release** with a build running Node 10 and 12. See [GitLab CI - Configuration of your jobs with .gitlab-ci.yml](https://docs.gitlab.com/ee/ci/yaml/README.html) for additional configuration options.
**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
# The release pipeline will run only on the master branch a commit is triggered
stages:
- release
release:
image: node:10-buster-slim
stage: release
before_script:
- apt-get update && apt-get install -y --no-install-recommends git-core ca-certificates
- npm install -g semantic-release @semantic-release/gitlab
script:
- semantic-release
only:
- master
release:
image: node:12-buster-slim
stage: release
before_script:
- apt-get update && apt-get install -y --no-install-recommends git-core ca-certificates
- npm install -g semantic-release @semantic-release/gitlab
script:
- semantic-release
only:
- master
```
### `package.json` configuration
A `package.json` is required only for [local](../usage/installation.md#local-installation) **semantic-release** installation.
+61
View File
@@ -0,0 +1,61 @@
# Using semantic-release with [Jenkins CI](https://www.jenkins.io/doc/book/pipeline/)
## Environment variables
The [Authentication](../usage/ci-configuration.md#authentication) environment variables can be configured in [Jenkins Project Settings](https://www.jenkins.io/doc/pipeline/tour/environment/)..
Alternatively, the default `NPM_TOKEN` and `GH_TOKEN` can be easily [setup with semantic-release-cli](../usage/getting-started.md#getting-started).
## Node.js project configuration
### `Jenkinsfile (Declarative Pipeline)` configuration for a Node.js job
**Note**: The publish pipeline must run a [Node >= 10.18 version](../support/FAQ.md#why-does-semantic-release-require-node-version--1018).
This example is a minimal configuration for **semantic-release** with a build running Node 10.18. See [Jenkins documentation](https://www.jenkins.io/doc/) for additional configuration options.
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
// The release stage in the pipeline will run only if the test stage in the pipeline is successful
pipeline {
agent any
environment {
GH_TOKEN = credentials('some-id')
}
stages {
stage('Test') {
steps {
sh '''
# Configure your test steps here (checkout, npm install, tests etc)
npm install
npm test
'''
}
}
stage('Release') {
tools {
nodejs "node 10.18"
}
steps {
sh '''
# Run optional required steps before releasing
npx semantic-release
'''
}
}
}
}
```
### `package.json` configuration for a Node job
A `package.json` is required only for [local](../usage/installation.md#local-installation) **semantic-release** installation.
```json
{
"devDependencies": {
"semantic-release": "^15.0.0"
}
}
```
+3 -1
View File
@@ -2,12 +2,14 @@
## Why is the `package.json`s version not updated in my repository?
**semantic-release** takes care of updating the `package.json`s version before publishing to [npm](https://www.npmjs.com).
[`@semantic-release/npm`](https://github.com/semantic-release/npm) takes care of updating the `package.json`s version before publishing to [npm](https://www.npmjs.com).
By default, only the published package will contain the version, which is the only place where it is *really* required, but the updated `package.json` will not be pushed to the Git repository
However, the [`@semantic-release/git`](https://github.com/semantic-release/git) plugin can be used to push the updated `package.json` as well as other files to the Git repository.
If you wish to only update the `package.json` and push via Git you can set the project to `"private": true,` within your `package.json` to prevent publishing to [npm](https://www.npmjs.com). This can be useful for using **semantic-release** with a non-node project.
## How can I use a npm build script that requires the `package.json`s version ?
The `package.json`s version will be updated by the `semantic-release` command just before publishing to [npm](https://www.npmjs.com), therefore it won't be available for scripts ran before the `semantic-release` command.
+1
View File
@@ -14,6 +14,7 @@
- ["Introduction to SemVer" - Irina Gebauer](https://blog.greenkeeper.io/introduction-to-semver-d272990c44f2)
- ["Introduction to Semantic Release" - liv](https://blog.greenkeeper.io/introduction-to-semantic-release-33f73b117c8)
- ["Series - Semantic Release Automation" - Abdelrahman Wahdan](https://dev.to/abdelrahmanahmed/semantic-release-and-how-to-automate-it-part-1-4pa2)
## Tutorials
+7 -6
View File
@@ -21,12 +21,13 @@ See [CI configuration recipes](../recipes/README.md#ci-configurations) for more
**semantic-release** requires push access to the project Git repository in order to create [Git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging). The Git authentication can be set with one of the following environment variables:
| Variable | Description |
|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `GH_TOKEN` or `GITHUB_TOKEN` | A GitHub [personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line). |
| `GL_TOKEN` or `GITLAB_TOKEN` | A GitLab [personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html). |
| `BB_TOKEN` or `BITBUCKET_TOKEN` | A Bitbucket [personal access token](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html). |
| `GIT_CREDENTIALS` | [URL encoded](https://en.wikipedia.org/wiki/Percent-encoding) Git username and password in the format `<username>:<password>`. The username and password must each be individually URL encoded, not the `:` separating them. |
| Variable | Description |
|-------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `GH_TOKEN` or `GITHUB_TOKEN` | A GitHub [personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line). |
| `GL_TOKEN` or `GITLAB_TOKEN` | A GitLab [personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html). |
| `BB_TOKEN` or `BITBUCKET_TOKEN` | A Bitbucket [personal access token](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html). |
| `BB_TOKEN_BASIC_AUTH` or `BITBUCKET_TOKEN_BASIC_AUTH` | A Bitbucket [personal access token](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html) with basic auth support. For clearification `user:token` has to be the value of this env. |
| `GIT_CREDENTIALS` | [URL encoded](https://en.wikipedia.org/wiki/Percent-encoding) Git username and password in the format `<username>:<password>`. The username and password must each be individually URL encoded, not the `:` separating them. |
Alternatively the Git authentication can be set up via [SSH keys](../recipes/git-auth-ssh-keys.md).
+4 -2
View File
@@ -74,7 +74,7 @@ The branches on which releases should happen. By default **semantic-release** wi
**Note**: If your repository does not have a release branch, then **semantic-release** will fail with an `ERELEASEBRANCHES` error message. If you are using the default configuration, you can fix this error by pushing a `master` branch.
**Note**: Once **semantic-release** is configured, any user with the permission to push commits on one of those branches will be able to publish a release. It is recommended to protect those branches, for example with [GitHub protected branches](https://help.github.com/articles/about-protected-branches).
**Note**: Once **semantic-release** is configured, any user with the permission to push commits on one of those branches will be able to publish a release. It is recommended to protect those branches, for example with [GitHub protected branches](https://docs.github.com/github/administering-a-repository/about-protected-branches).
See [Workflow configuration](workflow-configuration.md#workflow-configuration) for more details.
@@ -116,7 +116,9 @@ Type: `Boolean`<br>
Default: `false` if running in a CI environment, `true` otherwise<br>
CLI arguments: `-d`, `--dry-run`
Dry-run mode, skip publishing, print next version and release notes.
The objective of the dry-run mode is to get a preview of the pending release. Dry-run mode skips the following steps: prepare, publish, success and fail. In addition to this it prints the next version and release notes to the console.
**Note**: The Dry-run mode verifies the repository push permission, even though nothing will be pushed. The verification is done to help user to figure out potential configuration issues.
### ci
+2 -3
View File
@@ -1,6 +1,7 @@
# Getting started
In order to use **semantic-release** you must follow these steps:
1. [Install](./installation.md#installation) **semantic-release** in your project
2. Configure your Continuous Integration service to [run **semantic-release**](./ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded)
3. Configure your Git repository and package manager repository [authentication](ci-configuration.md#authentication) in your Continuous Integration service
@@ -9,10 +10,8 @@ In order to use **semantic-release** you must follow these steps:
Alternatively those steps can be easily done with the [**semantic-release** interactive CLI](https://github.com/semantic-release/cli):
```bash
npm install -g semantic-release-cli
cd your-module
semantic-release-cli setup
npx semantic-release-cli setup
```
![dialogue](../../media/semantic-release-cli.png)
+4 -3
View File
@@ -12,6 +12,7 @@ A plugin is a npm module that can implement one or more of the following steps:
| `generateNotes` | No | Responsible for generating the content of the release note. If multiple plugins with a `generateNotes` step are defined, the release notes will be the result of the concatenation of each plugin output. |
| `prepare` | No | Responsible for preparing the release, for example creating or updating files such as `package.json`, `CHANGELOG.md`, documentation or compiled assets and pushing a commit. |
| `publish` | No | Responsible for publishing the release. |
| `addChannel` | No | Responsible for adding a release channel (e.g. adding an npm dist-tag to a release). |
| `success` | No | Responsible for notifying of a new release. |
| `fail` | No | Responsible for notifying of a failed release. |
@@ -21,12 +22,12 @@ A plugin is a npm module that can implement one or more of the following steps:
### Default plugins
These four plugins are already part of **semantic-release** and don't have to be installed separately:
These four plugins are already part of **semantic-release** and are listed in order of execution. They do not have to be installed separately:
```
"@semantic-release/commit-analyzer"
"@semantic-release/github"
"@semantic-release/npm"
"@semantic-release/release-notes-generator"
"@semantic-release/npm"
"@semantic-release/github"
```
### Additional plugins
+1 -1
View File
@@ -140,7 +140,7 @@ A maintenance branch is characterized by a range which defines the versions that
Maintenance branches are always considered lower than [release branches](#release-branches) and similarly to them, when a commit that would create a version conflict is pushed, **semantic-release** will not perform the release and will throw an `EINVALIDNEXTVERSION` error, listing the problematic commits and the valid branches on which to move them.
**semantic-release** will automatically add releases to the corresponding distribution channel when code is [merged from a release or maintenance branch to another maintenance branch](#merging-into-a-maintenance-branch), however only version version within the branch `range` can be merged. Ia merged version is outside the maintenance branch `range` **semantic-release** will not add to the corresponding channel and will throw an `EINVALIDMAINTENANCEMERGE` error.
**semantic-release** will automatically add releases to the corresponding distribution channel when code is [merged from a release or maintenance branch to another maintenance branch](#merging-into-a-maintenance-branch), however only versions within the branch `range` can be merged. If a merged version is outside the maintenance branch `range`, **semantic-release** will not add to the corresponding channel and will throw an `EINVALIDMAINTENANCEMERGE` error.
See [publishing maintenance releases recipe](../recipes/maintenance-releases.md) for a detailed example.
+6 -5
View File
@@ -26,7 +26,8 @@ marked.setOptions({renderer: new TerminalRenderer()});
/* eslint complexity: off */
async function run(context, plugins) {
const {cwd, env, options, logger} = context;
const {isCi, branch: ciBranch, isPr} = context.envCi;
const {isCi, branch, prBranch, isPr} = context.envCi;
const ciBranch = isPr ? prBranch : branch;
if (!isCi && !options.dryRun && !options.noCi) {
logger.warn('This run was not triggered in a known CI environment, running in dry-run mode.');
@@ -214,7 +215,7 @@ async function run(context, plugins) {
}
function logErrors({logger, stderr}, err) {
const errors = extractErrors(err).sort(error => (error.semanticRelease ? -1 : 0));
const errors = extractErrors(err).sort((error) => (error.semanticRelease ? -1 : 0));
for (const error of errors) {
if (error.semanticRelease) {
logger.error(`${error.code} ${error.message}`);
@@ -228,7 +229,7 @@ function logErrors({logger, stderr}, err) {
}
async function callFail(context, plugins, err) {
const errors = extractErrors(err).filter(err => err.semanticRelease);
const errors = extractErrors(err).filter((err) => err.semanticRelease);
if (errors.length > 0) {
try {
await plugins.fail({...context, errors});
@@ -238,7 +239,7 @@ async function callFail(context, plugins, err) {
}
}
module.exports = async (opts = {}, {cwd = process.cwd(), env = process.env, stdout, stderr} = {}) => {
module.exports = async (cliOptions = {}, {cwd = process.cwd(), env = process.env, stdout, stderr} = {}) => {
const {unhook} = hookStd(
{silent: false, streams: [process.stdout, process.stderr, stdout, stderr].filter(Boolean)},
hideSensitive(env)
@@ -253,7 +254,7 @@ module.exports = async (opts = {}, {cwd = process.cwd(), env = process.env, stdo
context.logger = getLogger(context);
context.logger.log(`Running ${pkg.name} version ${pkg.version}`);
try {
const {plugins, options} = await getConfig(context, opts);
const {plugins, options} = await getConfig(context, cliOptions);
context.options = options;
try {
const result = await run(context, plugins);
+2 -2
View File
@@ -8,9 +8,9 @@ module.exports = async (repositoryUrl, {cwd}, branches) => {
return branches.reduce(
(branches, branch) => [
...branches,
...remove(gitBranches, name => micromatch(gitBranches, branch.name).includes(name)).map(name => ({
...remove(gitBranches, (name) => micromatch(gitBranches, branch.name).includes(name)).map((name) => ({
name,
...mapValues(omit(branch, 'name'), value => (isString(value) ? template(value)({name}) : value)),
...mapValues(omit(branch, 'name'), (value) => (isString(value) ? template(value)({name}) : value)),
})),
],
[]
+5 -5
View File
@@ -14,7 +14,7 @@ module.exports = async (repositoryUrl, ciBranch, context) => {
const remoteBranches = await expand(
repositoryUrl,
context,
context.options.branches.map(branch => (isString(branch) || isRegExp(branch) ? {name: branch} : branch))
context.options.branches.map((branch) => (isString(branch) || isRegExp(branch) ? {name: branch} : branch))
);
await pEachSeries(remoteBranches, async ({name}) => {
@@ -32,7 +32,7 @@ module.exports = async (repositoryUrl, ciBranch, context) => {
);
const result = Object.entries(DEFINITIONS).reduce((result, [type, {branchesValidator, branchValidator}]) => {
branchesByType[type].forEach(branch => {
branchesByType[type].forEach((branch) => {
if (branchValidator && !branchValidator(branch)) {
errors.push(getError(`E${type.toUpperCase()}BRANCH`, {branch}));
}
@@ -48,15 +48,15 @@ module.exports = async (repositoryUrl, ciBranch, context) => {
}, {});
const duplicates = [...branches]
.map(branch => branch.name)
.map((branch) => branch.name)
.sort()
.filter((_, idx, arr) => arr[idx] === arr[idx + 1] && arr[idx] !== arr[idx - 1]);
.filter((_, idx, array) => array[idx] === array[idx + 1] && array[idx] !== array[idx - 1]);
if (duplicates.length > 0) {
errors.push(getError('EDUPLICATEBRANCHES', {duplicates}));
}
await pEachSeries(branches, async branch => {
await pEachSeries(branches, async (branch) => {
if (!(await verifyBranchName(branch.name))) {
errors.push(getError('EINVALIDBRANCHNAME', branch));
}
+4 -4
View File
@@ -5,19 +5,19 @@ const {isMaintenanceRange} = require('../utils');
const maintenance = {
filter: ({name, range}) => (!isNil(range) && range !== false) || isMaintenanceRange(name),
branchValidator: ({range}) => (isNil(range) ? true : isMaintenanceRange(range)),
branchesValidator: branches => uniqBy(branches, ({range}) => semver.validRange(range)).length === branches.length,
branchesValidator: (branches) => uniqBy(branches, ({range}) => semver.validRange(range)).length === branches.length,
};
const prerelease = {
filter: ({prerelease}) => !isNil(prerelease) && prerelease !== false,
branchValidator: ({name, prerelease}) =>
Boolean(prerelease) && Boolean(semver.valid(`1.0.0-${prerelease === true ? name : prerelease}.1`)),
branchesValidator: branches => uniqBy(branches, 'prerelease').length === branches.length,
branchesValidator: (branches) => uniqBy(branches, 'prerelease').length === branches.length,
};
const release = {
filter: branch => !maintenance.filter(branch) && !prerelease.filter(branch),
branchesValidator: branches => branches.length <= 3 && branches.length > 0,
filter: (branch) => !maintenance.filter(branch) && !prerelease.filter(branch),
branchesValidator: (branches) => branches.length <= 3 && branches.length > 0,
};
module.exports = {maintenance, prerelease, release};
+5 -4
View File
@@ -4,9 +4,10 @@ const pkg = require('../../package.json');
const {RELEASE_TYPE} = require('./constants');
const [homepage] = pkg.homepage.split('#');
const stringify = obj => (isString(obj) ? obj : inspect(obj, {breakLength: Infinity, depth: 2, maxArrayLength: 5}));
const linkify = file => `${homepage}/blob/master/${file}`;
const wordsList = words =>
const stringify = (object) =>
isString(object) ? object : inspect(object, {breakLength: Infinity, depth: 2, maxArrayLength: 5});
const linkify = (file) => `${homepage}/blob/master/${file}`;
const wordsList = (words) =>
`${words.slice(0, -1).join(', ')}${words.length > 1 ? ` or ${words[words.length - 1]}` : trim(words[0])}`;
module.exports = {
@@ -84,7 +85,7 @@ Please refer to the \`${pluginName}\` and [semantic-release plugins configuratio
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}\``
(type) => `\`${type}\``
).join(', ')}.
The \`analyzeCommits\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
+7 -7
View File
@@ -16,12 +16,12 @@ module.exports = {
default: ['@semantic-release/commit-analyzer'],
required: true,
dryRun: true,
outputValidator: output => !output || RELEASE_TYPE.includes(output),
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)),
commits: commits.filter((commit) => !/\[skip\s+release]|\[release\s+skip]/i.test(commit.message)),
}),
postprocess: results =>
postprocess: (results) =>
RELEASE_TYPE[
results.reduce((highest, result) => {
const typeIndex = RELEASE_TYPE.indexOf(result);
@@ -37,7 +37,7 @@ module.exports = {
generateNotes: {
required: false,
dryRun: true,
outputValidator: output => !output || isString(output),
outputValidator: (output) => !output || isString(output),
pipelineConfig: () => ({
getNextInput: ({nextRelease, ...context}, notes) => ({
...context,
@@ -53,7 +53,7 @@ module.exports = {
required: false,
dryRun: false,
pipelineConfig: ({generateNotes}) => ({
getNextInput: async context => {
getNextInput: async (context) => {
const newGitHead = await getGitHead({cwd: context.cwd});
// If previous prepare plugin has created a commit (gitHead changed)
if (context.nextRelease.gitHead !== newGitHead) {
@@ -70,7 +70,7 @@ module.exports = {
publish: {
required: false,
dryRun: false,
outputValidator: output => !output || isPlainObject(output),
outputValidator: (output) => !output || isPlainObject(output),
pipelineConfig: () => ({
// Add `nextRelease` and plugin properties to published release
transform: (release, step, {nextRelease}) => ({
@@ -83,7 +83,7 @@ module.exports = {
addChannel: {
required: false,
dryRun: false,
outputValidator: output => !output || isPlainObject(output),
outputValidator: (output) => !output || isPlainObject(output),
pipelineConfig: () => ({
// Add `nextRelease` and plugin properties to published release
transform: (release, step, {nextRelease}) => ({
+13 -12
View File
@@ -19,17 +19,14 @@ const CONFIG_FILES = [
`${CONFIG_NAME}.config.js`,
];
module.exports = async (context, opts) => {
module.exports = async (context, cliOptions) => {
const {cwd, env} = context;
const {config, filepath} = (await cosmiconfig(CONFIG_NAME, {searchPlaces: CONFIG_FILES}).search(cwd)) || {};
debug('load config from: %s', filepath);
// Merge config file options and CLI/API options
let options = {...config, ...opts};
if (options.ci === false) {
options.noCi = true;
}
let options = {...config, ...cliOptions};
const pluginsPath = {};
let extendPaths;
@@ -38,14 +35,14 @@ module.exports = async (context, opts) => {
// If `extends` is defined, load and merge each shareable config with `options`
options = {
...castArray(extendPaths).reduce((result, extendPath) => {
const extendsOpts = require(resolveFrom.silent(__dirname, extendPath) || resolveFrom(cwd, extendPath));
const extendsOptions = require(resolveFrom.silent(__dirname, extendPath) || resolveFrom(cwd, extendPath));
// 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.entries(extendsOpts)
Object.entries(extendsOptions)
.filter(([, value]) => Boolean(value))
.reduce((pluginsPath, [option, value]) => {
castArray(value).forEach(plugin => {
castArray(value).forEach((plugin) => {
if (option === 'plugins' && validatePlugin(plugin)) {
pluginsPath[parseConfig(plugin)[0]] = extendPath;
} else if (
@@ -58,7 +55,7 @@ module.exports = async (context, opts) => {
return pluginsPath;
}, pluginsPath);
return {...result, ...extendsOpts};
return {...result, ...extendsOptions};
}, {}),
...options,
};
@@ -83,16 +80,20 @@ module.exports = async (context, opts) => {
'@semantic-release/github',
],
// Remove `null` and `undefined` options so they can be replaced with default ones
...pickBy(options, option => !isNil(option)),
...pickBy(options, (option) => !isNil(option)),
...(options.branches ? {branches: castArray(options.branches)} : {}),
};
if (options.ci === false) {
options.noCi = true;
}
debug('options values: %O', options);
return {options, plugins: await plugins({...context, options}, pluginsPath)};
};
async function pkgRepoUrl(opts) {
const {packageJson} = (await readPkgUp(opts)) || {};
async function pkgRepoUrl(options) {
const {packageJson} = (await readPkgUp(options)) || {};
return packageJson && (isPlainObject(packageJson.repository) ? packageJson.repository.url : packageJson.repository);
}
+72 -16
View File
@@ -2,6 +2,49 @@ const {parse, format} = require('url'); // eslint-disable-line node/no-deprecate
const {isNil} = require('lodash');
const hostedGitInfo = require('hosted-git-info');
const {verifyAuth} = require('./git');
const debug = require('debug')('semantic-release:get-git-auth-url');
/**
* Machinery to format a repository URL with the given credentials
*
* @param {String} protocol URL protocol (which should not be present in repositoryUrl)
* @param {String} repositoryUrl User-given repository URL
* @param {String} gitCredentials The basic auth part of the URL
*
* @return {String} The formatted Git repository URL.
*/
function formatAuthUrl(protocol, repositoryUrl, gitCredentials) {
const [match, auth, host, basePort, path] =
/^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<port>\d+)?:?\/?(?<path>.*)$/.exec(repositoryUrl) || [];
const {port, hostname, ...parsed} = parse(
match ? `ssh://${auth ? `${auth}@` : ''}${host}${basePort ? `:${basePort}` : ''}/${path}` : repositoryUrl
);
return format({
...parsed,
auth: gitCredentials,
host: `${hostname}${protocol === 'ssh:' ? '' : port ? `:${port}` : ''}`,
protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
});
}
/**
* Verify authUrl by calling git.verifyAuth, but don't throw on failure
*
* @param {Object} context semantic-release context.
* @param {String} authUrl Repository URL to verify
*
* @return {String} The authUrl as is if the connection was successfull, null otherwise
*/
async function ensureValidAuthUrl({cwd, env, branch}, authUrl) {
try {
await verifyAuth(authUrl, branch.name, {cwd, env});
return authUrl;
} catch (error) {
debug(error);
return null;
}
}
/**
* Determine the the git repository URL to use to push, either:
@@ -14,7 +57,8 @@ const {verifyAuth} = require('./git');
*
* @return {String} The formatted Git repository URL.
*/
module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
module.exports = async (context) => {
const {cwd, env, branch} = context;
const GIT_TOKENS = {
GIT_CREDENTIALS: undefined,
GH_TOKEN: undefined,
@@ -25,8 +69,11 @@ module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
GITLAB_TOKEN: 'gitlab-ci-token:',
BB_TOKEN: 'x-token-auth:',
BITBUCKET_TOKEN: 'x-token-auth:',
BB_TOKEN_BASIC_AUTH: '',
BITBUCKET_TOKEN_BASIC_AUTH: '',
};
let {repositoryUrl} = context.options;
const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
const {protocol, ...parsed} = parse(repositoryUrl);
@@ -40,25 +87,34 @@ module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
// Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
try {
debug('Verifying ssh auth by attempting to push to %s', repositoryUrl);
await verifyAuth(repositoryUrl, branch.name, {cwd, env});
} catch (_) {
const envVar = Object.keys(GIT_TOKENS).find(envVar => !isNil(env[envVar]));
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar] || ''}`;
debug('SSH key auth failed, falling back to https.');
const envVars = Object.keys(GIT_TOKENS).filter((envVar) => !isNil(env[envVar]));
if (gitCredentials) {
// If credentials are set via environment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
const [match, auth, host, path] =
/^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<path>.*)$/.exec(repositoryUrl) || [];
const {port, hostname, ...parsed} = parse(
match ? `ssh://${auth ? `${auth}@` : ''}${host}/${path}` : repositoryUrl
);
// Skip verification if there is no ambiguity on which env var to use for authentication
if (envVars.length === 1) {
const gitCredentials = `${GIT_TOKENS[envVars[0]] || ''}${env[envVars[0]]}`;
return formatAuthUrl(protocol, repositoryUrl, gitCredentials);
}
return format({
...parsed,
auth: gitCredentials,
host: `${hostname}${protocol === 'ssh:' ? '' : port ? `:${port}` : ''}`,
protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
});
if (envVars.length > 1) {
debug(`Found ${envVars.length} credentials in environment, trying all of them`);
const candidateRepositoryUrls = [];
for (const envVar of envVars) {
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar]}`;
const authUrl = formatAuthUrl(protocol, repositoryUrl, gitCredentials);
candidateRepositoryUrls.push(ensureValidAuthUrl(context, authUrl));
}
const validRepositoryUrls = await Promise.all(candidateRepositoryUrls);
const chosenAuthUrlIndex = validRepositoryUrls.findIndex((url) => url !== null);
if (chosenAuthUrlIndex > -1) {
debug(`Using "${envVars[chosenAuthUrlIndex]}" to authenticate`);
return validRepositoryUrls[chosenAuthUrlIndex];
}
}
}
+2 -2
View File
@@ -29,8 +29,8 @@ const {makeTag, isSameChannel} = require('./utils');
module.exports = ({branch, options: {tagFormat}}, {before} = {}) => {
const [{version, gitTag, channels} = {}] = branch.tags
.filter(
tag =>
((branch.type === 'prerelease' && tag.channels.some(channel => isSameChannel(branch.channel, channel))) ||
(tag) =>
((branch.type === 'prerelease' && tag.channels.some((channel) => isSameChannel(branch.channel, channel))) ||
!semver.prerelease(tag.version)) &&
(isUndefined(before) || semver.lt(tag.version, before))
)
+1 -1
View File
@@ -10,7 +10,7 @@ module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) =
if (branch.type === 'prerelease') {
if (
semver.prerelease(lastRelease.version) &&
lastRelease.channels.some(lastReleaseChannel => isSameChannel(lastReleaseChannel, channel))
lastRelease.channels.some((lastReleaseChannel) => isSameChannel(lastReleaseChannel, channel))
) {
version = highest(
semver.inc(lastRelease.version, 'prerelease'),
+1 -1
View File
@@ -11,7 +11,7 @@ const {makeTag, getLowerBound} = require('./utils');
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
module.exports = (context) => {
const {
branch,
branches,
+44 -44
View File
@@ -14,8 +14,8 @@ Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', commi
*
* @return {String} The commit sha of the tag in parameter or `null`.
*/
async function getTagHead(tagName, execaOpts) {
return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
async function getTagHead(tagName, execaOptions) {
return (await execa('git', ['rev-list', '-1', tagName], execaOptions)).stdout;
}
/**
@@ -27,10 +27,10 @@ async function getTagHead(tagName, execaOpts) {
* @return {Array<String>} List of git tags.
* @throws {Error} If the `git` command fails.
*/
async function getTags(branch, execaOpts) {
return (await execa('git', ['tag', '--merged', branch], execaOpts)).stdout
async function getTags(branch, execaOptions) {
return (await execa('git', ['tag', '--merged', branch], execaOptions)).stdout
.split('\n')
.map(tag => tag.trim())
.map((tag) => tag.trim())
.filter(Boolean);
}
@@ -42,12 +42,12 @@ async function getTags(branch, execaOpts) {
* @param {Object} [execaOpts] Options to pass to `execa`.
* @return {Promise<Array<Object>>} The list of commits between `from` and `to`.
*/
async function getCommits(from, to, execaOpts) {
async function getCommits(from, to, execaOptions) {
return (
await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}${to}`},
{cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}
{cwd: execaOptions.cwd, env: {...process.env, ...execaOptions.env}}
)
)
).map(({message, gitTags, ...commit}) => ({...commit, message: message.trim(), gitTags: gitTags.trim()}));
@@ -62,11 +62,11 @@ async function getCommits(from, to, execaOpts) {
* @return {Array<String>} List of git branches.
* @throws {Error} If the `git` command fails.
*/
async function getBranches(repositoryUrl, execaOpts) {
return (await execa('git', ['ls-remote', '--heads', repositoryUrl], execaOpts)).stdout
async function getBranches(repositoryUrl, execaOptions) {
return (await execa('git', ['ls-remote', '--heads', repositoryUrl], execaOptions)).stdout
.split('\n')
.filter(Boolean)
.map(branch => branch.match(/^.+refs\/heads\/(?<branch>.+)$/)[1]);
.map((branch) => branch.match(/^.+refs\/heads\/(?<branch>.+)$/)[1]);
}
/**
@@ -77,9 +77,9 @@ async function getBranches(repositoryUrl, execaOpts) {
*
* @return {Boolean} `true` if the reference exists, falsy otherwise.
*/
async function isRefExists(ref, execaOpts) {
async function isRefExists(ref, execaOptions) {
try {
return (await execa('git', ['rev-parse', '--verify', ref], execaOpts)).exitCode === 0;
return (await execa('git', ['rev-parse', '--verify', ref], execaOptions)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -99,9 +99,9 @@ async function isRefExists(ref, execaOpts) {
* @param {String} branch The repository branch to fetch.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
async function fetch(repositoryUrl, branch, ciBranch, execaOptions) {
const isDetachedHead =
(await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {...execaOpts, reject: false})).stdout === 'HEAD';
(await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {...execaOptions, reject: false})).stdout === 'HEAD';
try {
await execa(
@@ -114,7 +114,7 @@ async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
? [repositoryUrl]
: ['--update-head-ok', repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
],
execaOpts
execaOptions
);
} catch (_) {
await execa(
@@ -126,7 +126,7 @@ async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
? [repositoryUrl]
: ['--update-head-ok', repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
],
execaOpts
execaOptions
);
}
}
@@ -137,16 +137,16 @@ async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
* @param {String} repositoryUrl The remote repository URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function fetchNotes(repositoryUrl, execaOpts) {
async function fetchNotes(repositoryUrl, execaOptions) {
try {
await execa(
'git',
['fetch', '--unshallow', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`],
execaOpts
execaOptions
);
} catch (_) {
await execa('git', ['fetch', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`], {
...execaOpts,
...execaOptions,
reject: false,
});
}
@@ -159,8 +159,8 @@ async function fetchNotes(repositoryUrl, execaOpts) {
*
* @return {String} the sha of the HEAD commit.
*/
async function getGitHead(execaOpts) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOpts)).stdout;
async function getGitHead(execaOptions) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOptions)).stdout;
}
/**
@@ -170,9 +170,9 @@ async function getGitHead(execaOpts) {
*
* @return {string} The value of the remote git URL.
*/
async function repoUrl(execaOpts) {
async function repoUrl(execaOptions) {
try {
return (await execa('git', ['config', '--get', 'remote.origin.url'], execaOpts)).stdout;
return (await execa('git', ['config', '--get', 'remote.origin.url'], execaOptions)).stdout;
} catch (error) {
debug(error);
}
@@ -185,9 +185,9 @@ async function repoUrl(execaOpts) {
*
* @return {Boolean} `true` if the current working directory is in a git repository, falsy otherwise.
*/
async function isGitRepo(execaOpts) {
async function isGitRepo(execaOptions) {
try {
return (await execa('git', ['rev-parse', '--git-dir'], execaOpts)).exitCode === 0;
return (await execa('git', ['rev-parse', '--git-dir'], execaOptions)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -202,9 +202,9 @@ async function isGitRepo(execaOpts) {
*
* @throws {Error} if not authorized to push.
*/
async function verifyAuth(repositoryUrl, branch, execaOpts) {
async function verifyAuth(repositoryUrl, branch, execaOptions) {
try {
await execa('git', ['push', '--dry-run', '--no-verify', repositoryUrl, `HEAD:${branch}`], execaOpts);
await execa('git', ['push', '--dry-run', '--no-verify', repositoryUrl, `HEAD:${branch}`], execaOptions);
} catch (error) {
debug(error);
throw error;
@@ -220,8 +220,8 @@ async function verifyAuth(repositoryUrl, branch, execaOpts) {
*
* @throws {Error} if the tag creation failed.
*/
async function tag(tagName, ref, execaOpts) {
await execa('git', ['tag', tagName, ref], execaOpts);
async function tag(tagName, ref, execaOptions) {
await execa('git', ['tag', tagName, ref], execaOptions);
}
/**
@@ -232,8 +232,8 @@ async function tag(tagName, ref, execaOpts) {
*
* @throws {Error} if the push failed.
*/
async function push(repositoryUrl, execaOpts) {
await execa('git', ['push', '--tags', repositoryUrl], execaOpts);
async function push(repositoryUrl, execaOptions) {
await execa('git', ['push', '--tags', repositoryUrl], execaOptions);
}
/**
@@ -244,8 +244,8 @@ async function push(repositoryUrl, execaOpts) {
*
* @throws {Error} if the push failed.
*/
async function pushNotes(repositoryUrl, execaOpts) {
await execa('git', ['push', repositoryUrl, `refs/notes/${GIT_NOTE_REF}`], execaOpts);
async function pushNotes(repositoryUrl, execaOptions) {
await execa('git', ['push', repositoryUrl, `refs/notes/${GIT_NOTE_REF}`], execaOptions);
}
/**
@@ -256,9 +256,9 @@ async function pushNotes(repositoryUrl, execaOpts) {
*
* @return {Boolean} `true` if valid, falsy otherwise.
*/
async function verifyTagName(tagName, execaOpts) {
async function verifyTagName(tagName, execaOptions) {
try {
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`], execaOpts)).exitCode === 0;
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`], execaOptions)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -272,9 +272,9 @@ async function verifyTagName(tagName, execaOpts) {
*
* @return {Boolean} `true` if valid, falsy otherwise.
*/
async function verifyBranchName(branch, execaOpts) {
async function verifyBranchName(branch, execaOptions) {
try {
return (await execa('git', ['check-ref-format', `refs/heads/${branch}`], execaOpts)).exitCode === 0;
return (await execa('git', ['check-ref-format', `refs/heads/${branch}`], execaOptions)).exitCode === 0;
} catch (error) {
debug(error);
}
@@ -289,10 +289,10 @@ async function verifyBranchName(branch, execaOpts) {
*
* @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(repositoryUrl, branch, execaOpts) {
async function isBranchUpToDate(repositoryUrl, branch, execaOptions) {
return (
(await getGitHead(execaOpts)) ===
(await execa('git', ['ls-remote', '--heads', repositoryUrl, branch], execaOpts)).stdout.match(/^(?<ref>\w+)?/)[1]
(await getGitHead(execaOptions)) ===
(await execa('git', ['ls-remote', '--heads', repositoryUrl, branch], execaOptions)).stdout.match(/^(?<ref>\w+)?/)[1]
);
}
@@ -304,9 +304,9 @@ async function isBranchUpToDate(repositoryUrl, branch, execaOpts) {
*
* @return {Object} the parsed JSON note if there is one, an empty object otherwise.
*/
async function getNote(ref, execaOpts) {
async function getNote(ref, execaOptions) {
try {
return JSON.parse((await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout);
return JSON.parse((await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOptions)).stdout);
} catch (error) {
if (error.exitCode === 1) {
return {};
@@ -324,8 +324,8 @@ async function getNote(ref, execaOpts) {
* @param {String} ref The Git reference to add the note to.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function addNote(note, ref, execaOpts) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-f', '-m', JSON.stringify(note), ref], execaOpts);
async function addNote(note, ref, execaOptions) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-f', '-m', JSON.stringify(note), ref], execaOptions);
}
module.exports = {
+14 -6
View File
@@ -1,12 +1,20 @@
const {escapeRegExp, size, isString} = require('lodash');
const {SECRET_REPLACEMENT, SECRET_MIN_SIZE} = require('./definitions/constants');
module.exports = env => {
const toReplace = Object.keys(env).filter(
envVar => /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE
);
module.exports = (env) => {
const toReplace = Object.keys(env).filter((envVar) => {
// https://github.com/semantic-release/semantic-release/issues/1558
if (envVar === 'GOPRIVATE') {
return false;
}
const regexp = new RegExp(toReplace.map(envVar => escapeRegExp(env[envVar])).join('|'), 'g');
return output =>
return /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE;
});
const regexp = new RegExp(
toReplace.map((envVar) => `${escapeRegExp(env[envVar])}|${escapeRegExp(encodeURI(env[envVar]))}`).join('|'),
'g'
);
return (output) =>
output && isString(output) && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
};
+6 -6
View File
@@ -46,14 +46,14 @@ module.exports = (context, pluginsPath) => {
const pluginsConf = Object.entries(PLUGINS_DEFINITIONS).reduce(
(pluginsConf, [type, {required, default: def, pipelineConfig, postprocess = identity, preprocess = identity}]) => {
let pluginOpts;
let pluginOptions;
if (isNil(options[type]) && def) {
pluginOpts = def;
pluginOptions = def;
} else {
// If an object is passed and the path is missing, merge it with step options
if (isPlainObject(options[type]) && !options[type].path) {
options[type] = castArray(plugins[type]).map(plugin =>
options[type] = castArray(plugins[type]).map((plugin) =>
plugin ? [plugin[0], Object.assign(plugin[1], options[type])] : plugin
);
}
@@ -63,10 +63,10 @@ module.exports = (context, pluginsPath) => {
return pluginsConf;
}
pluginOpts = options[type];
pluginOptions = options[type];
}
const steps = castArray(pluginOpts).map(pluginOpt =>
const steps = castArray(pluginOptions).map((pluginOpt) =>
normalize(
{...context, options: omit(options, Object.keys(PLUGINS_DEFINITIONS), 'plugins')},
type,
@@ -75,7 +75,7 @@ module.exports = (context, pluginsPath) => {
)
);
pluginsConf[type] = async input =>
pluginsConf[type] = async (input) =>
postprocess(
await pipeline(steps, pipelineConfig && pipelineConfig(pluginsConf, logger))(await preprocess(input)),
input
+2 -2
View File
@@ -26,7 +26,7 @@ module.exports = (context, type, pluginOpt, pluginsPath) => {
throw getError('EPLUGIN', {type, pluginName});
}
const validator = async input => {
const validator = async (input) => {
const {dryRun, outputValidator} = PLUGINS_DEFINITIONS[type] || {};
try {
if (!input.options.dryRun || dryRun) {
@@ -48,7 +48,7 @@ module.exports = (context, type, pluginOpt, pluginsPath) => {
logger.warn(`Skip step "${type}" of plugin "${pluginName}" in dry-run mode`);
} catch (error) {
logger.error(`Failed step "${type}" of plugin "${pluginName}"`);
extractErrors(error).forEach(err => Object.assign(err, {pluginName}));
extractErrors(error).forEach((err) => Object.assign(err, {pluginName}));
throw error;
}
};
+1 -1
View File
@@ -25,7 +25,7 @@ const {extractErrors} = require('../utils');
*
* @return {Pipeline} A Function that execute the `steps` sequencially
*/
module.exports = (steps, {settleAll = false, getNextInput = identity, transform = identity} = {}) => async input => {
module.exports = (steps, {settleAll = false, getNextInput = identity, transform = identity} = {}) => async (input) => {
const results = [];
const errors = [];
await pReduce(
+2 -2
View File
@@ -2,8 +2,8 @@ const {dirname} = require('path');
const {isString, isFunction, castArray, isArray, isPlainObject, isNil} = require('lodash');
const resolveFrom = require('resolve-from');
const validateSteps = conf => {
return conf.every(conf => {
const validateSteps = (conf) => {
return conf.every((conf) => {
if (
isArray(conf) &&
(conf.length === 1 || conf.length === 2) &&
+15 -10
View File
@@ -8,13 +8,13 @@ function extractErrors(err) {
function hideSensitiveValues(env, objs) {
const hideFunction = hideSensitive(env);
return objs.map(obj => {
Object.getOwnPropertyNames(obj).forEach(prop => {
if (obj[prop]) {
obj[prop] = hideFunction(obj[prop]);
return objs.map((object) => {
Object.getOwnPropertyNames(object).forEach((prop) => {
if (object[prop]) {
object[prop] = hideFunction(object[prop]);
}
});
return obj;
return object;
});
}
@@ -31,9 +31,14 @@ function isMaintenanceRange(range) {
}
function getUpperBound(range) {
return semver.valid(range)
const result = semver.valid(range)
? range
: ((semver.validRange(range) || '').match(/<(?<upperBound>\d+\.\d+\.\d+)$/) || [])[1];
: ((semver.validRange(range) || '').match(/<(?<upperBound>\d+\.\d+\.\d+(-\d+)?)$/) || [])[1];
return result
? // https://github.com/npm/node-semver/issues/322
result.replace(/-\d+$/, '')
: result;
}
function getLowerBound(range) {
@@ -49,17 +54,17 @@ function lowest(version1, version2) {
}
function getLatestVersion(versions, {withPrerelease} = {}) {
return versions.filter(version => withPrerelease || !semver.prerelease(version)).sort(semver.rcompare)[0];
return versions.filter((version) => withPrerelease || !semver.prerelease(version)).sort(semver.rcompare)[0];
}
function getEarliestVersion(versions, {withPrerelease} = {}) {
return versions.filter(version => withPrerelease || !semver.prerelease(version)).sort(semver.compare)[0];
return versions.filter((version) => withPrerelease || !semver.prerelease(version)).sort(semver.compare)[0];
}
function getFirstVersion(versions, lowerBranches) {
const lowerVersion = union(...lowerBranches.map(({tags}) => tagsToVersions(tags))).sort(semver.rcompare);
if (lowerVersion[0]) {
return versions.sort(semver.compare).find(version => semver.gt(version, lowerVersion[0]));
return versions.sort(semver.compare).find((version) => semver.gt(version, lowerVersion[0]));
}
return getEarliestVersion(versions);
+2 -2
View File
@@ -3,7 +3,7 @@ const AggregateError = require('aggregate-error');
const {isGitRepo, verifyTagName} = require('./git');
const getError = require('./get-error');
module.exports = async context => {
module.exports = async (context) => {
const {
cwd,
env,
@@ -29,7 +29,7 @@ module.exports = async context => {
errors.push(getError('ETAGNOVERSION', context));
}
branches.forEach(branch => {
branches.forEach((branch) => {
if (
!((isString(branch) && branch.trim()) || (isPlainObject(branch) && isString(branch.name) && branch.name.trim()))
) {
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="15 0 500 500" width="500px" height="500px">
<path fill="#494949" fill-rule="nonzero" d="M265 300a50 50 0 110-100 50 50 0 010 100zm0-15a35 35 0 100-70 35 35 0 000 70zM189 65v36c2 11 21 55 35 81-18-13-58-43-69-58a92 92 0 01-17-29l-71 40v86c9 5 23 15 31 18 11 4 59 9 88 10-21 9-66 29-85 31-16 3-27 1-34 0v85l69 39c9-4 24-12 31-17 9-7 37-46 52-71-2 22-8 71-15 89-6 15-13 23-17 29l79 45 73-42c1-9 2-28 0-38-1-11-21-55-34-81 18 14 57 44 69 59 11 13 15 24 17 30l74-42v-84c-8-5-24-15-34-19s-58-9-87-10c21-10 66-29 85-31 18-3 29-1 36 1v-87l-70-40-31 19c-9 7-37 46-53 70 3-22 9-71 16-89 6-15 13-24 18-29l-79-45-77 44zm77-65l217 125v250L266 500 49 375V125L266 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 715 B

+12 -9
View File
@@ -31,20 +31,20 @@
"env-ci": "^5.0.0",
"execa": "^4.0.0",
"figures": "^3.0.0",
"find-versions": "^3.0.0",
"find-versions": "^4.0.0",
"get-stream": "^5.0.0",
"git-log-parser": "^1.2.0",
"hook-std": "^2.0.0",
"hosted-git-info": "^3.0.0",
"lodash": "^4.17.15",
"marked": "^0.8.0",
"marked": "^1.0.0",
"marked-terminal": "^4.0.0",
"micromatch": "^4.0.2",
"p-each-series": "^2.1.0",
"p-reduce": "^2.0.0",
"read-pkg-up": "^7.0.0",
"resolve-from": "^5.0.0",
"semver": "^7.1.1",
"semver": "^7.3.2",
"semver-diff": "^3.1.1",
"signale": "^1.2.1",
"yargs": "^15.0.1"
@@ -56,18 +56,18 @@
"delay": "^4.0.0",
"dockerode": "^3.0.0",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"got": "^10.5.2",
"fs-extra": "^9.0.0",
"got": "^11.0.0",
"js-yaml": "^3.10.0",
"mockserver-client": "^5.1.1",
"nock": "^12.0.0",
"nyc": "^15.0.0",
"p-retry": "^4.0.0",
"proxyquire": "^2.0.0",
"sinon": "^8.0.4",
"sinon": "^9.0.0",
"stream-buffers": "^3.0.2",
"tempy": "^0.4.0",
"xo": "^0.26.0"
"tempy": "^0.5.0",
"xo": "^0.29.0"
},
"engines": {
"node": ">=10.18"
@@ -126,6 +126,9 @@
},
"xo": {
"prettier": true,
"space": true
"space": true,
"rules": {
"unicorn/string-content": "off"
}
}
}
+4 -4
View File
@@ -12,7 +12,7 @@ const merge = (branches, source, target, tag) => {
);
};
test('Enforce ranges with branching release workflow', async t => {
test('Enforce ranges with branching release workflow', async (t) => {
const branches = [
{name: '1.x', tags: []},
{name: '1.0.x', tags: []},
@@ -189,7 +189,7 @@ test('Enforce ranges with branching release workflow', async t => {
t.is(getBranch(result, '1.x').range, '>=1.2.0 <2.0.0', 'Can release on 1.x only within range');
});
test('Throw SemanticReleaseError for invalid configurations', async t => {
test('Throw SemanticReleaseError for invalid configurations', async (t) => {
const branches = [
{name: '123', range: '123', tags: []},
{name: '1.x', tags: []},
@@ -224,7 +224,7 @@ test('Throw SemanticReleaseError for invalid configurations', async t => {
t.truthy(errors[4].details);
});
test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
test('Throw a SemanticReleaseError if there is duplicate branches', async (t) => {
const branches = [
{name: 'master', tags: []},
{name: 'master', tags: []},
@@ -239,7 +239,7 @@ test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
t.truthy(errors[0].details);
});
test('Throw a SemanticReleaseError for each invalid branch name', async t => {
test('Throw a SemanticReleaseError for each invalid branch name', async (t) => {
const branches = [
{name: '~master', tags: []},
{name: '^master', tags: []},
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('ava');
const expand = require('../../lib/branches/expand');
const {gitRepo, gitCommits, gitCheckout, gitPush} = require('../helpers/git-utils');
test('Expand branches defined with globs', async t => {
test('Expand branches defined with globs', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
+5 -5
View File
@@ -2,7 +2,7 @@ const test = require('ava');
const getTags = require('../../lib/branches/get-tags');
const {gitRepo, gitCommits, gitTagVersion, gitCheckout, gitAddNote} = require('../helpers/git-utils');
test('Get the valid tags', async t => {
test('Get the valid tags', async (t) => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitTagVersion('foo', undefined, {cwd});
@@ -28,7 +28,7 @@ test('Get the valid tags', async t => {
]);
});
test('Get the valid tags from multiple branches', async t => {
test('Get the valid tags from multiple branches', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -71,7 +71,7 @@ test('Get the valid tags from multiple branches', async t => {
]);
});
test('Return branches with and empty tags array if no valid tag is found', async t => {
test('Return branches with and empty tags array if no valid tag is found', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitTagVersion('foo', undefined, {cwd});
@@ -85,7 +85,7 @@ test('Return branches with and empty tags array if no valid tag is found', async
t.deepEqual(result, [{name: 'master', tags: []}]);
});
test('Return branches with and empty tags array if no valid tag is found in history of configured branches', async t => {
test('Return branches with and empty tags array if no valid tag is found in history of configured branches', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitCheckout('next', true, {cwd});
@@ -108,7 +108,7 @@ test('Return branches with and empty tags array if no valid tag is found in hist
]);
});
test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
test('Get the highest valid tag corresponding to the "tagFormat"', async (t) => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
+13 -13
View File
@@ -1,9 +1,9 @@
const test = require('ava');
const normalize = require('../../lib/branches/normalize');
const toTags = versions => versions.map(version => ({version}));
const toTags = (versions) => versions.map((version) => ({version}));
test('Maintenance branches - initial state', t => {
test('Maintenance branches - initial state', (t) => {
const maintenance = [
{name: '1.x', channel: '1.x', tags: []},
{name: '1.1.x', tags: []},
@@ -48,7 +48,7 @@ test('Maintenance branches - initial state', t => {
);
});
test('Maintenance branches - cap range to first release present on default branch and not in any Maintenance one', t => {
test('Maintenance branches - cap range to first release present on default branch and not in any Maintenance one', (t) => {
const maintenance = [
{name: '1.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.5.0'])},
{name: 'name', range: '1.1.x', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1'])},
@@ -110,7 +110,7 @@ test('Maintenance branches - cap range to first release present on default branc
);
});
test('Maintenance branches - cap range to default branch last release if all release are also present on maintenance branch', t => {
test('Maintenance branches - cap range to default branch last release if all release are also present on maintenance branch', (t) => {
const maintenance = [
{name: '1.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0'])},
{name: '2.x.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0', '2.0.0'])},
@@ -147,7 +147,7 @@ test('Maintenance branches - cap range to default branch last release if all rel
);
});
test('Release branches - initial state', t => {
test('Release branches - initial state', (t) => {
const release = [
{name: 'master', tags: []},
{name: 'next', channel: 'next', tags: []},
@@ -187,7 +187,7 @@ test('Release branches - initial state', t => {
);
});
test('Release branches - 3 release branches', t => {
test('Release branches - 3 release branches', (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.0.2'])},
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.0.2', '1.1.0', '1.2.0'])},
@@ -220,7 +220,7 @@ test('Release branches - 3 release branches', t => {
);
});
test('Release branches - 2 release branches', t => {
test('Release branches - 2 release branches', (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0', '2.0.0', '2.0.1', '2.1.0'])},
@@ -251,7 +251,7 @@ test('Release branches - 2 release branches', t => {
);
});
test('Release branches - 1 release branches', t => {
test('Release branches - 1 release branches', (t) => {
const release = [{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0'])}];
t.deepEqual(
@@ -260,7 +260,7 @@ test('Release branches - 1 release branches', t => {
);
});
test('Release branches - cap ranges to first release only present on following branch', t => {
test('Release branches - cap ranges to first release only present on following branch', (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0'])},
@@ -286,7 +286,7 @@ test('Release branches - cap ranges to first release only present on following b
);
});
test('Release branches - Handle missing previous tags in branch history', t => {
test('Release branches - Handle missing previous tags in branch history', (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '2.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '2.0.0'])},
@@ -317,7 +317,7 @@ test('Release branches - Handle missing previous tags in branch history', t => {
);
});
test('Release branches - limit releases on 2nd and 3rd branch based on 1st branch last release', t => {
test('Release branches - limit releases on 2nd and 3rd branch based on 1st branch last release', (t) => {
const release = [
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '2.0.0', '3.0.0'])},
{name: 'next', tags: toTags(['1.0.0', '1.1.0'])},
@@ -357,7 +357,7 @@ test('Release branches - limit releases on 2nd and 3rd branch based on 1st branc
);
});
test('Prerelease branches', t => {
test('Prerelease branches', (t) => {
const prerelease = [
{name: 'beta', channel: 'beta', prerelease: true, tags: []},
{name: 'alpha', prerelease: 'preview', tags: []},
@@ -372,7 +372,7 @@ test('Prerelease branches', t => {
);
});
test('Allow to set channel to "false" to prevent default', t => {
test('Allow to set channel to "false" to prevent default', (t) => {
const maintenance = [{name: '1.x', channel: false, tags: []}];
const release = [
{name: 'master', channel: false, tags: []},
+16 -16
View File
@@ -4,23 +4,23 @@ const proxyquire = require('proxyquire').noPreserveCache();
const {stub} = require('sinon');
const {SECRET_REPLACEMENT} = require('../lib/definitions/constants');
test.beforeEach(t => {
test.beforeEach((t) => {
t.context.logs = '';
t.context.errors = '';
t.context.stdout = stub(process.stdout, 'write').callsFake(val => {
t.context.logs += val.toString();
t.context.stdout = stub(process.stdout, 'write').callsFake((value) => {
t.context.logs += value.toString();
});
t.context.stderr = stub(process.stderr, 'write').callsFake(val => {
t.context.errors += val.toString();
t.context.stderr = stub(process.stderr, 'write').callsFake((value) => {
t.context.errors += value.toString();
});
});
test.afterEach.always(t => {
test.afterEach.always((t) => {
t.context.stdout.restore();
t.context.stderr.restore();
});
test.serial('Pass options to semantic-release API', async t => {
test.serial('Pass options to semantic-release API', async (t) => {
const run = stub().resolves(true);
const argv = [
'',
@@ -86,7 +86,7 @@ test.serial('Pass options to semantic-release API', async t => {
t.is(exitCode, 0);
});
test.serial('Pass options to semantic-release API with alias arguments', async t => {
test.serial('Pass options to semantic-release API with alias arguments', async (t) => {
const run = stub().resolves(true);
const argv = [
'',
@@ -119,7 +119,7 @@ test.serial('Pass options to semantic-release API with alias arguments', async t
t.is(exitCode, 0);
});
test.serial('Pass unknown options to semantic-release API', async t => {
test.serial('Pass unknown options to semantic-release API', async (t) => {
const run = stub().resolves(true);
const argv = ['', '', '--bool', '--first-option', 'value1', '--second-option', 'value2', '--second-option', 'value3'];
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
@@ -133,7 +133,7 @@ test.serial('Pass unknown options to semantic-release API', async t => {
t.is(exitCode, 0);
});
test.serial('Pass empty Array to semantic-release API for list option set to "false"', async t => {
test.serial('Pass empty Array to semantic-release API for list option set to "false"', async (t) => {
const run = stub().resolves(true);
const argv = ['', '', '--publish', 'false'];
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
@@ -145,7 +145,7 @@ test.serial('Pass empty Array to semantic-release API for list option set to "fa
t.is(exitCode, 0);
});
test.serial('Do not set properties in option for which arg is not in command line', async t => {
test.serial('Do not set properties in option for which arg is not in command line', async (t) => {
const run = stub().resolves(true);
const argv = ['', '', '-b', 'master'];
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
@@ -162,7 +162,7 @@ test.serial('Do not set properties in option for which arg is not in command lin
t.false('e' in run.args[0][0]);
});
test.serial('Display help', async t => {
test.serial('Display help', async (t) => {
const run = stub().resolves(true);
const argv = ['', '', '--help'];
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
@@ -173,7 +173,7 @@ test.serial('Display help', async t => {
t.is(exitCode, 0);
});
test.serial('Return error exitCode and prints help if called with a command', async t => {
test.serial('Return error exitCode and prints help if called with a command', async (t) => {
const run = stub().resolves(true);
const argv = ['', '', 'pre'];
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
@@ -185,7 +185,7 @@ test.serial('Return error exitCode and prints help if called with a command', as
t.is(exitCode, 1);
});
test.serial('Return error exitCode if multiple plugin are set for single plugin', async t => {
test.serial('Return error exitCode if multiple plugin are set for single plugin', async (t) => {
const run = stub().resolves(true);
const argv = ['', '', '--analyze-commits', 'analyze1', 'analyze2'];
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
@@ -197,7 +197,7 @@ test.serial('Return error exitCode if multiple plugin are set for single plugin'
t.is(exitCode, 1);
});
test.serial('Return error exitCode if semantic-release throw error', async t => {
test.serial('Return error exitCode if semantic-release throw error', async (t) => {
const run = stub().rejects(new Error('semantic-release error'));
const argv = ['', ''];
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
@@ -208,7 +208,7 @@ test.serial('Return error exitCode if semantic-release throw error', async t =>
t.is(exitCode, 1);
});
test.serial('Hide sensitive environment variable values from the logs', async t => {
test.serial('Hide sensitive environment variable values from the logs', async (t) => {
const env = {MY_TOKEN: 'secret token'};
const run = stub().rejects(new Error(`Throw error: Exposing token ${env.MY_TOKEN}`));
const argv = ['', ''];
+8 -8
View File
@@ -1,7 +1,7 @@
const test = require('ava');
const {maintenance, prerelease, release} = require('../../lib/definitions/branches');
test('A "maintenance" branch is identified by having a "range" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', t => {
test('A "maintenance" branch is identified by having a "range" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
t.true(maintenance.filter({name: '1.x.x'}));
t.true(maintenance.filter({name: '1.0.x'}));
t.true(maintenance.filter({name: '1.x'}));
@@ -17,7 +17,7 @@ test('A "maintenance" branch is identified by having a "range" property or a "na
t.false(maintenance.filter({name: 'x.x.x'}));
});
test('A "maintenance" branches must have a "range" property formatted like "N.x", "N.x.x" or "N.N.x"', t => {
test('A "maintenance" branches must have a "range" property formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
t.true(maintenance.branchValidator({name: 'some-name', range: '1.x.x'}));
t.true(maintenance.branchValidator({name: 'some-name', range: '1.1.x'}));
@@ -29,14 +29,14 @@ test('A "maintenance" branches must have a "range" property formatted like "N.x"
t.false(maintenance.branchValidator({name: 'some-name', range: ''}));
});
test('The "maintenance" branches must have unique ranges', t => {
test('The "maintenance" branches must have unique ranges', (t) => {
t.true(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.0.x'}]));
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x.x'}]));
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x'}]));
});
test('A "prerelease" branch is identified by having a thruthy "prerelease" property', t => {
test('A "prerelease" branch is identified by having a thruthy "prerelease" property', (t) => {
t.true(prerelease.filter({name: 'some-name', prerelease: true}));
t.true(prerelease.filter({name: 'some-name', prerelease: 'beta'}));
t.true(prerelease.filter({name: 'some-name', prerelease: ''}));
@@ -46,7 +46,7 @@ test('A "prerelease" branch is identified by having a thruthy "prerelease" prope
t.false(prerelease.filter({name: 'some-name'}));
});
test('A "prerelease" branch must have a valid prerelease detonation in "prerelease" property or in "name" if "prerelease" is "true"', t => {
test('A "prerelease" branch must have a valid prerelease detonation in "prerelease" property or in "name" if "prerelease" is "true"', (t) => {
t.true(prerelease.branchValidator({name: 'beta', prerelease: true}));
t.true(prerelease.branchValidator({name: 'some-name', prerelease: 'beta'}));
@@ -59,13 +59,13 @@ test('A "prerelease" branch must have a valid prerelease detonation in "prerelea
t.false(prerelease.branchValidator({name: '#beta', prerelease: true}));
});
test('The "prerelease" branches must have unique "prerelease" property', t => {
test('The "prerelease" branches must have unique "prerelease" property', (t) => {
t.true(prerelease.branchesValidator([{prerelease: 'beta'}, {prerelease: 'alpha'}]));
t.false(prerelease.branchesValidator([{range: 'beta'}, {range: 'beta'}, {range: 'alpha'}]));
});
test('A "release" branch is identified by not havin a "range" or "prerelease" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', t => {
test('A "release" branch is identified by not havin a "range" or "prerelease" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
t.true(release.filter({name: 'some-name'}));
t.false(release.filter({name: '1.x.x'}));
@@ -76,7 +76,7 @@ test('A "release" branch is identified by not havin a "range" or "prerelease" pr
t.false(release.filter({name: 'some-name', prerelease: 'beta'}));
});
test('There must be between 1 and 3 release branches', t => {
test('There must be between 1 and 3 release branches', (t) => {
t.true(release.branchesValidator([{name: 'branch1'}]));
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}]));
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}, {name: 'branch3'}]));
+6 -6
View File
@@ -2,7 +2,7 @@ const test = require('ava');
const plugins = require('../../lib/definitions/plugins');
const {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} = require('../../lib/definitions/constants');
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', t => {
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', (t) => {
t.false(plugins.analyzeCommits.outputValidator('invalid'));
t.false(plugins.analyzeCommits.outputValidator(1));
t.false(plugins.analyzeCommits.outputValidator({}));
@@ -12,7 +12,7 @@ test('The "analyzeCommits" plugin output must be either undefined or a valid sem
t.true(plugins.analyzeCommits.outputValidator('major'));
});
test('The "generateNotes" plugin output, if defined, must be a string', t => {
test('The "generateNotes" plugin output, if defined, must be a string', (t) => {
t.false(plugins.generateNotes.outputValidator(1));
t.false(plugins.generateNotes.outputValidator({}));
@@ -22,7 +22,7 @@ test('The "generateNotes" plugin output, if defined, must be a string', t => {
t.true(plugins.generateNotes.outputValidator('string'));
});
test('The "publish" plugin output, if defined, must be an object or "false"', t => {
test('The "publish" plugin output, if defined, must be an object or "false"', (t) => {
t.false(plugins.publish.outputValidator(1));
t.false(plugins.publish.outputValidator('string'));
@@ -33,7 +33,7 @@ test('The "publish" plugin output, if defined, must be an object or "false"', t
t.true(plugins.publish.outputValidator(false));
});
test('The "addChannel" plugin output, if defined, must be an object', t => {
test('The "addChannel" plugin output, if defined, must be an object', (t) => {
t.false(plugins.addChannel.outputValidator(1));
t.false(plugins.addChannel.outputValidator('string'));
@@ -43,7 +43,7 @@ test('The "addChannel" plugin output, if defined, must be an object', t => {
t.true(plugins.addChannel.outputValidator(''));
});
test('The "generateNotes" plugins output are concatenated with separator and sensitive data is hidden', t => {
test('The "generateNotes" plugins output are concatenated with separator and sensitive data is hidden', (t) => {
const env = {MY_TOKEN: 'secret token'};
t.is(plugins.generateNotes.postprocess(['note 1', 'note 2'], {env}), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
t.is(plugins.generateNotes.postprocess(['', 'note'], {env}), 'note');
@@ -63,7 +63,7 @@ test('The "generateNotes" plugins output are concatenated with separator and sen
);
});
test('The "analyzeCommits" plugins output are reduced to the highest release type', t => {
test('The "analyzeCommits" plugins output are reduced to the highest release type', (t) => {
t.is(plugins.analyzeCommits.postprocess(['major', 'minor']), 'major');
t.is(plugins.analyzeCommits.postprocess(['', 'minor']), 'minor');
t.is(plugins.analyzeCommits.postprocess([undefined, 'patch']), 'patch');
+7 -7
View File
@@ -3,14 +3,14 @@ const {stub} = require('sinon');
const getCommits = require('../lib/get-commits');
const {gitRepo, gitCommits, gitDetachedHead} = require('./helpers/git-utils');
test.beforeEach(t => {
test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.error = stub();
t.context.logger = {log: t.context.log, error: t.context.error};
});
test('Get all commits when there is no last release', async t => {
test('Get all commits when there is no last release', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -24,7 +24,7 @@ test('Get all commits when there is no last release', async t => {
t.deepEqual(result, commits);
});
test('Get all commits since gitHead (from lastRelease)', async t => {
test('Get all commits since gitHead (from lastRelease)', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -42,7 +42,7 @@ test('Get all commits since gitHead (from lastRelease)', async t => {
t.deepEqual(result, commits.slice(0, 2));
});
test('Get all commits since gitHead (from lastRelease) on a detached head repo', async t => {
test('Get all commits since gitHead (from lastRelease) on a detached head repo', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo();
// Add commits to the master branch
@@ -66,7 +66,7 @@ test('Get all commits since gitHead (from lastRelease) on a detached head repo',
t.truthy(result[0].committer.name);
});
test('Get all commits between lastRelease.gitHead and a shas', async t => {
test('Get all commits between lastRelease.gitHead and a shas', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -85,7 +85,7 @@ test('Get all commits between lastRelease.gitHead and a shas', async t => {
t.deepEqual(result, commits.slice(1, -1));
});
test('Return empty array if lastRelease.gitHead is the last commit', async t => {
test('Return empty array if lastRelease.gitHead is the last commit', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -102,7 +102,7 @@ test('Return empty array if lastRelease.gitHead is the last commit', async t =>
t.deepEqual(result, []);
});
test('Return empty array if there is no commits', async t => {
test('Return empty array if there is no commits', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
+38 -21
View File
@@ -15,12 +15,12 @@ const DEFAULT_PLUGINS = [
'@semantic-release/github',
];
test.beforeEach(t => {
test.beforeEach((t) => {
t.context.plugins = stub().returns({});
t.context.getConfig = proxyquire('../lib/get-config', {'./plugins': t.context.plugins});
});
test('Default values, reading repositoryUrl from package.json', async t => {
test('Default values, reading repositoryUrl from package.json', async (t) => {
const pkg = {repository: 'https://host.null/owner/package.git'};
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo(true);
@@ -47,7 +47,7 @@ test('Default values, reading repositoryUrl from package.json', async t => {
t.is(result.tagFormat, `v\${version}`);
});
test('Default values, reading repositoryUrl from repo if not set in package.json', async t => {
test('Default values, reading repositoryUrl from repo if not set in package.json', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo(true);
// Add remote.origin.url config
@@ -68,7 +68,7 @@ test('Default values, reading repositoryUrl from repo if not set in package.json
t.is(result.tagFormat, `v\${version}`);
});
test('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async t => {
test('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async (t) => {
const pkg = {repository: 'https://host.null/owner/module.git'};
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
@@ -90,7 +90,7 @@ test('Default values, reading repositoryUrl (http url) from package.json if not
t.is(result.tagFormat, `v\${version}`);
});
test('Convert "ci" option to "noCi"', async t => {
test('Convert "ci" option to "noCi"', async (t) => {
const pkg = {repository: 'https://host.null/owner/module.git', release: {ci: false}};
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
@@ -102,7 +102,7 @@ test('Convert "ci" option to "noCi"', async t => {
t.is(result.noCi, true);
});
test('Read options from package.json', async t => {
test('Read options from package.json', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -125,7 +125,7 @@ test('Read options from package.json', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from .releaserc.yml', async t => {
test('Read options from .releaserc.yml', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -147,7 +147,7 @@ test('Read options from .releaserc.yml', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from .releaserc.json', async t => {
test('Read options from .releaserc.json', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -169,7 +169,7 @@ test('Read options from .releaserc.json', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from .releaserc.js', async t => {
test('Read options from .releaserc.js', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -191,7 +191,7 @@ test('Read options from .releaserc.js', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from release.config.js', async t => {
test('Read options from release.config.js', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -213,7 +213,7 @@ test('Read options from release.config.js', async t => {
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Prioritise CLI/API parameters over file configuration and git repo', async t => {
test('Prioritise CLI/API parameters over file configuration and git repo', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -243,7 +243,7 @@ test('Prioritise CLI/API parameters over file configuration and git repo', async
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read configuration from file path in "extends"', async t => {
test('Read configuration from file path in "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: './shareable.json'};
@@ -274,7 +274,7 @@ test('Read configuration from file path in "extends"', async t => {
});
});
test('Read configuration from module path in "extends"', async t => {
test('Read configuration from module path in "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: 'shareable'};
@@ -303,7 +303,7 @@ test('Read configuration from module path in "extends"', async t => {
});
});
test('Read configuration from an array of paths in "extends"', async t => {
test('Read configuration from an array of paths in "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: ['./shareable1.json', './shareable2.json']};
@@ -342,7 +342,7 @@ test('Read configuration from an array of paths in "extends"', async t => {
});
});
test('Prioritize configuration from config file over "extends"', async t => {
test('Prioritize configuration from config file over "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {
@@ -378,7 +378,7 @@ test('Prioritize configuration from config file over "extends"', async t => {
});
});
test('Prioritize configuration from cli/API options over "extends"', async t => {
test('Prioritize configuration from cli/API options over "extends"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const cliOptions = {
@@ -421,7 +421,7 @@ test('Prioritize configuration from cli/API options over "extends"', async t =>
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Allow to unset properties defined in shareable config with "null"', async t => {
test('Allow to unset properties defined in shareable config with "null"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {
@@ -466,7 +466,7 @@ test('Allow to unset properties defined in shareable config with "null"', async
});
});
test('Allow to unset properties defined in shareable config with "undefined"', async t => {
test('Allow to unset properties defined in shareable config with "undefined"', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {
@@ -502,13 +502,13 @@ test('Allow to unset properties defined in shareable config with "undefined"', a
});
});
test('Throw an Error if one of the shareable config cannot be found', async t => {
test('Throw an Error if one of the shareable config cannot be found', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkhOptions = {extends: ['./shareable1.json', 'non-existing-path']};
const pkgOptions = {extends: ['./shareable1.json', 'non-existing-path']};
const options1 = {analyzeCommits: 'analyzeCommits'};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, 'package.json'), {release: pkhOptions});
await outputJson(path.resolve(cwd, 'package.json'), {release: pkgOptions});
await outputJson(path.resolve(cwd, 'shareable1.json'), options1);
await t.throwsAsync(t.context.getConfig({cwd}), {
@@ -516,3 +516,20 @@ test('Throw an Error if one of the shareable config cannot be found', async t =>
code: 'MODULE_NOT_FOUND',
});
});
test('Convert "ci" option to "noCi" when set from extended config', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: './no-ci.json'};
const options = {
ci: false,
};
// Create package.json and shareable.json in repository root
await outputJson(path.resolve(cwd, 'package.json'), {release: pkgOptions});
await outputJson(path.resolve(cwd, 'no-ci.json'), options);
const {options: result} = await t.context.getConfig({cwd});
t.is(result.ci, false);
t.is(result.noCi, true);
});
+80 -26
View File
@@ -4,7 +4,7 @@ const {gitRepo} = require('./helpers/git-utils');
const env = {GIT_ASKPASS: 'echo', GIT_TERMINAL_PROMPT: 0};
test('Return the same "git" formatted URL if "gitCredentials" is not defined', async t => {
test('Return the same "git" formatted URL if "gitCredentials" is not defined', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -13,7 +13,7 @@ test('Return the same "git" formatted URL if "gitCredentials" is not defined', a
);
});
test('Return the same "https" formatted URL if "gitCredentials" is not defined', async t => {
test('Return the same "https" formatted URL if "gitCredentials" is not defined', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -27,7 +27,7 @@ test('Return the same "https" formatted URL if "gitCredentials" is not defined',
);
});
test('Return the "https" formatted URL if "gitCredentials" is not defined and repositoryUrl is a "git+https" URL', async t => {
test('Return the "https" formatted URL if "gitCredentials" is not defined and repositoryUrl is a "git+https" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -41,7 +41,7 @@ test('Return the "https" formatted URL if "gitCredentials" is not defined and re
);
});
test('Do not add trailing ".git" if not present in the origian URL', async t => {
test('Do not add trailing ".git" if not present in the origian URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -50,7 +50,7 @@ test('Do not add trailing ".git" if not present in the origian URL', async t =>
);
});
test('Handle "https" URL with group and subgroup', async t => {
test('Handle "https" URL with group and subgroup', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -64,7 +64,7 @@ test('Handle "https" URL with group and subgroup', async t => {
);
});
test('Handle "git" URL with group and subgroup', async t => {
test('Handle "git" URL with group and subgroup', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -78,7 +78,7 @@ test('Handle "git" URL with group and subgroup', async t => {
);
});
test('Convert shorthand URL', async t => {
test('Convert shorthand URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -92,7 +92,7 @@ test('Convert shorthand URL', async t => {
);
});
test('Convert GitLab shorthand URL', async t => {
test('Convert GitLab shorthand URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -106,7 +106,7 @@ test('Convert GitLab shorthand URL', async t => {
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -120,7 +120,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL without user', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL without user', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -133,7 +133,33 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "https" URL', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL without user and with a custom port', async (t) => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GIT_CREDENTIALS: 'user:pass'},
options: {branch: 'master', repositoryUrl: 'host.null:6666:owner/repo.git'},
}),
'https://user:pass@host.null:6666/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL without user and with a custom port followed by a slash', async (t) => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GIT_CREDENTIALS: 'user:pass'},
options: {branch: 'master', repositoryUrl: 'host.null:6666:/owner/repo.git'},
}),
'https://user:pass@host.null:6666/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "https" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -147,7 +173,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
);
});
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "http" URL', async t => {
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "http" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -161,7 +187,7 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
);
});
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "http" URL with custom port', async t => {
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "http" URL with custom port', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -174,7 +200,7 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+https" URL', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+https" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -188,7 +214,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
);
});
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+http" URL', async t => {
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+http" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -202,7 +228,7 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
);
});
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "ssh" URL', async t => {
test('Return the "http" formatted URL if "gitCredentials" is defined and repositoryUrl is a "ssh" URL', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -215,7 +241,7 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GH_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "GH_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -229,7 +255,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GH_T
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GITHUB_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "GITHUB_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -243,7 +269,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GITH
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GL_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "GL_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -257,7 +283,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GL_T
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GITLAB_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "GITLAB_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -271,7 +297,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GITL
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "BB_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "BB_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -285,7 +311,7 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "BB_T
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "BITBUCKET_TOKEN"', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "BITBUCKET_TOKEN"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -299,7 +325,35 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "BITB
);
});
test('Return the "https" formatted URL if "GITHUB_ACTION" is set', async t => {
test('Return the "https" formatted URL if "gitCredentials" is defined with "BB_TOKEN_BASIC_AUTH"', async (t) => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, BB_TOKEN_BASIC_AUTH: 'username:token'},
branch: {name: 'master'},
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
}),
'https://username:token@host.null/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "BITBUCKET_TOKEN_BASIC_AUTH"', async (t) => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, BITBUCKET_TOKEN_BASIC_AUTH: 'username:token'},
branch: {name: 'master'},
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
}),
'https://username:token@host.null/owner/repo.git'
);
});
test('Return the "https" formatted URL if "GITHUB_ACTION" is set', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -312,7 +366,7 @@ test('Return the "https" formatted URL if "GITHUB_ACTION" is set', async t => {
);
});
test('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async t => {
test('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -326,7 +380,7 @@ test('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async
);
});
test('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', async t => {
test('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', async (t) => {
const {cwd} = await gitRepo();
t.is(
@@ -340,7 +394,7 @@ test('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', async t
);
});
test('Do not add git credential to repositoryUrl if push is allowed', async t => {
test('Do not add git credential to repositoryUrl if push is allowed', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
t.is(
+4 -4
View File
@@ -1,7 +1,7 @@
const test = require('ava');
const getLastRelease = require('../lib/get-last-release');
test('Get the highest non-prerelease valid tag', t => {
test('Get the highest non-prerelease valid tag', (t) => {
const result = getLastRelease({
branch: {
name: 'master',
@@ -18,7 +18,7 @@ test('Get the highest non-prerelease valid tag', t => {
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: 'v2.0.0', channels: undefined});
});
test('Get the highest prerelease valid tag, ignoring other tags from other prerelease channels', t => {
test('Get the highest prerelease valid tag, ignoring other tags from other prerelease channels', (t) => {
const result = getLastRelease({
branch: {
name: 'beta',
@@ -43,7 +43,7 @@ test('Get the highest prerelease valid tag, ignoring other tags from other prere
});
});
test('Return empty object if no valid tag is found', t => {
test('Return empty object if no valid tag is found', (t) => {
const result = getLastRelease({
branch: {
name: 'master',
@@ -56,7 +56,7 @@ test('Return empty object if no valid tag is found', t => {
t.deepEqual(result, {});
});
test('Get the highest non-prerelease valid tag before a certain version', t => {
test('Get the highest non-prerelease valid tag before a certain version', (t) => {
const result = getLastRelease(
{
branch: {
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('ava');
const {spy} = require('sinon');
const getLogger = require('../lib/get-logger');
test('Expose "error", "success" and "log" functions', t => {
test('Expose "error", "success" and "log" functions', (t) => {
const stdout = spy();
const stderr = spy();
const logger = getLogger({stdout: {write: stdout}, stderr: {write: stderr}});
+12 -12
View File
@@ -2,13 +2,13 @@ const test = require('ava');
const {stub} = require('sinon');
const getNextVersion = require('../lib/get-next-version');
test.beforeEach(t => {
test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.logger = {log: t.context.log};
});
test('Increase version for patch release', t => {
test('Increase version for patch release', (t) => {
t.is(
getNextVersion({
branch: {name: 'master', type: 'release', tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}]},
@@ -20,7 +20,7 @@ test('Increase version for patch release', t => {
);
});
test('Increase version for minor release', t => {
test('Increase version for minor release', (t) => {
t.is(
getNextVersion({
branch: {name: 'master', type: 'release', tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}]},
@@ -32,7 +32,7 @@ test('Increase version for minor release', t => {
);
});
test('Increase version for major release', t => {
test('Increase version for major release', (t) => {
t.is(
getNextVersion({
branch: {name: 'master', type: 'release', tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}]},
@@ -44,7 +44,7 @@ test('Increase version for major release', t => {
);
});
test('Return 1.0.0 if there is no previous release', t => {
test('Return 1.0.0 if there is no previous release', (t) => {
t.is(
getNextVersion({
branch: {name: 'master', type: 'release', tags: []},
@@ -56,7 +56,7 @@ test('Return 1.0.0 if there is no previous release', t => {
);
});
test('Increase version for patch release on prerelease branch', t => {
test('Increase version for patch release on prerelease branch', (t) => {
t.is(
getNextVersion({
branch: {
@@ -106,7 +106,7 @@ test('Increase version for patch release on prerelease branch', t => {
);
});
test('Increase version for minor release on prerelease branch', t => {
test('Increase version for minor release on prerelease branch', (t) => {
t.is(
getNextVersion({
branch: {
@@ -156,7 +156,7 @@ test('Increase version for minor release on prerelease branch', t => {
);
});
test('Increase version for major release on prerelease branch', t => {
test('Increase version for major release on prerelease branch', (t) => {
t.is(
getNextVersion({
branch: {
@@ -206,7 +206,7 @@ test('Increase version for major release on prerelease branch', t => {
);
});
test('Return 1.0.0 if there is no previous release on prerelease branch', t => {
test('Return 1.0.0 if there is no previous release on prerelease branch', (t) => {
t.is(
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta', tags: []},
@@ -218,7 +218,7 @@ test('Return 1.0.0 if there is no previous release on prerelease branch', t => {
);
});
test('Increase version for release on prerelease branch after previous commits were merged to release branch', t => {
test('Increase version for release on prerelease branch after previous commits were merged to release branch', (t) => {
t.is(
getNextVersion({
branch: {
@@ -239,7 +239,7 @@ test('Increase version for release on prerelease branch after previous commits w
);
});
test('Increase version for release on prerelease branch based on highest commit type since last regular release', t => {
test('Increase version for release on prerelease branch based on highest commit type since last regular release', (t) => {
t.is(
getNextVersion({
branch: {
@@ -259,7 +259,7 @@ test('Increase version for release on prerelease branch based on highest commit
);
});
test('Increase version for release on prerelease branch when there is no regular releases on other branches', t => {
test('Increase version for release on prerelease branch when there is no regular releases on other branches', (t) => {
t.is(
getNextVersion({
branch: {
+9 -9
View File
@@ -1,7 +1,7 @@
const test = require('ava');
const getReleaseToAdd = require('../lib/get-release-to-add');
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', t => {
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', (t) => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
@@ -42,7 +42,7 @@ test('Return versions merged from release to maintenance branch, excluding lower
});
});
test('Return versions merged between release branches', t => {
test('Return versions merged between release branches', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -83,7 +83,7 @@ test('Return versions merged between release branches', t => {
});
});
test('Return releases sorted by ascending order', t => {
test('Return releases sorted by ascending order', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -118,7 +118,7 @@ test('Return releases sorted by ascending order', t => {
});
});
test('No lastRelease', t => {
test('No lastRelease', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -149,7 +149,7 @@ test('No lastRelease', t => {
});
});
test('Ignore pre-release versions', t => {
test('Ignore pre-release versions', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -188,7 +188,7 @@ test('Ignore pre-release versions', t => {
});
});
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', t => {
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', (t) => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
@@ -214,7 +214,7 @@ test('Exclude versions merged from release to maintenance branch if they have th
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they have the same "channel"', t => {
test('Exclude versions merged between release branches if they have the same "channel"', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -236,7 +236,7 @@ test('Exclude versions merged between release branches if they have the same "ch
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they all have "channel" set to "false"', t => {
test('Exclude versions merged between release branches if they all have "channel" set to "false"', (t) => {
const result = getReleaseToAdd({
branch: {
name: 'master',
@@ -258,7 +258,7 @@ test('Exclude versions merged between release branches if they all have "channel
t.is(result, undefined);
});
test('Exclude versions number less than the latest version already released on that branch', t => {
test('Exclude versions number less than the latest version already released on that branch', (t) => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
+38 -38
View File
@@ -36,7 +36,7 @@ const {
initGit,
} = require('./helpers/git-utils');
test('Get the last commit sha', async t => {
test('Get the last commit sha', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -47,14 +47,14 @@ test('Get the last commit sha', async t => {
t.is(result, commits[0].hash);
});
test('Throw error if the last commit sha cannot be found', async t => {
test('Throw error if the last commit sha cannot be found', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
await t.throwsAsync(getGitHead({cwd}));
});
test('Unshallow and fetch repository', async t => {
test('Unshallow and fetch repository', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo();
// Add commits to the master branch
@@ -71,7 +71,7 @@ test('Unshallow and fetch repository', async t => {
t.is((await gitGetCommits(undefined, {cwd})).length, 2);
});
test('Do not throw error when unshallow a complete repository', async t => {
test('Do not throw error when unshallow a complete repository', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
@@ -84,7 +84,7 @@ test('Do not throw error when unshallow a complete repository', async t => {
await t.notThrowsAsync(fetch(repositoryUrl, 'second-branch', 'master', {cwd}));
});
test('Fetch all tags on a detached head repository', async t => {
test('Fetch all tags on a detached head repository', async (t) => {
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -101,7 +101,7 @@ test('Fetch all tags on a detached head repository', async t => {
t.deepEqual((await getTags('master', {cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort());
});
test('Fetch all tags on a repository with a detached head from branch (CircleCI)', async t => {
test('Fetch all tags on a repository with a detached head from branch (CircleCI)', async (t) => {
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -126,7 +126,7 @@ test('Fetch all tags on a repository with a detached head from branch (CircleCI)
t.deepEqual((await getTags('master', {cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0', 'v2.0.0'].sort());
});
test('Fetch all tags on a detached head repository with outdated cached repo (GitLab CI)', async t => {
test('Fetch all tags on a detached head repository with outdated cached repo (GitLab CI)', async (t) => {
const {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -154,7 +154,7 @@ test('Fetch all tags on a detached head repository with outdated cached repo (Gi
t.deepEqual((await getTags('master', {cwd: cloneCwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0', 'v1.2.0'].sort());
});
test('Verify if a branch exists', async t => {
test('Verify if a branch exists', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -169,7 +169,7 @@ test('Verify if a branch exists', async t => {
t.falsy(await isRefExists('next', {cwd}));
});
test('Get all branches', async t => {
test('Get all branches', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
@@ -183,12 +183,12 @@ test('Get all branches', async t => {
t.deepEqual((await getBranches(repositoryUrl, {cwd})).sort(), ['master', 'second-branch', 'third-branch'].sort());
});
test('Return empty array if there are no branches', async t => {
test('Return empty array if there are no branches', async (t) => {
const {cwd, repositoryUrl} = await initGit(true);
t.deepEqual(await getBranches(repositoryUrl, {cwd}), []);
});
test('Get the commit sha for a given tag', async t => {
test('Get the commit sha for a given tag', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -199,7 +199,7 @@ test('Get the commit sha for a given tag', async t => {
t.is(await getTagHead('v1.0.0', {cwd}), commits[0].hash);
});
test('Return git remote repository url from config', async t => {
test('Return git remote repository url from config', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add remote.origin.url config
@@ -208,7 +208,7 @@ test('Return git remote repository url from config', async t => {
t.is(await repoUrl({cwd}), 'git@hostname.com:owner/package.git');
});
test('Return git remote repository url set while cloning', async t => {
test('Return git remote repository url set while cloning', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
@@ -218,14 +218,14 @@ test('Return git remote repository url set while cloning', async t => {
t.is(await repoUrl({cwd}), repositoryUrl);
});
test('Return falsy if git repository url is not set', async t => {
test('Return falsy if git repository url is not set', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
t.falsy(await repoUrl({cwd}));
});
test('Add tag on head commit', async t => {
test('Add tag on head commit', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const commits = await gitCommits(['Test commit'], {cwd});
@@ -235,7 +235,7 @@ test('Add tag on head commit', async t => {
await t.is(await gitCommitTag(commits[0].hash, {cwd}), 'tag_name');
});
test('Push tag to remote repository', async t => {
test('Push tag to remote repository', async (t) => {
// Create a git repository with a remote, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['Test commit'], {cwd});
@@ -246,13 +246,13 @@ test('Push tag to remote repository', async t => {
t.is(await gitRemoteTagHead(repositoryUrl, 'tag_name', {cwd}), commits[0].hash);
});
test('Push tag to remote repository with remote branch ahead', async t => {
test('Push tag to remote repository with remote branch ahead', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
const tmpRepo = await gitShallowClone(repositoryUrl);
await gitCommits(['Second'], {cwd: tmpRepo});
await gitPush('origin', 'master', {cwd: tmpRepo});
const temporaryRepo = await gitShallowClone(repositoryUrl);
await gitCommits(['Second'], {cwd: temporaryRepo});
await gitPush('origin', 'master', {cwd: temporaryRepo});
await tag('tag_name', 'HEAD', {cwd});
await push(repositoryUrl, {cwd});
@@ -260,40 +260,40 @@ test('Push tag to remote repository with remote branch ahead', async t => {
t.is(await gitRemoteTagHead(repositoryUrl, 'tag_name', {cwd}), commits[0].hash);
});
test('Return "true" if in a Git repository', async t => {
test('Return "true" if in a Git repository', async (t) => {
// Create a git repository with a remote, set the current working directory at the root of the repo
const {cwd} = await gitRepo(true);
t.true(await isGitRepo({cwd}));
});
test('Return falsy if not in a Git repository', async t => {
test('Return falsy if not in a Git repository', async (t) => {
const cwd = tempy.directory();
t.falsy(await isGitRepo({cwd}));
});
test('Return "true" for valid tag names', async t => {
test('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('Return falsy for invalid tag names', async t => {
test('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('Throws error if obtaining the tags fails', async t => {
test('Throws error if obtaining the tags fails', async (t) => {
const cwd = tempy.directory();
await t.throwsAsync(getTags('master', {cwd}));
});
test('Return "true" if repository is up to date', async t => {
test('Return "true" if repository is up to date', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
@@ -301,7 +301,7 @@ test('Return "true" if repository is up to date', async t => {
t.true(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Return falsy if repository is not up to date', async t => {
test('Return falsy if repository is not up to date', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitCommits(['Second'], {cwd});
@@ -309,14 +309,14 @@ test('Return falsy if repository is not up to date', async t => {
t.true(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
const tmpRepo = await gitShallowClone(repositoryUrl);
await gitCommits(['Third'], {cwd: tmpRepo});
await gitPush('origin', 'master', {cwd: tmpRepo});
const temporaryRepo = await gitShallowClone(repositoryUrl);
await gitCommits(['Third'], {cwd: temporaryRepo});
await gitPush('origin', 'master', {cwd: temporaryRepo});
t.falsy(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Return falsy if detached head repository is not up to date', async t => {
test('Return falsy if detached head repository is not up to date', async (t) => {
let {cwd, repositoryUrl} = await gitRepo();
const [commit] = await gitCommits(['First'], {cwd});
@@ -328,7 +328,7 @@ test('Return falsy if detached head repository is not up to date', async t => {
t.falsy(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Get a commit note', async t => {
test('Get a commit note', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -339,7 +339,7 @@ test('Get a commit note', async t => {
t.deepEqual(await getNote(commits[0].hash, {cwd}), {note: 'note'});
});
test('Return empty object if there is no commit note', async t => {
test('Return empty object if there is no commit note', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -348,7 +348,7 @@ test('Return empty object if there is no commit note', async t => {
t.deepEqual(await getNote(commits[0].hash, {cwd}), {});
});
test('Throw error if a commit note in invalid', async t => {
test('Throw error if a commit note in invalid', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -359,7 +359,7 @@ test('Throw error if a commit note in invalid', async t => {
await t.throwsAsync(getNote(commits[0].hash, {cwd}));
});
test('Add a commit note', async t => {
test('Add a commit note', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -370,7 +370,7 @@ test('Add a commit note', async t => {
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
});
test('Overwrite a commit note', async t => {
test('Overwrite a commit note', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Add commits to the master branch
@@ -382,7 +382,7 @@ test('Overwrite a commit note', async t => {
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note2"}');
});
test('Unshallow and fetch repository with notes', async t => {
test('Unshallow and fetch repository with notes', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo();
// Add commits to the master branch
@@ -401,7 +401,7 @@ test('Unshallow and fetch repository with notes', async t => {
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
});
test('Fetch all notes on a detached head repository', async t => {
test('Fetch all notes on a detached head repository', async (t) => {
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
+40 -36
View File
@@ -82,12 +82,13 @@ async function initBareRepo(repositoryUrl, branch = 'master') {
*
* @returns {Array<Commit>} The created commits, in reverse order (to match `git log` order).
*/
async function gitCommits(messages, execaOpts) {
async function gitCommits(messages, execaOptions) {
await pEachSeries(
messages,
async message => (await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOpts)).stdout
async (message) =>
(await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOptions)).stdout
);
return (await gitGetCommits(undefined, execaOpts)).slice(0, messages.length);
return (await gitGetCommits(undefined, execaOptions)).slice(0, messages.length);
}
/**
@@ -98,13 +99,16 @@ async function gitCommits(messages, execaOpts) {
*
* @return {Array<Object>} The list of parsed commits.
*/
async function gitGetCommits(from, execaOpts) {
async function gitGetCommits(from, execaOptions) {
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
return (
await getStream.array(
gitLogParser.parse({_: `${from ? from + '..' : ''}HEAD`}, {...execaOpts, env: {...process.env, ...execaOpts.env}})
gitLogParser.parse(
{_: `${from ? from + '..' : ''}HEAD`},
{...execaOptions, env: {...process.env, ...execaOptions.env}}
)
)
).map(commit => {
).map((commit) => {
commit.message = commit.message.trim();
commit.gitTags = commit.gitTags.trim();
return commit;
@@ -118,8 +122,8 @@ async function gitGetCommits(from, execaOpts) {
* @param {Boolean} create to create the branch, `false` to checkout an existing branch.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function gitCheckout(branch, create, execaOpts) {
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch], execaOpts);
async function gitCheckout(branch, create, execaOptions) {
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch], execaOptions);
}
/**
@@ -128,8 +132,8 @@ async function gitCheckout(branch, create, execaOpts) {
* @param {String} repositoryUrl The repository remote URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function gitFetch(repositoryUrl, execaOpts) {
await execa('git', ['fetch', repositoryUrl], execaOpts);
async function gitFetch(repositoryUrl, execaOptions) {
await execa('git', ['fetch', repositoryUrl], execaOptions);
}
/**
@@ -139,8 +143,8 @@ async function gitFetch(repositoryUrl, execaOpts) {
*
* @return {String} The sha of the head commit in the current git repository.
*/
async function gitHead(execaOpts) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOpts)).stdout;
async function gitHead(execaOptions) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOptions)).stdout;
}
/**
@@ -150,8 +154,8 @@ async function gitHead(execaOpts) {
* @param {String} [sha] The commit on which to create the tag. If undefined the tag is created on the last commit.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function gitTagVersion(tagName, sha, execaOpts) {
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName], execaOpts);
async function gitTagVersion(tagName, sha, execaOptions) {
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName], execaOptions);
}
/**
@@ -207,8 +211,8 @@ async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
* @param {String} value Config value.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function gitAddConfig(name, value, execaOpts) {
await execa('git', ['config', '--add', name, value], execaOpts);
async function gitAddConfig(name, value, execaOptions) {
await execa('git', ['config', '--add', name, value], execaOptions);
}
/**
@@ -219,8 +223,8 @@ async function gitAddConfig(name, value, execaOpts) {
*
* @return {String} The sha of the commit associated with `tagName` on the local repository.
*/
async function gitTagHead(tagName, execaOpts) {
return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
async function gitTagHead(tagName, execaOptions) {
return (await execa('git', ['rev-list', '-1', tagName], execaOptions)).stdout;
}
/**
@@ -232,11 +236,11 @@ async function gitTagHead(tagName, execaOpts) {
*
* @return {String} The sha of the commit associated with `tagName` on the remote repository.
*/
async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
return (await execa('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOpts)).stdout
async function gitRemoteTagHead(repositoryUrl, tagName, execaOptions) {
return (await execa('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOptions)).stdout
.split('\n')
.filter(tag => Boolean(tag))
.map(tag => tag.match(/^(?<tag>\S+)/)[1])[0];
.filter((tag) => Boolean(tag))
.map((tag) => tag.match(/^(?<tag>\S+)/)[1])[0];
}
/**
@@ -247,8 +251,8 @@ async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
*
* @return {String} The tag associatedwith the sha in parameter or `null`.
*/
async function gitCommitTag(gitHead, execaOpts) {
return (await execa('git', ['describe', '--tags', '--exact-match', gitHead], execaOpts)).stdout;
async function gitCommitTag(gitHead, execaOptions) {
return (await execa('git', ['describe', '--tags', '--exact-match', gitHead], execaOptions)).stdout;
}
/**
@@ -260,8 +264,8 @@ async function gitCommitTag(gitHead, execaOpts) {
*
* @throws {Error} if the push failed.
*/
async function gitPush(repositoryUrl, branch, execaOpts) {
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOpts);
async function gitPush(repositoryUrl, branch, execaOptions) {
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOptions);
}
/**
@@ -270,8 +274,8 @@ async function gitPush(repositoryUrl, branch, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function merge(ref, execaOpts) {
await execa('git', ['merge', '--no-ff', ref], execaOpts);
async function merge(ref, execaOptions) {
await execa('git', ['merge', '--no-ff', ref], execaOptions);
}
/**
@@ -280,8 +284,8 @@ async function merge(ref, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function mergeFf(ref, execaOpts) {
await execa('git', ['merge', '--ff', ref], execaOpts);
async function mergeFf(ref, execaOptions) {
await execa('git', ['merge', '--ff', ref], execaOptions);
}
/**
@@ -290,8 +294,8 @@ async function mergeFf(ref, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function rebase(ref, execaOpts) {
await execa('git', ['rebase', ref], execaOpts);
async function rebase(ref, execaOptions) {
await execa('git', ['rebase', ref], execaOptions);
}
/**
@@ -301,8 +305,8 @@ async function rebase(ref, execaOpts) {
* @param {String} ref The ref to add the note to.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function gitAddNote(note, ref, execaOpts) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-m', note, ref], execaOpts);
async function gitAddNote(note, ref, execaOptions) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-m', note, ref], execaOptions);
}
/**
@@ -311,8 +315,8 @@ async function gitAddNote(note, ref, execaOpts) {
* @param {String} ref The ref to get the note from.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function gitGetNote(ref, execaOpts) {
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout;
async function gitGetNote(ref, execaOptions) {
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOptions)).stdout;
}
module.exports = {
+34 -8
View File
@@ -3,7 +3,7 @@ const {repeat} = require('lodash');
const hideSensitive = require('../lib/hide-sensitive');
const {SECRET_REPLACEMENT, SECRET_MIN_SIZE} = require('../lib/definitions/constants');
test('Replace multiple sensitive environment variable values', t => {
test('Replace multiple sensitive environment variable values', (t) => {
const env = {SOME_PASSWORD: 'password', SOME_TOKEN: 'secret'};
t.is(
hideSensitive(env)(`https://user:${env.SOME_PASSWORD}@host.com?token=${env.SOME_TOKEN}`),
@@ -11,7 +11,7 @@ test('Replace multiple sensitive environment variable values', t => {
);
});
test('Replace multiple occurences of sensitive environment variable values', t => {
test('Replace multiple occurences of sensitive environment variable values', (t) => {
const env = {secretKey: 'secret'};
t.is(
hideSensitive(env)(`https://user:${env.secretKey}@host.com?token=${env.secretKey}`),
@@ -19,7 +19,20 @@ test('Replace multiple occurences of sensitive environment variable values', t =
);
});
test('Escape regexp special characters', t => {
test('Replace sensitive environment variable matching specific regex for "private"', (t) => {
const env = {privateKey: 'secret', GOPRIVATE: 'host.com'};
t.is(hideSensitive(env)(`https://host.com?token=${env.privateKey}`), `https://host.com?token=${SECRET_REPLACEMENT}`);
});
test('Replace url-encoded environment variable', (t) => {
const env = {privateKey: 'secret '};
t.is(
hideSensitive(env)(`https://host.com?token=${encodeURI(env.privateKey)}`),
`https://host.com?token=${SECRET_REPLACEMENT}`
);
});
test('Escape regexp special characters', (t) => {
const env = {SOME_CREDENTIALS: 'p$^{.+}\\w[a-z]o.*rd'};
t.is(
hideSensitive(env)(`https://user:${env.SOME_CREDENTIALS}@host.com`),
@@ -27,15 +40,23 @@ test('Escape regexp special characters', t => {
);
});
test('Accept "undefined" input', t => {
test('Escape regexp special characters in url-encoded environment variable', (t) => {
const env = {SOME_PASSWORD: 'secret password p$^{.+}\\w[a-z]o.*rd)('};
t.is(
hideSensitive(env)(`https://user:${encodeURI(env.SOME_PASSWORD)}@host.com`),
`https://user:${SECRET_REPLACEMENT}@host.com`
);
});
test('Accept "undefined" input', (t) => {
t.is(hideSensitive({})(), undefined);
});
test('Return same string if no environment variable has to be replaced', t => {
test('Return same string if no environment variable has to be replaced', (t) => {
t.is(hideSensitive({})('test'), 'test');
});
test('Exclude empty environment variables from the regexp', t => {
test('Exclude empty environment variables from the regexp', (t) => {
const env = {SOME_PASSWORD: 'password', SOME_TOKEN: ''};
t.is(
hideSensitive(env)(`https://user:${env.SOME_PASSWORD}@host.com?token=`),
@@ -43,11 +64,16 @@ test('Exclude empty environment variables from the regexp', t => {
);
});
test('Exclude empty environment variables from the regexp if there is only empty ones', t => {
test('Exclude empty environment variables from the regexp if there is only empty ones', (t) => {
t.is(hideSensitive({SOME_PASSWORD: '', SOME_TOKEN: ' \n '})(`https://host.com?token=`), 'https://host.com?token=');
});
test('Exclude environment variables with value shorter than SECRET_MIN_SIZE from the regexp', t => {
test('Exclude nonsensitive GOPRIVATE environment variable for Golang projects from the regexp', (t) => {
const env = {GOPRIVATE: 'host.com'};
t.is(hideSensitive(env)(`https://host.com?token=`), 'https://host.com?token=');
});
test('Exclude environment variables with value shorter than SECRET_MIN_SIZE from the regexp', (t) => {
const SHORT_TOKEN = repeat('a', SECRET_MIN_SIZE - 1);
const LONG_TOKEN = repeat('b', SECRET_MIN_SIZE);
const env = {SHORT_TOKEN, LONG_TOKEN};
+38 -38
View File
@@ -26,7 +26,7 @@ const {
const requireNoCache = proxyquire.noPreserveCache();
const pluginNoop = require.resolve('./fixtures/plugin-noop');
test.beforeEach(t => {
test.beforeEach((t) => {
// Stub the logger functions
t.context.log = spy();
t.context.error = spy();
@@ -41,7 +41,7 @@ test.beforeEach(t => {
};
});
test('Plugins are called with expected values', async t => {
test('Plugins are called with expected values', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -390,7 +390,7 @@ test('Plugins are called with expected values', async t => {
t.is(env.GIT_COMMITTER_EMAIL, COMMIT_EMAIL);
});
test('Use custom tag format', async t => {
test('Use custom tag format', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitTagVersion('test-1.0.0', undefined, {cwd});
@@ -437,7 +437,7 @@ test('Use custom tag format', async t => {
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease.gitTag, {cwd}), nextRelease.gitHead);
});
test('Use new gitHead, and recreate release notes if a prepare plugin create a commit', async t => {
test('Use new gitHead, and recreate release notes if a prepare plugin create a commit', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -511,7 +511,7 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease.gitTag, {cwd}), commits[0].hash);
});
test('Make a new release when a commit is forward-ported to an upper branch', async t => {
test('Make a new release when a commit is forward-ported to an upper branch', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial release'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -558,7 +558,7 @@ test('Make a new release when a commit is forward-ported to an upper branch', as
t.is(success.callCount, 1);
});
test('Publish a pre-release version', async t => {
test('Publish a pre-release version', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -605,7 +605,7 @@ test('Publish a pre-release version', async t => {
t.is(await gitGetNote('v1.1.0-beta.2', {cwd}), '{"channels":["beta"]}');
});
test('Publish releases from different branch on the same channel', async t => {
test('Publish releases from different branch on the same channel', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -668,7 +668,7 @@ test('Publish releases from different branch on the same channel', async t => {
t.is(addChannel.callCount, 0);
});
test('Publish pre-releases the same channel as regular releases', async t => {
test('Publish pre-releases the same channel as regular releases', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -716,7 +716,7 @@ test('Publish pre-releases the same channel as regular releases', async t => {
t.is(releases[0].gitTag, 'v1.1.0-beta.2');
});
test('Do not add pre-releases to a different channel', async t => {
test('Do not add pre-releases to a different channel', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial release'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -844,13 +844,13 @@ async function addChannelMacro(t, mergeFunction) {
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease.gitTag, {cwd}), nextRelease.gitHead);
}
addChannelMacro.title = providedTitle => `Add version to a channel after a merge (${providedTitle})`;
addChannelMacro.title = (providedTitle) => `Add version to a channel after a merge (${providedTitle})`;
test('fast-forward', addChannelMacro, mergeFf);
test('non fast-forward', addChannelMacro, merge);
test('rebase', addChannelMacro, rebase);
test('Call all "success" plugins even if one errors out', async t => {
test('Call all "success" plugins even if one errors out', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -916,7 +916,7 @@ test('Call all "success" plugins even if one errors out', async t => {
]);
});
test('Log all "verifyConditions" errors', async t => {
test('Log all "verifyConditions" errors', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -957,7 +957,7 @@ test('Log all "verifyConditions" errors', async t => {
t.deepEqual(fail.args[0][1].errors, [error2, error3]);
});
test('Log all "verifyRelease" errors', async t => {
test('Log all "verifyRelease" errors', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -998,7 +998,7 @@ test('Log all "verifyRelease" errors', async t => {
t.deepEqual(fail.args[0][1].errors, [error1, error2]);
});
test('Dry-run skips addChannel, prepare, publish and success', async t => {
test('Dry-run skips addChannel, prepare, publish and success', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -1064,7 +1064,7 @@ test('Dry-run skips addChannel, prepare, publish and success', async t => {
t.true(t.context.warn.calledWith(`Skip step "success" of plugin "[Function: ${success.name}]" in dry-run mode`));
});
test('Dry-run skips fail', async t => {
test('Dry-run skips fail', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -1104,7 +1104,7 @@ test('Dry-run skips fail', async t => {
t.true(t.context.warn.calledWith(`Skip step "fail" of plugin "[Function: ${fail.name}]" in dry-run mode`));
});
test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async t => {
test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -1169,7 +1169,7 @@ test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async t
t.is(success.callCount, 0);
});
test('Dry-run does not print changelog if "generateNotes" return "undefined"', async t => {
test('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 {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -1213,7 +1213,7 @@ test('Dry-run does not print changelog if "generateNotes" return "undefined"', a
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['Release note for version 2.0.0:']);
});
test('Allow local releases with "noCi" option', async t => {
test('Allow local releases with "noCi" option', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -1258,7 +1258,7 @@ test('Allow local releases with "noCi" option', async t => {
const semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: false, branch: 'master', isPr: true}),
'env-ci': () => ({isCi: false, branch: 'master', isPr: false}),
});
t.truthy(
await semanticRelease(options, {
@@ -1282,7 +1282,7 @@ test('Allow local releases with "noCi" option', async t => {
t.is(success.callCount, 1);
});
test('Accept "undefined" value returned by "generateNotes" and "false" by "publish" and "addChannel"', async t => {
test('Accept "undefined" value returned by "generateNotes" and "false" by "publish" and "addChannel"', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -1351,13 +1351,13 @@ test('Accept "undefined" value returned by "generateNotes" and "false" by "publi
t.deepEqual(success.args[1][1].releases, [{pluginName: '[Function: functionStub]'}]);
});
test('Returns false if triggered by a PR', async t => {
test('Returns false if triggered by a PR', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
const semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: true}),
'env-ci': () => ({isCi: true, branch: 'master', prBranch: 'patch-1', isPr: true}),
});
t.false(
@@ -1372,7 +1372,7 @@ test('Returns false if triggered by a PR', async t => {
);
});
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current maintenance branch', async t => {
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current maintenance branch', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -1423,7 +1423,7 @@ test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the curren
t.regex(error.details, /A valid branch could be `master`./);
});
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current release branch', async t => {
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current release branch', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -1475,7 +1475,7 @@ test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the curren
t.regex(error.details, /A valid branch could be `next` or `next-major`./);
});
test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a maintenance branch', async t => {
test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a maintenance branch', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -1545,7 +1545,7 @@ test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a ma
t.truthy(errors[0].details);
});
test('Returns false value if triggered on an outdated clone', async t => {
test('Returns false value if triggered on an outdated clone', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo(true);
const repoDir = cwd;
@@ -1573,7 +1573,7 @@ test('Returns false value if triggered on an outdated clone', async t => {
]);
});
test('Returns false if not running from the configured branch', async t => {
test('Returns false if not running from the configured branch', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
const options = {
@@ -1609,7 +1609,7 @@ test('Returns false if not running from the configured branch', async t => {
);
});
test('Returns false if there is no relevant changes', async t => {
test('Returns false if there is no relevant changes', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -1658,7 +1658,7 @@ test('Returns false if there is no relevant changes', async t => {
);
});
test('Exclude commits with [skip release] or [release skip] from analysis', async t => {
test('Exclude commits with [skip release] or [release skip] from analysis', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -1707,7 +1707,7 @@ test('Exclude commits with [skip release] or [release skip] from analysis', asyn
t.deepEqual(analyzeCommits.args[0][1].commits[0], commits[commits.length - 1]);
});
test('Log both plugins errors and errors thrown by "fail" plugin', async t => {
test('Log both plugins errors and errors thrown by "fail" plugin', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const pluginError = new SemanticReleaseError('Plugin error', 'ERR');
const failError1 = new Error('Fail error 1');
@@ -1733,7 +1733,7 @@ test('Log both plugins errors and errors thrown by "fail" plugin', async t => {
t.is(t.context.error.args[t.context.error.args.length - 2][1], failError2);
});
test('Call "fail" only if a plugin returns a SemanticReleaseError', async t => {
test('Call "fail" only if a plugin returns a SemanticReleaseError', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const pluginError = new Error('Plugin error');
const fail = stub().resolves();
@@ -1757,7 +1757,7 @@ test('Call "fail" only if a plugin returns a SemanticReleaseError', async t => {
t.is(t.context.error.args[t.context.error.args.length - 1][1], pluginError);
});
test('Throw SemanticReleaseError if repositoryUrl is not set and cannot be found from repo config', async t => {
test('Throw SemanticReleaseError if repositoryUrl is not set and cannot be found from repo config', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
@@ -1778,7 +1778,7 @@ test('Throw SemanticReleaseError if repositoryUrl is not set and cannot be found
t.truthy(errors[0].details);
});
test('Throw an Error if plugin returns an unexpected value', async t => {
test('Throw an Error if plugin returns an unexpected value', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd, repositoryUrl} = await gitRepo(true);
// Add commits to the master branch
@@ -1812,7 +1812,7 @@ test('Throw an Error if plugin returns an unexpected value', async t => {
t.regex(error.details, /string/);
});
test('Hide sensitive information passed to "fail" plugin', async t => {
test('Hide sensitive information passed to "fail" plugin', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const fail = stub().resolves();
@@ -1844,14 +1844,14 @@ test('Hide sensitive information passed to "fail" plugin', async t => {
t.is(error.message, `Message: Exposing token ${SECRET_REPLACEMENT}`);
t.is(error.details, `Details: Exposing token ${SECRET_REPLACEMENT}`);
Object.getOwnPropertyNames(error).forEach(prop => {
Object.getOwnPropertyNames(error).forEach((prop) => {
if (isString(error[prop])) {
t.notRegex(error[prop], new RegExp(escapeRegExp(env.MY_TOKEN)));
}
});
});
test('Hide sensitive information passed to "success" plugin', async t => {
test('Hide sensitive information passed to "success" plugin', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial release'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
@@ -1887,14 +1887,14 @@ test('Hide sensitive information passed to "success" plugin', async t => {
t.is(release.name, `Name: Exposing token ${SECRET_REPLACEMENT}`);
t.is(release.url, `URL: Exposing token ${SECRET_REPLACEMENT}`);
Object.getOwnPropertyNames(release).forEach(prop => {
Object.getOwnPropertyNames(release).forEach((prop) => {
if (isString(release[prop])) {
t.notRegex(release[prop], new RegExp(escapeRegExp(env.MY_TOKEN)));
}
});
});
test('Get all commits including the ones not in the shallow clone', async t => {
test('Get all commits including the ones not in the shallow clone', async (t) => {
let {cwd, repositoryUrl} = await gitRepo(true);
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitCommits(['First', 'Second', 'Third'], {cwd});
+53 -12
View File
@@ -6,6 +6,7 @@ const {writeJson, readJson} = require('fs-extra');
const execa = require('execa');
const {WritableStreamBuffer} = require('stream-buffers');
const delay = require('delay');
const getAuthUrl = require('../lib/get-git-auth-url');
const {SECRET_REPLACEMENT} = require('../lib/definitions/constants');
const {
gitHead,
@@ -58,7 +59,7 @@ test.after.always(async () => {
await Promise.all([gitbox.stop(), npmRegistry.stop(), mockServer.stop()]);
});
test('Release patch, minor and major versions', async t => {
test('Release patch, minor and major versions', async (t) => {
const packageName = 'test-release';
const owner = 'git';
// Create a git repository, set the current working directory at the root of the repo
@@ -304,7 +305,7 @@ test('Release patch, minor and major versions', async t => {
await mockServer.verify(updateReleaseMock);
});
test('Exit with 1 if a plugin is not found', async t => {
test('Exit with 1 if a plugin is not found', async (t) => {
const packageName = 'test-plugin-not-found';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
@@ -322,7 +323,7 @@ test('Exit with 1 if a plugin is not found', async t => {
t.regex(stderr, /Cannot find module/);
});
test('Exit with 1 if a shareable config is not found', async t => {
test('Exit with 1 if a shareable config is not found', async (t) => {
const packageName = 'test-config-not-found';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
@@ -340,7 +341,7 @@ test('Exit with 1 if a shareable config is not found', async t => {
t.regex(stderr, /Cannot find module/);
});
test('Exit with 1 if a shareable config reference a not found plugin', async t => {
test('Exit with 1 if a shareable config reference a not found plugin', async (t) => {
const packageName = 'test-config-ref-not-found';
const owner = 'test-repo';
const shareable = {analyzeCommits: 'non-existing-path'};
@@ -361,7 +362,7 @@ test('Exit with 1 if a shareable config reference a not found plugin', async t =
t.regex(stderr, /Cannot find module/);
});
test('Dry-run', async t => {
test('Dry-run', async (t) => {
const packageName = 'test-dry-run';
const owner = 'git';
// Create a git repository, set the current working directory at the root of the repo
@@ -397,7 +398,7 @@ test('Dry-run', async t => {
await mockServer.verify(verifyMock);
});
test('Allow local releases with "noCi" option', async t => {
test('Allow local releases with "noCi" option', async (t) => {
const envNoCi = {...env};
delete envNoCi.TRAVIS;
delete envNoCi.CI;
@@ -456,7 +457,7 @@ test('Allow local releases with "noCi" option', async t => {
await mockServer.verify(createReleaseMock);
});
test('Pass options via CLI arguments', async t => {
test('Pass options via CLI arguments', async (t) => {
const packageName = 'test-cli';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
@@ -505,7 +506,7 @@ test('Pass options via CLI arguments', async t => {
t.log(`+ released ${releasedVersion} with head ${releasedGitHead}`);
});
test('Run via JS API', async t => {
test('Run via JS API', async (t) => {
const semanticRelease = requireNoCache('..', {
'./lib/logger': {log: () => {}, error: () => {}, stdout: () => {}},
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
@@ -564,7 +565,7 @@ test('Run via JS API', async t => {
await mockServer.verify(createReleaseMock);
});
test('Log unexpected errors from plugins and exit with 1', async t => {
test('Log unexpected errors from plugins and exit with 1', async (t) => {
const packageName = 'test-unexpected-error';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
@@ -591,7 +592,7 @@ test('Log unexpected errors from plugins and exit with 1', async t => {
t.is(exitCode, 1);
});
test('Log errors inheriting SemanticReleaseError and exit with 1', async t => {
test('Log errors inheriting SemanticReleaseError and exit with 1', async (t) => {
const packageName = 'test-inherited-error';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
@@ -614,7 +615,7 @@ test('Log errors inheriting SemanticReleaseError and exit with 1', async t => {
t.is(exitCode, 1);
});
test('Exit with 1 if missing permission to push to the remote repository', async t => {
test('Exit with 1 if missing permission to push to the remote repository', async (t) => {
const packageName = 'unauthorized';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
@@ -636,7 +637,7 @@ test('Exit with 1 if missing permission to push to the remote repository', async
t.is(exitCode, 1);
});
test('Hide sensitive environment variable values from the logs', async t => {
test('Hide sensitive environment variable values from the logs', async (t) => {
const packageName = 'log-secret';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
@@ -656,3 +657,43 @@ test('Hide sensitive environment variable values from the logs', async t => {
t.regex(stderr, new RegExp(`Error: Console token ${escapeRegExp(SECRET_REPLACEMENT)}`));
t.regex(stderr, new RegExp(`Throw error: Exposing ${escapeRegExp(SECRET_REPLACEMENT)}`));
});
test('Use the valid git credentials when multiple are provided', async (t) => {
const {cwd, authUrl} = await gitbox.createRepo('test-auth');
t.is(
await getAuthUrl({
cwd,
env: {
GITHUB_TOKEN: 'dummy',
GITLAB_TOKEN: 'trash',
BB_TOKEN_BASIC_AUTH: gitbox.gitCredential,
GIT_ASKPASS: 'echo',
GIT_TERMINAL_PROMPT: 0,
},
branch: {name: 'master'},
options: {repositoryUrl: 'http://toto@localhost:2080/git/test-auth.git'},
}),
authUrl
);
});
test('Use the repository URL as is if none of the given git credentials are valid', async (t) => {
const {cwd} = await gitbox.createRepo('test-invalid-auth');
const dummyUrl = 'http://toto@localhost:2080/git/test-auth.git';
t.is(
await getAuthUrl({
cwd,
env: {
GITHUB_TOKEN: 'dummy',
GITLAB_TOKEN: 'trash',
GIT_ASKPASS: 'echo',
GIT_TERMINAL_PROMPT: 0,
},
branch: {name: 'master'},
options: {repositoryUrl: dummyUrl},
}),
dummyUrl
);
});
+22 -22
View File
@@ -5,7 +5,7 @@ const normalize = require('../../lib/plugins/normalize');
const cwd = process.cwd();
test.beforeEach(t => {
test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.error = stub();
@@ -19,7 +19,7 @@ test.beforeEach(t => {
};
});
test('Normalize and load plugin from string', t => {
test('Normalize and load plugin from string', (t) => {
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
'verifyConditions',
@@ -32,7 +32,7 @@ test('Normalize and load plugin from string', t => {
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/plugin-noop"']);
});
test('Normalize and load plugin from object', t => {
test('Normalize and load plugin from object', (t) => {
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
'publish',
@@ -45,7 +45,7 @@ test('Normalize and load plugin from object', t => {
t.deepEqual(t.context.success.args[0], ['Loaded plugin "publish" from "./test/fixtures/plugin-noop"']);
});
test('Normalize and load plugin from a base file path', t => {
test('Normalize and load plugin from a base file path', (t) => {
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-noop', {
'./plugin-noop': './test/fixtures',
});
@@ -57,7 +57,7 @@ test('Normalize and load plugin from a base file path', t => {
]);
});
test('Wrap plugin in a function that add the "pluginName" to the error"', async t => {
test('Wrap plugin in a function that add the "pluginName" to the error"', async (t) => {
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-error', {
'./plugin-error': './test/fixtures',
});
@@ -67,7 +67,7 @@ test('Wrap plugin in a function that add the "pluginName" to the error"', async
t.is(error.pluginName, './plugin-error');
});
test('Wrap plugin in a function that add the "pluginName" to multiple errors"', async t => {
test('Wrap plugin in a function that add the "pluginName" to multiple errors"', async (t) => {
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-errors', {
'./plugin-errors': './test/fixtures',
});
@@ -78,7 +78,7 @@ test('Wrap plugin in a function that add the "pluginName" to multiple errors"',
}
});
test('Normalize and load plugin from function', t => {
test('Normalize and load plugin from function', (t) => {
const pluginFunction = () => {};
const plugin = normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
@@ -86,7 +86,7 @@ test('Normalize and load plugin from function', t => {
t.is(typeof plugin, 'function');
});
test('Normalize and load plugin that retuns multiple functions', t => {
test('Normalize and load plugin that retuns multiple functions', (t) => {
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
'verifyConditions',
@@ -98,7 +98,7 @@ test('Normalize and load plugin that retuns multiple functions', t => {
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/multi-plugin"']);
});
test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async t => {
test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async (t) => {
const analyzeCommits = stub().resolves(2);
const plugin = normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
@@ -116,7 +116,7 @@ test('Wrap "analyzeCommits" plugin in a function that validate the output of the
t.regex(error.details, /2/);
});
test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async t => {
test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async (t) => {
const generateNotes = stub().resolves(2);
const plugin = normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
@@ -134,7 +134,7 @@ test('Wrap "generateNotes" plugin in a function that validate the output of the
t.regex(error.details, /2/);
});
test('Wrap "publish" plugin in a function that validate the output of the plugin', async t => {
test('Wrap "publish" plugin in a function that validate the output of the plugin', async (t) => {
const publish = stub().resolves(2);
const plugin = normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
@@ -152,7 +152,7 @@ test('Wrap "publish" plugin in a function that validate the output of the plugin
t.regex(error.details, /2/);
});
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async t => {
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async (t) => {
const addChannel = stub().resolves(2);
const plugin = normalize(
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
@@ -170,7 +170,7 @@ test('Wrap "addChannel" plugin in a function that validate the output of the plu
t.regex(error.details, /2/);
});
test('Plugin is called with "pluginConfig" (with object definition) and input', async t => {
test('Plugin is called with "pluginConfig" (with object definition) and input', async (t) => {
const pluginFunction = stub().resolves();
const pluginConf = {path: pluginFunction, conf: 'confValue'};
const options = {global: 'globalValue'};
@@ -185,7 +185,7 @@ test('Plugin is called with "pluginConfig" (with object definition) and input',
);
});
test('Plugin is called with "pluginConfig" (with array definition) and input', async t => {
test('Plugin is called with "pluginConfig" (with array definition) and input', async (t) => {
const pluginFunction = stub().resolves();
const pluginConf = [pluginFunction, {conf: 'confValue'}];
const options = {global: 'globalValue'};
@@ -200,8 +200,8 @@ test('Plugin is called with "pluginConfig" (with array definition) and input', a
);
});
test('Prevent plugins to modify "pluginConfig"', async t => {
const pluginFunction = stub().callsFake(pluginConfig => {
test('Prevent plugins to modify "pluginConfig"', async (t) => {
const pluginFunction = stub().callsFake((pluginConfig) => {
pluginConfig.conf.subConf = 'otherConf';
});
const pluginConf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
@@ -213,7 +213,7 @@ test('Prevent plugins to modify "pluginConfig"', async t => {
t.is(options.globalConf.globalSubConf, 'originalGlobalConf');
});
test('Prevent plugins to modify its input', async t => {
test('Prevent plugins to modify its input', async (t) => {
const pluginFunction = stub().callsFake((pluginConfig, options) => {
options.param.subParam = 'otherParam';
});
@@ -224,13 +224,13 @@ test('Prevent plugins to modify its input', async t => {
t.is(input.param.subParam, 'originalSubParam');
});
test('Return noop if the plugin is not defined', t => {
test('Return noop if the plugin is not defined', (t) => {
const plugin = normalize({cwd, options: {}, logger: t.context.logger});
t.is(plugin, noop);
});
test('Always pass a defined "pluginConfig" for plugin defined with string', async t => {
test('Always pass a defined "pluginConfig" for plugin defined with string', async (t) => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
@@ -243,7 +243,7 @@ test('Always pass a defined "pluginConfig" for plugin defined with string', asyn
t.deepEqual(pluginResult.pluginConfig, {});
});
test('Always pass a defined "pluginConfig" for plugin defined with path', async t => {
test('Always pass a defined "pluginConfig" for plugin defined with path', async (t) => {
// Call the normalize function with the path of a plugin that returns its config
const plugin = normalize(
{cwd, options: {}, logger: t.context.logger},
@@ -256,7 +256,7 @@ test('Always pass a defined "pluginConfig" for plugin defined with path', async
t.deepEqual(pluginResult.pluginConfig, {});
});
test('Throws an error if the plugin return an object without the expected plugin function', t => {
test('Throws an error if the plugin return an object without the expected plugin function', (t) => {
const error = t.throws(() =>
normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', './test/fixtures/multi-plugin', {})
);
@@ -267,7 +267,7 @@ test('Throws an error if the plugin return an object without the expected plugin
t.truthy(error.details);
});
test('Throws an error if the plugin is not found', t => {
test('Throws an error if the plugin is not found', (t) => {
t.throws(() => normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', 'non-existing-path', {}), {
message: /Cannot find module 'non-existing-path'/,
code: 'MODULE_NOT_FOUND',
+13 -13
View File
@@ -3,7 +3,7 @@ const {stub} = require('sinon');
const AggregateError = require('aggregate-error');
const pipeline = require('../../lib/plugins/pipeline');
test('Execute each function in series passing the same input', async t => {
test('Execute each function in series passing the same input', async (t) => {
const step1 = stub().resolves(1);
const step2 = stub().resolves(2);
const step3 = stub().resolves(3);
@@ -18,7 +18,7 @@ test('Execute each function in series passing the same input', async t => {
t.true(step2.calledBefore(step3));
});
test('Execute each function in series passing a transformed input from "getNextInput"', async t => {
test('Execute each function in series passing a transformed input from "getNextInput"', async (t) => {
const step1 = stub().resolves(1);
const step2 = stub().resolves(2);
const step3 = stub().resolves(3);
@@ -37,7 +37,7 @@ test('Execute each function in series passing a transformed input from "getNextI
t.true(step3.calledBefore(step4));
});
test('Execute each function in series passing the "lastResult" and "result" to "getNextInput"', async t => {
test('Execute each function in series passing the "lastResult" and "result" to "getNextInput"', async (t) => {
const step1 = stub().resolves(1);
const step2 = stub().resolves(2);
const step3 = stub().resolves(3);
@@ -55,13 +55,13 @@ test('Execute each function in series passing the "lastResult" and "result" to "
]);
});
test('Execute each function in series calling "transform" to modify the results', async t => {
test('Execute each function in series calling "transform" to modify the results', async (t) => {
const step1 = stub().resolves(1);
const step2 = stub().resolves(2);
const step3 = stub().resolves(3);
const step4 = stub().resolves(4);
const getNextInput = stub().returnsArg(0);
const transform = stub().callsFake(result => result + 1);
const transform = stub().callsFake((result) => result + 1);
const result = await pipeline([step1, step2, step3, step4], {getNextInput, transform})(5);
@@ -74,13 +74,13 @@ test('Execute each function in series calling "transform" to modify the results'
]);
});
test('Execute each function in series calling "transform" to modify the results with "settleAll"', async t => {
test('Execute each function in series calling "transform" to modify the results with "settleAll"', async (t) => {
const step1 = stub().resolves(1);
const step2 = stub().resolves(2);
const step3 = stub().resolves(3);
const step4 = stub().resolves(4);
const getNextInput = stub().returnsArg(0);
const transform = stub().callsFake(result => result + 1);
const transform = stub().callsFake((result) => result + 1);
const result = await pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput, transform})(5);
@@ -93,7 +93,7 @@ test('Execute each function in series calling "transform" to modify the results
]);
});
test('Stop execution and throw error if 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);
@@ -108,7 +108,7 @@ test('Stop execution and throw error if a step rejects', async t => {
t.true(step3.notCalled);
});
test('Throw all errors from the first step throwing an AggregateError', async t => {
test('Throw all errors from the first step throwing an AggregateError', async (t) => {
const error1 = new Error('test error 1');
const error2 = new Error('test error 2');
@@ -124,7 +124,7 @@ test('Throw all errors from the first step throwing an AggregateError', async t
t.true(step3.notCalled);
});
test('Execute all even if a Promise rejects', async t => {
test('Execute all even if a Promise rejects', async (t) => {
const error1 = new Error('test error 1');
const error2 = new Error('test error 2');
const step1 = stub().resolves(1);
@@ -139,7 +139,7 @@ test('Execute all even if a Promise rejects', async t => {
t.true(step3.calledWith(0));
});
test('Throw all errors from all steps throwing an AggregateError', async t => {
test('Throw all errors from all steps throwing an AggregateError', async (t) => {
const error1 = new Error('test error 1');
const error2 = new Error('test error 2');
const error3 = new Error('test error 3');
@@ -154,14 +154,14 @@ test('Throw all errors from all steps throwing an AggregateError', async t => {
t.true(step2.calledWith(0));
});
test('Execute each function in series passing a transformed input even if a step rejects', async t => {
test('Execute each function in series passing a transformed input even if a step rejects', async (t) => {
const error2 = new Error('test error 2');
const error3 = new Error('test error 3');
const step1 = stub().resolves(1);
const step2 = stub().rejects(error2);
const step3 = stub().rejects(error3);
const step4 = stub().resolves(4);
const getNextInput = (prevResult, result) => prevResult + result;
const getNextInput = (previousResult, result) => previousResult + result;
const errors = await t.throwsAsync(pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput})(0));
+14 -14
View File
@@ -8,14 +8,14 @@ const getPlugins = require('../../lib/plugins');
// Save the current working diretory
const cwd = process.cwd();
test.beforeEach(t => {
test.beforeEach((t) => {
// Stub the logger functions
t.context.log = stub();
t.context.success = stub();
t.context.logger = {log: t.context.log, success: t.context.success, scope: () => t.context.logger};
});
test('Export default plugins', t => {
test('Export default plugins', (t) => {
const plugins = getPlugins({cwd, options: {}, logger: t.context.logger}, {});
// Verify the module returns a function for each plugin
@@ -29,7 +29,7 @@ test('Export default plugins', t => {
t.is(typeof plugins.fail, 'function');
});
test('Export plugins based on steps config', t => {
test('Export plugins based on steps config', (t) => {
const plugins = getPlugins(
{
cwd,
@@ -55,7 +55,7 @@ test('Export plugins based on steps config', t => {
t.is(typeof plugins.fail, 'function');
});
test('Export plugins based on "plugins" config (array)', async t => {
test('Export plugins based on "plugins" config (array)', async (t) => {
const plugin1 = {verifyConditions: stub(), publish: stub()};
const plugin2 = {verifyConditions: stub(), verifyRelease: stub()};
const plugins = getPlugins(
@@ -84,7 +84,7 @@ test('Export plugins based on "plugins" config (array)', async t => {
t.is(typeof plugins.fail, 'function');
});
test('Export plugins based on "plugins" config (single definition)', async t => {
test('Export plugins based on "plugins" config (single definition)', async (t) => {
const plugin1 = {verifyConditions: stub(), publish: stub()};
const plugins = getPlugins({cwd, logger: t.context.logger, options: {plugins: plugin1}}, {});
@@ -105,7 +105,7 @@ test('Export plugins based on "plugins" config (single definition)', async t =>
t.is(typeof plugins.fail, 'function');
});
test('Merge global options, "plugins" options and step options', async t => {
test('Merge global options, "plugins" options and step options', async (t) => {
const plugin1 = [{verifyConditions: stub(), publish: stub()}, {pluginOpt1: 'plugin1'}];
const plugin2 = [{verifyConditions: stub()}, {pluginOpt2: 'plugin2'}];
const plugin3 = [stub(), {pluginOpt3: 'plugin3'}];
@@ -129,7 +129,7 @@ test('Merge global options, "plugins" options and step options', async t => {
t.deepEqual(plugin3[0].args[0][0], {globalOpt: 'global', pluginOpt3: 'plugin3'});
});
test('Unknown steps of plugins configured in "plugins" are ignored', t => {
test('Unknown steps of plugins configured in "plugins" are ignored', (t) => {
const plugin1 = {verifyConditions: () => {}, unknown: () => {}};
const plugins = getPlugins({cwd, logger: t.context.logger, options: {plugins: [plugin1]}}, {});
@@ -137,7 +137,7 @@ test('Unknown steps of plugins configured in "plugins" are ignored', t => {
t.is(plugins.unknown, undefined);
});
test('Export plugins loaded from the dependency of a shareable config module', async t => {
test('Export plugins loaded from the dependency of a shareable config module', async (t) => {
const cwd = tempy.directory();
await copy(
'./test/fixtures/plugin-noop.js',
@@ -170,7 +170,7 @@ test('Export plugins loaded from the dependency of a shareable config module', a
t.is(typeof plugins.fail, 'function');
});
test('Export plugins loaded from the dependency of a shareable config file', async t => {
test('Export plugins loaded from the dependency of a shareable config file', async (t) => {
const cwd = tempy.directory();
await copy('./test/fixtures/plugin-noop.js', path.resolve(cwd, 'plugin/plugin-noop.js'));
await outputFile(path.resolve(cwd, 'shareable-config.js'), '');
@@ -200,7 +200,7 @@ test('Export plugins loaded from the dependency of a shareable config file', asy
t.is(typeof plugins.fail, 'function');
});
test('Use default when only options are passed for a single plugin', t => {
test('Use default when only options are passed for a single plugin', (t) => {
const analyzeCommits = {};
const generateNotes = {};
const publish = {};
@@ -234,7 +234,7 @@ test('Use default when only options are passed for a single plugin', t => {
t.falsy(fail.path);
});
test('Merge global options with plugin options', async t => {
test('Merge global options with plugin options', async (t) => {
const plugins = getPlugins(
{
cwd,
@@ -253,7 +253,7 @@ test('Merge global options with plugin options', async t => {
t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'});
});
test('Throw an error for each invalid plugin configuration', t => {
test('Throw an error for each invalid plugin configuration', (t) => {
const errors = [
...t.throws(() =>
getPlugins(
@@ -283,7 +283,7 @@ test('Throw an error for each invalid plugin configuration', t => {
t.is(errors[3].code, 'EPLUGINCONF');
});
test('Throw EPLUGINSCONF error if the "plugins" option contains an old plugin definition (returns a function)', t => {
test('Throw EPLUGINSCONF error if the "plugins" option contains an old plugin definition (returns a function)', (t) => {
const errors = [
...t.throws(() =>
getPlugins(
@@ -303,7 +303,7 @@ test('Throw EPLUGINSCONF error if the "plugins" option contains an old plugin de
t.is(errors[1].code, 'EPLUGINSCONF');
});
test('Throw EPLUGINSCONF error for each invalid definition if the "plugins" option', t => {
test('Throw EPLUGINSCONF error for each invalid definition if the "plugins" option', (t) => {
const errors = [
...t.throws(() =>
getPlugins({cwd, logger: t.context.logger, options: {plugins: [1, {path: 1}, [() => {}, {}, {}]]}}, {})
+5 -5
View File
@@ -1,7 +1,7 @@
const test = require('ava');
const {validatePlugin, validateStep, loadPlugin, parseConfig} = require('../../lib/plugins/utils');
test('validatePlugin', t => {
test('validatePlugin', (t) => {
const path = 'plugin-module';
const options = {option1: 'value1', option2: 'value2'};
@@ -25,7 +25,7 @@ test('validatePlugin', t => {
t.false(validatePlugin({path: 1}), 'Object definition, wrong path');
});
test('validateStep: optional plugin configuration', t => {
test('validateStep: optional plugin configuration', (t) => {
const type = {multiple: true, required: false};
// Empty config
@@ -107,7 +107,7 @@ test('validateStep: optional plugin configuration', t => {
);
});
test('validateStep: required plugin configuration', t => {
test('validateStep: required plugin configuration', (t) => {
const type = {required: true};
// Empty config
@@ -189,7 +189,7 @@ test('validateStep: required plugin configuration', t => {
);
});
test('loadPlugin', t => {
test('loadPlugin', (t) => {
const cwd = process.cwd();
const func = () => {};
@@ -202,7 +202,7 @@ test('loadPlugin', t => {
t.is(func, loadPlugin({cwd}, func, {}), 'Defined as a function');
});
test('parseConfig', t => {
test('parseConfig', (t) => {
const path = 'plugin-module';
const options = {option1: 'value1', option2: 'value2'};
+14 -14
View File
@@ -17,14 +17,14 @@ const {
isSameChannel,
} = require('../lib/utils');
test('extractErrors', t => {
test('extractErrors', (t) => {
const errors = [new Error('Error 1'), new Error('Error 2')];
t.deepEqual(extractErrors(new AggregateError(errors)), errors);
t.deepEqual(extractErrors(errors[0]), [errors[0]]);
});
test('tagsToVersions', t => {
test('tagsToVersions', (t) => {
t.deepEqual(tagsToVersions([{version: '1.0.0'}, {version: '1.1.0'}, {version: '1.2.0'}]), [
'1.0.0',
'1.1.0',
@@ -32,7 +32,7 @@ test('tagsToVersions', t => {
]);
});
test('isMajorRange', t => {
test('isMajorRange', (t) => {
t.false(isMajorRange('1.1.x'));
t.false(isMajorRange('1.11.x'));
t.false(isMajorRange('11.1.x'));
@@ -48,7 +48,7 @@ test('isMajorRange', t => {
t.true(isMajorRange('1.X'));
});
test('isMaintenanceRange', t => {
test('isMaintenanceRange', (t) => {
t.true(isMaintenanceRange('1.1.x'));
t.true(isMaintenanceRange('11.1.x'));
t.true(isMaintenanceRange('11.11.x'));
@@ -69,7 +69,7 @@ test('isMaintenanceRange', t => {
t.false(isMaintenanceRange('^1.0.0'));
});
test('getUpperBound', t => {
test('getUpperBound', (t) => {
t.is(getUpperBound('1.x.x'), '2.0.0');
t.is(getUpperBound('1.X.X'), '2.0.0');
t.is(getUpperBound('10.x.x'), '11.0.0');
@@ -84,7 +84,7 @@ test('getUpperBound', t => {
t.is(getUpperBound('foo'), undefined);
});
test('getLowerBound', t => {
test('getLowerBound', (t) => {
t.is(getLowerBound('1.x.x'), '1.0.0');
t.is(getLowerBound('1.X.X'), '1.0.0');
t.is(getLowerBound('10.x.x'), '10.0.0');
@@ -99,7 +99,7 @@ test('getLowerBound', t => {
t.is(getLowerBound('foo'), undefined);
});
test('highest', t => {
test('highest', (t) => {
t.is(highest('1.0.0', '2.0.0'), '2.0.0');
t.is(highest('1.1.1', '1.1.0'), '1.1.1');
t.is(highest(null, '1.0.0'), '1.0.0');
@@ -107,14 +107,14 @@ test('highest', t => {
t.is(highest(), undefined);
});
test('lowest', t => {
test('lowest', (t) => {
t.is(lowest('1.0.0', '2.0.0'), '1.0.0');
t.is(lowest('1.1.1', '1.1.0'), '1.1.0');
t.is(lowest(null, '1.0.0'), '1.0.0');
t.is(lowest(), undefined);
});
test.serial('getLatestVersion', t => {
test.serial('getLatestVersion', (t) => {
t.is(getLatestVersion(['1.2.3-alpha.3', '1.2.0', '1.0.1', '1.0.0-alpha.1']), '1.2.0');
t.is(getLatestVersion(['1.2.3-alpha.3', '1.2.3-alpha.2']), undefined);
@@ -127,7 +127,7 @@ test.serial('getLatestVersion', t => {
t.is(getLatestVersion([]), undefined);
});
test.serial('getEarliestVersion', t => {
test.serial('getEarliestVersion', (t) => {
t.is(getEarliestVersion(['1.2.3-alpha.3', '1.2.0', '1.0.0', '1.0.1-alpha.1']), '1.0.0');
t.is(getEarliestVersion(['1.2.3-alpha.3', '1.2.3-alpha.2']), undefined);
@@ -143,7 +143,7 @@ test.serial('getEarliestVersion', t => {
t.is(getEarliestVersion([]), undefined);
});
test('getFirstVersion', t => {
test('getFirstVersion', (t) => {
t.is(getFirstVersion(['1.2.0', '1.0.0', '1.3.0', '1.1.0', '1.4.0'], []), '1.0.0');
t.is(
getFirstVersion(
@@ -167,16 +167,16 @@ test('getFirstVersion', t => {
);
});
test('getRange', t => {
test('getRange', (t) => {
t.is(getRange('1.0.0', '1.1.0'), '>=1.0.0 <1.1.0');
t.is(getRange('1.0.0'), '>=1.0.0');
});
test('makeTag', t => {
test('makeTag', (t) => {
t.is(makeTag(`v\${version}`, '1.0.0'), 'v1.0.0');
});
test('isSameChannel', t => {
test('isSameChannel', (t) => {
t.true(isSameChannel('next', 'next'));
t.true(isSameChannel(null, undefined));
t.true(isSameChannel(false, undefined));
+7 -7
View File
@@ -3,7 +3,7 @@ const tempy = require('tempy');
const verify = require('../lib/verify');
const {gitRepo} = require('./helpers/git-utils');
test('Throw a AggregateError', async t => {
test('Throw a AggregateError', async (t) => {
const {cwd} = await gitRepo();
const options = {branches: [{name: 'master'}, {name: ''}]};
@@ -27,7 +27,7 @@ test('Throw a AggregateError', async t => {
t.truthy(errors[3].details);
});
test('Throw a SemanticReleaseError if does not run on a git repository', async t => {
test('Throw a SemanticReleaseError if does not run on a git repository', async (t) => {
const cwd = tempy.directory();
const options = {branches: []};
@@ -39,7 +39,7 @@ test('Throw a SemanticReleaseError if does not run on a git repository', async t
t.truthy(errors[0].details);
});
test('Throw a SemanticReleaseError if the "tagFormat" is not valid', async t => {
test('Throw a SemanticReleaseError if the "tagFormat" is not valid', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `?\${version}`, branches: []};
@@ -51,7 +51,7 @@ test('Throw a SemanticReleaseError if the "tagFormat" is not valid', async t =>
t.truthy(errors[0].details);
});
test('Throw a SemanticReleaseError if the "tagFormat" does not contains the "version" variable', async t => {
test('Throw a SemanticReleaseError if the "tagFormat" does not contains the "version" variable', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const options = {repositoryUrl, tagFormat: 'test', branches: []};
@@ -63,7 +63,7 @@ test('Throw a SemanticReleaseError if the "tagFormat" does not contains the "ver
t.truthy(errors[0].details);
});
test('Throw a SemanticReleaseError if the "tagFormat" contains multiple "version" variables', async t => {
test('Throw a SemanticReleaseError if the "tagFormat" contains multiple "version" variables', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `\${version}v\${version}`, branches: []};
@@ -75,7 +75,7 @@ test('Throw a SemanticReleaseError if the "tagFormat" contains multiple "version
t.truthy(errors[0].details);
});
test('Throw a SemanticReleaseError for each invalid branch', async t => {
test('Throw a SemanticReleaseError for each invalid branch', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const options = {
repositoryUrl,
@@ -109,7 +109,7 @@ test('Throw a SemanticReleaseError for each invalid branch', async t => {
t.truthy(errors[5].details);
});
test('Return "true" if all verification pass', async t => {
test('Return "true" if all verification pass', async (t) => {
const {cwd, repositoryUrl} = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `v\${version}`, branches: [{name: 'master'}]};