Compare commits

..
Author SHA1 Message Date
greenkeeper[bot]andPierre Vanduynslager ef1b8a0b91 chore(package): update ava to version 3.1.0 2020-01-27 13:52:59 -05:00
Pierre Vanduynslager bec57cd2ed chore: require Node.js >=10.18
BREAKING CHANGE: Require Node.js >= 10.18
2020-01-27 13:52:59 -05:00
Jozef CipaandPierre Vanduynslager c6b10766a7 fix: correct error when remote repository has no branches 2020-01-27 11:59:38 -05:00
Pierre Vanduynslager b54b20d412 fix: use --no-verify when testing the Git permissions 2020-01-22 13:08:59 -05:00
DanielHabenichtandGregor Martynus 88fe81915b docs: fix typo
just a little type fist -> first
2020-01-21 15:34:01 -08:00
Michaël De BoeyandGregor Martynus 559c152647 docs(README): update minimal required Node version / FAQ link (#1422) 2020-01-20 12:06:39 -08:00
Prokop SimekandGregor Martynus 31e78767d9 docs(travis): build on all branches by default
If you want to have more prereleases than default so you have to enable deploy on all branches. ref.: https://docs.travis-ci.com/user/deployment#examples-of-conditional-deployment
2020-01-20 08:40:16 -08:00
Daniel BasilioandGregor Martynus 8c0490d057 docs: replacing firefox plugin in the list (#1416) 2020-01-15 20:02:00 -08:00
Pierre Vanduynslager 6b5b02ea75 fix: fetch tags on repo cached by the CI 2020-01-15 13:43:44 -05:00
Pierre Vanduynslager 28b54800cd docs: correct plugin execution order 2020-01-15 11:34:26 -05:00
greenkeeper[bot]andPierre Vanduynslager 3739ab5f34 fix(package): update env-ci to version 5.0.0 2020-01-10 11:58:36 -05:00
greenkeeper[bot]andPierre Vanduynslager 11665b214d chore(package): update dependencies 2020-01-09 16:34:01 -05:00
Pierre Vanduynslager 0785a844fa fix: update plugin versions 2020-01-09 15:14:55 -05:00
Pierre Vanduynslager 152bf452e1 Merge remote-tracking branch 'origin/beta' 2020-01-09 15:08:45 -05:00
Pierre Vanduynslager 3ba8f2a929 Merge remote-tracking branch 'origin/master' into beta 2020-01-09 15:01:19 -05:00
Pierre Vanduynslager a8c747d592 feat: pass envi-ci values to plugins context 2019-12-20 19:36:02 -05:00
Pierre Vanduynslager fc70726d90 chore: add Mockserver generated file to gitignore 2019-12-20 19:36:02 -05:00
Pierre Vanduynslager fc7205d659 fix: correctly display command that errored out in logs 2019-12-15 17:47:46 -05:00
Pierre Vanduynslager 9772563a22 fix: look also for previous prerelease versions to determine the next one 2019-12-13 16:09:27 -05:00
Pierre Vanduynslager 61665be9ec fix: correct log when adding channel to tag 2019-12-10 00:23:38 -05:00
Pierre Vanduynslager a8747c4f86 fix: verify is branch is up to date by comparing remote and local HEAD 2019-12-10 00:23:38 -05:00
Pierre Vanduynslager 9a1af4de44 fix: remove unnecessary await 2019-12-10 00:23:38 -05:00
Pierre Vanduynslager 9ecc7a369c fix: increase next version on prerelease branch based on highest commit type 2019-12-09 22:33:29 -05:00
Pierre Vanduynslager c16fcc9d15 Merge branch 'master' into beta 2019-12-05 15:42:14 -05:00
Pierre Vanduynslager a373f8be14 ci: use Travis import to share config across organization 2019-12-05 15:39:31 -05:00
Pierre Vanduynslager 0716a45b7d feat: require Node.js >=10.13
BREAKING CHANGE: Require Node.js >= 10.13
2019-12-05 13:18:42 -05:00
Pierre Vanduynslager 916c2685c5 feat: allow to release any version on a branch if up to date with next branch 2019-12-04 18:00:16 -05:00
Pierre Vanduynslager 534c0dbc89 Merge branch 'master' into beta 2019-12-03 20:29:06 -05:00
Pierre Vanduynslager ec54c0be8a test: fix calls to fetch in tests 2019-12-03 20:20:04 -05:00
Pierre Vanduynslager cbef9d18da fix: modify fetch function to handle CircleCI specifics 2019-12-03 18:07:12 -05:00
Pierre Vanduynslager ecc4e663f5 docs: clarify GitLab CI/CD protected variable usage 2019-12-03 11:49:56 -05:00
Pierre Vanduynslager b2c1b2c670 feat: use Git notes to store the channels on which a version has been released
BREAKING CHANGE: this feature change the way semantic-release keep track of the channels on which a version has been released.
It now use a JSON object stored in a [Git note](https://git-scm.com/docs/git-notes) instead of Git tags formatted as v{version}@{channel}.

The tags formatted as v{version}@{channel} will now be ignored. If you have made releases with v16.0.0 on branches other than the default one you will have to update your repository.

The changes to make consist in:
- Finding all the versions that have been released on a branch other than the default one by searching for all tags formatted v{version}@{channel}
- For each of those version:
  - Create a tag without the {@channel} if none doesn't already exists
  - Add a Git note to the tag without the {@channel} containing the channels on which the version was released formatted as `{"channels":["channel1","channel2"]}` and using `null` for the default channel (for example.`{"channels":[null,"channel1","channel2"]}`)
  - Push the tags and notes
  - Update the GitHub releases that refer to a tag formatted as v{version}@{channel} to use the tag without it
  - Delete the tags formatted as v{version}@{channel}
2019-12-02 23:38:40 -05:00
Gregor MartynusandPierre Vanduynslager 29d6b5d6fa build: remove .github/airtable-crm.yml
I don't think we use that
2019-12-02 13:27:31 -05:00
Pierre Vanduynslager 2caafbaa2b fix: add a flag indicate which branch is the main one 2019-11-28 13:59:06 -05:00
Pierre Vanduynslager e722354f4f style: merge both filter functions in get-last-release 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 97748c5e25 fix: do not create tags in dry-run mode for released to add to a channel 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager aec96c791f fix: correctly determine release to add to a channel
- Add only the most recent release to a channel (rather than adding all the one not added yet)
- Avoid attempting to ad the version twice in case that version is already present in multiple upper branches
2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 5744c5ecd2 fix: add channel to publish success log 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager b2b7b57fbd style: prettier formatting 2019-11-20 20:25:36 -05:00
Sean DawsonandPierre Vanduynslager 9575198266 docs: add leiningen-semantic-release to list of community plugins 2019-11-18 11:29:10 -05:00
EdandPierre Vanduynslager 09754ac001 docs(shareable-configurations-list): add semantic-release-npm-github-publish 2019-11-18 11:27:53 -05:00
greenkeeper[bot]andPierre Vanduynslager 2c13136ff4 fix(package): update yargs to version 15.0.1 2019-11-17 18:13:39 -05:00
greenkeeper[bot]andPierre Vanduynslager ffff100f47 fix(package): update cosmiconfig to version 6.0.0 2019-11-02 15:32:18 -04:00
Pierre Vanduynslager 7a939a8970 fix: use authenticated URL to check if local branch is up to date 2019-11-01 20:44:31 -04:00
63 changed files with 1627 additions and 1137 deletions
-1
View File
@@ -1 +0,0 @@
base: app0ZOxG0FnHmOiuU
+7 -32
View File
@@ -1,36 +1,11 @@
language: node_js
version: ~> 1.0
services:
- docker
node_js:
- 12
- 10
- 8.16
# Trigger a push build on release and greenkeeper branches + PRs build on every branches
# Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147)
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
+2
View File
@@ -0,0 +1,2 @@
after_success:
- npm run codecov
+3
View File
@@ -0,0 +1,3 @@
branches:
only:
- /^greenkeeper.*$/
+3
View File
@@ -0,0 +1,3 @@
node_js:
- 12
- 10.18
+11
View File
@@ -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
+15
View File
@@ -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
View File
@@ -101,7 +101,7 @@ In order to use **semantic-release** you need:
- To host your code in a [Git repository](https://git-scm.com)
- Use a Continuous Integration service that allows you to [securely set up credentials](docs/usage/ci-configuration.md#authentication)
- Git CLI version [2.7.1 or higher](docs/support/FAQ.md#why-does-semantic-release-require-git-version--271) installed in your Continuous Integration environment
- [Node.js](https://nodejs.org) version [8.16.0 or higher](docs/support/FAQ.md#why-does-semantic-release-require-node-version--816) installed in your Continuous Integration environment
- [Node.js](https://nodejs.org) version [10.18 or higher](docs/support/FAQ.md#why-does-semantic-release-require-node-version--1018) installed in your Continuous Integration environment
## Documentation
+1 -1
View File
@@ -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;
+8 -4
View File
@@ -65,10 +65,10 @@
- `verifyConditions`: Verify the presence of the authentication (set via environment variables)
- `prepare`: Write the correct version to the `manifest.json` and creates a zip file of the whole dist folder
- `publish`: Uploads the generated zip file to the webstore, and publish the item
- [semantic-release-firefox](https://github.com/felixfbecker/semantic-release-firefox)
- `verifyConditions`: Verify the presence of the authentication (set via environment variables)
- `prepare`: Write the correct version to the `manifest.json`, creates a `xpi` file of the dist folder and a zip of the sources
- `publish`: Submit the generated archives to the webstore for review, and publish the item including release notes
- [semantic-release-firefox-add-on](https://github.com/tophat/semantic-release-firefox-add-on)
- `verifyConditions`: Verify that all required options are present and authentication is set via environment variables
- `prepare`: Write the correct version to the `manifest.json`
- `publish`: Creates an unsigned `.xpi` file, and submits it to the Mozilla Add On store for signing. Once the package is signed, downloads the signed `.xpi` to a local directory
- [semantic-release-gerrit](https://github.com/pascalMN/semantic-release-gerrit)
- `generateNotes`: Generate release notes with Gerrit reviews URL
- [semantic-release-expo](https://github.com/bycedric/semantic-release-expo)
@@ -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).
+1 -1
View File
@@ -10,7 +10,7 @@ In this example an [`NPM_TOKEN`](https://docs.npmjs.com/creating-and-viewing-aut
[GitHub Actions](https://github.com/features/actions) support [Workflows](https://help.github.com/en/articles/configuring-workflows), allowing to run tests on multiple Node versions and publish a release only when all test pass.
**Note**: The publish pipeline must run on [Node version >= 8.16](../support/FAQ.md#why-does-semantic-release-require-node-version--816).
**Note**: The publish pipeline must run on [Node version >= 10.18](../support/FAQ.md#why-does-semantic-release-require-node-version--1018).
### `.github/workflows/release.yml` configuration for Node projects
+4 -2
View File
@@ -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.16 version](../support/FAQ.md#why-does-semantic-release-require-node-version--816).
**Note**: The publish pipeline must run a [Node >= 10.18 version](../support/FAQ.md#why-does-semantic-release-require-node-version--1018).
### `.gitlab-ci.yml` configuration for Node projects
+1 -1
View File
@@ -169,7 +169,7 @@ The Git history of the repository is now:
We can now start to work on a new future major release, version `4.0.0`, on the `@beta` distribution channel.
To do so we fist need to update the `beta` branch with all the changes from `master` (the commits `fix: a fix`). As `beta` and `master` branches have diverged, this merge might require to resolve conflicts.
To do so we first need to update the `beta` branch with all the changes from `master` (the commits `fix: a fix`). As `beta` and `master` branches have diverged, this merge might require to resolve conflicts.
We can now commit our new feature on `beta`. When pushing that commit, **semantic-release** will publish the pre-release version `3.1.0-beta.1` on the dist-tag `@beta`. That allow us to run integration tests by installing our module with `npm install example-module@beta`. Other users installing with `npm install example-module` will still receive the version `3.0.0`.
+2
View File
@@ -92,4 +92,6 @@ jobs:
# Use nvm to install and use the Node LTS version (nvm is installed on all Travis images)
- nvm install lts/*
- npx semantic-release
on:
all_branches: true
```
+5 -5
View File
@@ -38,7 +38,7 @@ Yes with the [dry-run options](../usage/configuration.md#dryrun) which prints to
## Can I use semantic-release with Yarn?
If you are using a [local](../usage/installation.md#local-installation) **semantic-release** installation and run multiple CI jobs with different versions, the `yarn install` command will fail on jobs running with Node < 8 as **semantic-release** requires [Node >= 8.16](#why-does-semantic-release-require-node-version--816) and specifies it in its `package.json`s [`engines`](https://docs.npmjs.com/files/package.json#engines) key.
If you are using a [local](../usage/installation.md#local-installation) **semantic-release** installation and run multiple CI jobs with different versions, the `yarn install` command will fail on jobs running with Node < 8 as **semantic-release** requires [Node >= 10.18](#why-does-semantic-release-require-node-version--1018) and specifies it in its `package.json`s [`engines`](https://docs.npmjs.com/files/package.json#engines) key.
The recommended solution is to use the [Yarn](https://yarnpkg.com) [--ignore-engines](https://yarnpkg.com/en/docs/cli/install#toc-yarn-install-ignore-engines) option to install the project dependencies on the CI environment, so Yarn will ignore the **semantic-release**'s `engines` key:
@@ -48,7 +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.16.
Alternatively you can use a [global](../usage/installation.md#global-installation) **semantic-release** installation and make sure to install and run the `semantic-release` command only in a CI jobs running with Node >= 10.18.
If your CI environment provides [nvm](https://github.com/creationix/nvm) you can switch to Node 8 before installing and running the `semantic-release` command:
@@ -73,7 +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.16](#why-does-semantic-release-require-node-version--816) to execute the `semantic-release` command
- Make sure your CI job executing the `semantic-release` command has access to [Node >= 10.18](#why-does-semantic-release-require-node-version--1018) to execute the `semantic-release` command
See the [CI configuration recipes](../recipes/README.md#ci-configurations) for more details on specific CI environments.
@@ -232,9 +232,9 @@ See [“Introduction to SemVer” - Irina Gebauer](https://blog.greenkeeper.io/i
In addition the [verify conditions step](../../README.md#release-steps) verifies that all necessary conditions for proceeding with a release are met, and a new release will be performed [only if all your tests pass](../usage/ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded).
## Why does semantic-release require Node version >= 8.16?
## Why does semantic-release require Node version >= 10.18?
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which **requires Node version 8.16 or higher**.
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which **requires Node version 10.18 or higher**.
See [Node version requirement](./node-version.md#node-version-requirement) for more details and solutions.
+3 -3
View File
@@ -1,6 +1,6 @@
# Node version requirement
**semantic-release** is written using the latest [ECMAScript 2017](https://www.ecma-international.org/publications/standards/Ecma-262.htm) features, without transpilation which requires **requires Node version 8.16 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.16
### Run at least one CI job with Node >= 10.18
The recommended approach is to run the `semantic-release` command from a CI job running on Node 8.16 or higher. This can either be a job used by your project to test on Node >= 8.16 or a dedicated job for the release steps.
The recommended approach is to run the `semantic-release` command from a CI job running on Node 10.18 or higher. This can either be a job used by your project to test on Node >= 10.18 or a dedicated job for the release steps.
See [CI configuration](../usage/ci-configuration.md) and [CI configuration recipes](../recipes/README.md#ci-configurations) for more details.
+1 -1
View File
@@ -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
+9 -12
View File
@@ -100,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).
@@ -112,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
+53 -33
View File
@@ -3,7 +3,6 @@ const marked = require('marked');
const TerminalRenderer = require('marked-terminal');
const envCi = require('env-ci');
const hookStd = require('hook-std');
const pEachSeries = require('p-each-series');
const semver = require('semver');
const AggregateError = require('aggregate-error');
const pkg = require('./package.json');
@@ -13,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, getTagHead} = 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) {
@@ -92,41 +91,52 @@ async function run(context, plugins) {
await plugins.verifyConditions(context);
const releasesToAdd = getReleasesToAdd(context);
const errors = [];
context.releases = [];
const releaseToAdd = getReleaseToAdd(context);
if (releaseToAdd) {
const {lastRelease, currentRelease, nextRelease} = releaseToAdd;
await pEachSeries(releasesToAdd, async ({lastRelease, currentRelease, nextRelease}) => {
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});
}
@@ -143,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) {
@@ -153,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', {
@@ -176,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}`);
}
@@ -185,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}:`);
@@ -227,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 {
+12 -6
View File
@@ -2,22 +2,28 @@ const {template, escapeRegExp} = require('lodash');
const semver = require('semver');
const pReduce = require('p-reduce');
const debug = require('debug')('semantic-release:get-tags');
const {getTags} = 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 tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.+)')}`;
return pReduce(
branches,
async (branches, branch) => {
const branchTags = (await getTags(branch.name, {cwd, env})).reduce((tags, tag) => {
const [, version, channel] = tag.match(tagRegexp) || [];
return version && semver.valid(semver.clean(version)) ? [...tags, {gitTag: tag, version, channel}] : tags;
}, []);
const branchTags = await pReduce(
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;
},
[]
);
debug('found tags for branch %s: %o', branch.name, branchTags);
return [...branches, {...branch, tags: branchTags}];
+6 -4
View File
@@ -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
View File
@@ -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,
};
});
}
+3
View File
@@ -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,
};
+1 -1
View File
@@ -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');
+2 -1
View File
@@ -47,7 +47,8 @@ module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
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
);
+5 -4
View File
@@ -27,16 +27,17 @@ const {makeTag, isSameChannel} = require('./utils');
* @return {LastRelease} The last tagged release or empty object if none is found.
*/
module.exports = ({branch, options: {tagFormat}}, {before} = {}) => {
const [{version, gitTag, channel} = {}] = branch.tags
const [{version, gitTag, channels} = {}] = branch.tags
.filter(
tag =>
(branch.type === 'prerelease' && isSameChannel(branch.channel, tag.channel)) || !semver.prerelease(tag.version)
((branch.type === 'prerelease' && tag.channels.some(channel => isSameChannel(branch.channel, channel))) ||
!semver.prerelease(tag.version)) &&
(isUndefined(before) || semver.lt(tag.version, before))
)
.filter(tag => isUndefined(before) || semver.lt(tag.version, before))
.sort((a, b) => semver.rcompare(a.version, b.version));
if (gitTag) {
return {version, gitTag, channel, gitHead: gitTag, name: makeTag(tagFormat, version)};
return {version, gitTag, channels, gitHead: gitTag, name: makeTag(tagFormat, version)};
}
return {};
+20 -7
View File
@@ -1,17 +1,30 @@
const semver = require('semver');
const {FIRST_RELEASE, FIRSTPRERELEASE} = require('./definitions/constants');
const {isSameChannel} = require('./utils');
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) && isSameChannel(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;
+60
View File
@@ -0,0 +1,60 @@
const {uniqBy, intersection} = require('lodash');
const semver = require('semver');
const semverDiff = require('semver-diff');
const getLastRelease = require('./get-last-release');
const {makeTag, getLowerBound} = require('./utils');
/**
* Find releases that have been merged from from a higher branch but not added on the channel of the current branch.
*
* @param {Object} context semantic-release context.
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
const {
branch,
branches,
options: {tagFormat},
} = context;
const higherChannels = branches
// Consider only releases of higher branches
.slice(branches.findIndex(({name}) => name === branch.name) + 1)
// Exclude prerelease branches
.filter(({type}) => type !== 'prerelease')
.map(({channel}) => channel || 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,
},
};
}
};
-71
View File
@@ -1,71 +0,0 @@
const {uniq} = require('lodash');
const semver = require('semver');
const semverDiff = require('semver-diff');
const getLastRelease = require('./get-last-release');
const {makeTag, getLowerBound} = require('./utils');
/**
* Find releases that have been merged from from a higher branch but not added on the channel of the current branch.
*
* @param {Object} context semantic-release context.
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
const {
branch,
branches,
options: {tagFormat},
} = context;
return (
branches
// Consider only releases of higher branches
.slice(branches.findIndex(({name}) => name === branch.name) + 1)
// Exclude prerelease branches
.filter(({type}) => type !== 'prerelease')
// Find higher branch releases merged to building branch but not released on associated channel
.reduce(
(releases, higherBranch) => [
...releases,
// For all unique release version of the higher branch merged on current branch, excluding lower than start range version for maintenance branches
...uniq(
branch.tags.filter(
({channel, version}) =>
channel === higherBranch.channel &&
channel !== branch.channel &&
(branch.type !== 'maintenance' || semver.gte(version, getLowerBound(branch.mergeRange)))
)
)
// Find ones that are not released on the building branch channel
.filter(tag =>
branch.tags.every(
({version, channel}) =>
version !== tag.version || channel === higherBranch.channel || channel !== branch.channel
)
)
// Sort in ascending order to add the most recent release last
.sort((a, b) => semver.compare(a.version, b.version))
// Construct the last and next release to add to the building branch channel
.map(({version, 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: gitTag},
nextRelease: {
type,
version,
channel: branch.channel,
gitTag: makeTag(tagFormat, version, branch.channel),
name,
gitHead: gitTag,
},
};
}),
],
[]
)
);
};
+90 -39
View File
@@ -2,6 +2,7 @@ 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}});
@@ -64,35 +65,8 @@ 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])
.filter(Boolean);
}
/**
* 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 {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, 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) {
return false;
}
debug(error);
throw error;
}
.filter(Boolean)
.map(branch => branch.match(/^.+refs\/heads\/(?<branch>.+)$/)[1]);
}
/**
@@ -112,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(
@@ -129,7 +110,9 @@ 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
);
@@ -139,13 +122,36 @@ async function fetch(repositoryUrl, branch, execaOpts) {
[
'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.
*
@@ -198,7 +204,7 @@ async function isGitRepo(execaOpts) {
*/
async function verifyAuth(repositoryUrl, branch, execaOpts) {
try {
await execa('git', ['push', '--dry-run', repositoryUrl, `HEAD:${branch}`], execaOpts);
await execa('git', ['push', '--dry-run', '--no-verify', repositoryUrl, `HEAD:${branch}`], execaOpts);
} catch (error) {
debug(error);
throw error;
@@ -230,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.
*
@@ -272,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, 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,
};
+6 -4
View File
@@ -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,8 @@ 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) {
+15 -17
View File
@@ -7,9 +7,7 @@
"files": [
"test/**/*.test.js"
],
"helpers": [
"test/helpers/**/*"
]
"timeout": "2m"
},
"bin": {
"semantic-release": "bin/semantic-release.js"
@@ -22,16 +20,16 @@
"Pierre Vanduynslager (https://twitter.com/@pvdlg_)"
],
"dependencies": {
"@semantic-release/commit-analyzer": "^7.0.0-beta",
"@semantic-release/commit-analyzer": "^7.0.0",
"@semantic-release/error": "^2.2.0",
"@semantic-release/github": "^5.6.0-beta",
"@semantic-release/npm": "^6.0.0-beta",
"@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.2.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 +37,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": "^4.0.2",
"p-each-series": "^2.1.0",
"p-reduce": "^2.0.0",
"read-pkg-up": "^7.0.0",
"resolve-from": "^5.0.0",
"semver": "^6.0.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",
"ava": "^3.1.0",
"clear-module": "^4.0.0",
"codecov": "^3.0.0",
"delay": "^4.0.0",
"dockerode": "^3.0.0",
"file-url": "^3.0.0",
"fs-extra": "^8.0.0",
"got": "^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.25.0"
},
"engines": {
"node": ">=8.16"
"node": ">=10.18"
},
"files": [
"bin",
+97 -43
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {union} from 'lodash';
import semver from 'semver';
import proxyquire from 'proxyquire';
const test = require('ava');
const {union} = require('lodash');
const semver = require('semver');
const proxyquire = require('proxyquire');
const getBranch = (branches, branch) => branches.find(({name}) => name === branch);
const release = (branches, name, version) => getBranch(branches, name).tags.push({version});
@@ -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,46 +124,66 @@ 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,
'>=1.0.1 <1.0.2',
'Cannot release on 1.0.x before 1.0.x version from master are merged'
'Cannot release on 1.0.x before 1.0.x version = require(master are merged'
);
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');
@@ -177,7 +231,7 @@ test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
];
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');
@@ -192,7 +246,7 @@ test('Throw a SemanticReleaseError for each invalid branch name', async t => {
];
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');
+3 -3
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import expand from '../../lib/branches/expand';
import {gitRepo, gitCommits, gitCheckout, gitPush} from '../helpers/git-utils';
const test = require('ava');
const expand = require('../../lib/branches/expand');
const {gitRepo, gitCommits, gitCheckout, gitPush} = require('../helpers/git-utils');
test('Expand branches defined with globs', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
+31 -58
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import getTags from '../../lib/branches/get-tags';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout} from '../helpers/git-utils';
const test = require('ava');
const getTags = require('../../lib/branches/get-tags');
const {gitRepo, gitCommits, gitTagVersion, gitCheckout, gitAddNote} = require('../helpers/git-utils');
test('Get the valid tags', async t => {
const {cwd} = await gitRepo();
@@ -20,30 +20,31 @@ test('Get the valid tags', async t => {
{
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channel: undefined},
{gitTag: 'v2.0.0', version: '2.0.0', channel: undefined},
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channel: undefined},
{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]},
],
},
]);
});
test('Get the valid tags from multiple branches', async t => {
test('Get the valid tags = require(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},
{gitTag: 'v1.0.0@1.x', version: '1.0.0', channel: '1.x'},
{gitTag: 'v1.1.0', version: '1.1.0', channel: undefined},
{gitTag: 'v1.1.0@1.x', version: '1.1.0', channel: '1.x'},
{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},
{gitTag: 'v2.0.0@next', version: '2.0.0', channel: 'next'},
],
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'}],
},
]);
});
test('Match the tag name from the begining of the string and the channel from the last "@"', async t => {
const {cwd} = await gitRepo();
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},
{gitTag: 'prefix@v1.0.0@next', version: '1.0.0', channel: 'next'},
{gitTag: 'prefix@v2.0.0', version: '2.0.0', channel: undefined},
{gitTag: 'prefix@v2.0.0@next', version: '2.0.0', channel: 'next'},
],
tags: [...result[1].tags, {gitTag: 'v3.0.0', version: '3.0.0', channels: ['next']}],
},
]);
});
@@ -120,11 +91,13 @@ test('Return branches with and empty tags array if no valid tag is found in hist
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'}]);
@@ -141,19 +114,19 @@ test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
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}]},
{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}]},
{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}],
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channels: [null]}],
},
]);
@@ -161,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}],
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channels: [null]}],
},
]);
@@ -169,12 +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}],
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}]},
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channels: [null]}]},
]);
});
+98 -42
View File
@@ -1,5 +1,5 @@
import test from 'ava';
import normalize from '../../lib/branches/normalize';
const test = require('ava');
const normalize = require('../../lib/branches/normalize');
const toTags = versions => versions.map(version => ({version}));
@@ -155,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,
},
]
);
@@ -178,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,
},
]
);
@@ -200,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,
},
]
);
});
@@ -225,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,
},
]
);
@@ -247,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'])},
@@ -286,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,
},
]
);
+15 -17
View File
@@ -1,10 +1,8 @@
import test from 'ava';
import {escapeRegExp} from 'lodash';
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import {SECRET_REPLACEMENT} from '../lib/definitions/constants';
const requireNoCache = proxyquire.noPreserveCache();
const test = require('ava');
const {escapeRegExp} = require('lodash');
const proxyquire = require('proxyquire').noPreserveCache();
const {stub} = require('sinon');
const {SECRET_REPLACEMENT} = require('../lib/definitions/constants');
test.beforeEach(t => {
t.context.logs = '';
@@ -65,7 +63,7 @@ test.serial('Pass options to semantic-release API', async t => {
'--debug',
'-d',
];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -107,7 +105,7 @@ test.serial('Pass options to semantic-release API with alias arguments', async t
'config2',
'--dry-run',
];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -124,7 +122,7 @@ test.serial('Pass options to semantic-release API with alias arguments', async t
test.serial('Pass unknown options to semantic-release API', async t => {
const run = stub().resolves(true);
const argv = ['', '', '--bool', '--first-option', 'value1', '--second-option', 'value2', '--second-option', 'value3'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -138,7 +136,7 @@ test.serial('Pass unknown options to semantic-release API', async t => {
test.serial('Pass empty Array to semantic-release API for list option set to "false"', async t => {
const run = stub().resolves(true);
const argv = ['', '', '--publish', 'false'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -150,7 +148,7 @@ test.serial('Pass empty Array to semantic-release API for list option set to "fa
test.serial('Do not set properties in option for which arg is not in command line', async t => {
const run = stub().resolves(true);
const argv = ['', '', '-b', 'master'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
await cli();
@@ -167,7 +165,7 @@ test.serial('Do not set properties in option for which arg is not in command lin
test.serial('Display help', async t => {
const run = stub().resolves(true);
const argv = ['', '', '--help'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -178,7 +176,7 @@ test.serial('Display help', async t => {
test.serial('Return error exitCode and prints help if called with a command', async t => {
const run = stub().resolves(true);
const argv = ['', '', 'pre'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -190,7 +188,7 @@ test.serial('Return error exitCode and prints help if called with a command', as
test.serial('Return error exitCode if multiple plugin are set for single plugin', async t => {
const run = stub().resolves(true);
const argv = ['', '', '--analyze-commits', 'analyze1', 'analyze2'];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -202,7 +200,7 @@ test.serial('Return error exitCode if multiple plugin are set for single plugin'
test.serial('Return error exitCode if semantic-release throw error', async t => {
const run = stub().rejects(new Error('semantic-release error'));
const argv = ['', ''];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv}});
const exitCode = await cli();
@@ -214,7 +212,7 @@ test.serial('Hide sensitive environment variable values from the logs', async t
const env = {MY_TOKEN: 'secret token'};
const run = stub().rejects(new Error(`Throw error: Exposing token ${env.MY_TOKEN}`));
const argv = ['', ''];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv, env: {...process.env, ...env}}});
const cli = proxyquire('../cli', {'.': run, process: {...process, argv, env: {...process.env, ...env}}});
const exitCode = await cli();
+2 -2
View File
@@ -1,5 +1,5 @@
import test from 'ava';
import {maintenance, prerelease, release} from '../../lib/definitions/branches';
const test = require('ava');
const {maintenance, prerelease, release} = require('../../lib/definitions/branches');
test('A "maintenance" branch is identified by having a "range" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', t => {
t.true(maintenance.filter({name: '1.x.x'}));
+3 -3
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import plugins from '../../lib/definitions/plugins';
import {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} from '../../lib/definitions/constants';
const test = require('ava');
const plugins = require('../../lib/definitions/plugins');
const {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} = require('../../lib/definitions/constants');
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', t => {
t.false(plugins.analyzeCommits.outputValidator('invalid'));
+4 -4
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {stub} from 'sinon';
import getCommits from '../lib/get-commits';
import {gitRepo, gitCommits, gitDetachedHead} from './helpers/git-utils';
const test = require('ava');
const {stub} = require('sinon');
const getCommits = require('../lib/get-commits');
const {gitRepo, gitCommits, gitDetachedHead} = require('./helpers/git-utils');
test.beforeEach(t => {
// Stub the logger functions
+52 -52
View File
@@ -1,12 +1,12 @@
import path from 'path';
import {format} from 'util';
import test from 'ava';
import {writeFile, outputJson} from 'fs-extra';
import {omit} from 'lodash';
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import yaml from 'js-yaml';
import {gitRepo, gitTagVersion, gitCommits, gitShallowClone, gitAddConfig} from './helpers/git-utils';
const path = require('path');
const {format} = require('util');
const test = require('ava');
const {writeFile, outputJson} = require('fs-extra');
const {omit} = require('lodash');
const proxyquire = require('proxyquire');
const {stub} = require('sinon');
const yaml = require('js-yaml');
const {gitRepo, gitTagVersion, gitCommits, gitShallowClone, gitAddConfig} = require('./helpers/git-utils');
const DEFAULT_PLUGINS = [
'@semantic-release/commit-analyzer',
@@ -20,7 +20,7 @@ test.beforeEach(t => {
t.context.getConfig = proxyquire('../lib/get-config', {'./plugins': t.context.plugins});
});
test('Default values, reading repositoryUrl from package.json', async t => {
test('Default values, reading repositoryUrl = require(package.json', async t => {
const pkg = {repository: 'https://host.null/owner/package.git'};
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo(true);
@@ -47,7 +47,7 @@ test('Default values, reading repositoryUrl from package.json', async t => {
t.is(result.tagFormat, `v\${version}`);
});
test('Default values, reading repositoryUrl from repo if not set in package.json', async t => {
test('Default values, reading repositoryUrl = require(repo if not set in package.json', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo(true);
// Add remote.origin.url config
@@ -68,7 +68,7 @@ test('Default values, reading repositoryUrl from repo if not set in package.json
t.is(result.tagFormat, `v\${version}`);
});
test('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async t => {
test('Default values, reading repositoryUrl (http url) = require(package.json if not set in repo', async t => {
const pkg = {repository: 'https://host.null/owner/module.git'};
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
@@ -102,7 +102,7 @@ test('Convert "ci" option to "noCi"', async t => {
t.is(result.noCi, true);
});
test('Read options from package.json', async t => {
test('Read options = require(package.json', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -119,13 +119,13 @@ test('Read options from package.json', async t => {
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options, branches: ['test_branch']};
// Verify the options contains the plugin config from package.json
// Verify the options contains the plugin config = require(package.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from package.json
// Verify the plugins module is called with the plugin options = require(package.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from .releaserc.yml', async t => {
test('Read options = require(.releaserc.yml', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -141,13 +141,13 @@ test('Read options from .releaserc.yml', async t => {
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options, branches: ['test_branch']};
// Verify the options contains the plugin config from package.json
// Verify the options contains the plugin config = require(package.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from package.json
// Verify the plugins module is called with the plugin options = require(package.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from .releaserc.json', async t => {
test('Read options = require(.releaserc.json', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -163,13 +163,13 @@ test('Read options from .releaserc.json', async t => {
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options, branches: ['test_branch']};
// Verify the options contains the plugin config from package.json
// Verify the options contains the plugin config = require(package.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from package.json
// Verify the plugins module is called with the plugin options = require(package.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from .releaserc.js', async t => {
test('Read options = require(.releaserc.js', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -185,13 +185,13 @@ test('Read options from .releaserc.js', async t => {
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options, branches: ['test_branch']};
// Verify the options contains the plugin config from package.json
// Verify the options contains the plugin config = require(package.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from package.json
// Verify the plugins module is called with the plugin options = require(package.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read options from release.config.js', async t => {
test('Read options = require(release.config.js', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const options = {
@@ -207,9 +207,9 @@ test('Read options from release.config.js', async t => {
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options, branches: ['test_branch']};
// Verify the options contains the plugin config from package.json
// Verify the options contains the plugin config = require(package.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from package.json
// Verify the plugins module is called with the plugin options = require(package.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
@@ -237,13 +237,13 @@ test('Prioritise CLI/API parameters over file configuration and git repo', async
const result = await t.context.getConfig({cwd}, options);
const expected = {...options, branches: ['branch_cli']};
// Verify the options contains the plugin config from CLI/API
// Verify the options contains the plugin config = require(CLI/API
t.deepEqual(result.options, expected);
// Verify the plugins module is called with the plugin options from CLI/API
// Verify the plugins module is called with the plugin options = require(CLI/API
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
test('Read configuration from file path in "extends"', async t => {
test('Read configuration = require(file path in "extends"', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: './shareable.json'};
@@ -262,9 +262,9 @@ test('Read configuration from file path in "extends"', async t => {
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options, branches: ['test_branch']};
// Verify the options contains the plugin config from shareable.json
// Verify the options contains the plugin config = require(shareable.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from shareable.json
// Verify the plugins module is called with the plugin options = require(shareable.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
t.deepEqual(t.context.plugins.args[0][1], {
analyzeCommits: './shareable.json',
@@ -274,7 +274,7 @@ test('Read configuration from file path in "extends"', async t => {
});
});
test('Read configuration from module path in "extends"', async t => {
test('Read configuration = require(module path in "extends"', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: 'shareable'};
@@ -293,9 +293,9 @@ test('Read configuration from module path in "extends"', async t => {
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options, branches: ['test_branch']};
// Verify the options contains the plugin config from shareable.json
// Verify the options contains the plugin config = require(shareable.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from shareable.json
// Verify the plugins module is called with the plugin options = require(shareable.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
t.deepEqual(t.context.plugins.args[0][1], {
analyzeCommits: 'shareable',
@@ -303,7 +303,7 @@ test('Read configuration from module path in "extends"', async t => {
});
});
test('Read configuration from an array of paths in "extends"', async t => {
test('Read configuration = require(an array of paths in "extends"', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {extends: ['./shareable1.json', './shareable2.json']};
@@ -329,9 +329,9 @@ test('Read configuration from an array of paths in "extends"', async t => {
const {options: result} = await t.context.getConfig({cwd});
const expected = {...options1, ...options2, branches: ['test_branch']};
// Verify the options contains the plugin config from shareable1.json and shareable2.json
// Verify the options contains the plugin config = require(shareable1.json and shareable2.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from shareable1.json and shareable2.json
// Verify the plugins module is called with the plugin options = require(shareable1.json and shareable2.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
t.deepEqual(t.context.plugins.args[0][1], {
verifyRelease1: './shareable1.json',
@@ -342,7 +342,7 @@ test('Read configuration from an array of paths in "extends"', async t => {
});
});
test('Prioritize configuration from config file over "extends"', async t => {
test('Prioritize configuration = require(config file over "extends"', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const pkgOptions = {
@@ -367,9 +367,9 @@ test('Prioritize configuration from config file over "extends"', async t => {
const {options: result} = await t.context.getConfig({cwd});
const expected = omit({...options1, ...pkgOptions, branches: ['test_pkg']}, 'extends');
// Verify the options contains the plugin config from package.json and shareable.json
// Verify the options contains the plugin config = require(package.json and shareable.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from package.json and shareable.json
// Verify the plugins module is called with the plugin options = require(package.json and shareable.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
t.deepEqual(t.context.plugins.args[0][1], {
analyzeCommits: './shareable.json',
@@ -378,7 +378,7 @@ test('Prioritize configuration from config file over "extends"', async t => {
});
});
test('Prioritize configuration from cli/API options over "extends"', async t => {
test('Prioritize configuration = require(cli/API options over "extends"', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
const cliOptions = {
@@ -415,9 +415,9 @@ test('Prioritize configuration from cli/API options over "extends"', async t =>
const {options: result} = await t.context.getConfig({cwd}, cliOptions);
const expected = omit({...options2, ...pkgOptions, ...cliOptions, branches: ['branch_opts']}, 'extends');
// Verify the options contains the plugin config from package.json and shareable2.json
// Verify the options contains the plugin config = require(package.json and shareable2.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from package.json and shareable2.json
// Verify the plugins module is called with the plugin options = require(package.json and shareable2.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
});
@@ -443,13 +443,13 @@ test('Allow to unset properties defined in shareable config with "null"', async
const {options} = await t.context.getConfig({cwd});
// Verify the options contains the plugin config from shareable.json and the default `plugins`
// Verify the options contains the plugin config = require(shareable.json and the default `plugins`
t.deepEqual(options, {
...omit(options1, ['analyzeCommits']),
...omit(pkgOptions, ['extends', 'analyzeCommits']),
plugins: DEFAULT_PLUGINS,
});
// Verify the plugins module is called with the plugin options from shareable.json and the default `plugins`
// Verify the plugins module is called with the plugin options = require(shareable.json and the default `plugins`
t.deepEqual(t.context.plugins.args[0][0], {
options: {
...omit(options1, 'analyzeCommits'),
@@ -492,9 +492,9 @@ test('Allow to unset properties defined in shareable config with "undefined"', a
...omit(pkgOptions, ['extends', 'analyzeCommits']),
branches: ['test_branch'],
};
// Verify the options contains the plugin config from shareable.json
// Verify the options contains the plugin config = require(shareable.json
t.deepEqual(result, expected);
// Verify the plugins module is called with the plugin options from shareable.json
// Verify the plugins module is called with the plugin options = require(shareable.json
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
t.deepEqual(t.context.plugins.args[0][1], {
generateNotes: './shareable.json',
@@ -511,8 +511,8 @@ test('Throw an Error if one of the shareable config cannot be found', async t =>
await outputJson(path.resolve(cwd, 'package.json'), {release: pkhOptions});
await outputJson(path.resolve(cwd, 'shareable1.json'), options1);
const error = await t.throwsAsync(t.context.getConfig({cwd}), Error);
t.regex(error.message, /Cannot find module 'non-existing-path'/);
t.is(error.code, 'MODULE_NOT_FOUND');
await t.throwsAsync(t.context.getConfig({cwd}), {
message: /Cannot find module 'non-existing-path'/,
code: 'MODULE_NOT_FOUND',
});
});
+3 -3
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import getAuthUrl from '../lib/get-git-auth-url';
import {gitRepo} from './helpers/git-utils';
const test = require('ava');
const getAuthUrl = require('../lib/get-git-auth-url');
const {gitRepo} = require('./helpers/git-utils');
const env = {GIT_ASKPASS: 'echo', GIT_TERMINAL_PROMPT: 0};
+14 -14
View File
@@ -1,5 +1,5 @@
import test from 'ava';
import getLastRelease from '../lib/get-last-release';
const test = require('ava');
const getLastRelease = require('../lib/get-last-release');
test('Get the highest non-prerelease valid tag', t => {
const result = getLastRelease({
@@ -8,26 +8,26 @@ test('Get the highest non-prerelease valid tag', t => {
tags: [
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: 'v2.0.0'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: 'v1.0.0'},
{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: 'v3.0.0-beta.1@beta'},
{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: 'v2.0.0', 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 => {
test('Get the highest prerelease valid tag, ignoring other tags = require(other prerelease channels', t => {
const result = getLastRelease({
branch: {
name: 'beta',
prerelease: 'beta',
channel: 'beta',
tags: [
{version: '1.0.0-beta.1', gitTag: 'v1.0.0-beta.1@beta', gitHead: 'v1.0.0-beta.1@beta', channel: 'beta'},
{version: '1.0.0-beta.2', gitTag: 'v1.0.0-beta.2@beta', gitHead: 'v1.0.0-beta.2@beta', channel: 'beta'},
{version: '1.0.0-alpha.1', gitTag: 'v1.0.0-alpha.1@alpha', gitHead: 'v1.0.0-alpha.1@alpha', 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: 'v1.0.0-beta.2@beta',
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: 'v3.0.0-beta.1@beta'}],
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}`},
@@ -65,7 +65,7 @@ test('Get the highest non-prerelease valid tag before a certain version', t => {
tags: [
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: 'v2.0.0'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: 'v1.0.0'},
{version: '2.0.0-beta.1', gitTag: 'v2.0.0-beta.1@beta', gitHead: 'v2.0.0-beta.1@beta'},
{version: '2.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'},
],
@@ -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: 'v2.0.0', channel: undefined});
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: 'v2.0.0', channels: undefined});
});
+3 -3
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import {spy} from 'sinon';
import getLogger from '../lib/get-logger';
const test = require('ava');
const {spy} = require('sinon');
const getLogger = require('../lib/get-logger');
test('Expose "error", "success" and "log" functions', t => {
const stdout = spy();
+153 -41
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import {stub} from 'sinon';
import getNextVersion from '../lib/get-next-version';
const test = require('ava');
const {stub} = require('sinon');
const getNextVersion = require('../lib/get-next-version');
test.beforeEach(t => {
// Stub the logger functions
@@ -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'
);
});
+282
View File
@@ -0,0 +1,282 @@
const test = require('ava');
const getReleaseToAdd = require('../lib/get-release-to-add');
test('Return versions merged = require(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 = require(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);
});
-323
View File
@@ -1,323 +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'},
{gitTag: 'v2.0.0', version: '2.0.0'},
{gitTag: 'v2.1.0', version: '2.1.0'},
{gitTag: 'v2.1.1', version: '2.1.1'},
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.1.0', version: '1.1.0'},
],
},
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: 'v2.0.0@2.x'},
currentRelease: {
type: 'minor',
version: '2.1.0',
channel: undefined,
gitTag: 'v2.1.0',
name: 'v2.1.0',
gitHead: 'v2.1.0',
},
nextRelease: {
type: 'minor',
version: '2.1.0',
channel: '2.x',
gitTag: 'v2.1.0@2.x',
name: 'v2.1.0',
gitHead: 'v2.1.0',
},
},
{
lastRelease: {version: '2.1.0', channel: undefined, gitTag: 'v2.1.0', name: 'v2.1.0', gitHead: 'v2.1.0'},
currentRelease: {
type: 'patch',
version: '2.1.1',
channel: undefined,
gitTag: 'v2.1.1',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
nextRelease: {
type: 'patch',
version: '2.1.1',
channel: '2.x',
gitTag: 'v2.1.1@2.x',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
},
]);
});
test('Return versions merged between release branches', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next'},
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channel: '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.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: 'v1.0.0'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
},
{
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: 'v1.1.0@next', channel: 'next'},
currentRelease: {
type: 'major',
version: '2.0.0',
channel: 'next-major',
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
},
]);
});
test('Return releases sorted by ascending order', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channel: 'next-major'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next'},
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next'},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: 'v1.0.0'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
},
{
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: 'v1.1.0@next', channel: 'next'},
currentRelease: {
type: 'major',
version: '2.0.0',
channel: 'next-major',
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
},
]);
});
test('No lastRelease', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next'}],
},
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: 'v1.0.0@next',
},
nextRelease: {
type: 'major',
version: '1.0.0',
channel: undefined,
gitTag: 'v1.0.0',
name: 'v1.0.0',
gitHead: 'v1.0.0@next',
},
},
]);
});
test('Ignore pre-release versions', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next'},
{gitTag: 'v2.0.0-alpha.1@alpha', version: '2.0.0', channel: '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', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: 'v1.0.0'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
},
]);
});
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', t => {
const result = 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'},
{gitTag: 'v2.0.0', version: '2.0.0'},
{gitTag: 'v2.1.0', version: '2.1.0'},
{gitTag: 'v2.1.1', version: '2.1.1'},
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.1.0', version: '1.1.0'},
],
},
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'},
{gitTag: 'v1.1.0', channel: 'latest', version: '1.1.0'},
{gitTag: 'v2.0.0', channel: 'latest', version: '2.0.0'},
],
},
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'},
{gitTag: 'v1.1.0', version: '1.1.0'},
{gitTag: 'v2.0.0', version: '2.0.0'},
],
},
branches: [
{name: 'master', channel: false},
{name: 'next', channel: false},
{name: 'next-major', channel: false},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
+171 -32
View File
@@ -1,8 +1,7 @@
import test from 'ava';
import tempy from 'tempy';
import {
const test = require('ava');
const tempy = require('tempy');
const {
getTagHead,
isRefInHistory,
isRefExists,
fetch,
getGitHead,
@@ -14,8 +13,11 @@ import {
isGitRepo,
verifyTagName,
isBranchUpToDate,
} from '../lib/git';
import {
getNote,
addNote,
fetchNotes,
} = require('../lib/git');
const {
gitRepo,
gitCommits,
gitCheckout,
@@ -27,7 +29,12 @@ import {
gitRemoteTagHead,
gitPush,
gitDetachedHead,
} from './helpers/git-utils';
gitDetachedHeadFromBranch,
gitAddNote,
gitGetNote,
gitFetch,
initGit,
} = require('./helpers/git-utils');
test('Get the last commit sha', async t => {
// Create a git repository, set the current working directory at the root of the repo
@@ -44,7 +51,7 @@ test('Throw error if the last commit sha cannot be found', async t => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();
await t.throwsAsync(getGitHead({cwd}), Error);
await t.throwsAsync(getGitHead({cwd}));
});
test('Unshallow and fetch repository', async t => {
@@ -58,7 +65,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 +80,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 +96,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('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', {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'master', {cwd}));
t.falsy(await isRefInHistory(otherCommits[0].hash, 'missing-branch', {cwd}));
await t.throwsAsync(isRefInHistory('non-existant-sha', 'master', {cwd}));
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,6 +183,11 @@ test('Get all branches', async t => {
t.deepEqual((await getBranches(repositoryUrl, {cwd})).sort(), ['master', 'second-branch', 'third-branch'].sort());
});
test('Return empty array if there are no branches', async t => {
const {cwd, repositoryUrl} = await initGit(true);
t.deepEqual(await getBranches(repositoryUrl, {cwd}), []);
});
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();
@@ -268,11 +316,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"}');
});
+113 -32
View File
@@ -1,12 +1,13 @@
import tempy from 'tempy';
import execa from 'execa';
import fileUrl from 'file-url';
import pEachSeries from 'p-each-series';
import gitLogParser from 'git-log-parser';
import getStream from 'get-stream';
const tempy = require('tempy');
const execa = require('execa');
const fileUrl = require('file-url');
const pEachSeries = require('p-each-series');
const gitLogParser = require('git-log-parser');
const getStream = require('get-stream');
const {GIT_NOTE_REF} = require('../../lib/definitions/constants');
/**
* Commit message informations.
* Commit message information.
*
* @typedef {Object} Commit
* @property {String} branch The commit branch.
@@ -14,21 +15,34 @@ import getStream from 'get-stream';
* @property {String} message The commit message.
*/
/**
* Initialize git repository
* If `withRemote` is `true`, creates a bare repository and initialize it.
* If `withRemote` is `false`, creates a regular repository and initialize it.
*
* @param {Boolean} withRemote `true` to create a shallow clone of a bare repository.
* @return {String} The path of the repository
*/
async function initGit(withRemote) {
const cwd = tempy.directory();
await execa('git', ['init', ...(withRemote ? ['--bare'] : [])], {cwd});
const repositoryUrl = fileUrl(cwd);
return {cwd, repositoryUrl};
}
/**
* Create a temporary git repository.
* If `withRemote` is `true`, creates a bare repository, initialize it and create a shallow clone. Change the current working directory to the clone root.
* If `withRemote` is `false`, creates a regular repository and initialize it. Change the current working directory to the repository root.
* If `withRemote` is `true`, creates a shallow clone. Change the current working directory to the clone root.
* If `withRemote` is `false`, just change the current working directory to the repository root.
*
*
* @param {Boolean} withRemote `true` to create a shallow clone of a bare repository.
* @param {String} [branch='master'] The branch to initialize.
* @return {String} The path of the clone if `withRemote` is `true`, the path of the repository otherwise.
*/
export async function gitRepo(withRemote, branch = 'master') {
let cwd = tempy.directory();
await execa('git', ['init', ...(withRemote ? ['--bare'] : [])], {cwd});
const repositoryUrl = fileUrl(cwd);
async function gitRepo(withRemote, branch = 'master') {
let {cwd, repositoryUrl} = await initGit(withRemote);
if (withRemote) {
await initBareRepo(repositoryUrl, branch);
cwd = await gitShallowClone(repositoryUrl, branch);
@@ -52,7 +66,7 @@ export async function gitRepo(withRemote, branch = 'master') {
* @param {String} repositoryUrl The URL of the bare repository.
* @param {String} [branch='master'] the branch to initialize.
*/
export async function initBareRepo(repositoryUrl, branch = 'master') {
async function initBareRepo(repositoryUrl, branch = 'master') {
const cwd = tempy.directory();
await execa('git', ['clone', '--no-hardlinks', repositoryUrl, cwd], {cwd});
await gitCheckout(branch, true, {cwd});
@@ -68,7 +82,7 @@ export async function initBareRepo(repositoryUrl, branch = 'master') {
*
* @returns {Array<Commit>} The created commits, in reverse order (to match `git log` order).
*/
export async function gitCommits(messages, execaOpts) {
async function gitCommits(messages, execaOpts) {
await pEachSeries(
messages,
async message => (await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOpts)).stdout
@@ -84,7 +98,7 @@ export async function gitCommits(messages, execaOpts) {
*
* @return {Array<Object>} The list of parsed commits.
*/
export async function gitGetCommits(from, execaOpts) {
async function gitGetCommits(from, execaOpts) {
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
return (
await getStream.array(
@@ -104,10 +118,20 @@ export async function gitGetCommits(from, execaOpts) {
* @param {Boolean} create to create the branch, `false` to checkout an existing branch.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitCheckout(branch, create, execaOpts) {
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`.
*/
async function gitFetch(repositoryUrl, execaOpts) {
await execa('git', ['fetch', repositoryUrl], execaOpts);
}
/**
* Get the HEAD sha.
*
@@ -115,7 +139,7 @@ export async function gitCheckout(branch, create, execaOpts) {
*
* @return {String} The sha of the head commit in the current git repository.
*/
export async function gitHead(execaOpts) {
async function gitHead(execaOpts) {
return (await execa('git', ['rev-parse', 'HEAD'], execaOpts)).stdout;
}
@@ -126,7 +150,7 @@ export async function gitHead(execaOpts) {
* @param {String} [sha] The commit on which to create the tag. If undefined the tag is created on the last commit.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitTagVersion(tagName, sha, execaOpts) {
async function gitTagVersion(tagName, sha, execaOpts) {
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName], execaOpts);
}
@@ -139,7 +163,7 @@ export async function gitTagVersion(tagName, sha, execaOpts) {
* @param {Number} [depth=1] The number of commit to clone.
* @return {String} The path of the cloned repository.
*/
export async function gitShallowClone(repositoryUrl, branch = 'master', depth = 1) {
async function gitShallowClone(repositoryUrl, branch = 'master', depth = 1) {
const cwd = tempy.directory();
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, repositoryUrl, cwd], {
@@ -155,7 +179,7 @@ export async function gitShallowClone(repositoryUrl, branch = 'master', depth =
* @param {Number} head A commit sha of the remote repo that will become the detached head of the new one.
* @return {String} The path of the new repository.
*/
export async function gitDetachedHead(repositoryUrl, head) {
async function gitDetachedHead(repositoryUrl, head) {
const cwd = tempy.directory();
await execa('git', ['init'], {cwd});
@@ -165,6 +189,17 @@ export async function gitDetachedHead(repositoryUrl, head) {
return cwd;
}
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.
*
@@ -172,7 +207,7 @@ export async function gitDetachedHead(repositoryUrl, head) {
* @param {String} value Config value.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitAddConfig(name, value, execaOpts) {
async function gitAddConfig(name, value, execaOpts) {
await execa('git', ['config', '--add', name, value], execaOpts);
}
@@ -184,7 +219,7 @@ export async function gitAddConfig(name, value, execaOpts) {
*
* @return {String} The sha of the commit associated with `tagName` on the local repository.
*/
export async function gitTagHead(tagName, execaOpts) {
async function gitTagHead(tagName, execaOpts) {
return (await execa('git', ['rev-list', '-1', tagName], execaOpts)).stdout;
}
@@ -197,11 +232,11 @@ export async function gitTagHead(tagName, execaOpts) {
*
* @return {String} The sha of the commit associated with `tagName` on the remote repository.
*/
export async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
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];
}
/**
@@ -212,7 +247,7 @@ export async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
*
* @return {String} The tag associatedwith the sha in parameter or `null`.
*/
export async function gitCommitTag(gitHead, execaOpts) {
async function gitCommitTag(gitHead, execaOpts) {
return (await execa('git', ['describe', '--tags', '--exact-match', gitHead], execaOpts)).stdout;
}
@@ -225,7 +260,7 @@ export async function gitCommitTag(gitHead, execaOpts) {
*
* @throws {Error} if the push failed.
*/
export async function gitPush(repositoryUrl, branch, execaOpts) {
async function gitPush(repositoryUrl, branch, execaOpts) {
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOpts);
}
@@ -235,7 +270,7 @@ export async function gitPush(repositoryUrl, branch, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function merge(ref, execaOpts) {
async function merge(ref, execaOpts) {
await execa('git', ['merge', '--no-ff', ref], execaOpts);
}
@@ -245,7 +280,7 @@ export async function merge(ref, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function mergeFf(ref, execaOpts) {
async function mergeFf(ref, execaOpts) {
await execa('git', ['merge', '--ff', ref], execaOpts);
}
@@ -255,6 +290,52 @@ export async function mergeFf(ref, execaOpts) {
* @param {String} ref The ref to merge.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function rebase(ref, execaOpts) {
async function rebase(ref, execaOpts) {
await execa('git', ['rebase', ref], 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`.
*/
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`.
*/
async function gitGetNote(ref, execaOpts) {
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout;
}
module.exports = {
initGit,
gitRepo,
initBareRepo,
gitCommits,
gitGetCommits,
gitCheckout,
gitFetch,
gitHead,
gitTagVersion,
gitShallowClone,
gitDetachedHead,
gitDetachedHeadFromBranch,
gitAddConfig,
gitTagHead,
gitRemoteTagHead,
gitCommitTag,
gitPush,
merge,
mergeFf,
rebase,
gitAddNote,
gitGetNote,
};
+5 -5
View File
@@ -1,7 +1,7 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import pRetry from 'p-retry';
import {initBareRepo, gitShallowClone} from './git-utils';
const Docker = require('dockerode');
const getStream = require('get-stream');
const pRetry = require('p-retry');
const {initBareRepo, gitShallowClone} = require('./git-utils');
const IMAGE = 'pvdlg/docker-gitbox:latest';
const SERVER_PORT = 80;
@@ -69,4 +69,4 @@ async function createRepo(name, branch = 'master', description = `Repository ${n
return {cwd, repositoryUrl, authUrl};
}
export default {start, stop, gitCredential, createRepo};
module.exports = {start, stop, gitCredential, createRepo};
+6 -6
View File
@@ -1,8 +1,8 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import got from 'got';
import pRetry from 'p-retry';
import {mockServerClient} from 'mockserver-client';
const Docker = require('dockerode');
const getStream = require('get-stream');
const got = require('got');
const pRetry = require('p-retry');
const {mockServerClient} = require('mockserver-client');
const IMAGE = 'jamesdbloom/mockserver:latest';
const MOCK_SERVER_PORT = 1080;
@@ -100,4 +100,4 @@ function verify(expectation) {
return client.verify(expectation);
}
export default {start, stop, mock, verify, url};
module.exports = {start, stop, mock, verify, url};
+9 -9
View File
@@ -1,8 +1,8 @@
import Docker from 'dockerode';
import getStream from 'get-stream';
import got from 'got';
import delay from 'delay';
import pRetry from 'p-retry';
const Docker = require('dockerode');
const getStream = require('get-stream');
const got = require('got');
const delay = require('delay');
const pRetry = require('p-retry');
const IMAGE = 'semanticrelease/npm-registry-docker:latest';
const SERVER_PORT = 15986;
@@ -45,10 +45,10 @@ async function start() {
// 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: [],
@@ -76,4 +76,4 @@ async function stop() {
await container.remove();
}
export default {start, stop, authEnv, url};
module.exports = {start, stop, authEnv, url};
+4 -2
View File
@@ -1,5 +1,7 @@
import execa from 'execa';
const execa = require('execa');
export async function npmView(packageName, env) {
async function npmView(packageName, env) {
return JSON.parse((await execa('npm', ['view', packageName, '--json'], {env})).stdout);
}
module.exports = {npmView};
+4 -4
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {repeat} from 'lodash';
import hideSensitive from '../lib/hide-sensitive';
import {SECRET_REPLACEMENT, SECRET_MIN_SIZE} from '../lib/definitions/constants';
const test = require('ava');
const {repeat} = require('lodash');
const hideSensitive = require('../lib/hide-sensitive');
const {SECRET_REPLACEMENT, SECRET_MIN_SIZE} = require('../lib/definitions/constants');
test('Replace multiple sensitive environment variable values', t => {
const env = {SOME_PASSWORD: 'password', SOME_TOKEN: 'secret'};
+125 -107
View File
@@ -1,13 +1,13 @@
import test from 'ava';
import {escapeRegExp, isString, sortBy} from 'lodash';
import proxyquire from 'proxyquire';
import {spy, stub} from 'sinon';
import {WritableStreamBuffer} from 'stream-buffers';
import AggregateError from 'aggregate-error';
import SemanticReleaseError from '@semantic-release/error';
import {COMMIT_NAME, COMMIT_EMAIL, SECRET_REPLACEMENT} from '../lib/definitions/constants';
import {
gitHead as getGitHead,
const test = require('ava');
const {escapeRegExp, isString, sortBy, omit} = require('lodash');
const proxyquire = require('proxyquire');
const {spy, stub} = require('sinon');
const {WritableStreamBuffer} = require('stream-buffers');
const AggregateError = require('aggregate-error');
const SemanticReleaseError = require('@semantic-release/error');
const {COMMIT_NAME, COMMIT_EMAIL, SECRET_REPLACEMENT} = require('../lib/definitions/constants');
const {
gitHead: getGitHead,
gitCheckout,
gitTagHead,
gitRepo,
@@ -19,7 +19,9 @@ import {
merge,
mergeFf,
rebase,
} from './helpers/git-utils';
gitAddNote,
gitGetNote,
} = require('./helpers/git-utils');
const requireNoCache = proxyquire.noPreserveCache();
const pluginNoop = require.resolve('./fixtures/plugin-noop');
@@ -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'}],
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', 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,7 +601,8 @@ 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 => {
@@ -694,14 +718,16 @@ test('Publish pre-releases the same channel as regular releases', async t => {
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});
@@ -746,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
@@ -771,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,
@@ -782,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,
};
@@ -806,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})`;
@@ -842,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();
@@ -977,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});
@@ -1259,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});
@@ -1272,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();
@@ -1345,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();
@@ -1396,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();
@@ -1447,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});
@@ -1490,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}),
@@ -1509,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);
@@ -1788,7 +1807,7 @@ test('Throw an Error if plugin returns an unexpected value', async t => {
});
const error = await t.throwsAsync(
semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}),
Error
{instanceOf: SemanticReleaseError}
);
t.regex(error.details, /string/);
});
@@ -1817,8 +1836,7 @@ test('Hide sensitive information passed to "fail" plugin', async t => {
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
await t.throwsAsync(
semanticRelease(options, {cwd, env, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}),
Error
semanticRelease(options, {cwd, env, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()})
);
const error = fail.args[0][1].errors[0];
+25 -25
View File
@@ -1,13 +1,13 @@
import path from 'path';
import proxyquire from 'proxyquire';
import test from 'ava';
import {escapeRegExp} from 'lodash';
import {writeJson, readJson} from 'fs-extra';
import execa from 'execa';
import {WritableStreamBuffer} from 'stream-buffers';
import delay from 'delay';
import {SECRET_REPLACEMENT} from '../lib/definitions/constants';
import {
const path = require('path');
const test = require('ava');
const proxyquire = require('proxyquire');
const {escapeRegExp} = require('lodash');
const {writeJson, readJson} = require('fs-extra');
const execa = require('execa');
const {WritableStreamBuffer} = require('stream-buffers');
const delay = require('delay');
const {SECRET_REPLACEMENT} = require('../lib/definitions/constants');
const {
gitHead,
gitTagHead,
gitRepo,
@@ -16,11 +16,12 @@ import {
gitPush,
gitCheckout,
merge,
} from './helpers/git-utils';
import {npmView} from './helpers/npm-utils';
import gitbox from './helpers/gitbox';
import mockServer from './helpers/mockserver';
import npmRegistry from './helpers/npm-registry';
gitGetNote,
} = require('./helpers/git-utils');
const {npmView} = require('./helpers/npm-utils');
const gitbox = require('./helpers/gitbox');
const mockServer = require('./helpers/mockserver');
const npmRegistry = require('./helpers/npm-registry');
/* eslint camelcase: ["error", {properties: "never"}] */
@@ -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);
+9 -11
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {noop} from 'lodash';
import {stub} from 'sinon';
import normalize from '../../lib/plugins/normalize';
const test = require('ava');
const {noop} = require('lodash');
const {stub} = require('sinon');
const normalize = require('../../lib/plugins/normalize');
const cwd = process.cwd();
@@ -268,11 +268,9 @@ test('Throws an error if the plugin return an object without the expected plugin
});
test('Throws an error if the plugin is not found', t => {
const error = t.throws(
() => normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', 'non-existing-path', {}),
Error
);
t.regex(error.message, /Cannot find module 'non-existing-path'/);
t.is(error.code, 'MODULE_NOT_FOUND');
t.throws(() => normalize({cwd, options: {}, logger: t.context.logger}, 'inexistantPlugin', 'non-existing-path', {}), {
message: /Cannot find module 'non-existing-path'/,
code: 'MODULE_NOT_FOUND',
instanceOf: Error,
});
});
+8 -5
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import {stub} from 'sinon';
import AggregateError from 'aggregate-error';
import pipeline from '../../lib/plugins/pipeline';
const test = require('ava');
const {stub} = require('sinon');
const AggregateError = require('aggregate-error');
const pipeline = require('../../lib/plugins/pipeline');
test('Execute each function in series passing the same input', async t => {
const step1 = stub().resolves(1);
@@ -98,7 +98,10 @@ test('Stop execution and throw error if a step rejects', async t => {
const step2 = stub().rejects(new Error('test error'));
const step3 = stub().resolves(3);
const error = await t.throwsAsync(pipeline([step1, step2, step3])(0), Error);
const error = await t.throwsAsync(pipeline([step1, step2, step3])(0), {
instanceOf: Error,
message: 'test error',
});
t.is(error.message, 'test error');
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
+6 -6
View File
@@ -1,9 +1,9 @@
import path from 'path';
import test from 'ava';
import {copy, outputFile} from 'fs-extra';
import {stub} from 'sinon';
import tempy from 'tempy';
import getPlugins from '../../lib/plugins';
const path = require('path');
const test = require('ava');
const {copy, outputFile} = require('fs-extra');
const {stub} = require('sinon');
const tempy = require('tempy');
const getPlugins = require('../../lib/plugins');
// Save the current working diretory
const cwd = process.cwd();
+2 -2
View File
@@ -1,5 +1,5 @@
import test from 'ava';
import {validatePlugin, validateStep, loadPlugin, parseConfig} from '../../lib/plugins/utils';
const test = require('ava');
const {validatePlugin, validateStep, loadPlugin, parseConfig} = require('../../lib/plugins/utils');
test('validatePlugin', t => {
const path = 'plugin-module';
+4 -8
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import AggregateError from 'aggregate-error';
import {
const test = require('ava');
const AggregateError = require('aggregate-error');
const {
extractErrors,
tagsToVersions,
isMajorRange,
@@ -15,7 +15,7 @@ import {
getRange,
makeTag,
isSameChannel,
} from '../lib/utils';
} = require('../lib/utils');
test('extractErrors', t => {
const errors = [new Error('Error 1'), new Error('Error 2')];
@@ -174,10 +174,6 @@ 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 => {
+4 -4
View File
@@ -1,7 +1,7 @@
import test from 'ava';
import tempy from 'tempy';
import verify from '../lib/verify';
import {gitRepo} from './helpers/git-utils';
const test = require('ava');
const tempy = require('tempy');
const verify = require('../lib/verify');
const {gitRepo} = require('./helpers/git-utils');
test('Throw a AggregateError', async t => {
const {cwd} = await gitRepo();