Compare commits
1 Commits
master
...
windows-gi
Author | SHA1 | Date | |
---|---|---|---|
|
97dc680536 |
@ -1,4 +0,0 @@
|
|||||||
# style: prettier (#2670)
|
|
||||||
b06c9bbe4c6be121c5561b356d8c465c1cadffba
|
|
||||||
# style: upgraded prettier to v3 (#2863)
|
|
||||||
272af210523804de782b3076f05e56bcb4aeeb8f
|
|
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@ -6,28 +6,18 @@ name: Release
|
|||||||
- next
|
- next
|
||||||
- beta
|
- beta
|
||||||
- "*.x"
|
- "*.x"
|
||||||
permissions:
|
|
||||||
contents: read # for checkout
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
permissions:
|
|
||||||
contents: write # to be able to publish a GitHub release
|
|
||||||
issues: write # to be able to comment on released issues
|
|
||||||
pull-requests: write # to be able to comment on released pull requests
|
|
||||||
id-token: write # to enable use of OIDC for npm provenance
|
|
||||||
name: release
|
name: release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: npm
|
cache: npm
|
||||||
node-version: lts/*
|
node-version: 16
|
||||||
- run: npm clean-install
|
- run: npm ci
|
||||||
- run: npm audit signatures
|
- run: npx semantic-release
|
||||||
# pinned version updated automatically by Renovate.
|
|
||||||
# details at https://semantic-release.gitbook.io/semantic-release/usage/installation#global-installation
|
|
||||||
- run: npx semantic-release@21.0.2
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_BOT_NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_BOT_NPM_TOKEN }}
|
||||||
|
36
.github/workflows/scorecard.yml
vendored
36
.github/workflows/scorecard.yml
vendored
@ -1,36 +0,0 @@
|
|||||||
name: OpenSSF Scorecard
|
|
||||||
"on":
|
|
||||||
schedule:
|
|
||||||
- cron: 31 2 * * 1
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
permissions: read-all
|
|
||||||
jobs:
|
|
||||||
analysis:
|
|
||||||
name: Scorecard analysis
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
security-events: write
|
|
||||||
id-token: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: Run analysis
|
|
||||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
|
||||||
with:
|
|
||||||
results_file: results.sarif
|
|
||||||
results_format: sarif
|
|
||||||
publish_results: true
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
|
||||||
with:
|
|
||||||
name: SARIF file
|
|
||||||
path: results.sarif
|
|
||||||
retention-days: 5
|
|
||||||
- name: Upload to code-scanning
|
|
||||||
uses: github/codeql-action/upload-sarif@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
|
|
||||||
with:
|
|
||||||
sarif_file: results.sarif
|
|
64
.github/workflows/test.yml
vendored
64
.github/workflows/test.yml
vendored
@ -12,69 +12,43 @@ on:
|
|||||||
- opened
|
- opened
|
||||||
- synchronize
|
- synchronize
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read # to fetch code (actions/checkout)
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_COLOR: 1
|
|
||||||
NPM_CONFIG_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# verify against ranges defined as supported in engines.node
|
|
||||||
test_matrix:
|
test_matrix:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version:
|
node-version:
|
||||||
- 20.8.1
|
- 18.0.0
|
||||||
- 20
|
- 19
|
||||||
- 21
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
- windows-latest
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: "${{ matrix.os }}"
|
||||||
timeout-minutes: 5
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
- uses: actions/checkout@v3
|
||||||
- run: git config --global user.name github-actions
|
- run: git config --global user.name github-actions
|
||||||
- run: git config --global user.email github-actions@github.com
|
- run: git config --global user.email github-actions@github.com
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: npm
|
cache: npm
|
||||||
- run: npm clean-install
|
- run: npm clean-install
|
||||||
- run: npm audit signatures
|
- name: Ensure dependencies are compatible with the version of node
|
||||||
- run: npm test
|
run: npx ls-engines
|
||||||
|
- run: npm run test:ci
|
||||||
# verify against the node version defined for development in the .nvmrc
|
|
||||||
test_dev:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
|
||||||
- run: git config --global user.name github-actions
|
|
||||||
- run: git config --global user.email github-actions@github.com
|
|
||||||
- name: Use Node.js from .nvmrc
|
|
||||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
|
||||||
with:
|
|
||||||
node-version-file: .nvmrc
|
|
||||||
cache: npm
|
|
||||||
- run: npm clean-install
|
|
||||||
- run: npm audit signatures
|
|
||||||
- run: npm test
|
|
||||||
|
|
||||||
# separate job to set as required in branch protection,
|
# separate job to set as required in branch protection,
|
||||||
# as the build names above change each time Node versions change
|
# as the build names above change each time Node versions change
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs: test_matrix
|
||||||
- test_dev
|
|
||||||
- test_matrix
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: All matrix versions passed
|
- uses: actions/checkout@v3
|
||||||
if: ${{ !(contains(needs.*.result, 'failure')) }}
|
- uses: actions/setup-node@v3
|
||||||
run: exit 0
|
with:
|
||||||
- name: Some matrix version failed
|
node-version: lts/*
|
||||||
if: ${{ contains(needs.*.result, 'failure') }}
|
cache: npm
|
||||||
run: exit 1
|
- run: npm clean-install
|
||||||
|
- run: npm run lint
|
||||||
|
@ -146,7 +146,7 @@ If possible, make [atomic commits](https://en.wikipedia.org/wiki/Atomic_commit),
|
|||||||
|
|
||||||
- a commit should contain exactly one self-contained functional change
|
- a commit should contain exactly one self-contained functional change
|
||||||
- a functional change should be contained in exactly one commit
|
- a functional change should be contained in exactly one commit
|
||||||
- a commit should not create an inconsistent state (such as test errors, linting errors, partial fix, feature without documentation, etc...)
|
- a commit should not create an inconsistent state (such as test errors, linting errors, partial fix, feature with documentation etc...)
|
||||||
|
|
||||||
A complex feature can be broken down into multiple commits as long as each one maintains a consistent state and consists of a self-contained change.
|
A complex feature can be broken down into multiple commits as long as each one maintains a consistent state and consists of a self-contained change.
|
||||||
|
|
||||||
@ -241,82 +241,42 @@ $ git clone https://github.com/semantic-release/<repo-name>
|
|||||||
$ cd <repo-name>
|
$ cd <repo-name>
|
||||||
# Assign the original repo to a remote called "upstream"
|
# Assign the original repo to a remote called "upstream"
|
||||||
$ git remote add upstream https://github.com/semantic-release/<repo-name>
|
$ git remote add upstream https://github.com/semantic-release/<repo-name>
|
||||||
# Switch your node version to the version defined by the project as the development version
|
|
||||||
# This step assumes you have already installed and configured https://github.com/nvm-sh/nvm
|
|
||||||
# You may need to run `nvm install` if you have not already installed the development node version
|
|
||||||
$ nvm use
|
|
||||||
# Install the dependencies
|
# Install the dependencies
|
||||||
$ npm install
|
$ npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Verification
|
### Lint
|
||||||
|
|
||||||
The `test` script is structured to execute as much of the verification for the project as possible.
|
All the [semantic-release](https://github.com/semantic-release) repositories use [XO](https://github.com/sindresorhus/xo) for linting and [Prettier](https://prettier.io) for formatting.
|
||||||
Ensuring that the `test` script fully passes in the node version defined as the development version in the `.nvmrc`
|
Prettier formatting will be automatically verified and fixed by XO.
|
||||||
minimizes the chances of the test workflow failing after pushing your changes.
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
Before pushing your code changes make sure there are no linting errors with `npm run lint`.
|
||||||
> Before pushing your code changes, be sure to run the verification for the project with `npm test`.
|
|
||||||
|
|
||||||
[npm-run-all2](https://www.npmjs.com/package/npm-run-all2) is used to enable running multiple independent lint and test
|
**Tips**:
|
||||||
scripts together from the `test` script.
|
|
||||||
This enables the test script to not only run all scripts, but also parallelize some of the scripts to optimize the overall
|
|
||||||
time required for verification.
|
|
||||||
|
|
||||||
When a failure occurs with the `test`, the output can be a bit confusing because there may be output from multiple parallel
|
- Most linting errors can be automatically fixed with `npm run lint -- --fix`.
|
||||||
scripts mixed together.
|
- Install the [XO plugin](https://github.com/sindresorhus/xo#editor-plugins) for your editor to see linting errors directly in your editor and automatically fix them on save.
|
||||||
To investigate the failure with cleaner output, re-run the problematic script directly using the script name from the label
|
|
||||||
included on the left side of the output
|
|
||||||
|
|
||||||
```shell
|
### Tests
|
||||||
$ npm run <script-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Lint
|
Running the integration test requires you to install [Docker](https://docs.docker.com/engine/installation) on your machine.
|
||||||
|
|
||||||
##### Prettier
|
|
||||||
|
|
||||||
All the [semantic-release](https://github.com/semantic-release) repositories use [Prettier](https://prettier.io) for formatting.
|
|
||||||
Prettier formatting will be automatically verified by the `lint:prettier` script, included in the `test` script.
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Most linting errors can be automatically fixed with `npm run lint:prettier:fix`.
|
|
||||||
|
|
||||||
##### Other Lint Tools
|
|
||||||
|
|
||||||
Other tools are used for specific compatibility concerns, but are less likely to result in failures in common contributions.
|
|
||||||
Please follow the guidance of these tools if failures are encountered.
|
|
||||||
|
|
||||||
#### Tests
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Before pushing your code changes make sure all **tests pass** and the unit test **coverage is 100%**:
|
|
||||||
|
|
||||||
All the [semantic-release](https://github.com/semantic-release) repositories use [AVA](https://github.com/avajs/ava) for writing and running tests.
|
All the [semantic-release](https://github.com/semantic-release) repositories use [AVA](https://github.com/avajs/ava) for writing and running tests.
|
||||||
|
|
||||||
During development, you can:
|
Before pushing your code changes make sure all **tests pass** and the **coverage is 100%**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tips:** During development you can:
|
||||||
|
|
||||||
- run only a subset of test files with `ava <glob>`, for example `ava test/mytestfile.test.js`
|
- run only a subset of test files with `ava <glob>`, for example `ava test/mytestfile.test.js`
|
||||||
- run in watch mode with `ava -w` to automatically run a test file when you modify it
|
- run in watch mode with `ava -w` to automatically run a test file when you modify it
|
||||||
- run only the test you are working on by adding [`.only` to the test definition](https://github.com/avajs/ava#running-specific-tests)
|
- run only the test you are working on by adding [`.only` to the test definition](https://github.com/avajs/ava#running-specific-tests)
|
||||||
|
|
||||||
##### Unit Tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm run test:unit
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Integration Tests
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Running the integration test requires you to install [Docker](https://docs.docker.com/engine/installation) on your machine.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm run test:integration
|
|
||||||
```
|
|
||||||
|
|
||||||
### Commits
|
### Commits
|
||||||
|
|
||||||
All the [semantic-release](https://github.com/semantic-release) repositories use [Commitizen](https://github.com/commitizen/cz-cli) to help you create [valid commit messages](#commit-message-guidelines).
|
All the [semantic-release](https://github.com/semantic-release) repositories use [Commitizen](https://github.com/commitizen/cz-cli) to help you create [valid commit messages](#commit-message-guidelines).
|
||||||
|
|
||||||
Assuming you have [installed Commitizen](https://github.com/commitizen/cz-cli#installing-the-command-line-tool), run `git cz` to start the interactive commit message CLI rather than `git commit` when committing.
|
After staging your changes with `git add`, run `npm run cm` to start the interactive commit message CLI.
|
||||||
|
4
LICENSE
4
LICENSE
@ -9,8 +9,8 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
The above copyright notice and this permission notice shall be included in all
|
||||||
all copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
12
README.md
12
README.md
@ -7,9 +7,6 @@
|
|||||||
<a href="https://github.com/semantic-release/semantic-release/actions?query=workflow%3ATest+branch%3Amaster">
|
<a href="https://github.com/semantic-release/semantic-release/actions?query=workflow%3ATest+branch%3Amaster">
|
||||||
<img alt="Build states" src="https://github.com/semantic-release/semantic-release/workflows/Test/badge.svg">
|
<img alt="Build states" src="https://github.com/semantic-release/semantic-release/workflows/Test/badge.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://securityscorecards.dev/viewer/?uri=github.com/semantic-release/semantic-release">
|
|
||||||
<img alt="OpenSSF Scorecard" src="https://api.securityscorecards.dev/projects/github.com/semantic-release/semantic-release/badge">
|
|
||||||
</a>
|
|
||||||
<a href="#badge">
|
<a href="#badge">
|
||||||
<img alt="semantic-release: angular" src="https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release">
|
<img alt="semantic-release: angular" src="https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release">
|
||||||
</a>
|
</a>
|
||||||
@ -44,7 +41,6 @@ This removes the immediate connection between human emotions and version numbers
|
|||||||
- Avoid potential errors associated with manual releases
|
- Avoid potential errors associated with manual releases
|
||||||
- Support any [package managers and languages](docs/recipes/release-workflow/README.md#package-managers-and-languages) via [plugins](docs/usage/plugins.md)
|
- Support any [package managers and languages](docs/recipes/release-workflow/README.md#package-managers-and-languages) via [plugins](docs/usage/plugins.md)
|
||||||
- Simple and reusable configuration via [shareable configurations](docs/usage/shareable-configurations.md)
|
- Simple and reusable configuration via [shareable configurations](docs/usage/shareable-configurations.md)
|
||||||
- Support for [npm package provenance](https://github.com/semantic-release/npm#npm-provenance) that promotes increased supply-chain security via signed attestations on GitHub Actions
|
|
||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
||||||
@ -69,7 +65,7 @@ The table below shows which commit message gets you which release type when `sem
|
|||||||
### Automation with CI
|
### Automation with CI
|
||||||
|
|
||||||
**semantic-release** is meant to be executed on the CI environment after every successful build on the release branch.
|
**semantic-release** is meant to be executed on the CI environment after every successful build on the release branch.
|
||||||
This way no human is directly involved in the release process and the releases are guaranteed to be [unromantic and unsentimental](https://github.com/dominictarr/sentimental-versioning#readme).
|
This way no human is directly involved in the release process and the releases are guaranteed to be [unromantic and unsentimental](http://sentimentalversioning.org).
|
||||||
|
|
||||||
### Triggering a release
|
### Triggering a release
|
||||||
|
|
||||||
@ -110,9 +106,9 @@ In order to use **semantic-release** you need:
|
|||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- Usage
|
- Usage
|
||||||
- [Getting started](docs/usage/getting-started.md)
|
- [Getting started](docs/usage/getting-started.md#getting-started)
|
||||||
- [Installation](docs/usage/installation.md)
|
- [Installation](docs/usage/installation.md#installation)
|
||||||
- [CI Configuration](docs/usage/ci-configuration.md)
|
- [CI Configuration](docs/usage/ci-configuration.md#ci-configuration)
|
||||||
- [Configuration](docs/usage/configuration.md#configuration)
|
- [Configuration](docs/usage/configuration.md#configuration)
|
||||||
- [Plugins](docs/usage/plugins.md)
|
- [Plugins](docs/usage/plugins.md)
|
||||||
- [Workflow configuration](docs/usage/workflow-configuration.md)
|
- [Workflow configuration](docs/usage/workflow-configuration.md)
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- [Getting started](docs/usage/getting-started.md)
|
- [Getting started](docs/usage/getting-started.md#getting-started)
|
||||||
- [Installation](docs/usage/installation.md)
|
- [Installation](docs/usage/installation.md#installation)
|
||||||
- [CI Configuration](docs/usage/ci-configuration.md)
|
- [CI Configuration](docs/usage/ci-configuration.md#ci-configuration)
|
||||||
- [Configuration](docs/usage/configuration.md)
|
- [Configuration](docs/usage/configuration.md#configuration)
|
||||||
- [Plugins](docs/usage/plugins.md)
|
- [Plugins](docs/usage/plugins.md)
|
||||||
- [Workflow configuration](docs/usage/workflow-configuration.md)
|
- [Workflow configuration](docs/usage/workflow-configuration.md)
|
||||||
- [Shareable configurations](docs/usage/shareable-configurations.md)
|
- [Shareable configurations](docs/usage/shareable-configurations.md)
|
||||||
|
@ -25,7 +25,7 @@ See https://github.com/semantic-release/semantic-release/blob/master/docs/suppor
|
|||||||
|
|
||||||
execa("git", ["--version"])
|
execa("git", ["--version"])
|
||||||
.then(({ stdout }) => {
|
.then(({ stdout }) => {
|
||||||
const gitVersion = findVersions(stdout, { loose: true })[0];
|
const gitVersion = findVersions(stdout)[0];
|
||||||
if (lt(gitVersion, MIN_GIT_VERSION)) {
|
if (lt(gitVersion, MIN_GIT_VERSION)) {
|
||||||
console.error(`[semantic-release]: Git version ${MIN_GIT_VERSION} is required. Found ${gitVersion}.`);
|
console.error(`[semantic-release]: Git version ${MIN_GIT_VERSION} is required. Found ${gitVersion}.`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
const semanticRelease = require("semantic-release");
|
const semanticRelease = require("semantic-release");
|
||||||
const { WritableStreamBuffer } = require("stream-buffers");
|
const { WritableStreamBuffer } = require("stream-buffers");
|
||||||
|
|
||||||
const stdoutBuffer = new WritableStreamBuffer();
|
const stdoutBuffer = WritableStreamBuffer();
|
||||||
const stderrBuffer = new WritableStreamBuffer();
|
const stderrBuffer = WritableStreamBuffer();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await semanticRelease(
|
const result = await semanticRelease(
|
||||||
@ -127,7 +127,7 @@ It allows to configure **semantic-release** to write errors to a specific stream
|
|||||||
|
|
||||||
Type: `Object` `Boolean`<br>
|
Type: `Object` `Boolean`<br>
|
||||||
|
|
||||||
An object with [`lastRelease`](#lastrelease), [`nextRelease`](#nextrelease), [`commits`](#commits) and [`releases`](#releases) if a release is published or `false` if no release was published.
|
And object with [`lastRelease`](#lastrelease), [`nextRelease`](#nextrelease), [`commits`](#commits) and [`releases`](#releases) if a release is published or `false` if no release was published.
|
||||||
|
|
||||||
#### lastRelease
|
#### lastRelease
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ Information related to the last release found:
|
|||||||
| gitTag | `String` | The [Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) associated with the last release. |
|
| gitTag | `String` | The [Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) associated with the last release. |
|
||||||
| channel | `String` | The distribution channel on which the last release was initially made available (`undefined` for the default distribution channel). |
|
| channel | `String` | The distribution channel on which the last release was initially made available (`undefined` for the default distribution channel). |
|
||||||
|
|
||||||
**Note**: If no previous release is found, `lastRelease` will be an empty `Object`.
|
**Notes**: If no previous release is found, `lastRelease` will be an empty `Object`.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ Example:
|
|||||||
|
|
||||||
Type: `Array<Object>`
|
Type: `Array<Object>`
|
||||||
|
|
||||||
The list of commit(s) included in the new release.<br>
|
The list of commit included in the new release.<br>
|
||||||
Each commit object has the following properties:
|
Each commit object has the following properties:
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
|
@ -84,11 +84,6 @@
|
|||||||
- `verifyRelease`: Checks and warns (does not error by default) if the version numbers found on maven central and within the Git project differ by quite a bit
|
- `verifyRelease`: Checks and warns (does not error by default) if the version numbers found on maven central and within the Git project differ by quite a bit
|
||||||
- `prepare`: Changes the version number in the `pom.xml` (or all `pom.xml` files in maven projects with multiple `pom.xml` files) and optionally creates a commit with this version number and pushes it to `master`
|
- `prepare`: Changes the version number in the `pom.xml` (or all `pom.xml` files in maven projects with multiple `pom.xml` files) and optionally creates a commit with this version number and pushes it to `master`
|
||||||
- `publish`: Runs `mvn deploy` to deploy to maven central and optionally will update to next snapshot version and merge changes to development branch
|
- `publish`: Runs `mvn deploy` to deploy to maven central and optionally will update to next snapshot version and merge changes to development branch
|
||||||
- [maven-semantic-release](https://github.com/terrestris/maven-semantic-release) (alternative version)
|
|
||||||
- `verifyConditions`: Verifies that the `mvn` command exists.
|
|
||||||
- `prepare`: Changes version number in `pom.xml` and optionally in all child modules.
|
|
||||||
- `publish`: Runs one of the mvn targets `deploy`, `package jib:build` or `deploy jib:build`.
|
|
||||||
- `success`: Optionally sets new snapshot version and commits it.
|
|
||||||
- [semantic-release-ado](https://github.com/lluchmk/semantic-release-ado)
|
- [semantic-release-ado](https://github.com/lluchmk/semantic-release-ado)
|
||||||
- `prepare`: Stores the version number as an Azure DevOps pipeline variable available to downstream steps on the job
|
- `prepare`: Stores the version number as an Azure DevOps pipeline variable available to downstream steps on the job
|
||||||
- [gradle-semantic-release](https://github.com/KengoTODA/gradle-semantic-release-plugin)
|
- [gradle-semantic-release](https://github.com/KengoTODA/gradle-semantic-release-plugin)
|
||||||
@ -112,7 +107,7 @@
|
|||||||
- `verifyConditions`: Verify the presence and the validity of the authentication and the assets option configuration.
|
- `verifyConditions`: Verify the presence and the validity of the authentication and the assets option configuration.
|
||||||
- `publish`: Publish a Gitea release, optionally uploading file assets.
|
- `publish`: Publish a Gitea release, optionally uploading file assets.
|
||||||
- `addChannel`: Update a Gitea release's pre-release field.
|
- `addChannel`: Update a Gitea release's pre-release field.
|
||||||
- [semantic-release-replace-plugin](https://github.com/jpoehnelt/semantic-release-replace-plugin)
|
- [@google/semantic-release-replace-plugin](https://github.com/google/semantic-release-replace-plugin)
|
||||||
- `prepare`: Replace version strings in files using regex and glob.
|
- `prepare`: Replace version strings in files using regex and glob.
|
||||||
- [semantic-release-rubygem](https://github.com/Gusto/semantic-release-rubygem)
|
- [semantic-release-rubygem](https://github.com/Gusto/semantic-release-rubygem)
|
||||||
- `verifyConditions`: Locate and validate a `.gemspec` file, locate and validate a `lib/**/version.rb` file, verify the presence of the `GEM_HOST_API_KEY` environment variable, and create a credentials file with the API key.
|
- `verifyConditions`: Locate and validate a `.gemspec` file, locate and validate a `lib/**/version.rb` file, verify the presence of the `GEM_HOST_API_KEY` environment variable, and create a credentials file with the API key.
|
||||||
@ -176,21 +171,3 @@
|
|||||||
- `verifyConditions` Validate configuration, `Cargo.toml`, and local cargo executable. Also, logs in into `crates.io`.
|
- `verifyConditions` Validate configuration, `Cargo.toml`, and local cargo executable. Also, logs in into `crates.io`.
|
||||||
- `prepare` Write the new version number into `Cargo.toml` file and perform `cargo check` if configured.
|
- `prepare` Write the new version number into `Cargo.toml` file and perform `cargo check` if configured.
|
||||||
- `publish` Publish the Rust crate to `crates.io`
|
- `publish` Publish the Rust crate to `crates.io`
|
||||||
- [semantic-release-coralogix](https://github.com/adobe/semantic-release-coralogix)
|
|
||||||
- `verifyConditions` Verified that required credentials are provided and API is accessible
|
|
||||||
- `publish` add a release tag to Coralogix
|
|
||||||
- [semantic-release-jira-notes](https://github.com/iamludal/semantic-release-jira-notes)
|
|
||||||
- `verifyConditions`: Validate the config options.
|
|
||||||
- `generateNotes`: Generate the release notes with links to JIRA issues.
|
|
||||||
- [semantic-release-major-tag](https://github.com/doteric/semantic-release-major-tag)
|
|
||||||
- `success` Create major version tag, for example `v1`.
|
|
||||||
- [semantic-release-yarn](https://github.com/hongaar/semantic-release-yarn)
|
|
||||||
- **Note**: this is an alternative to the default `@semantic-release/npm` plugin and adds support for monorepos.
|
|
||||||
- `verifyConditions` Verify Yarn 2 or higher is installed, verify the presence of a NPM auth token (either in an environment variable or a `.yarnrc.yml` file) and verify the authentication method is valid.
|
|
||||||
- `prepare` Update the `package.json` version and create the package tarball.
|
|
||||||
- `addChannel` Add a tag for the release.
|
|
||||||
- `publish` Publish to the npm registry.
|
|
||||||
- [semantic-release-pub](https://github.com/zeshuaro/semantic-release-pub)
|
|
||||||
- `verifyConditions`: Verify the presence of the `pub.dev` authentication and release configuration
|
|
||||||
- `prepare`: Update the `pubspec.yaml` version
|
|
||||||
- `publish`: Publish the package onto the `pub.dev` registry
|
|
||||||
|
@ -6,11 +6,6 @@ The [Authentication](../../usage/ci-configuration.md#authentication) environment
|
|||||||
|
|
||||||
In this example a publish type [`NPM_TOKEN`](https://docs.npmjs.com/creating-and-viewing-authentication-tokens) is required to publish a package to the npm registry. GitHub Actions [automatically populate](https://help.github.com/en/articles/virtual-environments-for-github-actions#github_token-secret) a [`GITHUB_TOKEN`](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) environment variable which can be used in Workflows.
|
In this example a publish type [`NPM_TOKEN`](https://docs.npmjs.com/creating-and-viewing-authentication-tokens) is required to publish a package to the npm registry. GitHub Actions [automatically populate](https://help.github.com/en/articles/virtual-environments-for-github-actions#github_token-secret) a [`GITHUB_TOKEN`](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) environment variable which can be used in Workflows.
|
||||||
|
|
||||||
## npm provenance
|
|
||||||
|
|
||||||
Since GitHub Actions is a [supported provider](https://docs.npmjs.com/generating-provenance-statements#provenance-limitations) for [npm provenance](https://docs.npmjs.com/generating-provenance-statements), it is recommended to enable this to increase supply-chain security for your npm packages.
|
|
||||||
Find more detail about configuring npm to publish with provenance through semantic-release [in the documentation for our npm plugin](https://github.com/semantic-release/npm#npm-provenance).
|
|
||||||
|
|
||||||
## Node project configuration
|
## Node project configuration
|
||||||
|
|
||||||
[GitHub Actions](https://github.com/features/actions) support [Workflows](https://help.github.com/en/articles/configuring-workflows), allowing to run tests on multiple Node versions and publish a release only when all test pass.
|
[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.
|
||||||
@ -28,32 +23,21 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read # for checkout
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: write # to be able to publish a GitHub release
|
|
||||||
issues: write # to be able to comment on released issues
|
|
||||||
pull-requests: write # to be able to comment on released pull requests
|
|
||||||
id-token: write # to enable use of OIDC for npm provenance
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm clean-install
|
run: npm ci
|
||||||
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
|
|
||||||
run: npm audit signatures
|
|
||||||
- name: Release
|
- name: Release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
@ -6,11 +6,6 @@ The [Authentication](../../usage/ci-configuration.md#authentication) environment
|
|||||||
|
|
||||||
**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.
|
**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.
|
||||||
|
|
||||||
## npm provenance
|
|
||||||
|
|
||||||
Since GitLab CI is a [supported provider](https://docs.npmjs.com/generating-provenance-statements#provenance-limitations) for [npm provenance](https://docs.npmjs.com/generating-provenance-statements), it is recommended to enable this to increase supply-chain security for your npm packages.
|
|
||||||
Find more detail about configuring npm to publish with provenance through semantic-release [in the documentation for our npm plugin](https://github.com/semantic-release/npm#npm-provenance).
|
|
||||||
|
|
||||||
## Node project configuration
|
## 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.
|
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.
|
||||||
|
@ -62,7 +62,7 @@ The Git history of the repository is now:
|
|||||||
|
|
||||||
We now decide to work on another future major release, in parallel of the beta one, which will also be composed of multiple features, some of them being breaking changes.
|
We now decide to work on another future major release, in parallel of the beta one, which will also be composed of multiple features, some of them being breaking changes.
|
||||||
|
|
||||||
To implement that workflow we can create the branch `alpha` from the branch `beta` and commit our first feature there. When pushing that commit, **semantic-release** will publish the pre-release version `3.0.0-alpha.1` on the dist-tag `@alpha`. That allow us to run integration tests by installing our module with `npm install example-module@alpha`. Other users installing with `npm install example-module` will still receive the version `1.0.1`.
|
To implement that workflow we can create the branch `alpha` from the branch `beta` and commit our first feature there. When pushing that commit, **semantic-release** will publish the pre-release version `3.0.0-alpha.1` on the dist-tag `@alpha`. That allow us to run integration tests by installing our module with `npm install example-module@alpha`. Other users installing with `npm install example-module` will still receive the version `1.0.0`.
|
||||||
|
|
||||||
The Git history of the repository is now:
|
The Git history of the repository is now:
|
||||||
|
|
||||||
|
@ -158,6 +158,14 @@ Or with the `publishConfig.access` key in your project's `package.json`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Can I use semantic-release to publish a package on Artifactory?
|
||||||
|
|
||||||
|
Any npm compatible registry is supported with the [`@semantic-release/npm`](https://github.com/semantic-release/npm) plugin. For Artifactory versions prior to 5.4, the legacy authentication has to be used (with `NPM_USERNAME`, `NPM_PASSWORD` and `NPM_EMAIL` [environment variables](https://github.com/semantic-release/npm#environment-variables)).
|
||||||
|
|
||||||
|
See [npm registry authentication](https://github.com/semantic-release/npm#npm-registry-authentication) for more details.
|
||||||
|
|
||||||
|
See [Artifactory - npm Registry](https://www.jfrog.com/confluence/display/RTF/Npm+Registry#NpmRegistry-AuthenticatingthenpmClient) documentation for Artifactory configuration.
|
||||||
|
|
||||||
## Can I manually trigger the release of a specific version?
|
## Can I manually trigger the release of a specific version?
|
||||||
|
|
||||||
You can trigger a release by pushing to your Git repository. You deliberately cannot trigger a _specific_ version release, because this is the whole point of semantic-release.
|
You can trigger a release by pushing to your Git repository. You deliberately cannot trigger a _specific_ version release, because this is the whole point of semantic-release.
|
||||||
@ -195,7 +203,7 @@ If you need more control over the timing of releases, see [Triggering a release]
|
|||||||
|
|
||||||
## Can I set the initial release version of my package to `0.0.1`?
|
## Can I set the initial release version of my package to `0.0.1`?
|
||||||
|
|
||||||
This is not supported by semantic-release. [Semantic Versioning](https://semver.org/) rules apply differently to major version zero and supporting those differences is out of scope and not one of the goals of the semantic-release project.
|
This is not supported by **semantic-release** as it's not considered a good practice, mostly because [Semantic Versioning](https://semver.org) rules applies differently to major version zero.
|
||||||
|
|
||||||
If your project is under heavy development, with frequent breaking changes, and is not production ready yet we recommend [publishing pre-releases](../recipes/release-workflow/pre-releases.md#publishing-pre-releases).
|
If your project is under heavy development, with frequent breaking changes, and is not production ready yet we recommend [publishing pre-releases](../recipes/release-workflow/pre-releases.md#publishing-pre-releases).
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Node version requirement
|
# 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 Node version 20.8.1 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 18.0.0 or higher**.
|
||||||
|
|
||||||
**semantic-release** is meant to be used in a CI environment as a development support tool, not as a production dependency.
|
**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 version of Node that meets our version requirement.
|
Therefore, the only constraint is to run the `semantic-release` in a CI environment providing version of Node that meets our version requirement.
|
||||||
@ -14,7 +14,7 @@ See our [Node Support Policy](node-support-policy.md) for our long-term promise
|
|||||||
The recommended approach is to run the `semantic-release` command from a CI job running on the latest available LTS version of node.
|
The recommended approach is to run the `semantic-release` command from a CI job running on the latest available LTS version of node.
|
||||||
This can either be a job used by your project to test on the latest Node LTS version or a dedicated job for the release steps.
|
This can either be a job used by your project to test on the latest Node LTS version or a dedicated job for the release steps.
|
||||||
|
|
||||||
See [CI configuration](../usage/ci-configuration.md) and [CI configuration recipes](../recipes/ci-configurations/README.md#ci-configurations) for more details.
|
See [CI configuration](../usage/ci-configuration.md) and [CI configuration recipes](../recipes/release-workflow/README.md#ci-configurations) for more details.
|
||||||
|
|
||||||
## Alternative solutions
|
## Alternative solutions
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ See [CI configuration](../usage/ci-configuration.md) and [CI configuration recip
|
|||||||
Use it to execute the `semantic-release` command.
|
Use it to execute the `semantic-release` command.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npx -p node@v18-lts -c "npx semantic-release"
|
$ npx -p node@lts -c "npx semantic-release"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: See [What is npx](./FAQ.md#what-is-npx) for more details.
|
**Note**: See [What is npx](./FAQ.md#what-is-npx) for more details.
|
||||||
|
@ -14,7 +14,7 @@ Here are a few examples of the CI services that can be used to achieve this:
|
|||||||
- [Wercker Workflows](http://devcenter.wercker.com/docs/workflows)
|
- [Wercker Workflows](http://devcenter.wercker.com/docs/workflows)
|
||||||
- [GoCD Pipelines](https://docs.gocd.org/current/introduction/concepts_in_go.html#pipeline).
|
- [GoCD Pipelines](https://docs.gocd.org/current/introduction/concepts_in_go.html#pipeline).
|
||||||
|
|
||||||
See [CI configuration recipes](../recipes/ci-configurations/README.md) for more details.
|
See [CI configuration recipes](../recipes/ci-configurations#ci-configurations) for more details.
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
@ -45,6 +45,6 @@ See each plugin's documentation for the environment variables required.
|
|||||||
|
|
||||||
The authentication token/credentials have to be made available in the CI service via environment variables.
|
The authentication token/credentials have to be made available in the CI service via environment variables.
|
||||||
|
|
||||||
See [CI configuration recipes](../recipes/ci-configurations/README.md) for more details on how to configure environment variables in your CI service.
|
See [CI configuration recipes](../recipes/ci-configurations#ci-configurations) for more details on how to configure environment variables in your CI service.
|
||||||
|
|
||||||
**Note**: The environment variables `GH_TOKEN`, `GITHUB_TOKEN`, `GL_TOKEN` and `GITLAB_TOKEN` can be used for both the Git authentication and the API authentication required by [@semantic-release/github](https://github.com/semantic-release/github) and [@semantic-release/gitlab](https://github.com/semantic-release/gitlab).
|
**Note**: The environment variables `GH_TOKEN`, `GITHUB_TOKEN`, `GL_TOKEN` and `GITLAB_TOKEN` can be used for both the Git authentication and the API authentication required by [@semantic-release/github](https://github.com/semantic-release/github) and [@semantic-release/gitlab](https://github.com/semantic-release/gitlab).
|
||||||
|
@ -40,17 +40,6 @@ The following three examples are the same.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Via `release.config.cjs` file:
|
|
||||||
|
|
||||||
```js
|
|
||||||
/**
|
|
||||||
* @type {import('semantic-release').GlobalConfig}
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
branches: ["master", "next"],
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
- Via CLI argument:
|
- Via CLI argument:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -6,3 +6,16 @@ In order to use **semantic-release** you must follow these steps:
|
|||||||
2. Configure your Continuous Integration service to [run **semantic-release**](./ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded)
|
2. Configure your Continuous Integration service to [run **semantic-release**](./ci-configuration.md#run-semantic-release-only-after-all-tests-succeeded)
|
||||||
3. Configure your Git repository and package manager repository [authentication](ci-configuration.md#authentication) in your Continuous Integration service
|
3. Configure your Git repository and package manager repository [authentication](ci-configuration.md#authentication) in your Continuous Integration service
|
||||||
4. Configure **semantic-release** [options and plugins](./configuration.md#configuration)
|
4. Configure **semantic-release** [options and plugins](./configuration.md#configuration)
|
||||||
|
|
||||||
|
Alternatively those steps can be easily done with the [**semantic-release** interactive CLI](https://github.com/semantic-release/cli):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd your-module
|
||||||
|
npx semantic-release-cli setup
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
See the [semantic-release-cli](https://github.com/semantic-release/cli#what-it-does) documentation for more details.
|
||||||
|
|
||||||
|
**Note**: only a limited number of options, CI services and plugins are currently supported by `semantic-release-cli`.
|
||||||
|
@ -24,28 +24,9 @@ For other type of projects we recommend installing **semantic-release** directly
|
|||||||
$ npx semantic-release
|
$ npx semantic-release
|
||||||
```
|
```
|
||||||
|
|
||||||
### Notes
|
**Note**: For a global installation, it's recommended to specify the major **semantic-release** version to install (for example with `npx semantic-release@18`).
|
||||||
|
This way your build will not automatically use the next major **semantic-release** release that could possibly break your build.
|
||||||
|
You will have to upgrade manually when a new major version is released.
|
||||||
|
|
||||||
1. If you've globally installed **semantic-release** then we recommend that you set the major **semantic-release** version to install.
|
**Note**: `npx` is a tool bundled with `npm@>=5.2.0`. It is used to conveniently install the semantic-release binary and to execute it.
|
||||||
For example, by using `npx semantic-release@18`.
|
See [What is npx](../support/FAQ.md#what-is-npx) for more details.
|
||||||
This way you control which major version of **semantic-release** is used by your build, and thus avoid breaking the build when there's a new major version of **semantic-release**.
|
|
||||||
This also means you, or a bot, must upgrade **semantic-release** when a new major version is released.
|
|
||||||
2. Pinning **semantic-release** to an exact version makes your releases even more deterministic.
|
|
||||||
But pinning also means you, or a bot, must update to newer versions of **semantic-release** more often.
|
|
||||||
3. You can use [Renovate's regex manager](https://docs.renovatebot.com/modules/manager/regex/) to get automatic updates for **semantic-release** in either of the above scenarios.
|
|
||||||
Put this in your Renovate configuration file:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"regexManagers": [
|
|
||||||
{
|
|
||||||
"description": "Update semantic-release version used by npx",
|
|
||||||
"fileMatch": ["^\\.github/workflows/[^/]+\\.ya?ml$"],
|
|
||||||
"matchStrings": ["\\srun: npx semantic-release@(?<currentValue>.*?)\\s"],
|
|
||||||
"datasourceTemplate": "npm",
|
|
||||||
"depNameTemplate": "semantic-release"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
4. `npx` is a tool bundled with `npm@>=5.2.0`. You can use it to install (and run) the **semantic-release** binary.
|
|
||||||
See [What is npx](../support/FAQ.md#what-is-npx) for more details.
|
|
||||||
|
@ -12,7 +12,7 @@ See [Release workflow recipes](../recipes/release-workflow/README.md#release-wor
|
|||||||
The release workflow is configured via the [branches option](./configuration.md#branches) which accepts a single or an array of branch definitions.
|
The release workflow is configured via the [branches option](./configuration.md#branches) which accepts a single or an array of branch definitions.
|
||||||
Each branch can be defined either as a string, a [glob](https://github.com/micromatch/micromatch#matching-features) or an object. For string and glob definitions each [property](#branches-properties) will be defaulted.
|
Each branch can be defined either as a string, a [glob](https://github.com/micromatch/micromatch#matching-features) or an object. For string and glob definitions each [property](#branches-properties) will be defaulted.
|
||||||
|
|
||||||
A branch can be defined as one of three types:
|
A branch can defined as one of three types:
|
||||||
|
|
||||||
- [release](#release-branches): to make releases on top of the last version released
|
- [release](#release-branches): to make releases on top of the last version released
|
||||||
- [maintenance](#maintenance-branches): to make releases on top of an old release
|
- [maintenance](#maintenance-branches): to make releases on top of an old release
|
||||||
|
711
index.d.ts
vendored
711
index.d.ts
vendored
@ -1,711 +0,0 @@
|
|||||||
declare interface AggregateError extends Error {
|
|
||||||
errors: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "semantic-release" {
|
|
||||||
import { Signale } from "signale";
|
|
||||||
export interface EnvCi {
|
|
||||||
/**
|
|
||||||
* Boolean, true if the environment is a CI environment
|
|
||||||
*/
|
|
||||||
isCi: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit hash
|
|
||||||
*/
|
|
||||||
commit: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current branch name
|
|
||||||
*/
|
|
||||||
branch: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base context used in every semantic release step.
|
|
||||||
*/
|
|
||||||
export interface BaseContext {
|
|
||||||
/**
|
|
||||||
* stdout for semantic-release process
|
|
||||||
*/
|
|
||||||
stdout: NodeJS.WriteStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* stderr for semantic-release process
|
|
||||||
*/
|
|
||||||
stderr: NodeJS.WriteStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signale console loger instance.
|
|
||||||
*
|
|
||||||
* Has error, log and success methods.
|
|
||||||
*/
|
|
||||||
logger: Signale<"error" | "log" | "success">;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context used for the verify conditions step.
|
|
||||||
*/
|
|
||||||
export interface VerifyConditionsContext extends BaseContext {
|
|
||||||
/**
|
|
||||||
* The current working directory to use. It should be configured to
|
|
||||||
* the root of the Git repository to release from.
|
|
||||||
*
|
|
||||||
* It allows to run semantic-release from a specific path without
|
|
||||||
* having to change the local process cwd with process.chdir().
|
|
||||||
*
|
|
||||||
* @default process.cwd
|
|
||||||
*/
|
|
||||||
cwd?: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The environment variables to use.
|
|
||||||
*
|
|
||||||
* It allows to run semantic-release with specific environment
|
|
||||||
* variables without having to modify the local process.env.
|
|
||||||
*
|
|
||||||
* @default process.env
|
|
||||||
*/
|
|
||||||
env: Record<string, string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information about the CI environment.
|
|
||||||
*/
|
|
||||||
envCi: EnvCi;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information of the current branch
|
|
||||||
*/
|
|
||||||
branch: BranchObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information on branches
|
|
||||||
*/
|
|
||||||
branches: ReadonlyArray<BranchObject>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context used for the analyze commits step.
|
|
||||||
*/
|
|
||||||
export interface AnalyzeCommitsContext extends VerifyConditionsContext {
|
|
||||||
/**
|
|
||||||
* List of commits taken into account when determining the new version.
|
|
||||||
*/
|
|
||||||
commits: ReadonlyArray<Commit>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of releases
|
|
||||||
*/
|
|
||||||
releases: ReadonlyArray<Release>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Last release
|
|
||||||
*/
|
|
||||||
lastRelease: LastRelease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context used for the verify release step.
|
|
||||||
*/
|
|
||||||
export interface VerifyReleaseContext extends AnalyzeCommitsContext {
|
|
||||||
/**
|
|
||||||
* The next release.
|
|
||||||
*/
|
|
||||||
nextRelease: NextRelease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context used for the generate notes step.
|
|
||||||
*/
|
|
||||||
export type GenerateNotesContext = VerifyReleaseContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context used for the add channel step.
|
|
||||||
*/
|
|
||||||
export type AddChannelContext = VerifyReleaseContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context used for the prepare step.
|
|
||||||
*/
|
|
||||||
export type PrepareContext = VerifyReleaseContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context used for the publish step.
|
|
||||||
*/
|
|
||||||
export type PublishContext = VerifyReleaseContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context used for the success step.
|
|
||||||
*/
|
|
||||||
export type SuccessContext = VerifyReleaseContext;
|
|
||||||
|
|
||||||
export interface FailContext extends BaseContext {
|
|
||||||
/**
|
|
||||||
* Errors that occurred during the release process.
|
|
||||||
*/
|
|
||||||
errors: AggregateError;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Commit {
|
|
||||||
/**
|
|
||||||
* The commit abbreviated and full hash.
|
|
||||||
*/
|
|
||||||
commit: {
|
|
||||||
/**
|
|
||||||
* The commit hash.
|
|
||||||
*/
|
|
||||||
long: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit abbreviated hash.
|
|
||||||
*/
|
|
||||||
short: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit abbreviated and full tree hash.
|
|
||||||
*/
|
|
||||||
tree: {
|
|
||||||
/**
|
|
||||||
* The commit tree hash.
|
|
||||||
*/
|
|
||||||
long: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit abbreviated tree hash.
|
|
||||||
*/
|
|
||||||
short: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit author information.
|
|
||||||
*/
|
|
||||||
author: {
|
|
||||||
/**
|
|
||||||
* The commit author name.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit author email.
|
|
||||||
*/
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit author date.
|
|
||||||
*/
|
|
||||||
short: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The committer information.
|
|
||||||
*/
|
|
||||||
committer: {
|
|
||||||
/**
|
|
||||||
* The committer name.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The committer email.
|
|
||||||
*/
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The committer date.
|
|
||||||
*/
|
|
||||||
short: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit subject.
|
|
||||||
*/
|
|
||||||
subject: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit body.
|
|
||||||
*/
|
|
||||||
body: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit full message (subject and body).
|
|
||||||
*/
|
|
||||||
message: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The commit hash.
|
|
||||||
*/
|
|
||||||
hash: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The committer date.
|
|
||||||
*/
|
|
||||||
committerDate: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BranchObject {
|
|
||||||
/**
|
|
||||||
* The name of git branch.
|
|
||||||
*
|
|
||||||
* A `name` is required for all types of branch. It can be defined as a
|
|
||||||
* [glob](https://github.com/micromatch/micromatch#matching-features)
|
|
||||||
* in which case the definition will be expanded to one per matching
|
|
||||||
* branch existing in the repository.
|
|
||||||
*
|
|
||||||
* If `name` doesn't match any branch existing in the repository, the
|
|
||||||
* definition will be ignored. For example, the default configuration
|
|
||||||
* includes the definition `next` and `next-major` which will become
|
|
||||||
* active only when the branches `next` and/or `next-major` are
|
|
||||||
* created in the repository.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The distribution channel on which to publish releases from this
|
|
||||||
* branch.
|
|
||||||
*
|
|
||||||
* If this field is set to `false`, then the branch will be released
|
|
||||||
* on the default distribution channel (for example the `@latest`
|
|
||||||
* [dist-tag](https://docs.npmjs.com/cli/dist-tag) for npm). This is
|
|
||||||
* also the default behavior for the first
|
|
||||||
* [release branch](https://semantic-release.gitbook.io/semantic-release/usage/workflow-configuration#release-branches)
|
|
||||||
* if the channel property is not set.
|
|
||||||
*
|
|
||||||
* For all other branches, if the channel property is not set, then the
|
|
||||||
* channel name will be the same as the branch name.
|
|
||||||
*
|
|
||||||
* The value of `channel`, if defined as a string, is generated with
|
|
||||||
* [Lodash template](https://lodash.com/docs#template) with the
|
|
||||||
* variable `name` set to the branch name.
|
|
||||||
*
|
|
||||||
* For example `{name: 'next', channel: 'channel-${name}'}` will be
|
|
||||||
* expanded to `{name: 'next', channel: 'channel-next'}`.
|
|
||||||
*/
|
|
||||||
channel?: string | false | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The range of [semantic versions](https://semver.org/) to support on
|
|
||||||
* this branch.
|
|
||||||
*
|
|
||||||
* A `range` only applies to maintenance branches and must be formatted
|
|
||||||
* like `N.N.x` or `N.x` (`N` is a number). If no range is specified
|
|
||||||
* but the `name` is formatted as a range, then the branch will be
|
|
||||||
* considered a maintenance branch and the `name` value will be used
|
|
||||||
* for the `range`.
|
|
||||||
*
|
|
||||||
* Required for maintenance branches, unless `name` is formatted like
|
|
||||||
* `N.N.x` or `N.x` (`N` is a number).
|
|
||||||
*/
|
|
||||||
range?: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The pre-release identifier to append to [semantic versions](https://semver.org/)
|
|
||||||
* released from this branch.
|
|
||||||
*
|
|
||||||
* A `prerelease` property applies only to pre-release branches and
|
|
||||||
* the `prerelease` value must be valid per the [Semantic Versioning
|
|
||||||
* Specification](https://semver.org/#spec-item-9). It will determine
|
|
||||||
* the name of versions. For example if `prerelease` is set to
|
|
||||||
* `"beta"`, the version will be formatted like `2.0.0-beta.1`,
|
|
||||||
* `2.0.0-beta.2`, etc.
|
|
||||||
*
|
|
||||||
* The value of `prerelease`, if defined as a string, is generated with
|
|
||||||
* [Lodash template](https://lodash.com/docs#template) with the
|
|
||||||
* variable `name` set to the name of the branch.
|
|
||||||
*
|
|
||||||
* If the `prerelease property is set to `true` then the name of the
|
|
||||||
* branch is used as the pre-release identifier.
|
|
||||||
*
|
|
||||||
* Required for pre-release branches.
|
|
||||||
*/
|
|
||||||
prerelease?: string | boolean | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies a git branch holding commits to analyze and code to release.
|
|
||||||
*
|
|
||||||
* Each branch may be defined either by a string or an object. Specifying
|
|
||||||
* a string is a shortcut for specifying that string as the `name` field,
|
|
||||||
* for example `"master"` expands to `{name: "master"}`.
|
|
||||||
*/
|
|
||||||
export type BranchSpec = string | BranchObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A semver release type.
|
|
||||||
* See https://github.com/semantic-release/commit-analyzer/blob/master/lib/default-release-types.js
|
|
||||||
*/
|
|
||||||
export type ReleaseType = "prerelease" | "prepatch" | "patch" | "preminor" | "minor" | "premajor" | "major";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Details of a release published by a publish plugin.
|
|
||||||
*/
|
|
||||||
export interface Release {
|
|
||||||
/**
|
|
||||||
* The release name, only if set by the corresponding publish plugin.
|
|
||||||
*/
|
|
||||||
name?: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The release URL, only if set by the corresponding publish plugin.
|
|
||||||
*/
|
|
||||||
url?: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The semver export type of the release.
|
|
||||||
*/
|
|
||||||
type: ReleaseType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The version of the release.
|
|
||||||
*/
|
|
||||||
version: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The sha of the last commit being part of the release.
|
|
||||||
*/
|
|
||||||
gitHead: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Git tag associated with the release.
|
|
||||||
*/
|
|
||||||
gitTag: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The release notes for the release.
|
|
||||||
*/
|
|
||||||
notes: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the plugin that published the release.
|
|
||||||
*/
|
|
||||||
pluginName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LastRelease {
|
|
||||||
/**
|
|
||||||
* The version name of the release.
|
|
||||||
*/
|
|
||||||
version: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Git tag of the release.
|
|
||||||
*/
|
|
||||||
gitTag: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of channels the release was published to.
|
|
||||||
*/
|
|
||||||
channels: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Git checksum of the last commit of the release.
|
|
||||||
*/
|
|
||||||
gitHead: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Release name
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NextRelease extends Omit<LastRelease, "channels"> {
|
|
||||||
/**
|
|
||||||
* The semver export type of the release.
|
|
||||||
*/
|
|
||||||
type: ReleaseType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The release channel of the release.
|
|
||||||
*/
|
|
||||||
channel: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The release notes of the next release.
|
|
||||||
*/
|
|
||||||
notes?: string | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies a plugin to use.
|
|
||||||
*
|
|
||||||
* The plugin is specified by its module name.
|
|
||||||
*
|
|
||||||
* To pass options to a plugin, specify an array containing the plugin module
|
|
||||||
* name and an options object.
|
|
||||||
*/
|
|
||||||
export type PluginSpec<T = any> = string | [string, T];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* semantic-release options, after normalization and defaults have been
|
|
||||||
* applied.
|
|
||||||
*/
|
|
||||||
export interface GlobalConfig extends Options {
|
|
||||||
/**
|
|
||||||
* The branches on which releases should happen. By default
|
|
||||||
* **semantic-release** will release:
|
|
||||||
*
|
|
||||||
* * regular releases to the default distribution channel from the
|
|
||||||
* branch `master`
|
|
||||||
* * regular releases to a distribution channel matching the branch
|
|
||||||
* name from any existing branch with a name matching a maintenance
|
|
||||||
* release range (`N.N.x` or `N.x.x` or `N.x` with `N` being a
|
|
||||||
* number)
|
|
||||||
* * regular releases to the `next` distribution channel from the
|
|
||||||
* branch `next` if it exists
|
|
||||||
* * regular releases to the `next-major` distribution channel from
|
|
||||||
* the branch `next-major` if it exists.
|
|
||||||
* * prereleases to the `beta` distribution channel from the branch
|
|
||||||
* `beta` if it exists
|
|
||||||
* * prereleases to the `alpha` distribution channel from the branch
|
|
||||||
* `alpha` if it exists
|
|
||||||
*
|
|
||||||
* **Note**: If your repository does not have a release branch, then
|
|
||||||
* **semantic-release** will fail with an `ERELEASEBRANCHES` error
|
|
||||||
* message. If you are using the default configuration, you can fix
|
|
||||||
* this error by pushing a `master` branch.
|
|
||||||
*
|
|
||||||
* **Note**: Once **semantic-release** is configured, any user with the
|
|
||||||
* permission to push commits on one of those branches will be able to
|
|
||||||
* publish a release. It is recommended to protect those branches, for
|
|
||||||
* example with [GitHub protected branches](https://help.github.com/articles/about-protected-branches).
|
|
||||||
*
|
|
||||||
* See [Workflow configuration](https://semantic-release.gitbook.io/semantic-release/usage/workflow-configuration#workflow-configuration)
|
|
||||||
* for more details.
|
|
||||||
*/
|
|
||||||
branches: ReadonlyArray<BranchSpec> | BranchSpec;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The git repository URL.
|
|
||||||
*
|
|
||||||
* Any valid git url format is supported (see
|
|
||||||
* [git protocols](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols))
|
|
||||||
*
|
|
||||||
* Default: `repository` property in `package.json`, or git origin url.
|
|
||||||
*/
|
|
||||||
repositoryUrl: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The git tag format used by **semantic-release** to identify
|
|
||||||
* releases. The tag name is generated with [Lodash template](https://lodash.com/docs#template)
|
|
||||||
* and will be compiled with the `version` variable.
|
|
||||||
*
|
|
||||||
* **Note**: The `tagFormat` must contain the `version` variable
|
|
||||||
* exactly once and compile to a
|
|
||||||
* [valid git reference](https://git-scm.com/docs/git-check-ref-format#_description).
|
|
||||||
*/
|
|
||||||
tagFormat: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the list of plugins to use. Plugins will run in series, in
|
|
||||||
* the order defined, for each [step](https://semantic-release.gitbook.io/semantic-release/#release-steps)
|
|
||||||
* if they implement it.
|
|
||||||
*
|
|
||||||
* Plugins configuration can be defined by wrapping the name and an
|
|
||||||
* options object in an array.
|
|
||||||
*
|
|
||||||
* See [Plugins configuration](https://semantic-release.gitbook.io/semantic-release/usage/plugins#plugins)
|
|
||||||
* for more details.
|
|
||||||
*
|
|
||||||
* Default: `[
|
|
||||||
* "@semantic-release/commit-analyzer",
|
|
||||||
* "@semantic-release/release-notes-generator",
|
|
||||||
* "@semantic-release/npm",
|
|
||||||
* "@semantic-release/github"
|
|
||||||
* ]`
|
|
||||||
*/
|
|
||||||
plugins: ReadonlyArray<PluginSpec>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** semantic-release configuration specific for API usage. */
|
|
||||||
export interface Config {
|
|
||||||
/**
|
|
||||||
* The current working directory to use. It should be configured to
|
|
||||||
* the root of the Git repository to release from.
|
|
||||||
*
|
|
||||||
* It allows to run semantic-release from a specific path without
|
|
||||||
* having to change the local process cwd with process.chdir().
|
|
||||||
*
|
|
||||||
* @default process.cwd
|
|
||||||
*/
|
|
||||||
cwd?: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The environment variables to use.
|
|
||||||
*
|
|
||||||
* It allows to run semantic-release with specific environment
|
|
||||||
* variables without having to modify the local process.env.
|
|
||||||
*
|
|
||||||
* @default process.env
|
|
||||||
*/
|
|
||||||
env?: Record<string, any> | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The writable stream used to log information.
|
|
||||||
*
|
|
||||||
* It allows to configure semantic-release to write logs to a specific
|
|
||||||
* stream rather than the local process.stdout.
|
|
||||||
*
|
|
||||||
* @default process.stdout
|
|
||||||
*/
|
|
||||||
stdout?: NodeJS.WriteStream | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The writable stream used to log errors.
|
|
||||||
*
|
|
||||||
* It allows to configure semantic-release to write errors to a
|
|
||||||
* specific stream rather than the local process.stderr.
|
|
||||||
*
|
|
||||||
* @default process.stderr
|
|
||||||
*/
|
|
||||||
stderr?: NodeJS.WriteStream | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* semantic-release options.
|
|
||||||
*
|
|
||||||
* Can be used to set any core option or plugin options.
|
|
||||||
* Each option will take precedence over options configured in the
|
|
||||||
* configuration file and shareable configurations.
|
|
||||||
*/
|
|
||||||
export interface Options {
|
|
||||||
/**
|
|
||||||
* List of modules or file paths containing a
|
|
||||||
* [shareable configuration](https://semantic-release.gitbook.io/semantic-release/usage/shareable-configurations).
|
|
||||||
* If multiple shareable configurations are set, they will be imported
|
|
||||||
* in the order defined with each configuration option taking
|
|
||||||
* precedence over the options defined in a previous shareable
|
|
||||||
* configuration.
|
|
||||||
*
|
|
||||||
* **Note**: Options defined via CLI arguments or in the configuration
|
|
||||||
* file will take precedence over the ones defined in any shareable
|
|
||||||
* configuration.
|
|
||||||
*/
|
|
||||||
extends?: ReadonlyArray<string> | string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The branches on which releases should happen. By default
|
|
||||||
* **semantic-release** will release:
|
|
||||||
*
|
|
||||||
* * regular releases to the default distribution channel from the
|
|
||||||
* branch `master`
|
|
||||||
* * regular releases to a distribution channel matching the branch
|
|
||||||
* name from any existing branch with a name matching a maintenance
|
|
||||||
* release range (`N.N.x` or `N.x.x` or `N.x` with `N` being a
|
|
||||||
* number)
|
|
||||||
* * regular releases to the `next` distribution channel from the
|
|
||||||
* branch `next` if it exists
|
|
||||||
* * regular releases to the `next-major` distribution channel from
|
|
||||||
* the branch `next-major` if it exists.
|
|
||||||
* * prereleases to the `beta` distribution channel from the branch
|
|
||||||
* `beta` if it exists
|
|
||||||
* * prereleases to the `alpha` distribution channel from the branch
|
|
||||||
* `alpha` if it exists
|
|
||||||
*
|
|
||||||
* **Note**: If your repository does not have a release branch, then
|
|
||||||
* **semantic-release** will fail with an `ERELEASEBRANCHES` error
|
|
||||||
* message. If you are using the default configuration, you can fix
|
|
||||||
* this error by pushing a `master` branch.
|
|
||||||
*
|
|
||||||
* **Note**: Once **semantic-release** is configured, any user with the
|
|
||||||
* permission to push commits on one of those branches will be able to
|
|
||||||
* publish a release. It is recommended to protect those branches, for
|
|
||||||
* example with [GitHub protected branches](https://help.github.com/articles/about-protected-branches).
|
|
||||||
*
|
|
||||||
* See [Workflow configuration](https://semantic-release.gitbook.io/semantic-release/usage/workflow-configuration#workflow-configuration)
|
|
||||||
* for more details.
|
|
||||||
*/
|
|
||||||
branches?: ReadonlyArray<BranchSpec> | BranchSpec | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The git repository URL.
|
|
||||||
*
|
|
||||||
* Any valid git url format is supported (see
|
|
||||||
* [git protocols](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols))
|
|
||||||
*
|
|
||||||
* Default: `repository` property in `package.json`, or git origin url.
|
|
||||||
*/
|
|
||||||
repositoryUrl?: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The git tag format used by **semantic-release** to identify
|
|
||||||
* releases. The tag name is generated with [Lodash template](https://lodash.com/docs#template)
|
|
||||||
* and will be compiled with the `version` variable.
|
|
||||||
*
|
|
||||||
* **Note**: The `tagFormat` must contain the `version` variable
|
|
||||||
* exactly once and compile to a
|
|
||||||
* [valid git reference](https://git-scm.com/docs/git-check-ref-format#_description).
|
|
||||||
*/
|
|
||||||
tagFormat?: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the list of plugins to use. Plugins will run in series, in
|
|
||||||
* the order defined, for each [step](https://semantic-release.gitbook.io/semantic-release/#release-steps)
|
|
||||||
* if they implement it.
|
|
||||||
*
|
|
||||||
* Plugins configuration can be defined by wrapping the name and an
|
|
||||||
* options object in an array.
|
|
||||||
*
|
|
||||||
* See [Plugins configuration](https://semantic-release.gitbook.io/semantic-release/usage/plugins#plugins)
|
|
||||||
* for more details.
|
|
||||||
*
|
|
||||||
* Default: `[
|
|
||||||
* "@semantic-release/commit-analyzer",
|
|
||||||
* "@semantic-release/release-notes-generator",
|
|
||||||
* "@semantic-release/npm",
|
|
||||||
* "@semantic-release/github"
|
|
||||||
* ]`
|
|
||||||
*/
|
|
||||||
plugins?: ReadonlyArray<PluginSpec> | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dry-run mode, skip publishing, print next version and release notes.
|
|
||||||
*/
|
|
||||||
dryRun?: boolean | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set to false to skip Continuous Integration environment verifications.
|
|
||||||
* This allows for making releases from a local machine.
|
|
||||||
*/
|
|
||||||
ci?: boolean | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Any other options supported by plugins.
|
|
||||||
*/
|
|
||||||
[name: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object with details of the release if a release was published, or
|
|
||||||
* false if no release was published.
|
|
||||||
*/
|
|
||||||
export type Result =
|
|
||||||
| false
|
|
||||||
| {
|
|
||||||
/**
|
|
||||||
* Information related to the last release found.
|
|
||||||
*/
|
|
||||||
lastRelease: LastRelease;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of commits included in the new release.
|
|
||||||
*/
|
|
||||||
commits: Commit[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information related to the newly published release.
|
|
||||||
*/
|
|
||||||
nextRelease: NextRelease;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of releases published, one release per publish plugin.
|
|
||||||
*/
|
|
||||||
releases: Release[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run semantic-release and returns a Promise that resolves to a Result
|
|
||||||
* object.
|
|
||||||
* @async
|
|
||||||
*/
|
|
||||||
export default function (options: Options, environment?: Config): Promise<Result>;
|
|
||||||
}
|
|
11
index.js
11
index.js
@ -123,15 +123,12 @@ async function run(context, plugins) {
|
|||||||
if (options.dryRun) {
|
if (options.dryRun) {
|
||||||
logger.warn(`Skip ${nextRelease.gitTag} tag creation in dry-run mode`);
|
logger.warn(`Skip ${nextRelease.gitTag} tag creation in dry-run mode`);
|
||||||
} else {
|
} else {
|
||||||
await addNote({ channels: [...currentRelease.channels, nextRelease.channel] }, nextRelease.gitTag, {
|
await addNote({ channels: [...currentRelease.channels, nextRelease.channel] }, nextRelease.gitHead, {
|
||||||
cwd,
|
cwd,
|
||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
await push(options.repositoryUrl, { cwd, env });
|
await push(options.repositoryUrl, { cwd, env });
|
||||||
await pushNotes(options.repositoryUrl, nextRelease.gitTag, {
|
await pushNotes(options.repositoryUrl, { cwd, env });
|
||||||
cwd,
|
|
||||||
env,
|
|
||||||
});
|
|
||||||
logger.success(
|
logger.success(
|
||||||
`Add ${nextRelease.channel ? `channel ${nextRelease.channel}` : "default channel"} to tag ${
|
`Add ${nextRelease.channel ? `channel ${nextRelease.channel}` : "default channel"} to tag ${
|
||||||
nextRelease.gitTag
|
nextRelease.gitTag
|
||||||
@ -206,9 +203,9 @@ async function run(context, plugins) {
|
|||||||
} else {
|
} else {
|
||||||
// Create the tag before calling the publish plugins as some require the tag to exists
|
// Create the tag before calling the publish plugins as some require the tag to exists
|
||||||
await tag(nextRelease.gitTag, nextRelease.gitHead, { cwd, env });
|
await tag(nextRelease.gitTag, nextRelease.gitHead, { cwd, env });
|
||||||
await addNote({ channels: [nextRelease.channel] }, nextRelease.gitTag, { cwd, env });
|
await addNote({ channels: [nextRelease.channel] }, nextRelease.gitHead, { cwd, env });
|
||||||
await push(options.repositoryUrl, { cwd, env });
|
await push(options.repositoryUrl, { cwd, env });
|
||||||
await pushNotes(options.repositoryUrl, nextRelease.gitTag, { cwd, env });
|
await pushNotes(options.repositoryUrl, { cwd, env });
|
||||||
logger.success(`Created tag ${nextRelease.gitTag}`);
|
logger.success(`Created tag ${nextRelease.gitTag}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { isString, mapValues, omit, remove, template } from "lodash-es";
|
import {isString, mapValues, omit, remove, template} from 'lodash-es';
|
||||||
import micromatch from "micromatch";
|
import micromatch from 'micromatch';
|
||||||
import { getBranches } from "../git.js";
|
import {getBranches} from '../git.js';
|
||||||
|
|
||||||
export default async (repositoryUrl, { cwd }, branches) => {
|
export default async (repositoryUrl, {cwd}, branches) => {
|
||||||
const gitBranches = await getBranches(repositoryUrl, { cwd });
|
const gitBranches = await getBranches(repositoryUrl, {cwd});
|
||||||
|
|
||||||
return branches.reduce(
|
return branches.reduce(
|
||||||
(branches, branch) => [
|
(branches, branch) => [
|
||||||
...branches,
|
...branches,
|
||||||
...remove(gitBranches, (name) => micromatch(gitBranches, branch.name).includes(name)).map((name) => ({
|
...remove(gitBranches, (name) => micromatch(gitBranches, branch.name).includes(name)).map((name) => ({
|
||||||
name,
|
name,
|
||||||
...mapValues(omit(branch, "name"), (value) => (isString(value) ? template(value)({ name }) : value)),
|
...mapValues(omit(branch, 'name'), (value) => (isString(value) ? template(value)({name}) : value)),
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
@ -1,35 +1,36 @@
|
|||||||
import { escapeRegExp, template } from "lodash-es";
|
import {escapeRegExp, template} from 'lodash-es';
|
||||||
import semver from "semver";
|
import semver from 'semver';
|
||||||
import pReduce from "p-reduce";
|
import pReduce from 'p-reduce';
|
||||||
import debugTags from "debug";
|
import debugTags from 'debug';
|
||||||
import { getNote, getTags } from "../../lib/git.js";
|
import {getNote, getTags} from '../../lib/git.js';
|
||||||
|
|
||||||
const debug = debugTags("semantic-release:get-tags");
|
const debug = debugTags('semantic-release:get-tags');
|
||||||
|
|
||||||
export default async ({ cwd, env, options: { tagFormat } }, branches) => {
|
|
||||||
|
export default async ({cwd, env, options: {tagFormat}}, branches) => {
|
||||||
// Generate a regex to parse tags formatted with `tagFormat`
|
// Generate a regex to parse tags formatted with `tagFormat`
|
||||||
// by replacing the `version` variable in the template by `(.+)`.
|
// 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,
|
// 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`.
|
// 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(
|
return pReduce(
|
||||||
branches,
|
branches,
|
||||||
async (branches, branch) => {
|
async (branches, branch) => {
|
||||||
const branchTags = await pReduce(
|
const branchTags = await pReduce(
|
||||||
await getTags(branch.name, { cwd, env }),
|
await getTags(branch.name, {cwd, env}),
|
||||||
async (branchTags, tag) => {
|
async (branchTags, tag) => {
|
||||||
const [, version] = tag.match(tagRegexp) || [];
|
const [, version] = tag.match(tagRegexp) || [];
|
||||||
return version && semver.valid(semver.clean(version))
|
return version && semver.valid(semver.clean(version))
|
||||||
? [...branchTags, { gitTag: tag, version, channels: (await getNote(tag, { cwd, env })).channels || [null] }]
|
? [...branchTags, {gitTag: tag, version, channels: (await getNote(tag, {cwd, env})).channels || [null]}]
|
||||||
: branchTags;
|
: branchTags;
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
debug("found tags for branch %s: %o", branch.name, branchTags);
|
debug('found tags for branch %s: %o', branch.name, branchTags);
|
||||||
return [...branches, { ...branch, tags: branchTags }];
|
return [...branches, {...branch, tags: branchTags}];
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
@ -1,51 +1,51 @@
|
|||||||
import { isRegExp, isString } from "lodash-es";
|
import {isRegExp, isString} from 'lodash-es';
|
||||||
import AggregateError from "aggregate-error";
|
import AggregateError from 'aggregate-error';
|
||||||
import pEachSeries from "p-each-series";
|
import pEachSeries from 'p-each-series';
|
||||||
import * as DEFINITIONS from "../definitions/branches.js";
|
import * as DEFINITIONS from '../definitions/branches.js';
|
||||||
import getError from "../get-error.js";
|
import getError from '../get-error.js';
|
||||||
import { fetch, fetchNotes, verifyBranchName } from "../git.js";
|
import {fetch, fetchNotes, verifyBranchName} from '../git.js';
|
||||||
import expand from "./expand.js";
|
import expand from './expand.js';
|
||||||
import getTags from "./get-tags.js";
|
import getTags from './get-tags.js';
|
||||||
import * as normalize from "./normalize.js";
|
import * as normalize from './normalize.js';
|
||||||
|
|
||||||
export default async (repositoryUrl, ciBranch, context) => {
|
export default async (repositoryUrl, ciBranch, context) => {
|
||||||
const { cwd, env } = context;
|
const {cwd, env} = context;
|
||||||
|
|
||||||
const remoteBranches = await expand(
|
const remoteBranches = await expand(
|
||||||
repositoryUrl,
|
repositoryUrl,
|
||||||
context,
|
context,
|
||||||
context.options.branches.map((branch) => (isString(branch) || isRegExp(branch) ? { name: branch } : branch))
|
context.options.branches.map((branch) => (isString(branch) || isRegExp(branch) ? {name: branch} : branch))
|
||||||
);
|
);
|
||||||
|
|
||||||
await pEachSeries(remoteBranches, async ({ name }) => {
|
await pEachSeries(remoteBranches, async ({name}) => {
|
||||||
await fetch(repositoryUrl, name, ciBranch, { cwd, env });
|
await fetch(repositoryUrl, name, ciBranch, {cwd, env});
|
||||||
});
|
});
|
||||||
|
|
||||||
await fetchNotes(repositoryUrl, { cwd, env });
|
await fetchNotes(repositoryUrl, {cwd, env});
|
||||||
|
|
||||||
const branches = await getTags(context, remoteBranches);
|
const branches = await getTags(context, remoteBranches);
|
||||||
|
|
||||||
const errors = [];
|
const errors = [];
|
||||||
const branchesByType = Object.entries(DEFINITIONS).reduce(
|
const branchesByType = Object.entries(DEFINITIONS).reduce(
|
||||||
// eslint-disable-next-line unicorn/no-fn-reference-in-iterator
|
// eslint-disable-next-line unicorn/no-fn-reference-in-iterator
|
||||||
(branchesByType, [type, { filter }]) => ({ [type]: branches.filter(filter), ...branchesByType }),
|
(branchesByType, [type, {filter}]) => ({[type]: branches.filter(filter), ...branchesByType}),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = Object.entries(DEFINITIONS).reduce((result, [type, { branchesValidator, branchValidator }]) => {
|
const result = Object.entries(DEFINITIONS).reduce((result, [type, {branchesValidator, branchValidator}]) => {
|
||||||
branchesByType[type].forEach((branch) => {
|
branchesByType[type].forEach((branch) => {
|
||||||
if (branchValidator && !branchValidator(branch)) {
|
if (branchValidator && !branchValidator(branch)) {
|
||||||
errors.push(getError(`E${type.toUpperCase()}BRANCH`, { branch }));
|
errors.push(getError(`E${type.toUpperCase()}BRANCH`, {branch}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const branchesOfType = normalize[type](branchesByType);
|
const branchesOfType = normalize[type](branchesByType);
|
||||||
|
|
||||||
if (!branchesValidator(branchesOfType)) {
|
if (!branchesValidator(branchesOfType)) {
|
||||||
errors.push(getError(`E${type.toUpperCase()}BRANCHES`, { branches: branchesOfType }));
|
errors.push(getError(`E${type.toUpperCase()}BRANCHES`, {branches: branchesOfType}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...result, [type]: branchesOfType };
|
return {...result, [type]: branchesOfType};
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const duplicates = [...branches]
|
const duplicates = [...branches]
|
||||||
@ -54,12 +54,12 @@ export default async (repositoryUrl, ciBranch, context) => {
|
|||||||
.filter((_, idx, array) => array[idx] === array[idx + 1] && array[idx] !== array[idx - 1]);
|
.filter((_, idx, array) => array[idx] === array[idx + 1] && array[idx] !== array[idx - 1]);
|
||||||
|
|
||||||
if (duplicates.length > 0) {
|
if (duplicates.length > 0) {
|
||||||
errors.push(getError("EDUPLICATEBRANCHES", { duplicates }));
|
errors.push(getError('EDUPLICATEBRANCHES', {duplicates}));
|
||||||
}
|
}
|
||||||
|
|
||||||
await pEachSeries(branches, async (branch) => {
|
await pEachSeries(branches, async (branch) => {
|
||||||
if (!(await verifyBranchName(branch.name))) {
|
if (!(await verifyBranchName(branch.name))) {
|
||||||
errors.push(getError("EINVALIDBRANCHNAME", branch));
|
errors.push(getError('EINVALIDBRANCHNAME', branch));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -68,4 +68,4 @@ export default async (repositoryUrl, ciBranch, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [...result.maintenance, ...result.release, ...result.prerelease];
|
return [...result.maintenance, ...result.release, ...result.prerelease];
|
||||||
};
|
}
|
||||||
|
@ -1,28 +1,27 @@
|
|||||||
import { isNil, sortBy } from "lodash-es";
|
import {isNil, sortBy} from 'lodash-es';
|
||||||
import semverDiff from "semver-diff";
|
import semverDiff from 'semver-diff';
|
||||||
import { FIRST_RELEASE, RELEASE_TYPE } from "../definitions/constants.js";
|
import {FIRST_RELEASE, RELEASE_TYPE} from '../definitions/constants.js';
|
||||||
import {
|
import {
|
||||||
getFirstVersion,
|
getFirstVersion,
|
||||||
getLatestVersion,
|
getLatestVersion,
|
||||||
getLowerBound,
|
getLowerBound, getRange,
|
||||||
getRange,
|
|
||||||
getUpperBound,
|
getUpperBound,
|
||||||
highest,
|
highest,
|
||||||
isMajorRange,
|
isMajorRange,
|
||||||
lowest,
|
lowest,
|
||||||
tagsToVersions,
|
tagsToVersions
|
||||||
} from "../utils.js";
|
} from '../utils.js';
|
||||||
|
|
||||||
export function maintenance({ maintenance, release }) {
|
export function maintenance({maintenance, release}) {
|
||||||
return sortBy(
|
return sortBy(
|
||||||
maintenance.map(({ name, range, channel, ...rest }) => ({
|
maintenance.map(({name, range, channel, ...rest}) => ({
|
||||||
...rest,
|
...rest,
|
||||||
name,
|
name,
|
||||||
range: range || name,
|
range: range || name,
|
||||||
channel: isNil(channel) ? name : channel,
|
channel: isNil(channel) ? name : channel,
|
||||||
})),
|
})),
|
||||||
"range"
|
'range'
|
||||||
).map(({ name, range, tags, ...rest }, idx, branches) => {
|
).map(({name, range, tags, ...rest}, idx, branches) => {
|
||||||
const versions = tagsToVersions(tags);
|
const versions = tagsToVersions(tags);
|
||||||
// Find the lower bound based on Maintenance branches
|
// Find the lower bound based on Maintenance branches
|
||||||
const maintenanceMin =
|
const maintenanceMin =
|
||||||
@ -45,7 +44,7 @@ export function maintenance({ maintenance, release }) {
|
|||||||
const diff = semverDiff(min, max);
|
const diff = semverDiff(min, max);
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name,
|
name,
|
||||||
tags,
|
tags,
|
||||||
range: getRange(min, max),
|
range: getRange(min, max),
|
||||||
@ -55,15 +54,15 @@ export function maintenance({ maintenance, release }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function release({ release }) {
|
export function release({release}) {
|
||||||
if (release.length === 0) {
|
if (release.length === 0) {
|
||||||
return release;
|
return release;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The initial lastVersion is the last release from the base branch of `FIRST_RELEASE` (1.0.0)
|
// 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;
|
let lastVersion = getLatestVersion(tagsToVersions(release[0].tags)) || FIRST_RELEASE;
|
||||||
|
|
||||||
return release.map(({ name, tags, channel, ...rest }, idx) => {
|
return release.map(({name, tags, channel, ...rest}, idx) => {
|
||||||
const versions = tagsToVersions(tags);
|
const versions = tagsToVersions(tags);
|
||||||
// The new lastVersion is the highest version between the current branch last release and the previous branch lastVersion
|
// The new lastVersion is the highest version between the current branch last release and the previous branch lastVersion
|
||||||
lastVersion = highest(getLatestVersion(versions), lastVersion);
|
lastVersion = highest(getLatestVersion(versions), lastVersion);
|
||||||
@ -80,7 +79,7 @@ export function release({ release }) {
|
|||||||
...rest,
|
...rest,
|
||||||
channel: idx === 0 ? channel : isNil(channel) ? name : channel,
|
channel: idx === 0 ? channel : isNil(channel) ? name : channel,
|
||||||
tags,
|
tags,
|
||||||
type: "release",
|
type: 'release',
|
||||||
name,
|
name,
|
||||||
range: getRange(lastVersion, bound),
|
range: getRange(lastVersion, bound),
|
||||||
accept: bound ? RELEASE_TYPE.slice(0, RELEASE_TYPE.indexOf(diff)) : RELEASE_TYPE,
|
accept: bound ? RELEASE_TYPE.slice(0, RELEASE_TYPE.indexOf(diff)) : RELEASE_TYPE,
|
||||||
@ -89,13 +88,13 @@ export function release({ release }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prerelease({ prerelease }) {
|
export function prerelease({prerelease}) {
|
||||||
return prerelease.map(({ name, prerelease, channel, tags, ...rest }) => {
|
return prerelease.map(({name, prerelease, channel, tags, ...rest}) => {
|
||||||
const preid = prerelease === true ? name : prerelease;
|
const preid = prerelease === true ? name : prerelease;
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
channel: isNil(channel) ? name : channel,
|
channel: isNil(channel) ? name : channel,
|
||||||
type: "prerelease",
|
type: 'prerelease',
|
||||||
name,
|
name,
|
||||||
prerelease: preid,
|
prerelease: preid,
|
||||||
tags,
|
tags,
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { isNil, uniqBy } from "lodash-es";
|
import {isNil, uniqBy} from 'lodash-es';
|
||||||
import semver from "semver";
|
import semver from 'semver';
|
||||||
import { isMaintenanceRange } from "../utils.js";
|
import {isMaintenanceRange} from '../utils.js';
|
||||||
|
|
||||||
export const maintenance = {
|
export const maintenance = {
|
||||||
filter: ({ name, range }) => (!isNil(range) && range !== false) || isMaintenanceRange(name),
|
filter: ({name, range}) => (!isNil(range) && range !== false) || isMaintenanceRange(name),
|
||||||
branchValidator: ({ range }) => (isNil(range) ? true : isMaintenanceRange(range)),
|
branchValidator: ({range}) => (isNil(range) ? true : isMaintenanceRange(range)),
|
||||||
branchesValidator: (branches) => uniqBy(branches, ({ range }) => semver.validRange(range)).length === branches.length,
|
branchesValidator: (branches) => uniqBy(branches, ({range}) => semver.validRange(range)).length === branches.length,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const prerelease = {
|
export const prerelease = {
|
||||||
filter: ({ prerelease }) => !isNil(prerelease) && prerelease !== false,
|
filter: ({prerelease}) => !isNil(prerelease) && prerelease !== false,
|
||||||
branchValidator: ({ name, prerelease }) =>
|
branchValidator: ({name, prerelease}) =>
|
||||||
Boolean(prerelease) && Boolean(semver.valid(`1.0.0-${prerelease === true ? name : prerelease}.1`)),
|
Boolean(prerelease) && Boolean(semver.valid(`1.0.0-${prerelease === true ? name : prerelease}.1`)),
|
||||||
branchesValidator: (branches) => uniqBy(branches, "prerelease").length === branches.length,
|
branchesValidator: (branches) => uniqBy(branches, 'prerelease').length === branches.length,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const release = {
|
export const release = {
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
export const RELEASE_TYPE = ["patch", "minor", "major"];
|
export const RELEASE_TYPE = ['patch', 'minor', 'major'];
|
||||||
|
|
||||||
export const FIRST_RELEASE = "1.0.0";
|
export const FIRST_RELEASE = '1.0.0';
|
||||||
|
|
||||||
export const FIRSTPRERELEASE = "1";
|
export const FIRSTPRERELEASE = '1';
|
||||||
|
|
||||||
export const COMMIT_NAME = "semantic-release-bot";
|
export const COMMIT_NAME = 'semantic-release-bot';
|
||||||
|
|
||||||
export const COMMIT_EMAIL = "semantic-release-bot@martynus.net";
|
export const COMMIT_EMAIL = 'semantic-release-bot@martynus.net';
|
||||||
|
|
||||||
export const RELEASE_NOTES_SEPARATOR = "\n\n";
|
export const RELEASE_NOTES_SEPARATOR = '\n\n';
|
||||||
|
|
||||||
export const SECRET_REPLACEMENT = "[secure]";
|
export const SECRET_REPLACEMENT = '[secure]';
|
||||||
|
|
||||||
export const SECRET_MIN_SIZE = 5;
|
export const SECRET_MIN_SIZE = 5;
|
||||||
|
|
||||||
export const GIT_NOTE_REF = "semantic-release";
|
export const GIT_NOTE_REF = 'semantic-release';
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { inspect } from "node:util";
|
import {inspect} from 'node:util';
|
||||||
import { createRequire } from "node:module";
|
import {createRequire} from 'node:module';
|
||||||
import { isString, toLower, trim } from "lodash-es";
|
import {isString, toLower, trim} from 'lodash-es';
|
||||||
import { RELEASE_TYPE } from "./constants.js";
|
import {RELEASE_TYPE} from './constants.js';
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const pkg = require("../../package.json");
|
const pkg = require('../../package.json');
|
||||||
|
|
||||||
const [homepage] = pkg.homepage.split("#");
|
const [homepage] = pkg.homepage.split('#');
|
||||||
const stringify = (object) =>
|
const stringify = (object) =>
|
||||||
isString(object) ? object : inspect(object, { breakLength: Infinity, depth: 2, maxArrayLength: 5 });
|
isString(object) ? object : inspect(object, {breakLength: Infinity, depth: 2, maxArrayLength: 5});
|
||||||
const linkify = (file) => `${homepage}/blob/master/${file}`;
|
const linkify = (file) => `${homepage}/blob/master/${file}`;
|
||||||
const wordsList = (words) =>
|
const wordsList = (words) =>
|
||||||
`${words.slice(0, -1).join(", ")}${words.length > 1 ? ` or ${words[words.length - 1]}` : trim(words[0])}`;
|
`${words.slice(0, -1).join(', ')}${words.length > 1 ? ` or ${words[words.length - 1]}` : trim(words[0])}`;
|
||||||
|
|
||||||
export function ENOGITREPO({ cwd }) {
|
export function ENOGITREPO({cwd}) {
|
||||||
return {
|
return {
|
||||||
message: "Not running from a git repository.",
|
message: 'Not running from a git repository.',
|
||||||
details: `The \`semantic-release\` command must be executed from a Git repository.
|
details: `The \`semantic-release\` command must be executed from a Git repository.
|
||||||
|
|
||||||
The current working directory is \`${cwd}\`.
|
The current working directory is \`${cwd}\`.
|
||||||
@ -26,76 +26,76 @@ Please verify your CI configuration to make sure the \`semantic-release\` comman
|
|||||||
|
|
||||||
export function ENOREPOURL() {
|
export function ENOREPOURL() {
|
||||||
return {
|
return {
|
||||||
message: "The `repositoryUrl` option is required.",
|
message: 'The `repositoryUrl` option is required.',
|
||||||
details: `The [repositoryUrl option](${linkify(
|
details: `The [repositoryUrl option](${linkify(
|
||||||
"docs/usage/configuration.md#repositoryurl"
|
'docs/usage/configuration.md#repositoryurl'
|
||||||
)}) cannot be determined from the semantic-release configuration, the \`package.json\` nor the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes).
|
)}) cannot be determined from the semantic-release configuration, the \`package.json\` nor the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes).
|
||||||
|
|
||||||
Please make sure to add the \`repositoryUrl\` to the [semantic-release configuration] (${linkify(
|
Please make sure to add the \`repositoryUrl\` to the [semantic-release configuration] (${linkify(
|
||||||
"docs/usage/configuration.md"
|
'docs/usage/configuration.md'
|
||||||
)}).`,
|
)}).`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EGITNOPERMISSION({ options: { repositoryUrl }, branch: { name } }) {
|
export function EGITNOPERMISSION({options: {repositoryUrl}, branch: {name}}) {
|
||||||
return {
|
return {
|
||||||
message: "Cannot push to the Git repository.",
|
message: 'Cannot push to the Git repository.',
|
||||||
details: `**semantic-release** cannot push the version tag to the branch \`${name}\` on the remote Git repository with URL \`${repositoryUrl}\`.
|
details: `**semantic-release** cannot push the version tag to the branch \`${name}\` on the remote Git repository with URL \`${repositoryUrl}\`.
|
||||||
|
|
||||||
This can be caused by:
|
This can be caused by:
|
||||||
- a misconfiguration of the [repositoryUrl](${linkify("docs/usage/configuration.md#repositoryurl")}) option
|
- a misconfiguration of the [repositoryUrl](${linkify('docs/usage/configuration.md#repositoryurl')}) option
|
||||||
- the repository being unavailable
|
- the repository being unavailable
|
||||||
- or missing push permission for the user configured via the [Git credentials on your CI environment](${linkify(
|
- or missing push permission for the user configured via the [Git credentials on your CI environment](${linkify(
|
||||||
"docs/usage/ci-configuration.md#authentication"
|
'docs/usage/ci-configuration.md#authentication'
|
||||||
)})`,
|
)})`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EINVALIDTAGFORMAT({ options: { tagFormat } }) {
|
export function EINVALIDTAGFORMAT({options: {tagFormat}}) {
|
||||||
return {
|
return {
|
||||||
message: "Invalid `tagFormat` option.",
|
message: 'Invalid `tagFormat` option.',
|
||||||
details: `The [tagFormat](${linkify(
|
details: `The [tagFormat](${linkify(
|
||||||
"docs/usage/configuration.md#tagformat"
|
'docs/usage/configuration.md#tagformat'
|
||||||
)}) must compile to a [valid Git reference](https://git-scm.com/docs/git-check-ref-format#_description).
|
)}) must compile to a [valid Git reference](https://git-scm.com/docs/git-check-ref-format#_description).
|
||||||
|
|
||||||
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
|
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ETAGNOVERSION({ options: { tagFormat } }) {
|
export function ETAGNOVERSION({options: {tagFormat}}) {
|
||||||
return {
|
return {
|
||||||
message: "Invalid `tagFormat` option.",
|
message: 'Invalid `tagFormat` option.',
|
||||||
details: `The [tagFormat](${linkify(
|
details: `The [tagFormat](${linkify(
|
||||||
"docs/usage/configuration.md#tagformat"
|
'docs/usage/configuration.md#tagformat'
|
||||||
)}) option must contain the variable \`version\` exactly once.
|
)}) option must contain the variable \`version\` exactly once.
|
||||||
|
|
||||||
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
|
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EPLUGINCONF({ type, required, pluginConf }) {
|
export function EPLUGINCONF({type, required, pluginConf}) {
|
||||||
return {
|
return {
|
||||||
message: `The \`${type}\` plugin configuration is invalid.`,
|
message: `The \`${type}\` plugin configuration is invalid.`,
|
||||||
details: `The [${type} plugin configuration](${linkify(`docs/usage/plugins.md#${toLower(type)}-plugin`)}) ${
|
details: `The [${type} plugin configuration](${linkify(`docs/usage/plugins.md#${toLower(type)}-plugin`)}) ${
|
||||||
required ? "is required and " : ""
|
required ? 'is required and ' : ''
|
||||||
} must be a single or an array of plugins definition. A plugin definition is an npm module name, optionally wrapped in an array with an object.
|
} must be a single or an array of plugins definition. A plugin definition is an npm module name, optionally wrapped in an array with an object.
|
||||||
|
|
||||||
Your configuration for the \`${type}\` plugin is \`${stringify(pluginConf)}\`.`,
|
Your configuration for the \`${type}\` plugin is \`${stringify(pluginConf)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EPLUGINSCONF({ plugin }) {
|
export function EPLUGINSCONF({plugin}) {
|
||||||
return {
|
return {
|
||||||
message: "The `plugins` configuration is invalid.",
|
message: 'The `plugins` configuration is invalid.',
|
||||||
details: `The [plugins](${linkify(
|
details: `The [plugins](${linkify(
|
||||||
"docs/usage/configuration.md#plugins"
|
'docs/usage/configuration.md#plugins'
|
||||||
)}) option must be an array of plugin definitions. A plugin definition is an npm module name, optionally wrapped in an array with an object.
|
)}) option must be an array of plugin definitions. A plugin definition is an npm module name, optionally wrapped in an array with an object.
|
||||||
|
|
||||||
The invalid configuration is \`${stringify(plugin)}\`.`,
|
The invalid configuration is \`${stringify(plugin)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EPLUGIN({ pluginName, type }) {
|
export function EPLUGIN({pluginName, type}) {
|
||||||
return {
|
return {
|
||||||
message: `A plugin configured in the step ${type} is not a valid semantic-release plugin.`,
|
message: `A plugin configured in the step ${type} is not a valid semantic-release plugin.`,
|
||||||
details: `A valid \`${type}\` **semantic-release** plugin must be a function or an object with a function in the property \`${type}\`.
|
details: `A valid \`${type}\` **semantic-release** plugin must be a function or an object with a function in the property \`${type}\`.
|
||||||
@ -103,17 +103,17 @@ export function EPLUGIN({ pluginName, type }) {
|
|||||||
The plugin \`${pluginName}\` doesn't have the property \`${type}\` and cannot be used for the \`${type}\` step.
|
The plugin \`${pluginName}\` doesn't have the property \`${type}\` and cannot be used for the \`${type}\` step.
|
||||||
|
|
||||||
Please refer to the \`${pluginName}\` and [semantic-release plugins configuration](${linkify(
|
Please refer to the \`${pluginName}\` and [semantic-release plugins configuration](${linkify(
|
||||||
"docs/usage/plugins.md"
|
'docs/usage/plugins.md'
|
||||||
)}) documentation for more details.`,
|
)}) documentation for more details.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EANALYZECOMMITSOUTPUT({ result, pluginName }) {
|
export function EANALYZECOMMITSOUTPUT({result, pluginName}) {
|
||||||
return {
|
return {
|
||||||
message: "The `analyzeCommits` plugin returned an invalid value. It must return a valid semver release type.",
|
message: 'The `analyzeCommits` plugin returned an invalid value. It must return a valid semver release type.',
|
||||||
details: `The \`analyzeCommits\` plugin must return a valid [semver](https://semver.org) release type. The valid values are: ${RELEASE_TYPE.map(
|
details: `The \`analyzeCommits\` plugin must return a valid [semver](https://semver.org) release type. The valid values are: ${RELEASE_TYPE.map(
|
||||||
(type) => `\`${type}\``
|
(type) => `\`${type}\``
|
||||||
).join(", ")}.
|
).join(', ')}.
|
||||||
|
|
||||||
The \`analyzeCommits\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
|
The \`analyzeCommits\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
|
||||||
|
|
||||||
@ -121,15 +121,15 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
|
|||||||
- The **semantic-release** version: \`${pkg.version}\`
|
- The **semantic-release** version: \`${pkg.version}\`
|
||||||
- The **semantic-release** logs from your CI job
|
- The **semantic-release** logs from your CI job
|
||||||
- The value returned by the plugin: \`${stringify(result)}\`
|
- The value returned by the plugin: \`${stringify(result)}\`
|
||||||
- A link to the **semantic-release** plugin developer guide: [${linkify("docs/developer-guide/plugin.md")}](${linkify(
|
- A link to the **semantic-release** plugin developer guide: [${linkify('docs/developer-guide/plugin.md')}](${linkify(
|
||||||
"docs/developer-guide/plugin.md"
|
'docs/developer-guide/plugin.md'
|
||||||
)})`,
|
)})`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EGENERATENOTESOUTPUT({ result, pluginName }) {
|
export function EGENERATENOTESOUTPUT({result, pluginName}) {
|
||||||
return {
|
return {
|
||||||
message: "The `generateNotes` plugin returned an invalid value. It must return a `String`.",
|
message: 'The `generateNotes` plugin returned an invalid value. It must return a `String`.',
|
||||||
details: `The \`generateNotes\` plugin must return a \`String\`.
|
details: `The \`generateNotes\` plugin must return a \`String\`.
|
||||||
|
|
||||||
The \`generateNotes\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
|
The \`generateNotes\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
|
||||||
@ -138,15 +138,15 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
|
|||||||
- The **semantic-release** version: \`${pkg.version}\`
|
- The **semantic-release** version: \`${pkg.version}\`
|
||||||
- The **semantic-release** logs from your CI job
|
- The **semantic-release** logs from your CI job
|
||||||
- The value returned by the plugin: \`${stringify(result)}\`
|
- The value returned by the plugin: \`${stringify(result)}\`
|
||||||
- A link to the **semantic-release** plugin developer guide: [${linkify("docs/developer-guide/plugin.md")}](${linkify(
|
- A link to the **semantic-release** plugin developer guide: [${linkify('docs/developer-guide/plugin.md')}](${linkify(
|
||||||
"docs/developer-guide/plugin.md"
|
'docs/developer-guide/plugin.md'
|
||||||
)})`,
|
)})`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EPUBLISHOUTPUT({ result, pluginName }) {
|
export function EPUBLISHOUTPUT({result, pluginName}) {
|
||||||
return {
|
return {
|
||||||
message: "A `publish` plugin returned an invalid value. It must return an `Object`.",
|
message: 'A `publish` plugin returned an invalid value. It must return an `Object`.',
|
||||||
details: `The \`publish\` plugins must return an \`Object\`.
|
details: `The \`publish\` plugins must return an \`Object\`.
|
||||||
|
|
||||||
The \`publish\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
|
The \`publish\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
|
||||||
@ -155,15 +155,15 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
|
|||||||
- The **semantic-release** version: \`${pkg.version}\`
|
- The **semantic-release** version: \`${pkg.version}\`
|
||||||
- The **semantic-release** logs from your CI job
|
- The **semantic-release** logs from your CI job
|
||||||
- The value returned by the plugin: \`${stringify(result)}\`
|
- The value returned by the plugin: \`${stringify(result)}\`
|
||||||
- A link to the **semantic-release** plugin developer guide: [${linkify("docs/developer-guide/plugin.md")}](${linkify(
|
- A link to the **semantic-release** plugin developer guide: [${linkify('docs/developer-guide/plugin.md')}](${linkify(
|
||||||
"docs/developer-guide/plugin.md"
|
'docs/developer-guide/plugin.md'
|
||||||
)})`,
|
)})`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EADDCHANNELOUTPUT({ result, pluginName }) {
|
export function EADDCHANNELOUTPUT({result, pluginName}) {
|
||||||
return {
|
return {
|
||||||
message: "A `addChannel` plugin returned an invalid value. It must return an `Object`.",
|
message: 'A `addChannel` plugin returned an invalid value. It must return an `Object`.',
|
||||||
details: `The \`addChannel\` plugins must return an \`Object\`.
|
details: `The \`addChannel\` plugins must return an \`Object\`.
|
||||||
|
|
||||||
The \`addChannel\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
|
The \`addChannel\` function of the \`${pluginName}\` returned \`${stringify(result)}\` instead.
|
||||||
@ -172,72 +172,72 @@ We recommend to report the issue to the \`${pluginName}\` authors, providing the
|
|||||||
- The **semantic-release** version: \`${pkg.version}\`
|
- The **semantic-release** version: \`${pkg.version}\`
|
||||||
- The **semantic-release** logs from your CI job
|
- The **semantic-release** logs from your CI job
|
||||||
- The value returned by the plugin: \`${stringify(result)}\`
|
- The value returned by the plugin: \`${stringify(result)}\`
|
||||||
- A link to the **semantic-release** plugin developer guide: [${linkify("docs/developer-guide/plugin.md")}](${linkify(
|
- A link to the **semantic-release** plugin developer guide: [${linkify('docs/developer-guide/plugin.md')}](${linkify(
|
||||||
"docs/developer-guide/plugin.md"
|
'docs/developer-guide/plugin.md'
|
||||||
)})`,
|
)})`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EINVALIDBRANCH({ branch }) {
|
export function EINVALIDBRANCH({branch}) {
|
||||||
return {
|
return {
|
||||||
message: "A branch is invalid in the `branches` configuration.",
|
message: 'A branch is invalid in the `branches` configuration.',
|
||||||
details: `Each branch in the [branches configuration](${linkify(
|
details: `Each branch in the [branches configuration](${linkify(
|
||||||
"docs/usage/configuration.md#branches"
|
'docs/usage/configuration.md#branches'
|
||||||
)}) must be either a string, a regexp or an object with a \`name\` property.
|
)}) must be either a string, a regexp or an object with a \`name\` property.
|
||||||
|
|
||||||
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
|
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EINVALIDBRANCHNAME({ branch }) {
|
export function EINVALIDBRANCHNAME({branch}) {
|
||||||
return {
|
return {
|
||||||
message: "A branch name is invalid in the `branches` configuration.",
|
message: 'A branch name is invalid in the `branches` configuration.',
|
||||||
details: `Each branch in the [branches configuration](${linkify(
|
details: `Each branch in the [branches configuration](${linkify(
|
||||||
"docs/usage/configuration.md#branches"
|
'docs/usage/configuration.md#branches'
|
||||||
)}) must be a [valid Git reference](https://git-scm.com/docs/git-check-ref-format#_description).
|
)}) must be a [valid Git reference](https://git-scm.com/docs/git-check-ref-format#_description).
|
||||||
|
|
||||||
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
|
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EDUPLICATEBRANCHES({ duplicates }) {
|
export function EDUPLICATEBRANCHES({duplicates}) {
|
||||||
return {
|
return {
|
||||||
message: "The `branches` configuration has duplicate branches.",
|
message: 'The `branches` configuration has duplicate branches.',
|
||||||
details: `Each branch in the [branches configuration](${linkify(
|
details: `Each branch in the [branches configuration](${linkify(
|
||||||
"docs/usage/configuration.md#branches"
|
'docs/usage/configuration.md#branches'
|
||||||
)}) must havea unique name.
|
)}) must havea unique name.
|
||||||
|
|
||||||
Your configuration contains duplicates for the following branch names: \`${stringify(duplicates)}\`.`,
|
Your configuration contains duplicates for the following branch names: \`${stringify(duplicates)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EMAINTENANCEBRANCH({ branch }) {
|
export function EMAINTENANCEBRANCH({branch}) {
|
||||||
return {
|
return {
|
||||||
message: "A maintenance branch is invalid in the `branches` configuration.",
|
message: 'A maintenance branch is invalid in the `branches` configuration.',
|
||||||
details: `Each maintenance branch in the [branches configuration](${linkify(
|
details: `Each maintenance branch in the [branches configuration](${linkify(
|
||||||
"docs/usage/configuration.md#branches"
|
'docs/usage/configuration.md#branches'
|
||||||
)}) must have a \`range\` property formatted like \`N.x\`, \`N.x.x\` or \`N.N.x\` (\`N\` is a number).
|
)}) must have a \`range\` property formatted like \`N.x\`, \`N.x.x\` or \`N.N.x\` (\`N\` is a number).
|
||||||
|
|
||||||
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
|
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EMAINTENANCEBRANCHES({ branches }) {
|
export function EMAINTENANCEBRANCHES({branches}) {
|
||||||
return {
|
return {
|
||||||
message: "The maintenance branches are invalid in the `branches` configuration.",
|
message: 'The maintenance branches are invalid in the `branches` configuration.',
|
||||||
details: `Each maintenance branch in the [branches configuration](${linkify(
|
details: `Each maintenance branch in the [branches configuration](${linkify(
|
||||||
"docs/usage/configuration.md#branches"
|
'docs/usage/configuration.md#branches'
|
||||||
)}) must have a unique \`range\` property.
|
)}) must have a unique \`range\` property.
|
||||||
|
|
||||||
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
|
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ERELEASEBRANCHES({ branches }) {
|
export function ERELEASEBRANCHES({branches}) {
|
||||||
return {
|
return {
|
||||||
message: "The release branches are invalid in the `branches` configuration.",
|
message: 'The release branches are invalid in the `branches` configuration.',
|
||||||
details: `A minimum of 1 and a maximum of 3 release branches are required in the [branches configuration](${linkify(
|
details: `A minimum of 1 and a maximum of 3 release branches are required in the [branches configuration](${linkify(
|
||||||
"docs/usage/configuration.md#branches"
|
'docs/usage/configuration.md#branches'
|
||||||
)}).
|
)}).
|
||||||
|
|
||||||
This may occur if your repository does not have a release branch, such as \`master\`.
|
This may occur if your repository does not have a release branch, such as \`master\`.
|
||||||
@ -246,53 +246,53 @@ Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EPRERELEASEBRANCH({ branch }) {
|
export function EPRERELEASEBRANCH({branch}) {
|
||||||
return {
|
return {
|
||||||
message: "A pre-release branch configuration is invalid in the `branches` configuration.",
|
message: 'A pre-release branch configuration is invalid in the `branches` configuration.',
|
||||||
details: `Each pre-release branch in the [branches configuration](${linkify(
|
details: `Each pre-release branch in the [branches configuration](${linkify(
|
||||||
"docs/usage/configuration.md#branches"
|
'docs/usage/configuration.md#branches'
|
||||||
)}) must have a \`prerelease\` property valid per the [Semantic Versioning Specification](https://semver.org/#spec-item-9). If the \`prerelease\` property is set to \`true\`, then the \`name\` property is used instead.
|
)}) must have a \`prerelease\` property valid per the [Semantic Versioning Specification](https://semver.org/#spec-item-9). If the \`prerelease\` property is set to \`true\`, then the \`name\` property is used instead.
|
||||||
|
|
||||||
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
|
Your configuration for the problematic branch is \`${stringify(branch)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EPRERELEASEBRANCHES({ branches }) {
|
export function EPRERELEASEBRANCHES({branches}) {
|
||||||
return {
|
return {
|
||||||
message: "The pre-release branches are invalid in the `branches` configuration.",
|
message: 'The pre-release branches are invalid in the `branches` configuration.',
|
||||||
details: `Each pre-release branch in the [branches configuration](${linkify(
|
details: `Each pre-release branch in the [branches configuration](${linkify(
|
||||||
"docs/usage/configuration.md#branches"
|
'docs/usage/configuration.md#branches'
|
||||||
)}) must have a unique \`prerelease\` property. If the \`prerelease\` property is set to \`true\`, then the \`name\` property is used instead.
|
)}) must have a unique \`prerelease\` property. If the \`prerelease\` property is set to \`true\`, then the \`name\` property is used instead.
|
||||||
|
|
||||||
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
|
Your configuration for the problematic branches is \`${stringify(branches)}\`.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EINVALIDNEXTVERSION({ nextRelease: { version }, branch: { name, range }, commits, validBranches }) {
|
export function EINVALIDNEXTVERSION({nextRelease: {version}, branch: {name, range}, commits, validBranches}) {
|
||||||
return {
|
return {
|
||||||
message: `The release \`${version}\` on branch \`${name}\` cannot be published as it is out of range.`,
|
message: `The release \`${version}\` on branch \`${name}\` cannot be published as it is out of range.`,
|
||||||
details: `Based on the releases published on other branches, only versions within the range \`${range}\` can be published from branch \`${name}\`.
|
details: `Based on the releases published on other branches, only versions within the range \`${range}\` can be published from branch \`${name}\`.
|
||||||
|
|
||||||
The following commit${commits.length > 1 ? "s are" : " is"} responsible for the invalid release:
|
The following commit${commits.length > 1 ? 's are' : ' is'} responsible for the invalid release:
|
||||||
${commits.map(({ commit: { short }, subject }) => `- ${subject} (${short})`).join("\n")}
|
${commits.map(({commit: {short}, subject}) => `- ${subject} (${short})`).join('\n')}
|
||||||
|
|
||||||
${
|
${
|
||||||
commits.length > 1 ? "Those commits" : "This commit"
|
commits.length > 1 ? 'Those commits' : 'This commit'
|
||||||
} should be moved to a valid branch with [git merge](https://git-scm.com/docs/git-merge) or [git cherry-pick](https://git-scm.com/docs/git-cherry-pick) and removed from branch \`${name}\` with [git revert](https://git-scm.com/docs/git-revert) or [git reset](https://git-scm.com/docs/git-reset).
|
} should be moved to a valid branch with [git merge](https://git-scm.com/docs/git-merge) or [git cherry-pick](https://git-scm.com/docs/git-cherry-pick) and removed from branch \`${name}\` with [git revert](https://git-scm.com/docs/git-revert) or [git reset](https://git-scm.com/docs/git-reset).
|
||||||
|
|
||||||
A valid branch could be ${wordsList(validBranches.map(({ name }) => `\`${name}\``))}.
|
A valid branch could be ${wordsList(validBranches.map(({name}) => `\`${name}\``))}.
|
||||||
|
|
||||||
See the [workflow configuration documentation](${linkify("docs/usage/workflow-configuration.md")}) for more details.`,
|
See the [workflow configuration documentation](${linkify('docs/usage/workflow-configuration.md')}) for more details.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EINVALIDMAINTENANCEMERGE({ nextRelease: { channel, gitTag, version }, branch: { mergeRange, name } }) {
|
export function EINVALIDMAINTENANCEMERGE({nextRelease: {channel, gitTag, version}, branch: {mergeRange, name}}) {
|
||||||
return {
|
return {
|
||||||
message: `The release \`${version}\` on branch \`${name}\` cannot be published as it is out of range.`,
|
message: `The release \`${version}\` on branch \`${name}\` cannot be published as it is out of range.`,
|
||||||
details: `Only releases within the range \`${mergeRange}\` can be merged into the maintenance branch \`${name}\` and published to the \`${channel}\` distribution channel.
|
details: `Only releases within the range \`${mergeRange}\` can be merged into the maintenance branch \`${name}\` and published to the \`${channel}\` distribution channel.
|
||||||
|
|
||||||
The branch \`${name}\` head should be [reset](https://git-scm.com/docs/git-reset) to a previous commit so the commit with tag \`${gitTag}\` is removed from the branch history.
|
The branch \`${name}\` head should be [reset](https://git-scm.com/docs/git-reset) to a previous commit so the commit with tag \`${gitTag}\` is removed from the branch history.
|
||||||
|
|
||||||
See the [workflow configuration documentation](${linkify("docs/usage/workflow-configuration.md")}) for more details.`,
|
See the [workflow configuration documentation](${linkify('docs/usage/workflow-configuration.md')}) for more details.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
/* eslint require-atomic-updates: off */
|
/* eslint require-atomic-updates: off */
|
||||||
|
|
||||||
import { isPlainObject, isString } from "lodash-es";
|
import {isPlainObject, isString} from 'lodash-es';
|
||||||
import { getGitHead } from "../git.js";
|
import {getGitHead} from '../git.js';
|
||||||
import hideSensitive from "../hide-sensitive.js";
|
import hideSensitive from '../hide-sensitive.js';
|
||||||
import { hideSensitiveValues } from "../utils.js";
|
import {hideSensitiveValues} from '../utils.js';
|
||||||
import { RELEASE_NOTES_SEPARATOR, RELEASE_TYPE } from "./constants.js";
|
import {RELEASE_NOTES_SEPARATOR, RELEASE_TYPE} from './constants.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
verifyConditions: {
|
verifyConditions: {
|
||||||
required: false,
|
required: false,
|
||||||
dryRun: true,
|
dryRun: true,
|
||||||
pipelineConfig: () => ({ settleAll: true }),
|
pipelineConfig: () => ({settleAll: true}),
|
||||||
},
|
},
|
||||||
analyzeCommits: {
|
analyzeCommits: {
|
||||||
default: ["@semantic-release/commit-analyzer"],
|
default: ['@semantic-release/commit-analyzer'],
|
||||||
required: true,
|
required: true,
|
||||||
dryRun: true,
|
dryRun: true,
|
||||||
outputValidator: (output) => !output || RELEASE_TYPE.includes(output),
|
outputValidator: (output) => !output || RELEASE_TYPE.includes(output),
|
||||||
preprocess: ({ commits, ...inputs }) => ({
|
preprocess: ({commits, ...inputs}) => ({
|
||||||
...inputs,
|
...inputs,
|
||||||
commits: commits.filter((commit) => !/\[skip\s+release]|\[release\s+skip]/i.test(commit.message)),
|
commits: commits.filter((commit) => !/\[skip\s+release]|\[release\s+skip]/i.test(commit.message)),
|
||||||
}),
|
}),
|
||||||
@ -32,29 +32,29 @@ export default {
|
|||||||
verifyRelease: {
|
verifyRelease: {
|
||||||
required: false,
|
required: false,
|
||||||
dryRun: true,
|
dryRun: true,
|
||||||
pipelineConfig: () => ({ settleAll: true }),
|
pipelineConfig: () => ({settleAll: true}),
|
||||||
},
|
},
|
||||||
generateNotes: {
|
generateNotes: {
|
||||||
required: false,
|
required: false,
|
||||||
dryRun: true,
|
dryRun: true,
|
||||||
outputValidator: (output) => !output || isString(output),
|
outputValidator: (output) => !output || isString(output),
|
||||||
pipelineConfig: () => ({
|
pipelineConfig: () => ({
|
||||||
getNextInput: ({ nextRelease, ...context }, notes) => ({
|
getNextInput: ({nextRelease, ...context}, notes) => ({
|
||||||
...context,
|
...context,
|
||||||
nextRelease: {
|
nextRelease: {
|
||||||
...nextRelease,
|
...nextRelease,
|
||||||
notes: `${nextRelease.notes ? `${nextRelease.notes}${RELEASE_NOTES_SEPARATOR}` : ""}${notes}`,
|
notes: `${nextRelease.notes ? `${nextRelease.notes}${RELEASE_NOTES_SEPARATOR}` : ''}${notes}`,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
postprocess: (results, { env }) => hideSensitive(env)(results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR)),
|
postprocess: (results, {env}) => hideSensitive(env)(results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR)),
|
||||||
},
|
},
|
||||||
prepare: {
|
prepare: {
|
||||||
required: false,
|
required: false,
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
pipelineConfig: ({ generateNotes }) => ({
|
pipelineConfig: ({generateNotes}) => ({
|
||||||
getNextInput: async (context) => {
|
getNextInput: async (context) => {
|
||||||
const newGitHead = await getGitHead({ cwd: context.cwd });
|
const newGitHead = await getGitHead({cwd: context.cwd});
|
||||||
// If previous prepare plugin has created a commit (gitHead changed)
|
// If previous prepare plugin has created a commit (gitHead changed)
|
||||||
if (context.nextRelease.gitHead !== newGitHead) {
|
if (context.nextRelease.gitHead !== newGitHead) {
|
||||||
context.nextRelease.gitHead = newGitHead;
|
context.nextRelease.gitHead = newGitHead;
|
||||||
@ -73,7 +73,7 @@ export default {
|
|||||||
outputValidator: (output) => !output || isPlainObject(output),
|
outputValidator: (output) => !output || isPlainObject(output),
|
||||||
pipelineConfig: () => ({
|
pipelineConfig: () => ({
|
||||||
// Add `nextRelease` and plugin properties to published release
|
// Add `nextRelease` and plugin properties to published release
|
||||||
transform: (release, step, { nextRelease }) => ({
|
transform: (release, step, {nextRelease}) => ({
|
||||||
...(release === false ? {} : nextRelease),
|
...(release === false ? {} : nextRelease),
|
||||||
...release,
|
...release,
|
||||||
...step,
|
...step,
|
||||||
@ -86,7 +86,7 @@ export default {
|
|||||||
outputValidator: (output) => !output || isPlainObject(output),
|
outputValidator: (output) => !output || isPlainObject(output),
|
||||||
pipelineConfig: () => ({
|
pipelineConfig: () => ({
|
||||||
// Add `nextRelease` and plugin properties to published release
|
// Add `nextRelease` and plugin properties to published release
|
||||||
transform: (release, step, { nextRelease }) => ({
|
transform: (release, step, {nextRelease}) => ({
|
||||||
...(release === false ? {} : nextRelease),
|
...(release === false ? {} : nextRelease),
|
||||||
...release,
|
...release,
|
||||||
...step,
|
...step,
|
||||||
@ -96,13 +96,13 @@ export default {
|
|||||||
success: {
|
success: {
|
||||||
required: false,
|
required: false,
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
pipelineConfig: () => ({ settleAll: true }),
|
pipelineConfig: () => ({settleAll: true}),
|
||||||
preprocess: ({ releases, env, ...inputs }) => ({ ...inputs, env, releases: hideSensitiveValues(env, releases) }),
|
preprocess: ({releases, env, ...inputs}) => ({...inputs, env, releases: hideSensitiveValues(env, releases)}),
|
||||||
},
|
},
|
||||||
fail: {
|
fail: {
|
||||||
required: false,
|
required: false,
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
pipelineConfig: () => ({ settleAll: true }),
|
pipelineConfig: () => ({settleAll: true}),
|
||||||
preprocess: ({ errors, env, ...inputs }) => ({ ...inputs, env, errors: hideSensitiveValues(env, errors) }),
|
preprocess: ({errors, env, ...inputs}) => ({...inputs, env, errors: hideSensitiveValues(env, errors)}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { dirname } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { createRequire } from "node:module";
|
||||||
|
|
||||||
import { castArray, isNil, isPlainObject, isString, pickBy } from "lodash-es";
|
import { castArray, isNil, isPlainObject, isString, pickBy } from "lodash-es";
|
||||||
import { readPackageUp } from "read-pkg-up";
|
import { readPackageUp } from "read-pkg-up";
|
||||||
import { cosmiconfig } from "cosmiconfig";
|
import { cosmiconfig } from "cosmiconfig";
|
||||||
import importFrom from "import-from-esm";
|
import resolveFrom from "resolve-from";
|
||||||
import debugConfig from "debug";
|
import debugConfig from "debug";
|
||||||
import { repoUrl } from "./git.js";
|
import { repoUrl } from "./git.js";
|
||||||
import PLUGINS_DEFINITIONS from "./definitions/plugins.js";
|
import PLUGINS_DEFINITIONS from "./definitions/plugins.js";
|
||||||
@ -13,6 +14,7 @@ import { parseConfig, validatePlugin } from "./plugins/utils.js";
|
|||||||
|
|
||||||
const debug = debugConfig("semantic-release:config");
|
const debug = debugConfig("semantic-release:config");
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
const CONFIG_NAME = "release";
|
const CONFIG_NAME = "release";
|
||||||
|
|
||||||
@ -33,7 +35,7 @@ export default async (context, cliOptions) => {
|
|||||||
options = {
|
options = {
|
||||||
...(await castArray(extendPaths).reduce(async (eventualResult, extendPath) => {
|
...(await castArray(extendPaths).reduce(async (eventualResult, extendPath) => {
|
||||||
const result = await eventualResult;
|
const result = await eventualResult;
|
||||||
const extendsOptions = (await importFrom.silent(__dirname, extendPath)) || (await importFrom(cwd, extendPath));
|
const extendsOptions = require(resolveFrom.silent(__dirname, extendPath) || resolveFrom(cwd, extendPath));
|
||||||
|
|
||||||
// For each plugin defined in a shareable config, save in `pluginsPath` the extendable config path,
|
// For each plugin defined in a shareable config, save in `pluginsPath` the extendable config path,
|
||||||
// so those plugin will be loaded relative to the config file
|
// so those plugin will be loaded relative to the config file
|
||||||
@ -74,8 +76,8 @@ export default async (context, cliOptions) => {
|
|||||||
plugins: [
|
plugins: [
|
||||||
"@semantic-release/commit-analyzer",
|
"@semantic-release/commit-analyzer",
|
||||||
"@semantic-release/release-notes-generator",
|
"@semantic-release/release-notes-generator",
|
||||||
//"@semantic-release/npm",
|
"@semantic-release/npm",
|
||||||
//"@semantic-release/github",
|
"@semantic-release/github",
|
||||||
],
|
],
|
||||||
// Remove `null` and `undefined` options, so they can be replaced with default ones
|
// Remove `null` and `undefined` options, so they can be replaced with default ones
|
||||||
...pickBy(options, (option) => !isNil(option)),
|
...pickBy(options, (option) => !isNil(option)),
|
||||||
|
@ -36,7 +36,7 @@ function formatAuthUrl(protocol, repositoryUrl, gitCredentials) {
|
|||||||
* @param {Object} context semantic-release context.
|
* @param {Object} context semantic-release context.
|
||||||
* @param {String} authUrl Repository URL to verify
|
* @param {String} authUrl Repository URL to verify
|
||||||
*
|
*
|
||||||
* @return {String} The authUrl as is if the connection was successful, null otherwise
|
* @return {String} The authUrl as is if the connection was successfull, null otherwise
|
||||||
*/
|
*/
|
||||||
async function ensureValidAuthUrl({ cwd, env, branch }, authUrl) {
|
async function ensureValidAuthUrl({ cwd, env, branch }, authUrl) {
|
||||||
try {
|
try {
|
||||||
|
@ -30,11 +30,7 @@ export default ({ branch, options: { tagFormat } }, { before } = {}) => {
|
|||||||
const [{ version, gitTag, channels } = {}] = branch.tags
|
const [{ version, gitTag, channels } = {}] = branch.tags
|
||||||
.filter(
|
.filter(
|
||||||
(tag) =>
|
(tag) =>
|
||||||
((branch.type === "prerelease" &&
|
((branch.type === "prerelease" && tag.channels.some((channel) => isSameChannel(branch.channel, channel))) ||
|
||||||
tag.channels.some((channel) => isSameChannel(branch.channel, channel)) &&
|
|
||||||
semver
|
|
||||||
.parse(tag.version)
|
|
||||||
.prerelease.includes(branch.prerelease === true ? branch.name : branch.prerelease)) ||
|
|
||||||
!semver.prerelease(tag.version)) &&
|
!semver.prerelease(tag.version)) &&
|
||||||
(isUndefined(before) || semver.lt(tag.version, before))
|
(isUndefined(before) || semver.lt(tag.version, before))
|
||||||
)
|
)
|
||||||
|
49
lib/git.js
49
lib/git.js
@ -2,7 +2,6 @@ import gitLogParser from "git-log-parser";
|
|||||||
import getStream from "get-stream";
|
import getStream from "get-stream";
|
||||||
import { execa } from "execa";
|
import { execa } from "execa";
|
||||||
import debugGit from "debug";
|
import debugGit from "debug";
|
||||||
import { merge } from "lodash-es";
|
|
||||||
import { GIT_NOTE_REF } from "./definitions/constants.js";
|
import { GIT_NOTE_REF } from "./definitions/constants.js";
|
||||||
|
|
||||||
const debug = debugGit("semantic-release:git");
|
const debug = debugGit("semantic-release:git");
|
||||||
@ -142,9 +141,13 @@ export async function fetch(repositoryUrl, branch, ciBranch, execaOptions) {
|
|||||||
*/
|
*/
|
||||||
export async function fetchNotes(repositoryUrl, execaOptions) {
|
export async function fetchNotes(repositoryUrl, execaOptions) {
|
||||||
try {
|
try {
|
||||||
await execa("git", ["fetch", "--unshallow", repositoryUrl, `+refs/notes/*:refs/notes/*`], execaOptions);
|
await execa(
|
||||||
|
"git",
|
||||||
|
["fetch", "--unshallow", repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`],
|
||||||
|
execaOptions
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
await execa("git", ["fetch", repositoryUrl, `+refs/notes/*:refs/notes/*`], {
|
await execa("git", ["fetch", repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`], {
|
||||||
...execaOptions,
|
...execaOptions,
|
||||||
reject: false,
|
reject: false,
|
||||||
});
|
});
|
||||||
@ -243,8 +246,8 @@ export async function push(repositoryUrl, execaOptions) {
|
|||||||
*
|
*
|
||||||
* @throws {Error} if the push failed.
|
* @throws {Error} if the push failed.
|
||||||
*/
|
*/
|
||||||
export async function pushNotes(repositoryUrl, ref, execaOptions) {
|
export async function pushNotes(repositoryUrl, execaOptions) {
|
||||||
await execa("git", ["push", repositoryUrl, `refs/notes/${GIT_NOTE_REF}-${ref}`], execaOptions);
|
await execa("git", ["push", repositoryUrl, `refs/notes/${GIT_NOTE_REF}`], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -304,26 +307,8 @@ export async function isBranchUpToDate(repositoryUrl, branch, execaOptions) {
|
|||||||
* @return {Object} the parsed JSON note if there is one, an empty object otherwise.
|
* @return {Object} the parsed JSON note if there is one, an empty object otherwise.
|
||||||
*/
|
*/
|
||||||
export async function getNote(ref, execaOptions) {
|
export async function getNote(ref, execaOptions) {
|
||||||
const handleError = (error) => {
|
|
||||||
if (error.exitCode === 1) {
|
|
||||||
return { stdout: "{}" };
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(error);
|
|
||||||
throw error;
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return merge(
|
return JSON.parse((await execa("git", ["notes", "--ref", GIT_NOTE_REF, "show", ref], execaOptions)).stdout);
|
||||||
JSON.parse(
|
|
||||||
// Used for retro-compatibility
|
|
||||||
(await execa("git", ["notes", "--ref", GIT_NOTE_REF, "show", ref], execaOptions).catch(handleError)).stdout
|
|
||||||
),
|
|
||||||
JSON.parse(
|
|
||||||
(await execa("git", ["notes", "--ref", `${GIT_NOTE_REF}-${ref}`, "show", ref], execaOptions).catch(handleError))
|
|
||||||
.stdout
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.exitCode === 1) {
|
if (error.exitCode === 1) {
|
||||||
return {};
|
return {};
|
||||||
@ -342,19 +327,5 @@ export async function getNote(ref, execaOptions) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function addNote(note, ref, execaOptions) {
|
export async function addNote(note, ref, execaOptions) {
|
||||||
await execa(
|
await execa("git", ["notes", "--ref", GIT_NOTE_REF, "add", "-f", "-m", JSON.stringify(note), ref], execaOptions);
|
||||||
"git",
|
|
||||||
["notes", "--ref", `${GIT_NOTE_REF}-${ref}`, "add", "-f", "-m", JSON.stringify(note), ref],
|
|
||||||
execaOptions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the reference of a tag
|
|
||||||
*
|
|
||||||
* @param {String} tag The tag name to get the reference of.
|
|
||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
|
||||||
**/
|
|
||||||
export async function getTagRef(tag, execaOptions) {
|
|
||||||
return (await execa("git", ["show-ref", tag, "--hash"], execaOptions)).stdout;
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { castArray, identity, isNil, isPlainObject, isString, omit } from "lodash-es";
|
import {castArray, identity, isNil, isPlainObject, isString, omit} from 'lodash-es';
|
||||||
import AggregateError from "aggregate-error";
|
import AggregateError from 'aggregate-error';
|
||||||
import getError from "../get-error.js";
|
import getError from '../get-error.js';
|
||||||
import PLUGINS_DEFINITIONS from "../definitions/plugins.js";
|
import PLUGINS_DEFINITIONS from '../definitions/plugins.js';
|
||||||
import { loadPlugin, parseConfig, validatePlugin, validateStep } from "./utils.js";
|
import {loadPlugin, parseConfig, validatePlugin, validateStep} from './utils.js';
|
||||||
import pipeline from "./pipeline.js";
|
import pipeline from './pipeline.js';
|
||||||
import normalize from "./normalize.js";
|
import normalize from './normalize.js';
|
||||||
|
|
||||||
export default async (context, pluginsPath) => {
|
export default async (context, pluginsPath) => {
|
||||||
let { options, logger } = context;
|
let {options, logger} = context;
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
const plugins = options.plugins
|
const plugins = options.plugins
|
||||||
@ -20,8 +20,8 @@ export default async (context, pluginsPath) => {
|
|||||||
if (isPlainObject(plugin)) {
|
if (isPlainObject(plugin)) {
|
||||||
Object.entries(plugin).forEach(([type, func]) => {
|
Object.entries(plugin).forEach(([type, func]) => {
|
||||||
if (PLUGINS_DEFINITIONS[type]) {
|
if (PLUGINS_DEFINITIONS[type]) {
|
||||||
Reflect.defineProperty(func, "pluginName", {
|
Reflect.defineProperty(func, 'pluginName', {
|
||||||
value: isPlainObject(name) ? "Inline plugin" : name,
|
value: isPlainObject(name) ? 'Inline plugin' : name,
|
||||||
writable: false,
|
writable: false,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
});
|
});
|
||||||
@ -29,10 +29,10 @@ export default async (context, pluginsPath) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
errors.push(getError("EPLUGINSCONF", { plugin }));
|
errors.push(getError('EPLUGINSCONF', {plugin}));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.push(getError("EPLUGINSCONF", { plugin }));
|
errors.push(getError('EPLUGINSCONF', {plugin}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return pluginsList;
|
return pluginsList;
|
||||||
@ -43,12 +43,12 @@ export default async (context, pluginsPath) => {
|
|||||||
throw new AggregateError(errors);
|
throw new AggregateError(errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
options = { ...plugins, ...options };
|
options = {...plugins, ...options};
|
||||||
|
|
||||||
const pluginsConfig = await Object.entries(PLUGINS_DEFINITIONS).reduce(
|
const pluginsConfig = await Object.entries(PLUGINS_DEFINITIONS).reduce(
|
||||||
async (
|
async (
|
||||||
eventualPluginsConfigAccumulator,
|
eventualPluginsConfigAccumulator,
|
||||||
[type, { required, default: def, pipelineConfig, postprocess = identity, preprocess = identity }]
|
[type, {required, default: def, pipelineConfig, postprocess = identity, preprocess = identity}]
|
||||||
) => {
|
) => {
|
||||||
let pluginOptions;
|
let pluginOptions;
|
||||||
const pluginsConfigAccumulator = await eventualPluginsConfigAccumulator;
|
const pluginsConfigAccumulator = await eventualPluginsConfigAccumulator;
|
||||||
@ -63,8 +63,8 @@ export default async (context, pluginsPath) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateStep({ required }, options[type])) {
|
if (!validateStep({required}, options[type])) {
|
||||||
errors.push(getError("EPLUGINCONF", { type, required, pluginConf: options[type] }));
|
errors.push(getError('EPLUGINCONF', {type, required, pluginConf: options[type]}));
|
||||||
return pluginsConfigAccumulator;
|
return pluginsConfigAccumulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ export default async (context, pluginsPath) => {
|
|||||||
const steps = await Promise.all(
|
const steps = await Promise.all(
|
||||||
castArray(pluginOptions).map(async (pluginOpt) =>
|
castArray(pluginOptions).map(async (pluginOpt) =>
|
||||||
normalize(
|
normalize(
|
||||||
{ ...context, options: omit(options, Object.keys(PLUGINS_DEFINITIONS), "plugins") },
|
{...context, options: omit(options, Object.keys(PLUGINS_DEFINITIONS), 'plugins')},
|
||||||
type,
|
type,
|
||||||
pluginOpt,
|
pluginOpt,
|
||||||
pluginsPath
|
pluginsPath
|
||||||
@ -100,4 +100,4 @@ export default async (context, pluginsPath) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return pluginsConfig;
|
return pluginsConfig;
|
||||||
};
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { cloneDeep, isFunction, isPlainObject, noop, omit } from "lodash-es";
|
import {cloneDeep, isFunction, isPlainObject, noop, omit} from 'lodash-es';
|
||||||
import debugPlugins from "debug";
|
import debugPlugins from 'debug';
|
||||||
import getError from "../get-error.js";
|
import getError from '../get-error.js';
|
||||||
import { extractErrors } from "../utils.js";
|
import {extractErrors} from '../utils.js';
|
||||||
import PLUGINS_DEFINITIONS from "../definitions/plugins.js";
|
import PLUGINS_DEFINITIONS from '../definitions/plugins.js';
|
||||||
import { loadPlugin, parseConfig } from "./utils.js";
|
import {loadPlugin, parseConfig} from './utils.js';
|
||||||
|
|
||||||
const debug = debugPlugins("semantic-release:plugins");
|
const debug = debugPlugins('semantic-release:plugins');
|
||||||
|
|
||||||
export default async (context, type, pluginOpt, pluginsPath) => {
|
export default async (context, type, pluginOpt, pluginsPath) => {
|
||||||
const { stdout, stderr, options, logger } = context;
|
const {stdout, stderr, options, logger} = context;
|
||||||
if (!pluginOpt) {
|
if (!pluginOpt) {
|
||||||
return noop;
|
return noop;
|
||||||
}
|
}
|
||||||
@ -21,26 +21,26 @@ export default async (context, type, pluginOpt, pluginsPath) => {
|
|||||||
|
|
||||||
let func;
|
let func;
|
||||||
if (isFunction(plugin)) {
|
if (isFunction(plugin)) {
|
||||||
func = plugin.bind(null, cloneDeep({ ...options, ...config }));
|
func = plugin.bind(null, cloneDeep({...options, ...config}));
|
||||||
} else if (isPlainObject(plugin) && plugin[type] && isFunction(plugin[type])) {
|
} else if (isPlainObject(plugin) && plugin[type] && isFunction(plugin[type])) {
|
||||||
func = plugin[type].bind(null, cloneDeep({ ...options, ...config }));
|
func = plugin[type].bind(null, cloneDeep({...options, ...config}));
|
||||||
} else {
|
} else {
|
||||||
throw getError("EPLUGIN", { type, pluginName });
|
throw getError('EPLUGIN', {type, pluginName});
|
||||||
}
|
}
|
||||||
|
|
||||||
const validator = async (input) => {
|
const validator = async (input) => {
|
||||||
const { dryRun, outputValidator } = PLUGINS_DEFINITIONS[type] || {};
|
const {dryRun, outputValidator} = PLUGINS_DEFINITIONS[type] || {};
|
||||||
try {
|
try {
|
||||||
if (!input.options.dryRun || dryRun) {
|
if (!input.options.dryRun || dryRun) {
|
||||||
logger.log(`Start step "${type}" of plugin "${pluginName}"`);
|
logger.log(`Start step "${type}" of plugin "${pluginName}"`);
|
||||||
const result = await func({
|
const result = await func({
|
||||||
...cloneDeep(omit(input, ["stdout", "stderr", "logger"])),
|
...cloneDeep(omit(input, ['stdout', 'stderr', 'logger'])),
|
||||||
stdout,
|
stdout,
|
||||||
stderr,
|
stderr,
|
||||||
logger: logger.scope(logger.scopeName, pluginName),
|
logger: logger.scope(logger.scopeName, pluginName),
|
||||||
});
|
});
|
||||||
if (outputValidator && !outputValidator(result)) {
|
if (outputValidator && !outputValidator(result)) {
|
||||||
throw getError(`E${type.toUpperCase()}OUTPUT`, { result, pluginName });
|
throw getError(`E${type.toUpperCase()}OUTPUT`, {result, pluginName});
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.success(`Completed step "${type}" of plugin "${pluginName}"`);
|
logger.success(`Completed step "${type}" of plugin "${pluginName}"`);
|
||||||
@ -50,12 +50,12 @@ export default async (context, type, pluginOpt, pluginsPath) => {
|
|||||||
logger.warn(`Skip step "${type}" of plugin "${pluginName}" in dry-run mode`);
|
logger.warn(`Skip step "${type}" of plugin "${pluginName}" in dry-run mode`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed step "${type}" of plugin "${pluginName}"`);
|
logger.error(`Failed step "${type}" of plugin "${pluginName}"`);
|
||||||
extractErrors(error).forEach((err) => Object.assign(err, { pluginName }));
|
extractErrors(error).forEach((err) => Object.assign(err, {pluginName}));
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Reflect.defineProperty(validator, "pluginName", { value: pluginName, writable: false, enumerable: true });
|
Reflect.defineProperty(validator, 'pluginName', {value: pluginName, writable: false, enumerable: true});
|
||||||
|
|
||||||
if (!isFunction(pluginOpt)) {
|
if (!isFunction(pluginOpt)) {
|
||||||
if (pluginsPath[name]) {
|
if (pluginsPath[name]) {
|
||||||
@ -66,4 +66,4 @@ export default async (context, type, pluginOpt, pluginsPath) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return validator;
|
return validator;
|
||||||
};
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { identity } from "lodash-es";
|
import {identity} from 'lodash-es';
|
||||||
import pReduce from "p-reduce";
|
import pReduce from 'p-reduce';
|
||||||
import AggregateError from "aggregate-error";
|
import AggregateError from 'aggregate-error';
|
||||||
import { extractErrors } from "../utils.js";
|
import {extractErrors} from '../utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Function that execute a list of function sequencially. If at least one Function ins the pipeline throws an Error or rejects, the pipeline function rejects as well.
|
* A Function that execute a list of function sequencially. If at least one Function ins the pipeline throws an Error or rejects, the pipeline function rejects as well.
|
||||||
@ -25,35 +25,34 @@ import { extractErrors } from "../utils.js";
|
|||||||
*
|
*
|
||||||
* @return {Pipeline} A Function that execute the `steps` sequentially
|
* @return {Pipeline} A Function that execute the `steps` sequentially
|
||||||
*/
|
*/
|
||||||
export default (steps, { settleAll = false, getNextInput = identity, transform = identity } = {}) =>
|
export default (steps, {settleAll = false, getNextInput = identity, transform = identity} = {}) => async (input) => {
|
||||||
async (input) => {
|
const results = [];
|
||||||
const results = [];
|
const errors = [];
|
||||||
const errors = [];
|
await pReduce(
|
||||||
await pReduce(
|
steps,
|
||||||
steps,
|
async (lastInput, step) => {
|
||||||
async (lastInput, step) => {
|
let result;
|
||||||
let result;
|
try {
|
||||||
try {
|
// Call the step with the input computed at the end of the previous iteration and save intermediary result
|
||||||
// Call the step with the input computed at the end of the previous iteration and save intermediary result
|
result = await transform(await step(lastInput), step, lastInput);
|
||||||
result = await transform(await step(lastInput), step, lastInput);
|
results.push(result);
|
||||||
results.push(result);
|
} catch (error) {
|
||||||
} catch (error) {
|
if (settleAll) {
|
||||||
if (settleAll) {
|
errors.push(...extractErrors(error));
|
||||||
errors.push(...extractErrors(error));
|
result = error;
|
||||||
result = error;
|
} else {
|
||||||
} else {
|
throw error;
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare input for the next step, passing the input of the last iteration (or initial parameter for the first iteration) and the result of the current one
|
// Prepare input for the next step, passing the input of the last iteration (or initial parameter for the first iteration) and the result of the current one
|
||||||
return getNextInput(lastInput, result);
|
return getNextInput(lastInput, result);
|
||||||
},
|
},
|
||||||
input
|
input
|
||||||
);
|
);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
throw new AggregateError(errors);
|
throw new AggregateError(errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
};
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { dirname } from "node:path";
|
import {dirname} from 'node:path';
|
||||||
import { fileURLToPath } from "node:url";
|
import {fileURLToPath} from 'node:url';
|
||||||
import { castArray, isArray, isFunction, isNil, isPlainObject, isString } from "lodash-es";
|
import {castArray, isArray, isFunction, isNil, isPlainObject, isString} from 'lodash-es';
|
||||||
import resolveFrom from "resolve-from";
|
import resolveFrom from 'resolve-from';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ export function validatePlugin(conf) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateStep({ required }, conf) {
|
export function validateStep({required}, conf) {
|
||||||
conf = castArray(conf).filter(Boolean);
|
conf = castArray(conf).filter(Boolean);
|
||||||
if (required) {
|
if (required) {
|
||||||
return conf.length >= 1 && validateSteps(conf);
|
return conf.length >= 1 && validateSteps(conf);
|
||||||
@ -47,23 +47,14 @@ export function validateStep({ required }, conf) {
|
|||||||
return conf.length === 0 || validateSteps(conf);
|
return conf.length === 0 || validateSteps(conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadPlugin({ cwd }, name, pluginsPath) {
|
export async function loadPlugin({cwd}, name, pluginsPath) {
|
||||||
const basePath = pluginsPath[name]
|
const basePath = pluginsPath[name]
|
||||||
? dirname(resolveFrom.silent(__dirname, pluginsPath[name]) || resolveFrom(cwd, pluginsPath[name]))
|
? dirname(resolveFrom.silent(__dirname, pluginsPath[name]) || resolveFrom(cwd, pluginsPath[name]))
|
||||||
: __dirname;
|
: __dirname;
|
||||||
|
|
||||||
if (isFunction(name)) {
|
// See https://github.com/mysticatea/eslint-plugin-node/issues/250
|
||||||
return name;
|
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
||||||
}
|
return isFunction(name) ? name : (await import(resolveFrom.silent(basePath, name) || resolveFrom(cwd, name))).default;
|
||||||
|
|
||||||
const file = resolveFrom.silent(basePath, name) || resolveFrom(cwd, name);
|
|
||||||
const { default: cjsExport, ...esmNamedExports } = await import(`file://${file}`);
|
|
||||||
|
|
||||||
if (cjsExport) {
|
|
||||||
return cjsExport;
|
|
||||||
}
|
|
||||||
|
|
||||||
return esmNamedExports;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseConfig(plugin) {
|
export function parseConfig(plugin) {
|
||||||
@ -72,7 +63,7 @@ export function parseConfig(plugin) {
|
|||||||
if (isArray(plugin)) {
|
if (isArray(plugin)) {
|
||||||
[path, config] = plugin;
|
[path, config] = plugin;
|
||||||
} else if (isPlainObject(plugin) && !isNil(plugin.path)) {
|
} else if (isPlainObject(plugin) && !isNil(plugin.path)) {
|
||||||
({ path, ...config } = plugin);
|
({path, ...config} = plugin);
|
||||||
} else {
|
} else {
|
||||||
path = plugin;
|
path = plugin;
|
||||||
}
|
}
|
||||||
|
BIN
media/semantic-release-cli.png
Normal file
BIN
media/semantic-release-cli.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
20810
package-lock.json
generated
20810
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
103
package.json
103
package.json
@ -6,8 +6,7 @@
|
|||||||
"author": "Stephan Bönnemann <stephan@boennemann.me> (http://boennemann.me)",
|
"author": "Stephan Bönnemann <stephan@boennemann.me> (http://boennemann.me)",
|
||||||
"ava": {
|
"ava": {
|
||||||
"files": [
|
"files": [
|
||||||
"test/**/*.test.js",
|
"test/**/*.test.js"
|
||||||
"!test/integration.test.js"
|
|
||||||
],
|
],
|
||||||
"nodeArguments": [
|
"nodeArguments": [
|
||||||
"--loader=testdouble",
|
"--loader=testdouble",
|
||||||
@ -27,68 +26,62 @@
|
|||||||
"Matt Travi <npm@travi.org> (https://matt.travi.org/)"
|
"Matt Travi <npm@travi.org> (https://matt.travi.org/)"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@semantic-release/commit-analyzer": "^11.0.0",
|
"@semantic-release/commit-analyzer": "^9.0.2",
|
||||||
"@semantic-release/error": "^4.0.0",
|
"@semantic-release/error": "^3.0.0",
|
||||||
"@semantic-release/github": "^9.0.0",
|
"@semantic-release/github": "^8.0.0",
|
||||||
"@semantic-release/npm": "^11.0.0",
|
"@semantic-release/npm": "^9.0.0",
|
||||||
"@semantic-release/release-notes-generator": "^12.0.0",
|
"@semantic-release/release-notes-generator": "^10.0.0",
|
||||||
"aggregate-error": "^5.0.0",
|
"aggregate-error": "^4.0.1",
|
||||||
"cosmiconfig": "^9.0.0",
|
"cosmiconfig": "^7.0.0",
|
||||||
"debug": "^4.0.0",
|
"debug": "^4.0.0",
|
||||||
"env-ci": "^11.0.0",
|
"env-ci": "^8.0.0",
|
||||||
"execa": "^8.0.0",
|
"execa": "^6.1.0",
|
||||||
"figures": "^6.0.0",
|
"figures": "^5.0.0",
|
||||||
"find-versions": "^5.1.0",
|
"find-versions": "^5.1.0",
|
||||||
"get-stream": "^6.0.0",
|
"get-stream": "^6.0.0",
|
||||||
"git-log-parser": "^1.2.0",
|
"git-log-parser": "^1.2.0",
|
||||||
"hook-std": "^3.0.0",
|
"hook-std": "^3.0.0",
|
||||||
"hosted-git-info": "^7.0.0",
|
"hosted-git-info": "^5.1.0",
|
||||||
"import-from-esm": "^1.3.1",
|
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^11.0.0",
|
"marked": "^4.1.0",
|
||||||
"marked-terminal": "^7.0.0",
|
"marked-terminal": "^5.1.1",
|
||||||
"micromatch": "^4.0.2",
|
"micromatch": "^4.0.2",
|
||||||
"p-each-series": "^3.0.0",
|
"p-each-series": "^3.0.0",
|
||||||
"p-reduce": "^3.0.0",
|
"p-reduce": "^3.0.0",
|
||||||
"read-pkg-up": "^11.0.0",
|
"read-pkg-up": "^9.1.0",
|
||||||
"resolve-from": "^5.0.0",
|
"resolve-from": "^5.0.0",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"semver-diff": "^4.0.0",
|
"semver-diff": "^3.1.1",
|
||||||
"signale": "^1.2.1",
|
"signale": "^1.2.1",
|
||||||
"yargs": "^17.5.1"
|
"yargs": "^17.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ava": "6.1.1",
|
"ava": "4.3.3",
|
||||||
"c8": "9.1.0",
|
"c8": "7.12.0",
|
||||||
"clear-module": "4.1.2",
|
"clear-module": "4.1.2",
|
||||||
"codecov": "3.8.3",
|
"codecov": "3.8.3",
|
||||||
"cz-conventional-changelog": "3.3.0",
|
"delay": "5.0.0",
|
||||||
"dockerode": "4.0.2",
|
"dockerode": "3.3.4",
|
||||||
"file-url": "4.0.0",
|
"file-url": "^4.0.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "^10.1.0",
|
||||||
"got": "14.2.0",
|
"got": "^12.5.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"lockfile-lint": "4.12.1",
|
"mockserver-client": "5.14.0",
|
||||||
"ls-engines": "0.9.1",
|
"nock": "13.2.9",
|
||||||
"mockserver-client": "5.15.0",
|
"p-retry": "^5.1.1",
|
||||||
"nock": "13.5.1",
|
"prettier": "^2.7.1",
|
||||||
"npm-run-all2": "6.1.2",
|
"sinon": "14.0.0",
|
||||||
"p-retry": "6.2.0",
|
|
||||||
"prettier": "3.2.5",
|
|
||||||
"publint": "0.2.7",
|
|
||||||
"sinon": "17.0.1",
|
|
||||||
"stream-buffers": "3.0.2",
|
"stream-buffers": "3.0.2",
|
||||||
"tempy": "3.1.0",
|
"tempy": "^3.0.0",
|
||||||
"testdouble": "3.20.1"
|
"testdouble": "3.16.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.8.1"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"bin",
|
"bin",
|
||||||
"docs",
|
"docs",
|
||||||
"lib",
|
"lib",
|
||||||
"index.d.ts",
|
|
||||||
"index.js",
|
"index.js",
|
||||||
"cli.js"
|
"cli.js"
|
||||||
],
|
],
|
||||||
@ -105,8 +98,7 @@
|
|||||||
"version"
|
"version"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
|
||||||
"c8": {
|
"c8": {
|
||||||
"include": [
|
"include": [
|
||||||
"lib/**/*.js",
|
"lib/**/*.js",
|
||||||
@ -120,46 +112,29 @@
|
|||||||
],
|
],
|
||||||
"all": true
|
"all": true
|
||||||
},
|
},
|
||||||
"lockfile-lint": {
|
|
||||||
"path": "package-lock.json",
|
|
||||||
"type": "npm",
|
|
||||||
"validate-https": true,
|
|
||||||
"allowed-hosts": [
|
|
||||||
"npm"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
"trailingComma": "es5"
|
"trailingComma": "es5"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public"
|
||||||
"provenance": true
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/semantic-release/semantic-release.git"
|
"url": "git+https://github.com/semantic-release/semantic-release.git"
|
||||||
},
|
},
|
||||||
"config": {
|
|
||||||
"commitizen": {
|
|
||||||
"path": "./node_modules/cz-conventional-changelog"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codecov": "codecov -f coverage/coverage-final.json",
|
"codecov": "codecov -f coverage/coverage-final.json",
|
||||||
"lint:prettier": "prettier --check \"*.{js,json,md}\" \".github/**/*.{md,yml}\" \"docs/**/*.md\" \"{bin,lib,test}/**/*.js\"",
|
"lint": "prettier --check \"*.{js,json,md}\" \".github/**/*.{md,yml}\" \"docs/**/*.md\" \"{bin,lib,test}/*.js\"",
|
||||||
"lint:prettier:fix": "prettier --write \"*.{js,json,md}\" \".github/**/*.{md,yml}\" \"docs/**/*.md\" \"{bin,lib,test}/**/*.js\"",
|
"lint:fix": "prettier --write \"*.{js,json,md}\" \".github/**/*.{md,yml}\" \"docs/**/*.md\" \"{bin,lib,test}/*.js\"",
|
||||||
"lint:lockfile": "lockfile-lint",
|
"pretest": "npm run lint",
|
||||||
"lint:engines": "ls-engines",
|
|
||||||
"lint:publish": "publint --strict",
|
|
||||||
"semantic-release": "./bin/semantic-release.js",
|
"semantic-release": "./bin/semantic-release.js",
|
||||||
"test": "npm-run-all --print-label --parallel lint:* --parallel test:*",
|
"test": "c8 ava --verbose",
|
||||||
"test:unit": "c8 ava --verbose",
|
"test:ci": "c8 ava --verbose"
|
||||||
"test:integration": "ava --verbose test/integration.test.js"
|
|
||||||
},
|
},
|
||||||
"renovate": {
|
"renovate": {
|
||||||
"extends": [
|
"extends": [
|
||||||
"github>semantic-release/.github:renovate-config"
|
"github>semantic-release/.github"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,277 +1,277 @@
|
|||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import { union } from "lodash-es";
|
import {union} from 'lodash-es';
|
||||||
import semver from "semver";
|
import semver from 'semver';
|
||||||
import * as td from "testdouble";
|
import * as td from 'testdouble';
|
||||||
|
|
||||||
const getBranch = (branches, branch) => branches.find(({ name }) => name === branch);
|
const getBranch = (branches, branch) => branches.find(({name}) => name === branch);
|
||||||
const release = (branches, name, version) => getBranch(branches, name).tags.push({ version });
|
const release = (branches, name, version) => getBranch(branches, name).tags.push({version});
|
||||||
const merge = (branches, source, target, tag) => {
|
const merge = (branches, source, target, tag) => {
|
||||||
getBranch(branches, target).tags = union(
|
getBranch(branches, target).tags = union(
|
||||||
getBranch(branches, source).tags.filter(({ version }) => !tag || semver.cmp(version, "<=", tag)),
|
getBranch(branches, source).tags.filter(({version}) => !tag || semver.cmp(version, '<=', tag)),
|
||||||
getBranch(branches, target).tags
|
getBranch(branches, target).tags
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const remoteBranches = [];
|
const remoteBranches = [];
|
||||||
const repositoryUrl = "repositoryUrl";
|
const repositoryUrl = 'repositoryUrl';
|
||||||
let expand, getTags, getBranches;
|
let expand, getTags, getBranches;
|
||||||
|
|
||||||
test.beforeEach(async (t) => {
|
test.beforeEach(async (t) => {
|
||||||
getTags = (await td.replaceEsm("../../lib/branches/get-tags.js")).default;
|
getTags = (await td.replaceEsm('../../lib/branches/get-tags.js')).default;
|
||||||
expand = (await td.replaceEsm("../../lib/branches/expand.js")).default;
|
expand = (await td.replaceEsm('../../lib/branches/expand.js')).default;
|
||||||
getBranches = (await import("../../lib/branches/index.js")).default;
|
getBranches = (await import('../../lib/branches/index.js')).default;
|
||||||
});
|
})
|
||||||
|
|
||||||
test.afterEach.always((t) => {
|
test.afterEach.always((t) => {
|
||||||
td.reset();
|
td.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial("Enforce ranges with branching release workflow", async (t) => {
|
test.serial('Enforce ranges with branching release workflow', async (t) => {
|
||||||
const branches = [
|
const branches = [
|
||||||
{ name: "1.x", tags: [] },
|
{name: '1.x', tags: []},
|
||||||
{ name: "1.0.x", tags: [] },
|
{name: '1.0.x', tags: []},
|
||||||
{ name: "master", tags: [] },
|
{name: 'master', tags: []},
|
||||||
{ name: "next", tags: [] },
|
{name: 'next', tags: []},
|
||||||
{ name: "next-major", tags: [] },
|
{name: 'next-major', tags: []},
|
||||||
{ name: "beta", prerelease: true, tags: [] },
|
{name: 'beta', prerelease: true, tags: []},
|
||||||
{ name: "alpha", prerelease: true, tags: [] },
|
{name: 'alpha', prerelease: true, tags: []},
|
||||||
];
|
];
|
||||||
const context = { options: { branches } };
|
const context = {options: {branches}};
|
||||||
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
|
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
|
||||||
td.when(getTags(context, remoteBranches)).thenResolve(branches);
|
td.when(getTags(context, remoteBranches)).thenResolve(branches);
|
||||||
|
|
||||||
let result = (await getBranches(repositoryUrl, "master", context)).map(({ name, range }) => ({ name, range }));
|
let result = (await getBranches(repositoryUrl, 'master', context)).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.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, '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");
|
t.is(getBranch(result, 'master').range, '>=1.0.0');
|
||||||
t.is(getBranch(result, "next").range, ">=1.0.0");
|
t.is(getBranch(result, 'next').range, '>=1.0.0');
|
||||||
t.is(getBranch(result, "next-major").range, ">=1.0.0");
|
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
|
||||||
|
|
||||||
release(branches, "master", "1.0.0");
|
release(branches, 'master', '1.0.0');
|
||||||
result = (await getBranches("repositoryUrl", "master", context)).map(({ name, range }) => ({ name, range }));
|
result = (await getBranches('repositoryUrl', 'master', context)).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.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, '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");
|
t.is(getBranch(result, 'master').range, '>=1.0.0');
|
||||||
t.is(getBranch(result, "next").range, ">=1.0.0");
|
t.is(getBranch(result, 'next').range, '>=1.0.0');
|
||||||
t.is(getBranch(result, "next-major").range, ">=1.0.0");
|
t.is(getBranch(result, 'next-major').range, '>=1.0.0');
|
||||||
|
|
||||||
release(branches, "master", "1.0.1");
|
release(branches, 'master', '1.0.1');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
}));
|
}));
|
||||||
t.is(getBranch(result, "master").range, ">=1.0.1", "Can release only > than 1.0.1 on master");
|
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').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");
|
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');
|
||||||
merge(branches, "master", "next-major");
|
merge(branches, 'master', 'next-major');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
}));
|
}));
|
||||||
t.is(getBranch(result, "master").range, ">=1.0.1", "Can release only > than 1.0.1 on master");
|
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').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");
|
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.0');
|
||||||
release(branches, "next", "1.1.1");
|
release(branches, 'next', '1.1.1');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
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, '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').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");
|
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.0');
|
||||||
release(branches, "next-major", "2.0.1");
|
release(branches, 'next-major', '2.0.1');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
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, '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').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");
|
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");
|
merge(branches, 'next-major', 'beta');
|
||||||
release(branches, "beta", "3.0.0-beta.1");
|
release(branches, 'beta', '3.0.0-beta.1');
|
||||||
merge(branches, "beta", "alpha");
|
merge(branches, 'beta', 'alpha');
|
||||||
release(branches, "alpha", "4.0.0-alpha.1");
|
release(branches, 'alpha', '4.0.0-alpha.1');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
}));
|
}));
|
||||||
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, '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.0.x');
|
||||||
merge(branches, "master", "1.x");
|
merge(branches, 'master', '1.x');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
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, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
|
||||||
t.is(
|
t.is(
|
||||||
getBranch(result, "1.0.x").range,
|
getBranch(result, '1.0.x').range,
|
||||||
">=1.0.1 <1.0.1",
|
'>=1.0.1 <1.0.1',
|
||||||
"Cannot release on 1.0.x before >= 1.1.0 is released on master"
|
'Cannot release on 1.0.x before >= 1.1.0 is released on master'
|
||||||
);
|
);
|
||||||
t.is(getBranch(result, "1.x").range, ">=1.1.0 <1.0.1", "Cannot release on 1.x before >= 1.2.0 is released on master");
|
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.1', 'Cannot release on 1.x before >= 1.2.0 is released on master');
|
||||||
|
|
||||||
release(branches, "master", "1.0.2");
|
release(branches, 'master', '1.0.2');
|
||||||
release(branches, "master", "1.0.3");
|
release(branches, 'master', '1.0.3');
|
||||||
release(branches, "master", "1.0.4");
|
release(branches, 'master', '1.0.4');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
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, 'master').range, '>=1.0.4 <1.1.0', 'Can release only patch, > than 1.0.4 on master');
|
||||||
t.is(
|
t.is(
|
||||||
getBranch(result, "1.0.x").range,
|
getBranch(result, '1.0.x').range,
|
||||||
">=1.0.1 <1.0.2",
|
'>=1.0.1 <1.0.2',
|
||||||
"Cannot release on 1.0.x before >= 1.1.0 is released on master"
|
'Cannot release on 1.0.x before >= 1.1.0 is released on master'
|
||||||
);
|
);
|
||||||
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");
|
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");
|
merge(branches, 'next', 'master');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
t.is(getBranch(result, "master").range, ">=1.1.1", "Can release only > than 1.1.1 on master");
|
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').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, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
|
||||||
t.is(
|
t.is(
|
||||||
getBranch(result, "1.0.x").range,
|
getBranch(result, '1.0.x').range,
|
||||||
">=1.0.1 <1.0.2",
|
'>=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 from 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");
|
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");
|
merge(branches, 'master', '1.0.x', '1.0.4');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
}));
|
}));
|
||||||
t.is(getBranch(result, "master").range, ">=1.1.1", "Can release only > than 1.1.1 on master");
|
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.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");
|
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");
|
merge(branches, 'master', '1.x');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
}));
|
}));
|
||||||
t.is(getBranch(result, "master").range, ">=1.1.1", "Can release only > than 1.1.1 on master");
|
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.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");
|
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-major', 'next');
|
||||||
merge(branches, "next", "master");
|
merge(branches, 'next', 'master');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
}));
|
}));
|
||||||
t.is(getBranch(result, "master").range, ">=2.0.1", "Can release only > than 2.0.1 on master");
|
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').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, '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");
|
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");
|
merge(branches, 'beta', 'master');
|
||||||
release(branches, "master", "3.0.0");
|
release(branches, 'master', '3.0.0');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
}));
|
}));
|
||||||
t.is(getBranch(result, "master").range, ">=3.0.0", "Can release only > than 3.0.0 on master");
|
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').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");
|
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: [] });
|
branches.push({name: '1.1.x', tags: []});
|
||||||
merge(branches, "1.x", "1.1.x");
|
merge(branches, '1.x', '1.1.x');
|
||||||
result = (await getBranches("repositoryUrl", "master", { options: { branches } })).map(({ name, range }) => ({
|
result = (await getBranches('repositoryUrl', 'master', {options: {branches}})).map(({name, range}) => ({
|
||||||
name,
|
name,
|
||||||
range,
|
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.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.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");
|
t.is(getBranch(result, '1.x').range, '>=1.2.0 <2.0.0', 'Can release on 1.x only within range');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial("Throw SemanticReleaseError for invalid configurations", async (t) => {
|
test.serial('Throw SemanticReleaseError for invalid configurations', async (t) => {
|
||||||
const branches = [
|
const branches = [
|
||||||
{ name: "123", range: "123", tags: [] },
|
{name: '123', range: '123', tags: []},
|
||||||
{ name: "1.x", tags: [] },
|
{name: '1.x', tags: []},
|
||||||
{ name: "maintenance-1", range: "1.x", tags: [] },
|
{name: 'maintenance-1', range: '1.x', tags: []},
|
||||||
{ name: "1.x.x", tags: [] },
|
{name: '1.x.x', tags: []},
|
||||||
{ name: "beta", prerelease: "", tags: [] },
|
{name: 'beta', prerelease: '', tags: []},
|
||||||
{ name: "alpha", prerelease: "alpha", tags: [] },
|
{name: 'alpha', prerelease: 'alpha', tags: []},
|
||||||
{ name: "preview", prerelease: "alpha", tags: [] },
|
{name: 'preview', prerelease: 'alpha', tags: []},
|
||||||
];
|
];
|
||||||
const context = { options: { branches } };
|
const context = {options: {branches}};
|
||||||
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
|
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
|
||||||
td.when(getTags(context, remoteBranches)).thenResolve(branches);
|
td.when(getTags(context, remoteBranches)).thenResolve(branches);
|
||||||
|
|
||||||
const error = await t.throwsAsync(getBranches(repositoryUrl, "master", context));
|
const error = await t.throwsAsync(getBranches(repositoryUrl, 'master', context));
|
||||||
const errors = [...error.errors];
|
const errors = [...error.errors];
|
||||||
|
|
||||||
t.is(errors[0].name, "SemanticReleaseError");
|
t.is(errors[0].name, 'SemanticReleaseError');
|
||||||
t.is(errors[0].code, "EMAINTENANCEBRANCH");
|
t.is(errors[0].code, 'EMAINTENANCEBRANCH');
|
||||||
t.truthy(errors[0].message);
|
t.truthy(errors[0].message);
|
||||||
t.truthy(errors[0].details);
|
t.truthy(errors[0].details);
|
||||||
t.is(errors[1].name, "SemanticReleaseError");
|
t.is(errors[1].name, 'SemanticReleaseError');
|
||||||
t.is(errors[1].code, "EMAINTENANCEBRANCHES");
|
t.is(errors[1].code, 'EMAINTENANCEBRANCHES');
|
||||||
t.truthy(errors[1].message);
|
t.truthy(errors[1].message);
|
||||||
t.truthy(errors[1].details);
|
t.truthy(errors[1].details);
|
||||||
t.is(errors[2].name, "SemanticReleaseError");
|
t.is(errors[2].name, 'SemanticReleaseError');
|
||||||
t.is(errors[2].code, "EPRERELEASEBRANCH");
|
t.is(errors[2].code, 'EPRERELEASEBRANCH');
|
||||||
t.truthy(errors[2].message);
|
t.truthy(errors[2].message);
|
||||||
t.truthy(errors[2].details);
|
t.truthy(errors[2].details);
|
||||||
t.is(errors[3].name, "SemanticReleaseError");
|
t.is(errors[3].name, 'SemanticReleaseError');
|
||||||
t.is(errors[3].code, "EPRERELEASEBRANCHES");
|
t.is(errors[3].code, 'EPRERELEASEBRANCHES');
|
||||||
t.truthy(errors[3].message);
|
t.truthy(errors[3].message);
|
||||||
t.truthy(errors[3].details);
|
t.truthy(errors[3].details);
|
||||||
t.is(errors[4].name, "SemanticReleaseError");
|
t.is(errors[4].name, 'SemanticReleaseError');
|
||||||
t.is(errors[4].code, "ERELEASEBRANCHES");
|
t.is(errors[4].code, 'ERELEASEBRANCHES');
|
||||||
t.truthy(errors[4].message);
|
t.truthy(errors[4].message);
|
||||||
t.truthy(errors[4].details);
|
t.truthy(errors[4].details);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial("Throw a SemanticReleaseError if there is duplicate branches", async (t) => {
|
test.serial('Throw a SemanticReleaseError if there is duplicate branches', async (t) => {
|
||||||
const branches = [
|
const branches = [
|
||||||
{ name: "master", tags: [] },
|
{name: 'master', tags: []},
|
||||||
{ name: "master", tags: [] },
|
{name: 'master', tags: []},
|
||||||
];
|
];
|
||||||
const context = { options: { branches } };
|
const context = {options: {branches}};
|
||||||
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
|
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
|
||||||
td.when(getTags(context, remoteBranches)).thenResolve(branches);
|
td.when(getTags(context, remoteBranches)).thenResolve(branches);
|
||||||
|
|
||||||
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, "master", context))).errors];
|
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, 'master', context))).errors];
|
||||||
|
|
||||||
t.is(errors[0].name, "SemanticReleaseError");
|
t.is(errors[0].name, 'SemanticReleaseError');
|
||||||
t.is(errors[0].code, "EDUPLICATEBRANCHES");
|
t.is(errors[0].code, 'EDUPLICATEBRANCHES');
|
||||||
t.truthy(errors[0].message);
|
t.truthy(errors[0].message);
|
||||||
t.truthy(errors[0].details);
|
t.truthy(errors[0].details);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial("Throw a SemanticReleaseError for each invalid branch name", async (t) => {
|
test.serial('Throw a SemanticReleaseError for each invalid branch name', async (t) => {
|
||||||
const branches = [
|
const branches = [
|
||||||
{ name: "~master", tags: [] },
|
{name: '~master', tags: []},
|
||||||
{ name: "^master", tags: [] },
|
{name: '^master', tags: []},
|
||||||
];
|
];
|
||||||
const context = { options: { branches } };
|
const context = {options: {branches}};
|
||||||
const remoteBranches = [];
|
const remoteBranches = [];
|
||||||
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
|
td.when(expand(repositoryUrl, context, branches)).thenResolve(remoteBranches);
|
||||||
td.when(getTags(context, remoteBranches)).thenResolve(branches);
|
td.when(getTags(context, remoteBranches)).thenResolve(branches);
|
||||||
|
|
||||||
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, "master", context))).errors];
|
const errors = [...(await t.throwsAsync(getBranches(repositoryUrl, 'master', context))).errors];
|
||||||
|
|
||||||
t.is(errors[0].name, "SemanticReleaseError");
|
t.is(errors[0].name, 'SemanticReleaseError');
|
||||||
t.is(errors[0].code, "EINVALIDBRANCHNAME");
|
t.is(errors[0].code, 'EINVALIDBRANCHNAME');
|
||||||
t.truthy(errors[0].message);
|
t.truthy(errors[0].message);
|
||||||
t.truthy(errors[0].details);
|
t.truthy(errors[0].details);
|
||||||
t.is(errors[1].name, "SemanticReleaseError");
|
t.is(errors[1].name, 'SemanticReleaseError');
|
||||||
t.is(errors[1].code, "EINVALIDBRANCHNAME");
|
t.is(errors[1].code, 'EINVALIDBRANCHNAME');
|
||||||
t.truthy(errors[1].message);
|
t.truthy(errors[1].message);
|
||||||
t.truthy(errors[1].details);
|
t.truthy(errors[1].details);
|
||||||
});
|
});
|
||||||
|
@ -1,54 +1,54 @@
|
|||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import expand from "../../lib/branches/expand.js";
|
import expand from '../../lib/branches/expand.js';
|
||||||
import { gitCheckout, gitCommits, gitPush, gitRepo } from "../helpers/git-utils.js";
|
import {gitCheckout, gitCommits, gitPush, gitRepo} from '../helpers/git-utils.js';
|
||||||
|
|
||||||
test("Expand branches defined with globs", async (t) => {
|
test('Expand branches defined with globs', async (t) => {
|
||||||
const { cwd, repositoryUrl } = await gitRepo(true);
|
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||||
await gitCommits(["First"], { cwd });
|
await gitCommits(['First'], {cwd});
|
||||||
await gitPush(repositoryUrl, "master", { cwd });
|
await gitPush(repositoryUrl, 'master', {cwd});
|
||||||
await gitCheckout("1.0.x", true, { cwd });
|
await gitCheckout('1.0.x', true, {cwd});
|
||||||
await gitCommits(["Second"], { cwd });
|
await gitCommits(['Second'], {cwd});
|
||||||
await gitPush(repositoryUrl, "1.0.x", { cwd });
|
await gitPush(repositoryUrl, '1.0.x', {cwd});
|
||||||
await gitCheckout("1.x.x", true, { cwd });
|
await gitCheckout('1.x.x', true, {cwd});
|
||||||
await gitCommits(["Third"], { cwd });
|
await gitCommits(['Third'], {cwd});
|
||||||
await gitPush(repositoryUrl, "1.x.x", { cwd });
|
await gitPush(repositoryUrl, '1.x.x', {cwd});
|
||||||
await gitCheckout("2.x", true, { cwd });
|
await gitCheckout('2.x', true, {cwd});
|
||||||
await gitCommits(["Fourth"], { cwd });
|
await gitCommits(['Fourth'], {cwd});
|
||||||
await gitPush(repositoryUrl, "2.x", { cwd });
|
await gitPush(repositoryUrl, '2.x', {cwd});
|
||||||
await gitCheckout("next", true, { cwd });
|
await gitCheckout('next', true, {cwd});
|
||||||
await gitCommits(["Fifth"], { cwd });
|
await gitCommits(['Fifth'], {cwd});
|
||||||
await gitPush(repositoryUrl, "next", { cwd });
|
await gitPush(repositoryUrl, 'next', {cwd});
|
||||||
await gitCheckout("pre/foo", true, { cwd });
|
await gitCheckout('pre/foo', true, {cwd});
|
||||||
await gitCommits(["Sixth"], { cwd });
|
await gitCommits(['Sixth'], {cwd});
|
||||||
await gitPush(repositoryUrl, "pre/foo", { cwd });
|
await gitPush(repositoryUrl, 'pre/foo', {cwd});
|
||||||
await gitCheckout("pre/bar", true, { cwd });
|
await gitCheckout('pre/bar', true, {cwd});
|
||||||
await gitCommits(["Seventh"], { cwd });
|
await gitCommits(['Seventh'], {cwd});
|
||||||
await gitPush(repositoryUrl, "pre/bar", { cwd });
|
await gitPush(repositoryUrl, 'pre/bar', {cwd});
|
||||||
await gitCheckout("beta", true, { cwd });
|
await gitCheckout('beta', true, {cwd});
|
||||||
await gitCommits(["Eighth"], { cwd });
|
await gitCommits(['Eighth'], {cwd});
|
||||||
await gitPush(repositoryUrl, "beta", { cwd });
|
await gitPush(repositoryUrl, 'beta', {cwd});
|
||||||
|
|
||||||
const branches = [
|
const branches = [
|
||||||
// Should match all maintenance type branches
|
// Should match all maintenance type branches
|
||||||
{ name: "+([0-9])?(.{+([0-9]),x}).x" },
|
{name: '+([0-9])?(.{+([0-9]),x}).x'},
|
||||||
{ name: "master", channel: "latest" },
|
{name: 'master', channel: 'latest'},
|
||||||
{ name: "next" },
|
{name: 'next'},
|
||||||
{ name: "pre/{foo,bar}", channel: `\${name.replace(/^pre\\//g, '')}`, prerelease: true },
|
{name: 'pre/{foo,bar}', channel: `\${name.replace(/^pre\\//g, '')}`, prerelease: true},
|
||||||
// Should be ignored as there is no matching branches in the repo
|
// Should be ignored as there is no matching branches in the repo
|
||||||
{ name: "missing" },
|
{name: 'missing'},
|
||||||
// Should be ignored as the matching branch in the repo is already matched by `/^pre\\/(\\w+)$/gi`
|
// Should be ignored as the matching branch in the repo is already matched by `/^pre\\/(\\w+)$/gi`
|
||||||
{ name: "*/foo", channel: "foo", prerelease: "foo" },
|
{name: '*/foo', channel: 'foo', prerelease: 'foo'},
|
||||||
{ name: "beta", channel: `channel-\${name}`, prerelease: true },
|
{name: 'beta', channel: `channel-\${name}`, prerelease: true},
|
||||||
];
|
];
|
||||||
|
|
||||||
t.deepEqual(await expand(repositoryUrl, { cwd }, branches), [
|
t.deepEqual(await expand(repositoryUrl, {cwd}, branches), [
|
||||||
{ name: "1.0.x" },
|
{name: '1.0.x'},
|
||||||
{ name: "1.x.x" },
|
{name: '1.x.x'},
|
||||||
{ name: "2.x" },
|
{name: '2.x'},
|
||||||
{ name: "master", channel: "latest" },
|
{name: 'master', channel: 'latest'},
|
||||||
{ name: "next" },
|
{name: 'next'},
|
||||||
{ name: "pre/bar", channel: "bar", prerelease: true },
|
{name: 'pre/bar', channel: 'bar', prerelease: true},
|
||||||
{ name: "pre/foo", channel: "foo", prerelease: true },
|
{name: 'pre/foo', channel: 'foo', prerelease: true},
|
||||||
{ name: "beta", channel: "channel-beta", prerelease: true },
|
{name: 'beta', channel: 'channel-beta', prerelease: true},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -1,156 +1,153 @@
|
|||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import getTags from "../../lib/branches/get-tags.js";
|
import getTags from '../../lib/branches/get-tags.js';
|
||||||
import { gitAddNote, gitCheckout, gitCommits, gitRepo, gitTagVersion } from "../helpers/git-utils.js";
|
import {gitAddNote, gitCheckout, gitCommits, gitRepo, gitTagVersion} from '../helpers/git-utils.js';
|
||||||
|
|
||||||
test("Get the valid tags", async (t) => {
|
test('Get the valid tags', async (t) => {
|
||||||
const { cwd } = await gitRepo();
|
const {cwd} = await gitRepo();
|
||||||
const commits = await gitCommits(["First"], { cwd });
|
const commits = await gitCommits(['First'], {cwd});
|
||||||
await gitTagVersion("foo", undefined, { cwd });
|
await gitTagVersion('foo', undefined, {cwd});
|
||||||
await gitTagVersion("v2.0.0", undefined, { cwd });
|
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||||
commits.push(...(await gitCommits(["Second"], { cwd })));
|
commits.push(...(await gitCommits(['Second'], {cwd})));
|
||||||
await gitTagVersion("v1.0.0", undefined, { cwd });
|
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||||
commits.push(...(await gitCommits(["Third"], { cwd })));
|
commits.push(...(await gitCommits(['Third'], {cwd})));
|
||||||
await gitTagVersion("v3.0", undefined, { cwd });
|
await gitTagVersion('v3.0', undefined, {cwd});
|
||||||
commits.push(...(await gitCommits(["Fourth"], { cwd })));
|
commits.push(...(await gitCommits(['Fourth'], {cwd})));
|
||||||
await gitTagVersion("v3.0.0-beta.1", undefined, { cwd });
|
await gitTagVersion('v3.0.0-beta.1', undefined, {cwd});
|
||||||
|
|
||||||
const result = await getTags({ cwd, options: { tagFormat: `v\${version}` } }, [{ name: "master" }]);
|
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [{name: 'master'}]);
|
||||||
|
|
||||||
t.deepEqual(result, [
|
t.deepEqual(result, [
|
||||||
{
|
{
|
||||||
name: "master",
|
name: 'master',
|
||||||
tags: [
|
tags: [
|
||||||
{ gitTag: "v1.0.0", version: "1.0.0", channels: [null] },
|
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
|
||||||
{ gitTag: "v2.0.0", version: "2.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] },
|
{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 from multiple branches', async (t) => {
|
||||||
const { cwd } = await gitRepo();
|
const {cwd} = await gitRepo();
|
||||||
await gitCommits(["First"], { cwd });
|
await gitCommits(['First'], {cwd});
|
||||||
await gitTagVersion("v1.0.0", undefined, { cwd });
|
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||||
await gitAddNote(JSON.stringify({ channels: [null, "1.x"] }), "v1.0.0", { cwd });
|
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.0.0', {cwd});
|
||||||
await gitCommits(["Second"], { cwd });
|
await gitCommits(['Second'], {cwd});
|
||||||
await gitTagVersion("v1.1.0", undefined, { cwd });
|
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||||
await gitAddNote(JSON.stringify({ channels: [null, "1.x"] }), "v1.1.0", { cwd });
|
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.1.0', {cwd});
|
||||||
await gitCheckout("1.x", true, { cwd });
|
await gitCheckout('1.x', true, {cwd});
|
||||||
await gitCheckout("master", false, { cwd });
|
await gitCheckout('master', false, {cwd});
|
||||||
await gitCommits(["Third"], { cwd });
|
await gitCommits(['Third'], {cwd});
|
||||||
await gitTagVersion("v2.0.0", undefined, { cwd });
|
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||||
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v2.0.0", { cwd });
|
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
|
||||||
await gitCheckout("next", true, { cwd });
|
await gitCheckout('next', true, {cwd});
|
||||||
await gitCommits(["Fourth"], { cwd });
|
await gitCommits(['Fourth'], {cwd});
|
||||||
await gitTagVersion("v3.0.0", undefined, { cwd });
|
await gitTagVersion('v3.0.0', undefined, {cwd});
|
||||||
await gitAddNote(JSON.stringify({ channels: ["next"] }), "v3.0.0", { cwd });
|
await gitAddNote(JSON.stringify({channels: ['next']}), 'v3.0.0', {cwd});
|
||||||
|
|
||||||
const result = await getTags({ cwd, options: { tagFormat: `v\${version}` } }, [
|
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [
|
||||||
{ name: "1.x" },
|
{name: '1.x'},
|
||||||
{ name: "master" },
|
{name: 'master'},
|
||||||
{ name: "next" },
|
{name: 'next'},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
t.deepEqual(result, [
|
t.deepEqual(result, [
|
||||||
{
|
{
|
||||||
name: "1.x",
|
name: '1.x',
|
||||||
tags: [
|
tags: [
|
||||||
{ gitTag: "v1.0.0", version: "1.0.0", channels: [null, "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"] },
|
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null, '1.x']},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "master",
|
name: 'master',
|
||||||
tags: [...result[0].tags, { gitTag: "v2.0.0", version: "2.0.0", channels: [null, "next"] }],
|
tags: [...result[0].tags, {gitTag: 'v2.0.0', version: '2.0.0', channels: [null, 'next']}],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "next",
|
name: 'next',
|
||||||
tags: [...result[1].tags, { gitTag: "v3.0.0", version: "3.0.0", channels: ["next"] }],
|
tags: [...result[1].tags, {gitTag: 'v3.0.0', version: '3.0.0', channels: ['next']}],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Return branches with and empty tags array if no valid tag is found", async (t) => {
|
test('Return branches with and empty tags array if no valid tag is found', async (t) => {
|
||||||
const { cwd } = await gitRepo();
|
const {cwd} = await gitRepo();
|
||||||
await gitCommits(["First"], { cwd });
|
await gitCommits(['First'], {cwd});
|
||||||
await gitTagVersion("foo", undefined, { cwd });
|
await gitTagVersion('foo', undefined, {cwd});
|
||||||
await gitCommits(["Second"], { cwd });
|
await gitCommits(['Second'], {cwd});
|
||||||
await gitTagVersion("v2.0.x", undefined, { cwd });
|
await gitTagVersion('v2.0.x', undefined, {cwd});
|
||||||
await gitCommits(["Third"], { cwd });
|
await gitCommits(['Third'], {cwd});
|
||||||
await gitTagVersion("v3.0", undefined, { cwd });
|
await gitTagVersion('v3.0', undefined, {cwd});
|
||||||
|
|
||||||
const result = await getTags({ cwd, options: { tagFormat: `prefix@v\${version}` } }, [{ name: "master" }]);
|
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}]);
|
||||||
|
|
||||||
t.deepEqual(result, [{ name: "master", tags: [] }]);
|
t.deepEqual(result, [{name: 'master', tags: []}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Return branches with and empty tags array if no valid tag is found in history of configured branches", async (t) => {
|
test('Return branches with and empty tags array if no valid tag is found in history of configured branches', async (t) => {
|
||||||
const { cwd } = await gitRepo();
|
const {cwd} = await gitRepo();
|
||||||
await gitCommits(["First"], { cwd });
|
await gitCommits(['First'], {cwd});
|
||||||
await gitCheckout("next", true, { cwd });
|
await gitCheckout('next', true, {cwd});
|
||||||
await gitCommits(["Second"], { cwd });
|
await gitCommits(['Second'], {cwd});
|
||||||
await gitTagVersion("v1.0.0", undefined, { cwd });
|
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||||
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v1.0.0", { cwd });
|
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
|
||||||
await gitCommits(["Third"], { cwd });
|
await gitCommits(['Third'], {cwd});
|
||||||
await gitTagVersion("v2.0.0", undefined, { cwd });
|
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||||
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v2.0.0", { cwd });
|
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
|
||||||
await gitCommits(["Fourth"], { cwd });
|
await gitCommits(['Fourth'], {cwd});
|
||||||
await gitTagVersion("v3.0.0", undefined, { cwd });
|
await gitTagVersion('v3.0.0', undefined, {cwd});
|
||||||
await gitAddNote(JSON.stringify({ channels: [null, "next"] }), "v3.0.0", { cwd });
|
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v3.0.0', {cwd});
|
||||||
await gitCheckout("master", false, { cwd });
|
await gitCheckout('master', false, {cwd});
|
||||||
|
|
||||||
const result = await getTags({ cwd, options: { tagFormat: `prefix@v\${version}` } }, [
|
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
|
||||||
{ name: "master" },
|
|
||||||
{ name: "next" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
t.deepEqual(result, [
|
t.deepEqual(result, [
|
||||||
{ name: "master", tags: [] },
|
{name: 'master', tags: []},
|
||||||
{ name: "next", tags: [] },
|
{name: 'next', tags: []},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Get the highest valid tag corresponding to the "tagFormat"', async (t) => {
|
test('Get the highest valid tag corresponding to the "tagFormat"', async (t) => {
|
||||||
const { cwd } = await gitRepo();
|
const {cwd} = await gitRepo();
|
||||||
await gitCommits(["First"], { cwd });
|
await gitCommits(['First'], {cwd});
|
||||||
|
|
||||||
await gitTagVersion("1.0.0", undefined, { cwd });
|
await gitTagVersion('1.0.0', undefined, {cwd});
|
||||||
t.deepEqual(await getTags({ cwd, options: { tagFormat: `\${version}` } }, [{ name: "master" }]), [
|
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}`}}, [{name: 'master'}]), [
|
||||||
{ name: "master", tags: [{ gitTag: "1.0.0", version: "1.0.0", channels: [null] }] },
|
{name: 'master', tags: [{gitTag: '1.0.0', version: '1.0.0', channels: [null]}]},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await gitTagVersion("foo-1.0.0-bar", undefined, { cwd });
|
await gitTagVersion('foo-1.0.0-bar', undefined, {cwd});
|
||||||
t.deepEqual(await getTags({ cwd, options: { tagFormat: `foo-\${version}-bar` } }, [{ name: "master" }]), [
|
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", channels: [null] }] },
|
{name: 'master', tags: [{gitTag: 'foo-1.0.0-bar', version: '1.0.0', channels: [null]}]},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await gitTagVersion("foo-v1.0.0-bar", undefined, { cwd });
|
await gitTagVersion('foo-v1.0.0-bar', undefined, {cwd});
|
||||||
t.deepEqual(await getTags({ cwd, options: { tagFormat: `foo-v\${version}-bar` } }, [{ name: "master" }]), [
|
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-v\${version}-bar`}}, [{name: 'master'}]), [
|
||||||
{
|
{
|
||||||
name: "master",
|
name: 'master',
|
||||||
tags: [{ gitTag: "foo-v1.0.0-bar", version: "1.0.0", channels: [null] }],
|
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channels: [null]}],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await gitTagVersion("(.+)/1.0.0/(a-z)", undefined, { cwd });
|
await gitTagVersion('(.+)/1.0.0/(a-z)', undefined, {cwd});
|
||||||
t.deepEqual(await getTags({ cwd, options: { tagFormat: `(.+)/\${version}/(a-z)` } }, [{ name: "master" }]), [
|
t.deepEqual(await getTags({cwd, options: {tagFormat: `(.+)/\${version}/(a-z)`}}, [{name: 'master'}]), [
|
||||||
{
|
{
|
||||||
name: "master",
|
name: 'master',
|
||||||
tags: [{ gitTag: "(.+)/1.0.0/(a-z)", version: "1.0.0", channels: [null] }],
|
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channels: [null]}],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await gitTagVersion("2.0.0-1.0.0-bar.1", undefined, { cwd });
|
await gitTagVersion('2.0.0-1.0.0-bar.1', undefined, {cwd});
|
||||||
t.deepEqual(await getTags({ cwd, options: { tagFormat: `2.0.0-\${version}-bar.1` } }, [{ name: "master" }]), [
|
t.deepEqual(await getTags({cwd, options: {tagFormat: `2.0.0-\${version}-bar.1`}}, [{name: 'master'}]), [
|
||||||
{
|
{
|
||||||
name: "master",
|
name: 'master',
|
||||||
tags: [{ gitTag: "2.0.0-1.0.0-bar.1", version: "1.0.0", channels: [null] }],
|
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 });
|
await gitTagVersion('3.0.0-bar.2', undefined, {cwd});
|
||||||
t.deepEqual(await getTags({ cwd, options: { tagFormat: `\${version}-bar.2` } }, [{ name: "master" }]), [
|
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", channels: [null] }] },
|
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channels: [null]}]},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import * as normalize from "../../lib/branches/normalize.js";
|
import * as normalize from '../../lib/branches/normalize.js';
|
||||||
|
|
||||||
const toTags = (versions) => versions.map((version) => ({ version }));
|
const toTags = (versions) => versions.map((version) => ({version}));
|
||||||
|
|
||||||
test("Maintenance branches - initial state", (t) => {
|
test('Maintenance branches - initial state', (t) => {
|
||||||
const maintenance = [
|
const maintenance = [
|
||||||
{ name: "1.x", channel: "1.x", tags: [] },
|
{name: '1.x', channel: '1.x', tags: []},
|
||||||
{ name: "1.1.x", tags: [] },
|
{name: '1.1.x', tags: []},
|
||||||
{ name: "1.2.x", tags: [] },
|
{name: '1.2.x', tags: []},
|
||||||
];
|
];
|
||||||
const release = [{ name: "master", tags: [] }];
|
const release = [{name: 'master', tags: []}];
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize.maintenance({ maintenance, release }).map(({ type, name, range, accept, channel, mergeRange }) => ({
|
normalize.maintenance({maintenance, release}).map(({type, name, range, accept, channel, mergeRange}) => ({
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
@ -21,51 +21,51 @@ test("Maintenance branches - initial state", (t) => {
|
|||||||
})),
|
})),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name: "1.1.x",
|
name: '1.1.x',
|
||||||
range: ">=1.1.0 <1.0.0",
|
range: '>=1.1.0 <1.0.0',
|
||||||
accept: [],
|
accept: [],
|
||||||
channel: "1.1.x",
|
channel: '1.1.x',
|
||||||
mergeRange: ">=1.1.0 <1.2.0",
|
mergeRange: '>=1.1.0 <1.2.0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name: "1.2.x",
|
name: '1.2.x',
|
||||||
range: ">=1.2.0 <1.0.0",
|
range: '>=1.2.0 <1.0.0',
|
||||||
accept: [],
|
accept: [],
|
||||||
channel: "1.2.x",
|
channel: '1.2.x',
|
||||||
mergeRange: ">=1.2.0 <1.3.0",
|
mergeRange: '>=1.2.0 <1.3.0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name: "1.x",
|
name: '1.x',
|
||||||
range: ">=1.3.0 <1.0.0",
|
range: '>=1.3.0 <1.0.0',
|
||||||
accept: [],
|
accept: [],
|
||||||
channel: "1.x",
|
channel: '1.x',
|
||||||
mergeRange: ">=1.3.0 <2.0.0",
|
mergeRange: '>=1.3.0 <2.0.0',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Maintenance branches - cap range to first release present on default branch and not in any Maintenance one", (t) => {
|
test('Maintenance branches - cap range to first release present on default branch and not in any Maintenance one', (t) => {
|
||||||
const maintenance = [
|
const maintenance = [
|
||||||
{ name: "1.x", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1", "1.3.0", "1.4.0", "1.5.0"]) },
|
{name: '1.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.5.0'])},
|
||||||
{ name: "name", range: "1.1.x", tags: toTags(["1.0.0", "1.0.1", "1.1.0", "1.1.1"]) },
|
{name: 'name', range: '1.1.x', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1'])},
|
||||||
{ name: "1.2.x", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1"]) },
|
{name: '1.2.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1'])},
|
||||||
{ name: "2.x.x", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1", "1.5.0"]) },
|
{name: '2.x.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.5.0'])},
|
||||||
];
|
];
|
||||||
const release = [
|
const release = [
|
||||||
{
|
{
|
||||||
name: "master",
|
name: 'master',
|
||||||
tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "1.2.1", "1.3.0", "1.4.0", "1.5.0", "1.6.0", "2.0.0"]),
|
tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.5.0', '1.6.0', '2.0.0']),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize
|
normalize
|
||||||
.maintenance({ maintenance, release })
|
.maintenance({maintenance, release})
|
||||||
.map(({ type, name, range, accept, channel, mergeRange: maintenanceRange }) => ({
|
.map(({type, name, range, accept, channel, mergeRange: maintenanceRange}) => ({
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
@ -75,50 +75,50 @@ test("Maintenance branches - cap range to first release present on default branc
|
|||||||
})),
|
})),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name: "name",
|
name: 'name',
|
||||||
range: ">=1.1.1 <1.2.0",
|
range: '>=1.1.1 <1.2.0',
|
||||||
accept: ["patch"],
|
accept: ['patch'],
|
||||||
channel: "name",
|
channel: 'name',
|
||||||
mergeRange: ">=1.1.0 <1.2.0",
|
mergeRange: '>=1.1.0 <1.2.0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name: "1.2.x",
|
name: '1.2.x',
|
||||||
range: ">=1.2.1 <1.3.0",
|
range: '>=1.2.1 <1.3.0',
|
||||||
accept: ["patch"],
|
accept: ['patch'],
|
||||||
channel: "1.2.x",
|
channel: '1.2.x',
|
||||||
mergeRange: ">=1.2.0 <1.3.0",
|
mergeRange: '>=1.2.0 <1.3.0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name: "1.x",
|
name: '1.x',
|
||||||
range: ">=1.5.0 <1.6.0",
|
range: '>=1.5.0 <1.6.0',
|
||||||
accept: ["patch"],
|
accept: ['patch'],
|
||||||
channel: "1.x",
|
channel: '1.x',
|
||||||
mergeRange: ">=1.3.0 <2.0.0",
|
mergeRange: '>=1.3.0 <2.0.0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name: "2.x.x",
|
name: '2.x.x',
|
||||||
range: ">=2.0.0 <1.6.0",
|
range: '>=2.0.0 <1.6.0',
|
||||||
accept: [],
|
accept: [],
|
||||||
channel: "2.x.x",
|
channel: '2.x.x',
|
||||||
mergeRange: ">=2.0.0 <3.0.0",
|
mergeRange: '>=2.0.0 <3.0.0',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Maintenance branches - cap range to default branch last release if all release are also present on maintenance branch", (t) => {
|
test('Maintenance branches - cap range to default branch last release if all release are also present on maintenance branch', (t) => {
|
||||||
const maintenance = [
|
const maintenance = [
|
||||||
{ name: "1.x", tags: toTags(["1.0.0", "1.2.0", "1.3.0"]) },
|
{name: '1.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0'])},
|
||||||
{ name: "2.x.x", tags: toTags(["1.0.0", "1.2.0", "1.3.0", "2.0.0"]) },
|
{name: '2.x.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0', '2.0.0'])},
|
||||||
];
|
];
|
||||||
const release = [{ name: "master", tags: toTags(["1.0.0", "1.2.0", "1.3.0", "2.0.0"]) }];
|
const release = [{name: 'master', tags: toTags(['1.0.0', '1.2.0', '1.3.0', '2.0.0'])}];
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize.maintenance({ maintenance, release }).map(({ type, name, range, accept, channel, mergeRange }) => ({
|
normalize.maintenance({maintenance, release}).map(({type, name, range, accept, channel, mergeRange}) => ({
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
range,
|
range,
|
||||||
@ -128,272 +128,270 @@ test("Maintenance branches - cap range to default branch last release if all rel
|
|||||||
})),
|
})),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name: "1.x",
|
name: '1.x',
|
||||||
range: ">=1.3.0 <2.0.0",
|
range: '>=1.3.0 <2.0.0',
|
||||||
accept: ["patch", "minor"],
|
accept: ['patch', 'minor'],
|
||||||
channel: "1.x",
|
channel: '1.x',
|
||||||
mergeRange: ">=1.0.0 <2.0.0",
|
mergeRange: '>=1.0.0 <2.0.0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "maintenance",
|
type: 'maintenance',
|
||||||
name: "2.x.x",
|
name: '2.x.x',
|
||||||
range: ">=2.0.0 <2.0.0",
|
range: '>=2.0.0 <2.0.0',
|
||||||
accept: [],
|
accept: [],
|
||||||
channel: "2.x.x",
|
channel: '2.x.x',
|
||||||
mergeRange: ">=2.0.0 <3.0.0",
|
mergeRange: '>=2.0.0 <3.0.0',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Release branches - initial state", (t) => {
|
test('Release branches - initial state', (t) => {
|
||||||
const release = [
|
const release = [
|
||||||
{ name: "master", tags: [] },
|
{name: 'master', tags: []},
|
||||||
{ name: "next", channel: "next", tags: [] },
|
{name: 'next', channel: 'next', tags: []},
|
||||||
{ name: "next-major", tags: [] },
|
{name: 'next-major', tags: []},
|
||||||
];
|
];
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize
|
normalize
|
||||||
.release({ release })
|
.release({release})
|
||||||
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
|
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "master",
|
name: 'master',
|
||||||
range: ">=1.0.0",
|
range: '>=1.0.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: undefined,
|
channel: undefined,
|
||||||
main: true,
|
main: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "next",
|
name: 'next',
|
||||||
range: ">=1.0.0",
|
range: '>=1.0.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: "next",
|
channel: 'next',
|
||||||
main: false,
|
main: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "next-major",
|
name: 'next-major',
|
||||||
range: ">=1.0.0",
|
range: '>=1.0.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: "next-major",
|
channel: 'next-major',
|
||||||
main: false,
|
main: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Release branches - 3 release branches", (t) => {
|
test('Release branches - 3 release branches', (t) => {
|
||||||
const release = [
|
const release = [
|
||||||
{ name: "master", tags: toTags(["1.0.0", "1.0.1", "1.0.2"]) },
|
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.0.2'])},
|
||||||
{ name: "next", tags: toTags(["1.0.0", "1.0.1", "1.0.2", "1.1.0", "1.2.0"]) },
|
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.0.2', '1.1.0', '1.2.0'])},
|
||||||
{ name: "next-major", tags: toTags(["1.0.0", "1.0.1", "1.0.2", "1.1.0", "1.2.0", "2.0.0", "2.0.1", "2.1.0"]) },
|
{name: 'next-major', tags: toTags(['1.0.0', '1.0.1', '1.0.2', '1.1.0', '1.2.0', '2.0.0', '2.0.1', '2.1.0'])},
|
||||||
];
|
];
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize
|
normalize
|
||||||
.release({ release })
|
.release({release})
|
||||||
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
|
.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, main: true },
|
{type: 'release', name: 'master', range: '>=1.0.2 <1.1.0', accept: ['patch'], channel: undefined, main: true},
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "next",
|
name: 'next',
|
||||||
range: ">=1.2.0 <2.0.0",
|
range: '>=1.2.0 <2.0.0',
|
||||||
accept: ["patch", "minor"],
|
accept: ['patch', 'minor'],
|
||||||
channel: "next",
|
channel: 'next',
|
||||||
main: false,
|
main: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "next-major",
|
name: 'next-major',
|
||||||
range: ">=2.1.0",
|
range: '>=2.1.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: "next-major",
|
channel: 'next-major',
|
||||||
main: false,
|
main: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Release branches - 2 release branches", (t) => {
|
test('Release branches - 2 release branches', (t) => {
|
||||||
const release = [
|
const release = [
|
||||||
{ name: "master", tags: toTags(["1.0.0", "1.0.1", "1.1.0", "1.1.1", "1.2.0"]) },
|
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0'])},
|
||||||
{ name: "next", tags: toTags(["1.0.0", "1.0.1", "1.1.0", "1.1.1", "1.2.0", "2.0.0", "2.0.1", "2.1.0"]) },
|
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0', '2.0.0', '2.0.1', '2.1.0'])},
|
||||||
];
|
];
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize
|
normalize
|
||||||
.release({ release })
|
.release({release})
|
||||||
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
|
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "master",
|
name: 'master',
|
||||||
range: ">=1.2.0 <2.0.0",
|
range: '>=1.2.0 <2.0.0',
|
||||||
accept: ["patch", "minor"],
|
accept: ['patch', 'minor'],
|
||||||
channel: undefined,
|
channel: undefined,
|
||||||
main: true,
|
main: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "next",
|
name: 'next',
|
||||||
range: ">=2.1.0",
|
range: '>=2.1.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: "next",
|
channel: 'next',
|
||||||
main: false,
|
main: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Release branches - 1 release branches", (t) => {
|
test('Release branches - 1 release branches', (t) => {
|
||||||
const release = [{ name: "master", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0"]) }];
|
const release = [{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0'])}];
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize
|
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||||
.release({ release })
|
[{type: 'release', name: 'master', range: '>=1.2.0', accept: ['patch', 'minor', 'major'], channel: undefined}]
|
||||||
.map(({ type, name, range, accept, channel }) => ({ type, name, range, accept, channel })),
|
|
||||||
[{ type: "release", name: "master", range: ">=1.2.0", accept: ["patch", "minor", "major"], channel: undefined }]
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Release branches - cap ranges to first release only present on following branch", (t) => {
|
test('Release branches - cap ranges to first release only present on following branch', (t) => {
|
||||||
const release = [
|
const release = [
|
||||||
{ name: "master", tags: toTags(["1.0.0", "1.1.0", "1.2.0", "2.0.0"]) },
|
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0'])},
|
||||||
{ name: "next", tags: toTags(["1.0.0", "1.1.0", "1.2.0", "2.0.0", "2.1.0"]) },
|
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0'])},
|
||||||
{ name: "next-major", tags: toTags(["1.0.0", "1.1.0", "1.2.0", "2.0.0", "2.1.0", "2.2.0"]) },
|
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0', '2.2.0'])},
|
||||||
];
|
];
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize
|
normalize
|
||||||
.release({ release })
|
.release({release})
|
||||||
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
|
.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, main: true },
|
{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', range: '>=2.1.0 <2.2.0', accept: ['patch'], channel: 'next', main: false},
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "next-major",
|
name: 'next-major',
|
||||||
range: ">=2.2.0",
|
range: '>=2.2.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: "next-major",
|
channel: 'next-major',
|
||||||
main: false,
|
main: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Release branches - Handle missing previous tags in branch history", (t) => {
|
test('Release branches - Handle missing previous tags in branch history', (t) => {
|
||||||
const release = [
|
const release = [
|
||||||
{ name: "master", tags: toTags(["1.0.0", "2.0.0"]) },
|
{name: 'master', tags: toTags(['1.0.0', '2.0.0'])},
|
||||||
{ name: "next", tags: toTags(["1.0.0", "1.1.0", "1.1.1", "1.2.0", "2.0.0"]) },
|
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '2.0.0'])},
|
||||||
];
|
];
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize
|
normalize
|
||||||
.release({ release })
|
.release({release})
|
||||||
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
|
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "master",
|
name: 'master',
|
||||||
range: ">=2.0.0",
|
range: '>=2.0.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: undefined,
|
channel: undefined,
|
||||||
main: true,
|
main: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "next",
|
name: 'next',
|
||||||
range: ">=2.0.0",
|
range: '>=2.0.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: "next",
|
channel: 'next',
|
||||||
main: false,
|
main: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Release branches - limit releases on 2nd and 3rd branch based on 1st branch last release", (t) => {
|
test('Release branches - limit releases on 2nd and 3rd branch based on 1st branch last release', (t) => {
|
||||||
const release = [
|
const release = [
|
||||||
{ name: "master", tags: toTags(["1.0.0", "1.1.0", "2.0.0", "3.0.0"]) },
|
{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"]) },
|
{name: 'next', tags: toTags(['1.0.0', '1.1.0'])},
|
||||||
{ name: "next-major", 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(
|
t.deepEqual(
|
||||||
normalize
|
normalize
|
||||||
.release({ release })
|
.release({release})
|
||||||
.map(({ type, name, range, accept, channel, main }) => ({ type, name, range, accept, channel, main })),
|
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "master",
|
name: 'master',
|
||||||
range: ">=3.0.0",
|
range: '>=3.0.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: undefined,
|
channel: undefined,
|
||||||
main: true,
|
main: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "next",
|
name: 'next',
|
||||||
range: ">=3.0.0",
|
range: '>=3.0.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: "next",
|
channel: 'next',
|
||||||
main: false,
|
main: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "release",
|
type: 'release',
|
||||||
name: "next-major",
|
name: 'next-major',
|
||||||
range: ">=3.0.0",
|
range: '>=3.0.0',
|
||||||
accept: ["patch", "minor", "major"],
|
accept: ['patch', 'minor', 'major'],
|
||||||
channel: "next-major",
|
channel: 'next-major',
|
||||||
main: false,
|
main: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Prerelease branches", (t) => {
|
test('Prerelease branches', (t) => {
|
||||||
const prerelease = [
|
const prerelease = [
|
||||||
{ name: "beta", channel: "beta", prerelease: true, tags: [] },
|
{name: 'beta', channel: 'beta', prerelease: true, tags: []},
|
||||||
{ name: "alpha", prerelease: "preview", tags: [] },
|
{name: 'alpha', prerelease: 'preview', tags: []},
|
||||||
];
|
];
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize.prerelease({ prerelease }).map(({ type, name, channel }) => ({ type, name, channel })),
|
normalize.prerelease({prerelease}).map(({type, name, channel}) => ({type, name, channel})),
|
||||||
[
|
[
|
||||||
{ type: "prerelease", name: "beta", channel: "beta" },
|
{type: 'prerelease', name: 'beta', channel: 'beta'},
|
||||||
{ type: "prerelease", name: "alpha", channel: "alpha" },
|
{type: 'prerelease', name: 'alpha', channel: 'alpha'},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Allow to set channel to "false" to prevent default', (t) => {
|
test('Allow to set channel to "false" to prevent default', (t) => {
|
||||||
const maintenance = [{ name: "1.x", channel: false, tags: [] }];
|
const maintenance = [{name: '1.x', channel: false, tags: []}];
|
||||||
const release = [
|
const release = [
|
||||||
{ name: "master", channel: false, tags: [] },
|
{name: 'master', channel: false, tags: []},
|
||||||
{ name: "next", channel: false, tags: [] },
|
{name: 'next', channel: false, tags: []},
|
||||||
];
|
];
|
||||||
const prerelease = [{ name: "beta", channel: false, prerelease: true, tags: [] }];
|
const prerelease = [{name: 'beta', channel: false, prerelease: true, tags: []}];
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize.maintenance({ maintenance, release }).map(({ name, channel }) => ({ name, channel })),
|
normalize.maintenance({maintenance, release}).map(({name, channel}) => ({name, channel})),
|
||||||
[{ name: "1.x", channel: false }]
|
[{name: '1.x', channel: false}]
|
||||||
);
|
);
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize.release({ release }).map(({ name, channel }) => ({ name, channel })),
|
normalize.release({release}).map(({name, channel}) => ({name, channel})),
|
||||||
[
|
[
|
||||||
{ name: "master", channel: false },
|
{name: 'master', channel: false},
|
||||||
{ name: "next", channel: false },
|
{name: 'next', channel: false},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
normalize.prerelease({ prerelease }).map(({ name, channel }) => ({ name, channel })),
|
normalize.prerelease({prerelease}).map(({name, channel}) => ({name, channel})),
|
||||||
[{ name: "beta", channel: false }]
|
[{name: 'beta', channel: false}]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,94 +1,92 @@
|
|||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import { maintenance, prerelease, release } from "../../lib/definitions/branches.js";
|
import {maintenance, prerelease, release} from '../../lib/definitions/branches.js';
|
||||||
|
|
||||||
test('A "maintenance" branch is identified by having a "range" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
|
test('A "maintenance" branch is identified by having a "range" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
|
||||||
/* eslint-disable unicorn/no-fn-reference-in-iterator */
|
/* eslint-disable unicorn/no-fn-reference-in-iterator */
|
||||||
t.true(maintenance.filter({ name: "1.x.x" }));
|
t.true(maintenance.filter({name: '1.x.x'}));
|
||||||
t.true(maintenance.filter({ name: "1.0.x" }));
|
t.true(maintenance.filter({name: '1.0.x'}));
|
||||||
t.true(maintenance.filter({ name: "1.x" }));
|
t.true(maintenance.filter({name: '1.x'}));
|
||||||
t.true(maintenance.filter({ name: "some-name", range: "1.x.x" }));
|
t.true(maintenance.filter({name: 'some-name', range: '1.x.x'}));
|
||||||
t.true(maintenance.filter({ name: "some-name", range: "1.1.x" }));
|
t.true(maintenance.filter({name: 'some-name', range: '1.1.x'}));
|
||||||
t.true(maintenance.filter({ name: "some-name", range: "" }));
|
t.true(maintenance.filter({name: 'some-name', range: ''}));
|
||||||
t.true(maintenance.filter({ name: "some-name", range: true }));
|
t.true(maintenance.filter({name: 'some-name', range: true}));
|
||||||
|
|
||||||
t.false(maintenance.filter({ name: "some-name", range: null }));
|
t.false(maintenance.filter({name: 'some-name', range: null}));
|
||||||
t.false(maintenance.filter({ name: "some-name", range: false }));
|
t.false(maintenance.filter({name: 'some-name', range: false}));
|
||||||
t.false(maintenance.filter({ name: "some-name" }));
|
t.false(maintenance.filter({name: 'some-name'}));
|
||||||
t.false(maintenance.filter({ name: "1.0.0" }));
|
t.false(maintenance.filter({name: '1.0.0'}));
|
||||||
t.false(maintenance.filter({ name: "x.x.x" }));
|
t.false(maintenance.filter({name: 'x.x.x'}));
|
||||||
/* eslint-enable unicorn/no-fn-reference-in-iterator */
|
/* eslint-enable unicorn/no-fn-reference-in-iterator */
|
||||||
});
|
});
|
||||||
|
|
||||||
test('A "maintenance" branches must have a "range" property formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
|
test('A "maintenance" branches must have a "range" property formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
|
||||||
t.true(maintenance.branchValidator({ name: "some-name", range: "1.x.x" }));
|
t.true(maintenance.branchValidator({name: 'some-name', range: '1.x.x'}));
|
||||||
t.true(maintenance.branchValidator({ name: "some-name", range: "1.1.x" }));
|
t.true(maintenance.branchValidator({name: 'some-name', range: '1.1.x'}));
|
||||||
|
|
||||||
t.false(maintenance.branchValidator({ name: "some-name", range: "^1.0.0" }));
|
t.false(maintenance.branchValidator({name: 'some-name', range: '^1.0.0'}));
|
||||||
t.false(maintenance.branchValidator({ name: "some-name", range: ">=1.0.0 <2.0.0" }));
|
t.false(maintenance.branchValidator({name: 'some-name', range: '>=1.0.0 <2.0.0'}));
|
||||||
t.false(maintenance.branchValidator({ name: "some-name", range: "1.0.0" }));
|
t.false(maintenance.branchValidator({name: 'some-name', range: '1.0.0'}));
|
||||||
t.false(maintenance.branchValidator({ name: "some-name", range: "wrong-range" }));
|
t.false(maintenance.branchValidator({name: 'some-name', range: 'wrong-range'}));
|
||||||
t.false(maintenance.branchValidator({ name: "some-name", range: true }));
|
t.false(maintenance.branchValidator({name: 'some-name', range: true}));
|
||||||
t.false(maintenance.branchValidator({ name: "some-name", range: "" }));
|
t.false(maintenance.branchValidator({name: 'some-name', range: ''}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('The "maintenance" branches must have unique ranges', (t) => {
|
test('The "maintenance" branches must have unique ranges', (t) => {
|
||||||
t.true(maintenance.branchesValidator([{ range: "1.x.x" }, { range: "1.0.x" }]));
|
t.true(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.0.x'}]));
|
||||||
|
|
||||||
t.false(maintenance.branchesValidator([{ range: "1.x.x" }, { range: "1.x.x" }]));
|
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x.x'}]));
|
||||||
t.false(maintenance.branchesValidator([{ range: "1.x.x" }, { range: "1.x" }]));
|
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x'}]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('A "prerelease" branch is identified by having a thruthy "prerelease" property', (t) => {
|
test('A "prerelease" branch is identified by having a thruthy "prerelease" property', (t) => {
|
||||||
/* eslint-disable unicorn/no-fn-reference-in-iterator */
|
/* eslint-disable unicorn/no-fn-reference-in-iterator */
|
||||||
t.true(prerelease.filter({ name: "some-name", prerelease: true }));
|
t.true(prerelease.filter({name: 'some-name', prerelease: true}));
|
||||||
t.true(prerelease.filter({ name: "some-name", prerelease: "beta" }));
|
t.true(prerelease.filter({name: 'some-name', prerelease: 'beta'}));
|
||||||
t.true(prerelease.filter({ name: "some-name", prerelease: "" }));
|
t.true(prerelease.filter({name: 'some-name', prerelease: ''}));
|
||||||
|
|
||||||
t.false(prerelease.filter({ name: "some-name", prerelease: null }));
|
t.false(prerelease.filter({name: 'some-name', prerelease: null}));
|
||||||
t.false(prerelease.filter({ name: "some-name", prerelease: false }));
|
t.false(prerelease.filter({name: 'some-name', prerelease: false}));
|
||||||
t.false(prerelease.filter({ name: "some-name" }));
|
t.false(prerelease.filter({name: 'some-name'}));
|
||||||
/* eslint-enable unicorn/no-fn-reference-in-iterator */
|
/* eslint-enable unicorn/no-fn-reference-in-iterator */
|
||||||
});
|
});
|
||||||
|
|
||||||
test('A "prerelease" branch must have a valid prerelease detonation in "prerelease" property or in "name" if "prerelease" is "true"', (t) => {
|
test('A "prerelease" branch must have a valid prerelease detonation in "prerelease" property or in "name" if "prerelease" is "true"', (t) => {
|
||||||
t.true(prerelease.branchValidator({ name: "beta", prerelease: true }));
|
t.true(prerelease.branchValidator({name: 'beta', prerelease: true}));
|
||||||
t.true(prerelease.branchValidator({ name: "some-name", prerelease: "beta" }));
|
t.true(prerelease.branchValidator({name: 'some-name', prerelease: 'beta'}));
|
||||||
|
|
||||||
t.false(prerelease.branchValidator({ name: "some-name", prerelease: "" }));
|
t.false(prerelease.branchValidator({name: 'some-name', prerelease: ''}));
|
||||||
t.false(prerelease.branchValidator({ name: "some-name", prerelease: null }));
|
t.false(prerelease.branchValidator({name: 'some-name', prerelease: null}));
|
||||||
t.false(prerelease.branchValidator({ name: "some-name", prerelease: false }));
|
t.false(prerelease.branchValidator({name: 'some-name', prerelease: false}));
|
||||||
t.false(prerelease.branchValidator({ name: "some-name", prerelease: "000" }));
|
t.false(prerelease.branchValidator({name: 'some-name', prerelease: '000'}));
|
||||||
t.false(prerelease.branchValidator({ name: "some-name", prerelease: "#beta" }));
|
t.false(prerelease.branchValidator({name: 'some-name', prerelease: '#beta'}));
|
||||||
t.false(prerelease.branchValidator({ name: "000", prerelease: true }));
|
t.false(prerelease.branchValidator({name: '000', prerelease: true}));
|
||||||
t.false(prerelease.branchValidator({ name: "#beta", prerelease: true }));
|
t.false(prerelease.branchValidator({name: '#beta', prerelease: true}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('The "prerelease" branches must have unique "prerelease" property', (t) => {
|
test('The "prerelease" branches must have unique "prerelease" property', (t) => {
|
||||||
t.true(prerelease.branchesValidator([{ prerelease: "beta" }, { prerelease: "alpha" }]));
|
t.true(prerelease.branchesValidator([{prerelease: 'beta'}, {prerelease: 'alpha'}]));
|
||||||
|
|
||||||
t.false(prerelease.branchesValidator([{ range: "beta" }, { range: "beta" }, { range: "alpha" }]));
|
t.false(prerelease.branchesValidator([{range: 'beta'}, {range: 'beta'}, {range: 'alpha'}]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('A "release" branch is identified by not havin a "range" or "prerelease" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
|
test('A "release" branch is identified by not havin a "range" or "prerelease" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', (t) => {
|
||||||
/* eslint-disable unicorn/no-fn-reference-in-iterator */
|
/* eslint-disable unicorn/no-fn-reference-in-iterator */
|
||||||
t.true(release.filter({ name: "some-name" }));
|
t.true(release.filter({name: 'some-name'}));
|
||||||
|
|
||||||
t.false(release.filter({ name: "1.x.x" }));
|
t.false(release.filter({name: '1.x.x'}));
|
||||||
t.false(release.filter({ name: "1.0.x" }));
|
t.false(release.filter({name: '1.0.x'}));
|
||||||
t.false(release.filter({ name: "some-name", range: "1.x.x" }));
|
t.false(release.filter({name: 'some-name', range: '1.x.x'}));
|
||||||
t.false(release.filter({ name: "some-name", range: "1.1.x" }));
|
t.false(release.filter({name: 'some-name', range: '1.1.x'}));
|
||||||
t.false(release.filter({ name: "some-name", prerelease: true }));
|
t.false(release.filter({name: 'some-name', prerelease: true}));
|
||||||
t.false(release.filter({ name: "some-name", prerelease: "beta" }));
|
t.false(release.filter({name: 'some-name', prerelease: 'beta'}));
|
||||||
/* eslint-enable unicorn/no-fn-reference-in-iterator */
|
/* eslint-enable unicorn/no-fn-reference-in-iterator */
|
||||||
});
|
});
|
||||||
|
|
||||||
test("There must be between 1 and 3 release branches", (t) => {
|
test('There must be between 1 and 3 release branches', (t) => {
|
||||||
t.true(release.branchesValidator([{ name: "branch1" }]));
|
t.true(release.branchesValidator([{name: 'branch1'}]));
|
||||||
t.true(release.branchesValidator([{ name: "branch1" }, { name: "branch2" }]));
|
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}]));
|
||||||
t.true(release.branchesValidator([{ name: "branch1" }, { name: "branch2" }, { name: "branch3" }]));
|
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}, {name: 'branch3'}]));
|
||||||
|
|
||||||
t.false(release.branchesValidator([]));
|
t.false(release.branchesValidator([]));
|
||||||
t.false(
|
t.false(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}, {name: 'branch3'}, {name: 'branch4'}]));
|
||||||
release.branchesValidator([{ name: "branch1" }, { name: "branch2" }, { name: "branch3" }, { name: "branch4" }])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import plugins from "../../lib/definitions/plugins.js";
|
import plugins from '../../lib/definitions/plugins.js';
|
||||||
import { RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT } from "../../lib/definitions/constants.js";
|
import {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} from '../../lib/definitions/constants.js';
|
||||||
|
|
||||||
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', (t) => {
|
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', (t) => {
|
||||||
t.false(plugins.analyzeCommits.outputValidator("invalid"));
|
t.false(plugins.analyzeCommits.outputValidator('invalid'));
|
||||||
t.false(plugins.analyzeCommits.outputValidator(1));
|
t.false(plugins.analyzeCommits.outputValidator(1));
|
||||||
t.false(plugins.analyzeCommits.outputValidator({}));
|
t.false(plugins.analyzeCommits.outputValidator({}));
|
||||||
|
|
||||||
t.true(plugins.analyzeCommits.outputValidator());
|
t.true(plugins.analyzeCommits.outputValidator());
|
||||||
t.true(plugins.analyzeCommits.outputValidator(null));
|
t.true(plugins.analyzeCommits.outputValidator(null));
|
||||||
t.true(plugins.analyzeCommits.outputValidator("major"));
|
t.true(plugins.analyzeCommits.outputValidator('major'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('The "generateNotes" plugin output, if defined, must be a string', (t) => {
|
test('The "generateNotes" plugin output, if defined, must be a string', (t) => {
|
||||||
@ -18,57 +18,57 @@ test('The "generateNotes" plugin output, if defined, must be a string', (t) => {
|
|||||||
|
|
||||||
t.true(plugins.generateNotes.outputValidator());
|
t.true(plugins.generateNotes.outputValidator());
|
||||||
t.true(plugins.generateNotes.outputValidator(null));
|
t.true(plugins.generateNotes.outputValidator(null));
|
||||||
t.true(plugins.generateNotes.outputValidator(""));
|
t.true(plugins.generateNotes.outputValidator(''));
|
||||||
t.true(plugins.generateNotes.outputValidator("string"));
|
t.true(plugins.generateNotes.outputValidator('string'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('The "publish" plugin output, if defined, must be an object or "false"', (t) => {
|
test('The "publish" plugin output, if defined, must be an object or "false"', (t) => {
|
||||||
t.false(plugins.publish.outputValidator(1));
|
t.false(plugins.publish.outputValidator(1));
|
||||||
t.false(plugins.publish.outputValidator("string"));
|
t.false(plugins.publish.outputValidator('string'));
|
||||||
|
|
||||||
t.true(plugins.publish.outputValidator({}));
|
t.true(plugins.publish.outputValidator({}));
|
||||||
t.true(plugins.publish.outputValidator());
|
t.true(plugins.publish.outputValidator());
|
||||||
t.true(plugins.publish.outputValidator(null));
|
t.true(plugins.publish.outputValidator(null));
|
||||||
t.true(plugins.publish.outputValidator(""));
|
t.true(plugins.publish.outputValidator(''));
|
||||||
t.true(plugins.publish.outputValidator(false));
|
t.true(plugins.publish.outputValidator(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('The "addChannel" plugin output, if defined, must be an object', (t) => {
|
test('The "addChannel" plugin output, if defined, must be an object', (t) => {
|
||||||
t.false(plugins.addChannel.outputValidator(1));
|
t.false(plugins.addChannel.outputValidator(1));
|
||||||
t.false(plugins.addChannel.outputValidator("string"));
|
t.false(plugins.addChannel.outputValidator('string'));
|
||||||
|
|
||||||
t.true(plugins.addChannel.outputValidator({}));
|
t.true(plugins.addChannel.outputValidator({}));
|
||||||
t.true(plugins.addChannel.outputValidator());
|
t.true(plugins.addChannel.outputValidator());
|
||||||
t.true(plugins.addChannel.outputValidator(null));
|
t.true(plugins.addChannel.outputValidator(null));
|
||||||
t.true(plugins.addChannel.outputValidator(""));
|
t.true(plugins.addChannel.outputValidator(''));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('The "generateNotes" plugins output are concatenated with separator and sensitive data is hidden', (t) => {
|
test('The "generateNotes" plugins output are concatenated with separator and sensitive data is hidden', (t) => {
|
||||||
const env = { MY_TOKEN: "secret token" };
|
const env = {MY_TOKEN: 'secret token'};
|
||||||
t.is(plugins.generateNotes.postprocess(["note 1", "note 2"], { env }), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
t.is(plugins.generateNotes.postprocess(['note 1', 'note 2'], {env}), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
||||||
t.is(plugins.generateNotes.postprocess(["", "note"], { env }), "note");
|
t.is(plugins.generateNotes.postprocess(['', 'note'], {env}), 'note');
|
||||||
t.is(plugins.generateNotes.postprocess([undefined, "note"], { env }), "note");
|
t.is(plugins.generateNotes.postprocess([undefined, 'note'], {env}), 'note');
|
||||||
t.is(plugins.generateNotes.postprocess(["note 1", "", "note 2"], { env }), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
t.is(plugins.generateNotes.postprocess(['note 1', '', 'note 2'], {env}), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
||||||
t.is(
|
t.is(
|
||||||
plugins.generateNotes.postprocess(["note 1", undefined, "note 2"], { env }),
|
plugins.generateNotes.postprocess(['note 1', undefined, 'note 2'], {env}),
|
||||||
`note 1${RELEASE_NOTES_SEPARATOR}note 2`
|
`note 1${RELEASE_NOTES_SEPARATOR}note 2`
|
||||||
);
|
);
|
||||||
|
|
||||||
t.is(
|
t.is(
|
||||||
plugins.generateNotes.postprocess(
|
plugins.generateNotes.postprocess(
|
||||||
[`Note 1: Exposing token ${env.MY_TOKEN}`, `Note 2: Exposing token ${SECRET_REPLACEMENT}`],
|
[`Note 1: Exposing token ${env.MY_TOKEN}`, `Note 2: Exposing token ${SECRET_REPLACEMENT}`],
|
||||||
{ env }
|
{env}
|
||||||
),
|
),
|
||||||
`Note 1: Exposing token ${SECRET_REPLACEMENT}${RELEASE_NOTES_SEPARATOR}Note 2: Exposing token ${SECRET_REPLACEMENT}`
|
`Note 1: Exposing token ${SECRET_REPLACEMENT}${RELEASE_NOTES_SEPARATOR}Note 2: Exposing token ${SECRET_REPLACEMENT}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('The "analyzeCommits" plugins output are reduced to the highest release type', (t) => {
|
test('The "analyzeCommits" plugins output are reduced to the highest release type', (t) => {
|
||||||
t.is(plugins.analyzeCommits.postprocess(["major", "minor"]), "major");
|
t.is(plugins.analyzeCommits.postprocess(['major', 'minor']), 'major');
|
||||||
t.is(plugins.analyzeCommits.postprocess(["", "minor"]), "minor");
|
t.is(plugins.analyzeCommits.postprocess(['', 'minor']), 'minor');
|
||||||
t.is(plugins.analyzeCommits.postprocess([undefined, "patch"]), "patch");
|
t.is(plugins.analyzeCommits.postprocess([undefined, 'patch']), 'patch');
|
||||||
t.is(plugins.analyzeCommits.postprocess([null, "patch"]), "patch");
|
t.is(plugins.analyzeCommits.postprocess([null, 'patch']), 'patch');
|
||||||
t.is(plugins.analyzeCommits.postprocess(["wrong_type", "minor"]), "minor");
|
t.is(plugins.analyzeCommits.postprocess(['wrong_type', 'minor']), 'minor');
|
||||||
t.is(plugins.analyzeCommits.postprocess([]), undefined);
|
t.is(plugins.analyzeCommits.postprocess([]), undefined);
|
||||||
t.is(plugins.analyzeCommits.postprocess(["wrong_type"]), undefined);
|
t.is(plugins.analyzeCommits.postprocess(['wrong_type']), undefined);
|
||||||
});
|
});
|
||||||
|
2
test/fixtures/index.js
vendored
2
test/fixtures/index.js
vendored
@ -1 +1 @@
|
|||||||
export default () => {};
|
export default () => {}
|
||||||
|
6
test/fixtures/plugin-error-inherited.js
vendored
6
test/fixtures/plugin-error-inherited.js
vendored
@ -1,4 +1,4 @@
|
|||||||
import SemanticReleaseError from "@semantic-release/error";
|
import SemanticReleaseError from '@semantic-release/error';
|
||||||
|
|
||||||
class InheritedError extends SemanticReleaseError {
|
class InheritedError extends SemanticReleaseError {
|
||||||
constructor(message, code) {
|
constructor(message, code) {
|
||||||
@ -10,5 +10,5 @@ class InheritedError extends SemanticReleaseError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
throw new InheritedError("Inherited error", "EINHERITED");
|
throw new InheritedError('Inherited error', 'EINHERITED');
|
||||||
};
|
}
|
||||||
|
6
test/fixtures/plugin-error.js
vendored
6
test/fixtures/plugin-error.js
vendored
@ -1,5 +1,5 @@
|
|||||||
export default () => {
|
export default () => {
|
||||||
const error = new Error("a");
|
const error = new Error('a');
|
||||||
error.errorProperty = "errorProperty";
|
error.errorProperty = 'errorProperty';
|
||||||
throw error;
|
throw error;
|
||||||
};
|
}
|
||||||
|
6
test/fixtures/plugin-errors.js
vendored
6
test/fixtures/plugin-errors.js
vendored
@ -1,5 +1,5 @@
|
|||||||
import AggregateError from "aggregate-error";
|
import AggregateError from 'aggregate-error';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
throw new AggregateError([new Error("a"), new Error("b")]);
|
throw new AggregateError([new Error('a'), new Error('b')]);
|
||||||
};
|
}
|
||||||
|
3
test/fixtures/plugin-esm-named-exports.js
vendored
3
test/fixtures/plugin-esm-named-exports.js
vendored
@ -1,3 +0,0 @@
|
|||||||
export async function verifyConditions(pluginConfig, context) {
|
|
||||||
context.logger.log("verifyConditions called");
|
|
||||||
}
|
|
2
test/fixtures/plugin-identity.js
vendored
2
test/fixtures/plugin-identity.js
vendored
@ -1 +1 @@
|
|||||||
export default (pluginConfig, context) => context;
|
export default (pluginConfig, context) => context
|
||||||
|
4
test/fixtures/plugin-log-env.js
vendored
4
test/fixtures/plugin-log-env.js
vendored
@ -1,6 +1,6 @@
|
|||||||
export default (pluginConfig, { env, logger }) => {
|
export default (pluginConfig, {env, logger}) => {
|
||||||
console.log(`Console: Exposing token ${env.MY_TOKEN}`);
|
console.log(`Console: Exposing token ${env.MY_TOKEN}`);
|
||||||
logger.log(`Log: Exposing token ${env.MY_TOKEN}`);
|
logger.log(`Log: Exposing token ${env.MY_TOKEN}`);
|
||||||
logger.error(`Error: Console token ${env.MY_TOKEN}`);
|
logger.error(`Error: Console token ${env.MY_TOKEN}`);
|
||||||
throw new Error(`Throw error: Exposing ${env.MY_TOKEN}`);
|
throw new Error(`Throw error: Exposing ${env.MY_TOKEN}`);
|
||||||
};
|
}
|
||||||
|
2
test/fixtures/plugin-result-config.js
vendored
2
test/fixtures/plugin-result-config.js
vendored
@ -1 +1 @@
|
|||||||
export default (pluginConfig, context) => ({ pluginConfig, context });
|
export default (pluginConfig, context) => ({pluginConfig, context})
|
||||||
|
@ -389,92 +389,6 @@ test.serial('Read configuration from an array of paths in "extends"', async (t)
|
|||||||
t.deepEqual(result, { options: expectedOptions, plugins: pluginsConfig });
|
t.deepEqual(result, { options: expectedOptions, plugins: pluginsConfig });
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial('Read configuration from an array of CJS files 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.cjs", "./shareable2.cjs"] };
|
|
||||||
const options1 = {
|
|
||||||
verifyRelease: "verifyRelease1",
|
|
||||||
analyzeCommits: { path: "analyzeCommits1", param: "analyzeCommits_param1" },
|
|
||||||
branches: ["test_branch"],
|
|
||||||
repositoryUrl: "https://host.null/owner/module.git",
|
|
||||||
};
|
|
||||||
const options2 = {
|
|
||||||
verifyRelease: "verifyRelease2",
|
|
||||||
generateNotes: "generateNotes2",
|
|
||||||
analyzeCommits: { path: "analyzeCommits2", param: "analyzeCommits_param2" },
|
|
||||||
branches: ["test_branch"],
|
|
||||||
tagFormat: `v\${version}`,
|
|
||||||
plugins: false,
|
|
||||||
};
|
|
||||||
// Create package.json and shareable.json in repository root
|
|
||||||
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
|
|
||||||
await writeFile(path.resolve(cwd, "shareable1.cjs"), `module.exports = ${JSON.stringify(options1)}`);
|
|
||||||
await writeFile(path.resolve(cwd, "shareable2.cjs"), `module.exports = ${JSON.stringify(options2)}`);
|
|
||||||
const expectedOptions = { ...options1, ...options2, branches: ["test_branch"] };
|
|
||||||
// Verify the plugins module is called with the plugin options from shareable1.mjs and shareable2.mjs
|
|
||||||
td.when(
|
|
||||||
plugins(
|
|
||||||
{ options: expectedOptions, cwd },
|
|
||||||
{
|
|
||||||
verifyRelease1: "./shareable1.cjs",
|
|
||||||
verifyRelease2: "./shareable2.cjs",
|
|
||||||
generateNotes2: "./shareable2.cjs",
|
|
||||||
analyzeCommits1: "./shareable1.cjs",
|
|
||||||
analyzeCommits2: "./shareable2.cjs",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
).thenResolve(pluginsConfig);
|
|
||||||
|
|
||||||
const result = await t.context.getConfig({ cwd });
|
|
||||||
|
|
||||||
// Verify the options contains the plugin config from shareable1.json and shareable2.json
|
|
||||||
t.deepEqual(result, { options: expectedOptions, plugins: pluginsConfig });
|
|
||||||
});
|
|
||||||
|
|
||||||
test.serial('Read configuration from an array of ESM files 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.mjs", "./shareable2.mjs"] };
|
|
||||||
const options1 = {
|
|
||||||
verifyRelease: "verifyRelease1",
|
|
||||||
analyzeCommits: { path: "analyzeCommits1", param: "analyzeCommits_param1" },
|
|
||||||
branches: ["test_branch"],
|
|
||||||
repositoryUrl: "https://host.null/owner/module.git",
|
|
||||||
};
|
|
||||||
const options2 = {
|
|
||||||
verifyRelease: "verifyRelease2",
|
|
||||||
generateNotes: "generateNotes2",
|
|
||||||
analyzeCommits: { path: "analyzeCommits2", param: "analyzeCommits_param2" },
|
|
||||||
branches: ["test_branch"],
|
|
||||||
tagFormat: `v\${version}`,
|
|
||||||
plugins: false,
|
|
||||||
};
|
|
||||||
// Create package.json and shareable.json in repository root
|
|
||||||
await outputJson(path.resolve(cwd, "package.json"), { release: pkgOptions });
|
|
||||||
await writeFile(path.resolve(cwd, "shareable1.mjs"), `export default ${JSON.stringify(options1)}`);
|
|
||||||
await writeFile(path.resolve(cwd, "shareable2.mjs"), `export default ${JSON.stringify(options2)}`);
|
|
||||||
const expectedOptions = { ...options1, ...options2, branches: ["test_branch"] };
|
|
||||||
// Verify the plugins module is called with the plugin options from shareable1.mjs and shareable2.mjs
|
|
||||||
td.when(
|
|
||||||
plugins(
|
|
||||||
{ options: expectedOptions, cwd },
|
|
||||||
{
|
|
||||||
verifyRelease1: "./shareable1.mjs",
|
|
||||||
verifyRelease2: "./shareable2.mjs",
|
|
||||||
generateNotes2: "./shareable2.mjs",
|
|
||||||
analyzeCommits1: "./shareable1.mjs",
|
|
||||||
analyzeCommits2: "./shareable2.mjs",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
).thenResolve(pluginsConfig);
|
|
||||||
|
|
||||||
const result = await t.context.getConfig({ cwd });
|
|
||||||
|
|
||||||
// Verify the options contains the plugin config from shareable1.json and shareable2.json
|
|
||||||
t.deepEqual(result, { options: expectedOptions, plugins: pluginsConfig });
|
|
||||||
});
|
|
||||||
|
|
||||||
test.serial('Prioritize configuration from config file over "extends"', async (t) => {
|
test.serial('Prioritize configuration from config file over "extends"', async (t) => {
|
||||||
// Create a git repository, set the current working directory at the root of the repo
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
const { cwd } = await gitRepo();
|
const { cwd } = await gitRepo();
|
||||||
|
@ -43,43 +43,6 @@ test("Get the highest prerelease valid tag, ignoring other tags from other prere
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Get the correct prerelease tag, when other prereleases share the same git HEAD", (t) => {
|
|
||||||
const testConfig = {
|
|
||||||
branch: {
|
|
||||||
name: "alpha",
|
|
||||||
prerelease: "alpha",
|
|
||||||
channel: "alpha",
|
|
||||||
tags: [
|
|
||||||
{ 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-alpha.1", channels: ["alpha", "beta"] },
|
|
||||||
{ version: "1.0.0-alpha.1", gitTag: "v1.0.0-alpha.1", gitHead: "v1.0.0-alpha.1", channels: ["alpha", "beta"] },
|
|
||||||
],
|
|
||||||
type: "prerelease",
|
|
||||||
},
|
|
||||||
options: { tagFormat: `v\${version}`, debug: true },
|
|
||||||
};
|
|
||||||
const firstResult = getLastRelease(testConfig);
|
|
||||||
|
|
||||||
t.deepEqual(firstResult, {
|
|
||||||
version: "1.0.0-alpha.1",
|
|
||||||
gitTag: "v1.0.0-alpha.1",
|
|
||||||
name: "v1.0.0-alpha.1",
|
|
||||||
gitHead: "v1.0.0-alpha.1",
|
|
||||||
channels: ["alpha", "beta"],
|
|
||||||
});
|
|
||||||
|
|
||||||
testConfig.branch.prerelease = true;
|
|
||||||
const secondResult = getLastRelease(testConfig);
|
|
||||||
|
|
||||||
t.deepEqual(secondResult, {
|
|
||||||
version: "1.0.0-alpha.1",
|
|
||||||
gitTag: "v1.0.0-alpha.1",
|
|
||||||
name: "v1.0.0-alpha.1",
|
|
||||||
gitHead: "v1.0.0-alpha.1",
|
|
||||||
channels: ["alpha", "beta"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Return empty object if no valid tag is found", (t) => {
|
test("Return empty object if no valid tag is found", (t) => {
|
||||||
const result = getLastRelease({
|
const result = getLastRelease({
|
||||||
branch: {
|
branch: {
|
||||||
|
@ -275,24 +275,3 @@ test("Increase version for release on prerelease branch when there is no regular
|
|||||||
"1.0.0-beta.2"
|
"1.0.0-beta.2"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Increase patch when previous version shares HEAD with other releases", (t) => {
|
|
||||||
t.is(
|
|
||||||
getNextVersion({
|
|
||||||
branch: {
|
|
||||||
name: "alpha",
|
|
||||||
type: "prerelease",
|
|
||||||
prerelease: "alpha",
|
|
||||||
tags: [
|
|
||||||
{ gitTag: "v1.0.0-beta.1", version: "1.0.0-beta.1", channels: ["beta"] },
|
|
||||||
{ gitTag: "v1.0.0-beta.2", version: "1.0.0-beta.2", channels: ["alpha", "beta"] },
|
|
||||||
{ gitTag: "v1.0.0-alpha.1", version: "1.0.0-alpha.1", channels: ["alpha", "beta"] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
nextRelease: { type: "patch", channel: "alpha" },
|
|
||||||
lastRelease: { version: "v1.0.0-alpha.1", channels: ["alpha", "beta"] },
|
|
||||||
logger: t.context.logger,
|
|
||||||
}),
|
|
||||||
"1.0.0-alpha.2"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { temporaryDirectory } from "tempy";
|
import {temporaryDirectory} from 'tempy';
|
||||||
import { execa } from "execa";
|
import {execa} from 'execa';
|
||||||
import fileUrl from "file-url";
|
import fileUrl from 'file-url';
|
||||||
import pEachSeries from "p-each-series";
|
import pEachSeries from 'p-each-series';
|
||||||
import gitLogParser from "git-log-parser";
|
import gitLogParser from 'git-log-parser';
|
||||||
import getStream from "get-stream";
|
import getStream from 'get-stream';
|
||||||
import { GIT_NOTE_REF } from "../../lib/definitions/constants.js";
|
import {GIT_NOTE_REF} from '../../lib/definitions/constants.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commit message information.
|
* Commit message information.
|
||||||
@ -25,14 +25,14 @@ import { GIT_NOTE_REF } from "../../lib/definitions/constants.js";
|
|||||||
*/
|
*/
|
||||||
export async function initGit(withRemote) {
|
export async function initGit(withRemote) {
|
||||||
const cwd = temporaryDirectory();
|
const cwd = temporaryDirectory();
|
||||||
const args = withRemote ? ["--bare", "--initial-branch=master"] : ["--initial-branch=master"];
|
const args = withRemote ? ['--bare', '--initial-branch=master'] : ['--initial-branch=master'];
|
||||||
|
|
||||||
await execa("git", ["init", ...args], { cwd }).catch(() => {
|
await execa('git', ['init', ...args], {cwd}).catch(() => {
|
||||||
const args = withRemote ? ["--bare"] : [];
|
const args = withRemote ? ['--bare'] : [];
|
||||||
return execa("git", ["init", ...args], { cwd });
|
return execa('git', ['init', ...args], {cwd});
|
||||||
});
|
});
|
||||||
const repositoryUrl = fileUrl(cwd);
|
const repositoryUrl = fileUrl(cwd);
|
||||||
return { cwd, repositoryUrl };
|
return {cwd, repositoryUrl};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,20 +43,20 @@ export async function initGit(withRemote) {
|
|||||||
*
|
*
|
||||||
* @param {Boolean} withRemote `true` to create a shallow clone of a bare repository.
|
* @param {Boolean} withRemote `true` to create a shallow clone of a bare repository.
|
||||||
* @param {String} [branch='master'] The branch to initialize.
|
* @param {String} [branch='master'] The branch to initialize.
|
||||||
* @return {Promise<Object>} The path of the clone if `withRemote` is `true`, the path of the repository otherwise.
|
* @return {String} The path of the clone if `withRemote` is `true`, the path of the repository otherwise.
|
||||||
*/
|
*/
|
||||||
export async function gitRepo(withRemote, branch = "master") {
|
export async function gitRepo(withRemote, branch = 'master') {
|
||||||
let { cwd, repositoryUrl } = await initGit(withRemote);
|
let {cwd, repositoryUrl} = await initGit(withRemote);
|
||||||
if (withRemote) {
|
if (withRemote) {
|
||||||
await initBareRepo(repositoryUrl, branch);
|
await initBareRepo(repositoryUrl, branch);
|
||||||
cwd = await gitShallowClone(repositoryUrl, branch);
|
cwd = await gitShallowClone(repositoryUrl, branch);
|
||||||
} else {
|
} else {
|
||||||
await gitCheckout(branch, true, { cwd });
|
await gitCheckout(branch, true, {cwd});
|
||||||
}
|
}
|
||||||
|
|
||||||
await execa("git", ["config", "commit.gpgsign", false], { cwd });
|
await execa('git', ['config', 'commit.gpgsign', false], {cwd});
|
||||||
|
|
||||||
return { cwd, repositoryUrl };
|
return {cwd, repositoryUrl};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,12 +70,12 @@ export async function gitRepo(withRemote, branch = "master") {
|
|||||||
* @param {String} repositoryUrl The URL of the bare repository.
|
* @param {String} repositoryUrl The URL of the bare repository.
|
||||||
* @param {String} [branch='master'] the branch to initialize.
|
* @param {String} [branch='master'] the branch to initialize.
|
||||||
*/
|
*/
|
||||||
export async function initBareRepo(repositoryUrl, branch = "master") {
|
export async function initBareRepo(repositoryUrl, branch = 'master') {
|
||||||
const cwd = temporaryDirectory();
|
const cwd = temporaryDirectory();
|
||||||
await execa("git", ["clone", "--no-hardlinks", repositoryUrl, cwd], { cwd });
|
await execa('git', ['clone', '--no-hardlinks', repositoryUrl, cwd], {cwd});
|
||||||
await gitCheckout(branch, true, { cwd });
|
await gitCheckout(branch, true, {cwd});
|
||||||
await gitCommits(["Initial commit"], { cwd });
|
await gitCommits(['Initial commit'], {cwd});
|
||||||
await execa("git", ["push", repositoryUrl, branch], { cwd });
|
await execa('git', ['push', repositoryUrl, branch], {cwd});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,7 +90,7 @@ export async function gitCommits(messages, execaOptions) {
|
|||||||
await pEachSeries(
|
await pEachSeries(
|
||||||
messages,
|
messages,
|
||||||
async (message) =>
|
async (message) =>
|
||||||
(await execa("git", ["commit", "-m", message, "--allow-empty", "--no-gpg-sign"], execaOptions)).stdout
|
(await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOptions)).stdout
|
||||||
);
|
);
|
||||||
return (await gitGetCommits(undefined, execaOptions)).slice(0, messages.length);
|
return (await gitGetCommits(undefined, execaOptions)).slice(0, messages.length);
|
||||||
}
|
}
|
||||||
@ -98,23 +98,18 @@ export async function gitCommits(messages, execaOptions) {
|
|||||||
/**
|
/**
|
||||||
* Get the list of parsed commits since a git reference.
|
* Get the list of parsed commits since a git reference.
|
||||||
*
|
*
|
||||||
* @param {String} [from] Git reference from which to search commits.
|
* @param {String} [from] Git reference from which to seach commits.
|
||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*
|
*
|
||||||
* @return {Array<Object>} The list of parsed commits.
|
* @return {Array<Object>} The list of parsed commits.
|
||||||
*/
|
*/
|
||||||
export async function gitGetCommits(from, execaOptions) {
|
export async function gitGetCommits(from, execaOptions) {
|
||||||
Object.assign(gitLogParser.fields, {
|
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
|
||||||
hash: "H",
|
|
||||||
message: "B",
|
|
||||||
gitTags: "d",
|
|
||||||
committerDate: { key: "ci", type: Date },
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
await getStream.array(
|
await getStream.array(
|
||||||
gitLogParser.parse(
|
gitLogParser.parse(
|
||||||
{ _: `${from ? from + ".." : ""}HEAD` },
|
{_: `${from ? from + '..' : ''}HEAD`},
|
||||||
{ ...execaOptions, env: { ...process.env, ...execaOptions.env } }
|
{...execaOptions, env: {...process.env, ...execaOptions.env}}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).map((commit) => {
|
).map((commit) => {
|
||||||
@ -132,7 +127,7 @@ export async function gitGetCommits(from, execaOptions) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function gitCheckout(branch, create, execaOptions) {
|
export async function gitCheckout(branch, create, execaOptions) {
|
||||||
await execa("git", create ? ["checkout", "-b", branch] : ["checkout", branch], execaOptions);
|
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,7 +137,7 @@ export async function gitCheckout(branch, create, execaOptions) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function gitFetch(repositoryUrl, execaOptions) {
|
export async function gitFetch(repositoryUrl, execaOptions) {
|
||||||
await execa("git", ["fetch", repositoryUrl], execaOptions);
|
await execa('git', ['fetch', repositoryUrl], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -153,7 +148,7 @@ export async function gitFetch(repositoryUrl, execaOptions) {
|
|||||||
* @return {String} The sha of the head commit in the current git repository.
|
* @return {String} The sha of the head commit in the current git repository.
|
||||||
*/
|
*/
|
||||||
export async function gitHead(execaOptions) {
|
export async function gitHead(execaOptions) {
|
||||||
return (await execa("git", ["rev-parse", "HEAD"], execaOptions)).stdout;
|
return (await execa('git', ['rev-parse', 'HEAD'], execaOptions)).stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,7 +159,7 @@ export async function gitHead(execaOptions) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function gitTagVersion(tagName, sha, execaOptions) {
|
export async function gitTagVersion(tagName, sha, execaOptions) {
|
||||||
await execa("git", sha ? ["tag", "-f", tagName, sha] : ["tag", tagName], execaOptions);
|
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,10 +171,10 @@ export async function gitTagVersion(tagName, sha, execaOptions) {
|
|||||||
* @param {Number} [depth=1] The number of commit to clone.
|
* @param {Number} [depth=1] The number of commit to clone.
|
||||||
* @return {String} The path of the cloned repository.
|
* @return {String} The path of the cloned repository.
|
||||||
*/
|
*/
|
||||||
export async function gitShallowClone(repositoryUrl, branch = "master", depth = 1) {
|
export async function gitShallowClone(repositoryUrl, branch = 'master', depth = 1) {
|
||||||
const cwd = temporaryDirectory();
|
const cwd = temporaryDirectory();
|
||||||
|
|
||||||
await execa("git", ["clone", "--no-hardlinks", "--no-tags", "-b", branch, "--depth", depth, repositoryUrl, cwd], {
|
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, repositoryUrl, cwd], {
|
||||||
cwd,
|
cwd,
|
||||||
});
|
});
|
||||||
return cwd;
|
return cwd;
|
||||||
@ -195,21 +190,21 @@ export async function gitShallowClone(repositoryUrl, branch = "master", depth =
|
|||||||
export async function gitDetachedHead(repositoryUrl, head) {
|
export async function gitDetachedHead(repositoryUrl, head) {
|
||||||
const cwd = temporaryDirectory();
|
const cwd = temporaryDirectory();
|
||||||
|
|
||||||
await execa("git", ["init"], { cwd });
|
await execa('git', ['init'], {cwd});
|
||||||
await execa("git", ["remote", "add", "origin", repositoryUrl], { cwd });
|
await execa('git', ['remote', 'add', 'origin', repositoryUrl], {cwd});
|
||||||
await execa("git", ["fetch", repositoryUrl], { cwd });
|
await execa('git', ['fetch', repositoryUrl], {cwd});
|
||||||
await execa("git", ["checkout", head], { cwd });
|
await execa('git', ['checkout', head], {cwd});
|
||||||
return cwd;
|
return cwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
|
export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
|
||||||
const cwd = temporaryDirectory();
|
const cwd = temporaryDirectory();
|
||||||
|
|
||||||
await execa("git", ["init"], { cwd });
|
await execa('git', ['init'], {cwd});
|
||||||
await execa("git", ["remote", "add", "origin", repositoryUrl], { cwd });
|
await execa('git', ['remote', 'add', 'origin', repositoryUrl], {cwd});
|
||||||
await execa("git", ["fetch", "--force", repositoryUrl, `${branch}:remotes/origin/${branch}`], { cwd });
|
await execa('git', ['fetch', '--force', repositoryUrl, `${branch}:remotes/origin/${branch}`], {cwd});
|
||||||
await execa("git", ["reset", "--hard", head], { cwd });
|
await execa('git', ['reset', '--hard', head], {cwd});
|
||||||
await execa("git", ["checkout", "-q", "-B", branch], { cwd });
|
await execa('git', ['checkout', '-q', '-B', branch], {cwd});
|
||||||
return cwd;
|
return cwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +216,7 @@ export async function gitDetachedHeadFromBranch(repositoryUrl, branch, head) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function gitAddConfig(name, value, execaOptions) {
|
export async function gitAddConfig(name, value, execaOptions) {
|
||||||
await execa("git", ["config", "--add", name, value], execaOptions);
|
await execa('git', ['config', '--add', name, value], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -233,21 +228,21 @@ export async function gitAddConfig(name, value, execaOptions) {
|
|||||||
* @return {String} The sha of the commit associated with `tagName` on the local repository.
|
* @return {String} The sha of the commit associated with `tagName` on the local repository.
|
||||||
*/
|
*/
|
||||||
export async function gitTagHead(tagName, execaOptions) {
|
export async function gitTagHead(tagName, execaOptions) {
|
||||||
return (await execa("git", ["rev-list", "-1", tagName], execaOptions)).stdout;
|
return (await execa('git', ['rev-list', '-1', tagName], execaOptions)).stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the first commit sha referenced by the tag `tagName` in the remote repository.
|
* Get the first commit sha referenced by the tag `tagName` in the remote repository.
|
||||||
*
|
*
|
||||||
* @param {String} repositoryUrl The repository remote URL.
|
* @param {String} repositoryUrl The repository remote URL.
|
||||||
* @param {String} tagName The tag name to search for.
|
* @param {String} tagName The tag name to seach for.
|
||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*
|
*
|
||||||
* @return {String} The sha of the commit associated with `tagName` on the remote repository.
|
* @return {String} The sha of the commit associated with `tagName` on the remote repository.
|
||||||
*/
|
*/
|
||||||
export async function gitRemoteTagHead(repositoryUrl, tagName, execaOptions) {
|
export async function gitRemoteTagHead(repositoryUrl, tagName, execaOptions) {
|
||||||
return (await execa("git", ["ls-remote", "--tags", repositoryUrl, tagName], execaOptions)).stdout
|
return (await execa('git', ['ls-remote', '--tags', repositoryUrl, tagName], execaOptions)).stdout
|
||||||
.split("\n")
|
.split('\n')
|
||||||
.filter((tag) => Boolean(tag))
|
.filter((tag) => Boolean(tag))
|
||||||
.map((tag) => tag.match(/^(?<tag>\S+)/)[1])[0];
|
.map((tag) => tag.match(/^(?<tag>\S+)/)[1])[0];
|
||||||
}
|
}
|
||||||
@ -261,7 +256,7 @@ export async function gitRemoteTagHead(repositoryUrl, tagName, execaOptions) {
|
|||||||
* @return {String} The tag associatedwith the sha in parameter or `null`.
|
* @return {String} The tag associatedwith the sha in parameter or `null`.
|
||||||
*/
|
*/
|
||||||
export async function gitCommitTag(gitHead, execaOptions) {
|
export async function gitCommitTag(gitHead, execaOptions) {
|
||||||
return (await execa("git", ["describe", "--tags", "--exact-match", gitHead], execaOptions)).stdout;
|
return (await execa('git', ['describe', '--tags', '--exact-match', gitHead], execaOptions)).stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -274,7 +269,7 @@ export async function gitCommitTag(gitHead, execaOptions) {
|
|||||||
* @throws {Error} if the push failed.
|
* @throws {Error} if the push failed.
|
||||||
*/
|
*/
|
||||||
export async function gitPush(repositoryUrl, branch, execaOptions) {
|
export async function gitPush(repositoryUrl, branch, execaOptions) {
|
||||||
await execa("git", ["push", "--tags", repositoryUrl, `HEAD:${branch}`], execaOptions);
|
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -284,7 +279,7 @@ export async function gitPush(repositoryUrl, branch, execaOptions) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function merge(ref, execaOptions) {
|
export async function merge(ref, execaOptions) {
|
||||||
await execa("git", ["merge", "--no-ff", ref], execaOptions);
|
await execa('git', ['merge', '--no-ff', ref], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -294,7 +289,7 @@ export async function merge(ref, execaOptions) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function mergeFf(ref, execaOptions) {
|
export async function mergeFf(ref, execaOptions) {
|
||||||
await execa("git", ["merge", "--ff", ref], execaOptions);
|
await execa('git', ['merge', '--ff', ref], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -304,7 +299,7 @@ export async function mergeFf(ref, execaOptions) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function rebase(ref, execaOptions) {
|
export async function rebase(ref, execaOptions) {
|
||||||
await execa("git", ["rebase", ref], execaOptions);
|
await execa('git', ['rebase', ref], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -315,7 +310,7 @@ export async function rebase(ref, execaOptions) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function gitAddNote(note, ref, execaOptions) {
|
export async function gitAddNote(note, ref, execaOptions) {
|
||||||
await execa("git", ["notes", "--ref", `${GIT_NOTE_REF}-${ref}`, "add", "-m", note, ref], execaOptions);
|
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-m', note, ref], execaOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -325,5 +320,5 @@ export async function gitAddNote(note, ref, execaOptions) {
|
|||||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||||
*/
|
*/
|
||||||
export async function gitGetNote(ref, execaOptions) {
|
export async function gitGetNote(ref, execaOptions) {
|
||||||
return (await execa("git", ["notes", "--ref", `${GIT_NOTE_REF}-${ref}`, "show", ref], execaOptions)).stdout;
|
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOptions)).stdout;
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,34 @@
|
|||||||
import Docker from "dockerode";
|
import Docker from 'dockerode';
|
||||||
import pRetry from "p-retry";
|
import getStream from 'get-stream';
|
||||||
import { gitShallowClone, initBareRepo } from "./git-utils.js";
|
import pRetry from 'p-retry';
|
||||||
|
import {gitShallowClone, initBareRepo} from './git-utils.js';
|
||||||
|
|
||||||
const IMAGE = "semanticrelease/docker-gitbox:latest";
|
const IMAGE = 'semanticrelease/docker-gitbox:latest';
|
||||||
const SERVER_PORT = 80;
|
const SERVER_PORT = 80;
|
||||||
const HOST_PORT = 2080;
|
const HOST_PORT = 2080;
|
||||||
const SERVER_HOST = "localhost";
|
const SERVER_HOST = 'localhost';
|
||||||
const GIT_USERNAME = "integration";
|
const GIT_USERNAME = 'integration';
|
||||||
const GIT_PASSWORD = "suchsecure";
|
const GIT_PASSWORD = 'suchsecure';
|
||||||
const docker = new Docker();
|
const docker = new Docker();
|
||||||
let container;
|
let container;
|
||||||
|
|
||||||
export const gitCredential = `${GIT_USERNAME}:${GIT_PASSWORD}`;
|
export const gitCredential = `${GIT_USERNAME}:${GIT_PASSWORD}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download the `gitbox` Docker image
|
* Download the `gitbox` Docker image, create a new container and start it.
|
||||||
*/
|
|
||||||
export function pull() {
|
|
||||||
return docker.pull(IMAGE).then((stream) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
docker.modem.followProgress(stream, (err, res) => (err ? reject(err) : resolve(res)));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create a new container and start it.
|
|
||||||
*/
|
*/
|
||||||
export async function start() {
|
export async function start() {
|
||||||
|
await getStream(await docker.pull(IMAGE));
|
||||||
|
|
||||||
container = await docker.createContainer({
|
container = await docker.createContainer({
|
||||||
Tty: true,
|
Tty: true,
|
||||||
Image: IMAGE,
|
Image: IMAGE,
|
||||||
HostConfig: {
|
PortBindings: {[`${SERVER_PORT}/tcp`]: [{HostPort: `${HOST_PORT}`}]},
|
||||||
PortBindings: { [`${SERVER_PORT}/tcp`]: [{ HostPort: `${HOST_PORT}` }] },
|
|
||||||
},
|
|
||||||
ExposedPorts: { [`${SERVER_PORT}/tcp`]: {} },
|
|
||||||
});
|
});
|
||||||
await container.start();
|
await container.start();
|
||||||
|
|
||||||
const exec = await container.exec({
|
const exec = await container.exec({
|
||||||
Cmd: ["ng-auth", "-u", GIT_USERNAME, "-p", GIT_PASSWORD],
|
Cmd: ['ng-auth', '-u', GIT_USERNAME, '-p', GIT_PASSWORD],
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
});
|
});
|
||||||
@ -62,9 +51,9 @@ export async function stop() {
|
|||||||
* @param {String} [description=`Repository ${name}`] The repository description.
|
* @param {String} [description=`Repository ${name}`] The repository description.
|
||||||
* @return {Object} The `repositoryUrl` (URL without auth) and `authUrl` (URL with auth).
|
* @return {Object} The `repositoryUrl` (URL without auth) and `authUrl` (URL with auth).
|
||||||
*/
|
*/
|
||||||
export async function createRepo(name, branch = "master", description = `Repository ${name}`) {
|
export async function createRepo(name, branch = 'master', description = `Repository ${name}`) {
|
||||||
const exec = await container.exec({
|
const exec = await container.exec({
|
||||||
Cmd: ["repo-admin", "-n", name, "-d", description],
|
Cmd: ['repo-admin', '-n', name, '-d', description],
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
});
|
});
|
||||||
@ -74,8 +63,8 @@ export async function createRepo(name, branch = "master", description = `Reposit
|
|||||||
const authUrl = `http://${gitCredential}@${SERVER_HOST}:${HOST_PORT}/git/${name}.git`;
|
const authUrl = `http://${gitCredential}@${SERVER_HOST}:${HOST_PORT}/git/${name}.git`;
|
||||||
|
|
||||||
// Retry as the server might take a few ms to make the repo available push
|
// Retry as the server might take a few ms to make the repo available push
|
||||||
await pRetry(() => initBareRepo(authUrl, branch), { retries: 5, minTimeout: 500, factor: 2 });
|
await pRetry(() => initBareRepo(authUrl, branch), {retries: 5, minTimeout: 500, factor: 2});
|
||||||
const cwd = await gitShallowClone(authUrl);
|
const cwd = await gitShallowClone(authUrl);
|
||||||
|
|
||||||
return { cwd, repositoryUrl, authUrl };
|
return {cwd, repositoryUrl, authUrl};
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,31 @@
|
|||||||
import Docker from "dockerode";
|
import Docker from 'dockerode';
|
||||||
import got from "got";
|
import getStream from 'get-stream';
|
||||||
import pRetry from "p-retry";
|
import got from 'got';
|
||||||
import { mockServerClient } from "mockserver-client";
|
import pRetry from 'p-retry';
|
||||||
|
import {mockServerClient} from 'mockserver-client';
|
||||||
|
|
||||||
const IMAGE = "mockserver/mockserver:latest";
|
const IMAGE = 'mockserver/mockserver:latest';
|
||||||
const MOCK_SERVER_PORT = 1080;
|
const MOCK_SERVER_PORT = 1080;
|
||||||
const MOCK_SERVER_HOST = "localhost";
|
const MOCK_SERVER_HOST = 'localhost';
|
||||||
const docker = new Docker();
|
const docker = new Docker();
|
||||||
let container;
|
let container;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download the `mockserver` Docker image,
|
* Download the `mockserver` Docker image, create a new container and start it.
|
||||||
*/
|
|
||||||
export function pull() {
|
|
||||||
return docker.pull(IMAGE).then((stream) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
docker.modem.followProgress(stream, (err, res) => (err ? reject(err) : resolve(res)));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create a new container and start it.
|
|
||||||
*/
|
*/
|
||||||
export async function start() {
|
export async function start() {
|
||||||
|
await getStream(await docker.pull(IMAGE));
|
||||||
|
|
||||||
container = await docker.createContainer({
|
container = await docker.createContainer({
|
||||||
Tty: true,
|
Tty: true,
|
||||||
Image: IMAGE,
|
Image: IMAGE,
|
||||||
HostConfig: {
|
PortBindings: {[`${MOCK_SERVER_PORT}/tcp`]: [{HostPort: `${MOCK_SERVER_PORT}`}]},
|
||||||
PortBindings: { [`${MOCK_SERVER_PORT}/tcp`]: [{ HostPort: `${MOCK_SERVER_PORT}` }] },
|
|
||||||
},
|
|
||||||
ExposedPorts: { [`${MOCK_SERVER_PORT}/tcp`]: {} },
|
|
||||||
});
|
});
|
||||||
await container.start();
|
await container.start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wait for the mock server to be ready
|
// Wait for the mock server to be ready
|
||||||
await pRetry(() => got.put(`http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}/status`, { cache: false }), {
|
await pRetry(() => got.put(`http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}/status`, {cache: false}), {
|
||||||
retries: 7,
|
retries: 7,
|
||||||
minTimeout: 1000,
|
minTimeout: 1000,
|
||||||
factor: 2,
|
factor: 2,
|
||||||
@ -78,17 +67,17 @@ export const url = `http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}`;
|
|||||||
*/
|
*/
|
||||||
export async function mock(
|
export async function mock(
|
||||||
path,
|
path,
|
||||||
{ body: requestBody, headers: requestHeaders },
|
{body: requestBody, headers: requestHeaders},
|
||||||
{ method = "POST", statusCode = 200, body: responseBody }
|
{method = 'POST', statusCode = 200, body: responseBody}
|
||||||
) {
|
) {
|
||||||
await client.mockAnyResponse({
|
await client.mockAnyResponse({
|
||||||
httpRequest: { path, method },
|
httpRequest: {path, method},
|
||||||
httpResponse: {
|
httpResponse: {
|
||||||
statusCode,
|
statusCode,
|
||||||
headers: [{ name: "Content-Type", values: ["application/json; charset=utf-8"] }],
|
headers: [{name: 'Content-Type', values: ['application/json; charset=utf-8']}],
|
||||||
body: JSON.stringify(responseBody),
|
body: JSON.stringify(responseBody),
|
||||||
},
|
},
|
||||||
times: { remainingTimes: 1, unlimited: false },
|
times: {remainingTimes: 1, unlimited: false},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -96,7 +85,7 @@ export async function mock(
|
|||||||
path,
|
path,
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
body: requestBody
|
body: requestBody
|
||||||
? { type: "JSON", json: JSON.stringify(requestBody), matchType: "ONLY_MATCHING_FIELDS" }
|
? {type: 'JSON', json: JSON.stringify(requestBody), matchType: 'ONLY_MATCHING_FIELDS'}
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,51 +1,40 @@
|
|||||||
import path, { dirname } from "node:path";
|
import path, {dirname} from 'node:path';
|
||||||
import { fileURLToPath } from "node:url";
|
import {fileURLToPath} from 'node:url';
|
||||||
import { setTimeout } from "node:timers/promises";
|
import Docker from 'dockerode';
|
||||||
import Docker from "dockerode";
|
import getStream from 'get-stream';
|
||||||
import got from "got";
|
import got from 'got';
|
||||||
import pRetry from "p-retry";
|
import delay from 'delay';
|
||||||
|
import pRetry from 'p-retry';
|
||||||
|
|
||||||
const IMAGE = "verdaccio/verdaccio:5";
|
const IMAGE = 'verdaccio/verdaccio:5';
|
||||||
const REGISTRY_PORT = 4873;
|
const REGISTRY_PORT = 4873;
|
||||||
const REGISTRY_HOST = "localhost";
|
const REGISTRY_HOST = 'localhost';
|
||||||
const NPM_USERNAME = "integration";
|
const NPM_USERNAME = 'integration';
|
||||||
const NPM_PASSWORD = "suchsecure";
|
const NPM_PASSWORD = 'suchsecure';
|
||||||
const NPM_EMAIL = "integration@test.com";
|
const NPM_EMAIL = 'integration@test.com';
|
||||||
const docker = new Docker();
|
const docker = new Docker();
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
let container, npmToken;
|
let container, npmToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download the `npm-registry-docker` Docker image
|
* Download the `npm-registry-docker` Docker image, create a new container and start it.
|
||||||
*/
|
|
||||||
export function pull() {
|
|
||||||
return docker.pull(IMAGE).then((stream) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
docker.modem.followProgress(stream, (err, res) => (err ? reject(err) : resolve(res)));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create a new container and start it.
|
|
||||||
*/
|
*/
|
||||||
export async function start() {
|
export async function start() {
|
||||||
|
await getStream(await docker.pull(IMAGE));
|
||||||
|
|
||||||
container = await docker.createContainer({
|
container = await docker.createContainer({
|
||||||
Tty: true,
|
Tty: true,
|
||||||
Image: IMAGE,
|
Image: IMAGE,
|
||||||
HostConfig: {
|
PortBindings: {[`${REGISTRY_PORT}/tcp`]: [{HostPort: `${REGISTRY_PORT}`}]},
|
||||||
PortBindings: { [`${REGISTRY_PORT}/tcp`]: [{ HostPort: `${REGISTRY_PORT}` }] },
|
Binds: [`${path.join(__dirname, 'config.yaml')}:/verdaccio/conf/config.yaml`],
|
||||||
Binds: [`${path.join(__dirname, "config.yaml")}:/verdaccio/conf/config.yaml`],
|
|
||||||
},
|
|
||||||
ExposedPorts: { [`${REGISTRY_PORT}/tcp`]: {} },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await container.start();
|
await container.start();
|
||||||
await setTimeout(4000);
|
await delay(4000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wait for the registry to be ready
|
// Wait for the registry to be ready
|
||||||
await pRetry(() => got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/`, { cache: false }), {
|
await pRetry(() => got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/`, {cache: false}), {
|
||||||
retries: 7,
|
retries: 7,
|
||||||
minTimeout: 1000,
|
minTimeout: 1000,
|
||||||
factor: 2,
|
factor: 2,
|
||||||
@ -56,24 +45,24 @@ export async function start() {
|
|||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/user/org.couchdb.user:${NPM_USERNAME}`, {
|
await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/user/org.couchdb.user:${NPM_USERNAME}`, {
|
||||||
method: "PUT",
|
method: 'PUT',
|
||||||
json: {
|
json: {
|
||||||
_id: `org.couchdb.user:${NPM_USERNAME}`,
|
_id: `org.couchdb.user:${NPM_USERNAME}`,
|
||||||
name: NPM_USERNAME,
|
name: NPM_USERNAME,
|
||||||
roles: [],
|
roles: [],
|
||||||
type: "user",
|
type: 'user',
|
||||||
password: NPM_PASSWORD,
|
password: NPM_PASSWORD,
|
||||||
email: NPM_EMAIL,
|
email: NPM_EMAIL,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create token for user
|
// Create token for user
|
||||||
({ token: npmToken } = await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/npm/v1/tokens`, {
|
({token: npmToken} = await got(`http://${REGISTRY_HOST}:${REGISTRY_PORT}/-/npm/v1/tokens`, {
|
||||||
username: NPM_USERNAME,
|
username: NPM_USERNAME,
|
||||||
password: NPM_PASSWORD,
|
password: NPM_PASSWORD,
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: { "content-type": "application/json" },
|
headers: {'content-type': 'application/json'},
|
||||||
json: { password: NPM_PASSWORD, readonly: false, cidr_whitelist: [] },
|
json: {password: NPM_PASSWORD, readonly: false, cidr_whitelist: []}
|
||||||
}).json());
|
}).json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { execa } from "execa";
|
import {execa} from 'execa';
|
||||||
|
|
||||||
export async function npmView(packageName, env) {
|
export async function npmView(packageName, env) {
|
||||||
return JSON.parse((await execa("npm", ["view", packageName, "--json"], { env })).stdout);
|
return JSON.parse((await execa('npm', ['view', packageName, '--json'], {env})).stdout);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ test("Replace multiple sensitive environment variable values", (t) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Replace multiple occurrences of sensitive environment variable values", (t) => {
|
test("Replace multiple occurences of sensitive environment variable values", (t) => {
|
||||||
const env = { secretKey: "secret" };
|
const env = { secretKey: "secret" };
|
||||||
t.is(
|
t.is(
|
||||||
hideSensitive(env)(`https://user:${env.secretKey}@host.com?token=${env.secretKey}`),
|
hideSensitive(env)(`https://user:${env.secretKey}@host.com?token=${env.secretKey}`),
|
||||||
|
@ -39,11 +39,7 @@ test.beforeEach((t) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
test.afterEach.always((t) => {
|
test("Plugins are called with expected values", async (t) => {
|
||||||
td.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.serial("Plugins are called with expected values", async (t) => {
|
|
||||||
// Create a git repository, set the current working directory at the root of the repo
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
const { cwd, repositoryUrl } = await gitRepo(true);
|
const { cwd, repositoryUrl } = await gitRepo(true);
|
||||||
// Add commits to the master branch
|
// Add commits to the master branch
|
||||||
@ -1853,19 +1849,10 @@ test.serial("Throw an Error if plugin returns an unexpected value", async (t) =>
|
|||||||
await td.replaceEsm("../lib/get-logger.js", null, () => t.context.logger);
|
await td.replaceEsm("../lib/get-logger.js", null, () => t.context.logger);
|
||||||
await td.replaceEsm("env-ci", null, () => ({ isCi: true, branch: "master", isPr: false }));
|
await td.replaceEsm("env-ci", null, () => ({ isCi: true, branch: "master", isPr: false }));
|
||||||
const semanticRelease = (await import("../index.js")).default;
|
const semanticRelease = (await import("../index.js")).default;
|
||||||
|
const error = await t.throwsAsync(
|
||||||
let error;
|
semanticRelease(options, { cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer() }),
|
||||||
try {
|
{ instanceOf: SemanticReleaseError }
|
||||||
await semanticRelease(options, {
|
);
|
||||||
cwd,
|
|
||||||
env: {},
|
|
||||||
stdout: new WritableStreamBuffer(),
|
|
||||||
stderr: new WritableStreamBuffer(),
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
t.is(error.code, "EANALYZECOMMITSOUTPUT");
|
|
||||||
t.regex(error.details, /string/);
|
t.regex(error.details, /string/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import path from "node:path";
|
import path from "path";
|
||||||
import { setTimeout } from "node:timers/promises";
|
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
import * as td from "testdouble";
|
import * as td from "testdouble";
|
||||||
import { escapeRegExp } from "lodash-es";
|
import { escapeRegExp } from "lodash-es";
|
||||||
import fsExtra from "fs-extra";
|
import fsExtra from "fs-extra";
|
||||||
import { execa } from "execa";
|
import { execa } from "execa";
|
||||||
import { WritableStreamBuffer } from "stream-buffers";
|
import { WritableStreamBuffer } from "stream-buffers";
|
||||||
|
import delay from "delay";
|
||||||
|
|
||||||
import getAuthUrl from "../lib/get-git-auth-url.js";
|
import getAuthUrl from "../lib/get-git-auth-url.js";
|
||||||
import { SECRET_REPLACEMENT } from "../lib/definitions/constants.js";
|
import { SECRET_REPLACEMENT } from "../lib/definitions/constants.js";
|
||||||
@ -35,7 +35,7 @@ let env;
|
|||||||
|
|
||||||
// Environment variables used only for the local npm command used to do verification
|
// Environment variables used only for the local npm command used to do verification
|
||||||
const npmTestEnv = {
|
const npmTestEnv = {
|
||||||
...processEnvWithoutGitHubActionsVariables,
|
...process.env,
|
||||||
...npmRegistry.authEnv(),
|
...npmRegistry.authEnv(),
|
||||||
npm_config_registry: npmRegistry.url,
|
npm_config_registry: npmRegistry.url,
|
||||||
};
|
};
|
||||||
@ -44,10 +44,8 @@ const cli = path.resolve("./bin/semantic-release.js");
|
|||||||
const pluginError = path.resolve("./test/fixtures/plugin-error");
|
const pluginError = path.resolve("./test/fixtures/plugin-error");
|
||||||
const pluginInheritedError = path.resolve("./test/fixtures/plugin-error-inherited");
|
const pluginInheritedError = path.resolve("./test/fixtures/plugin-error-inherited");
|
||||||
const pluginLogEnv = path.resolve("./test/fixtures/plugin-log-env");
|
const pluginLogEnv = path.resolve("./test/fixtures/plugin-log-env");
|
||||||
const pluginEsmNamedExports = path.resolve("./test/fixtures/plugin-esm-named-exports");
|
|
||||||
|
|
||||||
test.before(async () => {
|
test.before(async () => {
|
||||||
await Promise.all([gitbox.pull(), npmRegistry.pull(), mockServer.pull()]);
|
|
||||||
await Promise.all([gitbox.start(), npmRegistry.start(), mockServer.start()]);
|
await Promise.all([gitbox.start(), npmRegistry.start(), mockServer.start()]);
|
||||||
|
|
||||||
env = {
|
env = {
|
||||||
@ -296,7 +294,7 @@ test("Release patch, minor and major versions", async (t) => {
|
|||||||
t.is(exitCode, 0);
|
t.is(exitCode, 0);
|
||||||
|
|
||||||
// Wait for 3s as the change of dist-tag takes time to be reflected in the registry
|
// Wait for 3s as the change of dist-tag takes time to be reflected in the registry
|
||||||
await setTimeout(3000);
|
await delay(3000);
|
||||||
// Retrieve the published package from the registry and check version and gitHead
|
// Retrieve the published package from the registry and check version and gitHead
|
||||||
({
|
({
|
||||||
"dist-tags": { latest: releasedVersion },
|
"dist-tags": { latest: releasedVersion },
|
||||||
@ -516,8 +514,8 @@ test("Pass options via CLI arguments", async (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Run via JS API", async (t) => {
|
test("Run via JS API", async (t) => {
|
||||||
await td.replaceEsm("../lib/logger", null, { log: () => {}, error: () => {}, stdout: () => {} });
|
td.replace("../lib/logger", { log: () => {}, error: () => {}, stdout: () => {} });
|
||||||
await td.replaceEsm("env-ci", null, () => ({ isCi: true, branch: "master", isPr: false }));
|
td.replace("env-ci", () => ({ isCi: true, branch: "master", isPr: false }));
|
||||||
const semanticRelease = (await import("../index.js")).default;
|
const semanticRelease = (await import("../index.js")).default;
|
||||||
const packageName = "test-js-api";
|
const packageName = "test-js-api";
|
||||||
const owner = "git";
|
const owner = "git";
|
||||||
@ -599,7 +597,7 @@ test("Log unexpected errors from plugins and exit with 1", async (t) => {
|
|||||||
// Verify the type and message are logged
|
// Verify the type and message are logged
|
||||||
t.regex(stderr, /Error: a/);
|
t.regex(stderr, /Error: a/);
|
||||||
// Verify the the stacktrace is logged
|
// Verify the the stacktrace is logged
|
||||||
t.regex(stderr, new RegExp(process.platform === "win32" ? pluginError.replace(/\\/g, "\\\\") : pluginError));
|
t.regex(stderr, new RegExp(pluginError));
|
||||||
// Verify the Error properties are logged
|
// Verify the Error properties are logged
|
||||||
t.regex(stderr, /errorProperty: 'errorProperty'/);
|
t.regex(stderr, /errorProperty: 'errorProperty'/);
|
||||||
t.is(exitCode, 1);
|
t.is(exitCode, 1);
|
||||||
@ -688,7 +686,6 @@ test("Use the valid git credentials when multiple are provided", async (t) => {
|
|||||||
BB_TOKEN_BASIC_AUTH: gitbox.gitCredential,
|
BB_TOKEN_BASIC_AUTH: gitbox.gitCredential,
|
||||||
GIT_ASKPASS: "echo",
|
GIT_ASKPASS: "echo",
|
||||||
GIT_TERMINAL_PROMPT: 0,
|
GIT_TERMINAL_PROMPT: 0,
|
||||||
GIT_CONFIG_PARAMETERS: "'credential.helper='",
|
|
||||||
},
|
},
|
||||||
branch: { name: "master" },
|
branch: { name: "master" },
|
||||||
options: { repositoryUrl: "http://toto@localhost:2080/git/test-auth.git" },
|
options: { repositoryUrl: "http://toto@localhost:2080/git/test-auth.git" },
|
||||||
@ -709,7 +706,6 @@ test("Use the repository URL as is if none of the given git credentials are vali
|
|||||||
GITLAB_TOKEN: "trash",
|
GITLAB_TOKEN: "trash",
|
||||||
GIT_ASKPASS: "echo",
|
GIT_ASKPASS: "echo",
|
||||||
GIT_TERMINAL_PROMPT: 0,
|
GIT_TERMINAL_PROMPT: 0,
|
||||||
GIT_CONFIG_PARAMETERS: "'credential.helper='",
|
|
||||||
},
|
},
|
||||||
branch: { name: "master" },
|
branch: { name: "master" },
|
||||||
options: { repositoryUrl: dummyUrl },
|
options: { repositoryUrl: dummyUrl },
|
||||||
@ -717,26 +713,3 @@ test("Use the repository URL as is if none of the given git credentials are vali
|
|||||||
dummyUrl
|
dummyUrl
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("ESM Plugin with named exports", async (t) => {
|
|
||||||
const packageName = "plugin-exports";
|
|
||||||
// Create a git repository, set the current working directory at the root of the repo
|
|
||||||
t.log("Create git repository");
|
|
||||||
const { cwd, repositoryUrl } = await gitbox.createRepo(packageName);
|
|
||||||
await writeJson(path.resolve(cwd, "package.json"), {
|
|
||||||
name: packageName,
|
|
||||||
version: "0.0.0-dev",
|
|
||||||
repository: { url: repositoryUrl },
|
|
||||||
release: { plugins: [pluginEsmNamedExports] },
|
|
||||||
});
|
|
||||||
|
|
||||||
t.log("$ semantic-release");
|
|
||||||
const { stdout, stderr } = await execa(cli, [], {
|
|
||||||
env: { ...env, MY_TOKEN: "secret token" },
|
|
||||||
cwd,
|
|
||||||
reject: false,
|
|
||||||
extendEnv: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.regex(stdout, new RegExp(`verifyConditions called`));
|
|
||||||
});
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import { noop } from "lodash-es";
|
import {noop} from 'lodash-es';
|
||||||
import { stub } from "sinon";
|
import {stub} from 'sinon';
|
||||||
import normalize from "../../lib/plugins/normalize.js";
|
import normalize from '../../lib/plugins/normalize.js';
|
||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ test.beforeEach((t) => {
|
|||||||
t.context.log = stub();
|
t.context.log = stub();
|
||||||
t.context.error = stub();
|
t.context.error = stub();
|
||||||
t.context.success = stub();
|
t.context.success = stub();
|
||||||
t.context.stderr = { write: stub() };
|
t.context.stderr = {write: stub()};
|
||||||
t.context.logger = {
|
t.context.logger = {
|
||||||
log: t.context.log,
|
log: t.context.log,
|
||||||
error: t.context.error,
|
error: t.context.error,
|
||||||
@ -19,108 +19,98 @@ test.beforeEach((t) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Normalize and load plugin from string", async (t) => {
|
test('Normalize and load plugin from string', async (t) => {
|
||||||
const plugin = await normalize(
|
const plugin = await normalize(
|
||||||
{ cwd, options: {}, logger: t.context.logger },
|
{cwd, options: {}, logger: t.context.logger},
|
||||||
"verifyConditions",
|
'verifyConditions',
|
||||||
"./test/fixtures/plugin-noop.cjs",
|
'./test/fixtures/plugin-noop.cjs',
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
t.is(plugin.pluginName, "./test/fixtures/plugin-noop.cjs");
|
t.is(plugin.pluginName, './test/fixtures/plugin-noop.cjs');
|
||||||
t.is(typeof plugin, "function");
|
t.is(typeof plugin, 'function');
|
||||||
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/plugin-noop.cjs"']);
|
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/plugin-noop.cjs"']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Normalize and load plugin from object", async (t) => {
|
test('Normalize and load plugin from object', async (t) => {
|
||||||
const plugin = await normalize(
|
const plugin = await normalize(
|
||||||
{ cwd, options: {}, logger: t.context.logger },
|
{cwd, options: {}, logger: t.context.logger},
|
||||||
"publish",
|
'publish',
|
||||||
{ path: "./test/fixtures/plugin-noop.cjs" },
|
{path: './test/fixtures/plugin-noop.cjs'},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
t.is(plugin.pluginName, "./test/fixtures/plugin-noop.cjs");
|
t.is(plugin.pluginName, './test/fixtures/plugin-noop.cjs');
|
||||||
t.is(typeof plugin, "function");
|
t.is(typeof plugin, 'function');
|
||||||
t.deepEqual(t.context.success.args[0], ['Loaded plugin "publish" from "./test/fixtures/plugin-noop.cjs"']);
|
t.deepEqual(t.context.success.args[0], ['Loaded plugin "publish" from "./test/fixtures/plugin-noop.cjs"']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Normalize and load plugin from a base file path", async (t) => {
|
test('Normalize and load plugin from a base file path', async (t) => {
|
||||||
const plugin = await normalize(
|
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-noop.cjs', {
|
||||||
{ cwd, options: {}, logger: t.context.logger },
|
'./plugin-noop.cjs': './test/fixtures',
|
||||||
"verifyConditions",
|
});
|
||||||
"./plugin-noop.cjs",
|
|
||||||
{
|
|
||||||
"./plugin-noop.cjs": "./test/fixtures",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
t.is(plugin.pluginName, "./plugin-noop.cjs");
|
t.is(plugin.pluginName, './plugin-noop.cjs');
|
||||||
t.is(typeof plugin, "function");
|
t.is(typeof plugin, 'function');
|
||||||
t.deepEqual(t.context.success.args[0], [
|
t.deepEqual(t.context.success.args[0], [
|
||||||
'Loaded plugin "verifyConditions" from "./plugin-noop.cjs" in shareable config "./test/fixtures"',
|
'Loaded plugin "verifyConditions" from "./plugin-noop.cjs" in shareable config "./test/fixtures"',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Wrap plugin in a function that add the "pluginName" to the error"', async (t) => {
|
test('Wrap plugin in a function that add the "pluginName" to the error"', async (t) => {
|
||||||
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger }, "verifyConditions", "./plugin-error", {
|
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-error', {
|
||||||
"./plugin-error": "./test/fixtures",
|
'./plugin-error': './test/fixtures',
|
||||||
});
|
});
|
||||||
|
|
||||||
const error = await t.throwsAsync(plugin({ options: {} }));
|
const error = await t.throwsAsync(plugin({options: {}}));
|
||||||
|
|
||||||
t.is(error.pluginName, "./plugin-error");
|
t.is(error.pluginName, './plugin-error');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Wrap plugin in a function that add the "pluginName" to multiple errors"', async (t) => {
|
test('Wrap plugin in a function that add the "pluginName" to multiple errors"', async (t) => {
|
||||||
const plugin = await normalize(
|
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, 'verifyConditions', './plugin-errors', {
|
||||||
{ cwd, options: {}, logger: t.context.logger },
|
'./plugin-errors': './test/fixtures',
|
||||||
"verifyConditions",
|
});
|
||||||
"./plugin-errors",
|
|
||||||
{
|
|
||||||
"./plugin-errors": "./test/fixtures",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const errors = [...(await t.throwsAsync(plugin({ options: {} }))).errors];
|
const errors = [...(await t.throwsAsync(plugin({options: {}}))).errors];
|
||||||
for (const error of errors) {
|
for (const error of errors) {
|
||||||
t.is(error.pluginName, "./plugin-errors");
|
t.is(error.pluginName, './plugin-errors');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Normalize and load plugin from function", async (t) => {
|
test('Normalize and load plugin from function', async (t) => {
|
||||||
const pluginFunction = () => {};
|
const pluginFunction = () => {};
|
||||||
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger }, "", pluginFunction, {});
|
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
|
||||||
|
|
||||||
t.is(plugin.pluginName, "[Function: pluginFunction]");
|
t.is(plugin.pluginName, '[Function: pluginFunction]');
|
||||||
t.is(typeof plugin, "function");
|
t.is(typeof plugin, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Normalize and load plugin that returns multiple functions", async (t) => {
|
test('Normalize and load plugin that retuns multiple functions', async (t) => {
|
||||||
const plugin = await normalize(
|
const plugin = await normalize(
|
||||||
{ cwd, options: {}, logger: t.context.logger },
|
{cwd, options: {}, logger: t.context.logger},
|
||||||
"verifyConditions",
|
'verifyConditions',
|
||||||
"./test/fixtures/multi-plugin.cjs",
|
'./test/fixtures/multi-plugin.cjs',
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
t.is(typeof plugin, "function");
|
t.is(typeof plugin, 'function');
|
||||||
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/multi-plugin.cjs"']);
|
t.deepEqual(t.context.success.args[0], ['Loaded plugin "verifyConditions" from "./test/fixtures/multi-plugin.cjs"']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async (t) => {
|
test('Wrap "analyzeCommits" plugin in a function that validate the output of the plugin', async (t) => {
|
||||||
const analyzeCommits = stub().resolves(2);
|
const analyzeCommits = stub().resolves(2);
|
||||||
const plugin = await normalize(
|
const plugin = await normalize(
|
||||||
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
|
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
|
||||||
"analyzeCommits",
|
'analyzeCommits',
|
||||||
analyzeCommits,
|
analyzeCommits,
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = await t.throwsAsync(plugin({ options: {} }));
|
const error = await t.throwsAsync(plugin({options: {}}));
|
||||||
|
|
||||||
t.is(error.code, "EANALYZECOMMITSOUTPUT");
|
t.is(error.code, 'EANALYZECOMMITSOUTPUT');
|
||||||
t.is(error.name, "SemanticReleaseError");
|
t.is(error.name, 'SemanticReleaseError');
|
||||||
t.truthy(error.message);
|
t.truthy(error.message);
|
||||||
t.truthy(error.details);
|
t.truthy(error.details);
|
||||||
t.regex(error.details, /2/);
|
t.regex(error.details, /2/);
|
||||||
@ -129,16 +119,16 @@ test('Wrap "analyzeCommits" plugin in a function that validate the output of the
|
|||||||
test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async (t) => {
|
test('Wrap "generateNotes" plugin in a function that validate the output of the plugin', async (t) => {
|
||||||
const generateNotes = stub().resolves(2);
|
const generateNotes = stub().resolves(2);
|
||||||
const plugin = await normalize(
|
const plugin = await normalize(
|
||||||
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
|
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
|
||||||
"generateNotes",
|
'generateNotes',
|
||||||
generateNotes,
|
generateNotes,
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = await t.throwsAsync(plugin({ options: {} }));
|
const error = await t.throwsAsync(plugin({options: {}}));
|
||||||
|
|
||||||
t.is(error.code, "EGENERATENOTESOUTPUT");
|
t.is(error.code, 'EGENERATENOTESOUTPUT');
|
||||||
t.is(error.name, "SemanticReleaseError");
|
t.is(error.name, 'SemanticReleaseError');
|
||||||
t.truthy(error.message);
|
t.truthy(error.message);
|
||||||
t.truthy(error.details);
|
t.truthy(error.details);
|
||||||
t.regex(error.details, /2/);
|
t.regex(error.details, /2/);
|
||||||
@ -147,16 +137,16 @@ test('Wrap "generateNotes" plugin in a function that validate the output of the
|
|||||||
test('Wrap "publish" plugin in a function that validate the output of the plugin', async (t) => {
|
test('Wrap "publish" plugin in a function that validate the output of the plugin', async (t) => {
|
||||||
const publish = stub().resolves(2);
|
const publish = stub().resolves(2);
|
||||||
const plugin = await normalize(
|
const plugin = await normalize(
|
||||||
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
|
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
|
||||||
"publish",
|
'publish',
|
||||||
publish,
|
publish,
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = await t.throwsAsync(plugin({ options: {} }));
|
const error = await t.throwsAsync(plugin({options: {}}));
|
||||||
|
|
||||||
t.is(error.code, "EPUBLISHOUTPUT");
|
t.is(error.code, 'EPUBLISHOUTPUT');
|
||||||
t.is(error.name, "SemanticReleaseError");
|
t.is(error.name, 'SemanticReleaseError');
|
||||||
t.truthy(error.message);
|
t.truthy(error.message);
|
||||||
t.truthy(error.details);
|
t.truthy(error.details);
|
||||||
t.regex(error.details, /2/);
|
t.regex(error.details, /2/);
|
||||||
@ -165,16 +155,16 @@ test('Wrap "publish" plugin in a function that validate the output of the plugin
|
|||||||
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async (t) => {
|
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async (t) => {
|
||||||
const addChannel = stub().resolves(2);
|
const addChannel = stub().resolves(2);
|
||||||
const plugin = await normalize(
|
const plugin = await normalize(
|
||||||
{ cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger },
|
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
|
||||||
"addChannel",
|
'addChannel',
|
||||||
addChannel,
|
addChannel,
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = await t.throwsAsync(plugin({ options: {} }));
|
const error = await t.throwsAsync(plugin({options: {}}));
|
||||||
|
|
||||||
t.is(error.code, "EADDCHANNELOUTPUT");
|
t.is(error.code, 'EADDCHANNELOUTPUT');
|
||||||
t.is(error.name, "SemanticReleaseError");
|
t.is(error.name, 'SemanticReleaseError');
|
||||||
t.truthy(error.message);
|
t.truthy(error.message);
|
||||||
t.truthy(error.details);
|
t.truthy(error.details);
|
||||||
t.regex(error.details, /2/);
|
t.regex(error.details, /2/);
|
||||||
@ -182,60 +172,60 @@ test('Wrap "addChannel" plugin in a function that validate the output of the plu
|
|||||||
|
|
||||||
test('Plugin is called with "pluginConfig" (with object definition) and input', async (t) => {
|
test('Plugin is called with "pluginConfig" (with object definition) and input', async (t) => {
|
||||||
const pluginFunction = stub().resolves();
|
const pluginFunction = stub().resolves();
|
||||||
const pluginConf = { path: pluginFunction, conf: "confValue" };
|
const pluginConf = {path: pluginFunction, conf: 'confValue'};
|
||||||
const options = { global: "globalValue" };
|
const options = {global: 'globalValue'};
|
||||||
const plugin = await normalize({ cwd, options, logger: t.context.logger }, "", pluginConf, {});
|
const plugin = await normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
|
||||||
await plugin({ options: {}, param: "param" });
|
await plugin({options: {}, param: 'param'});
|
||||||
|
|
||||||
t.true(
|
t.true(
|
||||||
pluginFunction.calledWithMatch(
|
pluginFunction.calledWithMatch(
|
||||||
{ conf: "confValue", global: "globalValue" },
|
{conf: 'confValue', global: 'globalValue'},
|
||||||
{ param: "param", logger: t.context.logger }
|
{param: 'param', logger: t.context.logger}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Plugin is called with "pluginConfig" (with array definition) and input', async (t) => {
|
test('Plugin is called with "pluginConfig" (with array definition) and input', async (t) => {
|
||||||
const pluginFunction = stub().resolves();
|
const pluginFunction = stub().resolves();
|
||||||
const pluginConf = [pluginFunction, { conf: "confValue" }];
|
const pluginConf = [pluginFunction, {conf: 'confValue'}];
|
||||||
const options = { global: "globalValue" };
|
const options = {global: 'globalValue'};
|
||||||
const plugin = await normalize({ cwd, options, logger: t.context.logger }, "", pluginConf, {});
|
const plugin = await normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
|
||||||
await plugin({ options: {}, param: "param" });
|
await plugin({options: {}, param: 'param'});
|
||||||
|
|
||||||
t.true(
|
t.true(
|
||||||
pluginFunction.calledWithMatch(
|
pluginFunction.calledWithMatch(
|
||||||
{ conf: "confValue", global: "globalValue" },
|
{conf: 'confValue', global: 'globalValue'},
|
||||||
{ param: "param", logger: t.context.logger }
|
{param: 'param', logger: t.context.logger}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prevent plugins to modify "pluginConfig"', async (t) => {
|
test('Prevent plugins to modify "pluginConfig"', async (t) => {
|
||||||
const pluginFunction = stub().callsFake((pluginConfig) => {
|
const pluginFunction = stub().callsFake((pluginConfig) => {
|
||||||
pluginConfig.conf.subConf = "otherConf";
|
pluginConfig.conf.subConf = 'otherConf';
|
||||||
});
|
});
|
||||||
const pluginConf = { path: pluginFunction, conf: { subConf: "originalConf" } };
|
const pluginConf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
|
||||||
const options = { globalConf: { globalSubConf: "originalGlobalConf" } };
|
const options = {globalConf: {globalSubConf: 'originalGlobalConf'}};
|
||||||
const plugin = await normalize({ cwd, options, logger: t.context.logger }, "", pluginConf, {});
|
const plugin = await normalize({cwd, options, logger: t.context.logger}, '', pluginConf, {});
|
||||||
await plugin({ options: {} });
|
await plugin({options: {}});
|
||||||
|
|
||||||
t.is(pluginConf.conf.subConf, "originalConf");
|
t.is(pluginConf.conf.subConf, 'originalConf');
|
||||||
t.is(options.globalConf.globalSubConf, "originalGlobalConf");
|
t.is(options.globalConf.globalSubConf, 'originalGlobalConf');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Prevent plugins to modify its input", async (t) => {
|
test('Prevent plugins to modify its input', async (t) => {
|
||||||
const pluginFunction = stub().callsFake((pluginConfig, options) => {
|
const pluginFunction = stub().callsFake((pluginConfig, options) => {
|
||||||
options.param.subParam = "otherParam";
|
options.param.subParam = 'otherParam';
|
||||||
});
|
});
|
||||||
const input = { param: { subParam: "originalSubParam" }, options: {} };
|
const input = {param: {subParam: 'originalSubParam'}, options: {}};
|
||||||
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger }, "", pluginFunction, {});
|
const plugin = await normalize({cwd, options: {}, logger: t.context.logger}, '', pluginFunction, {});
|
||||||
await plugin(input);
|
await plugin(input);
|
||||||
|
|
||||||
t.is(input.param.subParam, "originalSubParam");
|
t.is(input.param.subParam, 'originalSubParam');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Return noop if the plugin is not defined", async (t) => {
|
test('Return noop if the plugin is not defined', async (t) => {
|
||||||
const plugin = await normalize({ cwd, options: {}, logger: t.context.logger });
|
const plugin = await normalize({cwd, options: {}, logger: t.context.logger});
|
||||||
|
|
||||||
t.is(plugin, noop);
|
t.is(plugin, noop);
|
||||||
});
|
});
|
||||||
@ -243,12 +233,12 @@ test("Return noop if the plugin is not defined", async (t) => {
|
|||||||
test('Always pass a defined "pluginConfig" for plugin defined with string', async (t) => {
|
test('Always pass a defined "pluginConfig" for plugin defined with string', async (t) => {
|
||||||
// Call the normalize function with the path of a plugin that returns its config
|
// Call the normalize function with the path of a plugin that returns its config
|
||||||
const plugin = await normalize(
|
const plugin = await normalize(
|
||||||
{ cwd, options: {}, logger: t.context.logger },
|
{cwd, options: {}, logger: t.context.logger},
|
||||||
"",
|
'',
|
||||||
"./test/fixtures/plugin-result-config",
|
'./test/fixtures/plugin-result-config',
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const pluginResult = await plugin({ options: {} });
|
const pluginResult = await plugin({options: {}});
|
||||||
|
|
||||||
t.deepEqual(pluginResult.pluginConfig, {});
|
t.deepEqual(pluginResult.pluginConfig, {});
|
||||||
});
|
});
|
||||||
@ -256,38 +246,33 @@ test('Always pass a defined "pluginConfig" for plugin defined with string', asyn
|
|||||||
test('Always pass a defined "pluginConfig" for plugin defined with path', async (t) => {
|
test('Always pass a defined "pluginConfig" for plugin defined with path', async (t) => {
|
||||||
// Call the normalize function with the path of a plugin that returns its config
|
// Call the normalize function with the path of a plugin that returns its config
|
||||||
const plugin = await normalize(
|
const plugin = await normalize(
|
||||||
{ cwd, options: {}, logger: t.context.logger },
|
{cwd, options: {}, logger: t.context.logger},
|
||||||
"",
|
'',
|
||||||
{ path: "./test/fixtures/plugin-result-config" },
|
{path: './test/fixtures/plugin-result-config'},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const pluginResult = await plugin({ options: {} });
|
const pluginResult = await plugin({options: {}});
|
||||||
|
|
||||||
t.deepEqual(pluginResult.pluginConfig, {});
|
t.deepEqual(pluginResult.pluginConfig, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Throws an error if the plugin return an object without the expected plugin function", async (t) => {
|
test('Throws an error if the plugin return an object without the expected plugin function', async (t) => {
|
||||||
const error = await t.throwsAsync(() =>
|
const error = await t.throwsAsync(() =>
|
||||||
normalize(
|
normalize({cwd, options: {}, logger: t.context.logger}, 'nonExistentPlugin', './test/fixtures/multi-plugin.cjs', {})
|
||||||
{ cwd, options: {}, logger: t.context.logger },
|
|
||||||
"nonExistentPlugin",
|
|
||||||
"./test/fixtures/multi-plugin.cjs",
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
t.is(error.code, "EPLUGIN");
|
t.is(error.code, 'EPLUGIN');
|
||||||
t.is(error.name, "SemanticReleaseError");
|
t.is(error.name, 'SemanticReleaseError');
|
||||||
t.truthy(error.message);
|
t.truthy(error.message);
|
||||||
t.truthy(error.details);
|
t.truthy(error.details);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Throws an error if the plugin is not found", async (t) => {
|
test('Throws an error if the plugin is not found', async (t) => {
|
||||||
await t.throwsAsync(
|
await t.throwsAsync(
|
||||||
() => normalize({ cwd, options: {}, logger: t.context.logger }, "nonExistentPlugin", "non-existing-path", {}),
|
() => normalize({cwd, options: {}, logger: t.context.logger}, 'nonExistentPlugin', 'non-existing-path', {}),
|
||||||
{
|
{
|
||||||
message: /Cannot find module 'non-existing-path'/,
|
message: /Cannot find module 'non-existing-path'/,
|
||||||
code: "MODULE_NOT_FOUND",
|
code: 'MODULE_NOT_FOUND',
|
||||||
instanceOf: Error,
|
instanceOf: Error,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import { stub } from "sinon";
|
import {stub} from 'sinon';
|
||||||
import AggregateError from "aggregate-error";
|
import AggregateError from 'aggregate-error';
|
||||||
import pipeline from "../../lib/plugins/pipeline.js";
|
import pipeline from '../../lib/plugins/pipeline.js';
|
||||||
|
|
||||||
test("Execute each function in series passing the same input", async (t) => {
|
test('Execute each function in series passing the same input', async (t) => {
|
||||||
const step1 = stub().resolves(1);
|
const step1 = stub().resolves(1);
|
||||||
const step2 = stub().resolves(2);
|
const step2 = stub().resolves(2);
|
||||||
const step3 = stub().resolves(3);
|
const step3 = stub().resolves(3);
|
||||||
@ -25,7 +25,7 @@ test('Execute each function in series passing a transformed input from "getNextI
|
|||||||
const step4 = stub().resolves(4);
|
const step4 = stub().resolves(4);
|
||||||
const getNextInput = (lastResult, result) => lastResult + result;
|
const getNextInput = (lastResult, result) => lastResult + result;
|
||||||
|
|
||||||
const result = await pipeline([step1, step2, step3, step4], { settleAll: false, getNextInput })(0);
|
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(0);
|
||||||
|
|
||||||
t.deepEqual(result, [1, 2, 3, 4]);
|
t.deepEqual(result, [1, 2, 3, 4]);
|
||||||
t.true(step1.calledWith(0));
|
t.true(step1.calledWith(0));
|
||||||
@ -44,7 +44,7 @@ test('Execute each function in series passing the "lastResult" and "result" to "
|
|||||||
const step4 = stub().resolves(4);
|
const step4 = stub().resolves(4);
|
||||||
const getNextInput = stub().returnsArg(0);
|
const getNextInput = stub().returnsArg(0);
|
||||||
|
|
||||||
const result = await pipeline([step1, step2, step3, step4], { settleAll: false, getNextInput })(5);
|
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(5);
|
||||||
|
|
||||||
t.deepEqual(result, [1, 2, 3, 4]);
|
t.deepEqual(result, [1, 2, 3, 4]);
|
||||||
t.deepEqual(getNextInput.args, [
|
t.deepEqual(getNextInput.args, [
|
||||||
@ -63,7 +63,7 @@ test('Execute each function in series calling "transform" to modify the results'
|
|||||||
const getNextInput = stub().returnsArg(0);
|
const getNextInput = stub().returnsArg(0);
|
||||||
const transform = stub().callsFake((result) => result + 1);
|
const transform = stub().callsFake((result) => result + 1);
|
||||||
|
|
||||||
const result = await pipeline([step1, step2, step3, step4], { getNextInput, transform })(5);
|
const result = await pipeline([step1, step2, step3, step4], {getNextInput, transform})(5);
|
||||||
|
|
||||||
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
|
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
|
||||||
t.deepEqual(getNextInput.args, [
|
t.deepEqual(getNextInput.args, [
|
||||||
@ -82,7 +82,7 @@ test('Execute each function in series calling "transform" to modify the results
|
|||||||
const getNextInput = stub().returnsArg(0);
|
const getNextInput = stub().returnsArg(0);
|
||||||
const transform = stub().callsFake((result) => result + 1);
|
const transform = stub().callsFake((result) => result + 1);
|
||||||
|
|
||||||
const result = await pipeline([step1, step2, step3, step4], { settleAll: true, getNextInput, transform })(5);
|
const result = await pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput, transform})(5);
|
||||||
|
|
||||||
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
|
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
|
||||||
t.deepEqual(getNextInput.args, [
|
t.deepEqual(getNextInput.args, [
|
||||||
@ -93,24 +93,24 @@ test('Execute each function in series calling "transform" to modify the results
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Stop execution and throw error if a step rejects", async (t) => {
|
test('Stop execution and throw error if a step rejects', async (t) => {
|
||||||
const step1 = stub().resolves(1);
|
const step1 = stub().resolves(1);
|
||||||
const step2 = stub().rejects(new Error("test error"));
|
const step2 = stub().rejects(new Error('test error'));
|
||||||
const step3 = stub().resolves(3);
|
const step3 = stub().resolves(3);
|
||||||
|
|
||||||
const error = await t.throwsAsync(pipeline([step1, step2, step3])(0), {
|
const error = await t.throwsAsync(pipeline([step1, step2, step3])(0), {
|
||||||
instanceOf: Error,
|
instanceOf: Error,
|
||||||
message: "test error",
|
message: 'test error',
|
||||||
});
|
});
|
||||||
t.is(error.message, "test error");
|
t.is(error.message, 'test error');
|
||||||
t.true(step1.calledWith(0));
|
t.true(step1.calledWith(0));
|
||||||
t.true(step2.calledWith(0));
|
t.true(step2.calledWith(0));
|
||||||
t.true(step3.notCalled);
|
t.true(step3.notCalled);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Throw all errors from the first step throwing an AggregateError", async (t) => {
|
test('Throw all errors from the first step throwing an AggregateError', async (t) => {
|
||||||
const error1 = new Error("test error 1");
|
const error1 = new Error('test error 1');
|
||||||
const error2 = new Error("test error 2");
|
const error2 = new Error('test error 2');
|
||||||
|
|
||||||
const step1 = stub().resolves(1);
|
const step1 = stub().resolves(1);
|
||||||
const step2 = stub().rejects(new AggregateError([error1, error2]));
|
const step2 = stub().rejects(new AggregateError([error1, error2]));
|
||||||
@ -124,14 +124,14 @@ test("Throw all errors from the first step throwing an AggregateError", async (t
|
|||||||
t.true(step3.notCalled);
|
t.true(step3.notCalled);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Execute all even if a Promise rejects", async (t) => {
|
test('Execute all even if a Promise rejects', async (t) => {
|
||||||
const error1 = new Error("test error 1");
|
const error1 = new Error('test error 1');
|
||||||
const error2 = new Error("test error 2");
|
const error2 = new Error('test error 2');
|
||||||
const step1 = stub().resolves(1);
|
const step1 = stub().resolves(1);
|
||||||
const step2 = stub().rejects(error1);
|
const step2 = stub().rejects(error1);
|
||||||
const step3 = stub().rejects(error2);
|
const step3 = stub().rejects(error2);
|
||||||
|
|
||||||
const error = await t.throwsAsync(pipeline([step1, step2, step3], { settleAll: true })(0));
|
const error = await t.throwsAsync(pipeline([step1, step2, step3], {settleAll: true})(0));
|
||||||
|
|
||||||
t.deepEqual([...error.errors], [error1, error2]);
|
t.deepEqual([...error.errors], [error1, error2]);
|
||||||
t.true(step1.calledWith(0));
|
t.true(step1.calledWith(0));
|
||||||
@ -139,31 +139,31 @@ test("Execute all even if a Promise rejects", async (t) => {
|
|||||||
t.true(step3.calledWith(0));
|
t.true(step3.calledWith(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Throw all errors from all steps throwing an AggregateError", async (t) => {
|
test('Throw all errors from all steps throwing an AggregateError', async (t) => {
|
||||||
const error1 = new Error("test error 1");
|
const error1 = new Error('test error 1');
|
||||||
const error2 = new Error("test error 2");
|
const error2 = new Error('test error 2');
|
||||||
const error3 = new Error("test error 3");
|
const error3 = new Error('test error 3');
|
||||||
const error4 = new Error("test error 4");
|
const error4 = new Error('test error 4');
|
||||||
const step1 = stub().rejects(new AggregateError([error1, error2]));
|
const step1 = stub().rejects(new AggregateError([error1, error2]));
|
||||||
const step2 = stub().rejects(new AggregateError([error3, error4]));
|
const step2 = stub().rejects(new AggregateError([error3, error4]));
|
||||||
|
|
||||||
const error = await t.throwsAsync(pipeline([step1, step2], { settleAll: true })(0));
|
const error = await t.throwsAsync(pipeline([step1, step2], {settleAll: true})(0));
|
||||||
|
|
||||||
t.deepEqual([...error.errors], [error1, error2, error3, error4]);
|
t.deepEqual([...error.errors], [error1, error2, error3, error4]);
|
||||||
t.true(step1.calledWith(0));
|
t.true(step1.calledWith(0));
|
||||||
t.true(step2.calledWith(0));
|
t.true(step2.calledWith(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Execute each function in series passing a transformed input even if a step rejects", async (t) => {
|
test('Execute each function in series passing a transformed input even if a step rejects', async (t) => {
|
||||||
const error2 = new Error("test error 2");
|
const error2 = new Error('test error 2');
|
||||||
const error3 = new Error("test error 3");
|
const error3 = new Error('test error 3');
|
||||||
const step1 = stub().resolves(1);
|
const step1 = stub().resolves(1);
|
||||||
const step2 = stub().rejects(error2);
|
const step2 = stub().rejects(error2);
|
||||||
const step3 = stub().rejects(error3);
|
const step3 = stub().rejects(error3);
|
||||||
const step4 = stub().resolves(4);
|
const step4 = stub().resolves(4);
|
||||||
const getNextInput = (previousResult, result) => previousResult + result;
|
const getNextInput = (previousResult, result) => previousResult + result;
|
||||||
|
|
||||||
const error = await t.throwsAsync(pipeline([step1, step2, step3, step4], { settleAll: true, getNextInput })(0));
|
const error = await t.throwsAsync(pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput})(0));
|
||||||
|
|
||||||
t.deepEqual([...error.errors], [error2, error3]);
|
t.deepEqual([...error.errors], [error2, error3]);
|
||||||
t.true(step1.calledWith(0));
|
t.true(step1.calledWith(0));
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import path from "path";
|
import path from 'path';
|
||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import { copy, outputFile } from "fs-extra";
|
import {copy, outputFile} from 'fs-extra';
|
||||||
import { stub } from "sinon";
|
import {stub} from 'sinon';
|
||||||
import { temporaryDirectory } from "tempy";
|
import {temporaryDirectory} from 'tempy';
|
||||||
import getPlugins from "../../lib/plugins/index.js";
|
import getPlugins from '../../lib/plugins/index.js';
|
||||||
|
|
||||||
// Save the current working directory
|
// Save the current working directory
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
@ -12,32 +12,32 @@ test.beforeEach((t) => {
|
|||||||
// Stub the logger functions
|
// Stub the logger functions
|
||||||
t.context.log = stub();
|
t.context.log = stub();
|
||||||
t.context.success = stub();
|
t.context.success = stub();
|
||||||
t.context.logger = { log: t.context.log, success: t.context.success, scope: () => t.context.logger };
|
t.context.logger = {log: t.context.log, success: t.context.success, scope: () => t.context.logger};
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Export default plugins", async (t) => {
|
test('Export default plugins', async (t) => {
|
||||||
const plugins = await getPlugins({ cwd, options: {}, logger: t.context.logger }, {});
|
const plugins = await getPlugins({cwd, options: {}, logger: t.context.logger}, {});
|
||||||
|
|
||||||
// Verify the module returns a function for each plugin
|
// Verify the module returns a function for each plugin
|
||||||
t.is(typeof plugins.verifyConditions, "function");
|
t.is(typeof plugins.verifyConditions, 'function');
|
||||||
t.is(typeof plugins.analyzeCommits, "function");
|
t.is(typeof plugins.analyzeCommits, 'function');
|
||||||
t.is(typeof plugins.verifyRelease, "function");
|
t.is(typeof plugins.verifyRelease, 'function');
|
||||||
t.is(typeof plugins.generateNotes, "function");
|
t.is(typeof plugins.generateNotes, 'function');
|
||||||
t.is(typeof plugins.prepare, "function");
|
t.is(typeof plugins.prepare, 'function');
|
||||||
t.is(typeof plugins.publish, "function");
|
t.is(typeof plugins.publish, 'function');
|
||||||
t.is(typeof plugins.success, "function");
|
t.is(typeof plugins.success, 'function');
|
||||||
t.is(typeof plugins.fail, "function");
|
t.is(typeof plugins.fail, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Export plugins based on steps config", async (t) => {
|
test('Export plugins based on steps config', async (t) => {
|
||||||
const plugins = await getPlugins(
|
const plugins = await getPlugins(
|
||||||
{
|
{
|
||||||
cwd,
|
cwd,
|
||||||
logger: t.context.logger,
|
logger: t.context.logger,
|
||||||
options: {
|
options: {
|
||||||
verifyConditions: ["./test/fixtures/plugin-noop.cjs", { path: "./test/fixtures/plugin-noop.cjs" }],
|
verifyConditions: ['./test/fixtures/plugin-noop.cjs', {path: './test/fixtures/plugin-noop.cjs'}],
|
||||||
generateNotes: "./test/fixtures/plugin-noop.cjs",
|
generateNotes: './test/fixtures/plugin-noop.cjs',
|
||||||
analyzeCommits: { path: "./test/fixtures/plugin-noop.cjs" },
|
analyzeCommits: {path: './test/fixtures/plugin-noop.cjs'},
|
||||||
verifyRelease: () => {},
|
verifyRelease: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -45,161 +45,161 @@ test("Export plugins based on steps config", async (t) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Verify the module returns a function for each plugin
|
// Verify the module returns a function for each plugin
|
||||||
t.is(typeof plugins.verifyConditions, "function");
|
t.is(typeof plugins.verifyConditions, 'function');
|
||||||
t.is(typeof plugins.analyzeCommits, "function");
|
t.is(typeof plugins.analyzeCommits, 'function');
|
||||||
t.is(typeof plugins.verifyRelease, "function");
|
t.is(typeof plugins.verifyRelease, 'function');
|
||||||
t.is(typeof plugins.generateNotes, "function");
|
t.is(typeof plugins.generateNotes, 'function');
|
||||||
t.is(typeof plugins.prepare, "function");
|
t.is(typeof plugins.prepare, 'function');
|
||||||
t.is(typeof plugins.publish, "function");
|
t.is(typeof plugins.publish, 'function');
|
||||||
t.is(typeof plugins.success, "function");
|
t.is(typeof plugins.success, 'function');
|
||||||
t.is(typeof plugins.fail, "function");
|
t.is(typeof plugins.fail, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Export plugins based on "plugins" config (array)', async (t) => {
|
test('Export plugins based on "plugins" config (array)', async (t) => {
|
||||||
const plugin1 = { verifyConditions: stub(), publish: stub() };
|
const plugin1 = {verifyConditions: stub(), publish: stub()};
|
||||||
const plugin2 = { verifyConditions: stub(), verifyRelease: stub() };
|
const plugin2 = {verifyConditions: stub(), verifyRelease: stub()};
|
||||||
const plugins = await getPlugins(
|
const plugins = await getPlugins(
|
||||||
{ cwd, logger: t.context.logger, options: { plugins: [plugin1, [plugin2, {}]], verifyRelease: () => {} } },
|
{cwd, logger: t.context.logger, options: {plugins: [plugin1, [plugin2, {}]], verifyRelease: () => {}}},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
await plugins.verifyConditions({ options: {} });
|
await plugins.verifyConditions({options: {}});
|
||||||
t.true(plugin1.verifyConditions.calledOnce);
|
t.true(plugin1.verifyConditions.calledOnce);
|
||||||
t.true(plugin2.verifyConditions.calledOnce);
|
t.true(plugin2.verifyConditions.calledOnce);
|
||||||
|
|
||||||
await plugins.publish({ options: {} });
|
await plugins.publish({options: {}});
|
||||||
t.true(plugin1.publish.calledOnce);
|
t.true(plugin1.publish.calledOnce);
|
||||||
|
|
||||||
await plugins.verifyRelease({ options: {} });
|
await plugins.verifyRelease({options: {}});
|
||||||
t.true(plugin2.verifyRelease.notCalled);
|
t.true(plugin2.verifyRelease.notCalled);
|
||||||
|
|
||||||
// Verify the module returns a function for each plugin
|
// Verify the module returns a function for each plugin
|
||||||
t.is(typeof plugins.verifyConditions, "function");
|
t.is(typeof plugins.verifyConditions, 'function');
|
||||||
t.is(typeof plugins.analyzeCommits, "function");
|
t.is(typeof plugins.analyzeCommits, 'function');
|
||||||
t.is(typeof plugins.verifyRelease, "function");
|
t.is(typeof plugins.verifyRelease, 'function');
|
||||||
t.is(typeof plugins.generateNotes, "function");
|
t.is(typeof plugins.generateNotes, 'function');
|
||||||
t.is(typeof plugins.prepare, "function");
|
t.is(typeof plugins.prepare, 'function');
|
||||||
t.is(typeof plugins.publish, "function");
|
t.is(typeof plugins.publish, 'function');
|
||||||
t.is(typeof plugins.success, "function");
|
t.is(typeof plugins.success, 'function');
|
||||||
t.is(typeof plugins.fail, "function");
|
t.is(typeof plugins.fail, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Export plugins based on "plugins" config (single definition)', async (t) => {
|
test('Export plugins based on "plugins" config (single definition)', async (t) => {
|
||||||
const plugin1 = { verifyConditions: stub(), publish: stub() };
|
const plugin1 = {verifyConditions: stub(), publish: stub()};
|
||||||
const plugins = await getPlugins({ cwd, logger: t.context.logger, options: { plugins: plugin1 } }, {});
|
const plugins = await getPlugins({cwd, logger: t.context.logger, options: {plugins: plugin1}}, {});
|
||||||
|
|
||||||
await plugins.verifyConditions({ options: {} });
|
await plugins.verifyConditions({options: {}});
|
||||||
t.true(plugin1.verifyConditions.calledOnce);
|
t.true(plugin1.verifyConditions.calledOnce);
|
||||||
|
|
||||||
await plugins.publish({ options: {} });
|
await plugins.publish({options: {}});
|
||||||
t.true(plugin1.publish.calledOnce);
|
t.true(plugin1.publish.calledOnce);
|
||||||
|
|
||||||
// Verify the module returns a function for each plugin
|
// Verify the module returns a function for each plugin
|
||||||
t.is(typeof plugins.verifyConditions, "function");
|
t.is(typeof plugins.verifyConditions, 'function');
|
||||||
t.is(typeof plugins.analyzeCommits, "function");
|
t.is(typeof plugins.analyzeCommits, 'function');
|
||||||
t.is(typeof plugins.verifyRelease, "function");
|
t.is(typeof plugins.verifyRelease, 'function');
|
||||||
t.is(typeof plugins.generateNotes, "function");
|
t.is(typeof plugins.generateNotes, 'function');
|
||||||
t.is(typeof plugins.prepare, "function");
|
t.is(typeof plugins.prepare, 'function');
|
||||||
t.is(typeof plugins.publish, "function");
|
t.is(typeof plugins.publish, 'function');
|
||||||
t.is(typeof plugins.success, "function");
|
t.is(typeof plugins.success, 'function');
|
||||||
t.is(typeof plugins.fail, "function");
|
t.is(typeof plugins.fail, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Merge global options, "plugins" options and step options', async (t) => {
|
test('Merge global options, "plugins" options and step options', async (t) => {
|
||||||
const plugin1 = [{ verifyConditions: stub(), publish: stub() }, { pluginOpt1: "plugin1" }];
|
const plugin1 = [{verifyConditions: stub(), publish: stub()}, {pluginOpt1: 'plugin1'}];
|
||||||
const plugin2 = [{ verifyConditions: stub() }, { pluginOpt2: "plugin2" }];
|
const plugin2 = [{verifyConditions: stub()}, {pluginOpt2: 'plugin2'}];
|
||||||
const plugin3 = [stub(), { pluginOpt3: "plugin3" }];
|
const plugin3 = [stub(), {pluginOpt3: 'plugin3'}];
|
||||||
const plugins = await getPlugins(
|
const plugins = await getPlugins(
|
||||||
{
|
{
|
||||||
cwd,
|
cwd,
|
||||||
logger: t.context.logger,
|
logger: t.context.logger,
|
||||||
options: { globalOpt: "global", plugins: [plugin1, plugin2], verifyRelease: [plugin3] },
|
options: {globalOpt: 'global', plugins: [plugin1, plugin2], verifyRelease: [plugin3]},
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
await plugins.verifyConditions({ options: {} });
|
await plugins.verifyConditions({options: {}});
|
||||||
t.deepEqual(plugin1[0].verifyConditions.args[0][0], { globalOpt: "global", pluginOpt1: "plugin1" });
|
t.deepEqual(plugin1[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'});
|
||||||
t.deepEqual(plugin2[0].verifyConditions.args[0][0], { globalOpt: "global", pluginOpt2: "plugin2" });
|
t.deepEqual(plugin2[0].verifyConditions.args[0][0], {globalOpt: 'global', pluginOpt2: 'plugin2'});
|
||||||
|
|
||||||
await plugins.publish({ options: {} });
|
await plugins.publish({options: {}});
|
||||||
t.deepEqual(plugin1[0].publish.args[0][0], { globalOpt: "global", pluginOpt1: "plugin1" });
|
t.deepEqual(plugin1[0].publish.args[0][0], {globalOpt: 'global', pluginOpt1: 'plugin1'});
|
||||||
|
|
||||||
await plugins.verifyRelease({ options: {} });
|
await plugins.verifyRelease({options: {}});
|
||||||
t.deepEqual(plugin3[0].args[0][0], { globalOpt: "global", pluginOpt3: "plugin3" });
|
t.deepEqual(plugin3[0].args[0][0], {globalOpt: 'global', pluginOpt3: 'plugin3'});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unknown steps of plugins configured in "plugins" are ignored', async (t) => {
|
test('Unknown steps of plugins configured in "plugins" are ignored', async (t) => {
|
||||||
const plugin1 = { verifyConditions: () => {}, unknown: () => {} };
|
const plugin1 = {verifyConditions: () => {}, unknown: () => {}};
|
||||||
const plugins = await getPlugins({ cwd, logger: t.context.logger, options: { plugins: [plugin1] } }, {});
|
const plugins = await getPlugins({cwd, logger: t.context.logger, options: {plugins: [plugin1]}}, {});
|
||||||
|
|
||||||
t.is(typeof plugins.verifyConditions, "function");
|
t.is(typeof plugins.verifyConditions, 'function');
|
||||||
t.is(plugins.unknown, undefined);
|
t.is(plugins.unknown, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Export plugins loaded from the dependency of a shareable config module", async (t) => {
|
test('Export plugins loaded from the dependency of a shareable config module', async (t) => {
|
||||||
const cwd = temporaryDirectory();
|
const cwd = temporaryDirectory();
|
||||||
await copy(
|
await copy(
|
||||||
"./test/fixtures/plugin-noop.cjs",
|
'./test/fixtures/plugin-noop.cjs',
|
||||||
path.resolve(cwd, "node_modules/shareable-config/node_modules/custom-plugin/index.js")
|
path.resolve(cwd, 'node_modules/shareable-config/node_modules/custom-plugin/index.js')
|
||||||
);
|
);
|
||||||
await outputFile(path.resolve(cwd, "node_modules/shareable-config/index.js"), "");
|
await outputFile(path.resolve(cwd, 'node_modules/shareable-config/index.js'), '');
|
||||||
|
|
||||||
const plugins = await getPlugins(
|
const plugins = await getPlugins(
|
||||||
{
|
{
|
||||||
cwd,
|
cwd,
|
||||||
logger: t.context.logger,
|
logger: t.context.logger,
|
||||||
options: {
|
options: {
|
||||||
verifyConditions: ["custom-plugin", { path: "custom-plugin" }],
|
verifyConditions: ['custom-plugin', {path: 'custom-plugin'}],
|
||||||
generateNotes: "custom-plugin",
|
generateNotes: 'custom-plugin',
|
||||||
analyzeCommits: { path: "custom-plugin" },
|
analyzeCommits: {path: 'custom-plugin'},
|
||||||
verifyRelease: () => {},
|
verifyRelease: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ "custom-plugin": "shareable-config" }
|
{'custom-plugin': 'shareable-config'}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify the module returns a function for each plugin
|
// Verify the module returns a function for each plugin
|
||||||
t.is(typeof plugins.verifyConditions, "function");
|
t.is(typeof plugins.verifyConditions, 'function');
|
||||||
t.is(typeof plugins.analyzeCommits, "function");
|
t.is(typeof plugins.analyzeCommits, 'function');
|
||||||
t.is(typeof plugins.verifyRelease, "function");
|
t.is(typeof plugins.verifyRelease, 'function');
|
||||||
t.is(typeof plugins.generateNotes, "function");
|
t.is(typeof plugins.generateNotes, 'function');
|
||||||
t.is(typeof plugins.prepare, "function");
|
t.is(typeof plugins.prepare, 'function');
|
||||||
t.is(typeof plugins.publish, "function");
|
t.is(typeof plugins.publish, 'function');
|
||||||
t.is(typeof plugins.success, "function");
|
t.is(typeof plugins.success, 'function');
|
||||||
t.is(typeof plugins.fail, "function");
|
t.is(typeof plugins.fail, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Export plugins loaded from the dependency of a shareable config file", async (t) => {
|
test('Export plugins loaded from the dependency of a shareable config file', async (t) => {
|
||||||
const cwd = temporaryDirectory();
|
const cwd = temporaryDirectory();
|
||||||
await copy("./test/fixtures/plugin-noop.cjs", path.resolve(cwd, "plugin/plugin-noop.cjs"));
|
await copy('./test/fixtures/plugin-noop.cjs', path.resolve(cwd, 'plugin/plugin-noop.cjs'));
|
||||||
await outputFile(path.resolve(cwd, "shareable-config.js"), "");
|
await outputFile(path.resolve(cwd, 'shareable-config.js'), '');
|
||||||
|
|
||||||
const plugins = await getPlugins(
|
const plugins = await getPlugins(
|
||||||
{
|
{
|
||||||
cwd,
|
cwd,
|
||||||
logger: t.context.logger,
|
logger: t.context.logger,
|
||||||
options: {
|
options: {
|
||||||
verifyConditions: ["./plugin/plugin-noop.cjs", { path: "./plugin/plugin-noop.cjs" }],
|
verifyConditions: ['./plugin/plugin-noop.cjs', {path: './plugin/plugin-noop.cjs'}],
|
||||||
generateNotes: "./plugin/plugin-noop.cjs",
|
generateNotes: './plugin/plugin-noop.cjs',
|
||||||
analyzeCommits: { path: "./plugin/plugin-noop.cjs" },
|
analyzeCommits: {path: './plugin/plugin-noop.cjs'},
|
||||||
verifyRelease: () => {},
|
verifyRelease: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ "./plugin/plugin-noop": "./shareable-config.js" }
|
{'./plugin/plugin-noop': './shareable-config.js'}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify the module returns a function for each plugin
|
// Verify the module returns a function for each plugin
|
||||||
t.is(typeof plugins.verifyConditions, "function");
|
t.is(typeof plugins.verifyConditions, 'function');
|
||||||
t.is(typeof plugins.analyzeCommits, "function");
|
t.is(typeof plugins.analyzeCommits, 'function');
|
||||||
t.is(typeof plugins.verifyRelease, "function");
|
t.is(typeof plugins.verifyRelease, 'function');
|
||||||
t.is(typeof plugins.generateNotes, "function");
|
t.is(typeof plugins.generateNotes, 'function');
|
||||||
t.is(typeof plugins.prepare, "function");
|
t.is(typeof plugins.prepare, 'function');
|
||||||
t.is(typeof plugins.publish, "function");
|
t.is(typeof plugins.publish, 'function');
|
||||||
t.is(typeof plugins.success, "function");
|
t.is(typeof plugins.success, 'function');
|
||||||
t.is(typeof plugins.fail, "function");
|
t.is(typeof plugins.fail, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Use default when only options are passed for a single plugin", async (t) => {
|
test('Use default when only options are passed for a single plugin', async (t) => {
|
||||||
const analyzeCommits = {};
|
const analyzeCommits = {};
|
||||||
const generateNotes = {};
|
const generateNotes = {};
|
||||||
const publish = {};
|
const publish = {};
|
||||||
@ -211,7 +211,7 @@ test("Use default when only options are passed for a single plugin", async (t) =
|
|||||||
cwd,
|
cwd,
|
||||||
logger: t.context.logger,
|
logger: t.context.logger,
|
||||||
options: {
|
options: {
|
||||||
plugins: ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator"],
|
plugins: ['@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator'],
|
||||||
analyzeCommits,
|
analyzeCommits,
|
||||||
generateNotes,
|
generateNotes,
|
||||||
publish,
|
publish,
|
||||||
@ -223,102 +223,96 @@ test("Use default when only options are passed for a single plugin", async (t) =
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Verify the module returns a function for each plugin
|
// Verify the module returns a function for each plugin
|
||||||
t.is(typeof plugins.analyzeCommits, "function");
|
t.is(typeof plugins.analyzeCommits, 'function');
|
||||||
t.is(typeof plugins.generateNotes, "function");
|
t.is(typeof plugins.generateNotes, 'function');
|
||||||
t.is(typeof plugins.success, "function");
|
t.is(typeof plugins.success, 'function');
|
||||||
t.is(typeof plugins.fail, "function");
|
t.is(typeof plugins.fail, 'function');
|
||||||
|
|
||||||
// Verify only the plugins defined as an object with no `path` are set to the default value
|
// Verify only the plugins defined as an object with no `path` are set to the default value
|
||||||
t.falsy(success.path);
|
t.falsy(success.path);
|
||||||
t.falsy(fail.path);
|
t.falsy(fail.path);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Merge global options with plugin options", async (t) => {
|
test('Merge global options with plugin options', async (t) => {
|
||||||
const plugins = await getPlugins(
|
const plugins = await getPlugins(
|
||||||
{
|
{
|
||||||
cwd,
|
cwd,
|
||||||
logger: t.context.logger,
|
logger: t.context.logger,
|
||||||
options: {
|
options: {
|
||||||
globalOpt: "global",
|
globalOpt: 'global',
|
||||||
otherOpt: "globally-defined",
|
otherOpt: 'globally-defined',
|
||||||
verifyRelease: { path: "./test/fixtures/plugin-result-config", localOpt: "local", otherOpt: "locally-defined" },
|
verifyRelease: {path: './test/fixtures/plugin-result-config', localOpt: 'local', otherOpt: 'locally-defined'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const [result] = await plugins.verifyRelease({ options: {} });
|
const [result] = await plugins.verifyRelease({options: {}});
|
||||||
|
|
||||||
t.deepEqual(result.pluginConfig, { localOpt: "local", globalOpt: "global", otherOpt: "locally-defined" });
|
t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Throw an error for each invalid plugin configuration", async (t) => {
|
test('Throw an error for each invalid plugin configuration', async (t) => {
|
||||||
const errors = [
|
const errors = [
|
||||||
...(
|
...(await t.throwsAsync(() =>
|
||||||
await t.throwsAsync(() =>
|
getPlugins(
|
||||||
getPlugins(
|
{
|
||||||
{
|
cwd,
|
||||||
cwd,
|
logger: t.context.logger,
|
||||||
logger: t.context.logger,
|
options: {
|
||||||
options: {
|
plugins: ['@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator'],
|
||||||
plugins: ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator"],
|
verifyConditions: 1,
|
||||||
verifyConditions: 1,
|
analyzeCommits: [],
|
||||||
analyzeCommits: [],
|
verifyRelease: [{}],
|
||||||
verifyRelease: [{}],
|
generateNotes: [{path: null}],
|
||||||
generateNotes: [{ path: null }],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{}
|
},
|
||||||
)
|
{}
|
||||||
)
|
)
|
||||||
).errors,
|
)).errors,
|
||||||
];
|
];
|
||||||
|
|
||||||
t.is(errors[0].name, "SemanticReleaseError");
|
t.is(errors[0].name, 'SemanticReleaseError');
|
||||||
t.is(errors[0].code, "EPLUGINCONF");
|
t.is(errors[0].code, 'EPLUGINCONF');
|
||||||
t.is(errors[1].name, "SemanticReleaseError");
|
t.is(errors[1].name, 'SemanticReleaseError');
|
||||||
t.is(errors[1].code, "EPLUGINCONF");
|
t.is(errors[1].code, 'EPLUGINCONF');
|
||||||
t.is(errors[2].name, "SemanticReleaseError");
|
t.is(errors[2].name, 'SemanticReleaseError');
|
||||||
t.is(errors[2].code, "EPLUGINCONF");
|
t.is(errors[2].code, 'EPLUGINCONF');
|
||||||
t.is(errors[3].name, "SemanticReleaseError");
|
t.is(errors[3].name, 'SemanticReleaseError');
|
||||||
t.is(errors[3].code, "EPLUGINCONF");
|
t.is(errors[3].code, 'EPLUGINCONF');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Throw EPLUGINSCONF error if the "plugins" option contains an old plugin definition (returns a function)', async (t) => {
|
test('Throw EPLUGINSCONF error if the "plugins" option contains an old plugin definition (returns a function)', async (t) => {
|
||||||
const errors = [
|
const errors = [
|
||||||
...(
|
...(await t.throwsAsync(() =>
|
||||||
await t.throwsAsync(() =>
|
getPlugins(
|
||||||
getPlugins(
|
{
|
||||||
{
|
cwd,
|
||||||
cwd,
|
logger: t.context.logger,
|
||||||
logger: t.context.logger,
|
options: {plugins: ['./test/fixtures/multi-plugin.cjs', './test/fixtures/plugin-noop.cjs', () => {}]},
|
||||||
options: { plugins: ["./test/fixtures/multi-plugin.cjs", "./test/fixtures/plugin-noop.cjs", () => {}] },
|
},
|
||||||
},
|
{}
|
||||||
{}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
).errors,
|
)).errors,
|
||||||
];
|
];
|
||||||
|
|
||||||
t.is(errors[0].name, "SemanticReleaseError");
|
t.is(errors[0].name, 'SemanticReleaseError');
|
||||||
t.is(errors[0].code, "EPLUGINSCONF");
|
t.is(errors[0].code, 'EPLUGINSCONF');
|
||||||
t.is(errors[1].name, "SemanticReleaseError");
|
t.is(errors[1].name, 'SemanticReleaseError');
|
||||||
t.is(errors[1].code, "EPLUGINSCONF");
|
t.is(errors[1].code, 'EPLUGINSCONF');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Throw EPLUGINSCONF error for each invalid definition if the "plugins" option', async (t) => {
|
test('Throw EPLUGINSCONF error for each invalid definition if the "plugins" option', async (t) => {
|
||||||
const errors = [
|
const errors = [
|
||||||
...(
|
...(await t.throwsAsync(() =>
|
||||||
await t.throwsAsync(() =>
|
getPlugins({cwd, logger: t.context.logger, options: {plugins: [1, {path: 1}, [() => {}, {}, {}]]}}, {})
|
||||||
getPlugins({ cwd, logger: t.context.logger, options: { plugins: [1, { path: 1 }, [() => {}, {}, {}]] } }, {})
|
)).errors,
|
||||||
)
|
|
||||||
).errors,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
t.is(errors[0].name, "SemanticReleaseError");
|
t.is(errors[0].name, 'SemanticReleaseError');
|
||||||
t.is(errors[0].code, "EPLUGINSCONF");
|
t.is(errors[0].code, 'EPLUGINSCONF');
|
||||||
t.is(errors[1].name, "SemanticReleaseError");
|
t.is(errors[1].name, 'SemanticReleaseError');
|
||||||
t.is(errors[1].code, "EPLUGINSCONF");
|
t.is(errors[1].code, 'EPLUGINSCONF');
|
||||||
t.is(errors[2].name, "SemanticReleaseError");
|
t.is(errors[2].name, 'SemanticReleaseError');
|
||||||
t.is(errors[2].code, "EPLUGINSCONF");
|
t.is(errors[2].code, 'EPLUGINSCONF');
|
||||||
});
|
});
|
||||||
|
@ -1,224 +1,214 @@
|
|||||||
import test from "ava";
|
import test from 'ava';
|
||||||
import { loadPlugin, parseConfig, validatePlugin, validateStep } from "../../lib/plugins/utils.js";
|
import {loadPlugin, parseConfig, validatePlugin, validateStep} from '../../lib/plugins/utils.js';
|
||||||
|
|
||||||
test("validatePlugin", (t) => {
|
test('validatePlugin', (t) => {
|
||||||
const path = "plugin-module";
|
const path = 'plugin-module';
|
||||||
const options = { option1: "value1", option2: "value2" };
|
const options = {option1: 'value1', option2: 'value2'};
|
||||||
|
|
||||||
t.true(validatePlugin(path), "String definition");
|
t.true(validatePlugin(path), 'String definition');
|
||||||
t.true(validatePlugin({ publish: () => {} }), "Object definition");
|
t.true(validatePlugin({publish: () => {}}), 'Object definition');
|
||||||
t.true(validatePlugin([path]), "Array definition");
|
t.true(validatePlugin([path]), 'Array definition');
|
||||||
t.true(validatePlugin([path, options]), "Array definition with options");
|
t.true(validatePlugin([path, options]), 'Array definition with options');
|
||||||
t.true(validatePlugin([{ publish: () => {} }, options]), "Array definition with options and path as object");
|
t.true(validatePlugin([{publish: () => {}}, options]), 'Array definition with options and path as object');
|
||||||
t.true(validatePlugin({ path }), "Object with path definition");
|
t.true(validatePlugin({path}), 'Object with path definition');
|
||||||
t.true(validatePlugin({ path, ...options }), "Object with path definition with options");
|
t.true(validatePlugin({path, ...options}), 'Object with path definition with options');
|
||||||
t.true(
|
t.true(
|
||||||
validatePlugin({ path: { publish: () => {} }, ...options }),
|
validatePlugin({path: {publish: () => {}}, ...options}),
|
||||||
"Object with path definition with options and path as object"
|
'Object with path definition with options and path as object'
|
||||||
);
|
);
|
||||||
|
|
||||||
t.false(validatePlugin(1), "String definition, wrong path");
|
t.false(validatePlugin(1), 'String definition, wrong path');
|
||||||
t.false(validatePlugin([]), "Array definition, missing path");
|
t.false(validatePlugin([]), 'Array definition, missing path');
|
||||||
t.false(validatePlugin([path, options, {}]), "Array definition, additional parameter");
|
t.false(validatePlugin([path, options, {}]), 'Array definition, additional parameter');
|
||||||
t.false(validatePlugin([1]), "Array definition, wrong path");
|
t.false(validatePlugin([1]), 'Array definition, wrong path');
|
||||||
t.false(validatePlugin([path, 1]), "Array definition, wrong options");
|
t.false(validatePlugin([path, 1]), 'Array definition, wrong options');
|
||||||
t.false(validatePlugin({ path: 1 }), "Object definition, wrong path");
|
t.false(validatePlugin({path: 1}), 'Object definition, wrong path');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("validateStep: optional plugin configuration", (t) => {
|
test('validateStep: optional plugin configuration', (t) => {
|
||||||
const type = { multiple: true, required: false };
|
const type = {multiple: true, required: false};
|
||||||
|
|
||||||
// Empty config
|
// Empty config
|
||||||
t.true(validateStep(type));
|
t.true(validateStep(type));
|
||||||
t.true(validateStep(type, []));
|
t.true(validateStep(type, []));
|
||||||
|
|
||||||
// Single value definition
|
// Single value definition
|
||||||
t.true(validateStep(type, "plugin-path.js"));
|
t.true(validateStep(type, 'plugin-path.js'));
|
||||||
t.true(validateStep(type, () => {}));
|
t.true(validateStep(type, () => {}));
|
||||||
t.true(validateStep(type, ["plugin-path.js"]));
|
t.true(validateStep(type, ['plugin-path.js']));
|
||||||
t.true(validateStep(type, [() => {}]));
|
t.true(validateStep(type, [() => {}]));
|
||||||
t.false(validateStep(type, {}));
|
t.false(validateStep(type, {}));
|
||||||
t.false(validateStep(type, [{}]));
|
t.false(validateStep(type, [{}]));
|
||||||
|
|
||||||
// Array type definition
|
// Array type definition
|
||||||
t.true(validateStep(type, [["plugin-path.js"]]));
|
t.true(validateStep(type, [['plugin-path.js']]));
|
||||||
t.true(validateStep(type, [["plugin-path.js", { options: "value" }]]));
|
t.true(validateStep(type, [['plugin-path.js', {options: 'value'}]]));
|
||||||
t.true(validateStep(type, [[() => {}, { options: "value" }]]));
|
t.true(validateStep(type, [[() => {}, {options: 'value'}]]));
|
||||||
t.false(validateStep(type, [["plugin-path.js", 1]]));
|
t.false(validateStep(type, [['plugin-path.js', 1]]));
|
||||||
|
|
||||||
// Object type definition
|
// Object type definition
|
||||||
t.true(validateStep(type, { path: "plugin-path.js" }));
|
t.true(validateStep(type, {path: 'plugin-path.js'}));
|
||||||
t.true(validateStep(type, { path: "plugin-path.js", options: "value" }));
|
t.true(validateStep(type, {path: 'plugin-path.js', options: 'value'}));
|
||||||
t.true(validateStep(type, { path: () => {}, options: "value" }));
|
t.true(validateStep(type, {path: () => {}, options: 'value'}));
|
||||||
t.false(validateStep(type, { path: null }));
|
t.false(validateStep(type, {path: null}));
|
||||||
|
|
||||||
// Considered as an Array of 2 definitions and not as one Array definition in case of a multiple plugin type
|
// Considered as an Array of 2 definitions and not as one Array definition in case of a muliple plugin type
|
||||||
t.false(validateStep(type, [() => {}, { options: "value" }]));
|
t.false(validateStep(type, [() => {}, {options: 'value'}]));
|
||||||
t.false(validateStep(type, ["plugin-path.js", { options: "value" }]));
|
t.false(validateStep(type, ['plugin-path.js', {options: 'value'}]));
|
||||||
|
|
||||||
// Multiple definitions
|
// Multiple definitions
|
||||||
t.true(
|
t.true(
|
||||||
validateStep(type, [
|
validateStep(type, [
|
||||||
"plugin-path.js",
|
'plugin-path.js',
|
||||||
() => {},
|
() => {},
|
||||||
["plugin-path.js"],
|
['plugin-path.js'],
|
||||||
["plugin-path.js", { options: "value" }],
|
['plugin-path.js', {options: 'value'}],
|
||||||
[() => {}, { options: "value" }],
|
[() => {}, {options: 'value'}],
|
||||||
{ path: "plugin-path.js" },
|
{path: 'plugin-path.js'},
|
||||||
{ path: "plugin-path.js", options: "value" },
|
{path: 'plugin-path.js', options: 'value'},
|
||||||
{ path: () => {}, options: "value" },
|
{path: () => {}, options: 'value'},
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
t.false(
|
t.false(
|
||||||
validateStep(type, [
|
validateStep(type, [
|
||||||
"plugin-path.js",
|
'plugin-path.js',
|
||||||
() => {},
|
() => {},
|
||||||
["plugin-path.js"],
|
['plugin-path.js'],
|
||||||
["plugin-path.js", 1],
|
['plugin-path.js', 1],
|
||||||
[() => {}, { options: "value" }],
|
[() => {}, {options: 'value'}],
|
||||||
{ path: "plugin-path.js" },
|
{path: 'plugin-path.js'},
|
||||||
{ path: "plugin-path.js", options: "value" },
|
{path: 'plugin-path.js', options: 'value'},
|
||||||
{ path: () => {}, options: "value" },
|
{path: () => {}, options: 'value'},
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
t.false(
|
t.false(
|
||||||
validateStep(type, [
|
validateStep(type, [
|
||||||
"plugin-path.js",
|
'plugin-path.js',
|
||||||
{},
|
{},
|
||||||
["plugin-path.js"],
|
['plugin-path.js'],
|
||||||
["plugin-path.js", { options: "value" }],
|
['plugin-path.js', {options: 'value'}],
|
||||||
[() => {}, { options: "value" }],
|
[() => {}, {options: 'value'}],
|
||||||
{ path: "plugin-path.js" },
|
{path: 'plugin-path.js'},
|
||||||
{ path: "plugin-path.js", options: "value" },
|
{path: 'plugin-path.js', options: 'value'},
|
||||||
{ path: () => {}, options: "value" },
|
{path: () => {}, options: 'value'},
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
t.false(
|
t.false(
|
||||||
validateStep(type, [
|
validateStep(type, [
|
||||||
"plugin-path.js",
|
'plugin-path.js',
|
||||||
() => {},
|
() => {},
|
||||||
["plugin-path.js"],
|
['plugin-path.js'],
|
||||||
["plugin-path.js", { options: "value" }],
|
['plugin-path.js', {options: 'value'}],
|
||||||
[() => {}, { options: "value" }],
|
[() => {}, {options: 'value'}],
|
||||||
{ path: null },
|
{path: null},
|
||||||
{ path: "plugin-path.js", options: "value" },
|
{path: 'plugin-path.js', options: 'value'},
|
||||||
{ path: () => {}, options: "value" },
|
{path: () => {}, options: 'value'},
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("validateStep: required plugin configuration", (t) => {
|
test('validateStep: required plugin configuration', (t) => {
|
||||||
const type = { required: true };
|
const type = {required: true};
|
||||||
|
|
||||||
// Empty config
|
// Empty config
|
||||||
t.false(validateStep(type));
|
t.false(validateStep(type));
|
||||||
t.false(validateStep(type, []));
|
t.false(validateStep(type, []));
|
||||||
|
|
||||||
// Single value definition
|
// Single value definition
|
||||||
t.true(validateStep(type, "plugin-path.js"));
|
t.true(validateStep(type, 'plugin-path.js'));
|
||||||
t.true(validateStep(type, () => {}));
|
t.true(validateStep(type, () => {}));
|
||||||
t.true(validateStep(type, ["plugin-path.js"]));
|
t.true(validateStep(type, ['plugin-path.js']));
|
||||||
t.true(validateStep(type, [() => {}]));
|
t.true(validateStep(type, [() => {}]));
|
||||||
t.false(validateStep(type, {}));
|
t.false(validateStep(type, {}));
|
||||||
t.false(validateStep(type, [{}]));
|
t.false(validateStep(type, [{}]));
|
||||||
|
|
||||||
// Array type definition
|
// Array type definition
|
||||||
t.true(validateStep(type, [["plugin-path.js"]]));
|
t.true(validateStep(type, [['plugin-path.js']]));
|
||||||
t.true(validateStep(type, [["plugin-path.js", { options: "value" }]]));
|
t.true(validateStep(type, [['plugin-path.js', {options: 'value'}]]));
|
||||||
t.true(validateStep(type, [[() => {}, { options: "value" }]]));
|
t.true(validateStep(type, [[() => {}, {options: 'value'}]]));
|
||||||
t.false(validateStep(type, [["plugin-path.js", 1]]));
|
t.false(validateStep(type, [['plugin-path.js', 1]]));
|
||||||
|
|
||||||
// Object type definition
|
// Object type definition
|
||||||
t.true(validateStep(type, { path: "plugin-path.js" }));
|
t.true(validateStep(type, {path: 'plugin-path.js'}));
|
||||||
t.true(validateStep(type, { path: "plugin-path.js", options: "value" }));
|
t.true(validateStep(type, {path: 'plugin-path.js', options: 'value'}));
|
||||||
t.true(validateStep(type, { path: () => {}, options: "value" }));
|
t.true(validateStep(type, {path: () => {}, options: 'value'}));
|
||||||
t.false(validateStep(type, { path: null }));
|
t.false(validateStep(type, {path: null}));
|
||||||
|
|
||||||
// Considered as an Array of 2 definitions and not as one Array definition in the case of a multiple plugin type
|
// Considered as an Array of 2 definitions and not as one Array definition in the case of a muliple plugin type
|
||||||
t.false(validateStep(type, [() => {}, { options: "value" }]));
|
t.false(validateStep(type, [() => {}, {options: 'value'}]));
|
||||||
t.false(validateStep(type, ["plugin-path.js", { options: "value" }]));
|
t.false(validateStep(type, ['plugin-path.js', {options: 'value'}]));
|
||||||
|
|
||||||
// Multiple definitions
|
// Multiple definitions
|
||||||
t.true(
|
t.true(
|
||||||
validateStep(type, [
|
validateStep(type, [
|
||||||
"plugin-path.js",
|
'plugin-path.js',
|
||||||
() => {},
|
() => {},
|
||||||
["plugin-path.js"],
|
['plugin-path.js'],
|
||||||
["plugin-path.js", { options: "value" }],
|
['plugin-path.js', {options: 'value'}],
|
||||||
[() => {}, { options: "value" }],
|
[() => {}, {options: 'value'}],
|
||||||
{ path: "plugin-path.js" },
|
{path: 'plugin-path.js'},
|
||||||
{ path: "plugin-path.js", options: "value" },
|
{path: 'plugin-path.js', options: 'value'},
|
||||||
{ path: () => {}, options: "value" },
|
{path: () => {}, options: 'value'},
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
t.false(
|
t.false(
|
||||||
validateStep(type, [
|
validateStep(type, [
|
||||||
"plugin-path.js",
|
'plugin-path.js',
|
||||||
() => {},
|
() => {},
|
||||||
["plugin-path.js"],
|
['plugin-path.js'],
|
||||||
["plugin-path.js", 1],
|
['plugin-path.js', 1],
|
||||||
[() => {}, { options: "value" }],
|
[() => {}, {options: 'value'}],
|
||||||
{ path: "plugin-path.js" },
|
{path: 'plugin-path.js'},
|
||||||
{ path: "plugin-path.js", options: "value" },
|
{path: 'plugin-path.js', options: 'value'},
|
||||||
{ path: () => {}, options: "value" },
|
{path: () => {}, options: 'value'},
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
t.false(
|
t.false(
|
||||||
validateStep(type, [
|
validateStep(type, [
|
||||||
"plugin-path.js",
|
'plugin-path.js',
|
||||||
{},
|
{},
|
||||||
["plugin-path.js"],
|
['plugin-path.js'],
|
||||||
["plugin-path.js", { options: "value" }],
|
['plugin-path.js', {options: 'value'}],
|
||||||
[() => {}, { options: "value" }],
|
[() => {}, {options: 'value'}],
|
||||||
{ path: "plugin-path.js" },
|
{path: 'plugin-path.js'},
|
||||||
{ path: "plugin-path.js", options: "value" },
|
{path: 'plugin-path.js', options: 'value'},
|
||||||
{ path: () => {}, options: "value" },
|
{path: () => {}, options: 'value'},
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
t.false(
|
t.false(
|
||||||
validateStep(type, [
|
validateStep(type, [
|
||||||
"plugin-path.js",
|
'plugin-path.js',
|
||||||
() => {},
|
() => {},
|
||||||
["plugin-path.js"],
|
['plugin-path.js'],
|
||||||
["plugin-path.js", { options: "value" }],
|
['plugin-path.js', {options: 'value'}],
|
||||||
[() => {}, { options: "value" }],
|
[() => {}, {options: 'value'}],
|
||||||
{ path: null },
|
{path: null},
|
||||||
{ path: "plugin-path.js", options: "value" },
|
{path: 'plugin-path.js', options: 'value'},
|
||||||
{ path: () => {}, options: "value" },
|
{path: () => {}, options: 'value'},
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("loadPlugin", async (t) => {
|
test('loadPlugin', async (t) => {
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const func = () => {};
|
const func = () => {};
|
||||||
|
|
||||||
|
t.is((await import('../fixtures/plugin-noop.cjs')).default, await loadPlugin({cwd: './test/fixtures'}, './plugin-noop.cjs', {}), 'From cwd');
|
||||||
t.is(
|
t.is(
|
||||||
(await import("../fixtures/plugin-noop.cjs")).default,
|
(await import('../fixtures/plugin-noop.cjs')).default,
|
||||||
await loadPlugin({ cwd: "./test/fixtures" }, "./plugin-noop.cjs", {}),
|
await loadPlugin({cwd}, './plugin-noop.cjs', {'./plugin-noop.cjs': './test/fixtures'}),
|
||||||
"From cwd"
|
'From a shareable config context'
|
||||||
);
|
);
|
||||||
t.is(
|
t.is(func, await loadPlugin({cwd}, func, {}), 'Defined as a function');
|
||||||
(await import("../fixtures/plugin-noop.cjs")).default,
|
|
||||||
await loadPlugin({ cwd }, "./plugin-noop.cjs", { "./plugin-noop.cjs": "./test/fixtures" }),
|
|
||||||
"From a shareable config context"
|
|
||||||
);
|
|
||||||
const { ...namedExports } = await import("../fixtures/plugin-esm-named-exports.js");
|
|
||||||
const plugin = await loadPlugin({ cwd }, "./plugin-esm-named-exports.js", {
|
|
||||||
"./plugin-esm-named-exports.js": "./test/fixtures",
|
|
||||||
});
|
|
||||||
|
|
||||||
t.deepEqual(namedExports, plugin, "ESM with named exports");
|
|
||||||
t.is(func, await loadPlugin({ cwd }, func, {}), "Defined as a function");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parseConfig", (t) => {
|
test('parseConfig', (t) => {
|
||||||
const path = "plugin-module";
|
const path = 'plugin-module';
|
||||||
const options = { option1: "value1", option2: "value2" };
|
const options = {option1: 'value1', option2: 'value2'};
|
||||||
|
|
||||||
t.deepEqual(parseConfig(path), [path, {}], "String definition");
|
t.deepEqual(parseConfig(path), [path, {}], 'String definition');
|
||||||
t.deepEqual(parseConfig({ path }), [path, {}], "Object definition");
|
t.deepEqual(parseConfig({path}), [path, {}], 'Object definition');
|
||||||
t.deepEqual(parseConfig({ path, ...options }), [path, options], "Object definition with options");
|
t.deepEqual(parseConfig({path, ...options}), [path, options], 'Object definition with options');
|
||||||
t.deepEqual(parseConfig([path]), [path, {}], "Array definition");
|
t.deepEqual(parseConfig([path]), [path, {}], 'Array definition');
|
||||||
t.deepEqual(parseConfig([path, options]), [path, options], "Array definition with options");
|
t.deepEqual(parseConfig([path, options]), [path, options], 'Array definition with options');
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user