Compare commits
@@ -30,9 +30,9 @@ execa
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
.catch(error => {
|
||||
console.error(`[semantic-release]: Git version ${MIN_GIT_VERSION} is required. No git binary found.`);
|
||||
console.error(err);
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Usage:
|
||||
.option('verify-conditions', {...stringList, group: 'Plugins'})
|
||||
.option('analyze-commits', {type: 'string', group: 'Plugins'})
|
||||
.option('verify-release', {...stringList, group: 'Plugins'})
|
||||
.option('generate-notes', {type: 'string', group: 'Plugins'})
|
||||
.option('generate-notes', {...stringList, group: 'Plugins'})
|
||||
.option('prepare', {...stringList, group: 'Plugins'})
|
||||
.option('publish', {...stringList, group: 'Plugins'})
|
||||
.option('success', {...stringList, group: 'Plugins'})
|
||||
@@ -57,9 +57,9 @@ Usage:
|
||||
}
|
||||
await require('.')(opts);
|
||||
return 0;
|
||||
} catch (err) {
|
||||
if (err.name !== 'YError') {
|
||||
stderr.write(hideSensitive(env)(util.inspect(err, {colors: true})));
|
||||
} catch (error) {
|
||||
if (error.name !== 'YError') {
|
||||
stderr.write(hideSensitive(env)(util.inspect(error, {colors: true})));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -53,3 +53,7 @@
|
||||
- [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.
|
||||
- [semantic-release-firefox](https://github.com/felixfbecker/semantic-release-firefox) Set of semantic-release plugins for publishing a Firefox extension release.
|
||||
- [verifyConditions](https://github.com/felixfbecker/semantic-release-firefox#verifyconditions) Verify the presence of the authentication (set via environment variables).
|
||||
- [prepare](https://github.com/felixfbecker/semantic-release-firefox#prepare) Write the correct version to the manifest.json, creates a xpi file of the dist folder and a zip of the sources.
|
||||
- [publish](https://github.com/felixfbecker/semantic-release-firefox#publish) Submit the generated archives to the webstore for review, and publish the item including release notes.
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ See the [CI configuration recipes](../recipes/README.md#ci-configurations) for m
|
||||
|
||||
## Can I run semantic-release on my local machine rather than on a CI server?
|
||||
|
||||
Yes, you can by explicitly setting the [`--no-ci` CLI option](../usage/configuration.md#options) option. You will also have to set the required [authentication](../usage/ci-configuration.md#authentication) via environment variables on your local machine, for example:
|
||||
Yes, you can by explicitly setting the [`--no-ci` CLI option](../usage/configuration.md#ci) option. You will also have to set the required [authentication](../usage/ci-configuration.md#authentication) via environment variables on your local machine, for example:
|
||||
|
||||
```bash
|
||||
$ NPM_TOKEN=<your_npm_token> GH_TOKEN=<your_github_token> npx semantic-release --no-ci
|
||||
|
||||
@@ -10,14 +10,12 @@ 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). |
|
||||
| `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` must be the Git username and password in the format `<username>:<password>`.
|
||||
| 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](https://en.wikipedia.org/wiki/Percent-encoding) Git username and password in the format `<username>:<password>`. The username and password must each be individually URL encoded, not the `:` separating them. |
|
||||
|
||||
Alternatively the Git authentication can be set up via [SSH keys](../recipes/git-auth-ssh-keys.md).
|
||||
|
||||
|
||||
@@ -100,13 +100,15 @@ CLI arguments: `-d`, `--dry-run`
|
||||
|
||||
Dry-run mode, skip publishing, print next version and release notes.
|
||||
|
||||
### noCi
|
||||
### ci
|
||||
|
||||
Type: `Boolean`<br>
|
||||
Default: `false`<br>
|
||||
CLI arguments: `--no-ci`
|
||||
Default: `true`<br>
|
||||
CLI arguments: `--ci` / `--no-ci`
|
||||
|
||||
Skip Continuous Integration environment verifications. This allows for making releases from a local machine.
|
||||
Set to `fasle` to skip Continuous Integration environment verifications. This allows for making releases from a local machine.
|
||||
|
||||
**Note**: The CLI arguments `--no-ci` is equivalent to `--ci false`.
|
||||
|
||||
### debug
|
||||
|
||||
@@ -128,7 +130,7 @@ See [Plugins configuration](plugins.md#configuration) for more details.
|
||||
|
||||
### analyzeCommits
|
||||
|
||||
Type: `Array`, `String`, `Object`<br>
|
||||
Type: `String`, `Object`<br>
|
||||
Default: `'@semantic-release/commit-analyzer'`<br>
|
||||
CLI argument: `--analyze-commits`
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const process = require('process');
|
||||
const {template, pick} = require('lodash');
|
||||
const marked = require('marked');
|
||||
const TerminalRenderer = require('marked-terminal');
|
||||
@@ -61,14 +60,14 @@ async function run(context, plugins) {
|
||||
|
||||
try {
|
||||
await verifyAuth(options.repositoryUrl, options.branch, {cwd, env});
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
if (!(await isBranchUpToDate(options.branch, {cwd, env}))) {
|
||||
logger.log(
|
||||
`The local branch ${options.branch} is behind the remote one, therefore a new version won't be published.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
logger.error(`The command "${err.cmd}" failed with the error message ${err.stderr}.`);
|
||||
logger.error(`The command "${error.cmd}" failed with the error message ${error.stderr}.`);
|
||||
throw getError('EGITNOPERMISSION', {options});
|
||||
}
|
||||
|
||||
@@ -132,13 +131,13 @@ function logErrors({logger, stderr}, err) {
|
||||
}
|
||||
}
|
||||
|
||||
async function callFail(context, plugins, error) {
|
||||
const errors = extractErrors(error).filter(error => error.semanticRelease);
|
||||
async function callFail(context, plugins, err) {
|
||||
const errors = extractErrors(err).filter(err => err.semanticRelease);
|
||||
if (errors.length > 0) {
|
||||
try {
|
||||
await plugins.fail({...context, errors});
|
||||
} catch (err) {
|
||||
logErrors(context, err);
|
||||
} catch (error) {
|
||||
logErrors(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,15 +157,15 @@ module.exports = async (opts = {}, {cwd = process.cwd(), env = process.env, stdo
|
||||
const result = await run(context, plugins);
|
||||
unhook();
|
||||
return result;
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
if (!options.dryRun) {
|
||||
await callFail(context, plugins, err);
|
||||
await callFail(context, plugins, error);
|
||||
}
|
||||
throw err;
|
||||
throw error;
|
||||
}
|
||||
} catch (err) {
|
||||
logErrors(context, err);
|
||||
} catch (error) {
|
||||
logErrors(context, error);
|
||||
unhook();
|
||||
throw err;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,4 +10,14 @@ const RELEASE_NOTES_SEPARATOR = '\n\n';
|
||||
|
||||
const SECRET_REPLACEMENT = '[secure]';
|
||||
|
||||
module.exports = {RELEASE_TYPE, FIRST_RELEASE, COMMIT_NAME, COMMIT_EMAIL, RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT};
|
||||
const SECRET_MIN_SIZE = 5;
|
||||
|
||||
module.exports = {
|
||||
RELEASE_TYPE,
|
||||
FIRST_RELEASE,
|
||||
COMMIT_NAME,
|
||||
COMMIT_EMAIL,
|
||||
RELEASE_NOTES_SEPARATOR,
|
||||
SECRET_REPLACEMENT,
|
||||
SECRET_MIN_SIZE,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const {isString, isPlainObject} = require('lodash');
|
||||
const {gitHead} = require('../git');
|
||||
const hideSensitive = require('../hide-sensitive');
|
||||
const {hideSensitiveValues} = require('../utils');
|
||||
const {RELEASE_TYPE, RELEASE_NOTES_SEPARATOR} = require('./constants');
|
||||
|
||||
module.exports = {
|
||||
@@ -40,7 +42,7 @@ module.exports = {
|
||||
},
|
||||
}),
|
||||
}),
|
||||
postprocess: results => results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR),
|
||||
postprocess: (results, {env}) => hideSensitive(env)(results.filter(Boolean).join(RELEASE_NOTES_SEPARATOR)),
|
||||
},
|
||||
prepare: {
|
||||
default: ['@semantic-release/npm'],
|
||||
@@ -80,11 +82,13 @@ module.exports = {
|
||||
multiple: true,
|
||||
required: false,
|
||||
pipelineConfig: () => ({settleAll: true}),
|
||||
preprocess: ({releases, env, ...inputs}) => ({...inputs, env, releases: hideSensitiveValues(env, releases)}),
|
||||
},
|
||||
fail: {
|
||||
default: ['@semantic-release/github'],
|
||||
multiple: true,
|
||||
required: false,
|
||||
pipelineConfig: () => ({settleAll: true}),
|
||||
preprocess: ({errors, env, ...inputs}) => ({...inputs, env, errors: hideSensitiveValues(env, errors)}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ module.exports = async ({cwd, env, options: {repositoryUrl, branch}}) => {
|
||||
// Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
|
||||
try {
|
||||
await verifyAuth(repositoryUrl, branch, {cwd, env});
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
const envVar = Object.keys(GIT_TOKENS).find(envVar => !isUndefined(env[envVar]));
|
||||
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar] || ''}`;
|
||||
const {protocols, ...parsed} = gitUrlParse(repositoryUrl);
|
||||
|
||||
+19
-19
@@ -12,8 +12,8 @@ const debug = require('debug')('semantic-release:git');
|
||||
async function gitTagHead(tagName, execaOpts) {
|
||||
try {
|
||||
return await execa.stdout('git', ['rev-list', '-1', tagName], execaOpts);
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
} catch (error) {
|
||||
debug(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,13 @@ async function isRefInHistory(ref, execaOpts) {
|
||||
try {
|
||||
await execa('git', ['merge-base', '--is-ancestor', ref, 'HEAD'], execaOpts);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.code === 1) {
|
||||
} catch (error) {
|
||||
if (error.code === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
debug(err);
|
||||
throw err;
|
||||
debug(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ async function isRefInHistory(ref, execaOpts) {
|
||||
async function fetch(repositoryUrl, execaOpts) {
|
||||
try {
|
||||
await execa('git', ['fetch', '--unshallow', '--tags', repositoryUrl], execaOpts);
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
await execa('git', ['fetch', '--tags', repositoryUrl], execaOpts);
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ async function fetch(repositoryUrl, execaOpts) {
|
||||
*
|
||||
* @return {string} the sha of the HEAD commit.
|
||||
*/
|
||||
async function gitHead(execaOpts) {
|
||||
function gitHead(execaOpts) {
|
||||
return execa.stdout('git', ['rev-parse', 'HEAD'], execaOpts);
|
||||
}
|
||||
|
||||
@@ -89,8 +89,8 @@ async function gitHead(execaOpts) {
|
||||
async function repoUrl(execaOpts) {
|
||||
try {
|
||||
return await execa.stdout('git', ['config', '--get', 'remote.origin.url'], execaOpts);
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
} catch (error) {
|
||||
debug(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ async function repoUrl(execaOpts) {
|
||||
async function isGitRepo(execaOpts) {
|
||||
try {
|
||||
return (await execa('git', ['rev-parse', '--git-dir'], execaOpts)).code === 0;
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
} catch (error) {
|
||||
debug(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,9 +121,9 @@ async function isGitRepo(execaOpts) {
|
||||
async function verifyAuth(repositoryUrl, branch, execaOpts) {
|
||||
try {
|
||||
await execa('git', ['push', '--dry-run', repositoryUrl, `HEAD:${branch}`], execaOpts);
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
throw err;
|
||||
} catch (error) {
|
||||
debug(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,8 +163,8 @@ async function push(repositoryUrl, branch, execaOpts) {
|
||||
async function verifyTagName(tagName, execaOpts) {
|
||||
try {
|
||||
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`], execaOpts)).code === 0;
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
} catch (error) {
|
||||
debug(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,8 +182,8 @@ async function isBranchUpToDate(branch, execaOpts) {
|
||||
(await execa.stdout('git', ['ls-remote', '--heads', 'origin', branch], execaOpts)).match(/^(\w+)?/)[1],
|
||||
execaOpts
|
||||
);
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
} catch (error) {
|
||||
debug(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const {escapeRegExp} = require('lodash');
|
||||
const {SECRET_REPLACEMENT} = require('./definitions/constants');
|
||||
const {escapeRegExp, size, isString} = require('lodash');
|
||||
const {SECRET_REPLACEMENT, SECRET_MIN_SIZE} = require('./definitions/constants');
|
||||
|
||||
module.exports = env => {
|
||||
const toReplace = Object.keys(env).filter(
|
||||
envVar => /token|password|credential|secret|private/i.test(envVar) && env[envVar].trim()
|
||||
envVar => /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE
|
||||
);
|
||||
|
||||
const regexp = new RegExp(toReplace.map(envVar => escapeRegExp(env[envVar])).join('|'), 'g');
|
||||
return output => (output && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output);
|
||||
return output =>
|
||||
output && isString(output) && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
|
||||
};
|
||||
|
||||
@@ -36,7 +36,10 @@ module.exports = (context, pluginsPath) => {
|
||||
);
|
||||
|
||||
plugins[type] = async input =>
|
||||
postprocess(await pipeline(steps, pipelineConfig && pipelineConfig(plugins, logger))(await preprocess(input)));
|
||||
postprocess(
|
||||
await pipeline(steps, pipelineConfig && pipelineConfig(plugins, logger))(await preprocess(input)),
|
||||
input
|
||||
);
|
||||
|
||||
return plugins;
|
||||
},
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const {dirname} = require('path');
|
||||
const {isString, isPlainObject, isFunction, noop, cloneDeep} = require('lodash');
|
||||
const {isString, isPlainObject, isFunction, noop, cloneDeep, omit} = require('lodash');
|
||||
const resolveFrom = require('resolve-from');
|
||||
const getError = require('../get-error');
|
||||
const {extractErrors} = require('../utils');
|
||||
const PLUGINS_DEFINITIONS = require('../definitions/plugins');
|
||||
|
||||
module.exports = ({cwd, options, logger}, type, pluginOpt, pluginsPath) => {
|
||||
module.exports = ({cwd, stdout, stderr, options, logger}, type, pluginOpt, pluginsPath) => {
|
||||
if (!pluginOpt) {
|
||||
return noop;
|
||||
}
|
||||
@@ -31,16 +31,21 @@ module.exports = ({cwd, options, logger}, type, pluginOpt, pluginsPath) => {
|
||||
const {outputValidator} = PLUGINS_DEFINITIONS[type] || {};
|
||||
try {
|
||||
logger.log(`Start step "${type}" of plugin "${pluginName}"`);
|
||||
const result = await func({...cloneDeep(input), logger: logger.scope(logger.scopeName, pluginName)});
|
||||
const result = await func({
|
||||
...cloneDeep(omit(input, ['stdout', 'stderr', 'logger'])),
|
||||
stdout,
|
||||
stderr,
|
||||
logger: logger.scope(logger.scopeName, pluginName),
|
||||
});
|
||||
if (outputValidator && !outputValidator(result)) {
|
||||
throw getError(`E${type.toUpperCase()}OUTPUT`, {result, pluginName});
|
||||
}
|
||||
logger.success(`Completed step "${type}" of plugin "${pluginName}"`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
logger.error(`Failed step "${type}" of plugin "${pluginName}"`);
|
||||
extractErrors(err).forEach(err => Object.assign(err, {pluginName}));
|
||||
throw err;
|
||||
extractErrors(error).forEach(err => Object.assign(err, {pluginName}));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -36,12 +36,12 @@ module.exports = (steps, {settleAll = false, getNextInput = identity, transform
|
||||
// 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);
|
||||
results.push(result);
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
if (settleAll) {
|
||||
errors.push(...extractErrors(err));
|
||||
result = err;
|
||||
errors.push(...extractErrors(error));
|
||||
result = error;
|
||||
} else {
|
||||
throw err;
|
||||
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
|
||||
|
||||
+14
-1
@@ -1,7 +1,20 @@
|
||||
const {isFunction} = require('lodash');
|
||||
const hideSensitive = require('./hide-sensitive');
|
||||
|
||||
function extractErrors(err) {
|
||||
return err && isFunction(err[Symbol.iterator]) ? [...err] : [err];
|
||||
}
|
||||
|
||||
module.exports = {extractErrors};
|
||||
function hideSensitiveValues(env, objs) {
|
||||
const hideFunction = hideSensitive(env);
|
||||
return objs.map(obj => {
|
||||
Object.getOwnPropertyNames(obj).forEach(prop => {
|
||||
if (obj[prop]) {
|
||||
obj[prop] = hideFunction(obj[prop]);
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {extractErrors, hideSensitiveValues};
|
||||
|
||||
+8
-7
@@ -22,22 +22,22 @@
|
||||
"@semantic-release/commit-analyzer": "^6.0.0",
|
||||
"@semantic-release/error": "^2.2.0",
|
||||
"@semantic-release/github": "^5.0.0",
|
||||
"@semantic-release/npm": "^4.0.1",
|
||||
"@semantic-release/npm": "^5.0.1",
|
||||
"@semantic-release/release-notes-generator": "^7.0.0",
|
||||
"aggregate-error": "^1.0.0",
|
||||
"cosmiconfig": "^5.0.1",
|
||||
"debug": "^3.1.0",
|
||||
"env-ci": "^2.0.0",
|
||||
"execa": "^0.10.0",
|
||||
"execa": "^1.0.0",
|
||||
"figures": "^2.0.0",
|
||||
"find-versions": "^2.0.0",
|
||||
"get-stream": "^3.0.0",
|
||||
"get-stream": "^4.0.0",
|
||||
"git-log-parser": "^1.2.0",
|
||||
"git-url-parse": "^10.0.1",
|
||||
"hook-std": "^1.1.0",
|
||||
"hosted-git-info": "^2.7.1",
|
||||
"lodash": "^4.17.4",
|
||||
"marked": "^0.4.0",
|
||||
"marked": "^0.5.0",
|
||||
"marked-terminal": "^3.0.0",
|
||||
"p-locate": "^3.0.0",
|
||||
"p-reduce": "^1.0.0",
|
||||
@@ -53,11 +53,11 @@
|
||||
"codecov": "^3.0.0",
|
||||
"commitizen": "^2.9.6",
|
||||
"cz-conventional-changelog": "^2.0.0",
|
||||
"delay": "^3.0.0",
|
||||
"delay": "^4.0.0",
|
||||
"dockerode": "^2.5.2",
|
||||
"file-url": "^2.0.2",
|
||||
"fs-extra": "^7.0.0",
|
||||
"got": "^8.0.0",
|
||||
"got": "^9.0.0",
|
||||
"js-yaml": "^3.10.0",
|
||||
"mockserver-client": "^5.1.1",
|
||||
"nock": "^9.0.2",
|
||||
@@ -65,8 +65,9 @@
|
||||
"p-retry": "^2.0.0",
|
||||
"proxyquire": "^2.0.0",
|
||||
"sinon": "^6.0.0",
|
||||
"stream-buffers": "^3.0.2",
|
||||
"tempy": "^0.2.1",
|
||||
"xo": "^0.21.0"
|
||||
"xo": "^0.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.3"
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ test.serial('Pass options to semantic-release API', async t => {
|
||||
t.deepEqual(run.args[0][0].verifyConditions, ['condition1', 'condition2']);
|
||||
t.is(run.args[0][0].analyzeCommits, 'analyze');
|
||||
t.deepEqual(run.args[0][0].verifyRelease, ['verify1', 'verify2']);
|
||||
t.is(run.args[0][0].generateNotes, 'notes');
|
||||
t.deepEqual(run.args[0][0].generateNotes, ['notes']);
|
||||
t.deepEqual(run.args[0][0].prepare, ['prepare1', 'prepare2']);
|
||||
t.deepEqual(run.args[0][0].publish, ['publish1', 'publish2']);
|
||||
t.deepEqual(run.args[0][0].success, ['success1', 'success2']);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import test from 'ava';
|
||||
import plugins from '../../lib/definitions/plugins';
|
||||
import {RELEASE_NOTES_SEPARATOR} from '../../lib/definitions/constants';
|
||||
import {RELEASE_NOTES_SEPARATOR, SECRET_REPLACEMENT} from '../../lib/definitions/constants';
|
||||
|
||||
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', t => {
|
||||
t.false(plugins.analyzeCommits.outputValidator('invalid'));
|
||||
@@ -32,10 +32,22 @@ test('The "publish" plugin output, if defined, must be an object', t => {
|
||||
t.true(plugins.publish.outputValidator(''));
|
||||
});
|
||||
|
||||
test('The "generateNotes" plugins output are concatenated with separator', t => {
|
||||
t.is(plugins.generateNotes.postprocess(['note 1', 'note 2']), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
||||
t.is(plugins.generateNotes.postprocess(['', 'note']), 'note');
|
||||
t.is(plugins.generateNotes.postprocess([undefined, 'note']), 'note');
|
||||
t.is(plugins.generateNotes.postprocess(['note 1', '', 'note 2']), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
||||
t.is(plugins.generateNotes.postprocess(['note 1', undefined, 'note 2']), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
||||
test('The "generateNotes" plugins output are concatenated with separator and sensitive data is hidden', t => {
|
||||
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'], {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', undefined, 'note 2'], {env}),
|
||||
`note 1${RELEASE_NOTES_SEPARATOR}note 2`
|
||||
);
|
||||
|
||||
t.is(
|
||||
plugins.generateNotes.postprocess(
|
||||
[`Note 1: Exposing token ${env.MY_TOKEN}`, `Note 2: Exposing token ${SECRET_REPLACEMENT}`],
|
||||
{env}
|
||||
),
|
||||
`Note 1: Exposing token ${SECRET_REPLACEMENT}${RELEASE_NOTES_SEPARATOR}Note 2: Exposing token ${SECRET_REPLACEMENT}`
|
||||
);
|
||||
});
|
||||
|
||||
Vendored
-1
@@ -1,2 +1 @@
|
||||
module.exports = () => {};
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
const SemanticReleaseError = require('@semantic-release/error');
|
||||
|
||||
class InheritedError extends SemanticReleaseError {
|
||||
constructor(message, code, newProperty) {
|
||||
constructor(message, code) {
|
||||
super(message);
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
this.name = this.constructor.name;
|
||||
|
||||
@@ -69,7 +69,7 @@ export async function initBareRepo(repositoryUrl, branch = 'master') {
|
||||
* @returns {Array<Commit>} The created commits, in reverse order (to match `git log` order).
|
||||
*/
|
||||
export async function gitCommits(messages, execaOpts) {
|
||||
await pReduce(messages, async (_, message) =>
|
||||
await pReduce(messages, (_, message) =>
|
||||
execa.stdout('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOpts)
|
||||
);
|
||||
return (await gitGetCommits(undefined, execaOpts)).slice(0, messages.length);
|
||||
@@ -112,7 +112,7 @@ export async function gitCheckout(branch, create = true, execaOpts) {
|
||||
*
|
||||
* @return {String} The sha of the head commit in the current git repository.
|
||||
*/
|
||||
export async function gitHead(execaOpts) {
|
||||
export function gitHead(execaOpts) {
|
||||
return execa.stdout('git', ['rev-parse', 'HEAD'], execaOpts);
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ export async function gitAddConfig(name, value, execaOpts) {
|
||||
*
|
||||
* @return {String} The sha of the commit associated with `tagName` on the local repository.
|
||||
*/
|
||||
export async function gitTagHead(tagName, execaOpts) {
|
||||
export function gitTagHead(tagName, execaOpts) {
|
||||
return execa.stdout('git', ['rev-list', '-1', tagName], execaOpts);
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ export async function gitRemoteTagHead(repositoryUrl, tagName, execaOpts) {
|
||||
*
|
||||
* @return {String} The tag associatedwith the sha in parameter or `null`.
|
||||
*/
|
||||
export async function gitCommitTag(gitHead, execaOpts) {
|
||||
export function gitCommitTag(gitHead, execaOpts) {
|
||||
return execa.stdout('git', ['describe', '--tags', '--exact-match', gitHead], execaOpts);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ async function start() {
|
||||
minTimeout: 1000,
|
||||
factor: 2,
|
||||
});
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
throw new Error(`Couldn't start mock-server after 2 min`);
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ async function mock(
|
||||
* @param {Object} expectation The expectation created with `mock` function.
|
||||
* @return {Promise} A Promise that resolves if the expectation is met or reject otherwise.
|
||||
*/
|
||||
async function verify(expectation) {
|
||||
function verify(expectation) {
|
||||
return client.verify(expectation);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ async function start() {
|
||||
minTimeout: 1000,
|
||||
factor: 2,
|
||||
});
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
throw new Error(`Couldn't start npm-registry-docker after 2 min`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import test from 'ava';
|
||||
import {repeat} from 'lodash';
|
||||
import hideSensitive from '../lib/hide-sensitive';
|
||||
import {SECRET_REPLACEMENT, SECRET_MIN_SIZE} from '../lib/definitions/constants';
|
||||
|
||||
test('Replace multiple sensitive environment variable values', t => {
|
||||
const env = {SOME_PASSWORD: 'password', SOME_TOKEN: 'secret'};
|
||||
t.is(
|
||||
hideSensitive(env)(`https://user:${env.SOME_PASSWORD}@host.com?token=${env.SOME_TOKEN}`),
|
||||
'https://user:[secure]@host.com?token=[secure]'
|
||||
`https://user:${SECRET_REPLACEMENT}@host.com?token=${SECRET_REPLACEMENT}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -13,13 +15,16 @@ test('Replace multiple occurences of sensitive environment variable values', t =
|
||||
const env = {secretKey: 'secret'};
|
||||
t.is(
|
||||
hideSensitive(env)(`https://user:${env.secretKey}@host.com?token=${env.secretKey}`),
|
||||
'https://user:[secure]@host.com?token=[secure]'
|
||||
`https://user:${SECRET_REPLACEMENT}@host.com?token=${SECRET_REPLACEMENT}`
|
||||
);
|
||||
});
|
||||
|
||||
test('Escape regexp special characters', t => {
|
||||
const env = {SOME_CREDENTIALS: 'p$^{.+}\\w[a-z]o.*rd'};
|
||||
t.is(hideSensitive(env)(`https://user:${env.SOME_CREDENTIALS}@host.com`), 'https://user:[secure]@host.com');
|
||||
t.is(
|
||||
hideSensitive(env)(`https://user:${env.SOME_CREDENTIALS}@host.com`),
|
||||
`https://user:${SECRET_REPLACEMENT}@host.com`
|
||||
);
|
||||
});
|
||||
|
||||
test('Accept "undefined" input', t => {
|
||||
@@ -34,10 +39,20 @@ test('Exclude empty environment variables from the regexp', t => {
|
||||
const env = {SOME_PASSWORD: 'password', SOME_TOKEN: ''};
|
||||
t.is(
|
||||
hideSensitive(env)(`https://user:${env.SOME_PASSWORD}@host.com?token=`),
|
||||
'https://user:[secure]@host.com?token='
|
||||
`https://user:${SECRET_REPLACEMENT}@host.com?token=`
|
||||
);
|
||||
});
|
||||
|
||||
test('Exclude empty environment variables from the regexp if there is only empty ones', t => {
|
||||
t.is(hideSensitive({SOME_PASSWORD: '', SOME_TOKEN: ' \n '})(`https://host.com?token=`), 'https://host.com?token=');
|
||||
});
|
||||
|
||||
test('Exclude environment variables with value shorter than SECRET_MIN_SIZE from the regexp', t => {
|
||||
const SHORT_TOKEN = repeat('a', SECRET_MIN_SIZE - 1);
|
||||
const LONG_TOKEN = repeat('b', SECRET_MIN_SIZE);
|
||||
const env = {SHORT_TOKEN, LONG_TOKEN};
|
||||
t.is(
|
||||
hideSensitive(env)(`https://user:${SHORT_TOKEN}@host.com?token=${LONG_TOKEN}`),
|
||||
`https://user:${SHORT_TOKEN}@host.com?token=${SECRET_REPLACEMENT}`
|
||||
);
|
||||
});
|
||||
|
||||
+199
-24
@@ -1,9 +1,11 @@
|
||||
import test from 'ava';
|
||||
import {escapeRegExp, isString} from 'lodash';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {spy, stub} from 'sinon';
|
||||
import {WritableStreamBuffer} from 'stream-buffers';
|
||||
import AggregateError from 'aggregate-error';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
import {COMMIT_NAME, COMMIT_EMAIL} from '../lib/definitions/constants';
|
||||
import {COMMIT_NAME, COMMIT_EMAIL, SECRET_REPLACEMENT} from '../lib/definitions/constants';
|
||||
import {
|
||||
gitHead as getGitHead,
|
||||
gitTagHead,
|
||||
@@ -78,8 +80,8 @@ test('Plugins are called with expected values', async t => {
|
||||
const result = await semanticRelease(options, {
|
||||
cwd,
|
||||
env,
|
||||
stdout: {write: () => {}},
|
||||
stderr: {write: () => {}},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
});
|
||||
|
||||
t.is(verifyConditions1.callCount, 1);
|
||||
@@ -215,7 +217,14 @@ test('Use custom tag format', async t => {
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.truthy(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
|
||||
// Verify the tag has been created on the local and remote repo and reference the gitHead
|
||||
t.is(await gitTagHead(nextRelease.gitTag, {cwd}), nextRelease.gitHead);
|
||||
@@ -260,7 +269,14 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
|
||||
t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.truthy(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
|
||||
t.is(generateNotes.callCount, 2);
|
||||
t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease);
|
||||
@@ -318,7 +334,9 @@ test('Call all "success" plugins even if one errors out', async t => {
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
|
||||
await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
await t.throws(
|
||||
semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()})
|
||||
);
|
||||
|
||||
t.is(success1.callCount, 1);
|
||||
t.deepEqual(success1.args[0][1].releases, [{...release, ...nextRelease, notes, pluginName: '[Function: proxy]'}]);
|
||||
@@ -350,7 +368,9 @@ test('Log all "verifyConditions" errors', async t => {
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
const errors = [
|
||||
...(await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))),
|
||||
...(await t.throws(
|
||||
semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()})
|
||||
)),
|
||||
];
|
||||
|
||||
t.deepEqual(errors, [error1, error2, error3]);
|
||||
@@ -396,7 +416,9 @@ test('Log all "verifyRelease" errors', async t => {
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
const errors = [
|
||||
...(await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))),
|
||||
...(await t.throws(
|
||||
semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()})
|
||||
)),
|
||||
];
|
||||
|
||||
t.deepEqual(errors, [error1, error2]);
|
||||
@@ -445,7 +467,14 @@ test('Dry-run skips publish and success', async t => {
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.truthy(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
|
||||
t.not(t.context.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.');
|
||||
t.is(verifyConditions.callCount, 1);
|
||||
@@ -484,7 +513,9 @@ test('Dry-run skips fail', async t => {
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
const errors = [
|
||||
...(await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))),
|
||||
...(await t.throws(
|
||||
semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()})
|
||||
)),
|
||||
];
|
||||
|
||||
t.deepEqual(errors, [error1, error2]);
|
||||
@@ -532,7 +563,14 @@ test('Force a dry-run if not on a CI and "noCi" is not explicitly set', async t
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: false, branch: 'master'}),
|
||||
});
|
||||
t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.truthy(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
|
||||
t.is(t.context.log.args[1][0], 'This run was not triggered in a known CI environment, running in dry-run mode.');
|
||||
t.is(verifyConditions.callCount, 1);
|
||||
@@ -575,7 +613,14 @@ test('Dry-run does not print changelog if "generateNotes" return "undefined"', a
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.truthy(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
|
||||
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['Release note for version 2.0.0:']);
|
||||
});
|
||||
@@ -619,7 +664,14 @@ test('Allow local releases with "noCi" option', async t => {
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: false, branch: 'master', isPr: true}),
|
||||
});
|
||||
t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.truthy(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
|
||||
t.not(t.context.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.');
|
||||
t.not(
|
||||
@@ -671,7 +723,14 @@ test('Accept "undefined" value returned by the "generateNotes" plugins', async t
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.truthy(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
|
||||
t.is(analyzeCommits.callCount, 1);
|
||||
t.deepEqual(analyzeCommits.args[0][1].lastRelease, lastRelease);
|
||||
@@ -700,7 +759,10 @@ test('Returns false if triggered by a PR', async t => {
|
||||
});
|
||||
|
||||
t.false(
|
||||
await semanticRelease({cwd, repositoryUrl}, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}})
|
||||
await semanticRelease(
|
||||
{cwd, repositoryUrl},
|
||||
{cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}
|
||||
)
|
||||
);
|
||||
t.is(
|
||||
t.context.log.args[t.context.log.args.length - 1][0],
|
||||
@@ -728,7 +790,7 @@ test('Returns false if triggered on an outdated clone', async t => {
|
||||
t.false(
|
||||
await semanticRelease(
|
||||
{repositoryUrl},
|
||||
{cwd: repoDir, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}
|
||||
{cwd: repoDir, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}
|
||||
)
|
||||
);
|
||||
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], [
|
||||
@@ -757,7 +819,14 @@ test('Returns false if not running from the configured branch', async t => {
|
||||
'env-ci': () => ({isCi: true, branch: 'other-branch', isPr: false}),
|
||||
});
|
||||
|
||||
t.false(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.false(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
t.is(
|
||||
t.context.log.args[1][0],
|
||||
'This test run was triggered on the branch other-branch, while semantic-release is configured to only publish from master, therefore a new version won’t be published.'
|
||||
@@ -794,7 +863,14 @@ test('Returns false if there is no relevant changes', async t => {
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
|
||||
t.false(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.false(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
t.is(analyzeCommits.callCount, 1);
|
||||
t.is(verifyRelease.callCount, 0);
|
||||
t.is(generateNotes.callCount, 0);
|
||||
@@ -841,7 +917,12 @@ test('Exclude commits with [skip release] or [release skip] from analysis', asyn
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
});
|
||||
|
||||
t.is(analyzeCommits.callCount, 1);
|
||||
t.is(analyzeCommits.args[0][1].commits.length, 2);
|
||||
@@ -865,7 +946,9 @@ test('Log both plugins errors and errors thrown by "fail" plugin', async t => {
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
|
||||
await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
await t.throws(
|
||||
semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()})
|
||||
);
|
||||
|
||||
t.is(t.context.error.args[t.context.error.args.length - 1][0], 'ERR Plugin error');
|
||||
t.is(t.context.error.args[t.context.error.args.length - 3][1], failError1);
|
||||
@@ -888,7 +971,9 @@ test('Call "fail" only if a plugin returns a SemanticReleaseError', async t => {
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
|
||||
await t.throws(semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
await t.throws(
|
||||
semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()})
|
||||
);
|
||||
|
||||
t.true(fail.notCalled);
|
||||
t.is(t.context.error.args[t.context.error.args.length - 1][1], pluginError);
|
||||
@@ -903,7 +988,9 @@ test('Throw SemanticReleaseError if repositoryUrl is not set and cannot be found
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
const errors = [
|
||||
...(await t.throws(semanticRelease({}, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}))),
|
||||
...(await t.throws(
|
||||
semanticRelease({}, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()})
|
||||
)),
|
||||
];
|
||||
|
||||
// Verify error code and type
|
||||
@@ -939,12 +1026,93 @@ test('Throw an Error if plugin returns an unexpected value', async t => {
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
const error = await t.throws(
|
||||
semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}),
|
||||
semanticRelease(options, {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}),
|
||||
Error
|
||||
);
|
||||
t.regex(error.details, /string/);
|
||||
});
|
||||
|
||||
test('Hide sensitive information passed to "fail" plugin', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
|
||||
const fail = stub().resolves();
|
||||
const env = {MY_TOKEN: 'secret token'};
|
||||
const options = {
|
||||
branch: 'master',
|
||||
repositoryUrl,
|
||||
verifyConditions: stub().throws(
|
||||
new SemanticReleaseError(
|
||||
`Message: Exposing token ${env.MY_TOKEN}`,
|
||||
'ERR',
|
||||
`Details: Exposing token ${env.MY_TOKEN}`
|
||||
)
|
||||
),
|
||||
success: stub().resolves(),
|
||||
fail,
|
||||
};
|
||||
|
||||
const semanticRelease = requireNoCache('..', {
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
await t.throws(
|
||||
semanticRelease(options, {cwd, env, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()}),
|
||||
Error
|
||||
);
|
||||
|
||||
const error = fail.args[0][1].errors[0];
|
||||
|
||||
t.is(error.message, `Message: Exposing token ${SECRET_REPLACEMENT}`);
|
||||
t.is(error.details, `Details: Exposing token ${SECRET_REPLACEMENT}`);
|
||||
|
||||
Object.getOwnPropertyNames(error).forEach(prop => {
|
||||
if (isString(error[prop])) {
|
||||
t.notRegex(error[prop], new RegExp(escapeRegExp(env.MY_TOKEN)));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('Hide sensitive information passed to "success" plugin', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
await gitCommits(['feat: initial release'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitCommits(['feat: new feature'], {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
|
||||
const success = stub().resolves();
|
||||
const env = {MY_TOKEN: 'secret token'};
|
||||
const options = {
|
||||
branch: 'master',
|
||||
repositoryUrl,
|
||||
verifyConditions: false,
|
||||
verifyRelease: false,
|
||||
prepare: false,
|
||||
publish: stub().resolves({
|
||||
name: `Name: Exposing token ${env.MY_TOKEN}`,
|
||||
url: `URL: Exposing token ${env.MY_TOKEN}`,
|
||||
}),
|
||||
success,
|
||||
fail: stub().resolves(),
|
||||
};
|
||||
|
||||
const semanticRelease = requireNoCache('..', {
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
await semanticRelease(options, {cwd, env, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()});
|
||||
|
||||
const release = success.args[0][1].releases[0];
|
||||
|
||||
t.is(release.name, `Name: Exposing token ${SECRET_REPLACEMENT}`);
|
||||
t.is(release.url, `URL: Exposing token ${SECRET_REPLACEMENT}`);
|
||||
|
||||
Object.getOwnPropertyNames(release).forEach(prop => {
|
||||
if (isString(release[prop])) {
|
||||
t.notRegex(release[prop], new RegExp(escapeRegExp(env.MY_TOKEN)));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('Get all commits including the ones not in the shallow clone', async t => {
|
||||
let {cwd, repositoryUrl} = await gitRepo(true);
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
@@ -974,7 +1142,14 @@ test('Get all commits including the ones not in the shallow clone', async t => {
|
||||
'./lib/get-logger': () => t.context.logger,
|
||||
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||
});
|
||||
t.truthy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
|
||||
t.truthy(
|
||||
await semanticRelease(options, {
|
||||
cwd,
|
||||
env: {},
|
||||
stdout: new WritableStreamBuffer(),
|
||||
stderr: new WritableStreamBuffer(),
|
||||
})
|
||||
);
|
||||
|
||||
t.is(analyzeCommits.args[0][1].commits.length, 3);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import test from 'ava';
|
||||
import {escapeRegExp} from 'lodash';
|
||||
import {writeJson, readJson} from 'fs-extra';
|
||||
import execa from 'execa';
|
||||
import {WritableStreamBuffer} from 'stream-buffers';
|
||||
import {SECRET_REPLACEMENT} from '../lib/definitions/constants';
|
||||
import {gitHead as getGitHead, gitTagHead, gitRepo, gitCommits, gitRemoteTagHead, gitPush} from './helpers/git-utils';
|
||||
import gitbox from './helpers/gitbox';
|
||||
@@ -490,7 +491,7 @@ test('Run via JS API', async t => {
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit'], {cwd});
|
||||
t.log('$ Call semantic-release via API');
|
||||
await semanticRelease(undefined, {cwd, env, stdout: {write: () => {}}, stderr: {write: () => {}}});
|
||||
await semanticRelease(undefined, {cwd, env, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()});
|
||||
|
||||
// Verify package.json and has been updated
|
||||
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
|
||||
|
||||
@@ -160,7 +160,10 @@ test('Plugin is called with "pluginConfig" (omitting "path", adding global confi
|
||||
await plugin({param: 'param'});
|
||||
|
||||
t.true(
|
||||
pluginFunction.calledWith({conf: 'confValue', global: 'globalValue'}, {param: 'param', logger: t.context.logger})
|
||||
pluginFunction.calledWithMatch(
|
||||
{conf: 'confValue', global: 'globalValue'},
|
||||
{param: 'param', logger: t.context.logger}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user