Compare commits

...
12 Commits
Author SHA1 Message Date
Pierre VanduynslagerandGregor Martynus d7081fadb1 fix: use git rev-parse origin/${branch} to verify origin head 2018-05-09 13:27:33 -07:00
Pierre Vanduynslager 61d7d38ec2 feat: set tag author and committer name/email
Use [Git Environment Variables](https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#Committing). Set default values if environement variables are not set.
2018-05-08 14:25:23 -04:00
Cory ReedandGregor Martynus 4352144a98 fix: correct git merge-base error code handling 2018-05-08 10:03:46 -07:00
Even StensbergandPierre Vanduynslager 20245b733a docs: add comment about publishing with ssh keys 2018-05-08 12:01:06 -04:00
greenkeeper[bot]andPierre Vanduynslager 349b2e8d77 fix(package): update cosmiconfig to version 5.0.0 2018-05-05 15:04:42 -04:00
pvdlg 1ed466e337 ci: run test on Node.js 10 2018-05-04 21:55:30 -04:00
Pierre ColleandPierre Vanduynslager f0f325dd7f docs: add link to shareable configs and plugins list 2018-05-04 20:32:45 -04:00
greenkeeper[bot]andPierre Vanduynslager f3e4991819 chore(package): update xo to version 0.21.0 2018-05-04 16:06:24 -04:00
pvdlgandGregor Martynus cd9f2bdd44 feat: log git error message when authentication verification fails 2018-05-04 12:19:24 -07:00
Gregor d1c3ad0b57 docs: issue templates order 2018-05-02 16:01:16 -07:00
Gregor e3da17247c docs: remove old issue templates 2018-05-02 09:47:45 -07:00
Gregor Martynus 05b09b1738 docs(ISSUE_TEMPLATES): initial version 2018-05-02 09:47:45 -07:00
29 changed files with 161 additions and 144 deletions
-48
View File
@@ -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 -->
@@ -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. -->
@@ -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
View File
@@ -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
+2
View File
@@ -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:
+9
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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).
+16 -5
View File
@@ -15,6 +15,7 @@ const getGitAuthUrl = require('./lib/get-git-auth-url');
const logger = require('./lib/logger');
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) {
@@ -48,7 +56,7 @@ async function run(options, plugins) {
options.repositoryUrl = await getGitAuthUrl(options);
if (!await isBranchUpToDate(options.branch)) {
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
@@ -56,7 +64,10 @@ async function run(options, plugins) {
return false;
}
if (!await verifyAuth(options.repositoryUrl, options.branch)) {
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});
}
+9
View File
@@ -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};
+1 -1
View File
@@ -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}));
+1 -1
View File
@@ -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
View File
@@ -1 +0,0 @@
module.exports = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'];
+12 -1
View File
@@ -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 = {};
+4 -1
View File
@@ -43,7 +43,9 @@ 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)) {
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);
@@ -52,5 +54,6 @@ module.exports = async ({repositoryUrl, branch}) => {
// 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, protocols: [protocol], user: gitCredentials}.toString(protocol) : repositoryUrl;
}
return repositoryUrl;
};
+1
View File
@@ -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.
*/
+2 -1
View File
@@ -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);
}
+12 -5
View File
@@ -36,9 +36,15 @@ 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;
}
}
@@ -86,13 +92,14 @@ async function isGitRepo() {
* @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(repositoryUrl, branch) {
try {
return (await execa('git', ['push', '--dry-run', repositoryUrl, `HEAD:${branch}`])).code === 0;
await execa('git', ['push', '--dry-run', repositoryUrl, `HEAD:${branch}`]);
} catch (err) {
debug(err);
throw err;
}
}
@@ -136,10 +143,10 @@ async function verifyTagName(tagName) {
*
* @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, `false` otherwise.
* @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', `${branch}@{u}`]));
return isRefInHistory(await execa.stdout('git', ['rev-parse', `origin/${branch}`]));
}
module.exports = {
+15 -16
View File
@@ -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
View File
@@ -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}));
}
+4 -3
View File
@@ -26,7 +26,7 @@
"@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": "^2.0.0",
"execa": "^0.10.0",
@@ -64,7 +64,7 @@
"proxyquire": "^2.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
View File
@@ -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 => {
+1
View File
@@ -91,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 => {
+1 -1
View File
@@ -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') {
-4
View File
@@ -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();
-4
View File
@@ -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();
-4
View File
@@ -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();
+7
View File
@@ -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,
@@ -157,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 => {