Compare commits
@@ -1,48 +0,0 @@
|
||||
<!--
|
||||
Please fill the informations requested or use one of the following url:
|
||||
- Bug report: https://github.com/semantic-release/semantic-release/issues/new?template=bug-report.md
|
||||
- Feature request: https://github.com/semantic-release/semantic-release/issues/new?template=feature-request.md
|
||||
- New plugin request: https://github.com/semantic-release/semantic-release/issues/new?template=plugin-request.md
|
||||
-->
|
||||
|
||||
<!--------------------- FOR BUG REPORT ONLY --------------------->
|
||||
|
||||
## Current behavior
|
||||
|
||||
<!-- Describe how the issue manifests. -->
|
||||
|
||||
## Expected behavior
|
||||
|
||||
<!-- Describe what the desired behavior would be. -->
|
||||
|
||||
## Environment
|
||||
|
||||
- **semantic-release** version: <!-- Version set in package.json devDpendencies -->
|
||||
- CI environment: <!-- CI service name -->
|
||||
- Plugins used: <!-- List semantic-release plugin used if any -->
|
||||
- **semantic-release** configuration: <!-- link to your repository or relevant part of the semantic-release config -->
|
||||
- CI logs: <!-- link to your CI logs or semantic-release logs -->
|
||||
|
||||
<!--------------------- FOR FEATURE REQUEST ONLY --------------------->
|
||||
|
||||
## New feature motivation
|
||||
|
||||
<!-- Describe the context, the use-case and the advantages of the feature request. -->
|
||||
|
||||
## New feature description
|
||||
|
||||
<!-- Describe the functional changes that would have to be made in semantic-release or its plugins. -->
|
||||
|
||||
## New feature implementation
|
||||
|
||||
<!-- Optionally describe the technical changes to be made in semantic-release or its plugins. -->
|
||||
|
||||
<!--------------------- FOR NEW PLUGIN REQUEST ONLY --------------------->
|
||||
|
||||
## New plugin motivation
|
||||
|
||||
<!-- Describe the reasons to create a new plugin and why it's not covered by the existing ones. -->
|
||||
|
||||
## Third-party documentation
|
||||
|
||||
<!-- Provide explanation and documentation links for the platform to integrate with. -->
|
||||
@@ -1,17 +1,21 @@
|
||||
# Bug report
|
||||
|
||||
## Current behavior
|
||||
|
||||
<!-- Describe how the issue manifests. -->
|
||||
|
||||
## Expected behavior
|
||||
|
||||
<!-- Describe what the desired behavior would be. -->
|
||||
|
||||
## Environment
|
||||
|
||||
- **semantic-release** version: <!-- Version set in package.json devDpendencies -->
|
||||
- CI environment: <!-- CI service name -->
|
||||
- Plugins used: <!-- List semantic-release plugin used if any -->
|
||||
- **semantic-release** configuration: <!-- link to your repository or relevant part of the semantic-release config -->
|
||||
- CI logs: <!-- link to your CI logs or semantic-release logs -->
|
||||
---
|
||||
name: Bug report
|
||||
about: Something not working as expected
|
||||
|
||||
---
|
||||
|
||||
## Current behavior
|
||||
|
||||
<!-- Describe how the issue manifests. -->
|
||||
|
||||
## Expected behavior
|
||||
|
||||
<!-- Describe what the desired behavior would be. -->
|
||||
|
||||
## Environment
|
||||
|
||||
- **semantic-release** version: <!-- Version set in package.json devDpendencies -->
|
||||
- CI environment: <!-- CI service name -->
|
||||
- Plugins used: <!-- List semantic-release plugin used if any -->
|
||||
- **semantic-release** configuration: <!-- link to your repository or relevant part of the semantic-release config -->
|
||||
- CI logs: <!-- link to your CI logs or semantic-release logs -->
|
||||
+17
-13
@@ -1,13 +1,17 @@
|
||||
# Feature request
|
||||
|
||||
## New feature motivation
|
||||
|
||||
<!-- Describe the context, the use-case and the advantages of the feature request. -->
|
||||
|
||||
## New feature description
|
||||
|
||||
<!-- Describe the functional changes that would have to be made in semantic-release or its plugins. -->
|
||||
|
||||
## New feature implementation
|
||||
|
||||
<!-- Optionally describe the technical changes to be made in semantic-release or its plugins. -->
|
||||
---
|
||||
name: Feature request
|
||||
about: Wouldn’t it be nice if semantic-release could ...
|
||||
|
||||
---
|
||||
|
||||
## New feature motivation
|
||||
|
||||
<!-- Describe the context, the use-case and the advantages of the feature request. -->
|
||||
|
||||
## New feature description
|
||||
|
||||
<!-- Describe the functional changes that would have to be made in semantic-release or its plugins. -->
|
||||
|
||||
## New feature implementation
|
||||
|
||||
<!-- Optionally describe the technical changes to be made in semantic-release or its plugins. -->
|
||||
+13
-9
@@ -1,9 +1,13 @@
|
||||
# New plugin request
|
||||
|
||||
## New plugin motivation
|
||||
|
||||
<!-- Describe the reasons to create a new plugin and why it's not covered by the existing ones. -->
|
||||
|
||||
## Third-party documentation
|
||||
|
||||
<!-- Provide explanation and documentation links for the platform to integrate with. -->
|
||||
---
|
||||
name: New plugin suggestion
|
||||
about: Integrate with a new platform, etc
|
||||
|
||||
---
|
||||
|
||||
## New plugin motivation
|
||||
|
||||
<!-- Describe the reasons to create a new plugin and why it's not covered by the existing ones. -->
|
||||
|
||||
## Third-party documentation
|
||||
|
||||
<!-- Provide explanation and documentation links for the platform to integrate with. -->
|
||||
+1
-1
@@ -4,7 +4,7 @@ services:
|
||||
- docker
|
||||
|
||||
node_js:
|
||||
- 9
|
||||
- 10
|
||||
- 8
|
||||
|
||||
# Trigger a push build on caribou and greenkeeper branches + PRs build on every branches
|
||||
|
||||
@@ -42,3 +42,7 @@
|
||||
- [publish](https://github.com/felixfbecker/semantic-release-docker#publish) Tag the image specified by `name` with the new version, push it to Docker Hub and update the latest tag.
|
||||
- [semantic-release-verify-deps](https://github.com/piercus/semantic-release-verify-deps)
|
||||
- [verifyConditions](https://github.com/piercus/semantic-release-verify-deps) Check the dependencies format against a regexp before a release
|
||||
- [semantic-release-chrome](https://github.com/GabrielDuarteM/semantic-release-chrome) Set of semantic-release plugins for publishing a Chrome extension release.
|
||||
- [verifyConditions](https://github.com/GabrielDuarteM/semantic-release-chrome#verifyconditions) Verify the presence of the authentication (set via environment variables).
|
||||
- [prepare](https://github.com/GabrielDuarteM/semantic-release-chrome#prepare) Write the correct version to the manifest.json and creates a zip file of the whole dist folder.
|
||||
- [publish](https://github.com/GabrielDuarteM/semantic-release-chrome#publish) Uploads the generated zip file to the webstore, and publish the item.
|
||||
|
||||
@@ -4,6 +4,8 @@ When using [environment variables](../usage/ci-configuration.md#authentication)
|
||||
|
||||
Alternatively the Git repository can be accessed via [SSH](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols#_the_ssh_protocol) by creating SSH keys, adding the public one to your Git hosted account and making the private one available on the CI environment.
|
||||
|
||||
**Note:** SSH keys allow to push the [Git release tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) associated to the released version. Some plugins might also require an API token. See each plugin documentation for additional information.
|
||||
|
||||
## Generating the SSH keys
|
||||
|
||||
In your local repository root:
|
||||
|
||||
@@ -29,3 +29,18 @@ You can recover from that issue by identifying the commit in your branch history
|
||||
$ git tag -f v<version of the last release> <commit sha1 corresponding to last release>
|
||||
$ git push -f --tags origin <your release branch>
|
||||
```
|
||||
|
||||
## You do not have permission to publish 'package-name'
|
||||
|
||||
When running semantic-release you may encounter the following error:
|
||||
|
||||
```
|
||||
An error occurred while running semantic-release: { Error: Command failed: npm publish ./. --registry https://registry.npmjs.org/
|
||||
npm ERR! publish Failed PUT 403
|
||||
npm ERR! code E403
|
||||
npm ERR! You do not have permission to publish "<package-name>". Are you logged in as the correct user? : <package-name>
|
||||
```
|
||||
|
||||
This message is a little unclear, and might not have anything to with your `NPM_TOKEN` or authentication method. It might instead be related to the package name itself. If there is already a package with the same name as yours or, there is a very close match, it could trigger this error.
|
||||
|
||||
Best way to be sure, is to search [npmjs.org](https://www.npmjs.com/)) using your package name. If there is a name conflict, rename your package in your `package.json`
|
||||
|
||||
@@ -10,13 +10,14 @@ See [CI configuration recipes](../recipes/README.md#ci-configurations) for more
|
||||
|
||||
**semantic-release** requires push access to the project Git repository in order to create [Git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging). The Git authentication can be set with one of the following environment variables:
|
||||
|
||||
| Variable | Description |
|
||||
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `GH_TOKEN` or `GITHUB_TOKEN` | A GitHub [personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line). |
|
||||
| `GL_TOKEN` or `GITLAB_TOKEN` | A GitLab [personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html). |
|
||||
| `GIT_CREDENTIALS` | [URL encoded basic HTTP Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication#URL_encoding) credentials). |
|
||||
| Variable | Description |
|
||||
|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `GH_TOKEN` or `GITHUB_TOKEN` | A GitHub [personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line). |
|
||||
| `GL_TOKEN` or `GITLAB_TOKEN` | A GitLab [personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html). |
|
||||
| `BB_TOKEN` or `BITBUCKET_TOKEN` | A Bitbucket [personal access token](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html). |
|
||||
| `GIT_CREDENTIALS` | [URL encoded basic HTTP Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication#URL_encoding) credentials). |
|
||||
|
||||
`GIT_CREDENTIALS` can be the Git username and password in the format `<username>:<password>` or a token for certain Git providers like [Bitbucket](https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html).
|
||||
`GIT_CREDENTIALS` must be the Git username and password in the format `<username>:<password>`.
|
||||
|
||||
Alternatively the Git authentication can be set up via [SSH keys](../recipes/git-auth-ssh-keys.md).
|
||||
|
||||
|
||||
@@ -29,6 +29,15 @@ $ semantic-release
|
||||
|
||||
**Note**: Plugin options cannot be defined via CLI arguments and must be defined in the configuration file.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|
|
||||
| `GIT_AUTHOR_NAME` | The author name associated with the [Git release tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging). See [Git environment variables](https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#_committing). | @semantic-release-bot. |
|
||||
| `GIT_AUTHOR_EMAIL` | The author email associated with the [Git release tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging). See [Git environment variables](https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#_committing). | @semantic-release-bot email address. |
|
||||
| `GIT_COMMITTER_NAME` | The committer name associated with the [Git release tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging). See [Git environment variables](https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#_committing). | @semantic-release-bot. |
|
||||
| `GIT_COMMITTER_EMAIL` | The committer email associated with the [Git release tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging). See [Git environment variables](https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#_committing). | @semantic-release-bot email address. |
|
||||
|
||||
## Options
|
||||
|
||||
### extends
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Each [release step](../../README.md#release-steps) is implemented within a plugin or a list of plugins that can be configured. This allows for support of different [commit message formats](../../README.md#commit-message-format), release note generators and publishing platforms.
|
||||
|
||||
See [plugins list](../extending/plugins-list.md).
|
||||
|
||||
## Plugin types
|
||||
|
||||
### verifyConditions plugin
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
A sharable configuration is an [npm](https://www.npmjs.com/) package that exports a **semantic-release** configuration object. It allows for use of the same configuration across several projects.
|
||||
|
||||
The shareable configurations to use can be set with the [extends](configuration.md#extends) option.
|
||||
|
||||
See [shareable configurations list](../extending/shareable-configurations-list.md).
|
||||
|
||||
@@ -13,8 +13,9 @@ const getLastRelease = require('./lib/get-last-release');
|
||||
const {extractErrors} = require('./lib/utils');
|
||||
const getGitAuthUrl = require('./lib/get-git-auth-url');
|
||||
const logger = require('./lib/logger');
|
||||
const {unshallow, verifyAuth, gitHead: getGitHead, tag, push} = require('./lib/git');
|
||||
const {unshallow, verifyAuth, isBranchUpToDate, gitHead: getGitHead, tag, push} = require('./lib/git');
|
||||
const getError = require('./lib/get-error');
|
||||
const {COMMIT_NAME, COMMIT_EMAIL} = require('./lib/definitions/constants');
|
||||
|
||||
marked.setOptions({renderer: new TerminalRenderer()});
|
||||
|
||||
@@ -25,9 +26,16 @@ async function run(options, plugins) {
|
||||
logger.log('This run was not triggered in a known CI environment, running in dry-run mode.');
|
||||
options.dryRun = true;
|
||||
} else {
|
||||
// When running on CI, prevent the `git` CLI to prompt for username/password. See #703.
|
||||
process.env.GIT_ASKPASS = 'echo';
|
||||
process.env.GIT_TERMINAL_PROMPT = 0;
|
||||
// When running on CI, set the commits author and commiter info and prevent the `git` CLI to prompt for username/password. See #703.
|
||||
process.env = {
|
||||
GIT_AUTHOR_NAME: COMMIT_NAME,
|
||||
GIT_AUTHOR_EMAIL: COMMIT_EMAIL,
|
||||
GIT_COMMITTER_NAME: COMMIT_NAME,
|
||||
GIT_COMMITTER_EMAIL: COMMIT_EMAIL,
|
||||
...process.env,
|
||||
GIT_ASKPASS: 'echo',
|
||||
GIT_TERMINAL_PROMPT: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (isCi && isPr && !options.noCi) {
|
||||
@@ -46,9 +54,22 @@ async function run(options, plugins) {
|
||||
|
||||
await verify(options);
|
||||
|
||||
const {repositoryUrl} = options;
|
||||
options.repositoryUrl = await getGitAuthUrl(options);
|
||||
if (!await verifyAuth(options.repositoryUrl, options.branch)) {
|
||||
throw getError('EGITNOPERMISSION', {options});
|
||||
|
||||
if (!(await isBranchUpToDate(options.branch))) {
|
||||
logger.log(
|
||||
"The local branch %s is behind the remote one, therefore a new version won't be published.",
|
||||
options.branch
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await verifyAuth(options.repositoryUrl, options.branch);
|
||||
} catch (err) {
|
||||
logger.error(`The command "${err.cmd}" failed with the error message %s.`, err.stderr);
|
||||
throw getError('EGITNOPERMISSION', {options, repositoryUrl});
|
||||
}
|
||||
|
||||
logger.log('Run automated release from branch %s', options.branch);
|
||||
@@ -57,7 +78,7 @@ async function run(options, plugins) {
|
||||
await plugins.verifyConditions({options, logger}, {settleAll: true});
|
||||
|
||||
// Unshallow the repo in order to get all the tags
|
||||
await unshallow();
|
||||
await unshallow(options.repositoryUrl);
|
||||
|
||||
const lastRelease = await getLastRelease(options.tagFormat, logger);
|
||||
const commits = await getCommits(lastRelease.gitHead, options.branch, logger);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const RELEASE_TYPE = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'];
|
||||
|
||||
const FIRST_RELEASE = '1.0.0';
|
||||
|
||||
const COMMIT_NAME = 'semantic-release-bot';
|
||||
|
||||
const COMMIT_EMAIL = 'semantic-release-bot@martynus.net';
|
||||
|
||||
module.exports = {RELEASE_TYPE, FIRST_RELEASE, COMMIT_NAME, COMMIT_EMAIL};
|
||||
@@ -2,7 +2,7 @@ const url = require('url');
|
||||
const {inspect} = require('util');
|
||||
const {toLower, isString} = require('lodash');
|
||||
const pkg = require('../../package.json');
|
||||
const RELEASE_TYPE = require('./release-types');
|
||||
const {RELEASE_TYPE} = require('./constants');
|
||||
|
||||
const homepage = url.format({...url.parse(pkg.homepage), ...{hash: null}});
|
||||
const stringify = obj => (isString(obj) ? obj : inspect(obj, {breakLength: Infinity, depth: 2, maxArrayLength: 5}));
|
||||
@@ -27,15 +27,17 @@ Please make sure to add the \`repositoryUrl\` to the [semantic-release configura
|
||||
'docs/usage/configuration.md'
|
||||
)}).`,
|
||||
}),
|
||||
EGITNOPERMISSION: ({options}) => ({
|
||||
EGITNOPERMISSION: ({options, repositoryUrl}) => ({
|
||||
message: 'The push permission to the Git repository is required.',
|
||||
details: `**semantic-release** cannot push the version tag to the branch \`${
|
||||
options.branch
|
||||
}\` on remote Git repository.
|
||||
}\` on remote Git repository with URL \`${repositoryUrl}\`.
|
||||
|
||||
Please refer to the [authentication configuration documentation](${linkify(
|
||||
'docs/usage/ci-configuration.md#authentication'
|
||||
)}) to configure the Git credentials on your CI environment.`,
|
||||
)}) to configure the Git credentials on your CI environment and make sure the [repositoryUrl](${linkify(
|
||||
'docs/usage/configuration.md#repositoryurl'
|
||||
)}) is configured with a [valid Git URL](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).`,
|
||||
}),
|
||||
EINVALIDTAGFORMAT: ({tagFormat}) => ({
|
||||
message: 'Invalid `tagFormat` option.',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const {isString, isFunction, isArray, isPlainObject} = require('lodash');
|
||||
const RELEASE_TYPE = require('./release-types');
|
||||
const {RELEASE_TYPE} = require('./constants');
|
||||
|
||||
const validatePluginConfig = conf => isString(conf) || isString(conf.path) || isFunction(conf);
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'];
|
||||
+12
-1
@@ -7,8 +7,19 @@ const {repoUrl} = require('./git');
|
||||
const PLUGINS_DEFINITIONS = require('./definitions/plugins');
|
||||
const plugins = require('./plugins');
|
||||
|
||||
const CONFIG_NAME = 'release';
|
||||
const CONFIG_FILES = [
|
||||
'package.json',
|
||||
`.${CONFIG_NAME}rc`,
|
||||
`.${CONFIG_NAME}rc.json`,
|
||||
`.${CONFIG_NAME}rc.yaml`,
|
||||
`.${CONFIG_NAME}rc.yml`,
|
||||
`.${CONFIG_NAME}rc.js`,
|
||||
`${CONFIG_NAME}.config.js`,
|
||||
];
|
||||
|
||||
module.exports = async (opts, logger) => {
|
||||
const {config} = (await cosmiconfig('release', {rcExtensions: true}).load(process.cwd())) || {};
|
||||
const {config} = (await cosmiconfig(CONFIG_NAME, {searchPlaces: CONFIG_FILES}).search()) || {};
|
||||
// Merge config file options and CLI/API options
|
||||
let options = {...config, ...opts};
|
||||
const pluginsPath = {};
|
||||
|
||||
+16
-7
@@ -4,7 +4,15 @@ const gitUrlParse = require('git-url-parse');
|
||||
const hostedGitInfo = require('hosted-git-info');
|
||||
const {verifyAuth} = require('./git');
|
||||
|
||||
const GIT_TOKENS = ['GIT_CREDENTIALS', 'GH_TOKEN', 'GITHUB_TOKEN', 'GL_TOKEN', 'GITLAB_TOKEN'];
|
||||
const GIT_TOKENS = {
|
||||
GIT_CREDENTIALS: undefined,
|
||||
GH_TOKEN: undefined,
|
||||
GITHUB_TOKEN: undefined,
|
||||
GL_TOKEN: 'gitlab-ci-token:',
|
||||
GITLAB_TOKEN: 'gitlab-ci-token:',
|
||||
BB_TOKEN: 'x-token-auth:',
|
||||
BITBUCKET_TOKEN: 'x-token-auth:',
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the the git repository URL to use to push, either:
|
||||
@@ -35,16 +43,17 @@ module.exports = async ({repositoryUrl, branch}) => {
|
||||
}
|
||||
|
||||
// Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
|
||||
if (!await verifyAuth(repositoryUrl, branch)) {
|
||||
const envVar = GIT_TOKENS.find(envVar => !isUndefined(process.env[envVar]));
|
||||
const gitCredentials = ['GL_TOKEN', 'GITLAB_TOKEN'].includes(envVar)
|
||||
? `gitlab-ci-token:${process.env[envVar]}`
|
||||
: process.env[envVar];
|
||||
try {
|
||||
await verifyAuth(repositoryUrl, branch);
|
||||
} catch (err) {
|
||||
const envVar = Object.keys(GIT_TOKENS).find(envVar => !isUndefined(process.env[envVar]));
|
||||
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${process.env[envVar] || ''}`;
|
||||
const {protocols, ...parsed} = gitUrlParse(repositoryUrl);
|
||||
const protocol = protocols.includes('https') ? 'https' : protocols.includes('http') ? 'http' : 'https';
|
||||
|
||||
// If credentials are set via anvironment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
|
||||
return gitCredentials ? {...parsed, ...{token: gitCredentials}}.toString(protocol) : repositoryUrl;
|
||||
return gitCredentials ? {...parsed, protocols: [protocol], user: gitCredentials}.toString(protocol) : repositoryUrl;
|
||||
}
|
||||
|
||||
return repositoryUrl;
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ const {gitTags, isRefInHistory, gitTagHead} = require('./git');
|
||||
* - Sort the versions
|
||||
* - Retrive the highest version
|
||||
*
|
||||
* @param {String} tagFormat Git tag format.
|
||||
* @param {Object} logger Global logger.
|
||||
* @return {Promise<LastRelease>} The last tagged release or `undefined` if none is found.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const semver = require('semver');
|
||||
const {FIRST_RELEASE} = require('./definitions/constants');
|
||||
|
||||
module.exports = (type, lastRelease, logger) => {
|
||||
let version;
|
||||
@@ -6,7 +7,7 @@ module.exports = (type, lastRelease, logger) => {
|
||||
version = semver.inc(lastRelease.version, type);
|
||||
logger.log('The next release version is %s', version);
|
||||
} else {
|
||||
version = '1.0.0';
|
||||
version = FIRST_RELEASE;
|
||||
logger.log('There is no previous release, the next release version is %s', version);
|
||||
}
|
||||
|
||||
|
||||
+31
-10
@@ -36,17 +36,25 @@ async function gitTags() {
|
||||
*/
|
||||
async function isRefInHistory(ref) {
|
||||
try {
|
||||
return (await execa('git', ['merge-base', '--is-ancestor', ref, 'HEAD'])).code === 0;
|
||||
await execa('git', ['merge-base', '--is-ancestor', ref, 'HEAD']);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.code === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
debug(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshallow the git repository (retriving every commits and tags).
|
||||
*
|
||||
* @param {String} repositoryUrl The remote repository URL.
|
||||
*/
|
||||
async function unshallow() {
|
||||
await execa('git', ['fetch', '--unshallow', '--tags'], {reject: false});
|
||||
async function unshallow(repositoryUrl) {
|
||||
await execa('git', ['fetch', '--unshallow', '--tags', repositoryUrl], {reject: false});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,16 +89,17 @@ async function isGitRepo() {
|
||||
/**
|
||||
* Verify the write access authorization to remote repository with push dry-run.
|
||||
*
|
||||
* @param {String} origin The remote repository URL.
|
||||
* @param {String} repositoryUrl The remote repository URL.
|
||||
* @param {String} branch The repositoru branch for which to verify write access.
|
||||
*
|
||||
* @return {Boolean} `true` is authorized to push, falsy otherwise.
|
||||
* @throws {Error} if not authorized to push.
|
||||
*/
|
||||
async function verifyAuth(origin, branch) {
|
||||
async function verifyAuth(repositoryUrl, branch) {
|
||||
try {
|
||||
return (await execa('git', ['push', '--dry-run', origin, `HEAD:${branch}`])).code === 0;
|
||||
await execa('git', ['push', '--dry-run', repositoryUrl, `HEAD:${branch}`]);
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,12 +116,12 @@ async function tag(tagName) {
|
||||
/**
|
||||
* Push to the remote repository.
|
||||
*
|
||||
* @param {String} origin The remote repository URL.
|
||||
* @param {String} repositoryUrl The remote repository URL.
|
||||
* @param {String} branch The branch to push.
|
||||
* @throws {Error} if the push failed.
|
||||
*/
|
||||
async function push(origin, branch) {
|
||||
await execa('git', ['push', '--tags', origin, `HEAD:${branch}`]);
|
||||
async function push(repositoryUrl, branch) {
|
||||
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,6 +138,17 @@ async function verifyTagName(tagName) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the local branch is up to date with the remote one.
|
||||
*
|
||||
* @param {String} branch The repository branch for which to verify status.
|
||||
*
|
||||
* @return {Boolean} `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, falsy otherwise.
|
||||
*/
|
||||
async function isBranchUpToDate(branch) {
|
||||
return isRefInHistory(await execa.stdout('git', ['rev-parse', `origin/${branch}`]));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
gitTagHead,
|
||||
gitTags,
|
||||
@@ -141,4 +161,5 @@ module.exports = {
|
||||
tag,
|
||||
push,
|
||||
verifyTagName,
|
||||
isBranchUpToDate,
|
||||
};
|
||||
|
||||
+15
-16
@@ -37,21 +37,20 @@ module.exports = (pluginType, pluginsPath, globalOpts, pluginOpts, logger) => {
|
||||
throw getError('EPLUGIN', {pluginType, pluginName});
|
||||
}
|
||||
|
||||
return Object.defineProperty(
|
||||
async input => {
|
||||
const definition = PLUGINS_DEFINITIONS[pluginType];
|
||||
try {
|
||||
const result = await func(cloneDeep(input));
|
||||
if (definition && definition.output && !definition.output.validator(result)) {
|
||||
throw getError(PLUGINS_DEFINITIONS[pluginType].output.error, {result, pluginName});
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
extractErrors(err).forEach(err => Object.assign(err, {pluginName}));
|
||||
throw err;
|
||||
const validator = async input => {
|
||||
const definition = PLUGINS_DEFINITIONS[pluginType];
|
||||
try {
|
||||
const result = await func(cloneDeep(input));
|
||||
if (definition && definition.output && !definition.output.validator(result)) {
|
||||
throw getError(PLUGINS_DEFINITIONS[pluginType].output.error, {result, pluginName});
|
||||
}
|
||||
},
|
||||
'pluginName',
|
||||
{value: pluginName, writable: false, enumerable: true}
|
||||
);
|
||||
return result;
|
||||
} catch (err) {
|
||||
extractErrors(err).forEach(err => Object.assign(err, {pluginName}));
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
Reflect.defineProperty(validator, 'pluginName', {value: pluginName, writable: false, enumerable: true});
|
||||
return validator;
|
||||
};
|
||||
|
||||
+2
-2
@@ -6,14 +6,14 @@ const getError = require('./get-error');
|
||||
module.exports = async options => {
|
||||
const errors = [];
|
||||
|
||||
if (!await isGitRepo()) {
|
||||
if (!(await isGitRepo())) {
|
||||
errors.push(getError('ENOGITREPO'));
|
||||
} else if (!options.repositoryUrl) {
|
||||
errors.push(getError('ENOREPOURL'));
|
||||
}
|
||||
|
||||
// Verify that compiling the `tagFormat` produce a valid Git tag
|
||||
if (!await verifyTagName(template(options.tagFormat)({version: '0.0.0'}))) {
|
||||
if (!(await verifyTagName(template(options.tagFormat)({version: '0.0.0'})))) {
|
||||
errors.push(getError('EINVALIDTAGFORMAT', {tagFormat: options.tagFormat}));
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -26,9 +26,9 @@
|
||||
"@semantic-release/release-notes-generator": "^6.0.0",
|
||||
"aggregate-error": "^1.0.0",
|
||||
"chalk": "^2.3.0",
|
||||
"cosmiconfig": "^4.0.0",
|
||||
"cosmiconfig": "^5.0.1",
|
||||
"debug": "^3.1.0",
|
||||
"env-ci": "^1.0.0",
|
||||
"env-ci": "^2.0.0",
|
||||
"execa": "^0.10.0",
|
||||
"get-stream": "^3.0.0",
|
||||
"git-log-parser": "^1.2.0",
|
||||
@@ -54,17 +54,17 @@
|
||||
"delay": "^2.0.0",
|
||||
"dockerode": "^2.5.2",
|
||||
"file-url": "^2.0.2",
|
||||
"fs-extra": "^5.0.0",
|
||||
"fs-extra": "^6.0.0",
|
||||
"got": "^8.0.0",
|
||||
"js-yaml": "^3.10.0",
|
||||
"mockserver-client": "^5.1.1",
|
||||
"nock": "^9.0.2",
|
||||
"nyc": "^11.2.1",
|
||||
"p-retry": "^1.0.0",
|
||||
"p-retry": "^2.0.0",
|
||||
"proxyquire": "^2.0.0",
|
||||
"sinon": "^4.0.0",
|
||||
"sinon": "^5.0.1",
|
||||
"tempy": "^0.2.1",
|
||||
"xo": "^0.20.0"
|
||||
"xo": "^0.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.3"
|
||||
@@ -104,7 +104,8 @@
|
||||
"all": true
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 120
|
||||
"printWidth": 120,
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"publishConfig": {
|
||||
"tag": "next"
|
||||
|
||||
+6
-6
@@ -165,12 +165,12 @@ test.serial('Do not set properties in option for which arg is not in command lin
|
||||
|
||||
await cli();
|
||||
|
||||
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'ci'));
|
||||
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'd'));
|
||||
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'dry-run'));
|
||||
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'debug'));
|
||||
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'r'));
|
||||
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 't'));
|
||||
t.false(Reflect.apply(Object.prototype.hasOwnProperty, run.args[0][0], ['ci']));
|
||||
t.false(Reflect.apply(Object.prototype.hasOwnProperty, run.args[0][0], ['d']));
|
||||
t.false(Reflect.apply(Object.prototype.hasOwnProperty, run.args[0][0], ['dry-run']));
|
||||
t.false(Reflect.apply(Object.prototype.hasOwnProperty, run.args[0][0], ['debug']));
|
||||
t.false(Reflect.apply(Object.prototype.hasOwnProperty, run.args[0][0], ['r']));
|
||||
t.false(Reflect.apply(Object.prototype.hasOwnProperty, run.args[0][0], ['t']));
|
||||
});
|
||||
|
||||
test.serial('Set "noCi" options to "true" with "--no-ci"', async t => {
|
||||
|
||||
@@ -13,6 +13,10 @@ test.beforeEach(() => {
|
||||
delete process.env.GITHUB_TOKEN;
|
||||
delete process.env.GL_TOKEN;
|
||||
delete process.env.GITLAB_TOKEN;
|
||||
delete process.env.BB_TOKEN;
|
||||
delete process.env.BITBUCKET_TOKEN;
|
||||
process.env.GIT_ASKPASS = 'echo';
|
||||
process.env.GIT_TERMINAL_PROMPT = 0;
|
||||
});
|
||||
|
||||
test.afterEach.always(() => {
|
||||
@@ -150,6 +154,22 @@ test.serial('Return the "https" formatted URL if "gitCredentials" is defined wit
|
||||
);
|
||||
});
|
||||
|
||||
test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "BB_TOKEN"', async t => {
|
||||
process.env.BB_TOKEN = 'token';
|
||||
t.is(
|
||||
await getAuthUrl({repositoryUrl: 'git@host.null:owner/repo.git'}),
|
||||
'https://x-token-auth:token@host.null/owner/repo.git'
|
||||
);
|
||||
});
|
||||
|
||||
test.serial('Return the "https" formatted URL if "gitCredentials" is defined with "BITBUCKET_TOKEN"', async t => {
|
||||
process.env.BITBUCKET_TOKEN = 'token';
|
||||
t.is(
|
||||
await getAuthUrl({repositoryUrl: 'git@host.null:owner/repo.git'}),
|
||||
'https://x-token-auth:token@host.null/owner/repo.git'
|
||||
);
|
||||
});
|
||||
|
||||
test.serial('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async t => {
|
||||
process.env.GIT_CREDENTIALS = 'user:pass';
|
||||
t.is(
|
||||
|
||||
+37
-3
@@ -11,6 +11,7 @@ import {
|
||||
gitTags,
|
||||
isGitRepo,
|
||||
verifyTagName,
|
||||
isBranchUpToDate,
|
||||
} from '../lib/git';
|
||||
import {
|
||||
gitRepo,
|
||||
@@ -22,6 +23,8 @@ import {
|
||||
gitAddConfig,
|
||||
gitCommitTag,
|
||||
gitRemoteTagHead,
|
||||
push as pushUtil,
|
||||
reset,
|
||||
} from './helpers/git-utils';
|
||||
|
||||
// Save the current working diretory
|
||||
@@ -61,7 +64,7 @@ test.serial('Unshallow repository', async t => {
|
||||
// Verify the shallow clone contains only one commit
|
||||
t.is((await gitGetCommits()).length, 1);
|
||||
|
||||
await unshallow();
|
||||
await unshallow(repo);
|
||||
|
||||
// Verify the shallow clone contains all the commits
|
||||
t.is((await gitGetCommits()).length, 2);
|
||||
@@ -69,10 +72,10 @@ test.serial('Unshallow repository', async t => {
|
||||
|
||||
test.serial('Do not throw error when unshallow a complete repository', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
const repo = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First']);
|
||||
await t.notThrows(unshallow());
|
||||
await t.notThrows(unshallow(repo));
|
||||
});
|
||||
|
||||
test.serial('Verify if the commit `sha` is in the direct history of the current branch', async t => {
|
||||
@@ -88,6 +91,7 @@ test.serial('Verify if the commit `sha` is in the direct history of the current
|
||||
|
||||
t.true(await isRefInHistory(commits[0].hash));
|
||||
t.falsy(await isRefInHistory(otherCommits[0].hash));
|
||||
await t.throws(isRefInHistory('non-existant-sha'));
|
||||
});
|
||||
|
||||
test.serial('Get the commit sha for a given tag or falsy if the tag does not exists', async t => {
|
||||
@@ -183,3 +187,33 @@ test.serial('Throws error if obtaining the tags fails', async t => {
|
||||
|
||||
await t.throws(gitTags());
|
||||
});
|
||||
|
||||
test.serial('Return "true" if repository is up to date', async t => {
|
||||
await gitRepo(true);
|
||||
await gitCommits(['First']);
|
||||
await pushUtil();
|
||||
|
||||
t.true(await isBranchUpToDate('master'));
|
||||
});
|
||||
|
||||
test.serial('Return falsy if repository is not up to date', async t => {
|
||||
await gitRepo(true);
|
||||
await gitCommits(['First']);
|
||||
await gitCommits(['Second']);
|
||||
await pushUtil();
|
||||
|
||||
t.true(await isBranchUpToDate('master'));
|
||||
|
||||
await reset();
|
||||
|
||||
t.falsy(await isBranchUpToDate('master'));
|
||||
});
|
||||
|
||||
test.serial('Return "true" if local repository is ahead', async t => {
|
||||
await gitRepo(true);
|
||||
await gitCommits(['First']);
|
||||
await pushUtil();
|
||||
await gitCommits(['Second']);
|
||||
|
||||
t.true(await isBranchUpToDate('master'));
|
||||
});
|
||||
|
||||
+29
-23
@@ -20,7 +20,7 @@ import getStream from 'get-stream';
|
||||
* If `withRemote` is `false`, creates a regular repository and initialize it. Change the current working directory to the repository root.
|
||||
*
|
||||
* @param {Boolean} withRemote `true` to create a shallow clone of a bare repository.
|
||||
* @param {String} [branc='master'] The branch to initialize.
|
||||
* @param {String} [branch='master'] The branch to initialize.
|
||||
* @return {String} The path of the clone if `withRemote` is `true`, the path of the repository otherwise.
|
||||
*/
|
||||
export async function gitRepo(withRemote, branch = 'master') {
|
||||
@@ -46,16 +46,16 @@ export async function gitRepo(withRemote, branch = 'master') {
|
||||
* - Create an initial commits
|
||||
* - Push to origin
|
||||
*
|
||||
* @param {String} origin The URL of the bare repository.
|
||||
* @param {String} repositoryUrl The URL of the bare repository.
|
||||
* @param {String} [branch='master'] the branch to initialize.
|
||||
*/
|
||||
export async function initBareRepo(origin, branch = 'master') {
|
||||
export async function initBareRepo(repositoryUrl, branch = 'master') {
|
||||
const clone = tempy.directory();
|
||||
await execa('git', ['clone', '--no-hardlinks', origin, clone]);
|
||||
await execa('git', ['clone', '--no-hardlinks', repositoryUrl, clone]);
|
||||
process.chdir(clone);
|
||||
await gitCheckout(branch);
|
||||
await gitCommits(['Initial commit']);
|
||||
await execa('git', ['push', origin, branch]);
|
||||
await execa('git', ['push', repositoryUrl, branch]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +98,7 @@ export async function gitGetCommits(from) {
|
||||
* Checkout a branch on the current git repository.
|
||||
*
|
||||
* @param {String} branch Branch name.
|
||||
* @param {boolean} create `true` to create the branche ans switch, `false` to only switch.
|
||||
* @param {Boolean} create `true` to create the branche ans switch, `false` to only switch.
|
||||
*/
|
||||
export async function gitCheckout(branch, create = true) {
|
||||
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch]);
|
||||
@@ -121,39 +121,36 @@ export async function gitTagVersion(tagName, sha) {
|
||||
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName]);
|
||||
}
|
||||
|
||||
export async function gitRemoteTagVersion(origin, tagName, sha = 'HEAD') {
|
||||
await execa('git', ['push', origin, `${sha}:refs/tags/${tagName}`]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shallow clone of a git repository and change the current working directory to the cloned repository root.
|
||||
* The shallow will contain a limited number of commit and no tags.
|
||||
*
|
||||
* @param {String} origin The path of the repository to clone.
|
||||
* @param {String} repositoryUrl The path of the repository to clone.
|
||||
* @param {String} [branch='master'] the branch to clone.
|
||||
* @param {Number} [depth=1] The number of commit to clone.
|
||||
* @return {String} The path of the cloned repository.
|
||||
*/
|
||||
export async function gitShallowClone(origin, branch = 'master', depth = 1) {
|
||||
export async function gitShallowClone(repositoryUrl, branch = 'master', depth = 1) {
|
||||
const dir = tempy.directory();
|
||||
|
||||
process.chdir(dir);
|
||||
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, origin, dir]);
|
||||
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, repositoryUrl, dir]);
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a git repo with a detached head from another git repository and change the current working directory to the new repository root.
|
||||
*
|
||||
* @param {String} origin The path of the repository to clone.
|
||||
* @param {Number} head A commit sha of the origin repo that will become the detached head of the new one.
|
||||
* @param {String} repositoryUrl The path of the repository to clone.
|
||||
* @param {Number} head A commit sha of the remote repo that will become the detached head of the new one.
|
||||
* @return {String} The path of the new repository.
|
||||
*/
|
||||
export async function gitDetachedHead(origin, head) {
|
||||
export async function gitDetachedHead(repositoryUrl, head) {
|
||||
const dir = tempy.directory();
|
||||
|
||||
process.chdir(dir);
|
||||
await execa('git', ['init']);
|
||||
await execa('git', ['remote', 'add', 'origin', origin]);
|
||||
await execa('git', ['remote', 'add', 'origin', repositoryUrl]);
|
||||
await execa('git', ['fetch']);
|
||||
await execa('git', ['checkout', head]);
|
||||
return dir;
|
||||
@@ -183,12 +180,12 @@ export async function gitTagHead(tagName) {
|
||||
/**
|
||||
* Get the first commit sha referenced by the tag `tagName` in the remote repository.
|
||||
*
|
||||
* @param {String} origin The repository remote URL.
|
||||
* @param {String} repositoryUrl The repository remote URL.
|
||||
* @param {String} tagName The tag name to seach for.
|
||||
* @return {String} The sha of the commit associated with `tagName` on the remote repository.
|
||||
*/
|
||||
export async function gitRemoteTagHead(origin, tagName) {
|
||||
return (await execa.stdout('git', ['ls-remote', '--tags', origin, tagName]))
|
||||
export async function gitRemoteTagHead(repositoryUrl, tagName) {
|
||||
return (await execa.stdout('git', ['ls-remote', '--tags', repositoryUrl, tagName]))
|
||||
.split('\n')
|
||||
.filter(tag => Boolean(tag))
|
||||
.map(tag => tag.match(/^(\S+)/)[1])[0];
|
||||
@@ -208,10 +205,19 @@ export async function gitCommitTag(gitHead) {
|
||||
/**
|
||||
* Push to the remote repository.
|
||||
*
|
||||
* @param {String} origin The remote repository URL.
|
||||
* @param {String} repositoryUrl The remote repository URL.
|
||||
* @param {String} branch The branch to push.
|
||||
* @throws {Error} if the push failed.
|
||||
*/
|
||||
export async function push(origin, branch) {
|
||||
await execa('git', ['push', '--tags', origin, `HEAD:${branch}`]);
|
||||
export async function push(repositoryUrl = 'origin', branch = 'master') {
|
||||
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset repository to a commit.
|
||||
*
|
||||
* @param {String} [commit='HEAD~1'] Commit reference to reset the repo to.
|
||||
*/
|
||||
export async function reset(commit = 'HEAD~1') {
|
||||
await execa('git', ['reset', commit]);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ const gitCredential = `${GIT_USERNAME}:${GIT_PASSWORD}`;
|
||||
|
||||
/**
|
||||
* Download the `gitbox` Docker image, create a new container and start it.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is started.
|
||||
*/
|
||||
async function start() {
|
||||
await getStream(await docker.pull(IMAGE));
|
||||
@@ -39,8 +37,6 @@ async function start() {
|
||||
|
||||
/**
|
||||
* Stop and remote the `mockserver` Docker container.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is stopped.
|
||||
*/
|
||||
async function stop() {
|
||||
await container.stop();
|
||||
|
||||
@@ -12,8 +12,6 @@ let container;
|
||||
|
||||
/**
|
||||
* Download the `mockserver` Docker image, create a new container and start it.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is started.
|
||||
*/
|
||||
async function start() {
|
||||
await getStream(await docker.pull(IMAGE));
|
||||
@@ -39,8 +37,6 @@ async function start() {
|
||||
|
||||
/**
|
||||
* Stop and remote the `mockserver` Docker container.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is stopped.
|
||||
*/
|
||||
async function stop() {
|
||||
await container.stop();
|
||||
|
||||
@@ -16,8 +16,6 @@ let container;
|
||||
|
||||
/**
|
||||
* Download the `npm-docker-couchdb` Docker image, create a new container and start it.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is started.
|
||||
*/
|
||||
async function start() {
|
||||
await getStream(await docker.pull(IMAGE));
|
||||
@@ -69,8 +67,6 @@ const authEnv = {
|
||||
|
||||
/**
|
||||
* Stop and remote the `npm-docker-couchdb` Docker container.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is stopped.
|
||||
*/
|
||||
async function stop() {
|
||||
await container.stop();
|
||||
|
||||
+47
-1
@@ -5,6 +5,7 @@ import clearModule from 'clear-module';
|
||||
import AggregateError from 'aggregate-error';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
import DEFINITIONS from '../lib/definitions/plugins';
|
||||
import {COMMIT_NAME, COMMIT_EMAIL} from '../lib/definitions/constants';
|
||||
import {
|
||||
gitHead as getGitHead,
|
||||
gitTagHead,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
gitRemoteTagHead,
|
||||
push,
|
||||
gitShallowClone,
|
||||
reset,
|
||||
} from './helpers/git-utils';
|
||||
|
||||
// Save the current process.env
|
||||
@@ -57,6 +59,7 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second'])).concat(commits);
|
||||
await push();
|
||||
|
||||
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
@@ -155,6 +158,12 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
// Verify the tag has been created on the local and remote repo and reference the gitHead
|
||||
t.is(await gitTagHead(nextRelease.gitTag), nextRelease.gitHead);
|
||||
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease.gitTag), nextRelease.gitHead);
|
||||
|
||||
// Verify the author/commiter name and email hve been set
|
||||
t.is(process.env.GIT_AUTHOR_NAME, COMMIT_NAME);
|
||||
t.is(process.env.GIT_AUTHOR_EMAIL, COMMIT_EMAIL);
|
||||
t.is(process.env.GIT_COMMITTER_NAME, COMMIT_NAME);
|
||||
t.is(process.env.GIT_COMMITTER_EMAIL, COMMIT_EMAIL);
|
||||
});
|
||||
|
||||
test.serial('Use custom tag format', async t => {
|
||||
@@ -162,6 +171,7 @@ test.serial('Use custom tag format', async t => {
|
||||
await gitCommits(['First']);
|
||||
await gitTagVersion('test-1.0.0');
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'test-2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -198,6 +208,7 @@ test.serial('Use new gitHead, and recreate release notes if a prepare plugin cre
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second'])).concat(commits);
|
||||
await push();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -257,6 +268,7 @@ test.serial('Call all "success" plugins even if one errors out', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -304,6 +316,7 @@ test.serial('Log all "verifyConditions" errors', async t => {
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First']);
|
||||
await push();
|
||||
|
||||
const error1 = new Error('error 1');
|
||||
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
||||
@@ -346,6 +359,7 @@ test.serial('Log all "verifyRelease" errors', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
|
||||
const error1 = new SemanticReleaseError('error 1', 'ERR1');
|
||||
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
||||
@@ -382,6 +396,7 @@ test.serial('Dry-run skips publish and success', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -430,6 +445,7 @@ test.serial('Dry-run skips fail', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
|
||||
const error1 = new SemanticReleaseError('error 1', 'ERR1');
|
||||
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
||||
@@ -464,6 +480,7 @@ test.serial('Force a dry-run if not on a CI and "noCi" is not explicitly set', a
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -513,6 +530,7 @@ test.serial('Allow local releases with "noCi" option', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -566,6 +584,7 @@ test.serial('Accept "undefined" value returned by the "generateNotes" plugins',
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second'])).concat(commits);
|
||||
await push();
|
||||
|
||||
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
@@ -623,6 +642,27 @@ test.serial('Returns falsy value if triggered by a PR', async t => {
|
||||
);
|
||||
});
|
||||
|
||||
test.serial('Returns falsy value if triggered on an outdated clone', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First']);
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
await reset();
|
||||
|
||||
const semanticRelease = proxyquire('..', {
|
||||
'./lib/logger': t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
|
||||
t.falsy(await semanticRelease({repositoryUrl}));
|
||||
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], [
|
||||
"The local branch %s is behind the remote one, therefore a new version won't be published.",
|
||||
'master',
|
||||
]);
|
||||
});
|
||||
|
||||
test.serial('Returns falsy value if not running from the configured branch', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
@@ -656,6 +696,7 @@ test.serial('Returns falsy value if there is no relevant changes', async t => {
|
||||
const repositoryUrl = await gitRepo(true);
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First']);
|
||||
await push();
|
||||
|
||||
const analyzeCommits = stub().resolves();
|
||||
const verifyRelease = stub().resolves();
|
||||
@@ -685,7 +726,10 @@ test.serial('Returns falsy value if there is no relevant changes', async t => {
|
||||
t.is(verifyRelease.callCount, 0);
|
||||
t.is(generateNotes.callCount, 0);
|
||||
t.is(publish.callCount, 0);
|
||||
t.is(t.context.log.args[7][0], 'There are no relevant changes, so no new version is released.');
|
||||
t.is(
|
||||
t.context.log.args[t.context.log.args.length - 1][0],
|
||||
'There are no relevant changes, so no new version is released.'
|
||||
);
|
||||
});
|
||||
|
||||
test.serial('Exclude commits with [skip release] or [release skip] from analysis', async t => {
|
||||
@@ -702,6 +746,7 @@ test.serial('Exclude commits with [skip release] or [release skip] from analysis
|
||||
'Test commit\n\n commit body\n[skip release]',
|
||||
'Test commit\n\n commit body\n[release skip]',
|
||||
]);
|
||||
await push();
|
||||
const analyzeCommits = stub().resolves();
|
||||
const config = {branch: 'master', repositoryUrl, globalOpt: 'global'};
|
||||
const options = {
|
||||
@@ -826,6 +871,7 @@ test.serial('Throw an Error if plugin returns an unexpected value', async t => {
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
await push();
|
||||
|
||||
const verifyConditions = stub().resolves();
|
||||
const analyzeCommits = stub().resolves('string');
|
||||
|
||||
Reference in New Issue
Block a user