release-it 0.0.0-pl.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +421 -0
  3. package/bin/release-it.js +42 -0
  4. package/config/release-it.json +70 -0
  5. package/lib/cli.js +44 -0
  6. package/lib/config.js +139 -0
  7. package/lib/index.js +152 -0
  8. package/lib/log.js +69 -0
  9. package/lib/plugin/GitBase.js +125 -0
  10. package/lib/plugin/GitRelease.js +58 -0
  11. package/lib/plugin/Plugin.js +73 -0
  12. package/lib/plugin/factory.js +89 -0
  13. package/lib/plugin/git/Git.js +220 -0
  14. package/lib/plugin/git/prompts.js +19 -0
  15. package/lib/plugin/github/GitHub.js +403 -0
  16. package/lib/plugin/github/prompts.js +16 -0
  17. package/lib/plugin/github/util.js +39 -0
  18. package/lib/plugin/gitlab/GitLab.js +277 -0
  19. package/lib/plugin/gitlab/prompts.js +9 -0
  20. package/lib/plugin/npm/npm.js +281 -0
  21. package/lib/plugin/npm/prompts.js +12 -0
  22. package/lib/plugin/version/Version.js +129 -0
  23. package/lib/prompt.js +33 -0
  24. package/lib/shell.js +91 -0
  25. package/lib/spinner.js +29 -0
  26. package/lib/util.js +109 -0
  27. package/package.json +122 -0
  28. package/test/cli.js +20 -0
  29. package/test/config.js +144 -0
  30. package/test/git.init.js +250 -0
  31. package/test/git.js +358 -0
  32. package/test/github.js +487 -0
  33. package/test/gitlab.js +252 -0
  34. package/test/log.js +143 -0
  35. package/test/npm.js +417 -0
  36. package/test/plugin-name.js +9 -0
  37. package/test/plugins.js +238 -0
  38. package/test/prompt.js +97 -0
  39. package/test/resources/file-v2.0.1.txt +1 -0
  40. package/test/resources/file-v2.0.2.txt +1 -0
  41. package/test/resources/file1 +1 -0
  42. package/test/shell.js +74 -0
  43. package/test/spinner.js +58 -0
  44. package/test/stub/config/default/.release-it.json +5 -0
  45. package/test/stub/config/invalid-config-rc +1 -0
  46. package/test/stub/config/invalid-config-txt +2 -0
  47. package/test/stub/config/merge/.release-it.json +5 -0
  48. package/test/stub/config/merge/package.json +7 -0
  49. package/test/stub/config/toml/.release-it.toml +2 -0
  50. package/test/stub/config/yaml/.release-it.yaml +2 -0
  51. package/test/stub/config/yml/.release-it.yml +2 -0
  52. package/test/stub/github.js +130 -0
  53. package/test/stub/gitlab.js +44 -0
  54. package/test/stub/plugin-context.js +36 -0
  55. package/test/stub/plugin-replace.js +9 -0
  56. package/test/stub/plugin.js +39 -0
  57. package/test/stub/shell.js +24 -0
  58. package/test/tasks.interactive.js +208 -0
  59. package/test/tasks.js +585 -0
  60. package/test/util/helpers.js +32 -0
  61. package/test/util/index.js +78 -0
  62. package/test/util/setup.js +5 -0
  63. package/test/utils.js +97 -0
  64. package/test/version.js +173 -0
