Skip to content

Commit 589a5c4

Browse files
committedNov 29, 2018
feat: add addChannel plugin step
1 parent 869c827 commit 589a5c4

16 files changed

+678
-61
lines changed
 

Diff for: ‎README.md

+36-16
Large diffs are not rendered by default.

Diff for: ‎index.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const {defaultTo, castArray} = require('lodash');
22
const verifyGitHub = require('./lib/verify');
3+
const addChannelGitHub = require('./lib/add-channel');
34
const publishGitHub = require('./lib/publish');
45
const successGitHub = require('./lib/success');
56
const failGitHub = require('./lib/fail');
@@ -33,6 +34,14 @@ async function publish(pluginConfig, context) {
3334
return publishGitHub(pluginConfig, context);
3435
}
3536

37+
async function addChannel(pluginConfig, context) {
38+
if (!verified) {
39+
await verifyGitHub(pluginConfig, context);
40+
verified = true;
41+
}
42+
return addChannelGitHub(pluginConfig, context);
43+
}
44+
3645
async function success(pluginConfig, context) {
3746
if (!verified) {
3847
await verifyGitHub(pluginConfig, context);
@@ -49,4 +58,4 @@ async function fail(pluginConfig, context) {
4958
await failGitHub(pluginConfig, context);
5059
}
5160

52-
module.exports = {verifyConditions, publish, success, fail};
61+
module.exports = {verifyConditions, addChannel, publish, success, fail};

Diff for: ‎lib/add-channel.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const parseGithubUrl = require('parse-github-url');
2+
const debug = require('debug')('semantic-release:github');
3+
const {RELEASE_NAME} = require('./definitions/constants');
4+
const resolveConfig = require('./resolve-config');
5+
const getClient = require('./get-client');
6+
const isPrerelease = require('./is-prerelease');
7+
8+
/* eslint-disable camelcase */
9+
10+
module.exports = async (pluginConfig, context) => {
11+
const {
12+
options: {repositoryUrl},
13+
branch,
14+
currentRelease: {gitTag: currentGitTag, channel: currentChannel},
15+
nextRelease: {name, gitTag, notes},
16+
logger,
17+
} = context;
18+
const {githubToken, githubUrl, githubApiPathPrefix, proxy} = resolveConfig(pluginConfig, context);
19+
const {name: repo, owner} = parseGithubUrl(repositoryUrl);
20+
const github = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
21+
let release_id;
22+
23+
const release = {owner, repo, name, prerelease: isPrerelease(branch)};
24+
25+
debug('release owner: %o', release.owner);
26+
debug('release repo: %o', release.repo);
27+
debug('release tag_name: %o', release.tag_name);
28+
debug('release name: %o', release.name);
29+
debug('release prerelease: %o', release.prerelease);
30+
31+
try {
32+
({
33+
data: {id: release_id},
34+
} = await github.repos.getReleaseByTag({owner, repo, tag: currentGitTag}));
35+
} catch (error) {
36+
if (error.status === 404) {
37+
logger.log('There is no release for tag %s, creating a new one', currentGitTag);
38+
39+
debug('release tag_name: %o', gitTag);
40+
41+
const {
42+
data: {html_url: url},
43+
} = await github.repos.createRelease({...release, body: notes, tag_name: gitTag});
44+
45+
logger.log('Published GitHub release: %s', url);
46+
return {url, name: RELEASE_NAME};
47+
}
48+
throw error;
49+
}
50+
51+
const tag_name = currentChannel ? gitTag : undefined;
52+
53+
debug('release release_id: %o', release_id);
54+
debug('release tag_name: %o', tag_name);
55+
56+
const {
57+
data: {html_url: url},
58+
} = await github.repos.updateRelease({...release, release_id, tag_name});
59+
60+
logger.log('Updated GitHub release: %s', url);
61+
62+
return {url, name: RELEASE_NAME};
63+
};

Diff for: ‎lib/definitions/constants.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const ISSUE_ID = '<!-- semantic-release:github -->';
2+
3+
const RELEASE_NAME = 'GitHub release';
4+
5+
module.exports = {ISSUE_ID, RELEASE_NAME};

Diff for: ‎lib/definitions/sr-issue-id.js

-1
This file was deleted.

Diff for: ‎lib/fail.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const {template} = require('lodash');
22
const parseGithubUrl = require('parse-github-url');
33
const debug = require('debug')('semantic-release:github');
4-
const ISSUE_ID = require('./definitions/sr-issue-id');
4+
const {ISSUE_ID} = require('./definitions/constants');
55
const resolveConfig = require('./resolve-config');
66
const getClient = require('./get-client');
77
const findSRIssues = require('./find-sr-issues');

Diff for: ‎lib/find-sr-issues.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const ISSUE_ID = require('./definitions/sr-issue-id');
1+
const {ISSUE_ID} = require('./definitions/constants');
22

33
module.exports = async (github, title, owner, repo) => {
44
const {

Diff for: ‎lib/publish.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const {isPlainObject} = require('lodash');
44
const parseGithubUrl = require('parse-github-url');
55
const mime = require('mime');
66
const debug = require('debug')('semantic-release:github');
7+
const {RELEASE_NAME} = require('./definitions/constants');
78
const globAssets = require('./glob-assets.js');
89
const resolveConfig = require('./resolve-config');
910
const getClient = require('./get-client');
@@ -79,5 +80,5 @@ module.exports = async (pluginConfig, context) => {
7980
);
8081
}
8182

82-
return {url, name: 'GitHub release'};
83+
return {url, name: RELEASE_NAME};
8384
};

Diff for: ‎lib/resolve-config.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,9 @@ module.exports = (
2525
failComment,
2626
labels: isNil(labels) ? ['semantic-release'] : labels === false ? false : castArray(labels),
2727
assignees: assignees ? castArray(assignees) : assignees,
28-
releasedLabels: isNil(releasedLabels) ? ['released'] : releasedLabels === false ? false : castArray(releasedLabels),
28+
releasedLabels: isNil(releasedLabels)
29+
? [`released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>`]
30+
: releasedLabels === false
31+
? false
32+
: castArray(releasedLabels),
2933
});

Diff for: ‎lib/success.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ const findSRIssues = require('./find-sr-issues');
1212

1313
module.exports = async (pluginConfig, context) => {
1414
const {
15-
options: {branch, repositoryUrl},
16-
lastRelease,
15+
options: {repositoryUrl},
1716
commits,
1817
nextRelease,
1918
releases,
@@ -73,7 +72,7 @@ module.exports = async (pluginConfig, context) => {
7372
await Promise.all(
7473
uniqBy([...prs, ...issues], 'number').map(async issue => {
7574
const body = successComment
76-
? template(successComment)({branch, lastRelease, commits, nextRelease, releases, issue})
75+
? template(successComment)({...context, issue})
7776
: getSuccessComment(issue, releaseInfos, nextRelease);
7877
try {
7978
const state = issue.state || (await github.issues.get({owner, repo, number: issue.number})).data.state;
@@ -87,8 +86,9 @@ module.exports = async (pluginConfig, context) => {
8786
logger.log('Added comment to issue #%d: %s', issue.number, url);
8887

8988
if (releasedLabels) {
90-
await github.issues.addLabels({owner, repo, number: issue.number, labels: releasedLabels});
91-
logger.log('Added labels %O to issue #%d', releasedLabels, issue.number);
89+
const labels = releasedLabels.map(label => template(label)(context));
90+
await github.issues.addLabels({owner, repo, number: issue.number, labels});
91+
logger.log('Added labels %O to issue #%d', labels, issue.number);
9292
}
9393
} else {
9494
logger.log("Skip comment and labels on issue #%d as it's open: %s", issue.number);

Diff for: ‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
"all": true
8585
},
8686
"peerDependencies": {
87-
"semantic-release": ">=15.8.0 <16.0.0"
87+
"semantic-release": ">=16.8.0 <17.0.0"
8888
},
8989
"prettier": {
9090
"printWidth": 120,

Diff for: ‎test/add-channel.test.js

+353
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
import test from 'ava';
2+
import nock from 'nock';
3+
import {stub} from 'sinon';
4+
import proxyquire from 'proxyquire';
5+
import {authenticate} from './helpers/mock-github';
6+
import rateLimit from './helpers/rate-limit';
7+
8+
/* eslint camelcase: ["error", {properties: "never"}] */
9+
10+
// const cwd = 'test/fixtures/files';
11+
const addChannel = proxyquire('../lib/add-channel', {
12+
'./get-client': proxyquire('../lib/get-client', {'./definitions/rate-limit': rateLimit}),
13+
});
14+
15+
test.beforeEach(t => {
16+
// Mock logger
17+
t.context.log = stub();
18+
t.context.error = stub();
19+
t.context.logger = {log: t.context.log, error: t.context.error};
20+
});
21+
22+
test.afterEach.always(() => {
23+
// Clear nock
24+
nock.cleanAll();
25+
});
26+
27+
test.serial('Update a release', async t => {
28+
const owner = 'test_user';
29+
const repo = 'test_repo';
30+
const env = {GITHUB_TOKEN: 'github_token'};
31+
const pluginConfig = {};
32+
const currentRelease = {gitTag: 'v1.0.0@next', channel: 'next', name: 'v1.0.0', notes: 'Test release note body'};
33+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
34+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
35+
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
36+
const releaseId = 1;
37+
38+
const github = authenticate(env)
39+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
40+
.reply(200, {id: releaseId})
41+
.patch(`/repos/${owner}/${repo}/releases/${releaseId}`, {
42+
tag_name: nextRelease.gitTag,
43+
name: nextRelease.name,
44+
prerelease: false,
45+
})
46+
.reply(200, {html_url: releaseUrl});
47+
48+
const result = await addChannel(pluginConfig, {
49+
env,
50+
options,
51+
branch: {type: 'release'},
52+
currentRelease,
53+
nextRelease,
54+
logger: t.context.logger,
55+
});
56+
57+
t.is(result.url, releaseUrl);
58+
t.deepEqual(t.context.log.args[0], ['Updated GitHub release: %s', releaseUrl]);
59+
t.true(github.isDone());
60+
});
61+
62+
test.serial('Update a LTS release', async t => {
63+
const owner = 'test_user';
64+
const repo = 'test_repo';
65+
const env = {GITHUB_TOKEN: 'github_token'};
66+
const pluginConfig = {};
67+
const currentRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
68+
const nextRelease = {gitTag: 'v1.0.0@1.x', channel: '1.x', name: 'v1.0.0', notes: 'Test release note body'};
69+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
70+
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
71+
const releaseId = 1;
72+
73+
const github = authenticate(env)
74+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
75+
.reply(200, {id: releaseId})
76+
.patch(`/repos/${owner}/${repo}/releases/${releaseId}`, {
77+
name: nextRelease.name,
78+
prerelease: false,
79+
})
80+
.reply(200, {html_url: releaseUrl});
81+
82+
const result = await addChannel(pluginConfig, {
83+
env,
84+
options,
85+
branch: {type: 'lts', channel: '1.x'},
86+
currentRelease,
87+
nextRelease,
88+
logger: t.context.logger,
89+
});
90+
91+
t.is(result.url, releaseUrl);
92+
t.deepEqual(t.context.log.args[0], ['Updated GitHub release: %s', releaseUrl]);
93+
t.true(github.isDone());
94+
});
95+
96+
test.serial('Update a prerelease', async t => {
97+
const owner = 'test_user';
98+
const repo = 'test_repo';
99+
const env = {GITHUB_TOKEN: 'github_token'};
100+
const pluginConfig = {};
101+
const currentRelease = {
102+
gitTag: 'v1.0.0-beta.1',
103+
channel: 'beta',
104+
name: 'v1.0.0-beta.1',
105+
notes: 'Test release note body',
106+
};
107+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
108+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
109+
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
110+
const releaseId = 1;
111+
112+
const github = authenticate(env)
113+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
114+
.reply(200, {id: releaseId})
115+
.patch(`/repos/${owner}/${repo}/releases/${releaseId}`, {
116+
tag_name: nextRelease.gitTag,
117+
name: nextRelease.name,
118+
prerelease: false,
119+
})
120+
.reply(200, {html_url: releaseUrl});
121+
122+
const result = await addChannel(pluginConfig, {
123+
env,
124+
options,
125+
branch: {type: 'lts', channel: '1.x'},
126+
currentRelease,
127+
nextRelease,
128+
logger: t.context.logger,
129+
});
130+
131+
t.is(result.url, releaseUrl);
132+
t.deepEqual(t.context.log.args[0], ['Updated GitHub release: %s', releaseUrl]);
133+
t.true(github.isDone());
134+
});
135+
136+
test.serial('Update a release with a custom github url', async t => {
137+
const owner = 'test_user';
138+
const repo = 'test_repo';
139+
const env = {GH_URL: 'https://othertesturl.com:443', GH_TOKEN: 'github_token', GH_PREFIX: 'prefix'};
140+
const pluginConfig = {};
141+
const currentRelease = {gitTag: 'v1.0.0@next', channel: 'next', name: 'v1.0.0', notes: 'Test release note body'};
142+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
143+
const options = {repositoryUrl: `${env.GH_URL}/${owner}/${repo}.git`};
144+
const releaseUrl = `${env.GH_URL}/${owner}/${repo}/releases/${nextRelease.version}`;
145+
const releaseId = 1;
146+
147+
const github = authenticate(env)
148+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
149+
.reply(200, {id: releaseId})
150+
.patch(`/repos/${owner}/${repo}/releases/${releaseId}`, {
151+
tag_name: nextRelease.gitTag,
152+
name: nextRelease.name,
153+
prerelease: false,
154+
})
155+
.reply(200, {html_url: releaseUrl});
156+
157+
const result = await addChannel(pluginConfig, {
158+
env,
159+
options,
160+
branch: {type: 'release'},
161+
currentRelease,
162+
nextRelease,
163+
logger: t.context.logger,
164+
});
165+
166+
t.is(result.url, releaseUrl);
167+
t.deepEqual(t.context.log.args[0], ['Updated GitHub release: %s', releaseUrl]);
168+
t.true(github.isDone());
169+
});
170+
171+
test.serial('Update a release, retrying 4 times', async t => {
172+
const owner = 'test_user';
173+
const repo = 'test_repo';
174+
const env = {GITHUB_TOKEN: 'github_token'};
175+
const pluginConfig = {};
176+
const currentRelease = {gitTag: 'v1.0.0@next', channel: 'next', name: 'v1.0.0', notes: 'Test release note body'};
177+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
178+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
179+
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
180+
const releaseId = 1;
181+
182+
const github = authenticate(env)
183+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
184+
.times(3)
185+
.reply(404)
186+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
187+
.reply(200, {id: releaseId})
188+
.patch(`/repos/${owner}/${repo}/releases/${releaseId}`, {
189+
tag_name: nextRelease.gitTag,
190+
name: nextRelease.name,
191+
prerelease: false,
192+
})
193+
.times(3)
194+
.reply(500)
195+
.patch(`/repos/${owner}/${repo}/releases/${releaseId}`, {
196+
tag_name: nextRelease.gitTag,
197+
name: nextRelease.name,
198+
prerelease: false,
199+
})
200+
.reply(200, {html_url: releaseUrl});
201+
202+
const result = await addChannel(pluginConfig, {
203+
env,
204+
options,
205+
branch: {type: 'release'},
206+
currentRelease,
207+
nextRelease,
208+
logger: t.context.logger,
209+
});
210+
211+
t.is(result.url, releaseUrl);
212+
t.deepEqual(t.context.log.args[0], ['Updated GitHub release: %s', releaseUrl]);
213+
t.true(github.isDone());
214+
});
215+
216+
test.serial('Create the new release if current one is missing', async t => {
217+
const owner = 'test_user';
218+
const repo = 'test_repo';
219+
const env = {GITHUB_TOKEN: 'github_token'};
220+
const pluginConfig = {};
221+
const currentRelease = {gitTag: 'v1.0.0@next', channel: 'next', name: 'v1.0.0', notes: 'Test release note body'};
222+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
223+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
224+
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
225+
226+
const github = authenticate(env)
227+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
228+
.times(4)
229+
.reply(404)
230+
.post(`/repos/${owner}/${repo}/releases`, {
231+
tag_name: nextRelease.gitTag,
232+
name: nextRelease.name,
233+
body: nextRelease.notes,
234+
prerelease: false,
235+
})
236+
.reply(200, {html_url: releaseUrl});
237+
238+
const result = await addChannel(pluginConfig, {
239+
env,
240+
options,
241+
branch: {type: 'release'},
242+
currentRelease,
243+
nextRelease,
244+
logger: t.context.logger,
245+
});
246+
247+
t.is(result.url, releaseUrl);
248+
t.deepEqual(t.context.log.args[0], ['There is no release for tag %s, creating a new one', currentRelease.gitTag]);
249+
t.deepEqual(t.context.log.args[1], ['Published GitHub release: %s', releaseUrl]);
250+
t.true(github.isDone());
251+
});
252+
253+
test.serial('Throw error if cannot read current release', async t => {
254+
const owner = 'test_user';
255+
const repo = 'test_repo';
256+
const env = {GITHUB_TOKEN: 'github_token'};
257+
const pluginConfig = {};
258+
const currentRelease = {gitTag: 'v1.0.0@next', channel: 'next', name: 'v1.0.0', notes: 'Test release note body'};
259+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
260+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
261+
262+
const github = authenticate(env)
263+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
264+
.times(4)
265+
.reply(500);
266+
267+
const error = await t.throws(
268+
addChannel(pluginConfig, {
269+
env,
270+
options,
271+
branch: {type: 'release'},
272+
currentRelease,
273+
nextRelease,
274+
logger: t.context.logger,
275+
})
276+
);
277+
278+
t.is(error.status, 500);
279+
t.true(github.isDone());
280+
});
281+
282+
test.serial('Throw error if cannot create missing current release', async t => {
283+
const owner = 'test_user';
284+
const repo = 'test_repo';
285+
const env = {GITHUB_TOKEN: 'github_token'};
286+
const pluginConfig = {};
287+
const currentRelease = {gitTag: 'v1.0.0@next', channel: 'next', name: 'v1.0.0', notes: 'Test release note body'};
288+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
289+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
290+
291+
const github = authenticate(env)
292+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
293+
.times(4)
294+
.reply(404)
295+
.post(`/repos/${owner}/${repo}/releases`, {
296+
tag_name: nextRelease.gitTag,
297+
name: nextRelease.name,
298+
body: nextRelease.notes,
299+
prerelease: false,
300+
})
301+
.times(4)
302+
.reply(500);
303+
304+
const error = await t.throws(
305+
addChannel(pluginConfig, {
306+
env,
307+
options,
308+
branch: {type: 'release'},
309+
currentRelease,
310+
nextRelease,
311+
logger: t.context.logger,
312+
})
313+
);
314+
315+
t.is(error.status, 500);
316+
t.true(github.isDone());
317+
});
318+
319+
test.serial('Throw error if cannot update release', async t => {
320+
const owner = 'test_user';
321+
const repo = 'test_repo';
322+
const env = {GITHUB_TOKEN: 'github_token'};
323+
const pluginConfig = {};
324+
const currentRelease = {gitTag: 'v1.0.0@next', channel: 'next', name: 'v1.0.0', notes: 'Test release note body'};
325+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
326+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
327+
const releaseId = 1;
328+
329+
const github = authenticate(env)
330+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
331+
.reply(200, {id: releaseId})
332+
.patch(`/repos/${owner}/${repo}/releases/${releaseId}`, {
333+
tag_name: nextRelease.gitTag,
334+
name: nextRelease.name,
335+
prerelease: false,
336+
})
337+
.times(4)
338+
.reply(404);
339+
340+
const error = await t.throws(
341+
addChannel(pluginConfig, {
342+
env,
343+
options,
344+
branch: {type: 'release'},
345+
currentRelease,
346+
nextRelease,
347+
logger: t.context.logger,
348+
})
349+
);
350+
351+
t.is(error.status, 404);
352+
t.true(github.isDone());
353+
});

Diff for: ‎test/fail.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import nock from 'nock';
44
import {stub} from 'sinon';
55
import proxyquire from 'proxyquire';
66
import SemanticReleaseError from '@semantic-release/error';
7-
import ISSUE_ID from '../lib/definitions/sr-issue-id';
7+
import {ISSUE_ID} from '../lib/definitions/constants';
88
import {authenticate} from './helpers/mock-github';
99
import rateLimit from './helpers/rate-limit';
1010

Diff for: ‎test/find-sr-issue.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import test from 'ava';
33
import nock from 'nock';
44
import {stub} from 'sinon';
55
import proxyquire from 'proxyquire';
6-
import ISSUE_ID from '../lib/definitions/sr-issue-id';
6+
import {ISSUE_ID} from '../lib/definitions/constants';
77
import findSRIssues from '../lib/find-sr-issues';
88
import {authenticate} from './helpers/mock-github';
99
import rateLimit from './helpers/rate-limit';

Diff for: ‎test/integration.test.js

+95
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,39 @@ test.serial('Publish a release with an array of assets', async t => {
180180
t.true(githubUpload2.isDone());
181181
});
182182

183+
test.serial('Update a release', async t => {
184+
const owner = 'test_user';
185+
const repo = 'test_repo';
186+
const env = {GITHUB_TOKEN: 'github_token'};
187+
const currentRelease = {gitTag: 'v1.0.0@next', channel: 'next', name: 'v1.0.0', notes: 'Test release note body'};
188+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
189+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
190+
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
191+
const releaseId = 1;
192+
193+
const github = authenticate(env)
194+
.get(`/repos/${owner}/${repo}`)
195+
.reply(200, {permissions: {push: true}})
196+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
197+
.reply(200, {id: releaseId})
198+
.patch(`/repos/${owner}/${repo}/releases/${releaseId}`, {
199+
tag_name: nextRelease.gitTag,
200+
name: nextRelease.name,
201+
prerelease: false,
202+
})
203+
.reply(200, {html_url: releaseUrl});
204+
205+
const result = await t.context.m.addChannel(
206+
{},
207+
{cwd, env, options, branch: {type: 'release'}, currentRelease, nextRelease, logger: t.context.logger}
208+
);
209+
210+
t.is(result.url, releaseUrl);
211+
t.deepEqual(t.context.log.args[0], ['Verify GitHub authentication']);
212+
t.deepEqual(t.context.log.args[1], ['Updated GitHub release: %s', releaseUrl]);
213+
t.true(github.isDone());
214+
});
215+
183216
test.serial('Comment and add labels on PR included in the releases', async t => {
184217
const owner = 'test_user';
185218
const repo = 'test_repo';
@@ -339,6 +372,68 @@ test.serial('Verify, release and notify success', async t => {
339372
t.true(githubUpload2.isDone());
340373
});
341374

375+
test.serial('Verify, update release and notify success', async t => {
376+
const owner = 'test_user';
377+
const repo = 'test_repo';
378+
const env = {GITHUB_TOKEN: 'github_token'};
379+
const failTitle = 'The automated release is failing 🚨';
380+
const options = {
381+
publish: [{path: '@semantic-release/npm'}, {path: '@semantic-release/github'}],
382+
repositoryUrl: `https://github.com/${owner}/${repo}.git`,
383+
};
384+
const currentRelease = {gitTag: 'v1.0.0@next', channel: 'next', name: 'v1.0.0', notes: 'Test release note body'};
385+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
386+
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
387+
const releaseId = 1;
388+
const prs = [{number: 1, pull_request: {}, state: 'closed'}];
389+
const commits = [{hash: '123', message: 'Commit 1 message', tree: {long: 'aaa'}}];
390+
const github = authenticate(env)
391+
.get(`/repos/${owner}/${repo}`)
392+
.reply(200, {permissions: {push: true}})
393+
.get(`/repos/${owner}/${repo}/releases/tags/${currentRelease.gitTag}`)
394+
.reply(200, {id: releaseId})
395+
.patch(`/repos/${owner}/${repo}/releases/${releaseId}`, {
396+
tag_name: nextRelease.gitTag,
397+
name: nextRelease.name,
398+
prerelease: false,
399+
})
400+
.reply(200, {html_url: releaseUrl})
401+
.get(`/repos/${owner}/${repo}`)
402+
.reply(200, {full_name: `${owner}/${repo}`})
403+
.get(
404+
`/search/issues?q=${escape(`repo:${owner}/${repo}`)}+${escape('type:pr')}+${escape('is:merged')}+${commits
405+
.map(commit => commit.hash)
406+
.join('+')}`
407+
)
408+
.reply(200, {items: prs})
409+
.get(`/repos/${owner}/${repo}/pulls/1/commits`)
410+
.reply(200, [{sha: commits[0].hash}])
411+
.post(`/repos/${owner}/${repo}/issues/1/comments`, {body: /This PR is included/})
412+
.reply(200, {html_url: 'https://github.com/successcomment-1'})
413+
.post(`/repos/${owner}/${repo}/issues/1/labels`, '{"labels":["released"]}')
414+
.reply(200, {})
415+
.get(
416+
`/search/issues?q=${escape('in:title')}+${escape(`repo:${owner}/${repo}`)}+${escape('type:issue')}+${escape(
417+
'state:open'
418+
)}+${escape(failTitle)}`
419+
)
420+
.reply(200, {items: []});
421+
422+
await t.notThrows(t.context.m.verifyConditions({}, {cwd, env, options, logger: t.context.logger}));
423+
await t.context.m.addChannel(
424+
{},
425+
{cwd, env, branch: {type: 'release'}, currentRelease, nextRelease, options, logger: t.context.logger}
426+
);
427+
await t.context.m.success(
428+
{failTitle},
429+
{cwd, env, options, nextRelease, commits, releases: [], logger: t.context.logger}
430+
);
431+
432+
t.deepEqual(t.context.log.args[0], ['Verify GitHub authentication']);
433+
t.deepEqual(t.context.log.args[1], ['Updated GitHub release: %s', releaseUrl]);
434+
t.true(github.isDone());
435+
});
436+
342437
test.serial('Verify and notify failure', async t => {
343438
const owner = 'test_user';
344439
const repo = 'test_repo';

Diff for: ‎test/success.test.js

+99-31
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {repeat} from 'lodash';
44
import nock from 'nock';
55
import {stub} from 'sinon';
66
import proxyquire from 'proxyquire';
7-
import ISSUE_ID from '../lib/definitions/sr-issue-id';
7+
import {ISSUE_ID} from '../lib/definitions/constants';
88
import {authenticate} from './helpers/mock-github';
99
import rateLimit from './helpers/rate-limit';
1010

@@ -124,7 +124,7 @@ test.serial(
124124
{hash: '456', message: 'Commit 2 message'},
125125
{hash: '789', message: `Commit 3 message Closes https://custom-url.com/${owner}/${repo}/issues/4`},
126126
];
127-
const nextRelease = {version: '1.0.0'};
127+
const nextRelease = {version: '1.0.0', channel: 'next'};
128128
const releases = [{name: 'GitHub release', url: 'https://custom-url.com/release'}];
129129
const github = authenticate(env)
130130
.get(`/repos/${owner}/${repo}`)
@@ -141,23 +141,23 @@ test.serial(
141141
.reply(200, [{sha: commits[1].hash}])
142142
.post(`/repos/${owner}/${repo}/issues/1/comments`, {body: /This PR is included/})
143143
.reply(200, {html_url: 'https://custom-url.com/successcomment-1'})
144-
.post(`/repos/${owner}/${repo}/issues/1/labels`, '{"labels":["released"]}')
144+
.post(`/repos/${owner}/${repo}/issues/1/labels`, '{"labels":["released on @next"]}')
145145
.reply(200, {})
146146
.post(`/repos/${owner}/${repo}/issues/2/comments`, {body: /This PR is included/})
147147
.reply(200, {html_url: 'https://custom-url.com/successcomment-2'})
148-
.post(`/repos/${owner}/${repo}/issues/2/labels`, '{"labels":["released"]}')
148+
.post(`/repos/${owner}/${repo}/issues/2/labels`, '{"labels":["released on @next"]}')
149149
.reply(200, {})
150150
.get(`/repos/${owner}/${repo}/issues/3`)
151151
.reply(200, {state: 'closed'})
152152
.post(`/repos/${owner}/${repo}/issues/3/comments`, {body: /This issue has been resolved/})
153153
.reply(200, {html_url: 'https://custom-url.com/successcomment-3'})
154-
.post(`/repos/${owner}/${repo}/issues/3/labels`, '{"labels":["released"]}')
154+
.post(`/repos/${owner}/${repo}/issues/3/labels`, '{"labels":["released on @next"]}')
155155
.reply(200, {})
156156
.get(`/repos/${owner}/${repo}/issues/4`)
157157
.reply(200, {state: 'closed'})
158158
.post(`/repos/${owner}/${repo}/issues/4/comments`, {body: /This issue has been resolved/})
159159
.reply(200, {html_url: 'https://custom-url.com/successcomment-4'})
160-
.post(`/repos/${owner}/${repo}/issues/4/labels`, '{"labels":["released"]}')
160+
.post(`/repos/${owner}/${repo}/issues/4/labels`, '{"labels":["released on @next"]}')
161161
.reply(200, {})
162162
.get(
163163
`/search/issues?q=${escape('in:title')}+${escape(`repo:${owner}/${repo}`)}+${escape('type:issue')}+${escape(
@@ -169,13 +169,13 @@ test.serial(
169169
await success(pluginConfig, {env, options, commits, nextRelease, releases, logger: t.context.logger});
170170

171171
t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://custom-url.com/successcomment-1'));
172-
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released'], 1));
172+
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released on @next'], 1));
173173
t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 2, 'https://custom-url.com/successcomment-2'));
174-
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released'], 2));
174+
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released on @next'], 2));
175175
t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 3, 'https://custom-url.com/successcomment-3'));
176-
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released'], 3));
176+
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released on @next'], 3));
177177
t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 4, 'https://custom-url.com/successcomment-4'));
178-
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released'], 4));
178+
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released on @next'], 4));
179179
t.true(github.isDone());
180180
}
181181
);
@@ -504,20 +504,21 @@ test.serial('Ignore missing issues/PRs', async t => {
504504
t.true(github.isDone());
505505
});
506506

507-
test.serial('Add custom comment', async t => {
507+
test.serial('Add custom comment and labels', async t => {
508508
const owner = 'test_user';
509509
const repo = 'test_repo';
510510
const env = {GITHUB_TOKEN: 'github_token'};
511511
const failTitle = 'The automated release is failing 🚨';
512512
const pluginConfig = {
513-
successComment: `last release: \${lastRelease.version} nextRelease: \${nextRelease.version} branch: \${branch} commits: \${commits.length} releases: \${releases.length} PR attribute: \${issue.prop}`,
513+
successComment: `last release: \${lastRelease.version} nextRelease: \${nextRelease.version} branch: \${branch.name} commits: \${commits.length} releases: \${releases.length} PR attribute: \${issue.prop}`,
514514
failTitle,
515+
releasedLabels: ['released on @<%= nextRelease.channel %>', 'released from <%= branch.name %>'],
515516
};
516517
const prs = [{number: 1, prop: 'PR prop', pull_request: {}, state: 'closed'}];
517-
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
518+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
518519
const lastRelease = {version: '1.0.0'};
519520
const commits = [{hash: '123', message: 'Commit 1 message'}];
520-
const nextRelease = {version: '2.0.0'};
521+
const nextRelease = {version: '2.0.0', channel: 'next'};
521522
const releases = [{name: 'GitHub release', url: 'https://github.com/release'}];
522523
const github = authenticate(env)
523524
.get(`/repos/${owner}/${repo}`)
@@ -534,7 +535,7 @@ test.serial('Add custom comment', async t => {
534535
body: /last release: 1\.0\.0 nextRelease: 2\.0\.0 branch: master commits: 1 releases: 1 PR attribute: PR prop/,
535536
})
536537
.reply(200, {html_url: 'https://github.com/successcomment-1'})
537-
.post(`/repos/${owner}/${repo}/issues/1/labels`, '{"labels":["released"]}')
538+
.post(`/repos/${owner}/${repo}/issues/1/labels`, '{"labels":["released on @next","released from master"]}')
538539
.reply(200, {})
539540
.get(
540541
`/search/issues?q=${escape('in:title')}+${escape(`repo:${owner}/${repo}`)}+${escape('type:issue')}+${escape(
@@ -543,10 +544,19 @@ test.serial('Add custom comment', async t => {
543544
)
544545
.reply(200, {items: []});
545546

546-
await success(pluginConfig, {env, options, lastRelease, commits, nextRelease, releases, logger: t.context.logger});
547+
await success(pluginConfig, {
548+
env,
549+
branch: {name: 'master'},
550+
options,
551+
lastRelease,
552+
commits,
553+
nextRelease,
554+
releases,
555+
logger: t.context.logger,
556+
});
547557

548558
t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://github.com/successcomment-1'));
549-
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released'], 1));
559+
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['released on @next', 'released from master'], 1));
550560
t.true(github.isDone());
551561
});
552562

@@ -557,7 +567,7 @@ test.serial('Add custom label', async t => {
557567
const failTitle = 'The automated release is failing 🚨';
558568
const pluginConfig = {releasedLabels: ['custom label'], failTitle};
559569
const prs = [{number: 1, pull_request: {}, state: 'closed'}];
560-
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
570+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
561571
const lastRelease = {version: '1.0.0'};
562572
const commits = [{hash: '123', message: 'Commit 1 message'}];
563573
const nextRelease = {version: '2.0.0'};
@@ -584,7 +594,16 @@ test.serial('Add custom label', async t => {
584594
)
585595
.reply(200, {items: []});
586596

587-
await success(pluginConfig, {env, options, lastRelease, commits, nextRelease, releases, logger: t.context.logger});
597+
await success(pluginConfig, {
598+
env,
599+
options,
600+
branch: {name: 'master'},
601+
lastRelease,
602+
commits,
603+
nextRelease,
604+
releases,
605+
logger: t.context.logger,
606+
});
588607

589608
t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://github.com/successcomment-1'));
590609
t.true(t.context.log.calledWith('Added labels %O to issue #%d', ['custom label'], 1));
@@ -598,7 +617,7 @@ test.serial('Comment on issue/PR without ading a label', async t => {
598617
const failTitle = 'The automated release is failing 🚨';
599618
const pluginConfig = {releasedLabels: false, failTitle};
600619
const prs = [{number: 1, pull_request: {}, state: 'closed'}];
601-
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
620+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
602621
const lastRelease = {version: '1.0.0'};
603622
const commits = [{hash: '123', message: 'Commit 1 message'}];
604623
const nextRelease = {version: '2.0.0'};
@@ -623,7 +642,16 @@ test.serial('Comment on issue/PR without ading a label', async t => {
623642
)
624643
.reply(200, {items: []});
625644

626-
await success(pluginConfig, {env, options, lastRelease, commits, nextRelease, releases, logger: t.context.logger});
645+
await success(pluginConfig, {
646+
env,
647+
options,
648+
branch: {name: 'master'},
649+
lastRelease,
650+
commits,
651+
nextRelease,
652+
releases,
653+
logger: t.context.logger,
654+
});
627655

628656
t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://github.com/successcomment-1'));
629657
t.true(github.isDone());
@@ -641,7 +669,7 @@ test.serial('Ignore errors when adding comments and closing issues', async t =>
641669
{number: 3, body: `Issue 3 body\n\n${ISSUE_ID}`, title: failTitle},
642670
];
643671
const prs = [{number: 1, pull_request: {}, state: 'closed'}, {number: 2, pull_request: {}, state: 'closed'}];
644-
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
672+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
645673
const commits = [{hash: '123', message: 'Commit 1 message'}, {hash: '456', message: 'Commit 2 message'}];
646674
const nextRelease = {version: '1.0.0'};
647675
const releases = [{name: 'GitHub release', url: 'https://github.com/release'}];
@@ -675,7 +703,15 @@ test.serial('Ignore errors when adding comments and closing issues', async t =>
675703
.reply(200, {html_url: 'https://github.com/issues/3'});
676704

677705
const [error1, error2] = await t.throws(
678-
success(pluginConfig, {env, options, commits, nextRelease, releases, logger: t.context.logger})
706+
success(pluginConfig, {
707+
env,
708+
options,
709+
branch: {name: 'master'},
710+
commits,
711+
nextRelease,
712+
releases,
713+
logger: t.context.logger,
714+
})
679715
);
680716

681717
t.is(error1.status, 400);
@@ -698,7 +734,7 @@ test.serial('Close open issues when a release is successful', async t => {
698734
{number: 2, body: `Issue 2 body\n\n${ISSUE_ID}`, title: failTitle},
699735
{number: 3, body: `Issue 3 body\n\n${ISSUE_ID}`, title: failTitle},
700736
];
701-
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
737+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
702738
const commits = [{hash: '123', message: 'Commit 1 message'}];
703739
const nextRelease = {version: '1.0.0'};
704740
const releases = [{name: 'GitHub release', url: 'https://github.com/release'}];
@@ -722,7 +758,15 @@ test.serial('Close open issues when a release is successful', async t => {
722758
.patch(`/repos/${owner}/${repo}/issues/3`, {state: 'closed'})
723759
.reply(200, {html_url: 'https://github.com/issues/3'});
724760

725-
await success(pluginConfig, {env, options, commits, nextRelease, releases, logger: t.context.logger});
761+
await success(pluginConfig, {
762+
env,
763+
options,
764+
branch: {name: 'master'},
765+
commits,
766+
nextRelease,
767+
releases,
768+
logger: t.context.logger,
769+
});
726770

727771
t.true(t.context.log.calledWith('Closed issue #%d: %s.', 2, 'https://github.com/issues/2'));
728772
t.true(t.context.log.calledWith('Closed issue #%d: %s.', 3, 'https://github.com/issues/3'));
@@ -735,7 +779,7 @@ test.serial('Skip commention on issues/PR if "successComment" is "false"', async
735779
const env = {GITHUB_TOKEN: 'github_token'};
736780
const failTitle = 'The automated release is failing 🚨';
737781
const pluginConfig = {failTitle, successComment: false};
738-
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
782+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
739783
const commits = [{hash: '123', message: 'Commit 1 message\n\n Fix #1', tree: {long: 'aaa'}}];
740784
const nextRelease = {version: '1.0.0'};
741785
const releases = [{name: 'GitHub release', url: 'https://github.com/release'}];
@@ -749,7 +793,15 @@ test.serial('Skip commention on issues/PR if "successComment" is "false"', async
749793
)
750794
.reply(200, {items: []});
751795

752-
await success(pluginConfig, {env, options, commits, nextRelease, releases, logger: t.context.logger});
796+
await success(pluginConfig, {
797+
env,
798+
options,
799+
branch: {name: 'master'},
800+
commits,
801+
nextRelease,
802+
releases,
803+
logger: t.context.logger,
804+
});
753805

754806
t.true(t.context.log.calledWith('Skip commenting on issues and pull requests.'));
755807
t.true(github.isDone());
@@ -760,7 +812,7 @@ test.serial('Skip closing issues if "failComment" is "false"', async t => {
760812
const repo = 'test_repo';
761813
const env = {GITHUB_TOKEN: 'github_token'};
762814
const pluginConfig = {failComment: false};
763-
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
815+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
764816
const commits = [{hash: '123', message: 'Commit 1 message'}];
765817
const nextRelease = {version: '1.0.0'};
766818
const releases = [{name: 'GitHub release', url: 'https://github.com/release'}];
@@ -774,7 +826,15 @@ test.serial('Skip closing issues if "failComment" is "false"', async t => {
774826
)
775827
.reply(200, {items: []});
776828

777-
await success(pluginConfig, {env, options, commits, nextRelease, releases, logger: t.context.logger});
829+
await success(pluginConfig, {
830+
env,
831+
options,
832+
branch: {name: 'master'},
833+
commits,
834+
nextRelease,
835+
releases,
836+
logger: t.context.logger,
837+
});
778838
t.true(t.context.log.calledWith('Skip closing issue.'));
779839
t.true(github.isDone());
780840
});
@@ -784,7 +844,7 @@ test.serial('Skip closing issues if "failTitle" is "false"', async t => {
784844
const repo = 'test_repo';
785845
const env = {GITHUB_TOKEN: 'github_token'};
786846
const pluginConfig = {failTitle: false};
787-
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
847+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
788848
const commits = [{hash: '123', message: 'Commit 1 message'}];
789849
const nextRelease = {version: '1.0.0'};
790850
const releases = [{name: 'GitHub release', url: 'https://github.com/release'}];
@@ -798,7 +858,15 @@ test.serial('Skip closing issues if "failTitle" is "false"', async t => {
798858
)
799859
.reply(200, {items: []});
800860

801-
await success(pluginConfig, {env, options, commits, nextRelease, releases, logger: t.context.logger});
861+
await success(pluginConfig, {
862+
env,
863+
options,
864+
branch: {name: 'master'},
865+
commits,
866+
nextRelease,
867+
releases,
868+
logger: t.context.logger,
869+
});
802870
t.true(t.context.log.calledWith('Skip closing issue.'));
803871
t.true(github.isDone());
804872
});

0 commit comments

Comments
 (0)
Please sign in to comment.