Skip to content

Commit c2beb64

Browse files
committed
feat: add the prepare plugin hook
BREAKING CHANGE: Committing or creating files in the `publish` plugin hook is not supported anymore and now must be done in the `prepare` hook Plugins with a `publish` hook that makes a commit or create a file that can be committed must use the `prepare` hook.
1 parent 20246c0 commit c2beb64

File tree

14 files changed

+104
-61
lines changed

14 files changed

+104
-61
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ After running the tests the command `semantic-release` will execute the followin
8686
| Verify release | Verify the release conformity with the [verify release plugins](docs/usage/plugins.md#verifyrelease-plugin). |
8787
| Generate notes | Generate release notes with the [generate notes plugin](docs/usage/plugins.md#generatenotes-plugin) for the commits added since the last release. |
8888
| Create Git tag | Create a Git tag corresponding the new release version |
89+
| Prepare | Prepare the release with the [prepare plugins](docs/usage/plugins.md#prepare-plugin). |
8990
| Publish | Publish the release with the [publish plugins](docs/usage/plugins.md#publish-plugin). |
9091
| Notify | Notify of new releases or errors with the [success](docs/usage/plugins.md#success-plugin) and [fail](docs/usage/plugins.md#fail-plugin) plugins. |
9192

cli.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Usage:
2525
.option('analyze-commits', {type: 'string', group: 'Plugins'})
2626
.option('verify-release', {...stringList, group: 'Plugins'})
2727
.option('generate-notes', {type: 'string', group: 'Plugins'})
28+
.option('prepare', {...stringList, group: 'Plugins'})
2829
.option('publish', {...stringList, group: 'Plugins'})
2930
.option('success', {...stringList, group: 'Plugins'})
3031
.option('fail', {...stringList, group: 'Plugins'})

docs/extending/plugins-list.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [fail](https://github.com/semantic-release/github#fail): Open a GitHub issue when a release fails
1010
- [@semantic-release/npm](https://github.com/semantic-release/npm)
1111
- [verifyConditions](https://github.com/semantic-release/npm#verifyconditions): Verify the presence and the validity of the npm authentication and release configuration
12+
- [prepare](https://github.com/semantic-release/npm#prepare): Update the package.json version and create the npm package tarball
1213
- [publish](https://github.com/semantic-release/npm#publish): Publish the package on the npm registry
1314

1415
## Official plugins
@@ -18,15 +19,16 @@
1819
- [publish](https://github.com/semantic-release/gitlab#publish): Publish a [GitLab release](https://docs.gitlab.com/ce/workflow/releases.html)
1920
- [@semantic-release/git](https://github.com/semantic-release/git)
2021
- [verifyConditions](https://github.com/semantic-release/git#verifyconditions): Verify the presence and the validity of the Git authentication and release configuration
21-
- [publish](https://github.com/semantic-release/git#publish): Push a release commit and tag, including configurable files
22+
- [prepare](https://github.com/semantic-release/git#prepare): Push a release commit and tag, including configurable files
2223
- [@semantic-release/changelog](https://github.com/semantic-release/changelog)
2324
- [verifyConditions](https://github.com/semantic-release/changelog#verifyconditions): Verify the presence and the validity of the configuration
24-
- [publish](https://github.com/semantic-release/changelog#publish): Create or update the changelog file in the local project repository
25+
- [prepare](https://github.com/semantic-release/changelog#prepare): Create or update the changelog file in the local project repository
2526
- [@semantic-release/exec](https://github.com/semantic-release/exec)
2627
- [verifyConditions](https://github.com/semantic-release/exec#verifyconditions): Execute a shell command to verify if the release should happen
2728
- [analyzeCommits](https://github.com/semantic-release/exec#analyzecommits): Execute a shell command to determine the type of release
2829
- [verifyRelease](https://github.com/semantic-release/exec#verifyrelease): Execute a shell command to verifying a release that was determined before and is about to be published.
2930
- [generateNotes](https://github.com/semantic-release/exec#analyzecommits): Execute a shell command to generate the release note
31+
- [prepare](https://github.com/semantic-release/exec#prepare): Execute a shell command to prepare the release
3032
- [publish](https://github.com/semantic-release/exec#publish): Execute a shell command to publish the release
3133
- [success](https://github.com/semantic-release/exec#success): Execute a shell command to notify of a new release
3234
- [fail](https://github.com/semantic-release/exec#fail): Execute a shell command to notify of a failed release

docs/usage/configuration.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ Define the [generate notes plugin](plugins.md#generatenotes-plugin).
155155

156156
See [Plugins configuration](plugins.md#configuration) for more details.
157157

158+
### prepare
159+
160+
Type: `Array`, `String`, `Object`
161+
162+
Default: `['@semantic-release/npm']`
163+
164+
CLI argument: `--prepare`
165+
166+
Define the list of [prepare plugins](plugins.md#prepare-plugin). Plugins will run in series, in the order defined in the `Array`.
167+
168+
See [Plugins configuration](plugins.md#configuration) for more details.
169+
158170
### publish
159171

160172
Type: `Array`, `String`, `Object`

docs/usage/plugins.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ Plugin responsible for generating release notes.
2828

2929
Default implementation: [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator).
3030

31+
### prepare plugin
32+
33+
Plugin responsible for preparing the release, including:
34+
- Creating or updating files such as `package.json`, `CHANGELOG.md`, documentation or compiled assets.
35+
- Create and push commits
36+
37+
Default implementation: [npm](https://github.com/semantic-release/npm#prepare).
38+
3139
### publish plugin
3240

3341
Plugin responsible for publishing the release.

index.js

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const getCommits = require('./lib/get-commits');
1212
const getLastRelease = require('./lib/get-last-release');
1313
const {extractErrors} = require('./lib/utils');
1414
const logger = require('./lib/logger');
15-
const {unshallow, gitHead: getGitHead, tag, push, deleteTag} = require('./lib/git');
15+
const {unshallow, gitHead: getGitHead, tag, push} = require('./lib/git');
1616

1717
marked.setOptions({renderer: new TerminalRenderer()});
1818

@@ -41,7 +41,7 @@ async function run(options, plugins) {
4141
await verify(options);
4242

4343
logger.log('Run automated release from branch %s', options.branch);
44-
44+
console.log(options);
4545
logger.log('Call plugin %s', 'verify-conditions');
4646
await plugins.verifyConditions({options, logger}, {settleAll: true});
4747

@@ -79,26 +79,14 @@ async function run(options, plugins) {
7979
logger.log('Call plugin %s', 'generateNotes');
8080
nextRelease.notes = await plugins.generateNotes(generateNotesParam);
8181

82-
// Create the tag before calling the publish plugins as some require the tag to exists
83-
logger.log('Create tag %s', nextRelease.gitTag);
84-
await tag(nextRelease.gitTag);
85-
await push(options.repositoryUrl, branch);
86-
87-
logger.log('Call plugin %s', 'publish');
88-
const releases = await plugins.publish(
82+
logger.log('Call plugin %s', 'prepare');
83+
await plugins.prepare(
8984
{options, logger, lastRelease, commits, nextRelease},
9085
{
9186
getNextInput: async lastResult => {
9287
const newGitHead = await getGitHead();
93-
// If previous publish plugin has created a commit (gitHead changed)
88+
// If previous prepare plugin has created a commit (gitHead changed)
9489
if (lastResult.nextRelease.gitHead !== newGitHead) {
95-
// Delete the previously created tag
96-
await deleteTag(options.repositoryUrl, nextRelease.gitTag);
97-
// Recreate the tag, referencing the new gitHead
98-
logger.log('Create tag %s', nextRelease.gitTag);
99-
await tag(nextRelease.gitTag);
100-
await push(options.repositoryUrl, branch);
101-
10290
nextRelease.gitHead = newGitHead;
10391
// Regenerate the release notes
10492
logger.log('Call plugin %s', 'generateNotes');
@@ -107,11 +95,21 @@ async function run(options, plugins) {
10795
// Call the next publish plugin with the updated `nextRelease`
10896
return {options, logger, lastRelease, commits, nextRelease};
10997
},
110-
// Add nextRelease and plugin properties to published release
111-
transform: (release, step) => ({...(isPlainObject(release) ? release : {}), ...nextRelease, ...step}),
11298
}
11399
);
114100

101+
// Create the tag before calling the publish plugins as some require the tag to exists
102+
logger.log('Create tag %s', nextRelease.gitTag);
103+
await tag(nextRelease.gitTag);
104+
await push(options.repositoryUrl, branch);
105+
106+
logger.log('Call plugin %s', 'publish');
107+
const releases = await plugins.publish(
108+
{options, logger, lastRelease, commits, nextRelease},
109+
// Add nextRelease and plugin properties to published release
110+
{transform: (release, step) => ({...(isPlainObject(release) ? release : {}), ...nextRelease, ...step})}
111+
);
112+
115113
await plugins.success(
116114
{options, logger, lastRelease, commits, nextRelease, releases: castArray(releases)},
117115
{settleAll: true}

lib/definitions/plugins.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ module.exports = {
3636
error: 'ERELEASENOTESOUTPUT',
3737
},
3838
},
39+
prepare: {
40+
default: ['@semantic-release/npm'],
41+
config: {
42+
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
43+
},
44+
},
3945
publish: {
4046
default: ['@semantic-release/npm', '@semantic-release/github'],
4147
config: {

lib/git.js

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -115,22 +115,6 @@ async function push(origin, branch) {
115115
await execa('git', ['push', '--tags', origin, `HEAD:${branch}`]);
116116
}
117117

118-
/**
119-
* Delete a tag locally and remotely.
120-
*
121-
* @param {String} origin The remote repository URL.
122-
* @param {String} tagName The tag name to delete.
123-
*/
124-
async function deleteTag(origin, tagName) {
125-
// Delete the local tag
126-
let shell = await execa('git', ['tag', '-d', tagName], {reject: false});
127-
debug('delete local tag', shell);
128-
129-
// Delete the tag remotely
130-
shell = await execa('git', ['push', '--delete', origin, tagName], {reject: false});
131-
debug('delete remote tag', shell);
132-
}
133-
134118
/**
135119
* Verify a tag name is a valid Git reference.
136120
*
@@ -157,6 +141,5 @@ module.exports = {
157141
verifyAuth,
158142
tag,
159143
push,
160-
deleteTag,
161144
verifyTagName,
162145
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"@semantic-release/commit-analyzer": "^5.0.0",
2323
"@semantic-release/error": "^2.2.0",
2424
"@semantic-release/github": "^4.1.0",
25-
"@semantic-release/npm": "^3.1.0",
25+
"@semantic-release/npm": "^3.2.0",
2626
"@semantic-release/release-notes-generator": "^6.0.0",
2727
"aggregate-error": "^1.0.0",
2828
"chalk": "^2.3.0",

test/cli.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ test.serial('Pass options to semantic-release API', async t => {
5353
'verify2',
5454
'--generate-notes',
5555
'notes',
56+
'--prepare',
57+
'prepare1',
58+
'prepare2',
5659
'--publish',
5760
'publish1',
5861
'publish2',
@@ -76,6 +79,7 @@ test.serial('Pass options to semantic-release API', async t => {
7679
t.is(run.args[0][0].analyzeCommits, 'analyze');
7780
t.deepEqual(run.args[0][0].verifyRelease, ['verify1', 'verify2']);
7881
t.is(run.args[0][0].generateNotes, 'notes');
82+
t.deepEqual(run.args[0][0].prepare, ['prepare1', 'prepare2']);
7983
t.deepEqual(run.args[0][0].publish, ['publish1', 'publish2']);
8084
t.deepEqual(run.args[0][0].success, ['success1', 'success2']);
8185
t.deepEqual(run.args[0][0].fail, ['fail1', 'fail2']);

test/definitions/plugins.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ test('The "generateNotes" plugin, if defined, must be a single plugin definition
4646
t.true(plugins.generateNotes.config.validator(() => {}));
4747
});
4848

49+
test('The "prepare" plugin, if defined, must be a single or an array of plugins definition', t => {
50+
t.false(plugins.verifyRelease.config.validator({}));
51+
t.false(plugins.verifyRelease.config.validator({path: null}));
52+
53+
t.true(plugins.verifyRelease.config.validator({path: 'plugin-path.js'}));
54+
t.true(plugins.verifyRelease.config.validator());
55+
t.true(plugins.verifyRelease.config.validator('plugin-path.js'));
56+
t.true(plugins.verifyRelease.config.validator(() => {}));
57+
t.true(plugins.verifyRelease.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
58+
});
59+
4960
test('The "publish" plugin is mandatory, and must be a single or an array of plugins definition', t => {
5061
t.false(plugins.publish.config.validator({}));
5162
t.false(plugins.publish.config.validator({path: null}));

test/git.test.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
push,
1111
gitTags,
1212
isGitRepo,
13-
deleteTag,
1413
verifyTagName,
1514
} from '../lib/git';
1615
import {
@@ -139,18 +138,6 @@ test.serial('Add tag on head commit', async t => {
139138
await t.is(await gitCommitTag(commits[0].hash), 'tag_name');
140139
});
141140

142-
test.serial('Delete a tag', async t => {
143-
// Create a git repository with a remote, set the current working directory at the root of the repo
144-
const repo = await gitRepo(true);
145-
await gitCommits(['Test commit']);
146-
await tag('tag_name');
147-
await push(repo, 'master');
148-
149-
await deleteTag(repo, 'tag_name');
150-
t.falsy(await gitTagHead('tag_name'));
151-
t.falsy(await gitRemoteTagHead(repo, 'tag_name'));
152-
});
153-
154141
test.serial('Push tag and commit to remote repository', async t => {
155142
// Create a git repository with a remote, set the current working directory at the root of the repo
156143
const repo = await gitRepo(true);

0 commit comments

Comments
 (0)