Compare commits

..
Author SHA1 Message Date
Pierre Vanduynslager e722354f4f style: merge both filter functions in get-last-release 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 97748c5e25 fix: do not create tags in dry-run mode for released to add to a channel 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager aec96c791f fix: correctly determine release to add to a channel
- Add only the most recent release to a channel (rather than adding all the one not added yet)
- Avoid attempting to ad the version twice in case that version is already present in multiple upper branches
2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 5744c5ecd2 fix: add channel to publish success log 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 1275b8c005 revert: fix: update commit-analyzer dep version off errant publish
This reverts commit 8d8910577f.
2019-11-23 11:06:31 -05:00
Eddie LefflerandPierre Vanduynslager 8d8910577f fix: update commit-analyzer dep version off errant publish 2019-11-20 23:11:33 -05:00
EddieandPierre Vanduynslager 9890584007 fix: update plugins dependencies 2019-11-20 22:29:59 -05:00
Pierre Vanduynslager 63f51ae6dd fix: allow multiple branches with same channel 2019-11-20 16:17:24 -05:00
Pierre Vanduynslager 20e7a38cdb style: prettier formatting 2019-11-20 14:16:13 -05:00
Pierre Vanduynslager e06eb82cff chore: add Mockserver generated file to gitignore 2019-11-20 14:16:13 -05:00
Pierre Vanduynslager 751a5f1349 fix: handle branch properties set to false 2019-11-20 14:16:13 -05:00
Pierre Vanduynslager 00420a83c0 fix: simplify get-tags algorithm 2019-11-08 12:35:34 -05:00
Pierre Vanduynslager 131734873e fix: throws error if the commit associated with a tag cannot be found 2019-11-07 14:33:53 -05:00
Pierre Vanduynslager de77a799a8 fix: call getTagHead only when necessary 2019-11-07 14:33:53 -05:00
Pierre Vanduynslager 56186419a7 fix(package): update micromatch to version 4.0.2 2019-11-01 22:23:18 -04:00
Pierre Vanduynslager 2ec856eb1d docs: add requirements section to README 2019-11-01 16:58:55 -04:00
Pierre Vanduynslager cffe9a8d33 perf: use git tag --merge <branch> to filter tags present in a branch history
BREAKING CHANGE: Git CLI version 2.7.1 or higher is now required

