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.
This commit is contained in:
Pierre Vanduynslager
2018-02-19 00:28:50 -05:00
parent 20246c02b1
commit c2beb643fa
14 changed files with 104 additions and 61 deletions
+4
View File
@@ -53,6 +53,9 @@ test.serial('Pass options to semantic-release API', async t => {
'verify2',
'--generate-notes',
'notes',
'--prepare',
'prepare1',
'prepare2',
'--publish',
'publish1',
'publish2',
@@ -76,6 +79,7 @@ test.serial('Pass options to semantic-release API', async t => {
t.is(run.args[0][0].analyzeCommits, 'analyze');
t.deepEqual(run.args[0][0].verifyRelease, ['verify1', 'verify2']);
t.is(run.args[0][0].generateNotes, 'notes');
t.deepEqual(run.args[0][0].prepare, ['prepare1', 'prepare2']);
t.deepEqual(run.args[0][0].publish, ['publish1', 'publish2']);
t.deepEqual(run.args[0][0].success, ['success1', 'success2']);
t.deepEqual(run.args[0][0].fail, ['fail1', 'fail2']);
+11
View File
@@ -46,6 +46,17 @@ test('The "generateNotes" plugin, if defined, must be a single plugin definition
t.true(plugins.generateNotes.config.validator(() => {}));
});
test('The "prepare" plugin, if defined, must be a single or an array of plugins definition', t => {
t.false(plugins.verifyRelease.config.validator({}));
t.false(plugins.verifyRelease.config.validator({path: null}));
t.true(plugins.verifyRelease.config.validator({path: 'plugin-path.js'}));
t.true(plugins.verifyRelease.config.validator());
t.true(plugins.verifyRelease.config.validator('plugin-path.js'));
t.true(plugins.verifyRelease.config.validator(() => {}));
t.true(plugins.verifyRelease.config.validator([{path: 'plugin-path.js'}, 'plugin-path.js', () => {}]));
});
test('The "publish" plugin is mandatory, and must be a single or an array of plugins definition', t => {
t.false(plugins.publish.config.validator({}));
t.false(plugins.publish.config.validator({path: null}));
-13
View File
@@ -10,7 +10,6 @@ import {
push,
gitTags,
isGitRepo,
deleteTag,
verifyTagName,
} from '../lib/git';
import {
@@ -139,18 +138,6 @@ test.serial('Add tag on head commit', async t => {
await t.is(await gitCommitTag(commits[0].hash), 'tag_name');
});
test.serial('Delete a tag', async t => {
// Create a git repository with a remote, set the current working directory at the root of the repo
const repo = await gitRepo(true);
await gitCommits(['Test commit']);
await tag('tag_name');
await push(repo, 'master');
await deleteTag(repo, 'tag_name');
t.falsy(await gitTagHead('tag_name'));
t.falsy(await gitRemoteTagHead(repo, 'tag_name'));
});
test.serial('Push tag and commit to remote repository', async t => {
// Create a git repository with a remote, set the current working directory at the root of the repo
const repo = await gitRepo(true);
+35 -9
View File
@@ -67,6 +67,7 @@ test.serial('Plugins are called with expected values', async t => {
const verifyRelease = stub().resolves();
const generateNotes = stub().resolves(notes);
const release1 = {name: 'Release 1', url: 'https://release1.com'};
const prepare = stub().resolves();
const publish1 = stub().resolves(release1);
const success = stub().resolves();
@@ -77,6 +78,7 @@ test.serial('Plugins are called with expected values', async t => {
analyzeCommits,
verifyRelease,
generateNotes,
prepare,
publish: [publish1, pluginNoop],
success,
};
@@ -119,6 +121,15 @@ test.serial('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes.args[0][1].commits[0].message, commits[0].message);
t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease);
t.is(prepare.callCount, 1);
t.deepEqual(prepare.args[0][0], config);
t.deepEqual(prepare.args[0][1].options, options);
t.deepEqual(prepare.args[0][1].logger, t.context.logger);
t.deepEqual(prepare.args[0][1].lastRelease, lastRelease);
t.deepEqual(prepare.args[0][1].commits[0].hash, commits[0].hash);
t.deepEqual(prepare.args[0][1].commits[0].message, commits[0].message);
t.deepEqual(prepare.args[0][1].nextRelease, {...nextRelease, ...{notes}});
t.is(publish1.callCount, 1);
t.deepEqual(publish1.args[0][0], config);
t.deepEqual(publish1.args[0][1].options, options);
@@ -161,6 +172,7 @@ test.serial('Use custom tag format', async t => {
analyzeCommits: stub().resolves(nextRelease.type),
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(notes),
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
@@ -177,7 +189,7 @@ test.serial('Use custom tag format', async t => {
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease.gitTag), nextRelease.gitHead);
});
test.serial('Use new gitHead, and recreate release notes if a publish plugin create a commit', async t => {
test.serial('Use new gitHead, and recreate release notes if a prepare plugin create a commit', async t => {
// Create a git repository, set the current working directory at the root of the repo
const repositoryUrl = await gitRepo(true);
// Add commits to the master branch
@@ -191,10 +203,11 @@ test.serial('Use new gitHead, and recreate release notes if a publish plugin cre
const notes = 'Release notes';
const generateNotes = stub().resolves(notes);
const publish1 = stub().callsFake(async () => {
const prepare1 = stub().callsFake(async () => {
commits = (await gitCommits(['Third'])).concat(commits);
});
const publish2 = stub().resolves();
const prepare2 = stub().resolves();
const publish = stub().resolves();
const options = {
branch: 'master',
@@ -203,7 +216,8 @@ test.serial('Use new gitHead, and recreate release notes if a publish plugin cre
analyzeCommits: stub().resolves(nextRelease.type),
verifyRelease: stub().resolves(),
generateNotes,
publish: [publish1, publish2],
prepare: [prepare1, prepare2],
publish,
success: stub().resolves(),
fail: stub().resolves(),
};
@@ -217,14 +231,17 @@ test.serial('Use new gitHead, and recreate release notes if a publish plugin cre
t.is(generateNotes.callCount, 2);
t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease);
t.is(publish1.callCount, 1);
t.deepEqual(publish1.args[0][1].nextRelease, {...nextRelease, ...{notes}});
t.is(prepare1.callCount, 1);
t.deepEqual(prepare1.args[0][1].nextRelease, {...nextRelease, ...{notes}});
nextRelease.gitHead = await getGitHead();
t.deepEqual(generateNotes.secondCall.args[1].nextRelease, {...nextRelease, ...{notes}});
t.is(publish2.callCount, 1);
t.deepEqual(publish2.args[0][1].nextRelease, {...nextRelease, ...{notes}});
t.deepEqual(generateNotes.args[1][1].nextRelease, {...nextRelease, ...{notes}});
t.is(prepare2.callCount, 1);
t.deepEqual(prepare2.args[0][1].nextRelease, {...nextRelease, ...{notes}});
t.is(publish.callCount, 1);
t.deepEqual(publish.args[0][1].nextRelease, {...nextRelease, ...{notes}});
// Verify the tag has been created on the local and remote repo and reference the last gitHead
t.is(await gitTagHead(nextRelease.gitTag), commits[0].hash);
@@ -258,6 +275,7 @@ test.serial('Call all "success" plugins even if one errors out', async t => {
verifyConditions: [verifyConditions1, verifyConditions2],
analyzeCommits,
generateNotes,
prepare: stub().resolves(),
publish,
success: [success1, success2],
};
@@ -383,6 +401,7 @@ test.serial('Dry-run skips publish and success', async t => {
analyzeCommits,
verifyRelease,
generateNotes,
prepare: stub().resolves(),
publish,
success,
};
@@ -464,6 +483,7 @@ test.serial('Force a dry-run if not on a CI and "noCi" is not explicitly set', a
analyzeCommits,
verifyRelease,
generateNotes,
prepare: stub().resolves(),
publish,
success,
fail: stub().resolves(),
@@ -512,6 +532,7 @@ test.serial('Allow local releases with "noCi" option', async t => {
analyzeCommits,
verifyRelease,
generateNotes,
prepare: stub().resolves(),
publish,
success,
fail: stub().resolves(),
@@ -560,6 +581,7 @@ test.serial('Accept "undefined" value returned by the "generateNotes" plugins',
analyzeCommits,
verifyRelease,
generateNotes,
prepare: stub().resolves(),
publish,
success: stub().resolves(),
fail: stub().resolves(),
@@ -611,6 +633,7 @@ test.serial('Returns falsy value if not running from the configured branch', asy
analyzeCommits: stub().resolves(),
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(),
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
@@ -646,6 +669,7 @@ test.serial('Returns falsy value if there is no relevant changes', async t => {
analyzeCommits,
verifyRelease,
generateNotes,
prepare: stub().resolves(),
publish,
success: stub().resolves(),
fail: stub().resolves(),
@@ -686,6 +710,7 @@ test.serial('Exclude commits with [skip release] or [release skip] from analysis
analyzeCommits,
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(),
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
@@ -844,6 +869,7 @@ test.serial('Get all commits including the ones not in the shallow clone', async
analyzeCommits,
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(notes),
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
+4
View File
@@ -27,6 +27,7 @@ test('Export default plugins', t => {
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
@@ -49,6 +50,7 @@ test('Export plugins based on config', t => {
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
@@ -79,6 +81,7 @@ test.serial('Export plugins loaded from the dependency of a shareable config mod
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');
@@ -106,6 +109,7 @@ test.serial('Export plugins loaded from the dependency of a shareable config fil
t.is(typeof plugins.analyzeCommits, 'function');
t.is(typeof plugins.verifyRelease, 'function');
t.is(typeof plugins.generateNotes, 'function');
t.is(typeof plugins.prepare, 'function');
t.is(typeof plugins.publish, 'function');
t.is(typeof plugins.success, 'function');
t.is(typeof plugins.fail, 'function');