Skip to content

Commit 1b66a36

Browse files
committed
feat: add dedicated command option for each step
1 parent 38baf57 commit 1b66a36

18 files changed

+448
-380
lines changed

README.md

Lines changed: 65 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# @semantic-release/exec
22

3-
Set of [semantic-release](https://github.com/semantic-release/semantic-release) plugins to execute custom shell commands.
3+
[**semantic-release**](https://github.com/semantic-release/semantic-release) plugin to execute custom shell commands.
44

55
[![Travis](https://img.shields.io/travis/semantic-release/exec.svg)](https://travis-ci.org/semantic-release/exec)
66
[![Codecov](https://img.shields.io/codecov/c/github/semantic-release/exec.svg)](https://codecov.io/gh/semantic-release/exec)
@@ -9,7 +9,63 @@ Set of [semantic-release](https://github.com/semantic-release/semantic-release)
99
[![npm latest version](https://img.shields.io/npm/v/@semantic-release/exec/latest.svg)](https://www.npmjs.com/package/@semantic-release/exec)
1010
[![npm next version](https://img.shields.io/npm/v/@semantic-release/exec/next.svg)](https://www.npmjs.com/package/@semantic-release/exec)
1111

12-
## verifyConditions
12+
| Step | Description |
13+
|--------------------|---------------------------------------------------------------------------------------------------------|
14+
| `verifyConditions` | Execute a shell command to verify if the release should happen. |
15+
| `analyzeCommits` | Execute a shell command to determine the type of release. |
16+
| `verifyRelease` | Execute a shell command to verifying a release that was determined before and is about to be published. |
17+
| `generateNotes` | Execute a shell command to generate the release note. |
18+
| `prepare` | Execute a shell command to prepare the release. |
19+
| `publish` | Execute a shell command to publish the release. |
20+
| `success` | Execute a shell command to notify of a new release. |
21+
| `fail` | Execute a shell command to notify of a failed release. |
22+
23+
## Install
24+
25+
```bash
26+
$ npm install @semantic-release/exec -D
27+
```
28+
29+
## Usage
30+
31+
The plugin can be configured in the [**semantic-release** configuration file](https://github.com/semantic-release/semantic-release/blob/caribou/docs/usage/configuration.md#configuration):
32+
33+
```json
34+
{
35+
"plugins": [
36+
"@semantic-release/commit-analyzer",
37+
"@semantic-release/release-notes-generator",
38+
["@semantic-release/exec", {
39+
"verifyConditionsCmd": "./verify.sh",
40+
"publishCmd": "./publish.sh ${nextRelease.version} ${options.branch} ${commits.length} ${Date.now()}"
41+
}],
42+
]
43+
}
44+
```
45+
46+
With this example:
47+
- the shell command `./verify.sh` will be executed on the [verify conditions step](https://github.com/semantic-release/semantic-release#release-steps)
48+
- 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`) will be executed on the [publish step](https://github.com/semantic-release/semantic-release#release-steps)
49+
50+
## Configuration
51+
52+
### Options
53+
54+
| Options | Description |
55+
|-----------------------|-----------------------------------------------------------------------------------------------------------------|
56+
| `verifyConditionsCmd` | The shell command to execute during the verify condition step. See [verifyConditionsCmd](#verifyconditionscmd). |
57+
| `analyzeCommitsCmd` | The shell command to execute during the analyze commits step. See [analyzeCommitsCmd](#analyzecommitscmd). |
58+
| `verifyReleaseCmd` | The shell command to execute during the verify release step. See [verifyReleaseCmd](#verifyreleasecmd). |
59+
| `generateNotesCmd` | The shell command to execute during the generate notes step. See [generateNotesCmd](#generatenotescmd). |
60+
| `prepareCmd` | The shell command to execute during the prepare step. See [prepareCmd](#preparecmd). |
61+
| `publishCmd` | The shell command to execute during the publish step. See [publishCmd](#publishcmd). |
62+
| `successCmd` | The shell command to execute during the success step. See [successCmd](#successcmd). |
63+
| `failCmd` | The shell command to execute during the fail step. See [failCmd](#failcmd). |
64+
| `shell` | The shell to use to run the command. See [execa#shell](https://github.com/sindresorhus/execa#shell). |
65+
66+
Each 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.
67+
68+
## verifyConditionsCmd
1369

1470
Execute a shell command to verify if the release should happen.
1571

@@ -19,130 +75,58 @@ Execute a shell command to verify if the release should happen.
1975
| `stdout` | Write only the reason for the verification to fail. |
2076
| `stderr` | Can be used for logging. |
2177

22-
## analyzeCommits
23-
24-
Execute a shell command to determine the type release.
78+
## analyzeCommitsCmd
2579

2680
| Command property | Description |
2781
|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
2882
| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. |
2983
| `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`. |
3084
| `stderr` | Can be used for logging. |
3185

32-
## verifyRelease
33-
34-
Execute a shell command to verifying a release that was determined before and is about to be published.
86+
## verifyReleaseCmd
3587

3688
| Command property | Description |
3789
|------------------|--------------------------------------------------------------------------|
3890
| `exit code` | `0` if the verification is successful, or any other exit code otherwise. |
3991
| `stdout` | Only the reason for the verification to fail can be written to `stdout`. |
4092
| `stderr` | Can be used for logging. |
4193

42-
## generateNotes
43-
44-
Execute a shell command to generate the release note.
94+
## generateNotesCmd
4595

4696
| Command property | Description |
4797
|------------------|---------------------------------------------------------------------------------------------------------------------|
4898
| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. |
4999
| `stdout` | Only the release note must be written to `stdout`. |
50100
| `stderr` | Can be used for logging. |
51101

52-
## prepare
53-
54-
Execute a shell command to prepare the release.
102+
## prepareCmd
55103

56104
| Command property | Description |
57105
|------------------|---------------------------------------------------------------------------------------------------------------------|
58106
| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. |
59107
| `stdout` | Can be used for logging. |
60108
| `stderr` | Can be used for logging. |
61109

62-
## publish
63-
64-
Execute a shell command to publish the release.
110+
## publishCmd
65111

66112
| Command property | Description |
67113
|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
68114
| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. |
69115
| `stdout` | The `release` information can be written to `stdout` as parseable JSON (for example `{"name": "Release name", "url": "http://url/release/1.0.0"}`). If the command write non parseable JSON to `stdout` no `release` information will be returned. |
70116
| `stderr` | Can be used for logging. |
71117

72-
## success
73-
74-
Execute a shell command to notify of a successful release.
118+
## successCmd
75119

76120
| Command property | Description |
77121
|------------------|---------------------------------------------------------------------------------------------------------------------|
78122
| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. |
79123
| `stdout` | Can be used for logging. |
80124
| `stderr` | Can be used for logging. |
81125

82-
## fail
83-
84-
Execute a shell command to notify of a failed release.
126+
## failCmd
85127

86128
| Command property | Description |
87129
|------------------|---------------------------------------------------------------------------------------------------------------------|
88130
| `exit code` | Any non `0` code is considered as an unexpected error and will stop the `semantic-release` execution with an error. |
89131
| `stdout` | Can be used for logging. |
90132
| `stderr` | Can be used for logging. |
91-
92-
## Configuration
93-
94-
### Options
95-
96-
| Options | Description |
97-
|---------|------------------------------------------------------------------------------------------------------|
98-
| `cmd` | The shell command to execute. See [cmd](#cmd). |
99-
| `shell` | The shell to use to run the command. See [execa#shell](https://github.com/sindresorhus/execa#shell). |
100-
101-
#### `cmd`
102-
103-
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.
104-
105-
##### `cmd` examples
106-
107-
```json
108-
{
109-
"release": {
110-
"publish": [
111-
{
112-
"path": "@semantic-release/exec",
113-
"cmd": "./publish.sh ${nextRelease.version} ${options.branch} ${commits.length} ${Date.now()}",
114-
},
115-
"@semantic-release/npm",
116-
"@semantic-release/github"
117-
]
118-
}
119-
}
120-
```
121-
122-
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`).
123-
124-
### Usage
125-
126-
Options can be set within the plugin definition in the `semantic-release` configuration file:
127-
128-
```json
129-
{
130-
"release": {
131-
"verifyConditions": [
132-
"@semantic-release/npm",
133-
{
134-
"path": "@semantic-release/exec",
135-
"cmd": "./verify.sh",
136-
}
137-
],
138-
"publish": [
139-
"@semantic-release/npm",
140-
{
141-
"path": "@semantic-release/exec",
142-
"cmd": "./publish.sh ${nextRelease.version}",
143-
},
144-
"@semantic-release/github"
145-
]
146-
}
147-
}
148-
```

index.js

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,93 @@
1+
const {isNil} = require('lodash');
12
const parseJson = require('parse-json');
23
const debug = require('debug')('semantic-release:exec');
34
const SemanticReleaseError = require('@semantic-release/error');
4-
const execScript = require('./lib/exec-script');
5+
const exec = require('./lib/exec');
56
const verifyConfig = require('./lib/verify-config');
67

78
async function verifyConditions(pluginConfig, context) {
8-
verifyConfig(pluginConfig);
9-
10-
try {
11-
await execScript(pluginConfig, context);
12-
} catch (error) {
13-
throw new SemanticReleaseError(error.stdout, 'EVERIFYCONDITIONS');
9+
if (!isNil(pluginConfig.verifyConditionsCmd) || !isNil(pluginConfig.cmd)) {
10+
verifyConfig('verifyConditionsCmd', pluginConfig);
11+
12+
try {
13+
await exec('verifyConditionsCmd', pluginConfig, context);
14+
} catch (error) {
15+
throw new SemanticReleaseError(error.stdout, 'EVERIFYCONDITIONS');
16+
}
1417
}
1518
}
1619

1720
async function analyzeCommits(pluginConfig, context) {
18-
verifyConfig(pluginConfig);
21+
if (!isNil(pluginConfig.analyzeCommitsCmd) || !isNil(pluginConfig.cmd)) {
22+
verifyConfig('analyzeCommitsCmd', pluginConfig);
1923

20-
const stdout = await execScript(pluginConfig, context);
21-
return stdout || undefined;
24+
const stdout = await exec('analyzeCommitsCmd', pluginConfig, context);
25+
return stdout || undefined;
26+
}
2227
}
2328

2429
async function verifyRelease(pluginConfig, context) {
25-
verifyConfig(pluginConfig);
26-
27-
try {
28-
await execScript(pluginConfig, context);
29-
} catch (error) {
30-
throw new SemanticReleaseError(error.stdout, 'EVERIFYRELEASE');
30+
if (!isNil(pluginConfig.verifyReleaseCmd) || !isNil(pluginConfig.cmd)) {
31+
verifyConfig('verifyReleaseCmd', pluginConfig);
32+
33+
try {
34+
await exec('verifyReleaseCmd', pluginConfig, context);
35+
} catch (error) {
36+
throw new SemanticReleaseError(error.stdout, 'EVERIFYRELEASE');
37+
}
3138
}
3239
}
3340

3441
async function generateNotes(pluginConfig, context) {
35-
verifyConfig(pluginConfig);
42+
if (!isNil(pluginConfig.generateNotesCmd) || !isNil(pluginConfig.cmd)) {
43+
verifyConfig('generateNotesCmd', pluginConfig);
3644

37-
const stdout = await execScript(pluginConfig, context);
38-
return stdout;
45+
const stdout = await exec('generateNotesCmd', pluginConfig, context);
46+
return stdout;
47+
}
3948
}
4049

4150
async function prepare(pluginConfig, context) {
42-
verifyConfig(pluginConfig);
51+
if (!isNil(pluginConfig.prepareCmd) || !isNil(pluginConfig.cmd)) {
52+
verifyConfig('prepareCmd', pluginConfig);
4353

44-
await execScript(pluginConfig, context);
54+
await exec('prepareCmd', pluginConfig, context);
55+
}
4556
}
4657

4758
async function publish(pluginConfig, context) {
48-
verifyConfig(pluginConfig);
49-
50-
const stdout = await execScript(pluginConfig, context);
51-
52-
try {
53-
return stdout ? parseJson(stdout) : undefined;
54-
} catch (error) {
55-
debug(stdout);
56-
debug(error);
57-
context.logger.log(
58-
`The command ${pluginConfig.cmd} wrote invalid JSON to stdout. The stdout content will be ignored.`
59-
);
59+
if (!isNil(pluginConfig.publishCmd) || !isNil(pluginConfig.cmd)) {
60+
verifyConfig('publishCmd', pluginConfig);
61+
62+
const stdout = await exec('publishCmd', pluginConfig, context);
63+
64+
try {
65+
return stdout ? parseJson(stdout) : undefined;
66+
} catch (error) {
67+
debug(stdout);
68+
debug(error);
69+
context.logger.log(
70+
`The command ${pluginConfig.publishCmd ||
71+
pluginConfig.cmd} wrote invalid JSON to stdout. The stdout content will be ignored.`
72+
);
73+
}
6074
}
6175
}
6276

6377
async function success(pluginConfig, context) {
64-
verifyConfig(pluginConfig);
78+
if (!isNil(pluginConfig.successCmd) || !isNil(pluginConfig.cmd)) {
79+
verifyConfig('successCmd', pluginConfig);
6580

66-
await execScript(pluginConfig, context);
81+
await exec('successCmd', pluginConfig, context);
82+
}
6783
}
6884

6985
async function fail(pluginConfig, context) {
70-
verifyConfig(pluginConfig);
86+
if (!isNil(pluginConfig.failCmd) || !isNil(pluginConfig.cmd)) {
87+
verifyConfig('failCmd', pluginConfig);
7188

72-
await execScript(pluginConfig, context);
89+
await exec('failCmd', pluginConfig, context);
90+
}
7391
}
7492

7593
module.exports = {verifyConditions, analyzeCommits, verifyRelease, generateNotes, prepare, publish, success, fail};

lib/definitions/errors.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const url = require('url');
2+
const {inspect} = require('util');
3+
const {isString} = require('lodash');
4+
const pkg = require('../../package.json');
5+
6+
const homepage = url.format({...url.parse(pkg.homepage), ...{hash: null}});
7+
const stringify = obj => (isString(obj) ? obj : inspect(obj, {breakLength: Infinity, depth: 2, maxArrayLength: 5}));
8+
const linkify = file => `${homepage}/blob/master/${file}`;
9+
10+
module.exports = {
11+
EINVALIDCMD: ({cmd, cmdProp}) => ({
12+
message: `Invalid \`${cmdProp}\` option.`,
13+
details: `The [\`${cmdProp}\` option](${linkify(
14+
`README.md#${cmdProp}`
15+
)}) is required and must be a non empty \`String\`.
16+
17+
Your configuration for the \`${cmdProp}\` option is \`${stringify(cmd)}\`.`,
18+
}),
19+
EINVALIDSHELL: ({shell}) => ({
20+
message: 'Invalid `shell` option.',
21+
details: `The [\`shell\` option](${linkify(
22+
'README.md#options'
23+
)}) if defined, must be a non empty \`String\` or the value \`true\`.
24+
25+
Your configuration for the \`shell\` option is \`${stringify(shell)}\`.`,
26+
}),
27+
};

lib/exec-script.js renamed to lib/exec.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
const {template} = require('lodash');
22
const execa = require('execa');
33

4-
module.exports = async ({cmd, shell, ...config}, {cwd, env, stdout, stderr, logger, ...context}) => {
5-
const script = template(cmd)({config, ...context});
4+
module.exports = async (cmdProp, {shell, ...config}, {cwd, env, stdout, stderr, logger, ...context}) => {
5+
const cmd = config[cmdProp] ? cmdProp : 'cmd';
6+
const script = template(config[cmd])({config, ...context});
67

78
logger.log('Call script %s', script);
89

lib/get-error.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const SemanticReleaseError = require('@semantic-release/error');
2+
const ERROR_DEFINITIONS = require('./definitions/errors');
3+
4+
module.exports = (code, ctx) => {
5+
const {message, details} = ERROR_DEFINITIONS[code](ctx);
6+
return new SemanticReleaseError(message, code, details);
7+
};

0 commit comments

Comments
 (0)