package/test/github.js ADDED
@@ -0,0 +1,487 @@
1
+ import test from 'ava';
2
+ import sinon from 'sinon';
3
+ import { RequestError } from '@octokit/request-error';
4
+ import GitHub from '../lib/plugin/github/GitHub.js';
5
+ import { factory, runTasks } from './util/index.js';
6
+ import {
7
+ interceptAuthentication,
8
+ interceptCollaborator,
9
+ interceptListReleases,
10
+ interceptCreate,
11
+ interceptUpdate,
12
+ interceptAsset
13
+ } from './stub/github.js';
14
+
15
+ const tokenRef = 'GITHUB_TOKEN';
16
+ const pushRepo = 'git://github.com/user/repo';
17
+ const host = 'github.com';
18
+ const git = { changelog: '' };
19
+ const requestErrorOptions = { request: { url: '', headers: {} }, response: { headers: {} } };
20
+
21
+ test.serial('should check token and perform checks', async t => {
22
+ const tokenRef = 'MY_GITHUB_TOKEN';
23
+ const options = { github: { release: true, tokenRef, pushRepo } };
24
+ const github = factory(GitHub, { options });
25
+
26
+ process.env[tokenRef] = '123'; // eslint-disable-line require-atomic-updates
27
+
28
+ interceptAuthentication();
29
+ interceptCollaborator();
30
+ await t.notThrowsAsync(github.init());
31
+ });
32
+
33
+ test.serial('should check token and warn', async t => {
34
+ const tokenRef = 'MY_GITHUB_TOKEN';
35
+ const options = { github: { release: true, tokenRef, pushRepo } };
36
+ const github = factory(GitHub, { options });
37
+ delete process.env[tokenRef];
38
+
39
+ await t.notThrowsAsync(github.init());
40
+
41
+ t.is(github.log.warn.args[0][0], 'Environment variable "MY_GITHUB_TOKEN" is required for automated GitHub Releases.');
42
+ t.is(github.log.warn.args[1][0], 'Falling back to web-based GitHub Release.');
43
+ });
44
+
45
+ test('should release and upload assets', async t => {
46
+ const options = {
47
+ git,
48
+ github: {
49
+ pushRepo,
50
+ tokenRef,
51
+ release: true,
52
+ releaseName: 'Release ${tagName}',
53
+ releaseNotes: 'echo Custom notes',
54
+ assets: 'test/resources/file-v${version}.txt'
55
+ }
56
+ };
57
+ const github = factory(GitHub, { options });
58
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
59
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
60
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
61
+
62
+ interceptAuthentication();
63
+ interceptCollaborator();
64
+ interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', body: 'Custom notes' } });
65
+ interceptAsset({ body: '*' });
66
+
67
+ await runTasks(github);
68
+
69
+ const { isReleased, releaseUrl } = github.getContext();
70
+ t.true(isReleased);
71
+ t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
72
+ exec.restore();
73
+ });
74
+
75
+ test('should create a pre-release and draft release notes', async t => {
76
+ const options = {
77
+ git,
78
+ github: {
79
+ pushRepo,
80
+ tokenRef,
81
+ release: true,
82
+ releaseName: 'Release ${tagName}',
83
+ preRelease: true,
84
+ draft: true
85
+ }
86
+ };
87
+ const github = factory(GitHub, { options });
88
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
89
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
90
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
91
+
92
+ interceptAuthentication();
93
+ interceptCollaborator();
94
+ interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', prerelease: true, draft: true } });
95
+
96
+ await runTasks(github);
97
+
98
+ const { isReleased, releaseUrl } = github.getContext();
99
+ t.true(isReleased);
100
+ t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
101
+ exec.restore();
102
+ });
103
+
104
+ test('should create auto-generated release notes', async t => {
105
+ const options = {
106
+ git,
107
+ github: {
108
+ pushRepo,
109
+ tokenRef,
110
+ release: true,
111
+ releaseName: 'Release ${tagName}',
112
+ autoGenerate: true
113
+ }
114
+ };
115
+ const github = factory(GitHub, { options });
116
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
117
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
118
+
119
+ interceptAuthentication();
120
+ interceptCollaborator();
121
+ interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', generate_release_notes: true, body: '' } });
122
+
123
+ await runTasks(github);
124
+
125
+ const { isReleased, releaseUrl } = github.getContext();
126
+ t.true(isReleased);
127
+ t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
128
+ exec.restore();
129
+ });
130
+
131
+ test('should update release and upload assets', async t => {
132
+ const asset = 'file1';
133
+ const options = {
134
+ increment: false,
135
+ git,
136
+ github: {
137
+ update: true,
138
+ pushRepo,
139
+ tokenRef,
140
+ release: true,
141
+ releaseName: 'Release ${tagName}',
142
+ releaseNotes: 'echo Custom notes',
143
+ assets: `test/resources/${asset}`
144
+ }
145
+ };
146
+ const github = factory(GitHub, { options });
147
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
148
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
149
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
150
+ exec.withArgs('git rev-list 2.0.1 --tags --max-count=1').resolves('a123456');
151
+ exec.withArgs('git describe --tags --abbrev=0 "a123456^"').resolves('2.0.1');
152
+
153
+ interceptAuthentication();
154
+ interceptCollaborator();
155
+ interceptListReleases({ tag_name: '2.0.1' });
156
+ interceptUpdate({ body: { tag_name: '2.0.1', name: 'Release 2.0.1', body: 'Custom notes' } });
157
+ interceptAsset({ body: asset });
158
+
159
+ await runTasks(github);
160
+
161
+ const { isReleased, releaseUrl } = github.getContext();
162
+ t.true(isReleased);
163
+ t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.1');
164
+ exec.restore();
165
+ });
166
+
167
+ test('should create custom release notes using releaseNotes function', async t => {
168
+ const options = {
169
+ git,
170
+ github: {
171
+ pushRepo,
172
+ tokenRef,
173
+ release: true,
174
+ releaseName: 'Release ${tagName}',
175
+ releaseNotes(context) {
176
+ return `Custom notes for tag ${context.tagName}`;
177
+ }
178
+ }
179
+ };
180
+ const github = factory(GitHub, { options });
181
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
182
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
183
+
184
+ interceptAuthentication();
185
+ interceptCollaborator();
186
+ interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', body: 'Custom notes for tag 2.0.2' } });
187
+
188
+ await runTasks(github);
189
+
190
+ const { isReleased, releaseUrl } = github.getContext();
191
+ t.true(isReleased);
192
+ t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
193
+ exec.restore();
194
+ });
195
+
196
+ test('should create new release for unreleased tag', async t => {
197
+ const options = {
198
+ increment: false,
199
+ git,
200
+ github: {
201
+ update: true,
202
+ pushRepo,
203
+ tokenRef,
204
+ release: true,
205
+ releaseName: 'Release ${tagName}',
206
+ releaseNotes: 'echo Custom notes'
207
+ }
208
+ };
209
+ const github = factory(GitHub, { options });
210
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
211
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
212
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
213
+ exec.withArgs('git rev-list 2.0.1 --tags --max-count=1').resolves('b123456');
214
+ exec.withArgs('git describe --tags --abbrev=0 "b123456^"').resolves('2.0.1');
215
+
216
+ interceptAuthentication();
217
+ interceptCollaborator();
218
+ interceptListReleases({ tag_name: '2.0.0' });
219
+ interceptCreate({ body: { tag_name: '2.0.1', name: 'Release 2.0.1', body: 'Custom notes' } });
220
+
221
+ await runTasks(github);
222
+
223
+ const { isReleased, releaseUrl } = github.getContext();
224
+ t.true(isReleased);
225
+ t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.1');
226
+ exec.restore();
227
+ });
228
+
229
+ test('should release to enterprise host', async t => {
230
+ const options = { git, github: { tokenRef, pushRepo: 'git://github.example.org/user/repo' } };
231
+ const github = factory(GitHub, { options });
232
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
233
+ exec.withArgs('git remote get-url origin').resolves(`https://github.example.org/user/repo`);
234
+ exec.withArgs('git config --get remote.origin.url').resolves(`https://github.example.org/user/repo`);
235
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves(`1.0.0`);
236
+
237
+ const remote = { api: 'https://github.example.org/api/v3', host: 'github.example.org' };
238
+ interceptAuthentication(remote);
239
+ interceptCollaborator(remote);
240
+ interceptCreate(Object.assign({ body: { tag_name: '1.0.1' } }, remote));
241
+
242
+ await runTasks(github);
243
+
244
+ const { isReleased, releaseUrl } = github.getContext();
245
+ t.true(isReleased);
246
+ t.is(releaseUrl, `https://github.example.org/user/repo/releases/tag/1.0.1`);
247
+ exec.restore();
248
+ });
249
+
250
+ test('should release to alternative host and proxy', async t => {
251
+ const remote = { api: 'https://custom.example.org/api/v3', host: 'custom.example.org' };
252
+ interceptAuthentication(remote);
253
+ interceptCollaborator(remote);
254
+ interceptCreate(Object.assign({ body: { tag_name: '1.0.1' } }, remote));
255
+ const options = {
256
+ git,
257
+ github: {
258
+ tokenRef,
259
+ pushRepo: `git://custom.example.org/user/repo`,
260
+ host: 'custom.example.org',
261
+ proxy: 'http://proxy:8080'
262
+ }
263
+ };
264
+ const github = factory(GitHub, { options });
265
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
266
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
267
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('1.0.0');
268
+
269
+ await runTasks(github);
270
+
271
+ const { isReleased, releaseUrl } = github.getContext();
272
+ t.true(isReleased);
273
+ t.is(releaseUrl, `https://custom.example.org/user/repo/releases/tag/1.0.1`);
274
+ t.is(github.options.proxy, 'http://proxy:8080');
275
+ exec.restore();
276
+ });
277
+
278
+ test('should release to git.pushRepo', async t => {
279
+ const remote = { api: 'https://custom.example.org/api/v3', host: 'custom.example.org' };
280
+ interceptCreate(Object.assign({ body: { tag_name: '1.0.1' } }, remote));
281
+ const options = { git: { pushRepo: 'upstream', changelog: '' }, github: { tokenRef, skipChecks: true } };
282
+ const github = factory(GitHub, { options });
283
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
284
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
285
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('1.0.0');
286
+ exec.withArgs('git remote get-url upstream').resolves('https://custom.example.org/user/repo');
287
+
288
+ await runTasks(github);
289
+
290
+ const { isReleased, releaseUrl } = github.getContext();
291
+ t.true(isReleased);
292
+ t.is(releaseUrl, 'https://custom.example.org/user/repo/releases/tag/1.0.1');
293
+ exec.restore();
294
+ });
295
+
296
+ const testSkipOnActions = process.env.GITHUB_ACTIONS ? test.skip : test;
297
+
298
+ testSkipOnActions('should throw for unauthenticated user', async t => {
299
+ const options = { github: { tokenRef, pushRepo, host } };
300
+ const github = factory(GitHub, { options });
301
+ const stub = sinon.stub(github.client.users, 'getAuthenticated');
302
+ stub.throws(new RequestError('Bad credentials', 401, requestErrorOptions));
303
+
304
+ await t.throwsAsync(runTasks(github), {
305
+ message: /^Could not authenticate with GitHub using environment variable "GITHUB_TOKEN"/
306
+ });
307
+
308
+ t.is(stub.callCount, 1);
309
+ stub.restore();
310
+ });
311
+
312
+ testSkipOnActions('should throw for non-collaborator', async t => {
313
+ interceptAuthentication({ username: 'john' });
314
+ const options = { github: { tokenRef, pushRepo, host } };
315
+ const github = factory(GitHub, { options });
316
+ const stub = sinon.stub(github.client.repos, 'checkCollaborator');
317
+ stub.throws(new RequestError('HttpError', 401, requestErrorOptions));
318
+
319
+ await t.throwsAsync(runTasks(github), { message: /^User john is not a collaborator for user\/repo/ });
320
+
321
+ stub.restore();
322
+ });
323
+
324
+ test.serial('should skip authentication and collaborator checks when running on GitHub Actions', async t => {
325
+ const { GITHUB_ACTIONS, GITHUB_ACTOR } = process.env;
326
+ if (!GITHUB_ACTIONS) {
327
+ process.env.GITHUB_ACTIONS = 1;
328
+ process.env.GITHUB_ACTOR = 'webpro';
329
+ }
330
+
331
+ const options = { github: { tokenRef } };
332
+ const github = factory(GitHub, { options });
333
+ const authStub = sinon.stub(github, 'isAuthenticated');
334
+ const collaboratorStub = sinon.stub(github, 'isCollaborator');
335
+
336
+ await t.notThrowsAsync(github.init());
337
+
338
+ t.is(authStub.callCount, 0);
339
+ t.is(collaboratorStub.callCount, 0);
340
+ t.is(github.getContext('username'), process.env.GITHUB_ACTOR);
341
+
342
+ authStub.restore();
343
+ collaboratorStub.restore();
344
+
345
+ if (!GITHUB_ACTIONS) {
346
+ process.env.GITHUB_ACTIONS = GITHUB_ACTIONS || '';
347
+ process.env.GITHUB_ACTOR = GITHUB_ACTOR || '';
348
+ }
349
+ });
350
+
351
+ test('should handle octokit client error (without retries)', async t => {
352
+ const github = factory(GitHub, { options: { github: { tokenRef, pushRepo, host } } });
353
+ const stub = sinon.stub(github.client.repos, 'createRelease');
354
+ stub.throws(new RequestError('Not found', 404, requestErrorOptions));
355
+ interceptAuthentication();
356
+ interceptCollaborator();
357
+
358
+ await t.throwsAsync(runTasks(github), { message: /^404 \(Not found\)/ });
359
+
360
+ t.is(stub.callCount, 1);
361
+ stub.restore();
362
+ });
363
+
364
+ test('should handle octokit client error (with retries)', async t => {
365
+ const options = { github: { tokenRef, pushRepo, host, retryMinTimeout: 0 } };
366
+ const github = factory(GitHub, { options });
367
+ const stub = sinon.stub(github.client.repos, 'createRelease');
368
+ stub.throws(new RequestError('Request failed', 500, requestErrorOptions));
369
+ interceptAuthentication();
370
+ interceptCollaborator();
371
+
372
+ await t.throwsAsync(runTasks(github), { message: /^500 \(Request failed\)/ });
373
+
374
+ t.is(stub.callCount, 3);
375
+ stub.restore();
376
+ });
377
+
378
+ test('should not call octokit client in dry run', async t => {
379
+ const options = { 'dry-run': true, git, github: { tokenRef, pushRepo, releaseName: 'R ${version}', assets: ['*'] } };
380
+ const github = factory(GitHub, { options });
381
+ const spy = sinon.spy(github, 'client', ['get']);
382
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
383
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
384
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('v1.0.0');
385
+
386
+ await runTasks(github);
387
+
388
+ t.is(spy.get.callCount, 0);
389
+ t.is(github.log.exec.args[1][0], 'octokit repos.createRelease "R 1.0.1" (v1.0.1)');
390
+ t.is(github.log.exec.lastCall.args[0], 'octokit repos.uploadReleaseAssets');
391
+ const { isReleased, releaseUrl } = github.getContext();
392
+ t.true(isReleased);
393
+ t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/v1.0.1');
394
+ spy.get.restore();
395
+ exec.restore();
396
+ });
397
+
398
+ test('should skip checks', async t => {
399
+ const options = { github: { tokenRef, skipChecks: true } };
400
+ const github = factory(GitHub, { options });
401
+ await t.notThrowsAsync(github.init());
402
+ });
403
+
404
+ test('should generate GitHub web release url', async t => {
405
+ const options = {
406
+ github: {
407
+ pushRepo,
408
+ release: true,
409
+ web: true,
410
+ releaseName: 'Release ${tagName}',
411
+ releaseNotes: 'echo Custom notes'
412
+ }
413
+ };
414
+ const github = factory(GitHub, { options });
415
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
416
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
417
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
418
+
419
+ await runTasks(github);
420
+
421
+ const { isReleased, releaseUrl } = github.getContext();
422
+ t.true(isReleased);
423
+ t.is(
424
+ releaseUrl,
425
+ 'https://github.com/user/repo/releases/new?tag=2.0.2&title=Release+2.0.2&body=Custom+notes&prerelease=false'
426
+ );
427
+ exec.restore();
428
+ });
429
+
430
+ test('should generate GitHub web release url for enterprise host', async t => {
431
+ const options = {
432
+ git,
433
+ github: {
434
+ pushRepo: 'git@custom.example.org:user/repo',
435
+ release: true,
436
+ web: true,
437
+ host: 'custom.example.org',
438
+ releaseName: 'The Launch',
439
+ releaseNotes: 'echo It happened'
440
+ }
441
+ };
442
+ const github = factory(GitHub, { options });
443
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
444
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
445
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
446
+
447
+ await runTasks(github);
448
+
449
+ const { isReleased, releaseUrl } = github.getContext();
450
+ t.true(isReleased);
451
+ t.is(
452
+ releaseUrl,
453
+ 'https://custom.example.org/user/repo/releases/new?tag=2.0.2&title=The+Launch&body=It+happened&prerelease=false'
454
+ );
455
+ exec.restore();
456
+ });
457
+
458
+ // eslint-disable-next-line ava/no-skip-test
459
+ test.skip('should truncate long body', async t => {
460
+ const releaseNotes = 'a'.repeat(125001);
461
+ const body = 'a'.repeat(124000) + '...';
462
+ const options = {
463
+ git,
464
+ github: {
465
+ pushRepo,
466
+ tokenRef,
467
+ release: true,
468
+ releaseName: 'Release ${tagName}',
469
+ releaseNotes: 'echo ' + releaseNotes
470
+ }
471
+ };
472
+ const github = factory(GitHub, { options });
473
+ const exec = sinon.stub(github.shell, 'exec').callThrough();
474
+ exec.withArgs('git log --pretty=format:"* %s (%h)" ${from}...${to}').resolves('');
475
+ exec.withArgs('git describe --tags --match=* --abbrev=0').resolves('2.0.1');
476
+
477
+ interceptAuthentication();
478
+ interceptCollaborator();
479
+ interceptCreate({ body: { tag_name: '2.0.2', name: 'Release 2.0.2', body } });
480
+
481
+ await runTasks(github);
482
+
483
+ const { isReleased, releaseUrl } = github.getContext();
484
+ t.true(isReleased);
485
+ t.is(releaseUrl, 'https://github.com/user/repo/releases/tag/2.0.2');
486
+ exec.restore();
487
+ });