From f011d90e27d322b3bad84c2b3c278af54408c717 Mon Sep 17 00:00:00 2001 From: Pierre Vanduynslager Date: Mon, 12 Feb 2018 21:28:55 -0500 Subject: [PATCH 1/4] feat: return release informations from `publish` hook --- README.md | 10 +++++----- index.js | 4 +++- package.json | 3 ++- test/publish.test.js | 32 ++++++++++++++++++++++++++++---- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a1bdb417..5f354e0c 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,11 @@ Execute a shell command to generate the release note. 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. | +| 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` information must be written to `stdout` as parseable JSON (for example `{"name": "Release name", "url": "http://url/release/1.0.0"}`). | +| `stderr` | Can be used for logging. | ## Configuration diff --git a/index.js b/index.js index 300946e4..4ce0dfc2 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ 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'); @@ -46,7 +47,8 @@ async function generateNotes(pluginConfig, params) { } async function publish(pluginConfig, params) { - await execScript(pluginConfig, params); + const stdout = await execScript(pluginConfig, params); + return stdout.trim() ? parseJson(stdout) : undefined; } module.exports = {verifyConditions, analyzeCommits, verifyRelease, generateNotes, publish}; diff --git a/package.json b/package.json index 93f7e934..4d815ec7 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "@semantic-release/error": "^2.1.0", "debug": "^3.1.0", "execa": "^0.9.0", - "lodash": "^4.17.4" + "lodash": "^4.17.4", + "parse-json": "^4.0.0" }, "devDependencies": { "ava": "^0.25.0", diff --git a/test/publish.test.js b/test/publish.test.js index cd666a1f..5a7f519d 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -13,11 +13,35 @@ test.beforeEach(t => { 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: {}}; +test.serial('Parse JSON returned by publish script', async t => { + const pluginConfig = { + cmd: + './test/fixtures/echo-args.sh {\\"name\\": \\"Release name\\", \\"url\\": \\"https://host.com/release/1.0.0\\"}', + }; + const params = {logger: t.context.logger}; + + const result = await publish(pluginConfig, params); + t.deepEqual(result, {name: 'Release name', url: 'https://host.com/release/1.0.0'}); +}); + +test.serial('Return "undefined" if the publish script wrtite nothing to stdout', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh', + }; + const params = {logger: t.context.logger}; + + const result = await publish(pluginConfig, params); + t.is(result, undefined); +}); + +test.serial('Throw JSONError if publish script write invalid JSON to stdout', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh invalid_json', + }; + const params = {logger: t.context.logger}; - await t.notThrows(publish(pluginConfig, params)); + const error = await t.throws(publish(pluginConfig, params)); + t.is(error.name, 'JSONError'); }); test.serial('Throw "Error" if the publish script does not returns 0', async t => { From bdb074da72f14eea06112641c62c589202638959 Mon Sep 17 00:00:00 2001 From: Pierre Vanduynslager Date: Mon, 12 Feb 2018 21:38:46 -0500 Subject: [PATCH 2/4] feat: add `success` and `fail` hook --- README.md | 20 ++++++++++++++++++++ index.js | 10 +++++++++- test/fail.test.js | 29 +++++++++++++++++++++++++++++ test/success.test.js | 29 +++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 test/fail.test.js create mode 100644 test/success.test.js diff --git a/README.md b/README.md index 5f354e0c..96e8fa04 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,26 @@ Execute a shell command to publish the release. | `stdout` | Only the `release` information must be written to `stdout` as parseable JSON (for example `{"name": "Release name", "url": "http://url/release/1.0.0"}`). | | `stderr` | Can be used for logging. | +## success + +Execute a shell command to notify of a successful 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. | + +## fail + +Execute a shell command to notify of a failed 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 diff --git a/index.js b/index.js index 4ce0dfc2..f8cb2076 100644 --- a/index.js +++ b/index.js @@ -51,4 +51,12 @@ async function publish(pluginConfig, params) { return stdout.trim() ? parseJson(stdout) : undefined; } -module.exports = {verifyConditions, analyzeCommits, verifyRelease, generateNotes, publish}; +async function success(pluginConfig, params) { + await execScript(pluginConfig, params); +} + +async function fail(pluginConfig, params) { + await execScript(pluginConfig, params); +} + +module.exports = {verifyConditions, analyzeCommits, verifyRelease, generateNotes, publish, success, fail}; diff --git a/test/fail.test.js b/test/fail.test.js new file mode 100644 index 00000000..a4438271 --- /dev/null +++ b/test/fail.test.js @@ -0,0 +1,29 @@ +import test from 'ava'; +import {stub} from 'sinon'; +import {fail} 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 fail script wrote to stdout', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh', + }; + const params = {logger: t.context.logger}; + + await t.notThrows(fail(pluginConfig, params)); +}); + +test.serial('Throw "Error" if if the fail script does not returns 0', async t => { + const pluginConfig = {cmd: 'exit 1'}; + const params = {logger: t.context.logger}; + + await t.throws(fail(pluginConfig, params), Error); +}); diff --git a/test/success.test.js b/test/success.test.js new file mode 100644 index 00000000..8d36817d --- /dev/null +++ b/test/success.test.js @@ -0,0 +1,29 @@ +import test from 'ava'; +import {stub} from 'sinon'; +import {success} 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 success script wrote to stdout', async t => { + const pluginConfig = { + cmd: './test/fixtures/echo-args.sh', + }; + const params = {logger: t.context.logger}; + + await t.notThrows(success(pluginConfig, params)); +}); + +test.serial('Throw "Error" if if the success script does not returns 0', async t => { + const pluginConfig = {cmd: 'exit 1'}; + const params = {logger: t.context.logger}; + + await t.throws(success(pluginConfig, params), Error); +}); From 6e6a7deb696e5ce75ee0e856b9634f488dcbc6ab Mon Sep 17 00:00:00 2001 From: Gregor Martynus Date: Mon, 12 Feb 2018 19:51:30 -0800 Subject: [PATCH 3/4] Update success.test.js --- test/success.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/success.test.js b/test/success.test.js index 8d36817d..de8dacc4 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -21,7 +21,7 @@ test.serial('Return the value success script wrote to stdout', async t => { await t.notThrows(success(pluginConfig, params)); }); -test.serial('Throw "Error" if if the success script does not returns 0', async t => { +test.serial('Throw "Error" if the success script does not returns 0', async t => { const pluginConfig = {cmd: 'exit 1'}; const params = {logger: t.context.logger}; From 6b1611e9608547e3242b2a8b6d40f8c1262a2a4a Mon Sep 17 00:00:00 2001 From: Gregor Martynus Date: Mon, 12 Feb 2018 19:51:48 -0800 Subject: [PATCH 4/4] Update fail.test.js --- test/fail.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fail.test.js b/test/fail.test.js index a4438271..f948fc9e 100644 --- a/test/fail.test.js +++ b/test/fail.test.js @@ -21,7 +21,7 @@ test.serial('Return the value fail script wrote to stdout', async t => { await t.notThrows(fail(pluginConfig, params)); }); -test.serial('Throw "Error" if if the fail script does not returns 0', async t => { +test.serial('Throw "Error" if the fail script does not returns 0', async t => { const pluginConfig = {cmd: 'exit 1'}; const params = {logger: t.context.logger};