The `--merge` option of the `git tag` command has been added in Git version 2.7.1 and is now used by semantic-release
2019-11-01 16:58:55 -04:00
Pierre Vanduynslager 844e0b07e0 fix: remove hack to workaround GitHub Rebase & Merge 2019-11-01 16:58:55 -04:00
Pierre Vanduynslager 1edae67326 fix: display erroring git commands properly 2019-11-01 16:58:55 -04:00
Pierre Vanduynslager e5344cd89b fix(package): update micromatch to version 3.1.10 2019-10-29 13:26:41 -04:00
Pierre Vanduynslager 2c655b2328 fix(package): update p-each-series to version 2.1.0 2019-10-29 13:26:08 -04:00
Pierre Vanduynslager 581998cff0 fix(package): update semver-diff to version 3.1.1 2019-10-29 13:25:07 -04:00
Pierre Vanduynslager 65c412293e fix(package): update @semantic-release/npm to version 6.0.0-beta.3 2019-10-29 12:01:59 -04:00
Pierre Vanduynslager 6053b89af3 fix(package): update @semantic-release/github to version 5.6.0-beta.1 2019-10-29 12:01:33 -04:00
Pierre Vanduynslager 3daf78081f Merge branch 'master' into beta 2019-10-29 11:57:31 -04:00
Justin DietzandPierre Vanduynslager 9eaf9552c1 docs(contributing): correct lint fix command 2019-10-27 21:56:05 -04:00
Pierre Vanduynslager 1693073814 fix(package): update execa to version 3.2.0 2019-10-26 00:26:50 -04:00
Pierre Vanduynslager 2f3d934069 fix: require Node.js >=8.16 2019-10-26 00:26:50 -04:00
f645547f2f docs(recipes): GitHub actions (#1317)
Co-Authored-By: Natan Sągol <m@merlinnot.com>
2019-10-23 10:47:24 -07:00
Pierre Vanduynslager 3438ec5c79 test: fix typo in test examples 2019-10-18 16:25:40 -04:00
Pierre Vanduynslager 4af8548366 fix: ignore custom port when converting ssh repo URL to https 2019-10-18 16:25:40 -04:00
Pierre Vanduynslager 95379270d6 fix(package): update execa to version 3.0.0 2019-10-14 12:56:56 -04:00
Pierre Vanduynslager ff60ac594c test: specify version of gitbox image 2019-10-10 11:57:58 -04:00
Pierre Vanduynslager 4b66303d2f docs: typo is link to workflow config 2019-10-10 11:57:58 -04:00
Alexander GandGregor Martynus dc19dfa2e9 docs: remove unnecessarily apostrophe
- improve punctuation of the how-to revert a release section
2019-10-10 07:32:14 -07:00
knidarknessandPierre Vanduynslager 79d22a2556 fix: clarify message for EGITNOPERMISSION error 2019-10-09 17:57:18 -04:00
Pierre Vanduynslager 9f2ec79d67 docs: limit list of suggested tools for commit linting 2019-10-09 16:42:52 -04:00
greenkeeper[bot]andPierre Vanduynslager 0e24022608 fix(package): update read-pkg-up to version 7.0.0 2019-09-27 10:37:25 -07:00
greenkeeper[bot]andPierre Vanduynslager 5986bf920e chore(package): update xo to version 0.25.0 2019-09-26 10:18:12 -07:00
greenkeeper[bot]andPierre Vanduynslager c86045ca49 chore(package): update dockerode to version 3.0.0 2019-09-24 13:37:46 -07:00
46 changed files with 884 additions and 696 deletions
+3
View File
@@ -131,3 +131,6 @@ yarn.lock
# Gitbook
_book
# Mockserver
CertificateAuthorityCertificate.pem
+1 -1
View File
@@ -6,7 +6,7 @@ services:
node_js:
- 12
- 10
- 8.15
- 8.16
# Trigger a push build on release and greenkeeper branches + PRs build on every branches
# Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147)
+1 -1
View File
@@ -224,7 +224,7 @@ All the [semantic-release](https://github.com/semantic-release) repositories use
Before pushing your code changes make sure there are no linting errors with `npm run lint`.
**Tips**:
- Most linting errors can be automatically fixed with `npm run lint --fix`.
- Most linting errors can be automatically fixed with `npm run lint -- --fix`.
- Install the [XO plugin](https://github.com/sindresorhus/xo#editor-plugins) for your editor to see linting errors directly in your editor and automatically fix them on save.
### Tests
+9 -1
View File
@@ -56,7 +56,7 @@ This removes the immediate connection between human emotions and version numbers
By default **semantic-release** uses [Angular Commit Message Conventions](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines). The commit message format can be changed with the [`preset` or `config` options](docs/usage/configuration.md#options) of the [@semantic-release/commit-analyzer](https://github.com/semantic-release/commit-analyzer#options) and [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator#options) plugins.
Tools such as [commitizen](https://github.com/commitizen/cz-cli), [commitlint](https://github.com/conventional-changelog/commitlint) or [semantic-git-commit-cli](https://github.com/JPeer264/node-semantic-git-commit-cli) can be used to help contributors and enforce valid commit messages.
Tools such as [commitizen](https://github.com/commitizen/cz-cli) or [commitlint](https://github.com/conventional-changelog/commitlint) can be used to help contributors and enforce valid commit messages.
Here is an example of the release type that will be done based on a commit messages:
@@ -95,6 +95,14 @@ After running the tests, the command `semantic-release` will execute the followi
| Publish | Publish the release. |
| Notify | Notify of new releases or errors. |
## Requirements
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 [8.16.0 or higher](docs/support/FAQ.md#why-does-semantic-release-require-node-version--816) installed in your Continuous Integration environment
## Documentation
- Usage
+1 -1
View File
@@ -10,7 +10,7 @@ var execa = require('execa');
var findVersions = require('find-versions');
var pkg = require('../package.json');
var MIN_GIT_VERSION = '2.0.0';
var MIN_GIT_VERSION = '2.7.1';
if (!semver.satisfies(process.version, pkg.engines.node)) {
console.error(
+1
View File
@@ -4,6 +4,7 @@
- [CircleCI 2.0 workflows](circleci-workflows.md)
- [Travis CI](travis.md)
- [GitLab CI](gitlab-ci.md)
- [GitHub Actions](github-actions.md)
## Git hosted services
- [Git authentication with SSH keys](git-auth-ssh-keys.md)
+68
View File
@@ -0,0 +1,68 @@
# Using semantic-release with [GitHub Actions](https://help.github.com/en/categories/automating-your-workflow-with-github-actions)
## Environment variables
The [Authentication](../usage/ci-configuration.md#authentication) environment variables can be configured with [Secret Variables](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables).
In this example an [`NPM_TOKEN`](https://docs.npmjs.com/creating-and-viewing-authentication-tokens) is required to publish a package to the npm registry. GitHub Actions [automatically populate](https://help.github.com/en/articles/virtual-environments-for-github-actions#github_token-secret) a [`GITHUB_TOKEN`](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) environment variable which can be used in Workflows.
## Node project configuration
[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 >= 8.16](../support/FAQ.md#why-does-semantic-release-require-node-version--816).
### `.github/workflows/release.yml` configuration for Node projects
The following is a minimal configuration for [`semantic-release`](https://github.com/semantic-release/semantic-release) with a build running on Node 12 when a new commit is pushed to a `master` branch. See [Configuring a Workflow](https://help.github.com/en/articles/configuring-a-workflow) for additional configuration options.
```yaml
name: Release
on:
push:
branches:
- master
jobs:
release:
name: Release
runs-on: ubuntu-18.04
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- name: Install dependencies
run: npm ci
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
```
## Pushing `package.json` changes to a `master` branch
To keep `package.json` updated in the `master` branch, [`@semantic-release/git`](https://github.com/semantic-release/git) plugin can be used.
**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.
## 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.:
```yaml
name: Release
on:
repository_dispatch:
types: [semantic-release]
jobs:
# ...
```
To trigger a release, call (with a [Personal Access Tokens](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) stored in `GITHUB_TOKEN` environment variable):
```
$ 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" }'
```
+1 -1
View File
@@ -8,7 +8,7 @@ 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 >= 8 version](../support/FAQ.md#why-does-semantic-release-require-node-version--815).
**Note**: The publish pipeline must run a [Node >= 8.16 version](../support/FAQ.md#why-does-semantic-release-require-node-version--816).
### `.gitlab-ci.yml` configuration for Node projects
+12 -8
View File
@@ -38,7 +38,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 >= 8.15](#why-does-semantic-release-require-node-version--815) 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 >= 8.16](#why-does-semantic-release-require-node-version--816) 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 +48,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 >= 8.15.
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 >= 8.16.
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 +73,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 >= 8](#why-does-semantic-release-require-node-version--83) to execute the `semantic-release` command
- Make sure your CI job executing the `semantic-release` command has access to [Node >= 8.16](#why-does-semantic-release-require-node-version--816) to execute the `semantic-release` command
See the [CI configuration recipes](../recipes/README.md#ci-configurations) for more details on specific CI environments.
@@ -141,11 +141,11 @@ See the [`@semantic-release/npm`](https://github.com/semantic-release/npm#semant
If you have introduced a breaking bug in a release you have 2 options:
- If you have a fix immediately ready, commit and push it (or merge it via a pull request) to the release branch
- Otherwise [revert the commit](https://git-scm.com/docs/git-revert) that introduced the bug and push the revert commit (or merge it via a pull request) to the release branch
- Otherwise, [revert the commit](https://git-scm.com/docs/git-revert) that introduced the bug and push the revert commit (or merge it via a pull request) to the release branch
In both cases **semantic-release** will publish a new release, so your package users' will get the fixed/reverted version.
In both cases **semantic-release** will publish a new release, so your package users will get the fixed/reverted version.
Depending on the package manager you are using, you might be able to un-publish or deprecate a release, in order to prevent users from downloading it by accident. For example npm allows you to [un-publish](https://docs.npmjs.com/cli/unpublish) [within 72 hours](https://www.npmjs.com/policies/unpublish) after releasing. You may also [deprecate](https://docs.npmjs.com/cli/deprecate) a release if you would rather avoid un-publishing.
Depending on the package manager you are using, you might be able to un-publish or deprecate a release, in order to prevent users from downloading it by accident. For example, npm allows you to [un-publish](https://docs.npmjs.com/cli/unpublish) [within 72 hours](https://www.npmjs.com/policies/unpublish) after release. You may also [deprecate](https://docs.npmjs.com/cli/deprecate) a release if you would rather avoid un-publishing.
In any case **do not remove the Git tag associated with the buggy version**, otherwise **semantic-release** will later try to republish that version. Publishing a version after un-publishing is not supported by most package managers.
@@ -232,12 +232,16 @@ 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 >= 8.15?
## Why does semantic-release require Node version >= 8.16?
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which **requires Node version 8.15 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 8.16 or higher**.
See [Node version requirement](./node-version.md#node-version-requirement) for more details and solutions.
## Why does semantic-release require Git version >= 2.7.1?
**semantic-release** uses Git CLI commands to read information about the repository such as branches, commit history and tags. Certain commands and options (such as [the `--merged` option of the `git tag` command](https://git-scm.com/docs/git-tag/2.7.0#git-tag---no-mergedltcommitgt) or bug fixes related to `git ls-files`) used by **semantic-release** are only available in Git version 2.7.1 and higher.
## What is npx?
[`npx`](https://www.npmjs.com/package/npx) – short for "npm exec" – is a CLI to find and execute npm binaries within the local `node_modules` folder or in the $PATH. If a binary can't be located npx will download the required package and execute it from its cache location.
+3 -3
View File
@@ -1,6 +1,6 @@
# Node version requirement
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which requires **requires Node version 8.15 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 **requires Node version 8.16 or higher**.
**semantic-release** is meant to be used in a CI environment as a development support tool, not as a production dependency. Therefore the only constraint is to run the `semantic-release` in a CI environment providing Node 8 or higher.
@@ -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 >= 8.15
### Run at least one CI job with Node >= 8.16
The recommended approach is to run the `semantic-release` command from a CI job running on Node 8.15 or higher. This can either be a job used by your project to test on Node >= 8.15 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 8.16 or higher. This can either be a job used by your project to test on Node >= 8.16 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
@@ -6,6 +6,7 @@ The `semantic-release` command must be executed only after all the tests in the
Here is a few example of the CI services that can be used to achieve this:
- [Travis Build Stages](https://docs.travis-ci.com/user/build-stages)
- [CircleCI Workflows](https://circleci.com/docs/2.0/workflows)
- [GitHub Actions](https://github.com/features/actions)
- [Codeship Deployment Pipelines](https://documentation.codeship.com/basic/builds-and-configuration/deployment-pipelines)
- [GitLab Pipelines](https://docs.gitlab.com/ee/ci/pipelines.html#introduction-to-pipelines-and-jobs)
- [Codefresh Pipelines](https://codefresh.io/docs/docs/configure-ci-cd-pipeline/introduction-to-codefresh-pipelines)
+1 -1
View File
@@ -76,7 +76,7 @@ The branches on which releases should happen. By default **semantic-release** wi
**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).
See [Plugins configuration](workflow-configuration.md#workflow-configuration) for more details.
See [Workflow configuration](workflow-configuration.md#workflow-configuration) for more details.
### repositoryUrl
+5 -3
View File
@@ -48,8 +48,10 @@ For example the configuration `['+([0-9])?(.{+([0-9]),x}).x', 'master', 'next']`
### channel
The `channel` can be defined for any branch type. If it's not defined, releases will be done on the default distribution channel (for example the `@latest` [dist-tag](https://docs.npmjs.com/cli/dist-tag) for npm).
The value of `channel`, if defined, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
The `channel` can be defined for any branch type. By default releases will be done on the default distribution channel (for example the `@latest` [dist-tag](https://docs.npmjs.com/cli/dist-tag) for npm) for the first [release branch](#release-branches) and on a distribution channel named based on the branch `name` for any other branch.
If the `channel` property is set to `false` the default channel will be used.
The value of `channel`, if defined as a string, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
For example the configuration `['master', {name: 'next', channel: 'channel-${name}'}]` will be expanded as:
```js
@@ -78,7 +80,7 @@ For example the configuration `['1.1.x', '1.2.x', 'master']` will be expanded as
### prerelease
A `prerelease` property applies only to pre-release branches, is required and The `prerelease` value must be valid per the [Semantic Versioning Specification](https://semver.org/#spec-item-9). It will determine the name of versions (for example if `prerelease` is set to `beta` the version be formatted like `2.0.0-beta.1`, `2.0.0-beta.2` etc...).
A `prerelease` property applies only to pre-release branches and the `prerelease` value must be valid per the [Semantic Versioning Specification](https://semver.org/#spec-item-9). It will determine the name of versions (for example if `prerelease` is set to `beta` the version be formatted like `2.0.0-beta.1`, `2.0.0-beta.2` etc...).
If the `prerelease` property is set to `true` the `name` value will be used.
The value of `prerelease`, if defined as a string, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
+39 -27
View File
@@ -3,7 +3,6 @@ const marked = require('marked');
const TerminalRenderer = require('marked-terminal');
const envCi = require('env-ci');
const hookStd = require('hook-std');
const pEachSeries = require('p-each-series');
const semver = require('semver');
const AggregateError = require('aggregate-error');
const pkg = require('./package.json');
@@ -13,18 +12,18 @@ const verify = require('./lib/verify');
const getNextVersion = require('./lib/get-next-version');
const getCommits = require('./lib/get-commits');
const getLastRelease = require('./lib/get-last-release');
const getReleasesToAdd = require('./lib/get-releases-to-add');
const getReleaseToAdd = require('./lib/get-release-to-add');
const {extractErrors, makeTag} = require('./lib/utils');
const getGitAuthUrl = require('./lib/get-git-auth-url');
const getBranches = require('./lib/branches');
const getLogger = require('./lib/get-logger');
const {verifyAuth, isBranchUpToDate, getGitHead, tag, push} = require('./lib/git');
const {verifyAuth, isBranchUpToDate, getGitHead, tag, push, getTagHead} = require('./lib/git');
const getError = require('./lib/get-error');
const {COMMIT_NAME, COMMIT_EMAIL} = require('./lib/definitions/constants');
marked.setOptions({renderer: new TerminalRenderer()});
/* eslint complexity: ["warn", 25] */
/* eslint complexity: off */
async function run(context, plugins) {
const {cwd, env, options, logger} = context;
const {isCi, branch: ciBranch, isPr} = envCi({env, cwd});
@@ -84,7 +83,7 @@ async function run(context, plugins) {
throw error;
}
} catch (error) {
logger.error(`The command "${error.cmd}" failed with the error message ${error.stderr}.`);
logger.error(`The command "${error.command}" failed with the error message ${error.stderr}.`);
throw getError('EGITNOPERMISSION', context);
}
@@ -92,39 +91,50 @@ async function run(context, plugins) {
await plugins.verifyConditions(context);
const releasesToAdd = getReleasesToAdd(context);
const errors = [];
context.releases = [];
const releaseToAdd = getReleaseToAdd(context);
await pEachSeries(releasesToAdd, async ({lastRelease, currentRelease, nextRelease}) => {
if (releaseToAdd) {
const {lastRelease, currentRelease, nextRelease} = releaseToAdd;
nextRelease.gitHead = await getTagHead(nextRelease.gitHead, {cwd, env});
currentRelease.gitHead = await getTagHead(currentRelease.gitHead, {cwd, env});
if (context.branch.mergeRange && !semver.satisfies(nextRelease.version, context.branch.mergeRange)) {
errors.push(getError('EINVALIDMAINTENANCEMERGE', {...context, nextRelease}));
return;
} else {
const commits = await getCommits({...context, lastRelease, nextRelease});
nextRelease.notes = await plugins.generateNotes({...context, commits, lastRelease, nextRelease});
if (options.dryRun) {
logger.warn(`Skip ${nextRelease.gitTag} tag creation in dry-run mode`);
} else {
await tag(nextRelease.gitTag, nextRelease.gitHead, {cwd, env});
await push(options.repositoryUrl, {cwd, env});
logger.success(`Created tag ${nextRelease.gitTag}`);
}
context.branch.tags.push({
version: nextRelease.version,
channel: nextRelease.channel,
gitTag: nextRelease.gitTag,
gitHead: nextRelease.gitHead,
});
const releases = await plugins.addChannel({...context, commits, lastRelease, currentRelease, nextRelease});
context.releases.push(...releases);
await plugins.success({...context, lastRelease, commits, nextRelease, releases});
}
const commits = await getCommits({...context, lastRelease, nextRelease});
nextRelease.notes = await plugins.generateNotes({...context, commits, lastRelease, nextRelease});
logger.log('Create tag %s', nextRelease.gitTag);
await tag(nextRelease.gitTag, nextRelease.gitHead, {cwd, env});
await push(options.repositoryUrl, {cwd, env});
context.branch.tags.push({
version: nextRelease.version,
channel: nextRelease.channel,
gitTag: nextRelease.gitTag,
gitHead: nextRelease.gitHead,
});
const releases = await plugins.addChannel({...context, commits, lastRelease, currentRelease, nextRelease});
context.releases.push(...releases);
await plugins.success({...context, lastRelease, commits, nextRelease, releases});
});
}
if (errors.length > 0) {
throw new AggregateError(errors);
}
context.lastRelease = await getLastRelease(context);
if (context.lastRelease.gitHead) {
context.lastRelease.gitHead = await getTagHead(context.lastRelease.gitHead, {cwd, env});
}
if (context.lastRelease.gitTag) {
logger.log(
@@ -180,7 +190,9 @@ async function run(context, plugins) {
await plugins.success({...context, releases});
logger.success(`Published release ${nextRelease.version}`);
logger.success(
`Published release ${nextRelease.version} on ${nextRelease.channel ? nextRelease.channel : 'default'} channel`
);
if (options.dryRun) {
logger.log(`Release note for version ${nextRelease.version}:`);
+17 -18
View File
@@ -1,8 +1,8 @@
const {template, escapeRegExp} = require('lodash');
const {template, escapeRegExp, flatMap} = require('lodash');
const semver = require('semver');
const pReduce = require('p-reduce');
const debug = require('debug')('semantic-release:get-tags');
const {getTags, isRefInHistory, getTagHead} = require('../../lib/git');
const {getTags} = require('../../lib/git');
module.exports = async ({cwd, env, options: {tagFormat}}, branches) => {
// Generate a regex to parse tags formatted with `tagFormat`
@@ -10,26 +10,25 @@ module.exports = async ({cwd, env, options: {tagFormat}}, branches) => {
// The `tagFormat` is compiled with space as the `version` as it's an invalid tag character,
// so it's guaranteed to no be present in the `tagFormat`.
const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.[^@]+)@?(.+)?')}`;
const tags = (await getTags({cwd, env}))
.map(tag => {
const [, version, channel] = tag.match(tagRegexp) || [];
return {gitTag: tag, version, channel};
})
.filter(({version}) => version && semver.valid(semver.clean(version)));
debug('found tags: %o', tags);
return pReduce(
branches,
async (branches, branch) => {
const branchTags = await pReduce(
tags,
async (tags, {gitTag, ...rest}) =>
(await isRefInHistory(gitTag, branch.name, true, {cwd, env}))
? [...tags, {...rest, gitTag, gitHead: await getTagHead(gitTag, {cwd, env})}]
: tags,
[]
);
const versions = (await getTags(branch.name, {cwd, env})).reduce((versions, tag) => {
const [, version, channel] = tag.match(tagRegexp) || [];
if (version && semver.valid(semver.clean(version))) {
return {
...versions,
[version]: versions[version]
? {...versions[version], channels: [...versions[version].channels, channel]}
: {gitTag: tag, version, channels: [channel]},
};
}
return versions;
}, {});
const branchTags = flatMap(versions);
debug('found tags for branch %s: %o', branch.name, branchTags);
return [...branches, {...branch, tags: branchTags}];
+4 -4
View File
@@ -1,15 +1,15 @@
const {isUndefined, uniqBy} = require('lodash');
const {isNil, uniqBy} = require('lodash');
const semver = require('semver');
const {isMaintenanceRange} = require('../utils');
const maintenance = {
filter: ({name, range}) => !isUndefined(range) || isMaintenanceRange(name),
branchValidator: ({range}) => (isUndefined(range) ? true : isMaintenanceRange(range)),
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,
};
const prerelease = {
filter: ({prerelease}) => !isUndefined(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,
+9 -8
View File
@@ -29,14 +29,15 @@ Please make sure to add the \`repositoryUrl\` to the [semantic-release configura
)}).`,
}),
EGITNOPERMISSION: ({options: {repositoryUrl}, branch: {name}}) => ({
message: 'The push permission to the Git repository is required.',
details: `**semantic-release** cannot push the version tag to the branch \`${name}\` on remote Git repository with URL \`${repositoryUrl}\`.
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}\`.
Please refer to the [authentication configuration documentation](${linkify(
'docs/usage/ci-configuration.md#authentication'
)}) to configure the Git credentials on your CI environment and make sure the [repositoryUrl](${linkify(
'docs/usage/configuration.md#repositoryurl'
)}) is configured with a [valid Git URL](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).`,
This can be caused by:
- a misconfiguration of the [repositoryUrl](${linkify('docs/usage/configuration.md#repositoryurl')}) option
- the repository being unavailable
- or missing push permission for the user configured via the [Git credentials on your CI environment](${linkify(
'docs/usage/ci-configuration.md#authentication'
)})`,
}),
EINVALIDTAGFORMAT: ({options: {tagFormat}}) => ({
message: 'Invalid `tagFormat` option.',
@@ -183,7 +184,7 @@ Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
details: `A minimum of 1 and a maximum of 3 release branches are required in the [branches configuration](${linkify(
'docs/usage/configuration.md#branches'
)}).
This may occur if your repository does not have a release branch, such as \`master\`.
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
+2
View File
@@ -1,3 +1,5 @@
/* eslint require-atomic-updates: off */
const {isString, isPlainObject} = require('lodash');
const {getGitHead} = require('../git');
const hideSensitive = require('../hide-sensitive');
+2 -2
View File
@@ -93,6 +93,6 @@ module.exports = async (context, opts) => {
};
async function pkgRepoUrl(opts) {
const {package: pkg} = (await readPkgUp(opts)) || {};
return pkg && (isPlainObject(pkg.repository) ? pkg.repository.url : pkg.repository);
const {packageJson} = (await readPkgUp(opts)) || {};
return packageJson && (isPlainObject(packageJson.repository) ? packageJson.repository.url : packageJson.repository);
}
+7 -2
View File
@@ -41,16 +41,21 @@ 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 {
await verifyAuth(repositoryUrl, branch.name, {cwd, env});
} catch (error) {
} catch (_) {
const envVar = Object.keys(GIT_TOKENS).find(envVar => !isNil(env[envVar]));
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar] || ''}`;
if (gitCredentials) {
// If credentials are set via environment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
const [match, auth, host, path] = /^(?!.+:\/\/)(?:(.*)@)?(.*?):(.*)$/.exec(repositoryUrl) || [];
const {port, hostname, ...parsed} = parse(
match ? `ssh://${auth ? `${auth}@` : ''}${host}/${path}` : repositoryUrl
);
return format({
...parse(match ? `ssh://${auth ? `${auth}@` : ''}${host}/${path}` : repositoryUrl),
...parsed,
auth: gitCredentials,
host: `${hostname}${protocol === 'ssh:' ? '' : port ? `:${port}` : ''}`,
protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
});
}
+13 -6
View File
@@ -1,13 +1,16 @@
const {isUndefined} = require('lodash');
const semver = require('semver');
const {makeTag} = require('./utils');
const {makeTag, isSameChannel} = require('./utils');
/**
* Last release.
*
* @typedef {Object} LastRelease
* @property {string} version The version number of the last release.
* @property {string} [gitHead] The Git reference used to make the last release.
* @property {string} gitHead The Git reference used to make the last release.
* @property {string} gitTag The git tag associated with the last release.
* @property {string} channel The channel on which of the last release was published.
* @property {string} name The name of the last release.
*/
/**
@@ -24,13 +27,17 @@ const {makeTag} = require('./utils');
* @return {LastRelease} The last tagged release or empty object if none is found.
*/
module.exports = ({branch, options: {tagFormat}}, {before} = {}) => {
const [{version, gitTag, gitHead, channel} = {}] = branch.tags
.filter(tag => (branch.type === 'prerelease' && branch.channel === tag.channel) || !semver.prerelease(tag.version))
.filter(tag => isUndefined(before) || semver.lt(tag.version, before))
const [{version, gitTag, channels} = {}] = branch.tags
.filter(
tag =>
((branch.type === 'prerelease' && tag.channels.some(channel => isSameChannel(branch.channel, channel))) ||
!semver.prerelease(tag.version)) &&
(isUndefined(before) || semver.lt(tag.version, before))
)
.sort((a, b) => semver.rcompare(a.version, b.version));
if (gitTag) {
return {version, gitTag, gitHead, channel, name: makeTag(tagFormat, version)};
return {version, gitTag, channels, gitHead: gitTag, name: makeTag(tagFormat, version)};
}
return {};
+3 -1
View File
@@ -1,5 +1,6 @@
const semver = require('semver');
const {FIRST_RELEASE, FIRSTPRERELEASE} = require('./definitions/constants');
const {isSameChannel} = require('./utils');
module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) => {
let version;
@@ -7,7 +8,8 @@ module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) =
const {major, minor, patch} = semver.parse(lastRelease.version);
version =
branch.type === 'prerelease'
? semver.prerelease(lastRelease.version) && lastRelease.channel === channel
? semver.prerelease(lastRelease.version) &&
lastRelease.channels.some(lastReleaseChannel => isSameChannel(lastReleaseChannel, channel))
? semver.inc(lastRelease.version, 'prerelease')
: `${semver.inc(`${major}.${minor}.${patch}`, type)}-${branch.prerelease}.${FIRSTPRERELEASE}`
: semver.inc(lastRelease.version, type);
+60
View File
@@ -0,0 +1,60 @@
const {uniqBy, intersection} = require('lodash');
const semver = require('semver');
const semverDiff = require('semver-diff');
const getLastRelease = require('./get-last-release');
const {makeTag, getLowerBound} = require('./utils');
/**
* Find releases that have been merged from from a higher branch but not added on the channel of the current branch.
*
* @param {Object} context semantic-release context.
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
const {
branch,
branches,
options: {tagFormat},
} = context;
const higherChannels = branches
// Consider only releases of higher branches
.slice(branches.findIndex(({name}) => name === branch.name) + 1)
// Exclude prerelease branches
.filter(({type}) => type !== 'prerelease')
.map(({channel}) => channel);
const versiontoAdd = uniqBy(
branch.tags.filter(
({channels, version}) =>
!channels.includes(branch.channel) &&
intersection(channels, higherChannels).length > 0 &&
(branch.type !== 'maintenance' || semver.gte(version, getLowerBound(branch.mergeRange)))
),
'version'
).sort((a, b) => semver.compare(b.version, a.version))[0];
if (versiontoAdd) {
const {version, gitTag, channels} = versiontoAdd;
const lastRelease = getLastRelease(context, {before: version});
if (semver.gt(getLastRelease(context).version, version)) {
return;
}
const type = lastRelease.version ? semverDiff(lastRelease.version, version) : 'major';
const name = makeTag(tagFormat, version);
return {
lastRelease,
currentRelease: {type, version, channels, gitTag, name, gitHead: gitTag},
nextRelease: {
type,
version,
channel: branch.channel,
gitTag: makeTag(tagFormat, version, branch.channel),
name,
gitHead: gitTag,
},
};
}
};
-71
View File
@@ -1,71 +0,0 @@
const {uniq} = require('lodash');
const semver = require('semver');
const semverDiff = require('semver-diff');
const getLastRelease = require('./get-last-release');
const {makeTag, getLowerBound} = require('./utils');
/**
* Find releases that have been merged from from a higher branch but not added on the channel of the current branch.
*
* @param {Object} context semantic-release context.
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
const {
branch,
branches,
options: {tagFormat},
} = context;
return (
branches
// Consider only releases of higher branches
.slice(branches.findIndex(({name}) => name === branch.name) + 1)
// Exclude prerelease branches
.filter(({type}) => type !== 'prerelease')
// Find higher branch releases merged to building branch but not released on associated channel
.reduce(
(releases, higherBranch) => [
...releases,
// For all unique release version of the higher branch merged on current branch, excluding lower than start range version for maintenance branches
...uniq(
branch.tags.filter(
({channel, version}) =>
channel === higherBranch.channel &&
channel !== branch.channel &&
(branch.type !== 'maintenance' || semver.gte(version, getLowerBound(branch.mergeRange)))
)
)
// Find ones that are not released on the building branch channel
.filter(tag =>
branch.tags.every(
({version, channel}) =>
version !== tag.version || channel === higherBranch.channel || channel !== branch.channel
)
)
// Sort in ascending order to add the most recent release last
.sort((a, b) => semver.compare(a.version, b.version))
// Construct the last and next release to add to the building branch channel
.map(({version, gitHead, gitTag}) => {
const lastRelease = getLastRelease(context, {before: version});
const type = lastRelease.version ? semverDiff(lastRelease.version, version) : 'major';
const name = makeTag(tagFormat, version);
return {
lastRelease,
currentRelease: {type, version, channel: higherBranch.channel, gitTag, name, gitHead},
nextRelease: {
type,
version,
channel: branch.channel,
gitTag: makeTag(tagFormat, version, branch.channel),
name,
gitHead,
},
};
}),
],
[]
)
);
};
+15 -29
View File
@@ -1,4 +1,3 @@
const {matches, pick, memoize} = require('lodash');
const gitLogParser = require('git-log-parser');
const getStream = require('get-stream');
const execa = require('execa');
@@ -15,23 +14,20 @@ 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) {
try {
return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
} catch (error) {
debug(error);
}
return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
}
/**
* Get all the repository tags.
* Get all the tags for a given branch.
*
* @param {String} branch The branch for which to retrieve the tags.
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @return {Array<String>} List of git tags.
* @throws {Error} If the `git` command fails.
*/
async function getTags(execaOpts) {
return (await execa('git', ['tag'], execaOpts)).stdout
async function getTags(branch, execaOpts) {
return (await execa('git', ['tag', '--merged', branch], execaOpts)).stdout
.split('\n')
.map(tag => tag.trim())
.filter(Boolean);
@@ -46,12 +42,14 @@ async function getTags(execaOpts) {
* @return {Promise<Array<Object>>} The list of commits between `from` and `to`.
*/
async function getCommits(from, to, execaOpts) {
return (await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}${to}`},
{cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}
return (
await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}${to}`},
{cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}
)
)
)).map(({message, gitTags, ...commit}) => ({...commit, message: message.trim(), gitTags: gitTags.trim()}));
).map(({message, gitTags, ...commit}) => ({...commit, message: message.trim(), gitTags: gitTags.trim()}));
}
/**
@@ -70,21 +68,16 @@ async function getBranches(repositoryUrl, execaOpts) {
.filter(Boolean);
}
const getBranchCommits = memoize((branch, execaOpts) =>
getStream.array(gitLogParser.parse({_: branch}, {cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}))
);
/**
* Verify if the `ref` is in the direct history of a given branch.
*
* @param {String} ref The reference to look for.
* @param {String} branch The branch for which to check if the `ref` is in history.
* @param {Boolean} findRebasedTags Weither consider in history tags associated with a commit that was rebased to another branch (i.e. GitHub Rebase and Merge feature).
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @return {Boolean} `true` if the reference is in the history of the current branch, falsy otherwise.
*/
async function isRefInHistory(ref, branch, findRebasedTags, execaOpts) {
async function isRefInHistory(ref, branch, execaOpts) {
if (!(await isRefExists(branch, execaOpts))) {
return false;
}
@@ -94,13 +87,6 @@ async function isRefInHistory(ref, branch, findRebasedTags, execaOpts) {
return true;
} catch (error) {
if (error.exitCode === 1) {
if (findRebasedTags) {
const [tagCommit] = await getStream.array(
gitLogParser.parse({_: ref, n: '1'}, {cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}})
);
return (await getBranchCommits(branch, execaOpts)).some(matches(pick(tagCommit, ['message', 'author'])));
}
return false;
}
@@ -147,7 +133,7 @@ async function fetch(repositoryUrl, branch, execaOpts) {
],
execaOpts
);
} catch (error) {
} catch (_) {
await execa(
'git',
[
@@ -288,7 +274,7 @@ async function verifyBranchName(branch, execaOpts) {
async function isBranchUpToDate(repositoryUrl, branch, execaOpts) {
const {stdout: remoteHead} = await execa('git', ['ls-remote', '--heads', repositoryUrl, branch], execaOpts);
try {
return await isRefInHistory(remoteHead.match(/^(\w+)?/)[1], branch, false, execaOpts);
return await isRefInHistory(remoteHead.match(/^(\w+)?/)[1], branch, execaOpts);
} catch (error) {
debug(error);
}
+5
View File
@@ -71,6 +71,10 @@ function makeTag(tagFormat, version, channel) {
return template(tagFormat)({version: `${version}${channel ? `@${channel}` : ''}`});
}
function isSameChannel(channel, otherChannel) {
return channel === otherChannel || (!channel && !otherChannel);
}
module.exports = {
extractErrors,
hideSensitiveValues,
@@ -86,4 +90,5 @@ module.exports = {
getFirstVersion,
getRange,
makeTag,
isSameChannel,
};
+11 -11
View File
@@ -22,16 +22,16 @@
"Pierre Vanduynslager (https://twitter.com/@pvdlg_)"
],
"dependencies": {
"@semantic-release/commit-analyzer": "7.0.0-beta.4",
"@semantic-release/commit-analyzer": "^7.0.0-beta",
"@semantic-release/error": "^2.2.0",
"@semantic-release/github": "5.6.0-beta.1",
"@semantic-release/npm": "6.0.0-beta.1",
"@semantic-release/github": "^5.6.0-beta",
"@semantic-release/npm": "^6.0.0-beta",
"@semantic-release/release-notes-generator": "^7.1.2",
"aggregate-error": "^3.0.0",
"cosmiconfig": "^5.0.1",
"debug": "^4.0.0",
"env-ci": "^4.0.0",
"execa": "^2.0.0",
"execa": "^3.2.0",
"figures": "^3.0.0",
"find-versions": "^3.0.0",
"get-stream": "^5.0.0",
@@ -41,13 +41,13 @@
"lodash": "^4.17.15",
"marked": "^0.7.0",
"marked-terminal": "^3.2.0",
"micromatch": "3.1.5",
"p-each-series": "^1.0.0",
"micromatch": "^4.0.2",
"p-each-series": "^2.1.0",
"p-reduce": "^2.0.0",
"read-pkg-up": "^6.0.0",
"read-pkg-up": "^7.0.0",
"resolve-from": "^5.0.0",
"semver": "^6.0.0",
"semver-diff": "^2.1.0",
"semver-diff": "^3.1.1",
"signale": "^1.2.1",
"yargs": "^14.0.0"
},
@@ -56,7 +56,7 @@
"clear-module": "^4.0.0",
"codecov": "^3.0.0",
"delay": "^4.0.0",
"dockerode": "^2.5.2",
"dockerode": "^3.0.0",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"got": "^9.0.0",
@@ -69,10 +69,10 @@
"sinon": "^7.2.7",
"stream-buffers": "^3.0.2",
"tempy": "^0.3.0",
"xo": "^0.24.0"
"xo": "^0.25.0"
},
"engines": {
"node": ">=8.15"
"node": ">=8.16"
},
"files": [
"bin",
+8 -2
View File
@@ -171,7 +171,10 @@ test('Throw SemanticReleaseError for invalid configurations', async t => {
});
test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
const branches = [{name: 'master', tags: []}, {name: 'master', tags: []}];
const branches = [
{name: 'master', tags: []},
{name: 'master', tags: []},
];
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', {options: {branches}})))];
@@ -183,7 +186,10 @@ test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
});
test('Throw a SemanticReleaseError for each invalid branch name', async t => {
const branches = [{name: '~master', tags: []}, {name: '^master', tags: []}];
const branches = [
{name: '~master', tags: []},
{name: '^master', tags: []},
];
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', {options: {branches}})))];
+25 -55
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import getTags from '../../lib/branches/get-tags';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout, merge, changeAuthor} from '../helpers/git-utils';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout} from '../helpers/git-utils';
test('Get the valid tags', async t => {
const {cwd} = await gitRepo();
@@ -20,9 +20,9 @@ test('Get the valid tags', async t => {
{
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[1].hash},
{gitTag: 'v2.0.0', version: '2.0.0', channel: undefined, gitHead: commits[0].hash},
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channel: undefined, gitHead: commits[3].hash},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [undefined]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [undefined]},
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channels: [undefined]},
],
},
]);
@@ -55,30 +55,24 @@ test('Get the valid tags from multiple branches', async t => {
{
name: '1.x',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[0].hash},
{gitTag: 'v1.0.0@1.x', version: '1.0.0', channel: '1.x', gitHead: commits[0].hash},
{gitTag: 'v1.1.0', version: '1.1.0', channel: undefined, gitHead: commits[1].hash},
{gitTag: 'v1.1.0@1.x', version: '1.1.0', channel: '1.x', gitHead: commits[1].hash},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [undefined, '1.x']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [undefined, '1.x']},
],
},
{
name: 'master',
tags: [
...result[0].tags,
{gitTag: 'v2.0.0', version: '2.0.0', channel: undefined, gitHead: commits[2].hash},
{gitTag: 'v2.0.0@next', version: '2.0.0', channel: 'next', gitHead: commits[2].hash},
],
tags: [...result[0].tags, {gitTag: 'v2.0.0', version: '2.0.0', channels: [undefined, 'next']}],
},
{
name: 'next',
tags: [...result[1].tags, {gitTag: 'v3.0.0@next', version: '3.0.0', channel: 'next', gitHead: commits[3].hash}],
tags: [...result[1].tags, {gitTag: 'v3.0.0@next', version: '3.0.0', channels: ['next']}],
},
]);
});
test('Match the tag name from the begining of the string and the channel from the last "@"', async t => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitCommits(['First'], {cwd});
await gitTagVersion('prefix@v1.0.0', undefined, {cwd});
await gitTagVersion('prefix@v1.0.0@next', undefined, {cwd});
await gitTagVersion('prefix@v2.0.0', undefined, {cwd});
@@ -91,10 +85,8 @@ test('Match the tag name from the begining of the string and the channel from th
{
name: 'master',
tags: [
{gitTag: 'prefix@v1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[0].hash},
{gitTag: 'prefix@v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: commits[0].hash},
{gitTag: 'prefix@v2.0.0', version: '2.0.0', channel: undefined, gitHead: commits[0].hash},
{gitTag: 'prefix@v2.0.0@next', version: '2.0.0', channel: 'next', gitHead: commits[0].hash},
{gitTag: 'prefix@v1.0.0', version: '1.0.0', channels: [undefined, 'next']},
{gitTag: 'prefix@v2.0.0', version: '2.0.0', channels: [undefined, 'next']},
],
},
]);
@@ -109,15 +101,15 @@ test('Return branches with and empty tags array if no valid tag is found', async
await gitCommits(['Third'], {cwd});
await gitTagVersion('v3.0', undefined, {cwd});
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}]);
t.deepEqual(result, [{name: 'master', tags: []}, {name: 'next', tags: []}]);
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 => {
const {cwd} = await gitRepo();
await gitCommits(['First'], {cwd});
await gitCheckout('other-branch', true, {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@next', undefined, {cwd});
@@ -129,28 +121,31 @@ test('Return branches with and empty tags array if no valid tag is found in hist
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
t.deepEqual(result, [{name: 'master', tags: []}, {name: 'next', tags: []}]);
t.deepEqual(result, [
{name: 'master', tags: []},
{name: 'next', tags: []},
]);
});
test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitCommits(['First'], {cwd});
await gitTagVersion('1.0.0', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: '1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}]},
{name: 'master', tags: [{gitTag: '1.0.0', version: '1.0.0', channels: [undefined]}]},
]);
await gitTagVersion('foo-1.0.0-bar', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-\${version}-bar`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: 'foo-1.0.0-bar', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}]},
{name: 'master', tags: [{gitTag: 'foo-1.0.0-bar', version: '1.0.0', channels: [undefined]}]},
]);
await gitTagVersion('foo-v1.0.0-bar', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-v\${version}-bar`}}, [{name: 'master'}]), [
{
name: 'master',
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}],
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channels: [undefined]}],
},
]);
@@ -158,7 +153,7 @@ test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
t.deepEqual(await getTags({cwd, options: {tagFormat: `(.+)/\${version}/(a-z)`}}, [{name: 'master'}]), [
{
name: 'master',
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}],
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channels: [undefined]}],
},
]);
@@ -166,37 +161,12 @@ test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
t.deepEqual(await getTags({cwd, options: {tagFormat: `2.0.0-\${version}-bar.1`}}, [{name: 'master'}]), [
{
name: 'master',
tags: [{gitTag: '2.0.0-1.0.0-bar.1', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}],
tags: [{gitTag: '2.0.0-1.0.0-bar.1', version: '1.0.0', channels: [undefined]}],
},
]);
await gitTagVersion('3.0.0-bar.2', undefined, {cwd});
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}-bar.2`}}, [{name: 'master'}]), [
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channel: undefined, gitHead: commits[0].hash}]},
]);
});
test('Get the tag on branch where commits have been rebased', async t => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
await gitCheckout('next', true, {cwd});
commits.push(...(await gitCommits(['Second/n/n/commit body'], {cwd})));
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitCheckout('master', false, {cwd});
await merge('next', {cwd});
// Simulate GitHub "Rebase and Merge" by changing the committer info, which will result in a new commit sha and losing the tag
await changeAuthor(commits[1].hash, {cwd});
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
t.deepEqual(result, [
{
name: 'master',
tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: commits[1].hash}],
},
{
name: 'next',
tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: commits[1].hash}],
},
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channels: [undefined]}]},
]);
});
+31 -16
View File
@@ -4,7 +4,11 @@ import normalize from '../../lib/branches/normalize';
const toTags = versions => versions.map(version => ({version}));
test('Maintenance branches - initial state', t => {
const maintenance = [{name: '1.x', channel: '1.x', tags: []}, {name: '1.1.x', tags: []}, {name: '1.2.x', tags: []}];
const maintenance = [
{name: '1.x', channel: '1.x', tags: []},
{name: '1.1.x', tags: []},
{name: '1.2.x', tags: []},
];
const release = [{name: 'master', tags: []}];
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({type, name, range, accept, channel, mergeRange}) => ({
@@ -303,24 +307,35 @@ test('Prerelease branches', t => {
{name: 'alpha', prerelease: 'preview', tags: []},
];
t.deepEqual(normalize.prerelease({prerelease}).map(({type, name, channel}) => ({type, name, channel})), [
{type: 'prerelease', name: 'beta', channel: 'beta'},
{type: 'prerelease', name: 'alpha', channel: 'alpha'},
]);
t.deepEqual(
normalize.prerelease({prerelease}).map(({type, name, channel}) => ({type, name, channel})),
[
{type: 'prerelease', name: 'beta', channel: 'beta'},
{type: 'prerelease', name: 'alpha', channel: 'alpha'},
]
);
});
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: []}, {name: 'next', channel: false, tags: []}];
const release = [
{name: 'master', channel: false, tags: []},
{name: 'next', channel: false, tags: []},
];
const prerelease = [{name: 'beta', channel: false, prerelease: true, tags: []}];
t.deepEqual(normalize.maintenance({maintenance, release}).map(({name, channel}) => ({name, channel})), [
{name: '1.x', channel: false},
]);
t.deepEqual(normalize.release({release}).map(({name, channel}) => ({name, channel})), [
{name: 'master', channel: false},
{name: 'next', channel: false},
]);
t.deepEqual(normalize.prerelease({prerelease}).map(({name, channel}) => ({name, channel})), [
{name: 'beta', channel: false},
]);
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({name, channel}) => ({name, channel})),
[{name: '1.x', channel: false}]
);
t.deepEqual(
normalize.release({release}).map(({name, channel}) => ({name, channel})),
[
{name: 'master', channel: false},
{name: 'next', channel: false},
]
);
t.deepEqual(
normalize.prerelease({prerelease}).map(({name, channel}) => ({name, channel})),
[{name: 'beta', channel: false}]
);
});
+3 -3
View File
@@ -175,7 +175,7 @@ test.serial('Display help', async t => {
t.is(exitCode, 0);
});
test.serial('Return error code 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}});
@@ -187,7 +187,7 @@ test.serial('Return error code and prints help if called with a command', async
t.is(exitCode, 1);
});
test.serial('Return error code 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}});
@@ -199,7 +199,7 @@ test.serial('Return error code if multiple plugin are set for single plugin', as
t.is(exitCode, 1);
});
test.serial('Return error code 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}});
+7 -7
View File
@@ -8,9 +8,10 @@ test('A "maintenance" branch is identified by having a "range" property or a "na
t.true(maintenance.filter({name: 'some-name', range: '1.x.x'}));
t.true(maintenance.filter({name: 'some-name', range: '1.1.x'}));
t.true(maintenance.filter({name: 'some-name', range: ''}));
t.true(maintenance.filter({name: 'some-name', range: null}));
t.true(maintenance.filter({name: 'some-name', range: false}));
t.true(maintenance.filter({name: 'some-name', range: true}));
t.false(maintenance.filter({name: 'some-name', range: null}));
t.false(maintenance.filter({name: 'some-name', range: false}));
t.false(maintenance.filter({name: 'some-name'}));
t.false(maintenance.filter({name: '1.0.0'}));
t.false(maintenance.filter({name: 'x.x.x'}));
@@ -24,9 +25,8 @@ test('A "maintenance" branches must have a "range" property formatted like "N.x"
t.false(maintenance.branchValidator({name: 'some-name', range: '>=1.0.0 <2.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: '1.0.0'}));
t.false(maintenance.branchValidator({name: 'some-name', range: 'wrong-range'}));
t.false(maintenance.branchValidator({name: 'some-name', range: true}));
t.false(maintenance.branchValidator({name: 'some-name', range: ''}));
t.false(maintenance.branchValidator({name: 'some-name', range: null}));
t.false(maintenance.branchValidator({name: 'some-name', range: false}));
});
test('The "maintenance" branches must have unique ranges', t => {
@@ -36,13 +36,13 @@ test('The "maintenance" branches must have unique ranges', t => {
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x'}]));
});
test('A "prerelease" branch is identified by having a range "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: ''}));
t.true(prerelease.filter({name: 'some-name', prerelease: null}));
t.true(prerelease.filter({name: 'some-name', prerelease: false}));
t.false(prerelease.filter({name: 'some-name', prerelease: null}));
t.false(prerelease.filter({name: 'some-name', prerelease: false}));
t.false(prerelease.filter({name: 'some-name'}));
});
+30 -4
View File
@@ -86,9 +86,9 @@ test('Convert shorthand URL', async t => {
cwd,
env,
branch: {name: 'master'},
options: {repositoryUrl: 'semanitc-release/semanitc-release'},
options: {repositoryUrl: 'semantic-release/semantic-release'},
}),
'https://github.com/semanitc-release/semanitc-release.git'
'https://github.com/semantic-release/semantic-release.git'
);
});
@@ -100,9 +100,9 @@ test('Convert GitLab shorthand URL', async t => {
cwd,
env,
branch: {name: 'master'},
options: {repositoryUrl: 'gitlab:semanitc-release/semanitc-release'},
options: {repositoryUrl: 'gitlab:semantic-release/semantic-release'},
}),
'https://gitlab.com/semanitc-release/semanitc-release.git'
'https://gitlab.com/semantic-release/semantic-release.git'
);
});
@@ -161,6 +161,19 @@ 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 => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GIT_CREDENTIALS: 'user:pass'},
options: {branch: 'master', repositoryUrl: 'http://host.null:8080/owner/repo.git'},
}),
'http://user:pass@host.null:8080/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git+https" URL', async t => {
const {cwd} = await gitRepo();
@@ -189,6 +202,19 @@ 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 => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GIT_CREDENTIALS: 'user:pass'},
options: {branch: 'master', repositoryUrl: 'ssh://git@host.null:2222/owner/repo.git'},
}),
'https://user:pass@host.null/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined with "GH_TOKEN"', async t => {
const {cwd} = await gitRepo();
+21 -16
View File
@@ -6,16 +6,16 @@ test('Get the highest non-prerelease valid tag', t => {
branch: {
name: 'master',
tags: [
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: '222'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: '111'},
{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: '333'},
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: 'v2.0.0'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: 'v1.0.0'},
{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: 'v3.0.0-beta.1@beta'},
],
type: 'release',
},
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: '222', channel: undefined});
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 => {
@@ -25,9 +25,14 @@ test('Get the highest prerelease valid tag, ignoring other tags from other prere
prerelease: 'beta',
channel: 'beta',
tags: [
{version: '1.0.0-beta.1', gitTag: 'v1.0.0-beta.1@beta', gitHead: '111', channel: 'beta'},
{version: '1.0.0-beta.2', gitTag: 'v1.0.0-beta.2@beta', gitHead: '222', channel: 'beta'},
{version: '1.0.0-alpha.1', gitTag: 'v1.0.0-alpha.1@alpha', gitHead: '333', channel: 'alpha'},
{version: '1.0.0-beta.1', gitTag: 'v1.0.0-beta.1@beta', gitHead: 'v1.0.0-beta.1@beta', channels: ['beta']},
{version: '1.0.0-beta.2', gitTag: 'v1.0.0-beta.2@beta', gitHead: 'v1.0.0-beta.2@beta', channels: ['beta']},
{
version: '1.0.0-alpha.1',
gitTag: 'v1.0.0-alpha.1@alpha',
gitHead: 'v1.0.0-alpha.1@alpha',
channels: ['alpha'],
},
],
type: 'prerelease',
},
@@ -38,8 +43,8 @@ test('Get the highest prerelease valid tag, ignoring other tags from other prere
version: '1.0.0-beta.2',
gitTag: 'v1.0.0-beta.2@beta',
name: 'v1.0.0-beta.2',
gitHead: '222',
channel: 'beta',
gitHead: 'v1.0.0-beta.2@beta',
channels: ['beta'],
});
});
@@ -47,7 +52,7 @@ test('Return empty object if no valid tag is found', t => {
const result = getLastRelease({
branch: {
name: 'master',
tags: [{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: '111'}],
tags: [{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: 'v3.0.0-beta.1@beta'}],
type: 'release',
},
options: {tagFormat: `v\${version}`},
@@ -63,11 +68,11 @@ test('Get the highest non-prerelease valid tag before a certain version', t => {
name: 'master',
channel: undefined,
tags: [
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: '333'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: '111'},
{version: '2.0.0-beta.1', gitTag: 'v2.0.0-beta.1@beta', gitHead: '222'},
{version: '2.1.0', gitTag: 'v2.1.0', gitHead: '444'},
{version: '2.1.1', gitTag: 'v2.1.1', gitHead: '555'},
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: 'v2.0.0'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: 'v1.0.0'},
{version: '2.0.0-beta.1', gitTag: 'v2.0.0-beta.1@beta', gitHead: 'v2.0.0-beta.1@beta'},
{version: '2.1.0', gitTag: 'v2.1.0', gitHead: 'v2.1.0'},
{version: '2.1.1', gitTag: 'v2.1.1', gitHead: 'v2.1.1'},
],
type: 'release',
},
@@ -76,5 +81,5 @@ test('Get the highest non-prerelease valid tag before a certain version', t => {
{before: '2.1.0'}
);
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: '333', channel: undefined});
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: 'v2.0.0', channels: undefined});
});
+12 -12
View File
@@ -13,7 +13,7 @@ test('Increase version for patch release', t => {
getNextVersion({
branch: {name: 'master', type: 'release'},
nextRelease: {type: 'patch'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.1'
@@ -25,7 +25,7 @@ test('Increase version for minor release', t => {
getNextVersion({
branch: {name: 'master', type: 'release'},
nextRelease: {type: 'minor'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.1.0'
@@ -37,7 +37,7 @@ test('Increase version for major release', t => {
getNextVersion({
branch: {name: 'master', type: 'release'},
nextRelease: {type: 'major'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'2.0.0'
@@ -61,7 +61,7 @@ test('Increase version for patch release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'patch'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.1-beta.1'
@@ -71,7 +71,7 @@ test('Increase version for patch release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'patch'},
lastRelease: {version: '1.0.0-beta.1'},
lastRelease: {version: '1.0.0-beta.1', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.0-beta.2'
@@ -81,7 +81,7 @@ test('Increase version for patch release on prerelease branch', t => {
getNextVersion({
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
nextRelease: {type: 'patch', channel: 'alpha'},
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
lastRelease: {version: '1.0.0-beta.1', channels: ['beta']},
logger: t.context.logger,
}),
'1.0.1-alpha.1'
@@ -93,7 +93,7 @@ test('Increase version for minor release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'minor'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.1.0-beta.1'
@@ -103,7 +103,7 @@ test('Increase version for minor release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'minor'},
lastRelease: {version: '1.0.0-beta.1'},
lastRelease: {version: '1.0.0-beta.1', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.0-beta.2'
@@ -113,7 +113,7 @@ test('Increase version for minor release on prerelease branch', t => {
getNextVersion({
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
nextRelease: {type: 'minor', channel: 'alpha'},
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
lastRelease: {version: '1.0.0-beta.1', channels: ['beta']},
logger: t.context.logger,
}),
'1.1.0-alpha.1'
@@ -125,7 +125,7 @@ test('Increase version for major release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'major'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'2.0.0-beta.1'
@@ -135,7 +135,7 @@ test('Increase version for major release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'major'},
lastRelease: {version: '1.0.0-beta.1'},
lastRelease: {version: '1.0.0-beta.1', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.0-beta.2'
@@ -145,7 +145,7 @@ test('Increase version for major release on prerelease branch', t => {
getNextVersion({
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
nextRelease: {type: 'major', channel: 'alpha'},
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
lastRelease: {version: '1.0.0-beta.1', channels: ['beta']},
logger: t.context.logger,
}),
'2.0.0-alpha.1'
+283
View File
@@ -0,0 +1,283 @@
import test from 'ava';
import getReleaseToAdd from '../lib/get-release-to-add';
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', t => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
channel: '2.x',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0@2.x', version: '2.0.0', channels: ['2.x']},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [undefined]},
{gitTag: 'v2.1.0', version: '2.1.0', channels: [undefined]},
{gitTag: 'v2.1.1', version: '2.1.1', channels: [undefined]},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [undefined]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [undefined]},
],
},
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {version: '2.1.0', channels: [undefined], gitTag: 'v2.1.0', name: 'v2.1.0', gitHead: 'v2.1.0'},
currentRelease: {
type: 'patch',
version: '2.1.1',
channels: [undefined],
gitTag: 'v2.1.1',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
nextRelease: {
type: 'patch',
version: '2.1.1',
channel: '2.x',
gitTag: 'v2.1.1@2.x',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
});
});
test('Return versions merged between release branches', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [undefined, 'next']},
{gitTag: 'v1.1.0@next', version: '1.1.0', channels: ['next']},
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channels: ['next-major']},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {
version: '1.1.0',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
channels: ['next'],
},
currentRelease: {
type: 'major',
version: '2.0.0',
channels: ['next-major'],
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
});
});
test('Return releases sorted by ascending order', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channels: ['next-major']},
{gitTag: 'v1.1.0@next', version: '1.1.0', channels: ['next']},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [undefined, 'next']},
// {gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next'},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: 'v1.1.0@next', channels: ['next']},
currentRelease: {
type: 'major',
version: '2.0.0',
channels: ['next-major'],
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
});
});
test('No lastRelease', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channels: ['next']}],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {},
currentRelease: {
type: 'major',
version: '1.0.0',
channels: ['next'],
gitTag: 'v1.0.0@next',
name: 'v1.0.0',
gitHead: 'v1.0.0@next',
},
nextRelease: {
type: 'major',
version: '1.0.0',
channel: undefined,
gitTag: 'v1.0.0',
name: 'v1.0.0',
gitHead: 'v1.0.0@next',
},
});
});
test('Ignore pre-release versions', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [undefined, 'next']},
{gitTag: 'v1.1.0@next', version: '1.1.0', channels: ['next']},
{gitTag: 'v2.0.0-alpha.1@alpha', version: '2.0.0-alpha.1', channels: ['alpha']},
],
},
branches: [
{name: 'master'},
{name: 'next', channel: 'next'},
{name: 'alpha', type: 'prerelease', channel: 'alpha'},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {version: '1.0.0', channels: [undefined, 'next'], gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: 'v1.0.0'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channels: ['next'],
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
});
});
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', t => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
channel: 'latest',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', channels: [undefined]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [undefined]},
{gitTag: 'v2.1.0', version: '2.1.0', channels: [undefined]},
{gitTag: 'v2.1.1', version: '2.1.1', channels: [undefined]},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [undefined]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [undefined]},
],
},
branches: [
{name: '2.x', channel: 'latest'},
{name: 'master', channel: 'latest'},
],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they have the same "channel"', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
channel: 'latest',
tags: [
{gitTag: 'v1.0.0', channels: ['latest'], version: '1.0.0'},
{gitTag: 'v1.1.0', channels: ['latest'], version: '1.1.0'},
{gitTag: 'v2.0.0', channels: ['latest'], version: '2.0.0'},
],
},
branches: [
{name: 'master', channel: 'latest'},
{name: 'next', channel: 'latest'},
{name: 'next-major', channel: 'latest'},
],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they all have "channel" set to "false"', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
channel: false,
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [undefined]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [undefined]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [undefined]},
],
},
branches: [
{name: 'master', channel: false},
{name: 'next', channel: false},
{name: 'next-major', channel: false},
],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
test('Exclude versions number less than the latest version already released on that branch', t => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
channel: '2.x',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0@2.x', version: '2.0.0', channels: ['2.x']},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [undefined]},
{gitTag: 'v2.1.0', version: '2.1.0', channels: [undefined]},
{gitTag: 'v2.1.1', version: '2.1.1', channels: [undefined, '2.x']},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [undefined]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [undefined]},
],
},
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
-313
View File
@@ -1,313 +0,0 @@
import test from 'ava';
import getReleasesToAdd from '../lib/get-releases-to-add';
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', t => {
const result = getReleasesToAdd({
branch: {
name: '2.x',
channel: '2.x',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0@2.x', version: '2.0.0', channel: '2.x', gitHead: '111'},
{gitTag: 'v2.0.0', version: '2.0.0', gitHead: '111'},
{gitTag: 'v2.1.0', version: '2.1.0', gitHead: '222'},
{gitTag: 'v2.1.1', version: '2.1.1', gitHead: '333'},
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '444'},
{gitTag: 'v1.1.0', version: '1.1.0', gitHead: '555'},
],
},
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '2.0.0', channel: '2.x', gitTag: 'v2.0.0@2.x', name: 'v2.0.0', gitHead: '111'},
currentRelease: {
type: 'minor',
version: '2.1.0',
channel: undefined,
gitTag: 'v2.1.0',
name: 'v2.1.0',
gitHead: '222',
},
nextRelease: {
type: 'minor',
version: '2.1.0',
channel: '2.x',
gitTag: 'v2.1.0@2.x',
name: 'v2.1.0',
gitHead: '222',
},
},
{
lastRelease: {version: '2.1.0', channel: undefined, gitTag: 'v2.1.0', name: 'v2.1.0', gitHead: '222'},
currentRelease: {
type: 'patch',
version: '2.1.1',
channel: undefined,
gitTag: 'v2.1.1',
name: 'v2.1.1',
gitHead: '333',
},
nextRelease: {
type: 'patch',
version: '2.1.1',
channel: '2.x',
gitTag: 'v2.1.1@2.x',
name: 'v2.1.1',
gitHead: '333',
},
},
]);
});
test('Return versions merged between release branches', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next', gitHead: '222'},
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channel: 'next-major', gitHead: '333'},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: '111'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: '222',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: '222',
},
},
{
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: '222', channel: 'next'},
currentRelease: {
type: 'major',
version: '2.0.0',
channel: 'next-major',
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: '333',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: '333',
},
},
]);
});
test('Return releases sorted by ascending order', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channel: 'next-major', gitHead: '333'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next', gitHead: '222'},
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: '111'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: '222',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: '222',
},
},
{
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: '222', channel: 'next'},
currentRelease: {
type: 'major',
version: '2.0.0',
channel: 'next-major',
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: '333',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: '333',
},
},
]);
});
test('No lastRelease', t => {
const result = getReleasesToAdd({
branch: {name: 'master', tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'}]},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {},
currentRelease: {
type: 'major',
version: '1.0.0',
channel: 'next',
gitTag: 'v1.0.0@next',
name: 'v1.0.0',
gitHead: '111',
},
nextRelease: {
type: 'major',
version: '1.0.0',
channel: undefined,
gitTag: 'v1.0.0',
name: 'v1.0.0',
gitHead: '111',
},
},
]);
});
test('Ignore pre-release versions', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next', gitHead: '222'},
{gitTag: 'v2.0.0-alpha.1@alpha', version: '2.0.0', channel: 'alpha', gitHead: '333'},
],
},
branches: [
{name: 'master'},
{name: 'next', channel: 'next'},
{name: 'alpha', type: 'prerelease', channel: 'alpha'},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: '111'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: '222',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: '222',
},
},
]);
});
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', t => {
const result = getReleasesToAdd({
branch: {
name: '2.x',
channel: 'latest',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', gitHead: '111'},
{gitTag: 'v2.0.0', version: '2.0.0', gitHead: '111'},
{gitTag: 'v2.1.0', version: '2.1.0', gitHead: '222'},
{gitTag: 'v2.1.1', version: '2.1.1', gitHead: '333'},
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '444'},
{gitTag: 'v1.1.0', version: '1.1.0', gitHead: '555'},
],
},
branches: [{name: '2.x', channel: 'latest'}, {name: 'master', channel: 'latest'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
test('Exclude versions merged between release branches if they have the same "channel"', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
channel: 'latest',
tags: [
{gitTag: 'v1.0.0', channel: 'latest', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.1.0', channel: 'latest', version: '1.1.0', gitHead: '222'},
{gitTag: 'v2.0.0', channel: 'latest', version: '2.0.0', gitHead: '333'},
],
},
branches: [
{name: 'master', channel: 'latest'},
{name: 'next', channel: 'latest'},
{name: 'next-major', channel: 'latest'},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
test('Exclude versions merged between release branches if they all have "channel" set to "false"', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
channel: false,
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
{gitTag: 'v1.1.0', version: '1.1.0', gitHead: '222'},
{gitTag: 'v2.0.0', version: '2.0.0', gitHead: '333'},
],
},
branches: [{name: 'master', channel: false}, {name: 'next', channel: false}, {name: 'next-major', channel: false}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
+7 -8
View File
@@ -91,7 +91,7 @@ test('Fetch all tags on a detached head repository', async t => {
await fetch(repositoryUrl, 'master', {cwd});
t.deepEqual((await getTags({cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort());
t.deepEqual((await getTags('master', {cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort());
});
test('Verify if the commit `sha` is in the direct history of the current branch', async t => {
@@ -105,10 +105,10 @@ test('Verify if the commit `sha` is in the direct history of the current branch'
const otherCommits = await gitCommits(['Second'], {cwd});
await gitCheckout('master', false, {cwd});
t.true(await isRefInHistory(commits[0].hash, 'master', false, {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'master', false, {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'missing-branch', false, {cwd}));
await t.throwsAsync(isRefInHistory('non-existant-sha', 'master', false, {cwd}));
t.true(await isRefInHistory(commits[0].hash, 'master', {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'master', {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'missing-branch', {cwd}));
await t.throwsAsync(isRefInHistory('non-existant-sha', 'master', {cwd}));
});
test('Verify if a branch exists', async t => {
@@ -140,7 +140,7 @@ test('Get all branches', async t => {
t.deepEqual((await getBranches(repositoryUrl, {cwd})).sort(), ['master', 'second-branch', 'third-branch'].sort());
});
test('Get the commit sha for a given tag or falsy if the tag does not exists', 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
@@ -149,7 +149,6 @@ test('Get the commit sha for a given tag or falsy if the tag does not exists', a
await gitTagVersion('v1.0.0', undefined, {cwd});
t.is(await getTagHead('v1.0.0', {cwd}), commits[0].hash);
t.falsy(await getTagHead('missing_tag', {cwd}));
});
test('Return git remote repository url from config', async t => {
@@ -243,7 +242,7 @@ test('Return falsy for invalid tag names', async t => {
test('Throws error if obtaining the tags fails', async t => {
const cwd = tempy.directory();
await t.throwsAsync(getTags({cwd}));
await t.throwsAsync(getTags('master', {cwd}));
});
test('Return "true" if repository is up to date', async t => {
+8 -19
View File
@@ -86,9 +86,11 @@ export async function gitCommits(messages, execaOpts) {
*/
export async function gitGetCommits(from, execaOpts) {
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}})
)).map(commit => {
return (
await getStream.array(
gitLogParser.parse({_: `${from ? from + '..' : ''}HEAD`}, {...execaOpts, env: {...process.env, ...execaOpts.env}})
)
).map(commit => {
commit.message = commit.message.trim();
commit.gitTags = commit.gitTags.trim();
return commit;
@@ -99,10 +101,10 @@ export async function gitGetCommits(from, execaOpts) {
* Checkout a branch on the current git repository.
*
* @param {String} branch Branch name.
* @param {Boolean} create `true` to create the branch, `false` to checkout an existing branch.
* @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 = true, execaOpts) {
export async function gitCheckout(branch, create, execaOpts) {
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch], execaOpts);
}
@@ -223,7 +225,7 @@ export async function gitCommitTag(gitHead, execaOpts) {
*
* @throws {Error} if the push failed.
*/
export async function gitPush(repositoryUrl = 'origin', branch = 'master', execaOpts) {
export async function gitPush(repositoryUrl, branch, execaOpts) {
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOpts);
}
@@ -256,16 +258,3 @@ export async function mergeFf(ref, execaOpts) {
export async function rebase(ref, execaOpts) {
await execa('git', ['rebase', ref], execaOpts);
}
export async function changeAuthor(sha, execaOpts) {
await execa(
'git',
[
'filter-branch',
'-f',
'--env-filter',
`if [[ "$GIT_COMMIT" = "${sha}" ]]; then export GIT_COMMITTER_NAME="New Author" GIT_COMMITTER_EMAIL="author@test.com"; fi`,
],
execaOpts
);
}
+1 -1
View File
@@ -3,7 +3,7 @@ import getStream from 'get-stream';
import pRetry from 'p-retry';
import {initBareRepo, gitShallowClone} from './git-utils';
const IMAGE = 'pvdlg/docker-gitbox';
const IMAGE = 'pvdlg/docker-gitbox:latest';
const SERVER_PORT = 80;
const HOST_PORT = 2080;
const SERVER_HOST = 'localhost';
+1 -1
View File
@@ -30,7 +30,7 @@ async function start() {
minTimeout: 1000,
factor: 2,
});
} catch (error) {
} catch (_) {
throw new Error(`Couldn't start mock-server after 2 min`);
}
}
+1 -1
View File
@@ -39,7 +39,7 @@ async function start() {
minTimeout: 1000,
factor: 2,
});
} catch (error) {
} catch (_) {
throw new Error(`Couldn't start npm-registry-docker after 2 min`);
}
+123 -35
View File
@@ -1,5 +1,5 @@
import test from 'ava';
import {escapeRegExp, isString, sortBy} from 'lodash';
import {escapeRegExp, isString, sortBy, omit} from 'lodash';
import proxyquire from 'proxyquire';
import {spy, stub} from 'sinon';
import {WritableStreamBuffer} from 'stream-buffers';
@@ -57,7 +57,7 @@ test('Plugins are called with expected values', async t => {
gitHead: commits[commits.length - 1].hash,
gitTag: 'v1.0.0@next',
name: 'v1.0.0',
channel: 'next',
channels: ['next'],
};
const nextRelease = {
name: 'v1.1.0',
@@ -96,7 +96,7 @@ test('Plugins are called with expected values', async t => {
name: 'master',
range: '>=1.0.0 <2.0.0',
accept: ['patch', 'minor'],
tags: [{channel: 'next', gitTag: 'v1.0.0@next', version: '1.0.0', gitHead: commits[commits.length - 1].hash}],
tags: [{channels: ['next'], gitTag: 'v1.0.0@next', version: '1.0.0'}],
type: 'release',
},
{
@@ -104,7 +104,7 @@ test('Plugins are called with expected values', async t => {
name: 'next',
range: '>=2.0.0',
accept: ['patch', 'minor', 'major'],
tags: [{channel: 'next', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0@next', version: '1.0.0'}],
tags: [{channels: ['next'], gitTag: 'v1.0.0@next', version: '1.0.0'}],
type: 'release',
},
];
@@ -124,7 +124,7 @@ test('Plugins are called with expected values', async t => {
const releases = [
{
...lastRelease,
...omit(lastRelease, 'channels'),
...release1,
type: 'major',
version: '1.0.0',
@@ -176,7 +176,7 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes1.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(generateNotes1.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(generateNotes1.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
@@ -193,7 +193,7 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes2.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(generateNotes2.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(generateNotes2.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
@@ -211,7 +211,7 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes3.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(generateNotes3.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(generateNotes3.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
@@ -236,7 +236,7 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(addChannel.args[0][1].lastRelease, {});
t.deepEqual(addChannel.args[0][1].currentRelease, {...lastRelease, type: 'major'});
t.deepEqual(addChannel.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
@@ -330,7 +330,7 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(success.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(success.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(success.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
@@ -581,6 +581,117 @@ test('Publish a pre-release version', async t => {
t.is(releases[0].gitTag, 'v1.1.0-beta.2@beta');
});
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});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('next-major', true, {cwd});
await gitPush(repositoryUrl, 'next-major', {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['feat: a feature'], {cwd});
await gitPush(repositoryUrl, 'next', {cwd});
const config = {
branches: ['master', {name: 'next', channel: false}, {name: 'next-major', channel: false}],
repositoryUrl,
};
const addChannel = stub().resolves({});
const options = {
...config,
verifyConditions: stub().resolves(),
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(''),
addChannel,
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
};
let semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'next', isPr: false}),
});
let {releases} = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0');
t.is(releases[0].gitTag, 'v1.1.0');
await gitCommits(['fix: a fix'], {cwd});
({releases} = await semanticRelease(options, {
cwd,
env: {},
stdout: {write: () => {}},
stderr: {write: () => {}},
}));
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.1');
t.is(releases[0].gitTag, 'v1.1.1');
await gitCheckout('master', false, {cwd});
await merge('next', {cwd});
await gitPush('origin', 'master', {cwd});
semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
t.falsy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
t.is(addChannel.callCount, 0);
});
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});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('beta', true, {cwd});
await gitCommits(['feat: a feature'], {cwd});
await gitPush(repositoryUrl, 'beta', {cwd});
const config = {
branches: ['master', {name: 'beta', channel: false, prerelease: true}],
repositoryUrl,
};
const options = {
...config,
verifyConditions: stub().resolves(),
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(''),
addChannel: false,
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
};
const semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'beta', isPr: false}),
});
let {releases} = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.1');
t.is(releases[0].gitTag, 'v1.1.0-beta.1');
await gitCommits(['fix: a fix'], {cwd});
({releases} = await semanticRelease(options, {
cwd,
env: {},
stdout: {write: () => {}},
stderr: {write: () => {}},
}));
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.2');
t.is(releases[0].gitTag, 'v1.1.0-beta.2');
});
test('Do not add pre-releases to a different channel', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial release'], {cwd});
@@ -671,14 +782,6 @@ async function addChannelMacro(t, mergeFunction) {
publish,
success,
};
const nextRelease1 = {
name: 'v2.0.0',
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
gitHead: commits[1].hash,
};
const nextRelease2 = {
name: 'v2.0.1',
type: 'patch',
@@ -695,15 +798,11 @@ async function addChannelMacro(t, mergeFunction) {
const result = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.deepEqual(result.releases, [
{...nextRelease1, ...release1, notes, pluginName: '[Function: functionStub]'},
{...nextRelease1, notes, pluginName: '[Function: functionStub]'},
{...nextRelease2, ...release1, notes, pluginName: '[Function: functionStub]'},
{...nextRelease2, notes, pluginName: '[Function: functionStub]'},
]);
// Verify the tag has been created on the local and remote repo and reference
t.is(await gitTagHead(nextRelease1.gitTag, {cwd}), nextRelease1.gitHead);
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease1.gitTag, {cwd}), nextRelease1.gitHead);
t.is(await gitTagHead(nextRelease2.gitTag, {cwd}), nextRelease2.gitHead);
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease2.gitTag, {cwd}), nextRelease2.gitHead);
}
@@ -1379,15 +1478,6 @@ test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a ma
fail,
};
const nextRelease = {
type: 'patch',
version: '1.1.1',
channel: '1.1.x',
gitTag: 'v1.1.1@1.1.x',
name: 'v1.1.1',
gitHead: commits[2].hash,
};
const semanticRelease = proxyquire('..', {
'./lib/logger': t.context.logger,
'env-ci': () => ({isCi: true, branch: '1.1.x', isPr: false}),
@@ -1398,13 +1488,11 @@ test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a ma
)),
];
t.is(addChannel.callCount, 1);
t.deepEqual(addChannel.args[0][1].nextRelease, {...nextRelease, notes});
t.is(addChannel.callCount, 0);
t.is(publish.callCount, 0);
t.is(success.callCount, 1);
t.deepEqual(success.args[0][1].releases, [{...nextRelease, notes, pluginName: '[Function: functionStub]'}]);
t.is(success.callCount, 0);
t.is(fail.callCount, 1);
t.deepEqual(fail.args[0][1].errors, errors);
+1 -1
View File
@@ -269,7 +269,7 @@ test('Release patch, minor and major versions', async t => {
const updateReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases/${releaseId}`,
{
body: {tag_name: `v${version}`, name: `v${version}`, prerelease: false},
body: {name: `v${version}`, prerelease: false},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}, method: 'PATCH'}
+18 -3
View File
@@ -47,7 +47,12 @@ test('Execute each function in series passing the "lastResult" and "result" to "
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(5);
t.deepEqual(result, [1, 2, 3, 4]);
t.deepEqual(getNextInput.args, [[5, 1], [5, 2], [5, 3], [5, 4]]);
t.deepEqual(getNextInput.args, [
[5, 1],
[5, 2],
[5, 3],
[5, 4],
]);
});
test('Execute each function in series calling "transform" to modify the results', async t => {
@@ -61,7 +66,12 @@ test('Execute each function in series calling "transform" to modify the results'
const result = await pipeline([step1, step2, step3, step4], {getNextInput, transform})(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]);
t.deepEqual(getNextInput.args, [
[5, 1 + 1],
[5, 2 + 1],
[5, 3 + 1],
[5, 4 + 1],
]);
});
test('Execute each function in series calling "transform" to modify the results with "settleAll"', async t => {
@@ -75,7 +85,12 @@ test('Execute each function in series calling "transform" to modify the results
const result = await pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput, transform})(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]);
t.deepEqual(getNextInput.args, [
[5, 1 + 1],
[5, 2 + 1],
[5, 3 + 1],
[5, 4 + 1],
]);
});
test('Stop execution and throw error if a step rejects', async t => {
+10
View File
@@ -14,6 +14,7 @@ import {
getFirstVersion,
getRange,
makeTag,
isSameChannel,
} from '../lib/utils';
test('extractErrors', t => {
@@ -178,3 +179,12 @@ test('makeTag', t => {
t.is(makeTag(`v\${version}`, '1.0.0', 'next'), 'v1.0.0@next');
t.is(makeTag(`v\${version}@test`, '1.0.0', 'next'), 'v1.0.0@next@test');
});
test('isSameChannel', t => {
t.true(isSameChannel('next', 'next'));
t.true(isSameChannel(null, undefined));
t.true(isSameChannel(false, undefined));
t.true(isSameChannel('', false));
t.false(isSameChannel('next', false));
});