Compare commits

...
56 Commits
Author SHA1 Message Date
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
Pierre Vanduynslager 55be0ba2b1 fix: add repositoryUrl in logs 2020-02-17 14:09:01 -05:00
Raphaël HuchetandGitHub ec516a34bf docs(configuration): fix CLI usage with --branches option (#1465) 2020-02-17 10:05:33 -08:00
greenkeeper[bot]andGregor Martynus 5f89e6bfc0 chore(package): update nock to version 12.0.0 2020-02-16 13:54:44 -08:00
greenkeeper[bot]andPierre Vanduynslager 195e5b18c7 chore(package): update xo to version 0.26.0 2020-02-12 20:09:31 -05:00
Pierre Vanduynslager e7bede1866 fix: pass a branch name to getGitAuthUrl 2020-02-12 19:56:12 -05:00
greenkeeper[bot]andPierre Vanduynslager 8426b4203e chore(package): update tempy to version 0.4.0 2020-02-12 16:09:26 -05:00
Gregor MartynusandGitHub 804fc2a943 docs(Troubleshooting): release not found in prereleases branch (e.g. beta) after rebase on master) (#1444) 2020-02-08 11:03:48 -08:00
greenkeeper[bot]andPierre Vanduynslager 389e3316e9 chore(package): update got to version 10.5.2 2020-02-07 19:00:38 -05:00
Pierre Vanduynslager a93c96fec9 revert: fix: allow plugins to set environment variables to be used by other plugins
This reverts commit 68f7e928f9.
2020-02-07 17:50:14 -05:00
Pierre VanduynslagerandGregor Martynus 68f7e928f9 fix: allow plugins to set environment variables to be used by other plugins 2020-02-07 13:51:20 -08:00
Gregor MartynusandGitHub 8ce2d6e834 fix(package): update marked-terminal to version 4.0.0 2020-01-31 11:14:54 -05:00
Pierre Vanduynslager 45695b9183 fix(package): update @semantic-release/commit-analyzer to version 8.0.0 2020-01-28 11:31:01 -05:00
Pierre Vanduynslager 3c7b114eed fix(package): update @semantic-release/release-notes-generator to version 9.0.0 2020-01-28 11:31:01 -05:00
Pierre Vanduynslager f2b5826c0c fix(package): update @semantic-release/npm to version 7.0.0 2020-01-28 11:31:01 -05:00
greenkeeper[bot]andPierre Vanduynslager c48bd3ac36 fix(package): update @semantic-release/github to version 7.0.0 2020-01-28 11:31:01 -05:00
Pierre Vanduynslager bc97537c97 test: fix copy/paste typo in test titles 2020-01-28 10:33:24 -05:00
Mario LubenkaandPierre Vanduynslager c7e461dc1c docs: add @saithodev/semantic-release-gitea to community plugins 2020-01-27 14:23:38 -05:00
greenkeeper[bot]andPierre Vanduynslager ef1b8a0b91 chore(package): update ava to version 3.1.0 2020-01-27 13:52:59 -05:00
Pierre Vanduynslager bec57cd2ed chore: require Node.js >=10.18
BREAKING CHANGE: Require Node.js >= 10.18
2020-01-27 13:52:59 -05:00
69 changed files with 980 additions and 698 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+1 -1
View File
@@ -1,3 +1,3 @@
node_js:
- 12
- 10.13
- 10.18
+6 -6
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.
@@ -229,7 +229,7 @@ Before pushing your code changes make sure there are no linting errors with `npm
### Tests
Running the integration test requires you to install [Docker](https://docs.docker.com/engine/installation) on your machine.
Running the integration test requires you to install [Docker](https://docs.docker.com/engine/installation) on your machine. Note: the tests assume that running `git init` will create a `master` branch by default. If your local `git` is configured differently (see [`init.defaultBranch`](https://github.blog/2020-07-27-highlights-from-git-2-28/#introducing-init-defaultbranch)), change it temporarily when running the tests.
All the [semantic-release](https://github.com/semantic-release) repositories use [AVA](https://github.com/avajs/ava) for writing and running tests.
+11 -4
View File
@@ -101,7 +101,7 @@ In order to use **semantic-release** you need:
- To host your code in a [Git repository](https://git-scm.com)
- Use a Continuous Integration service that allows you to [securely set up credentials](docs/usage/ci-configuration.md#authentication)
- Git CLI version [2.7.1 or higher](docs/support/FAQ.md#why-does-semantic-release-require-git-version--271) installed in your Continuous Integration environment
- [Node.js](https://nodejs.org) version [10.13 or higher](docs/support/FAQ.md#why-does-semantic-release-require-node-version--1013) installed in your Continuous Integration environment
- [Node.js](https://nodejs.org) version [10.18 or higher](docs/support/FAQ.md#why-does-semantic-release-require-node-version--1018) installed in your Continuous Integration environment
## Documentation
@@ -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') {
+10
View File
@@ -95,3 +95,13 @@
- `verifyConditions`: Checks the project.clj is syntactically valid.
- `prepare`: Update the project.clj version and package the output jar file.
- `publish`: Publish the jar (and generated Maven metadata) to a maven repository (or clojars).
- [@saithodev/semantic-release-gitea](https://github.com/saitho/semantic-release-gitea)
- `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.
+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
@@ -10,7 +10,7 @@ In this example an [`NPM_TOKEN`](https://docs.npmjs.com/creating-and-viewing-aut
[GitHub Actions](https://github.com/features/actions) support [Workflows](https://help.github.com/en/articles/configuring-workflows), allowing to run tests on multiple Node versions and publish a release only when all test pass.
**Note**: The publish pipeline must run on [Node version >= 10.13](../support/FAQ.md#why-does-semantic-release-require-node-version--1013).
**Note**: The publish pipeline must run on [Node version >= 10.18](../support/FAQ.md#why-does-semantic-release-require-node-version--1018).
### `.github/workflows/release.yml` configuration for Node projects
@@ -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
+42 -7
View File
@@ -10,11 +10,11 @@ The [Authentication](../usage/ci-configuration.md#authentication) environment va
GitLab CI supports [Pipelines](https://docs.gitlab.com/ee/ci/pipelines.html) allowing to test on multiple Node versions and publishing a release only when all test pass.
**Note**: The publish pipeline must run a [Node >= 10.13 version](../support/FAQ.md#why-does-semantic-release-require-node-version--1013).
**Note**: The publish pipeline must run a [Node >= 10.18 version](../support/FAQ.md#why-does-semantic-release-require-node-version--1018).
### `.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"
}
}
```
+8 -6
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.
@@ -38,7 +40,7 @@ Yes with the [dry-run options](../usage/configuration.md#dryrun) which prints to
## Can I use semantic-release with Yarn?
If you are using a [local](../usage/installation.md#local-installation) **semantic-release** installation and run multiple CI jobs with different versions, the `yarn install` command will fail on jobs running with Node < 8 as **semantic-release** requires [Node >= 10.13](#why-does-semantic-release-require-node-version--1013) and specifies it in its `package.json`s [`engines`](https://docs.npmjs.com/files/package.json#engines) key.
If you are using a [local](../usage/installation.md#local-installation) **semantic-release** installation and run multiple CI jobs with different versions, the `yarn install` command will fail on jobs running with Node < 8 as **semantic-release** requires [Node >= 10.18](#why-does-semantic-release-require-node-version--1018) and specifies it in its `package.json`s [`engines`](https://docs.npmjs.com/files/package.json#engines) key.
The recommended solution is to use the [Yarn](https://yarnpkg.com) [--ignore-engines](https://yarnpkg.com/en/docs/cli/install#toc-yarn-install-ignore-engines) option to install the project dependencies on the CI environment, so Yarn will ignore the **semantic-release**'s `engines` key:
@@ -48,7 +50,7 @@ $ yarn install --ignore-engines
**Note**: Several CI services use Yarn by default if your repository contains a `yarn.lock` file. So you should override the install step to specify `yarn install --ignore-engines`.
Alternatively you can use a [global](../usage/installation.md#global-installation) **semantic-release** installation and make sure to install and run the `semantic-release` command only in a CI jobs running with Node >= 10.13.
Alternatively you can use a [global](../usage/installation.md#global-installation) **semantic-release** installation and make sure to install and run the `semantic-release` command only in a CI jobs running with Node >= 10.18.
If your CI environment provides [nvm](https://github.com/creationix/nvm) you can switch to Node 8 before installing and running the `semantic-release` command:
@@ -73,7 +75,7 @@ Yes, **semantic-release** is a Node CLI application but it can be used to publis
To publish a non-Node package (without a `package.json`) you would need to:
- Use a [global](../usage/installation.md#global-installation) **semantic-release** installation
- Set **semantic-release** [options](../usage/configuration.md#options) via [CLI arguments or rc file](../usage/configuration.md#configuration)
- Make sure your CI job executing the `semantic-release` command has access to [Node >= 10.13](#why-does-semantic-release-require-node-version--1013) to execute the `semantic-release` command
- Make sure your CI job executing the `semantic-release` command has access to [Node >= 10.18](#why-does-semantic-release-require-node-version--1018) to execute the `semantic-release` command
See the [CI configuration recipes](../recipes/README.md#ci-configurations) for more details on specific CI environments.
@@ -232,9 +234,9 @@ See [“Introduction to SemVer” - Irina Gebauer](https://blog.greenkeeper.io/i
In addition the [verify conditions step](../../README.md#release-steps) verifies that all necessary conditions for proceeding with a release are met, and a new release will be performed [only if all your tests pass](../usage/ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded).
## Why does semantic-release require Node version >= 10.13?
## Why does semantic-release require Node version >= 10.18?
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which **requires Node version 10.13 or higher**.
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which **requires Node version 10.18 or higher**.
See [Node version requirement](./node-version.md#node-version-requirement) for more details and solutions.
+2 -2
View File
@@ -8,9 +8,9 @@ See our [Node Support Policy](node-support-policy.md) for our long-term promise
## Recommended solution
### Run at least one CI job with Node >= 10.13
### Run at least one CI job with Node >= 10.18
The recommended approach is to run the `semantic-release` command from a CI job running on Node 10.13 or higher. This can either be a job used by your project to test on Node >= 10.13 or a dedicated job for the release steps.
The recommended approach is to run the `semantic-release` command from a CI job running on Node 10.18 or higher. This can either be a job used by your project to test on Node >= 10.18 or a dedicated job for the release steps.
See [CI configuration](../usage/ci-configuration.md) and [CI configuration recipes](../recipes/README.md#ci-configurations) for more details.
+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
+13
View File
@@ -54,3 +54,16 @@ $ git branch --contains <tag name>
$ git tag -d <tag name>
$ git push origin :refs/tags/<tag name>
```
## release not found release branch after `git push --force`
**semantic-release** is using both [git tags](https://git-scm.com/docs/git-tag) and [git notes](https://git-scm.com/docs/git-notes) to store information about which releases happened in which branch.
After a git history rewrite due to a `git push --force`, the git tags and notes referencing the commits that were rewritten are lost.
To recover from that situation, do the following:
1. Delete the tag(s) for the release(s) that have been lost from the git history. You can delete each tag from remote using `git push origin :[TAG NAME]`, e.g. `git push origin :v2.0.0-beta.1`. You can delete tags locally using `git tag -d [TAG NAME]`, e.g. `git tag -d v2.0.0-beta.1`.
2. Re-create the tags locally: `git tag [TAG NAME] [COMMIT HASH]`, where `[COMMIT HASH]` is the new commit that created the release for the lost tag. E.g. `git tag v2.0.0-beta.1 abcdef0`
3. Re-create the git notes for each release tag, e.g. `git notes --ref semantic-release add -f -m '{"channels":["beta"]}' v3.0.0-beta.1`. If the release was also published in the default channel (usually `master`), then set the first channel to `null`, e.g. `git notes --ref semantic-release add -f -m '{"channels":[null, "beta"]}'
4. Push the local notes: `git push --force origin refs/notes/semantic-release`. The `--force` is needed after the rebase. Be careful.
+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).
+5 -3
View File
@@ -38,7 +38,7 @@ The following three examples are the same.
- Via CLI argument:
```bash
$ semantic-release --branch next
$ semantic-release --branches next
```
**Note**: CLI arguments take precedence over options configured in the configuration 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)
+3 -3
View File
@@ -21,12 +21,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.
+10 -7
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.');
@@ -52,7 +53,7 @@ async function run(context, plugins) {
// Verify config
await verify(context);
options.repositoryUrl = await getGitAuthUrl(context);
options.repositoryUrl = await getGitAuthUrl({...context, branch: {name: ciBranch}});
context.branches = await getBranches(options.repositoryUrl, ciBranch, context);
context.branch = context.branches.find(({name}) => name === ciBranch);
@@ -66,7 +67,9 @@ async function run(context, plugins) {
}
logger[options.dryRun ? 'warn' : 'success'](
`Run automated release from branch ${ciBranch}${options.dryRun ? ' in dry-run mode' : ''}`
`Run automated release from branch ${ciBranch} on repository ${options.repositoryUrl}${
options.dryRun ? ' in dry-run mode' : ''
}`
);
try {
@@ -212,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}`);
@@ -226,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});
@@ -236,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)
@@ -251,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};
+9 -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 = {
@@ -28,6 +29,10 @@ Please make sure to add the \`repositoryUrl\` to the [semantic-release configura
'docs/usage/configuration.md'
)}).`,
}),
EDUPLICATEREPOSITORYKEY: ({packageJsonPath}) => ({
message: 'Duplicate `"repository"` key in package.json.',
details: `Your package.json file at ${packageJsonPath} has more than one "repository" keys.`,
}),
EGITNOPERMISSION: ({options: {repositoryUrl}, branch: {name}}) => ({
message: 'Cannot push to the Git repository.',
details: `**semantic-release** cannot push the version tag to the branch \`${name}\` on the remote Git repository with URL \`${repositoryUrl}\`.
@@ -84,7 +89,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}) => ({
+27 -12
View File
@@ -1,12 +1,15 @@
const {readFile} = require('fs').promises;
const {castArray, pickBy, isNil, isString, isPlainObject} = require('lodash');
const readPkgUp = require('read-pkg-up');
const findPkgUp = require('pkg-up');
const {cosmiconfig} = require('cosmiconfig');
const resolveFrom = require('resolve-from');
const findDuplicatedPropertyKeys = require('find-duplicated-property-keys');
const debug = require('debug')('semantic-release:config');
const {repoUrl} = require('./git');
const PLUGINS_DEFINITIONS = require('./definitions/plugins');
const plugins = require('./plugins');
const {validatePlugin, parseConfig} = require('./plugins/utils');
const getError = require('./get-error');
const CONFIG_NAME = 'release';
const CONFIG_FILES = [
@@ -19,14 +22,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};
let options = {...config, ...cliOptions};
if (options.ci === false) {
options.noCi = true;
}
@@ -38,14 +41,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 +61,7 @@ module.exports = async (context, opts) => {
return pluginsPath;
}, pluginsPath);
return {...result, ...extendsOpts};
return {...result, ...extendsOptions};
}, {}),
...options,
};
@@ -74,7 +77,7 @@ module.exports = async (context, opts) => {
{name: 'beta', prerelease: true},
{name: 'alpha', prerelease: true},
],
repositoryUrl: (await pkgRepoUrl({normalize: false, cwd})) || (await repoUrl({cwd, env})),
repositoryUrl: (await pkgRepoUrl({cwd})) || (await repoUrl({cwd, env})),
tagFormat: `v\${version}`,
plugins: [
'@semantic-release/commit-analyzer',
@@ -83,7 +86,7 @@ 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)} : {}),
};
@@ -92,7 +95,19 @@ module.exports = async (context, opts) => {
return {options, plugins: await plugins({...context, options}, pluginsPath)};
};
async function pkgRepoUrl(opts) {
const {packageJson} = (await readPkgUp(opts)) || {};
return packageJson && (isPlainObject(packageJson.repository) ? packageJson.repository.url : packageJson.repository);
async function pkgRepoUrl(options) {
const packageJsonPath = await findPkgUp(options);
if (!packageJsonPath) return;
const packageJsonString = await readFile(packageJsonPath, 'utf-8');
const result = findDuplicatedPropertyKeys(packageJsonString);
if (result.length > 0) {
throw getError('EDUPLICATEREPOSITORYKEY', {packageJsonPath});
}
const {repository} = require(packageJsonPath);
if (!repository) return;
return isPlainObject(repository) ? repository.url : repository;
}
+7 -1
View File
@@ -2,6 +2,7 @@ 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');
/**
* Determine the the git repository URL to use to push, either:
@@ -25,6 +26,8 @@ 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: '',
};
const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
@@ -40,9 +43,12 @@ 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]));
debug('SSH key auth failed, falling back to https.');
const envVar = Object.keys(GIT_TOKENS).find((envVar) => !isNil(env[envVar]));
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar] || ''}`;
if (gitCredentials) {
+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 = {
+11 -6
View File
@@ -1,12 +1,17 @@
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])).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()))
) {
+23 -20
View File
@@ -7,9 +7,7 @@
"files": [
"test/**/*.test.js"
],
"helpers": [
"test/helpers/**/*"
]
"timeout": "2m"
},
"bin": {
"semantic-release": "bin/semantic-release.js"
@@ -22,57 +20,58 @@
"Pierre Vanduynslager (https://twitter.com/@pvdlg_)"
],
"dependencies": {
"@semantic-release/commit-analyzer": "^7.0.0",
"@semantic-release/commit-analyzer": "^8.0.0",
"@semantic-release/error": "^2.2.0",
"@semantic-release/github": "^6.0.0",
"@semantic-release/npm": "^6.0.0",
"@semantic-release/release-notes-generator": "^7.1.2",
"@semantic-release/github": "^7.0.0",
"@semantic-release/npm": "^7.0.0",
"@semantic-release/release-notes-generator": "^9.0.0",
"aggregate-error": "^3.0.0",
"cosmiconfig": "^6.0.0",
"debug": "^4.0.0",
"env-ci": "^5.0.0",
"execa": "^4.0.0",
"figures": "^3.0.0",
"find-duplicated-property-keys": "^1.2.2",
"find-versions": "^3.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-terminal": "^3.2.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",
"pkg-up": "^3.1.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"
},
"devDependencies": {
"ava": "^2.0.0",
"ava": "^3.1.0",
"clear-module": "^4.0.0",
"codecov": "^3.0.0",
"delay": "^4.0.0",
"dockerode": "^3.0.0",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"got": "^10.2.1",
"fs-extra": "^9.0.0",
"got": "^11.0.0",
"js-yaml": "^3.10.0",
"mockserver-client": "^5.1.1",
"nock": "^11.1.0",
"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.3.0",
"xo": "^0.25.0"
"tempy": "^0.5.0",
"xo": "^0.29.0"
},
"engines": {
"node": ">=10.13"
"node": ">=10.18"
},
"files": [
"bin",
@@ -128,6 +127,10 @@
},
"xo": {
"prettier": true,
"space": true
"space": true,
"rules": {
"unicorn/string-content": "off",
"node/no-unsupported-features/node-builtins": "off"
}
}
}
+8 -8
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {union} from 'lodash';
import semver from 'semver';
import proxyquire from 'proxyquire';
const test = require('ava');
const {union} = require('lodash');
const semver = require('semver');
const proxyquire = require('proxyquire');
const getBranch = (branches, branch) => branches.find(({name}) => name === branch);
const release = (branches, name, version) => getBranch(branches, name).tags.push({version});
@@ -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: []},
+4 -4
View File
@@ -1,8 +1,8 @@
import test from 'ava';
import expand from '../../lib/branches/expand';
import {gitRepo, gitCommits, gitCheckout, gitPush} from '../helpers/git-utils';
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});
+8 -8
View File
@@ -1,8 +1,8 @@
import test from 'ava';
import getTags from '../../lib/branches/get-tags';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout, gitAddNote} from '../helpers/git-utils';
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});
+15 -15
View File
@@ -1,9 +1,9 @@
import test from 'ava';
import normalize from '../../lib/branches/normalize';
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: []},
+31 -33
View File
@@ -1,28 +1,26 @@
import test from 'ava';
import {escapeRegExp} from 'lodash';
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import {SECRET_REPLACEMENT} from '../lib/definitions/constants';
const test = require('ava');
const {escapeRegExp} = require('lodash');
const proxyquire = require('proxyquire').noPreserveCache();
const {stub} = require('sinon');
const {SECRET_REPLACEMENT} = require('../lib/definitions/constants');
const requireNoCache = proxyquire.noPreserveCache();
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 = [
'',
@@ -65,7 +63,7 @@ test.serial('Pass options to semantic-release API', async t => {
'--debug',
'-d',
];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -88,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 = [
'',
@@ -107,7 +105,7 @@ test.serial('Pass options to semantic-release API with alias arguments', async t
'config2',
'--dry-run',
];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -121,10 +119,10 @@ 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 = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -135,10 +133,10 @@ 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 = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -147,10 +145,10 @@ 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 = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
await cli();
@@ -164,10 +162,10 @@ 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 = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -175,10 +173,10 @@ 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 = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -187,10 +185,10 @@ 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 = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -199,10 +197,10 @@ 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 = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -210,11 +208,11 @@ 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 = ['', ''];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv, env: {...process.env, ...env}}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv, env: {...process.env, ...env}}});
const exitCode = await cli();
+10 -10
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {maintenance, prerelease, release} from '../../lib/definitions/branches';
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'}]));
+9 -9
View File
@@ -1,8 +1,8 @@
import test from 'ava';
import plugins from '../../lib/definitions/plugins';
import {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} from '../../lib/definitions/constants';
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');
+12 -12
View File
@@ -1,16 +1,16 @@
import test from 'ava';
import {stub} from 'sinon';
import getCommits from '../lib/get-commits';
import {gitRepo, gitCommits, gitDetachedHead} from './helpers/git-utils';
const test = require('ava');
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
@@ -82,10 +82,10 @@ test('Get all commits between lastRelease.gitHead and a shas', async t => {
// Verify the commits created and retrieved by the module are identical
t.is(result.length, 1);
t.deepEqual(result, commits.slice(1, commits.length - 1));
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();
+49 -31
View File
@@ -1,12 +1,12 @@
import path from 'path';
import {format} from 'util';
import test from 'ava';
import {writeFile, outputJson} from 'fs-extra';
import {omit} from 'lodash';
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import yaml from 'js-yaml';
import {gitRepo, gitTagVersion, gitCommits, gitShallowClone, gitAddConfig} from './helpers/git-utils';
const path = require('path');
const {format} = require('util');
const test = require('ava');
const {writeFile, outputJson} = require('fs-extra');
const {omit} = require('lodash');
const proxyquire = require('proxyquire');
const {stub} = require('sinon');
const yaml = require('js-yaml');
const {gitRepo, gitTagVersion, gitCommits, gitShallowClone, gitAddConfig} = require('./helpers/git-utils');
const DEFAULT_PLUGINS = [
'@semantic-release/commit-analyzer',
@@ -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,7 +502,7 @@ 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']};
@@ -511,8 +511,26 @@ test('Throw an Error if one of the shareable config cannot be found', async t =>
await outputJson(path.resolve(cwd, 'package.json'), {release: pkhOptions});
await outputJson(path.resolve(cwd, 'shareable1.json'), options1);
const error = await t.throwsAsync(t.context.getConfig({cwd}), Error);
await t.throwsAsync(t.context.getConfig({cwd}), {
message: /Cannot find module 'non-existing-path'/,
code: 'MODULE_NOT_FOUND',
});
});
t.regex(error.message, /Cannot find module 'non-existing-path'/);
t.is(error.code, 'MODULE_NOT_FOUND');
test('Throw an Error if package.json has duplicate "repository" key', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
// Create package.json with duplicate "repository" key
await writeFile(
path.resolve(cwd, 'package.json'),
`{
"repository": "https://github.com/octocat/repository",
"repository": "https://github.com/octocat/repository"
}`
);
const error = await t.throwsAsync(t.context.getConfig({cwd}));
t.is(error.code, 'EDUPLICATEREPOSITORYKEY');
t.is(error.message, 'Duplicate `"repository"` key in package.json.');
});
+57 -29
View File
@@ -1,10 +1,10 @@
import test from 'ava';
import getAuthUrl from '../lib/get-git-auth-url';
import {gitRepo} from './helpers/git-utils';
const test = require('ava');
const getAuthUrl = require('../lib/get-git-auth-url');
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,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 "https" URL', async t => {
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 +147,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 +161,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 +174,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 +188,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 +202,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 +215,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 +229,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 +243,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 +257,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 +271,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 +285,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 +299,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 +340,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 +354,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 +368,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(
+6 -6
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import getLastRelease from '../lib/get-last-release';
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: {
+4 -4
View File
@@ -1,8 +1,8 @@
import test from 'ava';
import {spy} from 'sinon';
import getLogger from '../lib/get-logger';
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}});
+15 -15
View File
@@ -1,14 +1,14 @@
import test from 'ava';
import {stub} from 'sinon';
import getNextVersion from '../lib/get-next-version';
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: {
+11 -11
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import getReleaseToAdd from '../lib/get-release-to-add';
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',
+45 -45
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import tempy from 'tempy';
import {
const test = require('ava');
const tempy = require('tempy');
const {
getTagHead,
isRefExists,
fetch,
@@ -16,8 +16,8 @@ import {
getNote,
addNote,
fetchNotes,
} from '../lib/git';
import {
} = require('../lib/git');
const {
gitRepo,
gitCommits,
gitCheckout,
@@ -34,9 +34,9 @@ import {
gitGetNote,
gitFetch,
initGit,
} from './helpers/git-utils';
} = 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}), Error);
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});
+78 -49
View File
@@ -1,10 +1,10 @@
import tempy from 'tempy';
import execa from 'execa';
import fileUrl from 'file-url';
import pEachSeries from 'p-each-series';
import gitLogParser from 'git-log-parser';
import getStream from 'get-stream';
import {GIT_NOTE_REF} from '../../lib/definitions/constants';
const tempy = require('tempy');
const execa = require('execa');
const fileUrl = require('file-url');
const pEachSeries = require('p-each-series');
const gitLogParser = require('git-log-parser');
const getStream = require('get-stream');
const {GIT_NOTE_REF} = require('../../lib/definitions/constants');
/**
* Commit message information.
@@ -23,7 +23,7 @@ import {GIT_NOTE_REF} from '../../lib/definitions/constants';
* @param {Boolean} withRemote `true` to create a shallow clone of a bare repository.
* @return {String} The path of the repository
*/
export async function initGit(withRemote) {
async function initGit(withRemote) {
const cwd = tempy.directory();
await execa('git', ['init', ...(withRemote ? ['--bare'] : [])], {cwd});
@@ -41,7 +41,7 @@ export async function initGit(withRemote) {
* @param {String} [branch='master'] The branch to initialize.
* @return {String} The path of the clone if `withRemote` is `true`, the path of the repository otherwise.
*/
export async function gitRepo(withRemote, branch = 'master') {
async function gitRepo(withRemote, branch = 'master') {
let {cwd, repositoryUrl} = await initGit(withRemote);
if (withRemote) {
await initBareRepo(repositoryUrl, branch);
@@ -66,7 +66,7 @@ export async function gitRepo(withRemote, branch = 'master') {
* @param {String} repositoryUrl The URL of the bare repository.
* @param {String} [branch='master'] the branch to initialize.
*/
export async function initBareRepo(repositoryUrl, branch = 'master') {
async function initBareRepo(repositoryUrl, branch = 'master') {
const cwd = tempy.directory();
await execa('git', ['clone', '--no-hardlinks', repositoryUrl, cwd], {cwd});
await gitCheckout(branch, true, {cwd});
@@ -82,12 +82,13 @@ export async function initBareRepo(repositoryUrl, branch = 'master') {
*
* @returns {Array<Commit>} The created commits, in reverse order (to match `git log` order).
*/
export 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 @@ export async function gitCommits(messages, execaOpts) {
*
* @return {Array<Object>} The list of parsed commits.
*/
export 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 @@ export 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`.
*/
export 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 @@ export async function gitCheckout(branch, create, execaOpts) {
* @param {String} repositoryUrl The repository remote URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export 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 @@ export async function gitFetch(repositoryUrl, execaOpts) {
*
* @return {String} The sha of the head commit in the current git repository.
*/
export 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 @@ export 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`.
*/
export 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);
}
/**
@@ -163,7 +167,7 @@ export async function gitTagVersion(tagName, sha, execaOpts) {
* @param {Number} [depth=1] The number of commit to clone.
* @return {String} The path of the cloned repository.
*/
export async function gitShallowClone(repositoryUrl, branch = 'master', depth = 1) {
async function gitShallowClone(repositoryUrl, branch = 'master', depth = 1) {
const cwd = tempy.directory();
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, repositoryUrl, cwd], {
@@ -179,7 +183,7 @@ export async function gitShallowClone(repositoryUrl, branch = 'master', depth =
* @param {Number} head A commit sha of the remote repo that will become the detached head of the new one.
* @return {String} The path of the new repository.
*/
export async function gitDetachedHead(repositoryUrl, head) {
async function gitDetachedHead(repositoryUrl, head) {
const cwd = tempy.directory();
await execa('git', ['init'], {cwd});
@@ -189,7 +193,7 @@ export async function gitDetachedHead(repositoryUrl, head) {
return cwd;
}
export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
const cwd = tempy.directory();
await execa('git', ['init'], {cwd});
@@ -207,8 +211,8 @@ export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
* @param {String} value Config value.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export 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 @@ export async function gitAddConfig(name, value, execaOpts) {
*
* @return {String} The sha of the commit associated with `tagName` on the local repository.
*/
export 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 @@ export async function gitTagHead(tagName, execaOpts) {
*
* @return {String} The sha of the commit associated with `tagName` on the remote repository.
*/
export 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 @@ export async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
*
* @return {String} The tag associatedwith the sha in parameter or `null`.
*/
export 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 @@ export async function gitCommitTag(gitHead, execaOpts) {
*
* @throws {Error} if the push failed.
*/
export 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 @@ export async function gitPush(repositoryUrl, branch, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export 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 @@ export async function merge(ref, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export 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 @@ export async function mergeFf(ref, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export 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 @@ export async function rebase(ref, execaOpts) {
* @param {String} ref The ref to add the note to.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export 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,6 +315,31 @@ export async function gitAddNote(note, ref, execaOpts) {
* @param {String} ref The ref to get the note from.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export 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 = {
initGit,
gitRepo,
initBareRepo,
gitCommits,
gitGetCommits,
gitCheckout,
gitFetch,
gitHead,
gitTagVersion,
gitShallowClone,
gitDetachedHead,
gitDetachedHeadFromBranch,
gitAddConfig,
gitTagHead,
gitRemoteTagHead,
gitCommitTag,
gitPush,
merge,
mergeFf,
rebase,
gitAddNote,
gitGetNote,
};
+5 -5
View File
@@ -1,7 +1,7 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import pRetry from 'p-retry';
import {initBareRepo, gitShallowClone} from './git-utils';
const Docker = require('dockerode');
const getStream = require('get-stream');
const pRetry = require('p-retry');
const {initBareRepo, gitShallowClone} = require('./git-utils');
const IMAGE = 'pvdlg/docker-gitbox:latest';
const SERVER_PORT = 80;
@@ -69,4 +69,4 @@ async function createRepo(name, branch = 'master', description = `Repository ${n
return {cwd, repositoryUrl, authUrl};
}
export default {start, stop, gitCredential, createRepo};
module.exports = {start, stop, gitCredential, createRepo};
+6 -6
View File
@@ -1,8 +1,8 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import got from 'got';
import pRetry from 'p-retry';
import {mockServerClient} from 'mockserver-client';
const Docker = require('dockerode');
const getStream = require('get-stream');
const got = require('got');
const pRetry = require('p-retry');
const {mockServerClient} = require('mockserver-client');
const IMAGE = 'jamesdbloom/mockserver:latest';
const MOCK_SERVER_PORT = 1080;
@@ -100,4 +100,4 @@ function verify(expectation) {
return client.verify(expectation);
}
export default {start, stop, mock, verify, url};
module.exports = {start, stop, mock, verify, url};
+6 -6
View File
@@ -1,8 +1,8 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import got from 'got';
import delay from 'delay';
import pRetry from 'p-retry';
const Docker = require('dockerode');
const getStream = require('get-stream');
const got = require('got');
const delay = require('delay');
const pRetry = require('p-retry');
const IMAGE = 'semanticrelease/npm-registry-docker:latest';
const SERVER_PORT = 15986;
@@ -76,4 +76,4 @@ async function stop() {
await container.remove();
}
export default {start, stop, authEnv, url};
module.exports = {start, stop, authEnv, url};
+4 -2
View File
@@ -1,5 +1,7 @@
import execa from 'execa';
const execa = require('execa');
export async function npmView(packageName, env) {
async function npmView(packageName, env) {
return JSON.parse((await execa('npm', ['view', packageName, '--json'], {env})).stdout);
}
module.exports = {npmView};
+22 -12
View File
@@ -1,9 +1,9 @@
import test from 'ava';
import {repeat} from 'lodash';
import hideSensitive from '../lib/hide-sensitive';
import {SECRET_REPLACEMENT, SECRET_MIN_SIZE} from '../lib/definitions/constants';
const test = require('ava');
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,12 @@ 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('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 +32,15 @@ test('Escape regexp special characters', t => {
);
});
test('Accept "undefined" input', t => {
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 +48,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};
+51 -52
View File
@@ -1,13 +1,13 @@
import test from 'ava';
import {escapeRegExp, isString, sortBy, omit} from 'lodash';
import proxyquire from 'proxyquire';
import {spy, stub} from 'sinon';
import {WritableStreamBuffer} from 'stream-buffers';
import AggregateError from 'aggregate-error';
import SemanticReleaseError from '@semantic-release/error';
import {COMMIT_NAME, COMMIT_EMAIL, SECRET_REPLACEMENT} from '../lib/definitions/constants';
import {
gitHead as getGitHead,
const test = require('ava');
const {escapeRegExp, isString, sortBy, omit} = require('lodash');
const proxyquire = require('proxyquire');
const {spy, stub} = require('sinon');
const {WritableStreamBuffer} = require('stream-buffers');
const AggregateError = require('aggregate-error');
const SemanticReleaseError = require('@semantic-release/error');
const {COMMIT_NAME, COMMIT_EMAIL, SECRET_REPLACEMENT} = require('../lib/definitions/constants');
const {
gitHead: getGitHead,
gitCheckout,
gitTagHead,
gitRepo,
@@ -21,12 +21,12 @@ import {
rebase,
gitAddNote,
gitGetNote,
} from './helpers/git-utils';
} = require('./helpers/git-utils');
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
@@ -1807,12 +1807,12 @@ test('Throw an Error if plugin returns an unexpected value', async t => {
});
const error = await t.throwsAsync(
semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}),
Error
{instanceOf: SemanticReleaseError}
);
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();
@@ -1836,8 +1836,7 @@ test('Hide sensitive information passed to "fail" plugin', async t => {
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
await t.throwsAsync(
semanticRelease(options, {cwd, env, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}),
Error
semanticRelease(options, {cwd, env, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()})
);
const error = fail.args[0][1].errors[0];
@@ -1845,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});
@@ -1888,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});
+27 -27
View File
@@ -1,13 +1,13 @@
import path from 'path';
import proxyquire from 'proxyquire';
import test from 'ava';
import {escapeRegExp} from 'lodash';
import {writeJson, readJson} from 'fs-extra';
import execa from 'execa';
import {WritableStreamBuffer} from 'stream-buffers';
import delay from 'delay';
import {SECRET_REPLACEMENT} from '../lib/definitions/constants';
import {
const path = require('path');
const test = require('ava');
const proxyquire = require('proxyquire');
const {escapeRegExp} = require('lodash');
const {writeJson, readJson} = require('fs-extra');
const execa = require('execa');
const {WritableStreamBuffer} = require('stream-buffers');
const delay = require('delay');
const {SECRET_REPLACEMENT} = require('../lib/definitions/constants');
const {
gitHead,
gitTagHead,
gitRepo,
@@ -17,11 +17,11 @@ import {
gitCheckout,
merge,
gitGetNote,
} from './helpers/git-utils';
import {npmView} from './helpers/npm-utils';
import gitbox from './helpers/gitbox';
import mockServer from './helpers/mockserver';
import npmRegistry from './helpers/npm-registry';
} = require('./helpers/git-utils');
const {npmView} = require('./helpers/npm-utils');
const gitbox = require('./helpers/gitbox');
const mockServer = require('./helpers/mockserver');
const npmRegistry = require('./helpers/npm-registry');
/* eslint camelcase: ["error", {properties: "never"}] */
@@ -58,7 +58,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 +304,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 +322,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 +340,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 +361,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 +397,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 +456,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 +505,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 +564,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 +591,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 +614,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 +636,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');
+31 -33
View File
@@ -1,11 +1,11 @@
import test from 'ava';
import {noop} from 'lodash';
import {stub} from 'sinon';
import normalize from '../../lib/plugins/normalize';
const test = require('ava');
const {noop} = require('lodash');
const {stub} = require('sinon');
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,12 +267,10 @@ 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 => {
const error = t.throws(
() => normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', 'non-existing-path', {}),
Error
);
t.regex(error.message, /Cannot find module 'non-existing-path'/);
t.is(error.code, 'MODULE_NOT_FOUND');
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',
instanceOf: Error,
});
});
+21 -18
View File
@@ -1,9 +1,9 @@
import test from 'ava';
import {stub} from 'sinon';
import AggregateError from 'aggregate-error';
import pipeline from '../../lib/plugins/pipeline';
const test = require('ava');
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,19 +93,22 @@ 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);
const error = await t.throwsAsync(pipeline([step1, step2, step3])(0), Error);
const error = await t.throwsAsync(pipeline([step1, step2, step3])(0), {
instanceOf: Error,
message: 'test error',
});
t.is(error.message, 'test error');
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
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');
@@ -121,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);
@@ -136,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');
@@ -151,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));
+20 -20
View File
@@ -1,21 +1,21 @@
import path from 'path';
import test from 'ava';
import {copy, outputFile} from 'fs-extra';
import {stub} from 'sinon';
import tempy from 'tempy';
import getPlugins from '../../lib/plugins';
const path = require('path');
const test = require('ava');
const {copy, outputFile} = require('fs-extra');
const {stub} = require('sinon');
const tempy = require('tempy');
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}, [() => {}, {}, {}]]}}, {})
+7 -7
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {validatePlugin, validateStep, loadPlugin, parseConfig} from '../../lib/plugins/utils';
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'};
+18 -18
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import AggregateError from 'aggregate-error';
import {
const test = require('ava');
const AggregateError = require('aggregate-error');
const {
extractErrors,
tagsToVersions,
isMajorRange,
@@ -15,16 +15,16 @@ import {
getRange,
makeTag,
isSameChannel,
} from '../lib/utils';
} = 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));
+11 -11
View File
@@ -1,9 +1,9 @@
import test from 'ava';
import tempy from 'tempy';
import verify from '../lib/verify';
import {gitRepo} from './helpers/git-utils';
const test = require('ava');
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'}]};