From 2ac206f677295e173906121ce22858757c76721a Mon Sep 17 00:00:00 2001 From: Pierre Vanduynslager Date: Thu, 21 Dec 2017 11:08:17 -0500 Subject: [PATCH] feat: initial release (#1) --- .gitignore | 131 +++++++++++++++++++++++++++++++++ .npmrc | 1 + .travis.yml | 24 ++++++ .yarnrc | 1 + LICENSE | 21 ++++++ README.md | 126 ++++++++++++++++++++++++++++++- index.js | 58 +++++++++++++++ lib/exec-script.js | 14 ++++ lib/verify-config.js | 11 +++ package.json | 101 +++++++++++++++++++++++++ test/analyze-commits.test.js | 41 +++++++++++ test/exec-script.test.js | 37 ++++++++++ test/fixtures/echo-args.sh | 2 + test/generate-notes.test.js | 30 ++++++++ test/get-last-release.test.js | 51 +++++++++++++ test/publish.test.js | 28 +++++++ test/verify-confitions.test.js | 87 ++++++++++++++++++++++ test/verify-release.test.js | 31 ++++++++ 18 files changed, 793 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .travis.yml create mode 100644 .yarnrc create mode 100644 LICENSE create mode 100644 index.js create mode 100644 lib/exec-script.js create mode 100644 lib/verify-config.js create mode 100644 package.json create mode 100644 test/analyze-commits.test.js create mode 100644 test/exec-script.test.js create mode 100755 test/fixtures/echo-args.sh create mode 100644 test/generate-notes.test.js create mode 100644 test/get-last-release.test.js create mode 100644 test/publish.test.js create mode 100644 test/verify-confitions.test.js create mode 100644 test/verify-release.test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..aae09ec6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,131 @@ + +# Created by https://www.gitignore.io/api/macos,windows,linux,node + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/macos,windows,linux,node + +package-lock.json +yarn.lock diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..43c97e71 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..1b0393fb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: node_js +notifications: + email: false +node_js: + - 9 + - 8 + +# Trigger a push build on master and greenkeeper branches + PRs build on every branches +# Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147) +branches: + only: + - master + - /^greenkeeper.*$/ + +# Retry install on fail to avoid failing a build on network/disk/external errors +install: + - travis_retry npm install + +script: + - npm run test + +after_success: + - npm run codecov + - npm run semantic-release diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 00000000..acaaffdb --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +--install.no-lockfile true diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8e443427 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 9c203263..3d2408b9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,124 @@ -# exec -:shell: Set of semantic-release plugins to run custom scripts +# @semantic-release/exec + +Set of [semantic-release](https://github.com/semantic-release/semantic-release) plugins to execute custom shell commands. + +[![Travis](https://img.shields.io/travis/semantic-release/exec.svg)](https://travis-ci.org/semantic-release/exec) +[![Codecov](https://img.shields.io/codecov/c/github/semantic-release/exec.svg)](https://codecov.io/gh/semantic-release/exec) +[![Greenkeeper badge](https://badges.greenkeeper.io/semantic-release/exec.svg)](https://greenkeeper.io/) + +## verifyConditions + +Execute a shell command to verify if the release should happen. + +| Command property | Description | +|------------------|--------------------------------------------------------------------------| +| `exit code` | `0` if the verification is successful, or any other exit code otherwise. | +| `stdout` | Write only the reason for the verification to fail. | +| `stderr` | Can be used for logging. | + +## getLastRelease + +Execute a shell command to determine the last release. + +| Command property | Description | +|------------------|-------------------------------------------------------------------------------------------------------------------------------| +| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. | +| `stdout` | Only the `lastRelease` must be written to `stdout` as parseable JSON (for example `{"version": "x.x.x", "gitHead": "xxxx"}`). | +| `stderr` | Can be used for logging. | + +## analyzeCommits + +Execute a shell command to determine the type release. + +| Command property | Description | +|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. | +| `stdout` | Only the release type (`major`, `minor` or `patch` etc..) can be written to `stdout`. If no release has to be done the command must not write to `stdout`. | +| `stderr` | Can be used for logging. | + +## verifyRelease + +Execute a shell command to verifying a release that was determined before and is about to be published. + +| Command property | Description | +|------------------|--------------------------------------------------------------------------| +| `exit code` | `0` if the verification is successful, or any other exit code otherwise. | +| `stdout` | Only the reason for the verification to fail can be written to `stdout`. | +| `stderr` | Can be used for logging. | + +## generateNotes + +Execute a shell command to generate the release note. + +| Command property | Description | +|------------------|---------------------------------------------------------------------------------------------------------------------| +| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. | +| `stdout` | Only the release note must be written to `stdout`. | +| `stderr` | Can be used for logging. | + +## publish + +Execute a shell command to publish the release. + +| Command property | Description | +|------------------|---------------------------------------------------------------------------------------------------------------------| +| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. | +| `stdout` | Can be used for logging. | +| `stderr` | Can be used for logging. | + +## Configuration + +### Options + +| Options | Description | +|---------|------------------------------------------------| +| `cmd` | The shell command to execute. See [cmd](#cmd). | + +#### `cmd` + +The shell command is generated with [Lodash template](https://lodash.com/docs#template). All the objets passed to the [semantic-release plugins](https://github.com/semantic-release/semantic-release#plugins) are available as template options. + +##### `cmd` examples + +```json +{ + "release": { + "publish": [ + { + "path": "@semantic-release/exec", + "cmd": "./publish.sh ${nextRelease.version} ${options.branch} ${commits.length} ${Date.now()}", + }, + "@semantic-release/npm", + "@semantic-release/github" + ] + } +} +``` + +This will execute the shell command `./publish.sh 1.0.0 master 3 870668040000` (for the release of version `1.0.0` from branch `master` with `3` commits on `August 4th, 1997 at 2:14 AM`). + +### Usage + +Options can be set within the plugin definition in the `semantic-release` configuration file: + +```json +{ + "release": { + "verifyConditions": [ + "@semantic-release/npm", + { + "path": "@semantic-release/exec", + "cmd": "./verify.sh", + } + ], + "publish": [ + "@semantic-release/npm", + { + "path": "@semantic-release/exec", + "cmd": "./publish.sh ${nextRelease.version}", + }, + "@semantic-release/github" + ] + } +} +``` diff --git a/index.js b/index.js new file mode 100644 index 00000000..18d9cf72 --- /dev/null +++ b/index.js @@ -0,0 +1,58 @@ +const {castArray, isPlainObject} = require('lodash'); +const parseJson = require('parse-json'); +const SemanticReleaseError = require('@semantic-release/error'); +const execScript = require('./lib/exec-script'); +const verifyConfig = require('./lib/verify-config'); + +const PLUGIN_TYPES = ['getLastRelease', 'analyzeCommits', 'verifyRelease', 'generateNotes', 'publish']; + +async function verifyConditions(pluginConfig, params) { + for (const [option, value] of Object.entries(params.options || {})) { + if (PLUGIN_TYPES.includes(option)) { + for (const plugin of castArray(value)) { + if ( + plugin === '@semantic-release/script' || + (isPlainObject(plugin) && plugin.path === '@semantic-release/script') + ) { + verifyConfig(plugin); + } + } + } + } + + verifyConfig(pluginConfig); + + try { + await execScript(pluginConfig, params); + } catch (err) { + throw new SemanticReleaseError(err.stdout, 'EVERIFYCONDITIONS'); + } +} + +async function getLastRelease(pluginConfig, params) { + const stdout = await execScript(pluginConfig, params); + return stdout ? parseJson(stdout) : undefined; +} + +async function analyzeCommits(pluginConfig, params) { + const stdout = await execScript(pluginConfig, params); + return stdout.trim() ? stdout : undefined; +} + +async function verifyRelease(pluginConfig, params) { + try { + await execScript(pluginConfig, params); + } catch (err) { + throw new SemanticReleaseError(err.stdout, 'EVERIFYRELEASE'); + } +} + +async function generateNotes(pluginConfig, params) { + return execScript(pluginConfig, params); +} + +async function publish(pluginConfig, params) { + await execScript(pluginConfig, params); +} + +module.exports = {verifyConditions, getLastRelease, analyzeCommits, verifyRelease, generateNotes, publish}; diff --git a/lib/exec-script.js b/lib/exec-script.js new file mode 100644 index 00000000..66d2dc94 --- /dev/null +++ b/lib/exec-script.js @@ -0,0 +1,14 @@ +const {template} = require('lodash'); +const execa = require('execa'); + +module.exports = async ({cmd, ...config}, {logger, ...opts}) => { + const script = template(cmd)({config, ...opts}); + + logger.log('Call script %s', script); + + const shell = execa.shell(script); + shell.stdout.pipe(process.stdout); + shell.stderr.pipe(process.stderr); + + return (await shell).stdout.trim(); +}; diff --git a/lib/verify-config.js b/lib/verify-config.js new file mode 100644 index 00000000..8b594d16 --- /dev/null +++ b/lib/verify-config.js @@ -0,0 +1,11 @@ +const {isString} = require('lodash'); +const SemanticReleaseError = require('@semantic-release/error'); + +module.exports = config => { + if (!isString(config.cmd) || !config.cmd.trim()) { + throw new SemanticReleaseError( + 'The script plugin must be configured with the shell command to execute in the a "cmd" option.', + 'EINVALIDCMD' + ); + } +}; diff --git a/package.json b/package.json new file mode 100644 index 00000000..3a170c75 --- /dev/null +++ b/package.json @@ -0,0 +1,101 @@ +{ + "name": "@semantic-release/exec", + "description": "Set of semantic-release plugins to run custom scripts", + "version": "0.0.0-development", + "author": "Pierre Vanduynslager (https://github.com/pvdlg)", + "bugs": { + "url": "https://github.com/semantic-release/git/issues" + }, + "config": { + "commitizen": { + "path": "cz-conventional-changelog" + } + }, + "contributors": [ + "Stephan Bönnemann (http://boennemann.me)", + "Gregor Martynus (https://twitter.com/gr2m)" + ], + "dependencies": { + "@semantic-release/error": "^2.1.0", + "debug": "^3.1.0", + "execa": "^0.8.0", + "lodash": "^4.17.4", + "parse-json": "^4.0.0" + }, + "devDependencies": { + "ava": "^0.24.0", + "codecov": "^3.0.0", + "commitizen": "^2.9.6", + "cz-conventional-changelog": "^2.0.0", + "eslint-config-prettier": "^2.3.0", + "eslint-plugin-prettier": "^2.3.0", + "nyc": "^11.1.0", + "prettier": "~1.8.2", + "semantic-release": "^11.0.0", + "sinon": "^4.1.2", + "xo": "^0.18.2" + }, + "engines": { + "node": ">=4" + }, + "files": [ + "lib", + "index.js" + ], + "homepage": "https://github.com/semantic-release/git#readme", + "keywords": [ + "cli", + "publish", + "release", + "script", + "semantic-release", + "shell", + "version" + ], + "license": "MIT", + "main": "index.js", + "nyc": { + "include": [ + "lib/**/*.js", + "index.js" + ], + "reporter": [ + "json", + "text", + "html" + ], + "all": true + }, + "prettier": { + "printWidth": 120, + "singleQuote": true, + "bracketSpacing": false, + "trailingComma": "es5" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/semantic-release/git.git" + }, + "scripts": { + "cm": "git-cz", + "codecov": "codecov -f coverage/coverage-final.json", + "lint": "xo", + "pretest": "npm run lint", + "semantic-release": "semantic-release", + "test": "nyc ava -v" + }, + "xo": { + "extends": [ + "prettier" + ], + "plugins": [ + "prettier" + ], + "rules": { + "prettier/prettier": 2 + } + } +} diff --git a/test/analyze-commits.test.js b/test/analyze-commits.test.js new file mode 100644 index 00000000..e43a4fb3 --- /dev/null +++ b/test/analyze-commits.test.js @@ -0,0 +1,41 @@ +import test from 'ava'; +import {stub} from 'sinon'; +import {analyzeCommits} from '..'; + +// Disable logs during tests +stub(process.stdout, 'write'); +stub(process.stderr, 'write'); + +test.beforeEach(t => { + // Mock logger + t.context.log = stub(); + t.context.error = stub(); + t.context.logger = {log: t.context.log, error: t.context.error}; +}); + +test.serial('Return the value analyzeCommits script wrote to stdout', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh "minor "', + }; + const params = {logger: t.context.logger}; + + const result = await analyzeCommits(pluginConfig, params); + t.is(result, 'minor'); +}); + +test.serial('Return "undefined" if the analyzeCommits script wrtite nothing to stdout', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh " "', + }; + const params = {logger: t.context.logger}; + + const result = await analyzeCommits(pluginConfig, params); + t.is(result, undefined); +}); + +test.serial('Throw Error if if the analyzeCommits script does not returns 0', async t => { + const pluginConfig = {cmd: 'exit 1'}; + const params = {logger: t.context.logger}; + + await t.throws(analyzeCommits(pluginConfig, params), Error); +}); diff --git a/test/exec-script.test.js b/test/exec-script.test.js new file mode 100644 index 00000000..a9b58979 --- /dev/null +++ b/test/exec-script.test.js @@ -0,0 +1,37 @@ +import test from 'ava'; +import {stub} from 'sinon'; +import execScript from '../lib/exec-script'; + +test.beforeEach(t => { + // Mock logger + t.context.log = stub(); + t.context.error = stub(); + t.context.logger = {log: t.context.log, error: t.context.error}; + t.context.stdout = stub(process.stdout, 'write'); + t.context.stderr = stub(process.stderr, 'write'); +}); + +test.afterEach.always(t => { + // Restore the logs + t.context.stdout.restore(); + t.context.stderr.restore(); +}); + +test.serial('Pipe script output to stdout and stderr', async t => { + const pluginConfig = {cmd: '>&2 echo "write to stderr" && echo "write to stdout"'}; + const params = {logger: t.context.logger, options: {}}; + + const result = await execScript(pluginConfig, params); + + t.is(result, 'write to stdout'); + t.is(t.context.stdout.args[0][0].toString().trim(), 'write to stdout'); + t.is(t.context.stderr.args[0][0].toString().trim(), 'write to stderr'); +}); + +test.serial('Generate command with template', async t => { + const pluginConfig = {cmd: `./test/fixtures/echo-args.sh \${config.conf} \${lastRelease.version}`, conf: 'confValue'}; + const params = {lastRelease: {version: '1.0.0'}, logger: t.context.logger}; + + const result = await execScript(pluginConfig, params); + t.is(result, 'confValue 1.0.0'); +}); diff --git a/test/fixtures/echo-args.sh b/test/fixtures/echo-args.sh new file mode 100755 index 00000000..7a0bbbbf --- /dev/null +++ b/test/fixtures/echo-args.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo "$@" diff --git a/test/generate-notes.test.js b/test/generate-notes.test.js new file mode 100644 index 00000000..6755da72 --- /dev/null +++ b/test/generate-notes.test.js @@ -0,0 +1,30 @@ +import test from 'ava'; +import {stub} from 'sinon'; +import {generateNotes} from '..'; + +stub(process.stdout, 'write'); +stub(process.stderr, 'write'); + +test.beforeEach(t => { + // Mock logger + t.context.log = stub(); + t.context.error = stub(); + t.context.logger = {log: t.context.log, error: t.context.error}; +}); + +test.serial('Return the value generateNotes script wrote to stdout', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh "\nRelease note \n\n"', + }; + const params = {logger: t.context.logger}; + + const result = await generateNotes(pluginConfig, params); + t.is(result, 'Release note'); +}); + +test.serial('Throw "Error" if if the generateNotes script does not returns 0', async t => { + const pluginConfig = {cmd: 'exit 1'}; + const params = {logger: t.context.logger}; + + await t.throws(generateNotes(pluginConfig, params), Error); +}); diff --git a/test/get-last-release.test.js b/test/get-last-release.test.js new file mode 100644 index 00000000..0cdb7447 --- /dev/null +++ b/test/get-last-release.test.js @@ -0,0 +1,51 @@ +import test from 'ava'; +import {stub} from 'sinon'; +import {getLastRelease} from '..'; + +// Disable logs during tests +stub(process.stdout, 'write'); +stub(process.stderr, 'write'); + +test.beforeEach(t => { + // Mock logger + t.context.log = stub(); + t.context.error = stub(); + t.context.logger = {log: t.context.log, error: t.context.error}; +}); + +test.serial('Parse JSON returned by getLastRelease script', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh {\\"version\\": \\"1.0.0\\", \\"gitHead\\": \\"12345678\\"}', + }; + const params = {logger: t.context.logger}; + + const result = await getLastRelease(pluginConfig, params); + t.deepEqual(result, {version: '1.0.0', gitHead: '12345678'}); +}); + +test.serial('Return "undefined" if the getLastRelease script wrtite nothing to stdout', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh', + }; + const params = {logger: t.context.logger}; + + const result = await getLastRelease(pluginConfig, params); + t.is(result, undefined); +}); + +test.serial('Throw JSONError if getLastRelease script write invalid JSON to stdout', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh invalid_json', + }; + const params = {logger: t.context.logger}; + + const error = await t.throws(getLastRelease(pluginConfig, params)); + t.is(error.name, 'JSONError'); +}); + +test.serial('Throw Error if if the getLastRelease script does not returns 0', async t => { + const pluginConfig = {cmd: 'exit 1'}; + const params = {logger: t.context.logger}; + + await t.throws(getLastRelease(pluginConfig, params), Error); +}); diff --git a/test/publish.test.js b/test/publish.test.js new file mode 100644 index 00000000..cd666a1f --- /dev/null +++ b/test/publish.test.js @@ -0,0 +1,28 @@ +import test from 'ava'; +import {stub} from 'sinon'; +import {publish} from '..'; + +// Disable logs during tests +stub(process.stdout, 'write'); +stub(process.stderr, 'write'); + +test.beforeEach(t => { + // Mock logger + t.context.log = stub(); + t.context.error = stub(); + t.context.logger = {log: t.context.log, error: t.context.error}; +}); + +test.serial('Return if the publish script returns 0', async t => { + const pluginConfig = {cmd: 'exit 0'}; + const params = {logger: t.context.logger, options: {}}; + + await t.notThrows(publish(pluginConfig, params)); +}); + +test.serial('Throw "Error" if the publish script does not returns 0', async t => { + const pluginConfig = {cmd: 'exit 1'}; + const params = {logger: t.context.logger, options: {}}; + + await t.throws(publish(pluginConfig, params), Error); +}); diff --git a/test/verify-confitions.test.js b/test/verify-confitions.test.js new file mode 100644 index 00000000..a7d72185 --- /dev/null +++ b/test/verify-confitions.test.js @@ -0,0 +1,87 @@ +import test from 'ava'; +import {stub} from 'sinon'; +import {verifyConditions} from '..'; + +// Disable logs during tests +stub(process.stdout, 'write'); +stub(process.stderr, 'write'); + +test.beforeEach(t => { + // Mock logger + t.context.log = stub(); + t.context.error = stub(); + t.context.logger = {log: t.context.log, error: t.context.error}; +}); + +test.serial('Verify plugin config is an Object with a "cmd" property', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh'}; + const params = {logger: t.context.logger}; + + await t.notThrows(verifyConditions(pluginConfig, params)); +}); + +test.serial('Throw "SemanticReleaseError" if "cmd" options is missing', async t => { + const pluginConfig = {}; + const params = {logger: t.context.logger}; + + const error = await t.throws(verifyConditions(pluginConfig, params)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDCMD'); +}); + +test.serial('Throw "SemanticReleaseError" if "cmd" options is empty', async t => { + const pluginConfig = {cmd: ' '}; + const params = {logger: t.context.logger, options: {}}; + + const error = await t.throws(verifyConditions(pluginConfig, params)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDCMD'); +}); + +test.serial('Throw "SemanticReleaseError" if another script plugin "cmd" options is missing', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh'}; + const params = { + logger: t.context.logger, + options: {publish: ['@semantic-release/npm', {path: '@semantic-release/script'}]}, + }; + + const error = await t.throws(verifyConditions(pluginConfig, params)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDCMD'); +}); + +test.serial('Throw "SemanticReleaseError" if another script plugin "cmd" options is empty', async t => { + const pluginConfig = {cmd: './test/fixtures/echo-args.sh'}; + const params = { + logger: t.context.logger, + options: { + branch: 'master', + getLastRelease: ['@semantic-release/npm', {path: '@semantic-release/script', cmd: ' '}], + }, + }; + + const error = await t.throws(verifyConditions(pluginConfig, params)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDCMD'); +}); + +test.serial('Return if the verifyConditions script returns 0', async t => { + const pluginConfig = {cmd: 'exit 0'}; + const params = {logger: t.context.logger, options: {}}; + + await t.notThrows(verifyConditions(pluginConfig, params)); +}); + +test.serial('Throw "SemanticReleaseError" if the verifyConditions script does not returns 0', async t => { + const pluginConfig = {cmd: 'exit 1'}; + const params = {logger: t.context.logger, options: {}}; + + const error = await t.throws(verifyConditions(pluginConfig, params)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EVERIFYCONDITIONS'); +}); diff --git a/test/verify-release.test.js b/test/verify-release.test.js new file mode 100644 index 00000000..5d5dba11 --- /dev/null +++ b/test/verify-release.test.js @@ -0,0 +1,31 @@ +import test from 'ava'; +import {stub} from 'sinon'; +import {verifyRelease} from '..'; + +// Disable logs during tests +stub(process.stdout, 'write'); +stub(process.stderr, 'write'); + +test.beforeEach(t => { + // Mock logger + t.context.log = stub(); + t.context.error = stub(); + t.context.logger = {log: t.context.log, error: t.context.error}; +}); + +test.serial('Return if the verifyRelease script returns 0', async t => { + const pluginConfig = {cmd: 'exit 0'}; + const params = {logger: t.context.logger, options: {}}; + + await t.notThrows(verifyRelease(pluginConfig, params)); +}); + +test.serial('Throw "SemanticReleaseError" if the verifyRelease script does not returns 0', async t => { + const pluginConfig = {cmd: 'exit 1'}; + const params = {logger: t.context.logger, options: {}}; + + const error = await t.throws(verifyRelease(pluginConfig, params)); + + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EVERIFYRELEASE'); +});