diff --git a/README.md b/README.md index b4ac7e35..a3e16670 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,10 @@ Execute a shell command to notify of a failed release. ### Options -| Options | Description | -|---------|------------------------------------------------| -| `cmd` | The shell command to execute. See [cmd](#cmd). | +| Options | Description | +|---------|------------------------------------------------------------------------------------------------------| +| `cmd` | The shell command to execute. See [cmd](#cmd). | +| `shell` | The shell to use to run the command. See [execa#shell](https://github.com/sindresorhus/execa#shell). | #### `cmd` diff --git a/lib/exec-script.js b/lib/exec-script.js index 18ee529c..f6207ba9 100644 --- a/lib/exec-script.js +++ b/lib/exec-script.js @@ -1,12 +1,12 @@ const {template} = require('lodash'); const execa = require('execa'); -module.exports = async ({cmd, ...config}, {cwd, env, stdout, stderr, logger, ...context}) => { +module.exports = async ({cmd, shell, ...config}, {cwd, env, stdout, stderr, logger, ...context}) => { const script = template(cmd)({config, ...context}); logger.log('Call script %s', script); - const result = execa.shell(script, {cwd, env}); + const result = execa.shell(script, {shell, cwd, env}); result.stdout.pipe( stdout, diff --git a/lib/verify-config.js b/lib/verify-config.js index 8b594d16..72fb8693 100644 --- a/lib/verify-config.js +++ b/lib/verify-config.js @@ -1,11 +1,18 @@ -const {isString} = require('lodash'); +const {isUndefined, isString} = require('lodash'); const SemanticReleaseError = require('@semantic-release/error'); -module.exports = config => { - if (!isString(config.cmd) || !config.cmd.trim()) { +module.exports = ({cmd, shell}) => { + if (!isString(cmd) || !cmd.trim()) { throw new SemanticReleaseError( - 'The script plugin must be configured with the shell command to execute in the a "cmd" option.', + 'The exec plugin must be configured with the shell command to execute in the a "cmd" option.', 'EINVALIDCMD' ); } + + if (!isUndefined(shell) && (!isString(shell) || !shell.trim())) { + throw new SemanticReleaseError( + 'The "shell" option, if specified, must be a non empty String or the value "true".', + 'EINVALIDSHELL' + ); + } }; diff --git a/test/analyze-commits.test.js b/test/analyze-commits.test.js index ea83868c..0fc81d18 100644 --- a/test/analyze-commits.test.js +++ b/test/analyze-commits.test.js @@ -52,6 +52,16 @@ test('Throw "SemanticReleaseError" if "cmd" options is empty', async t => { t.is(error.code, 'EINVALIDCMD'); }); +test('Throw "SemanticReleaseError" if "shell" options is invalid', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh', shell: ' '}; + const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; + + const error = await t.throws(analyzeCommits(pluginConfig, context)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDSHELL'); +}); + test('Throw Error if if the analyzeCommits script does not returns 0', async t => { const pluginConfig = {cmd: 'exit 1'}; const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger}; diff --git a/test/exec-script.test.js b/test/exec-script.test.js index cb745f14..73d1d030 100644 --- a/test/exec-script.test.js +++ b/test/exec-script.test.js @@ -12,7 +12,7 @@ test.beforeEach(t => { t.context.logger = {log: t.context.log, error: t.context.error}; }); -test.serial('Pipe script output to stdout and stderr', async t => { +test('Pipe script output to stdout and stderr', async t => { const pluginConfig = {cmd: '>&2 echo "write to stderr" && echo "write to stdout"'}; const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; @@ -23,7 +23,7 @@ test.serial('Pipe script output to stdout and stderr', async t => { t.is(t.context.stderr.getContentsAsString('utf8').trim(), 'write to stderr'); }); -test.serial('Generate command with template', async t => { +test('Generate command with template', async t => { const pluginConfig = {cmd: `./test/fixtures/echo-args.sh \${config.conf} \${lastRelease.version}`, conf: 'confValue'}; const context = { stdout: t.context.stdout, @@ -35,3 +35,13 @@ test.serial('Generate command with template', async t => { const result = await execScript(pluginConfig, context); t.is(result, 'confValue 1.0.0'); }); + +test('Execute the script with the specified "shell"', async t => { + const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger}; + + let result = await execScript({cmd: 'echo $0', shell: 'bash'}, context); + t.is(result, 'bash'); + + result = await execScript({cmd: 'echo $0', shell: 'sh'}, context); + t.is(result, 'sh'); +}); diff --git a/test/fail.test.js b/test/fail.test.js index 23a9cd66..b3a51c97 100644 --- a/test/fail.test.js +++ b/test/fail.test.js @@ -39,6 +39,16 @@ test('Throw "SemanticReleaseError" if "cmd" options is empty', async t => { t.is(error.code, 'EINVALIDCMD'); }); +test('Throw "SemanticReleaseError" if "shell" options is invalid', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh', shell: ' '}; + const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; + + const error = await t.throws(fail(pluginConfig, context)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDSHELL'); +}); + test('Throw "Error" if the fail script does not returns 0', async t => { const pluginConfig = {cmd: 'exit 1'}; const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger}; diff --git a/test/generate-notes.test.js b/test/generate-notes.test.js index d4341401..c77619fd 100644 --- a/test/generate-notes.test.js +++ b/test/generate-notes.test.js @@ -42,6 +42,16 @@ test('Throw "SemanticReleaseError" if "cmd" options is empty', async t => { t.is(error.code, 'EINVALIDCMD'); }); +test('Throw "SemanticReleaseError" if "shell" options is invalid', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh', shell: ' '}; + const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; + + const error = await t.throws(generateNotes(pluginConfig, context)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDSHELL'); +}); + test('Throw "Error" if if the generateNotes script does not returns 0', async t => { const pluginConfig = {cmd: 'exit 1'}; const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger}; diff --git a/test/prepare.test.js b/test/prepare.test.js index 9c17d6ab..b3d55a55 100644 --- a/test/prepare.test.js +++ b/test/prepare.test.js @@ -39,6 +39,16 @@ test('Throw "SemanticReleaseError" if "cmd" options is empty', async t => { t.is(error.code, 'EINVALIDCMD'); }); +test('Throw "SemanticReleaseError" if "shell" options is invalid', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh', shell: ' '}; + const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; + + const error = await t.throws(prepare(pluginConfig, context)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDSHELL'); +}); + test('Throw "Error" if the prepare script does not returns 0', async t => { const pluginConfig = {cmd: 'exit 1'}; const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger}; diff --git a/test/publish.test.js b/test/publish.test.js index 3e8c0eea..8fd177fb 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -63,6 +63,16 @@ test('Throw "SemanticReleaseError" if "cmd" options is empty', async t => { t.is(error.code, 'EINVALIDCMD'); }); +test('Throw "SemanticReleaseError" if "shell" options is invalid', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh', shell: ' '}; + const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; + + const error = await t.throws(publish(pluginConfig, context)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDSHELL'); +}); + test('Throw "Error" if the publish script does not returns 0', async t => { const pluginConfig = {cmd: 'exit 1'}; const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; diff --git a/test/success.test.js b/test/success.test.js index a9909641..278c0032 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -39,6 +39,16 @@ test('Throw "SemanticReleaseError" if "cmd" options is empty', async t => { t.is(error.code, 'EINVALIDCMD'); }); +test('Throw "SemanticReleaseError" if "shell" options is invalid', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh', shell: ' '}; + const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; + + const error = await t.throws(success(pluginConfig, context)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDSHELL'); +}); + test('Throw "Error" if the success script does not returns 0', async t => { const pluginConfig = {cmd: 'exit 1'}; const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger}; diff --git a/test/verify-confitions.test.js b/test/verify-confitions.test.js index 57d987c3..6bce0ddb 100644 --- a/test/verify-confitions.test.js +++ b/test/verify-confitions.test.js @@ -39,6 +39,16 @@ test('Throw "SemanticReleaseError" if "cmd" options is empty', async t => { t.is(error.code, 'EINVALIDCMD'); }); +test('Throw "SemanticReleaseError" if "shell" options is invalid', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh', shell: ' '}; + const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; + + const error = await t.throws(verifyConditions(pluginConfig, context)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDSHELL'); +}); + test('Return if the verifyConditions script returns 0', async t => { const pluginConfig = {cmd: 'exit 0'}; const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; diff --git a/test/verify-release.test.js b/test/verify-release.test.js index 24418162..1e29d9ce 100644 --- a/test/verify-release.test.js +++ b/test/verify-release.test.js @@ -39,6 +39,16 @@ test('Throw "SemanticReleaseError" if "cmd" options is empty', async t => { t.is(error.code, 'EINVALIDCMD'); }); +test('Throw "SemanticReleaseError" if "shell" options is invalid', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh', shell: ' '}; + const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}}; + + const error = await t.throws(verifyRelease(pluginConfig, context)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDSHELL'); +}); + test('Throw "SemanticReleaseError" if the verifyRelease script does not returns 0', async t => { const pluginConfig = {cmd: 'exit 1'}; const context = {stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger, options: {}};