Compare commits

...
7 Commits
15 changed files with 222 additions and 196 deletions
+4 -2
View File
@@ -1,4 +1,6 @@
const {argv} = require('process');
const {argv, env, stderr} = require('process');
const util = require('util');
const hideSensitive = require('./lib/hide-sensitive');
const stringList = {
type: 'string',
@@ -57,7 +59,7 @@ Usage:
return 0;
} catch (err) {
if (err.name !== 'YError') {
console.error(err);
stderr.write(hideSensitive(env)(util.inspect(err, {colors: true})));
}
return 1;
}
+29 -58
View File
@@ -57,8 +57,7 @@ $ semantic-release
### extends
Type: `Array`, `String`
Type: `Array`, `String`<br>
CLI arguments: `-e`, `--extends`
List of modules or file paths containing a [shareable configuration](shareable-configurations.md). If multiple shareable configurations are set, they will be imported in the order defined with each configuration option taking precedence over the options defined in a previous shareable configuration.
@@ -67,20 +66,16 @@ List of modules or file paths containing a [shareable configuration](shareable-c
### branch
Type: `String`
Default: `master`
Type: `String`<br>
Default: `master`<br>
CLI arguments: `-b`, `--branch`
The branch on which releases should happen.
### repositoryUrl
Type: `String`
Default: `repository` property in `package.json` or [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes)
Type: `String`<br>
Default: `repository` property in `package.json` or [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes)<br>
CLI arguments: `-r`, `--repository-url`
The git repository URL.
@@ -89,10 +84,8 @@ Any valid git url format is supported (See [Git protocols](https://git-scm.com/b
### tagFormat
Type: `String`
Default: `v${version}`
Type: `String`<br>
Default: `v${version}`<br>
CLI arguments: `-t`, `--tag-format`
The [Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) format used by **semantic-release** to identify releases. The tag name is generated with [Lodash template](https://lodash.com/docs#template) and will be compiled with the `version` variable.
@@ -101,40 +94,32 @@ The [Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) format used by
### dryRun
Type: `Boolean`
Default: `false` if running in a CI environment, `true` otherwise
Type: `Boolean`<br>
Default: `false` if running in a CI environment, `true` otherwise<br>
CLI arguments: `-d`, `--dry-run`
Dry-run mode, skip publishing, print next version and release notes.
### noCi
Type: `Boolean`
Default: `false`
Type: `Boolean`<br>
Default: `false`<br>
CLI arguments: `--no-ci`
Skip Continuous Integration environment verifications. This allows for making releases from a local machine.
### debug
Type: `Boolean`
Default: `false`
Type: `Boolean`<br>
Default: `false`<br>
CLI argument: `--debug`
Output debugging information. It can also be enabled by setting the `DEBUG` environment variable to `semantic-release:*`.
### verifyConditions
Type: `Array`, `String`, `Object`
Default: `['@semantic-release/npm', '@semantic-release/github']`
Type: `Array`, `String`, `Object`<br>
Default: `['@semantic-release/npm', '@semantic-release/github']`<br>
CLI argument: `--verify-conditions`
Define the list of [verify conditions plugins](plugins.md#verifyconditions-plugin). Plugins will run in series, in the order defined in the `Array`.
@@ -143,10 +128,8 @@ See [Plugins configuration](plugins.md#configuration) for more details.
### analyzeCommits
Type: `String`, `Object`
Default: `'@semantic-release/commit-analyzer'`
Type: `Array`, `String`, `Object`<br>
Default: `'@semantic-release/commit-analyzer'`<br>
CLI argument: `--analyze-commits`
Define the [analyze commits plugin](plugins.md#analyzecommits-plugin).
@@ -155,10 +138,8 @@ See [Plugins configuration](plugins.md#configuration) for more details.
### verifyRelease
Type: `Array`, `String`, `Object`
Default: `[]`
Type: `Array`, `String`, `Object`<br>
Default: `[]`<br>
CLI argument: `--verify-release`
Define the list of [verify release plugins](plugins.md#verifyrelease-plugin). Plugins will run in series, in the order defined in the `Array`.
@@ -167,10 +148,8 @@ See [Plugins configuration](plugins.md#configuration) for more details.
### generateNotes
Type: `Array`, `String`, `Object`
Default: `['@semantic-release/release-notes-generator']`
Type: `Array`, `String`, `Object`<br>
Default: `['@semantic-release/release-notes-generator']`<br>
CLI argument: `--generate-notes`
Define the [generate notes plugins](plugins.md#generatenotes-plugin).
@@ -179,10 +158,8 @@ See [Plugins configuration](plugins.md#configuration) for more details.
### prepare
Type: `Array`, `String`, `Object`
Default: `['@semantic-release/npm']`
Type: `Array`, `String`, `Object`<br>
Default: `['@semantic-release/npm']`<br>
CLI argument: `--prepare`
Define the list of [prepare plugins](plugins.md#prepare-plugin). Plugins will run in series, in the order defined in the `Array`.
@@ -191,10 +168,8 @@ See [Plugins configuration](plugins.md#configuration) for more details.
### publish
Type: `Array`, `String`, `Object`
Default: `['@semantic-release/npm', '@semantic-release/github']`
Type: `Array`, `String`, `Object`<br>
Default: `['@semantic-release/npm', '@semantic-release/github']`<br>
CLI argument: `--publish`
Define the list of [publish plugins](plugins.md#publish-plugin). Plugins will run in series, in the order defined in the `Array`.
@@ -203,10 +178,8 @@ See [Plugins configuration](plugins.md#configuration) for more details.
### success
Type: `Array`, `String`, `Object`
Default: `['@semantic-release/github']`
Type: `Array`, `String`, `Object`<br>
Default: `['@semantic-release/github']`<br>
CLI argument: `--success`
Define the list of [success plugins](plugins.md#success-plugin). Plugins will run in series, in the order defined in the `Array`.
@@ -215,10 +188,8 @@ See [Plugins configuration](plugins.md#configuration) for more details.
### fail
Type: `Array`, `String`, `Object`
Default: `['@semantic-release/github']`
Type: `Array`, `String`, `Object`<br>
Default: `['@semantic-release/github']`<br>
CLI argument: `--fail`
Define the list of [fail plugins](plugins.md#fail-plugin). Plugins will run in series, in the order defined in the `Array`.
+24 -8
View File
@@ -10,25 +10,33 @@ See [plugins list](../extending/plugins-list.md).
Responsible for verifying conditions necessary to proceed with the release: configuration is correct, authentication token are valid, etc...
Default implementation: [@semantic-release/npm](https://github.com/semantic-release/npm#verifyconditions) and [@semantic-release/github](https://github.com/semantic-release/github#verifyconditions).
Default implementation: [@semantic-release/npm](https://github.com/semantic-release/npm#verifyconditions) and [@semantic-release/github](https://github.com/semantic-release/github#verifyconditions).<br>
Optional.<br>
Accept multiple plugins.
### analyzeCommits plugin
Responsible for determining the type of the next release (`major`, `minor` or `patch`).
Default implementation: [@semantic-release/commit-analyzer](https://github.com/semantic-release/commit-analyzer).
Default implementation: [@semantic-release/commit-analyzer](https://github.com/semantic-release/commit-analyzer).<br>
Required.<br>
Accept only one plugin.
### verifyRelease plugin
Responsible for verifying the parameters (version, type, dist-tag etc...) of the release that is about to be published. For example the [cracks plugin](https://github.com/semantic-release/cracks) is able to verify that if a release contains breaking changes, its type must be `major`.
Default implementation: none.
Default implementation: none.<br>
Optional.<br>
Accept multiple plugins.
### generateNotes plugin
Responsible for generating release notes. If multiple `generateNotes` plugins are defined, the release notes will be the result of the concatenation of plugin output.
Default implementation: [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator).
Default implementation: [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator).<br>
Optional.<br>
Accept multiple plugins.
### prepare plugin
@@ -36,25 +44,33 @@ Responsible for preparing the release, including:
- Creating or updating files such as `package.json`, `CHANGELOG.md`, documentation or compiled assets.
- Create and push commits
Default implementation: [@semantic-release/npm](https://github.com/semantic-release/npm#prepare).
Default implementation: [@semantic-release/npm](https://github.com/semantic-release/npm#prepare).<br>
Optional.<br>
Accept multiple plugins.
### publish plugin
Responsible for publishing the release.
Default implementation: [@semantic-release/npm](https://github.com/semantic-release/npm#publish) and [@semantic-release/github](https://github.com/semantic-release/github#publish).
Default implementation: [@semantic-release/npm](https://github.com/semantic-release/npm#publish) and [@semantic-release/github](https://github.com/semantic-release/github#publish).<br>
Optional.<br>
Accept multiple plugins.
### success plugin
Responsible for notifying of a new release.
Default implementation: [@semantic-release/github](https://github.com/semantic-release/github#success).
Default implementation: [@semantic-release/github](https://github.com/semantic-release/github#success).<br>
Optional.<br>
Accept multiple plugins.
### fail plugin
Responsible for notifying of a failed release.
Default implementation: [@semantic-release/github](https://github.com/semantic-release/github#fail).
Default implementation: [@semantic-release/github](https://github.com/semantic-release/github#fail).<br>
Optional.<br>
Accept multiple plugins.
## Configuration
+11 -1
View File
@@ -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,
};
+6 -4
View File
@@ -55,11 +55,13 @@ Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`
Your configuration for the \`tagFormat\` option is \`${stringify(tagFormat)}\`.`,
}),
EPLUGINCONF: ({type, pluginConf}) => ({
EPLUGINCONF: ({type, multiple, required, pluginConf}) => ({
message: `The \`${type}\` plugin configuration is invalid.`,
details: `The [${type} plugin configuration](${linkify(
`docs/usage/plugins.md#${toLower(type)}-plugin`
)}) if defined, must be a single or an array of plugins definition. A plugin definition is either a string or an object with a \`path\` property.
details: `The [${type} plugin configuration](${linkify(`docs/usage/plugins.md#${toLower(type)}-plugin`)}) ${
required ? 'is required and ' : ''
}must be ${
multiple ? 'a single or an array of plugins' : 'a single plugin'
} definition. A plugin definition is either a string or an object with a \`path\` property.
Your configuration for the \`${type}\` plugin is \`${stringify(pluginConf)}\`.`,
}),
+17 -11
View File
@@ -1,18 +1,18 @@
const {isString, isFunction, isArray, isPlainObject} = require('lodash');
const {isString, isPlainObject} = require('lodash');
const {gitHead} = require('../git');
const {RELEASE_TYPE, RELEASE_NOTES_SEPARATOR} = require('./constants');
const validatePluginConfig = conf => isString(conf) || isString(conf.path) || isFunction(conf);
module.exports = {
verifyConditions: {
default: ['@semantic-release/npm', '@semantic-release/github'],
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
multiple: true,
required: false,
pipelineConfig: () => ({settleAll: true}),
},
analyzeCommits: {
default: '@semantic-release/commit-analyzer',
configValidator: conf => Boolean(conf) && validatePluginConfig(conf),
multiple: false,
required: true,
outputValidator: output => !output || RELEASE_TYPE.includes(output),
preprocess: ({commits, ...inputs}) => ({
...inputs,
@@ -22,12 +22,14 @@ module.exports = {
},
verifyRelease: {
default: false,
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
multiple: true,
required: false,
pipelineConfig: () => ({settleAll: true}),
},
generateNotes: {
default: ['@semantic-release/release-notes-generator'],
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
multiple: true,
required: false,
outputValidator: output => !output || isString(output),
pipelineConfig: () => ({
getNextInput: ({nextRelease, ...context}, notes) => ({
@@ -42,7 +44,8 @@ module.exports = {
},
prepare: {
default: ['@semantic-release/npm'],
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
multiple: true,
required: false,
pipelineConfig: ({generateNotes}, logger) => ({
getNextInput: async context => {
const newGitHead = await gitHead({cwd: context.cwd});
@@ -60,7 +63,8 @@ module.exports = {
},
publish: {
default: ['@semantic-release/npm', '@semantic-release/github'],
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
multiple: true,
required: false,
outputValidator: output => !output || isPlainObject(output),
pipelineConfig: () => ({
// Add `nextRelease` and plugin properties to published release
@@ -73,12 +77,14 @@ module.exports = {
},
success: {
default: ['@semantic-release/github'],
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
multiple: true,
required: false,
pipelineConfig: () => ({settleAll: true}),
},
fail: {
default: ['@semantic-release/github'],
configValidator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
multiple: true,
required: false,
pipelineConfig: () => ({settleAll: true}),
},
};
+4 -6
View File
@@ -1,13 +1,11 @@
const {escapeRegExp} = require('lodash');
const {SECRET_REPLACEMENT} = require('./definitions/constants');
const {escapeRegExp, size} = 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 => {
return output && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
};
return output => (output && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output);
};
+4 -3
View File
@@ -2,6 +2,7 @@ const {identity, isPlainObject, omit, castArray, isUndefined} = require('lodash'
const AggregateError = require('aggregate-error');
const getError = require('../get-error');
const PLUGINS_DEFINITIONS = require('../definitions/plugins');
const {validateConfig} = require('./utils');
const pipeline = require('./pipeline');
const normalize = require('./normalize');
@@ -11,7 +12,7 @@ module.exports = (context, pluginsPath) => {
const plugins = Object.entries(PLUGINS_DEFINITIONS).reduce(
(
plugins,
[type, {configValidator, default: def, pipelineConfig, postprocess = identity, preprocess = identity}]
[type, {multiple, required, default: def, pipelineConfig, postprocess = identity, preprocess = identity}]
) => {
let pluginOpts;
@@ -23,8 +24,8 @@ module.exports = (context, pluginsPath) => {
if (isPlainObject(options[type]) && !options[type].path && defaultPaths.length === 1) {
[options[type].path] = defaultPaths;
}
if (configValidator && !configValidator(options[type])) {
errors.push(getError('EPLUGINCONF', {type, pluginConf: options[type]}));
if (!validateConfig({multiple, required}, options[type])) {
errors.push(getError('EPLUGINCONF', {type, multiple, required, pluginConf: options[type]}));
return plugins;
}
pluginOpts = options[type];
+18
View File
@@ -0,0 +1,18 @@
const {isString, isFunction, castArray} = require('lodash');
const validateSingleConfig = conf => {
conf = castArray(conf);
return conf.length === 1 && (isString(conf[0]) || isString(conf[0].path) || isFunction(conf[0]));
};
const validateMultipleConfig = conf => castArray(conf).every(conf => validateSingleConfig(conf));
const validateConfig = ({multiple, required}, conf) => {
conf = castArray(conf).filter(Boolean);
if (required) {
return Boolean(conf) && conf.length >= 1 && (multiple ? validateMultipleConfig : validateSingleConfig)(conf);
}
return conf.length === 0 || (multiple ? validateMultipleConfig : validateSingleConfig)(conf);
};
module.exports = {validateConfig};
+2 -2
View File
@@ -22,7 +22,7 @@
"@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",
@@ -57,7 +57,7 @@
"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",
+14
View File
@@ -1,6 +1,8 @@
import test from 'ava';
import {escapeRegExp} from 'lodash';
import proxyquire from 'proxyquire';
import {stub} from 'sinon';
import {SECRET_REPLACEMENT} from '../lib/definitions/constants';
const requireNoCache = proxyquire.noPreserveCache();
@@ -208,3 +210,15 @@ test.serial('Return error code if semantic-release throw error', async t => {
t.regex(t.context.errors, /semantic-release error/);
t.is(exitCode, 1);
});
test.serial('Hide sensitive environment variable values from the logs', async t => {
const env = {MY_TOKEN: 'secret token'};
const run = stub().rejects(new Error(`Throw error: Exposing token ${env.MY_TOKEN}`));
const argv = ['', ''];
const cli = requireNoCache('../cli', {'.': run, process: {...process, argv, env: {...process.env, ...env}}});
const exitCode = await cli();
t.regex(t.context.errors, new RegExp(`Throw error: Exposing token ${escapeRegExp(SECRET_REPLACEMENT)}`));
t.is(exitCode, 1);
});
-88
View File
@@ -2,94 +2,6 @@ import test from 'ava';
import plugins from '../../lib/definitions/plugins';
import {RELEASE_NOTES_SEPARATOR} from '../../lib/definitions/constants';
test('The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition', t => {
t.false(plugins.verifyConditions.configValidator({}));
t.false(plugins.verifyConditions.configValidator({path: null}));
t.true(plugins.verifyConditions.configValidator({path: 'plugin-path.js'}));
t.true(plugins.verifyConditions.configValidator());
t.true(plugins.verifyConditions.configValidator('plugin-path.js'));
t.true(plugins.verifyConditions.configValidator(() => {}));
t.true(plugins.verifyConditions.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "analyzeCommits" plugin is mandatory, and must be a single plugin definition', t => {
t.false(plugins.analyzeCommits.configValidator({}));
t.false(plugins.analyzeCommits.configValidator({path: null}));
t.false(plugins.analyzeCommits.configValidator([]));
t.false(plugins.analyzeCommits.configValidator());
t.true(plugins.analyzeCommits.configValidator({path: 'plugin-path.js'}));
t.true(plugins.analyzeCommits.configValidator('plugin-path.js'));
t.true(plugins.analyzeCommits.configValidator(() => {}));
});
test('The "verifyRelease" plugin, if defined, must be a single or an array of plugins definition', t => {
t.false(plugins.verifyRelease.configValidator({}));
t.false(plugins.verifyRelease.configValidator({path: null}));
t.true(plugins.verifyRelease.configValidator({path: 'plugin-path.js'}));
t.true(plugins.verifyRelease.configValidator());
t.true(plugins.verifyRelease.configValidator('plugin-path.js'));
t.true(plugins.verifyRelease.configValidator(() => {}));
t.true(plugins.verifyRelease.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "generateNotes" plugin, if defined, must be a single or an array of plugins definition', t => {
t.false(plugins.generateNotes.configValidator({}));
t.false(plugins.generateNotes.configValidator({path: null}));
t.true(plugins.generateNotes.configValidator({path: 'plugin-path.js'}));
t.true(plugins.generateNotes.configValidator());
t.true(plugins.generateNotes.configValidator('plugin-path.js'));
t.true(plugins.generateNotes.configValidator(() => {}));
t.true(plugins.generateNotes.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "prepare" plugin, if defined, must be a single or an array of plugins definition', t => {
t.false(plugins.verifyRelease.configValidator({}));
t.false(plugins.verifyRelease.configValidator({path: null}));
t.true(plugins.verifyRelease.configValidator({path: 'plugin-path.js'}));
t.true(plugins.verifyRelease.configValidator());
t.true(plugins.verifyRelease.configValidator('plugin-path.js'));
t.true(plugins.verifyRelease.configValidator(() => {}));
t.true(plugins.verifyRelease.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "publish" plugin is mandatory, and must be a single or an array of plugins definition', t => {
t.false(plugins.publish.configValidator({}));
t.false(plugins.publish.configValidator({path: null}));
t.true(plugins.publish.configValidator({path: 'plugin-path.js'}));
t.true(plugins.publish.configValidator());
t.true(plugins.publish.configValidator('plugin-path.js'));
t.true(plugins.publish.configValidator(() => {}));
t.true(plugins.publish.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "success" plugin, if defined, must be a single or an array of plugins definition', t => {
t.false(plugins.success.configValidator({}));
t.false(plugins.success.configValidator({path: null}));
t.true(plugins.success.configValidator({path: 'plugin-path.js'}));
t.true(plugins.success.configValidator());
t.true(plugins.success.configValidator('plugin-path.js'));
t.true(plugins.success.configValidator(() => {}));
t.true(plugins.success.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "fail" plugin, if defined, must be a single or an array of plugins definition', t => {
t.false(plugins.fail.configValidator({}));
t.false(plugins.fail.configValidator({path: null}));
t.true(plugins.fail.configValidator({path: 'plugin-path.js'}));
t.true(plugins.fail.configValidator());
t.true(plugins.fail.configValidator('plugin-path.js'));
t.true(plugins.fail.configValidator(() => {}));
t.true(plugins.fail.configValidator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "analyzeCommits" plugin output must be either undefined or a valid semver release type', t => {
t.false(plugins.analyzeCommits.outputValidator('invalid'));
t.false(plugins.analyzeCommits.outputValidator(1));
+19 -4
View File
@@ -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}`
);
});
+12 -9
View File
@@ -159,18 +159,15 @@ test('Merge global options with plugin options', async t => {
t.deepEqual(result.pluginConfig, {localOpt: 'local', globalOpt: 'global', otherOpt: 'locally-defined'});
});
test('Throw an error if plugins configuration are missing a path for plugin pipeline', t => {
const errors = [...t.throws(() => getPlugins({cwd, logger: t.context.logger, options: {verifyConditions: {}}}, {}))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINCONF');
});
test('Throw an error if an array of plugin configuration is missing a path for plugin pipeline', t => {
test('Throw an error if plugins configuration are invalid', t => {
const errors = [
...t.throws(() =>
getPlugins(
{cwd, logger: t.context.logger, options: {verifyConditions: [{path: '@semantic-release/npm'}, {}]}},
{
cwd,
logger: t.context.logger,
options: {verifyConditions: {}, analyzeCommits: [], verifyRelease: [{}], generateNotes: [{path: null}]},
},
{}
)
),
@@ -178,4 +175,10 @@ test('Throw an error if an array of plugin configuration is missing a path for p
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINCONF');
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EPLUGINCONF');
t.is(errors[2].name, 'SemanticReleaseError');
t.is(errors[2].code, 'EPLUGINCONF');
t.is(errors[3].name, 'SemanticReleaseError');
t.is(errors[3].code, 'EPLUGINCONF');
});
+58
View File
@@ -0,0 +1,58 @@
import test from 'ava';
import {validateConfig} from '../../lib/plugins/utils';
test('Validate multiple/optional plugin configuration', t => {
const type = {multiple: true, required: false};
t.false(validateConfig(type, {}));
t.false(validateConfig(type, {path: null}));
t.true(validateConfig(type, {path: 'plugin-path.js'}));
t.true(validateConfig(type));
t.true(validateConfig(type, 'plugin-path.js'));
t.true(validateConfig(type, ['plugin-path.js']));
t.true(validateConfig(type, () => {}));
t.true(validateConfig(type, [{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('Validate multiple/required plugin configuration', t => {
const type = {multiple: true, required: true};
t.false(validateConfig(type, {}));
t.false(validateConfig(type, {path: null}));
t.false(validateConfig(type));
t.true(validateConfig(type, {path: 'plugin-path.js'}));
t.true(validateConfig(type, 'plugin-path.js'));
t.true(validateConfig(type, ['plugin-path.js']));
t.true(validateConfig(type, () => {}));
t.true(validateConfig(type, [{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('Validate single/required plugin configuration', t => {
const type = {multiple: false, required: true};
t.false(validateConfig(type, {}));
t.false(validateConfig(type, {path: null}));
t.false(validateConfig(type, []));
t.false(validateConfig(type));
t.false(validateConfig(type, [{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
t.true(validateConfig(type, {path: 'plugin-path.js'}));
t.true(validateConfig(type, 'plugin-path.js'));
t.true(validateConfig(type, ['plugin-path.js']));
t.true(validateConfig(type, () => {}));
});
test('Validate single/optional plugin configuration', t => {
const type = {multiple: false, required: false};
t.false(validateConfig(type, {}));
t.false(validateConfig(type, {path: null}));
t.false(validateConfig(type, [{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
t.true(validateConfig(type));
t.true(validateConfig(type, []));
t.true(validateConfig(type, {path: 'plugin-path.js'}));
t.true(validateConfig(type, 'plugin-path.js'));
t.true(validateConfig(type, ['plugin-path.js']));
t.true(validateConfig(type, () => {}));
});