Compare commits
69
Commits
@@ -1 +0,0 @@
|
||||
base: app0ZOxG0FnHmOiuU
|
||||
@@ -131,3 +131,6 @@ yarn.lock
|
||||
|
||||
# Gitbook
|
||||
_book
|
||||
|
||||
# Mockserver
|
||||
CertificateAuthorityCertificate.pem
|
||||
|
||||
+7
-32
@@ -1,36 +1,11 @@
|
||||
language: node_js
|
||||
version: ~> 1.0
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
node_js:
|
||||
- 12
|
||||
- 10
|
||||
- 8.15
|
||||
|
||||
# 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)
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- next
|
||||
- beta
|
||||
- /^\d+\.(\d+|x)(\.x)?$/
|
||||
- /^greenkeeper.*$/
|
||||
|
||||
# Retry install on fail to avoid failing a build on network/disk/external errors
|
||||
install:
|
||||
- travis_retry npm install
|
||||
|
||||
script:
|
||||
- npm run test
|
||||
|
||||
after_success:
|
||||
- npm run codecov
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: release
|
||||
node_js: lts/*
|
||||
script:
|
||||
- npm run semantic-release
|
||||
import:
|
||||
- .travis/node.yml
|
||||
- .travis/node-versions.yml
|
||||
- .travis/semantic-release.yml
|
||||
- .travis/greenkeeper.yml
|
||||
- .travis/codecov.yml
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
after_success:
|
||||
- npm run codecov
|
||||
@@ -0,0 +1,3 @@
|
||||
branches:
|
||||
only:
|
||||
- /^greenkeeper.*$/
|
||||
@@ -0,0 +1,3 @@
|
||||
node_js:
|
||||
- 12
|
||||
- 10.13
|
||||
@@ -0,0 +1,11 @@
|
||||
language: node_js
|
||||
|
||||
cache:
|
||||
npm: false
|
||||
|
||||
# Retry install on fail to avoid failing a build on network/disk/external errors
|
||||
install:
|
||||
- travis_retry npm install
|
||||
|
||||
script:
|
||||
- npm run test
|
||||
@@ -0,0 +1,15 @@
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- next
|
||||
- beta
|
||||
- /^\d+\.(\d+|x)(\.x)?$/
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: release
|
||||
node_js: lts/*
|
||||
install:
|
||||
- travis_retry npm install
|
||||
script:
|
||||
- npm run semantic-release
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
@@ -35,7 +35,7 @@ execa('git', ['--version'])
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Node 8+ from this point on
|
||||
// Node 10+ from this point on
|
||||
require('../cli')()
|
||||
.then(exitCode => {
|
||||
process.exitCode = exitCode;
|
||||
|
||||
@@ -91,3 +91,7 @@
|
||||
- [semantic-release-github-pages](https://github.com/qiwi/semantic-release-gh-pages-plugin)
|
||||
- `verifyConditions`: Verify the presence of the auth token set via environment variables.
|
||||
- `publish`: Pushes commit to the documentation branch.
|
||||
- [leiningen-semantic-release](https://github.com/NoxHarmonium/leiningen-semantic-release)
|
||||
- `verifyConditions`: Checks the project.clj is syntactically valid.
|
||||
- `prepare`: Update the project.clj version and package the output jar file.
|
||||
- `publish`: Publish the jar (and generated Maven metadata) to a maven repository (or clojars).
|
||||
|
||||
@@ -11,3 +11,11 @@
|
||||
- Publishes the same tarball to [npm](https://github.com/semantic-release/npm).
|
||||
- Commits the version change in `package.json`.
|
||||
- Creates or updates a [changelog](https://github.com/semantic-release/changelog) file.
|
||||
- [semantic-release-npm-github-publish](https://github.com/oleg-koval/semantic-release-npm-github-publish)
|
||||
- Based on [angular preset](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
|
||||
- Adds more keywords for the `chore` **PATCH** release.
|
||||
- Generates or updates a [changelog](https://github.com/semantic-release/changelog) file including all **PATCH** keywords (not included in default angular package).
|
||||
- Updates GitHub release with release-notes.
|
||||
- Bumps a version in package.json.
|
||||
- Publishes the new version to [NPM](https://npmjs.org).
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 >= 10.13](../support/FAQ.md#why-does-semantic-release-require-node-version--1013).
|
||||
|
||||
### `.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" }'
|
||||
```
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
## Environment variables
|
||||
|
||||
The [Authentication](../usage/ci-configuration.md#authentication) environment variables can be configured with [Secret variables](https://docs.gitlab.com/ce/ci/variables/README.html#secret-variables).
|
||||
The [Authentication](../usage/ci-configuration.md#authentication) environment variables can be configured with [Protected variables](https://docs.gitlab.com/ce/ci/variables/README.html#protected-environment-variables).
|
||||
|
||||
**Note**: Make sure to configure your release branch as [protected](https://docs.gitlab.com/ce/user/project/protected_branches.html) in order for the CI/CD build to access the protected variables.
|
||||
|
||||
## Node project configuration
|
||||
|
||||
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 >= 10.13 version](../support/FAQ.md#why-does-semantic-release-require-node-version--1013).
|
||||
|
||||
### `.gitlab-ci.yml` configuration for Node projects
|
||||
|
||||
|
||||
+12
-8
@@ -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 >= 10.13](#why-does-semantic-release-require-node-version--1013) and specifies it in its `package.json`s [`engines`](https://docs.npmjs.com/files/package.json#engines) key.
|
||||
|
||||
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 >= 10.13.
|
||||
|
||||
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 >= 10.13](#why-does-semantic-release-require-node-version--1013) 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 >= 10.13?
|
||||
|
||||
**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 10.13 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.
|
||||
|
||||
@@ -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 10 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 >= 10.13
|
||||
|
||||
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 10.13 or higher. This can either be a job used by your project to test on Node >= 10.13 or a dedicated job for the release steps.
|
||||
|
||||
See [CI configuration](../usage/ci-configuration.md) and [CI configuration recipes](../recipes/README.md#ci-configurations) for more details.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -65,8 +65,8 @@ For each [release step](../../README.md#release-steps) the plugins that implemen
|
||||
With this configuration **semantic-release** will:
|
||||
- execute the `verifyConditions` implementation of `@semantic-release/npm` then `@semantic-release/git`
|
||||
- execute the `analyzeCommits` implementation of `@semantic-release/commit-analyzer`
|
||||
- execute the `prepare` implementation of `@semantic-release/npm` then `@semantic-release/git`
|
||||
- execute the `generateNotes` implementation of `@semantic-release/release-notes-generator`
|
||||
- execute the `prepare` implementation of `@semantic-release/npm` then `@semantic-release/git`
|
||||
- execute the `publish` implementation of `@semantic-release/npm`
|
||||
|
||||
## Plugin options configuration
|
||||
|
||||
@@ -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.
|
||||
@@ -98,7 +100,7 @@ For example the configuration `['master', {name: 'pre/rc', prerelease: '${name.r
|
||||
|
||||
### Release branches
|
||||
|
||||
A release branch is the base type of branch used by **semantic-release** that allows to publish releases with a [semantic version](https://semver.org), optionally on a specific distribution channel. Distribution channels (for example [npm dist-tags](https://docs.npmjs.com/cli/dist-tag) or [Chrome release channels](https://www.chromium.org/getting-involved/dev-channel)) are a way to distribute new releases only to a subset of users in order to get early feedback. Later on those releases can be added to the general distribution channel to be made available to all users.
|
||||
A release branch is the base type of branch used by **semantic-release** that allows to publish releases with a [semantic version](https://semver.org), optionally on a specific distribution channel. Distribution channels (for example [npm dist-tags](https://docs.npmjs.com/cli/dist-tag) or [Chrome release channels](https://www.chromium.org/getting-involved/dev-channel)) are a way to distribute new releases only to a subset of users in order to get early feedback. Later on, those releases can be added to the general distribution channel to be made available to all users.
|
||||
|
||||
**semantic-release** will automatically add releases to the corresponding distribution channel when code is [merged from a release branch to another](#merging-into-a-release-branch).
|
||||
|
||||
@@ -110,20 +112,17 @@ See [publishing on distribution channels recipe](../recipes/distribution-channel
|
||||
|
||||
#### Pushing to a release branch
|
||||
|
||||
With the configuration `"branches": ["master", "next"]`, if the last release published from `master` is `1.0.0` then:
|
||||
With the configuration `"branches": ["master", "next"]`, if the last release published from `master` is `1.0.0` and the last one from `next` is `2.0.0` then:
|
||||
- Only versions in range `1.x.x` can be published from `master`, so only `fix` and `feat` commits can be pushed to `master`
|
||||
- Only versions in range `>=2.0.0` release can be published from `next`, so a `BREAKING CHANGE` commit must be pushed first on `next` and can be followed by any type of commits
|
||||
- Once `next` get merged into `master` the release `2.0.0` will be made available on the channel associated with `master` and both `master` and `next` will accept any commit type
|
||||
|
||||
With the configuration `"branches": ["master", "next", "next-major"]`, if the last release published from `master` is `1.0.0` then:
|
||||
- Only versions in range `1.0.x` can be published from `master`, so only `fix` commits can be pushed to `master`
|
||||
- Only versions in range `>=1.1.0` can be published from `next`, so a `feat` commit must be pushed first on `next` and can be followed by `fix` and `feat` commits
|
||||
- Only versions in range `>=2.0.0` release can be published from `next`, so a `BREAKING CHANGE` commit must be pushed first on `next-major` and can be followed by any type of commits
|
||||
|
||||
Those verifications prevent situations such as:
|
||||
1. Create a `feat` commit on `next` which triggers the release of version `1.0.0` on channel `next`
|
||||
This verification prevent scenario such as:
|
||||
1. Create a `feat` commit on `next` which triggers the release of version `1.0.0` on the `next` channel
|
||||
2. Merge `next` into `master` which adds `1.0.0` on the default channel
|
||||
3. Push a `fix` commit to `master` which triggers the release of version `1.0.1` on the default channel
|
||||
4. Push a `fix` commit to `next` which would attempt to release the version `1.0.1` on channel `next` and fails as this version already exists
|
||||
3. Create a `feat` commit on `next` which triggers the release of version `1.1.0` on the `next` channel
|
||||
4. Create a `feat` commit on `master` which would attempt to release the version `1.1.0` on the default channel
|
||||
|
||||
In step 4 **semantic-release** will throw an `EINVALIDNEXTVERSION` error to prevent the attempt at releasing version `1.1.0` which was already released on step 3 with a different codebase. The error will indicate that the commit should be created on `next` instead. Alternatively if the `next` branch is merged into `master`, the version `1.1.0` will be made available on the default channel and the `feat` commit would be allowed on `master` to release `1.2.0`.
|
||||
|
||||
#### Merging into a release branch
|
||||
|
||||
|
||||
@@ -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,21 +12,21 @@ 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, pushNotes, getTagHead, addNote} = 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});
|
||||
const {isCi, branch: ciBranch, isPr} = context.envCi;
|
||||
|
||||
if (!isCi && !options.dryRun && !options.noCi) {
|
||||
logger.warn('This run was not triggered in a known CI environment, running in dry-run mode.');
|
||||
@@ -54,7 +53,7 @@ async function run(context, plugins) {
|
||||
await verify(context);
|
||||
|
||||
options.repositoryUrl = await getGitAuthUrl(context);
|
||||
context.branches = await getBranches(options.repositoryUrl, context);
|
||||
context.branches = await getBranches(options.repositoryUrl, ciBranch, context);
|
||||
context.branch = context.branches.find(({name}) => name === ciBranch);
|
||||
|
||||
if (!context.branch) {
|
||||
@@ -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,55 @@ 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 addNote({channels: [...currentRelease.channels, nextRelease.channel]}, nextRelease.gitHead, {cwd, env});
|
||||
await push(options.repositoryUrl, {cwd, env});
|
||||
await pushNotes(options.repositoryUrl, {cwd, env});
|
||||
logger.success(
|
||||
`Add ${nextRelease.channel ? `channel ${nextRelease.channel}` : 'default channel'} to 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);
|
||||
context.lastRelease = getLastRelease(context);
|
||||
if (context.lastRelease.gitHead) {
|
||||
context.lastRelease.gitHead = await getTagHead(context.lastRelease.gitHead, {cwd, env});
|
||||
}
|
||||
|
||||
if (context.lastRelease.gitTag) {
|
||||
logger.log(
|
||||
@@ -138,7 +153,7 @@ async function run(context, plugins) {
|
||||
|
||||
const nextRelease = {
|
||||
type: await plugins.analyzeCommits(context),
|
||||
channel: context.branch.channel,
|
||||
channel: context.branch.channel || null,
|
||||
gitHead: await getGitHead({cwd, env}),
|
||||
};
|
||||
if (!nextRelease.type) {
|
||||
@@ -148,8 +163,8 @@ async function run(context, plugins) {
|
||||
|
||||
context.nextRelease = nextRelease;
|
||||
nextRelease.version = getNextVersion(context);
|
||||
nextRelease.gitTag = makeTag(options.tagFormat, nextRelease.version, nextRelease.channel);
|
||||
nextRelease.name = makeTag(options.tagFormat, nextRelease.version);
|
||||
nextRelease.gitTag = makeTag(options.tagFormat, nextRelease.version);
|
||||
nextRelease.name = nextRelease.gitTag;
|
||||
|
||||
if (context.branch.type !== 'prerelease' && !semver.satisfies(nextRelease.version, context.branch.range)) {
|
||||
throw getError('EINVALIDNEXTVERSION', {
|
||||
@@ -171,7 +186,9 @@ async function run(context, plugins) {
|
||||
} else {
|
||||
// Create the tag before calling the publish plugins as some require the tag to exists
|
||||
await tag(nextRelease.gitTag, nextRelease.gitHead, {cwd, env});
|
||||
await addNote({channels: [nextRelease.channel]}, nextRelease.gitHead, {cwd, env});
|
||||
await push(options.repositoryUrl, {cwd, env});
|
||||
await pushNotes(options.repositoryUrl, {cwd, env});
|
||||
logger.success(`Created tag ${nextRelease.gitTag}`);
|
||||
}
|
||||
|
||||
@@ -180,7 +197,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}:`);
|
||||
@@ -222,7 +241,13 @@ module.exports = async (opts = {}, {cwd = process.cwd(), env = process.env, stdo
|
||||
{silent: false, streams: [process.stdout, process.stderr, stdout, stderr].filter(Boolean)},
|
||||
hideSensitive(env)
|
||||
);
|
||||
const context = {cwd, env, stdout: stdout || process.stdout, stderr: stderr || process.stderr};
|
||||
const context = {
|
||||
cwd,
|
||||
env,
|
||||
stdout: stdout || process.stdout,
|
||||
stderr: stderr || process.stderr,
|
||||
envCi: envCi({env, cwd}),
|
||||
};
|
||||
context.logger = getLogger(context);
|
||||
context.logger.log(`Running ${pkg.name} version ${pkg.version}`);
|
||||
try {
|
||||
|
||||
@@ -2,32 +2,26 @@ const {template, escapeRegExp} = 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, getNote} = require('../../lib/git');
|
||||
|
||||
module.exports = async ({cwd, env, options: {tagFormat}}, branches) => {
|
||||
// Generate a regex to parse tags formatted with `tagFormat`
|
||||
// by replacing the `version` variable in the template by `(.+)`.
|
||||
// The `tagFormat` is compiled with space as the `version` as it's an invalid tag character,
|
||||
// so it's guaranteed to no be present in the `tagFormat`.
|
||||
const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.[^@]+)@?(.+)?')}`;
|
||||
const tags = (await 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);
|
||||
const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.+)')}`;
|
||||
|
||||
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,
|
||||
await getTags(branch.name, {cwd, env}),
|
||||
async (branchTags, tag) => {
|
||||
const [, version] = tag.match(tagRegexp) || [];
|
||||
return version && semver.valid(semver.clean(version))
|
||||
? [...branchTags, {gitTag: tag, version, channels: (await getNote(tag, {cwd, env})).channels || [null]}]
|
||||
: branchTags;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ const AggregateError = require('aggregate-error');
|
||||
const pEachSeries = require('p-each-series');
|
||||
const DEFINITIONS = require('../definitions/branches');
|
||||
const getError = require('../get-error');
|
||||
const {fetch, verifyBranchName} = require('../git');
|
||||
const {fetch, fetchNotes, verifyBranchName} = require('../git');
|
||||
const expand = require('./expand');
|
||||
const getTags = require('./get-tags');
|
||||
const normalize = require('./normalize');
|
||||
|
||||
module.exports = async (repositoryUrl, context) => {
|
||||
module.exports = async (repositoryUrl, ciBranch, context) => {
|
||||
const {cwd, env} = context;
|
||||
|
||||
const remoteBranches = await expand(
|
||||
@@ -18,9 +18,11 @@ module.exports = async (repositoryUrl, context) => {
|
||||
);
|
||||
|
||||
await pEachSeries(remoteBranches, async ({name}) => {
|
||||
await fetch(repositoryUrl, name, {cwd, env});
|
||||
await fetch(repositoryUrl, name, ciBranch, {cwd, env});
|
||||
});
|
||||
|
||||
await fetchNotes(repositoryUrl, {cwd, env});
|
||||
|
||||
const branches = await getTags(context, remoteBranches);
|
||||
|
||||
const errors = [];
|
||||
@@ -48,7 +50,7 @@ module.exports = async (repositoryUrl, context) => {
|
||||
const duplicates = [...branches]
|
||||
.map(branch => branch.name)
|
||||
.sort()
|
||||
.filter((val, idx, arr) => arr[idx] === arr[idx + 1] && arr[idx] !== arr[idx - 1]);
|
||||
.filter((_, idx, arr) => arr[idx] === arr[idx + 1] && arr[idx] !== arr[idx - 1]);
|
||||
|
||||
if (duplicates.length > 0) {
|
||||
errors.push(getError('EDUPLICATEBRANCHES', {duplicates}));
|
||||
|
||||
+14
-20
@@ -1,5 +1,4 @@
|
||||
const {sortBy, isNil} = require('lodash');
|
||||
const semver = require('semver');
|
||||
const semverDiff = require('semver-diff');
|
||||
const {FIRST_RELEASE, RELEASE_TYPE} = require('../definitions/constants');
|
||||
const {
|
||||
@@ -61,36 +60,31 @@ function release({release}) {
|
||||
return release;
|
||||
}
|
||||
|
||||
const breakpoints = release.length > 2 ? ['minor', 'major'] : ['major'];
|
||||
|
||||
// The intial bound is the last release from the base branch of `FIRST_RELEASE` (1.0.0)
|
||||
let bound = getLatestVersion(tagsToVersions(release[0].tags)) || FIRST_RELEASE;
|
||||
// The intial lastVersion is the last release from the base branch of `FIRST_RELEASE` (1.0.0)
|
||||
let lastVersion = getLatestVersion(tagsToVersions(release[0].tags)) || FIRST_RELEASE;
|
||||
|
||||
return release.map(({name, tags, channel, ...rest}, idx) => {
|
||||
const versions = tagsToVersions(tags);
|
||||
// The lower bound is the highest version between the current branch last release and the previous branch upper bound (`bound`)
|
||||
const min = highest(getLatestVersion(versions), bound);
|
||||
if (release.length - 1 === idx) {
|
||||
// If the current branch is the last one of the release branch, there is no upper bound
|
||||
bound = undefined;
|
||||
} else {
|
||||
// The default upper bound is the lower bound increment with the release type of the current branch position
|
||||
const upperBound = semver.inc(min, breakpoints[idx]);
|
||||
// Find the lowest version that is present on the current branch but none of the previous ones
|
||||
const nextFirstVersion = getFirstVersion(tagsToVersions(release[idx + 1].tags), release.slice(0, idx + 1));
|
||||
// The upper bound is the lowest version between `nextFirstVersion` and the default upper bound
|
||||
bound = lowest(nextFirstVersion, upperBound);
|
||||
}
|
||||
// The new lastVersion is the highest version between the current branch last release and the previous branch lastVersion
|
||||
lastVersion = highest(getLatestVersion(versions), lastVersion);
|
||||
// The upper bound is:
|
||||
// - None if the current branch is the last one of the release branches
|
||||
// - Otherwise, The upper bound is the lowest version that is present on the current branch but none of the previous ones
|
||||
const bound =
|
||||
release.length - 1 === idx
|
||||
? undefined
|
||||
: getFirstVersion(tagsToVersions(release[idx + 1].tags), release.slice(0, idx + 1));
|
||||
|
||||
const diff = bound ? semverDiff(min, bound) : null;
|
||||
const diff = bound ? semverDiff(lastVersion, bound) : null;
|
||||
return {
|
||||
...rest,
|
||||
channel: idx === 0 ? channel : isNil(channel) ? name : channel,
|
||||
tags,
|
||||
type: 'release',
|
||||
name,
|
||||
range: getRange(min, bound),
|
||||
range: getRange(lastVersion, bound),
|
||||
accept: bound ? RELEASE_TYPE.slice(0, RELEASE_TYPE.indexOf(diff)) : RELEASE_TYPE,
|
||||
main: idx === 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,6 +14,8 @@ const SECRET_REPLACEMENT = '[secure]';
|
||||
|
||||
const SECRET_MIN_SIZE = 5;
|
||||
|
||||
const GIT_NOTE_REF = 'semantic-release';
|
||||
|
||||
module.exports = {
|
||||
RELEASE_TYPE,
|
||||
FIRST_RELEASE,
|
||||
@@ -23,4 +25,5 @@ module.exports = {
|
||||
RELEASE_NOTES_SEPARATOR,
|
||||
SECRET_REPLACEMENT,
|
||||
SECRET_MIN_SIZE,
|
||||
GIT_NOTE_REF,
|
||||
};
|
||||
|
||||
@@ -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)}\`.`,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint require-atomic-updates: off */
|
||||
|
||||
const {isString, isPlainObject} = require('lodash');
|
||||
const {getGitHead} = require('../git');
|
||||
const hideSensitive = require('../hide-sensitive');
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
const {castArray, pickBy, isNil, isString, isPlainObject} = require('lodash');
|
||||
const readPkgUp = require('read-pkg-up');
|
||||
const cosmiconfig = require('cosmiconfig');
|
||||
const {cosmiconfig} = require('cosmiconfig');
|
||||
const resolveFrom = require('resolve-from');
|
||||
const debug = require('debug')('semantic-release:config');
|
||||
const {repoUrl} = require('./git');
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -41,16 +41,22 @@ 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 [match, auth, host, path] =
|
||||
/^(?!.+:\/\/)(?:(?<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
@@ -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 {};
|
||||
|
||||
+20
-6
@@ -1,16 +1,30 @@
|
||||
const semver = require('semver');
|
||||
const {FIRST_RELEASE, FIRSTPRERELEASE} = require('./definitions/constants');
|
||||
const {isSameChannel, getLatestVersion, tagsToVersions, highest} = require('./utils');
|
||||
|
||||
module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) => {
|
||||
let version;
|
||||
if (lastRelease.version) {
|
||||
const {major, minor, patch} = semver.parse(lastRelease.version);
|
||||
version =
|
||||
branch.type === 'prerelease'
|
||||
? semver.prerelease(lastRelease.version) && lastRelease.channel === channel
|
||||
? semver.inc(lastRelease.version, 'prerelease')
|
||||
: `${semver.inc(`${major}.${minor}.${patch}`, type)}-${branch.prerelease}.${FIRSTPRERELEASE}`
|
||||
: semver.inc(lastRelease.version, type);
|
||||
|
||||
if (branch.type === 'prerelease') {
|
||||
if (
|
||||
semver.prerelease(lastRelease.version) &&
|
||||
lastRelease.channels.some(lastReleaseChannel => isSameChannel(lastReleaseChannel, channel))
|
||||
) {
|
||||
version = highest(
|
||||
semver.inc(lastRelease.version, 'prerelease'),
|
||||
`${semver.inc(getLatestVersion(tagsToVersions(branch.tags), {withPrerelease: true}), type)}-${
|
||||
branch.prerelease
|
||||
}.${FIRSTPRERELEASE}`
|
||||
);
|
||||
} else {
|
||||
version = `${semver.inc(`${major}.${minor}.${patch}`, type)}-${branch.prerelease}.${FIRSTPRERELEASE}`;
|
||||
}
|
||||
} else {
|
||||
version = semver.inc(lastRelease.version, type);
|
||||
}
|
||||
|
||||
logger.log('The next release version is %s', version);
|
||||
} else {
|
||||
version = branch.type === 'prerelease' ? `${FIRST_RELEASE}-${branch.prerelease}.${FIRSTPRERELEASE}` : FIRST_RELEASE;
|
||||
|
||||
@@ -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 || null);
|
||||
|
||||
const versiontoAdd = uniqBy(
|
||||
branch.tags.filter(
|
||||
({channels, version}) =>
|
||||
!channels.includes(branch.channel || null) &&
|
||||
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 || null,
|
||||
gitTag: makeTag(tagFormat, version),
|
||||
name,
|
||||
gitHead: gitTag,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}),
|
||||
],
|
||||
[]
|
||||
)
|
||||
);
|
||||
};
|
||||
+101
-64
@@ -1,8 +1,8 @@
|
||||
const {matches, pick, memoize} = require('lodash');
|
||||
const gitLogParser = require('git-log-parser');
|
||||
const getStream = require('get-stream');
|
||||
const execa = require('execa');
|
||||
const debug = require('debug')('semantic-release:git');
|
||||
const {GIT_NOTE_REF} = require('./definitions/constants');
|
||||
|
||||
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
|
||||
|
||||
@@ -15,23 +15,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 +43,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()}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,49 +65,10 @@ async function getCommits(from, to, execaOpts) {
|
||||
async function getBranches(repositoryUrl, execaOpts) {
|
||||
return (await execa('git', ['ls-remote', '--heads', repositoryUrl], execaOpts)).stdout
|
||||
.split('\n')
|
||||
.map(branch => branch.match(/^.+refs\/heads\/(.+)$/)[1])
|
||||
.map(branch => branch.match(/^.+refs\/heads\/(?<branch>.+)$/)[1])
|
||||
.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) {
|
||||
if (!(await isRefExists(branch, execaOpts))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await execa('git', ['merge-base', '--is-ancestor', ref, branch], 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;
|
||||
}
|
||||
|
||||
debug(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the `ref` exits
|
||||
*
|
||||
@@ -126,15 +86,22 @@ async function isRefExists(ref, execaOpts) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshallow the git repository if necessary and fetch all the tags.
|
||||
* Fetch all the tags from a branch. Unshallow if necessary.
|
||||
* This will update the local branch from the latest on the remote if:
|
||||
* - The branch is not the one that triggered the CI
|
||||
* - The CI created a detached head
|
||||
*
|
||||
* Otherwise it just calls `git fetch` without specifying the `refspec` option to avoid overwritting the head commit set by the CI.
|
||||
*
|
||||
* The goal is to retrieve the informations on all the release branches without "disturbing" the CI, leaving the trigger branch or the detached head intact.
|
||||
*
|
||||
* @param {String} repositoryUrl The remote repository URL.
|
||||
* @param {String} branch The repository branch to fetch.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*/
|
||||
async function fetch(repositoryUrl, branch, execaOpts) {
|
||||
const isLocalExists =
|
||||
(await execa('git', ['rev-parse', '--verify', branch], {...execaOpts, reject: false})).exitCode === 0;
|
||||
async function fetch(repositoryUrl, branch, ciBranch, execaOpts) {
|
||||
const isDetachedHead =
|
||||
(await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {...execaOpts, reject: false})).stdout === 'HEAD';
|
||||
|
||||
try {
|
||||
await execa(
|
||||
@@ -143,23 +110,48 @@ async function fetch(repositoryUrl, branch, execaOpts) {
|
||||
'fetch',
|
||||
'--unshallow',
|
||||
'--tags',
|
||||
...(isLocalExists ? [repositoryUrl] : [repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
|
||||
...(branch === ciBranch && !isDetachedHead
|
||||
? [repositoryUrl]
|
||||
: ['--update-head-ok', repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
|
||||
],
|
||||
execaOpts
|
||||
);
|
||||
} catch (error) {
|
||||
} catch (_) {
|
||||
await execa(
|
||||
'git',
|
||||
[
|
||||
'fetch',
|
||||
'--tags',
|
||||
...(isLocalExists ? [repositoryUrl] : [repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
|
||||
...(branch === ciBranch && !isDetachedHead
|
||||
? [repositoryUrl]
|
||||
: ['--update-head-ok', repositoryUrl, `+refs/heads/${branch}:refs/heads/${branch}`]),
|
||||
],
|
||||
execaOpts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshallow the git repository if necessary and fetch all the notes.
|
||||
*
|
||||
* @param {String} repositoryUrl The remote repository URL.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*/
|
||||
async function fetchNotes(repositoryUrl, execaOpts) {
|
||||
try {
|
||||
await execa(
|
||||
'git',
|
||||
['fetch', '--unshallow', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`],
|
||||
execaOpts
|
||||
);
|
||||
} catch (_) {
|
||||
await execa('git', ['fetch', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`], {
|
||||
...execaOpts,
|
||||
reject: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HEAD sha.
|
||||
*
|
||||
@@ -244,6 +236,18 @@ async function push(repositoryUrl, execaOpts) {
|
||||
await execa('git', ['push', '--tags', repositoryUrl], execaOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push notes to the remote repository.
|
||||
*
|
||||
* @param {String} repositoryUrl The remote repository URL.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*
|
||||
* @throws {Error} if the push failed.
|
||||
*/
|
||||
async function pushNotes(repositoryUrl, execaOpts) {
|
||||
await execa('git', ['push', repositoryUrl, `refs/notes/${GIT_NOTE_REF}`], execaOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a tag name is a valid Git reference.
|
||||
*
|
||||
@@ -286,29 +290,62 @@ async function verifyBranchName(branch, execaOpts) {
|
||||
* @return {Boolean} `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, falsy otherwise.
|
||||
*/
|
||||
async function isBranchUpToDate(repositoryUrl, branch, execaOpts) {
|
||||
const {stdout: remoteHead} = await execa('git', ['ls-remote', '--heads', repositoryUrl, branch], execaOpts);
|
||||
return (
|
||||
(await getGitHead(execaOpts)) ===
|
||||
(await execa('git', ['ls-remote', '--heads', repositoryUrl, branch], execaOpts)).stdout.match(/^(?<ref>\w+)?/)[1]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and parse the JSON note of a given reference.
|
||||
*
|
||||
* @param {String} ref The Git reference for which to retrieve the note.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*
|
||||
* @return {Object} the parsed JSON note if there is one, an empty object otherwise.
|
||||
*/
|
||||
async function getNote(ref, execaOpts) {
|
||||
try {
|
||||
return await isRefInHistory(remoteHead.match(/^(\w+)?/)[1], branch, false, execaOpts);
|
||||
return JSON.parse((await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout);
|
||||
} catch (error) {
|
||||
if (error.exitCode === 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
debug(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and parse the JSON note of a given reference.
|
||||
*
|
||||
* @param {Object} note The object to save in the reference note.
|
||||
* @param {String} ref The Git reference to add the note to.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*/
|
||||
async function addNote(note, ref, execaOpts) {
|
||||
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-f', '-m', JSON.stringify(note), ref], execaOpts);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTagHead,
|
||||
getTags,
|
||||
getCommits,
|
||||
getBranches,
|
||||
isRefInHistory,
|
||||
isRefExists,
|
||||
fetch,
|
||||
fetchNotes,
|
||||
getGitHead,
|
||||
repoUrl,
|
||||
isGitRepo,
|
||||
verifyAuth,
|
||||
tag,
|
||||
push,
|
||||
pushNotes,
|
||||
verifyTagName,
|
||||
isBranchUpToDate,
|
||||
verifyBranchName,
|
||||
getNote,
|
||||
addNote,
|
||||
};
|
||||
|
||||
+11
-4
@@ -31,11 +31,13 @@ function isMaintenanceRange(range) {
|
||||
}
|
||||
|
||||
function getUpperBound(range) {
|
||||
return semver.valid(range) ? range : ((semver.validRange(range) || '').match(/<(\d+\.\d+\.\d+)$/) || [])[1];
|
||||
return semver.valid(range)
|
||||
? range
|
||||
: ((semver.validRange(range) || '').match(/<(?<upperBound>\d+\.\d+\.\d+)$/) || [])[1];
|
||||
}
|
||||
|
||||
function getLowerBound(range) {
|
||||
return ((semver.validRange(range) || '').match(/(\d+\.\d+\.\d+)/) || [])[1];
|
||||
return ((semver.validRange(range) || '').match(/(?<lowerBound>\d+\.\d+\.\d+)/) || [])[1];
|
||||
}
|
||||
|
||||
function highest(version1, version2) {
|
||||
@@ -67,8 +69,12 @@ function getRange(min, max) {
|
||||
return `>=${min}${max ? ` <${max}` : ''}`;
|
||||
}
|
||||
|
||||
function makeTag(tagFormat, version, channel) {
|
||||
return template(tagFormat)({version: `${version}${channel ? `@${channel}` : ''}`});
|
||||
function makeTag(tagFormat, version) {
|
||||
return template(tagFormat)({version});
|
||||
}
|
||||
|
||||
function isSameChannel(channel, otherChannel) {
|
||||
return channel === otherChannel || (!channel && !otherChannel);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -86,4 +92,5 @@ module.exports = {
|
||||
getFirstVersion,
|
||||
getRange,
|
||||
makeTag,
|
||||
isSameChannel,
|
||||
};
|
||||
|
||||
+19
-19
@@ -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",
|
||||
"@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": "^6.0.0",
|
||||
"@semantic-release/npm": "^6.0.0",
|
||||
"@semantic-release/release-notes-generator": "^7.1.2",
|
||||
"aggregate-error": "^3.0.0",
|
||||
"cosmiconfig": "^5.0.1",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"debug": "^4.0.0",
|
||||
"env-ci": "^4.0.0",
|
||||
"execa": "^3.0.0",
|
||||
"env-ci": "^5.0.0",
|
||||
"execa": "^4.0.0",
|
||||
"figures": "^3.0.0",
|
||||
"find-versions": "^3.0.0",
|
||||
"get-stream": "^5.0.0",
|
||||
@@ -39,40 +39,40 @@
|
||||
"hook-std": "^2.0.0",
|
||||
"hosted-git-info": "^3.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"marked": "^0.7.0",
|
||||
"marked": "^0.8.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": "^7.1.1",
|
||||
"semver-diff": "^3.1.1",
|
||||
"signale": "^1.2.1",
|
||||
"yargs": "^14.0.0"
|
||||
"yargs": "^15.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^2.0.0",
|
||||
"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",
|
||||
"got": "^10.2.1",
|
||||
"js-yaml": "^3.10.0",
|
||||
"mockserver-client": "^5.1.1",
|
||||
"nock": "^11.1.0",
|
||||
"nyc": "^14.0.0",
|
||||
"nyc": "^15.0.0",
|
||||
"p-retry": "^4.0.0",
|
||||
"proxyquire": "^2.0.0",
|
||||
"sinon": "^7.2.7",
|
||||
"sinon": "^8.0.4",
|
||||
"stream-buffers": "^3.0.2",
|
||||
"tempy": "^0.3.0",
|
||||
"xo": "^0.24.0"
|
||||
"xo": "^0.25.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.15"
|
||||
"node": ">=10.13"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
|
||||
+100
-40
@@ -24,51 +24,82 @@ test('Enforce ranges with branching release workflow', async t => {
|
||||
];
|
||||
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
|
||||
|
||||
let result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
let result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.0 <1.1.0', 'Can release only patch on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.1.0 <2.0.0', 'Can release only minor on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.0', 'Can release only major on next-major');
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.0');
|
||||
t.is(getBranch(result, 'next').range, '>=1.0.0');
|
||||
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
|
||||
|
||||
release(branches, 'master', '1.0.0');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.0 <1.1.0', 'Can release only patch on master');
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.0');
|
||||
t.is(getBranch(result, 'next').range, '>=1.0.0');
|
||||
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
|
||||
|
||||
release(branches, 'master', '1.0.1');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1', 'Can release only > than 1.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.0.1', 'Can release only > than 1.0.1 on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=1.0.1', 'Can release only > than 1.0.1 on next-major');
|
||||
|
||||
merge(branches, 'master', 'next');
|
||||
merge(branches, 'master', 'next-major');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.1.0 <2.0.0', 'Can release only minor on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.0', 'Can release only major on next-major');
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1', 'Can release only > than 1.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.0.1', 'Can release only > than 1.0.1 on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=1.0.1', 'Can release only > than 1.0.1 on next-major');
|
||||
|
||||
release(branches, 'next', '1.1.0');
|
||||
release(branches, 'next', '1.1.1');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.0 on next');
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.1.1', 'Can release only > than 1.1.1 on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=1.1.1', 'Can release > than 1.1.1 on next-major');
|
||||
|
||||
release(branches, 'next-major', '2.0.0');
|
||||
release(branches, 'next-major', '2.0.1');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.0 on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
|
||||
|
||||
merge(branches, 'next-major', 'beta');
|
||||
release(branches, 'beta', '3.0.0-beta.1');
|
||||
merge(branches, 'beta', 'alpha');
|
||||
release(branches, 'alpha', '4.0.0-alpha.1');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
|
||||
|
||||
merge(branches, 'master', '1.0.x');
|
||||
merge(branches, 'master', '1.x');
|
||||
release(branches, 'master', '1.0.1');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
|
||||
t.is(
|
||||
getBranch(result, '1.0.x').range,
|
||||
@@ -80,7 +111,10 @@ test('Enforce ranges with branching release workflow', async t => {
|
||||
release(branches, 'master', '1.0.2');
|
||||
release(branches, 'master', '1.0.3');
|
||||
release(branches, 'master', '1.0.4');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.4 <1.1.0', 'Can release only patch, > than 1.0.4 on master');
|
||||
t.is(
|
||||
getBranch(result, '1.0.x').range,
|
||||
@@ -90,9 +124,13 @@ test('Enforce ranges with branching release workflow', async t => {
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 1.2.0 is released on master');
|
||||
|
||||
merge(branches, 'next', 'master');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.1.1 <1.2.0', 'Can release only patch, > than 1.1.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.2.0 <2.0.0', 'Can release only patch or minor, > than 1.2.0 on next');
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
|
||||
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.1 on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
|
||||
t.is(
|
||||
getBranch(result, '1.0.x').range,
|
||||
@@ -102,34 +140,50 @@ test('Enforce ranges with branching release workflow', async t => {
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 2.0.0 is released on master');
|
||||
|
||||
merge(branches, 'master', '1.0.x', '1.0.4');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.1.1 <1.2.0', 'Can release only patch, > than 1.1.1 on master');
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.1.0', 'Cannot release on 1.x before >= 2.0.0 is released on master');
|
||||
|
||||
merge(branches, 'master', '1.x');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.1.1 <1.2.0', 'Can release only patch, > than 1.1.1 on master');
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.1.1', 'Can release only > than 1.1.1 on master');
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.1 <1.1.1', 'Cannot release on 1.x before >= 2.0.0 is released on master');
|
||||
|
||||
merge(branches, 'next-major', 'next');
|
||||
merge(branches, 'next', 'master');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=2.0.1 <2.1.0', 'Can release only patch, > than 2.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=2.1.0 <3.0.0', 'Can release only minor on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only major on next-major');
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=2.0.1', 'Can release only > than 2.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=2.0.1', 'Can release only > than 2.0.1 on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release only > than 2.0.1 on next-major');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.1 <2.0.0', 'Can release on 1.x only within range');
|
||||
|
||||
merge(branches, 'beta', 'master');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=2.0.1 <2.1.0', 'Can release only patch, > than 2.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=2.1.0 <3.0.0', 'Can release only minor on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only major on next-major');
|
||||
release(branches, 'master', '3.0.0');
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, 'master').range, '>=3.0.0', 'Can release only > than 3.0.0 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=3.0.0', 'Can release only > than 3.0.0 on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only > than 3.0.0 on next-major');
|
||||
|
||||
branches.push({name: '1.1.x', tags: []});
|
||||
merge(branches, '1.x', '1.1.x');
|
||||
result = (await getBranches('repositoryUrl', {options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||
name,
|
||||
range,
|
||||
}));
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
|
||||
t.is(getBranch(result, '1.1.x').range, '>=1.1.1 <1.2.0', 'Can release on 1.1.x only within range');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.2.0 <2.0.0', 'Can release on 1.x only within range');
|
||||
@@ -146,7 +200,7 @@ test('Throw SemanticReleaseError for invalid configurations', async t => {
|
||||
{name: 'preview', prerelease: 'alpha', tags: []},
|
||||
];
|
||||
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
|
||||
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', {options: {branches}})))];
|
||||
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', 'master', {options: {branches}})))];
|
||||
|
||||
t.is(errors[0].name, 'SemanticReleaseError');
|
||||
t.is(errors[0].code, 'EMAINTENANCEBRANCH');
|
||||
@@ -171,10 +225,13 @@ 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}})))];
|
||||
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', 'master', {options: {branches}})))];
|
||||
|
||||
t.is(errors[0].name, 'SemanticReleaseError');
|
||||
t.is(errors[0].code, 'EDUPLICATEBRANCHES');
|
||||
@@ -183,10 +240,13 @@ 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}})))];
|
||||
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', 'master', {options: {branches}})))];
|
||||
|
||||
t.is(errors[0].name, 'SemanticReleaseError');
|
||||
t.is(errors[0].code, 'EINVALIDBRANCHNAME');
|
||||
|
||||
@@ -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, gitAddNote} 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: [null]},
|
||||
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
|
||||
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channels: [null]},
|
||||
],
|
||||
},
|
||||
]);
|
||||
@@ -30,20 +30,21 @@ test('Get the valid tags', async t => {
|
||||
|
||||
test('Get the valid tags from multiple branches', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@1.x', undefined, {cwd});
|
||||
commits.push(...(await gitCommits(['Second'], {cwd})));
|
||||
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.0.0', {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.1.0@1.x', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.1.0', {cwd});
|
||||
await gitCheckout('1.x', true, {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
commits.push(...(await gitCommits(['Third'], {cwd})));
|
||||
await gitCommits(['Third'], {cwd});
|
||||
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v2.0.0@next', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
|
||||
await gitCheckout('next', true, {cwd});
|
||||
commits.push(...(await gitCommits(['Fourth'], {cwd})));
|
||||
await gitTagVersion('v3.0.0@next', undefined, {cwd});
|
||||
await gitCommits(['Fourth'], {cwd});
|
||||
await gitTagVersion('v3.0.0', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['next']}), 'v3.0.0', {cwd});
|
||||
|
||||
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [
|
||||
{name: '1.x'},
|
||||
@@ -55,47 +56,17 @@ 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: [null, '1.x']},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null, '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: [null, 'next']}],
|
||||
},
|
||||
{
|
||||
name: 'next',
|
||||
tags: [...result[1].tags, {gitTag: 'v3.0.0@next', version: '3.0.0', channel: 'next', gitHead: commits[3].hash}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
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 gitTagVersion('prefix@v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('prefix@v1.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('prefix@v2.0.0', undefined, {cwd});
|
||||
await gitTagVersion('prefix@v2.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('other-prefix@v3.0.0', undefined, {cwd});
|
||||
|
||||
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}]);
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
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},
|
||||
],
|
||||
tags: [...result[1].tags, {gitTag: 'v3.0.0', version: '3.0.0', channels: ['next']}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -109,48 +80,53 @@ 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});
|
||||
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
|
||||
await gitCommits(['Third'], {cwd});
|
||||
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v2.0.0@next', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
|
||||
await gitCommits(['Fourth'], {cwd});
|
||||
await gitTagVersion('v3.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v3.0.0@next', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v3.0.0', {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
|
||||
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: [null]}]},
|
||||
]);
|
||||
|
||||
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: [null]}]},
|
||||
]);
|
||||
|
||||
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: [null]}],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -158,7 +134,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: [null]}],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -166,37 +142,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: [null]}],
|
||||
},
|
||||
]);
|
||||
|
||||
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: [null]}]},
|
||||
]);
|
||||
});
|
||||
|
||||
+127
-56
@@ -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}) => ({
|
||||
@@ -151,16 +155,33 @@ test('Release branches - initial state', t => {
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
normalize
|
||||
.release({release})
|
||||
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=1.0.0 <1.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=1.1.0 <2.0.0', accept: ['patch', 'minor'], channel: 'next'},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'master',
|
||||
range: '>=1.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: undefined,
|
||||
main: true,
|
||||
},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next',
|
||||
range: '>=1.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next',
|
||||
main: false,
|
||||
},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=2.0.0',
|
||||
range: '>=1.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
main: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -174,16 +195,26 @@ test('Release branches - 3 release branches', t => {
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
normalize
|
||||
.release({release})
|
||||
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=1.0.2 <1.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=1.2.0 <2.0.0', accept: ['patch', 'minor'], channel: 'next'},
|
||||
{type: 'release', name: 'master', range: '>=1.0.2 <1.1.0', accept: ['patch'], channel: undefined, main: true},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next',
|
||||
range: '>=1.2.0 <2.0.0',
|
||||
accept: ['patch', 'minor'],
|
||||
channel: 'next',
|
||||
main: false,
|
||||
},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=2.1.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
main: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -196,10 +227,26 @@ test('Release branches - 2 release branches', t => {
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
normalize
|
||||
.release({release})
|
||||
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=1.2.0 <2.0.0', accept: ['patch', 'minor'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=2.1.0', accept: ['patch', 'minor', 'major'], channel: 'next'},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'master',
|
||||
range: '>=1.2.0 <2.0.0',
|
||||
accept: ['patch', 'minor'],
|
||||
channel: undefined,
|
||||
main: true,
|
||||
},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next',
|
||||
range: '>=2.1.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next',
|
||||
main: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -221,16 +268,19 @@ test('Release branches - cap ranges to first release only present on following b
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
normalize
|
||||
.release({release})
|
||||
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=2.1.0 <2.2.0', accept: ['patch'], channel: 'next'},
|
||||
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined, main: true},
|
||||
{type: 'release', name: 'next', range: '>=2.1.0 <2.2.0', accept: ['patch'], channel: 'next', main: false},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=2.2.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
main: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -243,38 +293,31 @@ test('Release branches - Handle missing previous tags in branch history', t => {
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
normalize
|
||||
.release({release})
|
||||
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=2.0.0 <3.0.0', accept: ['patch', 'minor'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=3.0.0', accept: ['patch', 'minor', 'major'], channel: 'next'},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - enforce release gaps after downstream merge', t => {
|
||||
const release = [
|
||||
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
|
||||
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
|
||||
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=2.1.0 <3.0.0', accept: ['patch', 'minor'], channel: 'next'},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=3.0.0',
|
||||
name: 'master',
|
||||
range: '>=2.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
channel: undefined,
|
||||
main: true,
|
||||
},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next',
|
||||
range: '>=2.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next',
|
||||
main: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - limit releases on 2nd and 3rd branche based on 1st branch last release', t => {
|
||||
test('Release branches - limit releases on 2nd and 3rd branch based on 1st branch last release', t => {
|
||||
const release = [
|
||||
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '2.0.0', '3.0.0'])},
|
||||
{name: 'next', tags: toTags(['1.0.0', '1.1.0'])},
|
||||
@@ -282,16 +325,33 @@ test('Release branches - limit releases on 2nd and 3rd branche based on 1st bran
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
normalize
|
||||
.release({release})
|
||||
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=3.0.0 <3.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=3.1.0 <4.0.0', accept: ['patch', 'minor'], channel: 'next'},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'master',
|
||||
range: '>=3.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: undefined,
|
||||
main: true,
|
||||
},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next',
|
||||
range: '>=3.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next',
|
||||
main: false,
|
||||
},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=4.0.0',
|
||||
range: '>=3.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
main: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -303,24 +363,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
@@ -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}});
|
||||
|
||||
@@ -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'}));
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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', gitHead: 'v3.0.0-beta.1'},
|
||||
],
|
||||
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,9 @@ 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', gitHead: 'v1.0.0-beta.1', channels: ['beta']},
|
||||
{version: '1.0.0-beta.2', gitTag: 'v1.0.0-beta.2', gitHead: 'v1.0.0-beta.2', channels: ['beta']},
|
||||
{version: '1.0.0-alpha.1', gitTag: 'v1.0.0-alpha.1', gitHead: 'v1.0.0-alpha.1', channels: ['alpha']},
|
||||
],
|
||||
type: 'prerelease',
|
||||
},
|
||||
@@ -36,10 +36,10 @@ test('Get the highest prerelease valid tag, ignoring other tags from other prere
|
||||
|
||||
t.deepEqual(result, {
|
||||
version: '1.0.0-beta.2',
|
||||
gitTag: 'v1.0.0-beta.2@beta',
|
||||
gitTag: 'v1.0.0-beta.2',
|
||||
name: 'v1.0.0-beta.2',
|
||||
gitHead: '222',
|
||||
channel: 'beta',
|
||||
gitHead: 'v1.0.0-beta.2',
|
||||
channels: ['beta'],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,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', gitHead: 'v3.0.0-beta.1'}],
|
||||
type: 'release',
|
||||
},
|
||||
options: {tagFormat: `v\${version}`},
|
||||
@@ -63,11 +63,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', gitHead: 'v2.0.0-beta.1'},
|
||||
{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 +76,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});
|
||||
});
|
||||
|
||||
+150
-38
@@ -11,9 +11,9 @@ test.beforeEach(t => {
|
||||
test('Increase version for patch release', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'master', type: 'release'},
|
||||
branch: {name: 'master', type: 'release', tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}]},
|
||||
nextRelease: {type: 'patch'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
lastRelease: {version: '1.0.0', channels: [null]},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.1'
|
||||
@@ -23,9 +23,9 @@ test('Increase version for patch release', t => {
|
||||
test('Increase version for minor release', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'master', type: 'release'},
|
||||
branch: {name: 'master', type: 'release', tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}]},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
lastRelease: {version: '1.0.0', channels: [null]},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.1.0'
|
||||
@@ -35,9 +35,9 @@ test('Increase version for minor release', t => {
|
||||
test('Increase version for major release', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'master', type: 'release'},
|
||||
branch: {name: 'master', type: 'release', tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}]},
|
||||
nextRelease: {type: 'major'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
lastRelease: {version: '1.0.0', channels: [null]},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'2.0.0'
|
||||
@@ -47,7 +47,7 @@ test('Increase version for major release', t => {
|
||||
test('Return 1.0.0 if there is no previous release', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'master', type: 'release'},
|
||||
branch: {name: 'master', type: 'release', tags: []},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {},
|
||||
logger: t.context.logger,
|
||||
@@ -59,9 +59,14 @@ test('Return 1.0.0 if there is no previous release', t => {
|
||||
test('Increase version for patch release on prerelease branch', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'patch'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
branch: {
|
||||
name: 'beta',
|
||||
type: 'prerelease',
|
||||
prerelease: 'beta',
|
||||
tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}],
|
||||
},
|
||||
nextRelease: {type: 'patch', channel: 'beta'},
|
||||
lastRelease: {version: '1.0.0', channels: [null]},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.1-beta.1'
|
||||
@@ -69,31 +74,49 @@ test('Increase version for patch release on prerelease branch', t => {
|
||||
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'patch'},
|
||||
lastRelease: {version: '1.0.0-beta.1'},
|
||||
branch: {
|
||||
name: 'beta',
|
||||
type: 'prerelease',
|
||||
prerelease: 'beta',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
|
||||
{gitTag: 'v1.0.1-beta.1', version: '1.0.1-beta.1', channels: ['beta']},
|
||||
],
|
||||
},
|
||||
nextRelease: {type: 'patch', channel: 'beta'},
|
||||
lastRelease: {version: '1.0.1-beta.1', channels: ['beta']},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.0-beta.2'
|
||||
'1.0.1-beta.2'
|
||||
);
|
||||
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
|
||||
branch: {
|
||||
name: 'alpha',
|
||||
type: 'prerelease',
|
||||
prerelease: 'alpha',
|
||||
tags: [{gitTag: 'v1.0.1-beta.1', version: '1.0.1-beta.1', channels: ['beta']}],
|
||||
},
|
||||
nextRelease: {type: 'patch', channel: 'alpha'},
|
||||
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
|
||||
lastRelease: {version: '1.0.1-beta.1', channels: ['beta']},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.1-alpha.1'
|
||||
'1.0.2-alpha.1'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for minor release on prerelease branch', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
branch: {
|
||||
name: 'beta',
|
||||
type: 'prerelease',
|
||||
prerelease: 'beta',
|
||||
tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}],
|
||||
},
|
||||
nextRelease: {type: 'minor', channel: 'beta'},
|
||||
lastRelease: {version: '1.0.0', channels: [null]},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.1.0-beta.1'
|
||||
@@ -101,31 +124,49 @@ test('Increase version for minor release on prerelease branch', t => {
|
||||
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {version: '1.0.0-beta.1'},
|
||||
branch: {
|
||||
name: 'beta',
|
||||
type: 'prerelease',
|
||||
prerelease: 'beta',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
|
||||
{gitTag: 'v1.1.0-beta.1', version: '1.1.0-beta.1', channels: ['beta']},
|
||||
],
|
||||
},
|
||||
nextRelease: {type: 'minor', channel: 'beta'},
|
||||
lastRelease: {version: '1.1.0-beta.1', channels: ['beta']},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.0-beta.2'
|
||||
'1.1.0-beta.2'
|
||||
);
|
||||
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
|
||||
branch: {
|
||||
name: 'alpha',
|
||||
type: 'prerelease',
|
||||
prerelease: 'alpha',
|
||||
tags: [{gitTag: 'v1.1.0-beta.1', version: '1.1.0-beta.1', channels: ['beta']}],
|
||||
},
|
||||
nextRelease: {type: 'minor', channel: 'alpha'},
|
||||
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
|
||||
lastRelease: {version: '1.1.0-beta.1', channels: ['beta']},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.1.0-alpha.1'
|
||||
'1.2.0-alpha.1'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for major release on prerelease branch', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'major'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
branch: {
|
||||
name: 'beta',
|
||||
type: 'prerelease',
|
||||
prerelease: 'beta',
|
||||
tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]}],
|
||||
},
|
||||
nextRelease: {type: 'major', channel: 'beta'},
|
||||
lastRelease: {version: '1.0.0', channels: [null]},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'2.0.0-beta.1'
|
||||
@@ -133,29 +174,42 @@ test('Increase version for major release on prerelease branch', t => {
|
||||
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'major'},
|
||||
lastRelease: {version: '1.0.0-beta.1'},
|
||||
branch: {
|
||||
name: 'beta',
|
||||
type: 'prerelease',
|
||||
prerelease: 'beta',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
|
||||
{gitTag: 'v2.0.0-beta.1', version: '2.0.0-beta.1', channels: ['beta']},
|
||||
],
|
||||
},
|
||||
nextRelease: {type: 'major', channel: 'beta'},
|
||||
lastRelease: {version: '2.0.0-beta.1', channels: ['beta']},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.0-beta.2'
|
||||
'2.0.0-beta.2'
|
||||
);
|
||||
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
|
||||
branch: {
|
||||
name: 'alpha',
|
||||
type: 'prerelease',
|
||||
prerelease: 'alpha',
|
||||
tags: [{gitTag: 'v2.0.0-beta.1', version: '2.0.0-beta.1', channels: ['beta']}],
|
||||
},
|
||||
nextRelease: {type: 'major', channel: 'alpha'},
|
||||
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
|
||||
lastRelease: {version: '2.0.0-beta.1', channels: ['beta']},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'2.0.0-alpha.1'
|
||||
'3.0.0-alpha.1'
|
||||
);
|
||||
});
|
||||
|
||||
test('Return 1.0.0 if there is no previous release on prerelease branch', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta', tags: []},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {},
|
||||
logger: t.context.logger,
|
||||
@@ -163,3 +217,61 @@ test('Return 1.0.0 if there is no previous release on prerelease branch', t => {
|
||||
'1.0.0-beta.1'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for release on prerelease branch after previous commits were merged to release branch', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {
|
||||
name: 'beta',
|
||||
type: 'prerelease',
|
||||
prerelease: 'beta',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]}, // Version v1.1.0 released on default branch after beta was merged into master
|
||||
{gitTag: 'v1.1.0-beta.1', version: '1.1.0-beta.1', channels: [null, 'beta']},
|
||||
],
|
||||
},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {version: '1.1.0', channels: [null]},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.2.0-beta.1'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for release on prerelease branch based on highest commit type since last regular release', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {
|
||||
name: 'beta',
|
||||
type: 'prerelease',
|
||||
prerelease: 'beta',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
|
||||
{gitTag: 'v1.1.0-beta.1', version: '1.1.0-beta.1', channels: [null, 'beta']},
|
||||
],
|
||||
},
|
||||
nextRelease: {type: 'major'},
|
||||
lastRelease: {version: 'v1.1.0-beta.1', channels: [null]},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'2.0.0-beta.1'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for release on prerelease branch when there is no regular releases on other branches', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {
|
||||
name: 'beta',
|
||||
type: 'prerelease',
|
||||
prerelease: 'beta',
|
||||
tags: [{gitTag: 'v1.0.0-beta.1', version: '1.0.0-beta.1', channels: ['beta']}],
|
||||
},
|
||||
nextRelease: {type: 'minor', channel: 'beta'},
|
||||
lastRelease: {version: 'v1.0.0-beta.1', channels: ['beta']},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.0-beta.2'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
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', version: '2.0.0', channels: ['2.x']},
|
||||
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
|
||||
{gitTag: 'v2.1.0', version: '2.1.0', channels: [null]},
|
||||
{gitTag: 'v2.1.1', version: '2.1.1', channels: [null]},
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
|
||||
],
|
||||
},
|
||||
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
|
||||
options: {tagFormat: `v\${version}`},
|
||||
});
|
||||
|
||||
t.deepEqual(result, {
|
||||
lastRelease: {version: '2.1.0', channels: [null], gitTag: 'v2.1.0', name: 'v2.1.0', gitHead: 'v2.1.0'},
|
||||
currentRelease: {
|
||||
type: 'patch',
|
||||
version: '2.1.1',
|
||||
channels: [null],
|
||||
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',
|
||||
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: [null, 'next']},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channels: ['next']},
|
||||
{gitTag: 'v2.0.0', 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',
|
||||
name: 'v1.1.0',
|
||||
gitHead: 'v1.1.0',
|
||||
channels: ['next'],
|
||||
},
|
||||
currentRelease: {
|
||||
type: 'major',
|
||||
version: '2.0.0',
|
||||
channels: ['next-major'],
|
||||
gitTag: 'v2.0.0',
|
||||
name: 'v2.0.0',
|
||||
gitHead: 'v2.0.0',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'major',
|
||||
version: '2.0.0',
|
||||
channel: null,
|
||||
gitTag: 'v2.0.0',
|
||||
name: 'v2.0.0',
|
||||
gitHead: 'v2.0.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Return releases sorted by ascending order', t => {
|
||||
const result = getReleaseToAdd({
|
||||
branch: {
|
||||
name: 'master',
|
||||
tags: [
|
||||
{gitTag: 'v2.0.0', version: '2.0.0', channels: ['next-major']},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channels: ['next']},
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, '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', name: 'v1.1.0', gitHead: 'v1.1.0', channels: ['next']},
|
||||
currentRelease: {
|
||||
type: 'major',
|
||||
version: '2.0.0',
|
||||
channels: ['next-major'],
|
||||
gitTag: 'v2.0.0',
|
||||
name: 'v2.0.0',
|
||||
gitHead: 'v2.0.0',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'major',
|
||||
version: '2.0.0',
|
||||
channel: null,
|
||||
gitTag: 'v2.0.0',
|
||||
name: 'v2.0.0',
|
||||
gitHead: 'v2.0.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('No lastRelease', t => {
|
||||
const result = getReleaseToAdd({
|
||||
branch: {
|
||||
name: 'master',
|
||||
tags: [{gitTag: 'v1.0.0', 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',
|
||||
name: 'v1.0.0',
|
||||
gitHead: 'v1.0.0',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'major',
|
||||
version: '1.0.0',
|
||||
channel: null,
|
||||
gitTag: 'v1.0.0',
|
||||
name: 'v1.0.0',
|
||||
gitHead: 'v1.0.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Ignore pre-release versions', t => {
|
||||
const result = getReleaseToAdd({
|
||||
branch: {
|
||||
name: 'master',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, 'next']},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channels: ['next']},
|
||||
{gitTag: 'v2.0.0-alpha.1', 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: [null, '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',
|
||||
name: 'v1.1.0',
|
||||
gitHead: 'v1.1.0',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'minor',
|
||||
version: '1.1.0',
|
||||
channel: null,
|
||||
gitTag: 'v1.1.0',
|
||||
name: 'v1.1.0',
|
||||
gitHead: 'v1.1.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
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: [null]},
|
||||
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
|
||||
{gitTag: 'v2.1.0', version: '2.1.0', channels: [null]},
|
||||
{gitTag: 'v2.1.1', version: '2.1.1', channels: [null]},
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
|
||||
],
|
||||
},
|
||||
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: [null]},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
|
||||
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
|
||||
],
|
||||
},
|
||||
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', version: '2.0.0', channels: ['2.x']},
|
||||
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
|
||||
{gitTag: 'v2.1.0', version: '2.1.0', channels: [null]},
|
||||
{gitTag: 'v2.1.1', version: '2.1.1', channels: [null, '2.x']},
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
|
||||
],
|
||||
},
|
||||
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
|
||||
options: {tagFormat: `v\${version}`},
|
||||
});
|
||||
|
||||
t.is(result, undefined);
|
||||
});
|
||||
@@ -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, []);
|
||||
});
|
||||
+161
-29
@@ -2,7 +2,6 @@ import test from 'ava';
|
||||
import tempy from 'tempy';
|
||||
import {
|
||||
getTagHead,
|
||||
isRefInHistory,
|
||||
isRefExists,
|
||||
fetch,
|
||||
getGitHead,
|
||||
@@ -14,6 +13,9 @@ import {
|
||||
isGitRepo,
|
||||
verifyTagName,
|
||||
isBranchUpToDate,
|
||||
getNote,
|
||||
addNote,
|
||||
fetchNotes,
|
||||
} from '../lib/git';
|
||||
import {
|
||||
gitRepo,
|
||||
@@ -27,6 +29,10 @@ import {
|
||||
gitRemoteTagHead,
|
||||
gitPush,
|
||||
gitDetachedHead,
|
||||
gitDetachedHeadFromBranch,
|
||||
gitAddNote,
|
||||
gitGetNote,
|
||||
gitFetch,
|
||||
} from './helpers/git-utils';
|
||||
|
||||
test('Get the last commit sha', async t => {
|
||||
@@ -58,7 +64,7 @@ test('Unshallow and fetch repository', async t => {
|
||||
// Verify the shallow clone contains only one commit
|
||||
t.is((await gitGetCommits(undefined, {cwd})).length, 1);
|
||||
|
||||
await fetch(repositoryUrl, 'master', {cwd});
|
||||
await fetch(repositoryUrl, 'master', 'master', {cwd});
|
||||
|
||||
// Verify the shallow clone contains all the commits
|
||||
t.is((await gitGetCommits(undefined, {cwd})).length, 2);
|
||||
@@ -73,8 +79,8 @@ test('Do not throw error when unshallow a complete repository', async t => {
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitPush(repositoryUrl, 'second-branch', {cwd});
|
||||
|
||||
await t.notThrowsAsync(fetch(repositoryUrl, 'master', {cwd}));
|
||||
await t.notThrowsAsync(fetch(repositoryUrl, 'second-branch', {cwd}));
|
||||
await t.notThrowsAsync(fetch(repositoryUrl, 'master', 'master', {cwd}));
|
||||
await t.notThrowsAsync(fetch(repositoryUrl, 'second-branch', 'master', {cwd}));
|
||||
});
|
||||
|
||||
test('Fetch all tags on a detached head repository', async t => {
|
||||
@@ -89,26 +95,62 @@ test('Fetch all tags on a detached head repository', async t => {
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
cwd = await gitDetachedHead(repositoryUrl, commit.hash);
|
||||
|
||||
await fetch(repositoryUrl, 'master', {cwd});
|
||||
await fetch(repositoryUrl, 'master', '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 => {
|
||||
// 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
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
// Create the new branch 'other-branch' from master
|
||||
await gitCheckout('other-branch', true, {cwd});
|
||||
// Add commits to the 'other-branch' branch
|
||||
const otherCommits = await gitCommits(['Second'], {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
test('Fetch all tags on a repository with a detached head from branch (CircleCI)', async t => {
|
||||
let {cwd, repositoryUrl} = await gitRepo();
|
||||
|
||||
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}));
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitTagVersion('v1.0.1', undefined, {cwd});
|
||||
const [commit] = await gitCommits(['Third'], {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
await gitCheckout('other-branch', true, {cwd});
|
||||
await gitPush(repositoryUrl, 'other-branch', {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
await gitCommits(['Fourth'], {cwd});
|
||||
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
cwd = await gitDetachedHeadFromBranch(repositoryUrl, 'other-branch', commit.hash);
|
||||
|
||||
await fetch(repositoryUrl, 'master', 'other-branch', {cwd});
|
||||
await fetch(repositoryUrl, 'other-branch', 'other-branch', {cwd});
|
||||
|
||||
t.deepEqual((await getTags('other-branch', {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', 'v2.0.0'].sort());
|
||||
});
|
||||
|
||||
test('Fetch all tags on a detached head repository with outdated cached repo (GitLab CI)', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo();
|
||||
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitTagVersion('v1.0.1', undefined, {cwd});
|
||||
let [commit] = await gitCommits(['Third'], {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
|
||||
// Create a clone (as first CI run would)
|
||||
const cloneCwd = await gitShallowClone(repositoryUrl);
|
||||
await gitFetch(repositoryUrl, {cwd: cloneCwd});
|
||||
await gitCheckout(commit.hash, false, {cwd: cloneCwd});
|
||||
|
||||
// Push tag to remote
|
||||
[commit] = await gitCommits(['Fourth'], {cwd});
|
||||
await gitTagVersion('v1.2.0', undefined, {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
|
||||
// Fetch on the cached repo and make detached head, leaving master outdated
|
||||
await fetch(repositoryUrl, 'master', 'master', {cwd: cloneCwd});
|
||||
await gitCheckout(commit.hash, false, {cwd: cloneCwd});
|
||||
|
||||
t.deepEqual((await getTags('master', {cwd: cloneCwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0', 'v1.2.0'].sort());
|
||||
});
|
||||
|
||||
test('Verify if a branch exists', async t => {
|
||||
@@ -140,7 +182,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 +191,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 +284,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 => {
|
||||
@@ -269,11 +310,102 @@ test('Return falsy if repository is not up to date', async t => {
|
||||
t.falsy(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
|
||||
});
|
||||
|
||||
test('Return "true" if local repository is ahead', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
test('Return falsy if detached head repository is not up to date', async t => {
|
||||
let {cwd, repositoryUrl} = await gitRepo();
|
||||
|
||||
t.true(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
|
||||
const [commit] = await gitCommits(['First'], {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
cwd = await gitDetachedHead(repositoryUrl, commit.hash);
|
||||
await fetch(repositoryUrl, 'master', 'master', {cwd});
|
||||
|
||||
t.falsy(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
|
||||
});
|
||||
|
||||
test('Get a commit note', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
|
||||
await gitAddNote(JSON.stringify({note: 'note'}), commits[0].hash, {cwd});
|
||||
|
||||
t.deepEqual(await getNote(commits[0].hash, {cwd}), {note: 'note'});
|
||||
});
|
||||
|
||||
test('Return empty object if there is no commit note', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
|
||||
t.deepEqual(await getNote(commits[0].hash, {cwd}), {});
|
||||
});
|
||||
|
||||
test('Throw error if a commit note in invalid', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
|
||||
await gitAddNote('non-json note', commits[0].hash, {cwd});
|
||||
|
||||
await t.throwsAsync(getNote(commits[0].hash, {cwd}));
|
||||
});
|
||||
|
||||
test('Add a commit note', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
|
||||
await addNote({note: 'note'}, commits[0].hash, {cwd});
|
||||
|
||||
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
|
||||
});
|
||||
|
||||
test('Overwrite a commit note', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
|
||||
await addNote({note: 'note'}, commits[0].hash, {cwd});
|
||||
await addNote({note: 'note2'}, commits[0].hash, {cwd});
|
||||
|
||||
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note2"}');
|
||||
});
|
||||
|
||||
test('Unshallow and fetch repository with notes', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
let {cwd, repositoryUrl} = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['First', 'Second'], {cwd});
|
||||
await gitAddNote(JSON.stringify({note: 'note'}), commits[0].hash, {cwd});
|
||||
// Create a shallow clone with only 1 commit
|
||||
cwd = await gitShallowClone(repositoryUrl);
|
||||
|
||||
// Verify the shallow clone doesn't contains the note
|
||||
await t.throwsAsync(gitGetNote(commits[0].hash, {cwd}));
|
||||
|
||||
await fetch(repositoryUrl, 'master', 'master', {cwd});
|
||||
await fetchNotes(repositoryUrl, {cwd});
|
||||
|
||||
// Verify the shallow clone contains the note
|
||||
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
|
||||
});
|
||||
|
||||
test('Fetch all notes on a detached head repository', async t => {
|
||||
let {cwd, repositoryUrl} = await gitRepo();
|
||||
|
||||
await gitCommits(['First'], {cwd});
|
||||
const [commit] = await gitCommits(['Second'], {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
await gitAddNote(JSON.stringify({note: 'note'}), commit.hash, {cwd});
|
||||
cwd = await gitDetachedHead(repositoryUrl, commit.hash);
|
||||
|
||||
await fetch(repositoryUrl, 'master', 'master', {cwd});
|
||||
await fetchNotes(repositoryUrl, {cwd});
|
||||
|
||||
t.is(await gitGetNote(commit.hash, {cwd}), '{"note":"note"}');
|
||||
});
|
||||
|
||||
+50
-18
@@ -4,6 +4,7 @@ import fileUrl from 'file-url';
|
||||
import pEachSeries from 'p-each-series';
|
||||
import gitLogParser from 'git-log-parser';
|
||||
import getStream from 'get-stream';
|
||||
import {GIT_NOTE_REF} from '../../lib/definitions/constants';
|
||||
|
||||
/**
|
||||
* Commit message informations.
|
||||
@@ -86,9 +87,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,13 +102,23 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch current git repository.
|
||||
*
|
||||
* @param {String} repositoryUrl The repository remote URL.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*/
|
||||
export async function gitFetch(repositoryUrl, execaOpts) {
|
||||
await execa('git', ['fetch', repositoryUrl], execaOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HEAD sha.
|
||||
*
|
||||
@@ -163,6 +176,17 @@ export async function gitDetachedHead(repositoryUrl, head) {
|
||||
return cwd;
|
||||
}
|
||||
|
||||
export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
|
||||
const cwd = tempy.directory();
|
||||
|
||||
await execa('git', ['init'], {cwd});
|
||||
await execa('git', ['remote', 'add', 'origin', repositoryUrl], {cwd});
|
||||
await execa('git', ['fetch', '--force', repositoryUrl, `${branch}:remotes/origin/${branch}`], {cwd});
|
||||
await execa('git', ['reset', '--hard', head], {cwd});
|
||||
await execa('git', ['checkout', '-q', '-B', branch], {cwd});
|
||||
return cwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Git configuration.
|
||||
*
|
||||
@@ -199,7 +223,7 @@ export async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
|
||||
return (await execa('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOpts)).stdout
|
||||
.split('\n')
|
||||
.filter(tag => Boolean(tag))
|
||||
.map(tag => tag.match(/^(\S+)/)[1])[0];
|
||||
.map(tag => tag.match(/^(?<tag>\S+)/)[1])[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,7 +247,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);
|
||||
}
|
||||
|
||||
@@ -257,15 +281,23 @@ 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
|
||||
);
|
||||
/**
|
||||
* Add a note to a Git reference.
|
||||
*
|
||||
* @param {String} note The note to add.
|
||||
* @param {String} ref The ref to add the note to.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*/
|
||||
export async function gitAddNote(note, ref, execaOpts) {
|
||||
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-m', note, ref], execaOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the note associated with a Git reference.
|
||||
*
|
||||
* @param {String} ref The ref to get the note from.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*/
|
||||
export async function gitGetNote(ref, execaOpts) {
|
||||
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout;
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,16 +39,16 @@ async function start() {
|
||||
minTimeout: 1000,
|
||||
factor: 2,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (_) {
|
||||
throw new Error(`Couldn't start npm-registry-docker after 2 min`);
|
||||
}
|
||||
|
||||
// Create user
|
||||
await got(`http://${SERVER_HOST}:${SERVER_PORT}/_users/org.couchdb.user:${NPM_USERNAME}`, {
|
||||
json: true,
|
||||
auth: `${COUCHDB_USER}:${COUCHDB_PASSWORD}`,
|
||||
username: COUCHDB_USER,
|
||||
password: COUCHDB_PASSWORD,
|
||||
method: 'PUT',
|
||||
body: {
|
||||
json: {
|
||||
_id: `org.couchdb.user:${NPM_USERNAME}`,
|
||||
name: NPM_USERNAME,
|
||||
roles: [],
|
||||
|
||||
+224
-94
@@ -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';
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
merge,
|
||||
mergeFf,
|
||||
rebase,
|
||||
gitAddNote,
|
||||
gitGetNote,
|
||||
} from './helpers/git-utils';
|
||||
|
||||
const requireNoCache = proxyquire.noPreserveCache();
|
||||
@@ -45,7 +47,8 @@ test('Plugins are called with expected values', async t => {
|
||||
// Add commits to the master branch
|
||||
let commits = await gitCommits(['First'], {cwd});
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('v1.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.0.0', {cwd});
|
||||
commits = (await gitCommits(['Second'], {cwd})).concat(commits);
|
||||
await gitCheckout('next', true, {cwd});
|
||||
await gitPush(repositoryUrl, 'next', {cwd});
|
||||
@@ -55,9 +58,9 @@ test('Plugins are called with expected values', async t => {
|
||||
const lastRelease = {
|
||||
version: '1.0.0',
|
||||
gitHead: commits[commits.length - 1].hash,
|
||||
gitTag: 'v1.0.0@next',
|
||||
gitTag: 'v1.0.0',
|
||||
name: 'v1.0.0',
|
||||
channel: 'next',
|
||||
channels: ['next'],
|
||||
};
|
||||
const nextRelease = {
|
||||
name: 'v1.1.0',
|
||||
@@ -65,7 +68,7 @@ test('Plugins are called with expected values', async t => {
|
||||
version: '1.1.0',
|
||||
gitHead: await getGitHead({cwd}),
|
||||
gitTag: 'v1.1.0',
|
||||
channel: undefined,
|
||||
channel: null,
|
||||
};
|
||||
const notes1 = 'Release notes 1';
|
||||
const notes2 = 'Release notes 2';
|
||||
@@ -94,18 +97,20 @@ test('Plugins are called with expected values', async t => {
|
||||
{
|
||||
channel: undefined,
|
||||
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}],
|
||||
range: '>=1.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
tags: [{channels: ['next'], gitTag: 'v1.0.0', version: '1.0.0'}],
|
||||
type: 'release',
|
||||
main: true,
|
||||
},
|
||||
{
|
||||
channel: 'next',
|
||||
name: 'next',
|
||||
range: '>=2.0.0',
|
||||
range: '>=1.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', version: '1.0.0'}],
|
||||
type: 'release',
|
||||
main: false,
|
||||
},
|
||||
];
|
||||
const branch = branches[0];
|
||||
@@ -121,14 +126,15 @@ test('Plugins are called with expected values', async t => {
|
||||
publish: [publish, pluginNoop],
|
||||
success,
|
||||
};
|
||||
const envCi = {branch: 'master', isCi: true, isPr: false};
|
||||
|
||||
const releases = [
|
||||
{
|
||||
...lastRelease,
|
||||
...omit(lastRelease, 'channels'),
|
||||
...release1,
|
||||
type: 'major',
|
||||
version: '1.0.0',
|
||||
channel: undefined,
|
||||
channel: null,
|
||||
gitTag: 'v1.0.0',
|
||||
notes: `${notes1}\n\n${notes2}\n\n${notes3}`,
|
||||
pluginName: '[Function: functionStub]',
|
||||
@@ -139,7 +145,7 @@ test('Plugins are called with expected values', async t => {
|
||||
|
||||
const semanticRelease = requireNoCache('..', {
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
'env-ci': () => envCi,
|
||||
});
|
||||
const result = await semanticRelease(options, {
|
||||
cwd,
|
||||
@@ -155,6 +161,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(verifyConditions1.args[0][1].branch, branch);
|
||||
t.deepEqual(verifyConditions1.args[0][1].branches, branches);
|
||||
t.deepEqual(verifyConditions1.args[0][1].logger, t.context.logger);
|
||||
t.deepEqual(verifyConditions1.args[0][1].envCi, envCi);
|
||||
t.is(verifyConditions2.callCount, 1);
|
||||
t.deepEqual(verifyConditions2.args[0][0], config);
|
||||
t.deepEqual(verifyConditions2.args[0][1].cwd, cwd);
|
||||
@@ -162,6 +169,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(verifyConditions2.args[0][1].branch, branch);
|
||||
t.deepEqual(verifyConditions2.args[0][1].branches, branches);
|
||||
t.deepEqual(verifyConditions2.args[0][1].logger, t.context.logger);
|
||||
t.deepEqual(verifyConditions2.args[0][1].envCi, envCi);
|
||||
|
||||
t.is(generateNotes1.callCount, 2);
|
||||
t.is(generateNotes2.callCount, 2);
|
||||
@@ -176,13 +184,14 @@ 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,
|
||||
channel: null,
|
||||
gitTag: 'v1.0.0',
|
||||
name: 'v1.0.0',
|
||||
});
|
||||
t.deepEqual(generateNotes2.args[0][1].envCi, envCi);
|
||||
|
||||
t.deepEqual(generateNotes2.args[0][0], config);
|
||||
t.deepEqual(generateNotes2.args[0][1].options, options);
|
||||
@@ -193,14 +202,15 @@ 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,
|
||||
channel: null,
|
||||
gitTag: 'v1.0.0',
|
||||
name: 'v1.0.0',
|
||||
notes: notes1,
|
||||
});
|
||||
t.deepEqual(generateNotes2.args[0][1].envCi, envCi);
|
||||
|
||||
t.deepEqual(generateNotes3.args[0][0], config);
|
||||
t.deepEqual(generateNotes3.args[0][1].options, options);
|
||||
@@ -211,18 +221,19 @@ 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,
|
||||
channel: null,
|
||||
gitTag: 'v1.0.0',
|
||||
name: 'v1.0.0',
|
||||
notes: `${notes1}\n\n${notes2}`,
|
||||
});
|
||||
t.deepEqual(generateNotes3.args[0][1].envCi, envCi);
|
||||
|
||||
branch.tags.push({
|
||||
version: '1.0.0',
|
||||
channel: undefined,
|
||||
channel: null,
|
||||
gitTag: 'v1.0.0',
|
||||
gitHead: commits[commits.length - 1].hash,
|
||||
});
|
||||
@@ -236,16 +247,17 @@ 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,
|
||||
channel: null,
|
||||
gitTag: 'v1.0.0',
|
||||
name: 'v1.0.0',
|
||||
notes: `${notes1}\n\n${notes2}\n\n${notes3}`,
|
||||
});
|
||||
t.deepEqual(addChannel.args[0][1].commits[0].hash, commits[1].hash);
|
||||
t.deepEqual(addChannel.args[0][1].commits[0].message, commits[1].message);
|
||||
t.deepEqual(addChannel.args[0][1].envCi, envCi);
|
||||
|
||||
t.is(analyzeCommits.callCount, 1);
|
||||
t.deepEqual(analyzeCommits.args[0][0], config);
|
||||
@@ -256,6 +268,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(analyzeCommits.args[0][1].lastRelease, lastRelease);
|
||||
t.deepEqual(analyzeCommits.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(analyzeCommits.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(analyzeCommits.args[0][1].envCi, envCi);
|
||||
|
||||
t.is(verifyRelease.callCount, 1);
|
||||
t.deepEqual(verifyRelease.args[0][0], config);
|
||||
@@ -267,6 +280,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(verifyRelease.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(verifyRelease.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(verifyRelease.args[0][1].nextRelease, nextRelease);
|
||||
t.deepEqual(verifyRelease.args[0][1].envCi, envCi);
|
||||
|
||||
t.deepEqual(generateNotes1.args[1][0], config);
|
||||
t.deepEqual(generateNotes1.args[1][1].options, options);
|
||||
@@ -277,6 +291,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(generateNotes1.args[1][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(generateNotes1.args[1][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(generateNotes1.args[1][1].nextRelease, nextRelease);
|
||||
t.deepEqual(generateNotes1.args[1][1].envCi, envCi);
|
||||
|
||||
t.deepEqual(generateNotes2.args[1][0], config);
|
||||
t.deepEqual(generateNotes2.args[1][1].options, options);
|
||||
@@ -287,6 +302,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(generateNotes2.args[1][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(generateNotes2.args[1][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(generateNotes2.args[1][1].nextRelease, {...nextRelease, notes: notes1});
|
||||
t.deepEqual(generateNotes2.args[1][1].envCi, envCi);
|
||||
|
||||
t.deepEqual(generateNotes3.args[1][0], config);
|
||||
t.deepEqual(generateNotes3.args[1][1].options, options);
|
||||
@@ -297,6 +313,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(generateNotes3.args[1][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(generateNotes3.args[1][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(generateNotes3.args[1][1].nextRelease, {...nextRelease, notes: `${notes1}\n\n${notes2}`});
|
||||
t.deepEqual(generateNotes3.args[1][1].envCi, envCi);
|
||||
|
||||
t.is(prepare.callCount, 1);
|
||||
t.deepEqual(prepare.args[0][0], config);
|
||||
@@ -308,6 +325,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(prepare.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(prepare.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(prepare.args[0][1].nextRelease, {...nextRelease, notes: `${notes1}\n\n${notes2}\n\n${notes3}`});
|
||||
t.deepEqual(prepare.args[0][1].envCi, envCi);
|
||||
|
||||
t.is(publish.callCount, 1);
|
||||
t.deepEqual(publish.args[0][0], config);
|
||||
@@ -319,6 +337,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(publish.args[0][1].commits[0].hash, commits[0].hash);
|
||||
t.deepEqual(publish.args[0][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(publish.args[0][1].nextRelease, {...nextRelease, notes: `${notes1}\n\n${notes2}\n\n${notes3}`});
|
||||
t.deepEqual(publish.args[0][1].envCi, envCi);
|
||||
|
||||
t.is(success.callCount, 2);
|
||||
t.deepEqual(success.args[0][0], config);
|
||||
@@ -330,15 +349,16 @@ 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,
|
||||
channel: null,
|
||||
gitTag: 'v1.0.0',
|
||||
name: 'v1.0.0',
|
||||
notes: `${notes1}\n\n${notes2}\n\n${notes3}`,
|
||||
});
|
||||
t.deepEqual(success.args[0][1].releases, [releases[0]]);
|
||||
t.deepEqual(success.args[0][1].envCi, envCi);
|
||||
|
||||
t.deepEqual(success.args[1][0], config);
|
||||
t.deepEqual(success.args[1][1].options, options);
|
||||
@@ -350,6 +370,7 @@ test('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(success.args[1][1].commits[0].message, commits[0].message);
|
||||
t.deepEqual(success.args[1][1].nextRelease, {...nextRelease, notes: `${notes1}\n\n${notes2}\n\n${notes3}`});
|
||||
t.deepEqual(success.args[1][1].releases, [releases[1], releases[2]]);
|
||||
t.deepEqual(success.args[1][1].envCi, envCi);
|
||||
|
||||
t.deepEqual(result, {
|
||||
lastRelease,
|
||||
@@ -433,7 +454,7 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c
|
||||
version: '2.0.0',
|
||||
gitHead: await getGitHead({cwd}),
|
||||
gitTag: 'v2.0.0',
|
||||
channel: undefined,
|
||||
channel: null,
|
||||
};
|
||||
const notes = 'Release notes';
|
||||
|
||||
@@ -492,15 +513,16 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c
|
||||
|
||||
test('Make a new release when a commit is forward-ported to an upper branch', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const commits = await gitCommits(['feat: initial release'], {cwd});
|
||||
await gitCommits(['feat: initial release'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@1.0.x', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, '1.0.x']}), 'v1.0.0', {cwd});
|
||||
await gitCheckout('1.0.x', true, {cwd});
|
||||
commits.push(...(await gitCommits(['fix: fix on maintenance version 1.0.x'], {cwd})));
|
||||
await gitTagVersion('v1.0.1@1.0.x', undefined, {cwd});
|
||||
await gitCommits(['fix: fix on maintenance version 1.0.x'], {cwd});
|
||||
await gitTagVersion('v1.0.1', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['1.0.x']}), 'v1.0.1', {cwd});
|
||||
await gitPush('origin', '1.0.x', {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
commits.push(...(await gitCommits(['feat: new feature on master'], {cwd})));
|
||||
await gitCommits(['feat: new feature on master'], {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await merge('1.0.x', {cwd});
|
||||
await gitPush('origin', 'master', {cwd});
|
||||
@@ -566,7 +588,8 @@ test('Publish a pre-release version', async t => {
|
||||
|
||||
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@beta');
|
||||
t.is(releases[0].gitTag, 'v1.1.0-beta.1');
|
||||
t.is(await gitGetNote('v1.1.0-beta.1', {cwd}), '{"channels":["beta"]}');
|
||||
|
||||
await gitCommits(['fix: a fix'], {cwd});
|
||||
({releases} = await semanticRelease(options, {
|
||||
@@ -578,19 +601,133 @@ test('Publish a pre-release version', async t => {
|
||||
|
||||
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@beta');
|
||||
t.is(releases[0].gitTag, 'v1.1.0-beta.2');
|
||||
t.is(await gitGetNote('v1.1.0-beta.2', {cwd}), '{"channels":["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});
|
||||
await gitCommits(['feat: initial release'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@beta', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, 'beta']}), 'v1.0.0', {cwd});
|
||||
await gitCheckout('beta', true, {cwd});
|
||||
commits.push(...(await gitCommits(['feat: breaking change/n/nBREAKING CHANGE: break something'], {cwd})));
|
||||
await gitTagVersion('v2.0.0-beta.1@beta', undefined, {cwd});
|
||||
commits.push(...(await gitCommits(['fix: a fix'], {cwd})));
|
||||
await gitTagVersion('v2.0.0-beta.2@beta', undefined, {cwd});
|
||||
await gitCommits(['feat: breaking change/n/nBREAKING CHANGE: break something'], {cwd});
|
||||
await gitTagVersion('v2.0.0-beta.1', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['beta']}), 'v2.0.0-beta.1', {cwd});
|
||||
await gitCommits(['fix: a fix'], {cwd});
|
||||
await gitTagVersion('v2.0.0-beta.2', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['beta']}), 'v2.0.0-beta.2', {cwd});
|
||||
await gitPush('origin', 'beta', {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
await merge('beta', {cwd});
|
||||
@@ -635,14 +772,18 @@ async function addChannelMacro(t, mergeFunction) {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const commits = await gitCommits(['feat: initial release'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@next', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
|
||||
await gitCheckout('next', true, {cwd});
|
||||
commits.push(...(await gitCommits(['feat: breaking change/n/nBREAKING CHANGE: break something'], {cwd})));
|
||||
await gitTagVersion('v2.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['next']}), 'v2.0.0', {cwd});
|
||||
|
||||
commits.push(...(await gitCommits(['fix: a fix'], {cwd})));
|
||||
await gitTagVersion('v2.0.1@next', undefined, {cwd});
|
||||
await gitTagVersion('v2.0.1', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['next']}), 'v2.0.1', {cwd});
|
||||
commits.push(...(await gitCommits(['feat: a feature'], {cwd})));
|
||||
await gitTagVersion('v2.1.0@next', undefined, {cwd});
|
||||
await gitTagVersion('v2.1.0', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['next']}), 'v2.1.0', {cwd});
|
||||
await gitPush('origin', 'next', {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
// Merge all commits but last one from next to master
|
||||
@@ -660,7 +801,14 @@ async function addChannelMacro(t, mergeFunction) {
|
||||
const publish = stub().resolves();
|
||||
const success = stub().resolves();
|
||||
|
||||
const config = {branches: [{name: 'master'}, {name: 'next'}], repositoryUrl, tagFormat: `v\${version}`};
|
||||
const config = {
|
||||
branches: [
|
||||
{name: 'master', channel: 'latest'},
|
||||
{name: 'next', channel: 'next'},
|
||||
],
|
||||
repositoryUrl,
|
||||
tagFormat: `v\${version}`,
|
||||
};
|
||||
const options = {
|
||||
...config,
|
||||
verifyConditions,
|
||||
@@ -671,19 +819,11 @@ 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 = {
|
||||
const nextRelease = {
|
||||
name: 'v2.0.1',
|
||||
type: 'patch',
|
||||
version: '2.0.1',
|
||||
channel: undefined,
|
||||
channel: 'latest',
|
||||
gitTag: 'v2.0.1',
|
||||
gitHead: commits[2].hash,
|
||||
};
|
||||
@@ -695,17 +835,13 @@ 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]'},
|
||||
{...nextRelease, ...release1, notes, pluginName: '[Function: functionStub]'},
|
||||
{...nextRelease, 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);
|
||||
t.is(await gitTagHead(nextRelease.gitTag, {cwd}), nextRelease.gitHead);
|
||||
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease.gitTag, {cwd}), nextRelease.gitHead);
|
||||
}
|
||||
|
||||
addChannelMacro.title = providedTitle => `Add version to a channel after a merge (${providedTitle})`;
|
||||
@@ -731,7 +867,7 @@ test('Call all "success" plugins even if one errors out', async t => {
|
||||
version: '2.0.0',
|
||||
gitHead: await getGitHead({cwd}),
|
||||
gitTag: 'v2.0.0',
|
||||
channel: undefined,
|
||||
channel: null,
|
||||
};
|
||||
const notes = 'Release notes';
|
||||
const verifyConditions1 = stub().resolves();
|
||||
@@ -866,9 +1002,11 @@ test('Dry-run skips addChannel, prepare, publish and success', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('v1.1.0@next', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.1.0', {cwd});
|
||||
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
await gitCheckout('next', true, {cwd});
|
||||
await gitPush('origin', 'next', {cwd});
|
||||
@@ -1148,8 +1286,10 @@ test('Accept "undefined" value returned by "generateNotes" and "false" by "publi
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('v1.1.0@next', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.1.0', {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
await gitCheckout('next', true, {cwd});
|
||||
await gitPush('origin', 'next', {cwd});
|
||||
@@ -1161,7 +1301,7 @@ test('Accept "undefined" value returned by "generateNotes" and "false" by "publi
|
||||
version: '1.2.0',
|
||||
gitHead: await getGitHead({cwd}),
|
||||
gitTag: 'v1.2.0',
|
||||
channel: undefined,
|
||||
channel: null,
|
||||
};
|
||||
const analyzeCommits = stub().resolves(nextRelease.type);
|
||||
const verifyRelease = stub().resolves();
|
||||
@@ -1234,16 +1374,16 @@ test('Returns false if triggered by a PR', async t => {
|
||||
|
||||
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current maintenance branch', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const commits = await gitCommits(['feat: initial commit'], {cwd});
|
||||
await gitCommits(['feat: initial commit'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@1.x', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.0.0', {cwd});
|
||||
await gitCheckout('1.x', true, {cwd});
|
||||
await gitPush('origin', '1.x', {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
commits.push(...(await gitCommits(['feat: new feature on master'], {cwd})));
|
||||
await gitCommits(['feat: new feature on master'], {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await gitCheckout('1.x', false, {cwd});
|
||||
commits.push(...(await gitCommits(['feat: feature on maintenance version 1.x'], {cwd})));
|
||||
await gitCommits(['feat: feature on maintenance version 1.x'], {cwd});
|
||||
await gitPush('origin', 'master', {cwd});
|
||||
|
||||
const verifyConditions = stub().resolves();
|
||||
@@ -1285,16 +1425,17 @@ test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the curren
|
||||
|
||||
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current release branch', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const commits = await gitCommits(['feat: initial commit'], {cwd});
|
||||
await gitCommits(['feat: initial commit'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitCheckout('next', true, {cwd});
|
||||
commits.push(...(await gitCommits(['feat: new feature on next'], {cwd})));
|
||||
await gitTagVersion('v1.1.0@next', undefined, {cwd});
|
||||
await gitCommits(['feat: new feature on next'], {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.1.0', {cwd});
|
||||
await gitPush('origin', 'next', {cwd});
|
||||
await gitCheckout('next-major', true, {cwd});
|
||||
await gitPush('origin', 'next-major', {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
commits.push(...(await gitCommits(['feat: new feature on master', 'fix: new fix on master'], {cwd})));
|
||||
await gitCommits(['feat: new feature on master', 'fix: new fix on master'], {cwd});
|
||||
await gitPush('origin', 'master', {cwd});
|
||||
|
||||
const verifyConditions = stub().resolves();
|
||||
@@ -1336,18 +1477,18 @@ test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the curren
|
||||
|
||||
test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a maintenance branch', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@1.1.x', undefined, {cwd});
|
||||
commits.push(...(await gitCommits(['Second'], {cwd})));
|
||||
await gitAddNote(JSON.stringify({channels: [null, '1.1.x']}), 'v1.0.0', {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.1.0@1.1.x', undefined, {cwd});
|
||||
await gitAddNote(JSON.stringify({channels: [null, '1.1.x']}), 'v1.1.0', {cwd});
|
||||
await gitCheckout('1.1.x', 'master', {cwd});
|
||||
await gitPush('origin', '1.1.x', {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
commits.push(...(await gitCommits(['Third'], {cwd})));
|
||||
await gitCommits(['Third'], {cwd});
|
||||
await gitTagVersion('v1.1.1', undefined, {cwd});
|
||||
commits.push(...(await gitCommits(['Fourth'], {cwd})));
|
||||
await gitCommits(['Fourth'], {cwd});
|
||||
await gitTagVersion('v1.2.0', undefined, {cwd});
|
||||
await gitPush('origin', 'master', {cwd});
|
||||
await gitCheckout('1.1.x', false, {cwd});
|
||||
@@ -1379,15 +1520,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 +1530,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);
|
||||
|
||||
+10
-10
@@ -16,6 +16,7 @@ import {
|
||||
gitPush,
|
||||
gitCheckout,
|
||||
merge,
|
||||
gitGetNote,
|
||||
} from './helpers/git-utils';
|
||||
import {npmView} from './helpers/npm-utils';
|
||||
import gitbox from './helpers/gitbox';
|
||||
@@ -220,7 +221,7 @@ test('Release patch, minor and major versions', async t => {
|
||||
createReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}@next`, name: `v${version}`},
|
||||
body: {tag_name: `v${version}`, name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
@@ -246,8 +247,9 @@ test('Release patch, minor and major versions', async t => {
|
||||
} = await npmView(packageName, testEnv));
|
||||
head = await gitHead({cwd});
|
||||
t.is(releasedVersion, version);
|
||||
t.is(await gitTagHead(`v${version}@next`, {cwd}), head);
|
||||
t.is(await gitRemoteTagHead(authUrl, `v${version}@next`, {cwd}), head);
|
||||
t.is(await gitGetNote(`v${version}`, {cwd}), '{"channels":["next"]}');
|
||||
t.is(await gitTagHead(`v${version}`, {cwd}), head);
|
||||
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), head);
|
||||
t.log(`+ released ${releasedVersion} on @next`);
|
||||
|
||||
await mockServer.verify(verifyMock);
|
||||
@@ -262,14 +264,14 @@ test('Release patch, minor and major versions', async t => {
|
||||
{body: {permissions: {push: true}}, method: 'GET'}
|
||||
);
|
||||
const getReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases/tags/v2.0.0@next`,
|
||||
`/repos/${owner}/${packageName}/releases/tags/v2.0.0`,
|
||||
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {id: releaseId}, method: 'GET'}
|
||||
);
|
||||
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'}
|
||||
@@ -292,11 +294,9 @@ test('Release patch, minor and major versions', async t => {
|
||||
'dist-tags': {latest: releasedVersion},
|
||||
} = await npmView(packageName, testEnv));
|
||||
t.is(releasedVersion, version);
|
||||
t.is(await gitTagHead(`v${version}`, {cwd}), await gitTagHead(`v${version}@next`, {cwd}));
|
||||
t.is(
|
||||
await gitRemoteTagHead(authUrl, `v${version}`, {cwd}),
|
||||
await gitRemoteTagHead(authUrl, `v${version}@next`, {cwd})
|
||||
);
|
||||
t.is(await gitGetNote(`v${version}`, {cwd}), '{"channels":["next",null]}');
|
||||
t.is(await gitTagHead(`v${version}`, {cwd}), await gitTagHead(`v${version}`, {cwd}));
|
||||
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), await gitRemoteTagHead(authUrl, `v${version}`, {cwd}));
|
||||
t.log(`+ added ${releasedVersion}`);
|
||||
|
||||
await mockServer.verify(verifyMock);
|
||||
|
||||
@@ -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
-4
@@ -14,6 +14,7 @@ import {
|
||||
getFirstVersion,
|
||||
getRange,
|
||||
makeTag,
|
||||
isSameChannel,
|
||||
} from '../lib/utils';
|
||||
|
||||
test('extractErrors', t => {
|
||||
@@ -173,8 +174,13 @@ test('getRange', t => {
|
||||
|
||||
test('makeTag', t => {
|
||||
t.is(makeTag(`v\${version}`, '1.0.0'), 'v1.0.0');
|
||||
t.is(makeTag(`v\${version}`, '1.0.0', false), 'v1.0.0');
|
||||
t.is(makeTag(`v\${version}`, '1.0.0', null), 'v1.0.0');
|
||||
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));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user