Skip to content

Commit 08fe4da

Browse files
committed
feat: add 'draftRelease' option
Fix semantic-release#275
1 parent 82160dd commit 08fe4da

File tree

7 files changed

+120
-8
lines changed

7 files changed

+120
-8
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ If you have actions that trigger on newly created releases, please use a generat
8383
| `assignees` | The [assignees](https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users) to add to the issue created when a release fails. | - |
8484
| `releasedLabels` | The [labels](https://help.github.com/articles/about-labels) to add to each issue and pull request resolved by the release. Set to `false` to not add any label. See [releasedLabels](#releasedlabels). | `['released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>']- |
8585
| `addReleases` | Will add release links to the GitHub Release. Can be `false`, `"bottom"` or `"top"`. See [addReleases](#addReleases). | `false` |
86+
| `draftRelease` | A boolean indicating if a GitHub Draft Release should be created instead of publishing an actual GitHub Release. | `false` |
8687

8788
#### proxy
8889

@@ -212,4 +213,4 @@ Valid values for this option are `false`, `"top"` or `"bottom"`.
212213

213214
##### addReleases example
214215

215-
See [The introducing PR](https://github.com/semantic-release/github/pull/282) for an example on how it will look.
216+
See [The introducing PR](https://github.com/semantic-release/github/pull/282) for an example on how it will look.

lib/definitions/errors.js

+6
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ Your configuration for the \`releasedLabels\` option is \`${stringify(releasedLa
6363
details: `The [addReleases option](${linkify('README.md#options')}) if defined, must be one of \`false|top|bottom\`.
6464
6565
Your configuration for the \`addReleases\` option is \`${stringify(addReleases)}\`.`,
66+
}),
67+
EINVALIDDRAFTRELEASE: ({draftRelease}) => ({
68+
message: 'Invalid `draftRelease` option.',
69+
details: `The [draftRelease option](${linkify('README.md#options')}) if defined, must be a \`Boolean\`.
70+
71+
Your configuration for the \`draftRelease\` option is \`${stringify(draftRelease)}\`.`,
6672
}),
6773
EINVALIDGITHUBURL: () => ({
6874
message: 'The git repository URL is not a valid GitHub URL.',

lib/publish.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ module.exports = async (pluginConfig, context) => {
1818
nextRelease: {name, gitTag, notes},
1919
logger,
2020
} = context;
21-
const {githubToken, githubUrl, githubApiPathPrefix, proxy, assets} = resolveConfig(pluginConfig, context);
21+
const {githubToken, githubUrl, githubApiPathPrefix, proxy, assets, draftRelease} = resolveConfig(
22+
pluginConfig,
23+
context
24+
);
2225
const {owner, repo} = parseGithubUrl(repositoryUrl);
2326
const github = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
2427
const release = {
@@ -33,23 +36,24 @@ module.exports = async (pluginConfig, context) => {
3336

3437
debug('release object: %O', release);
3538

36-
// When there are no assets, we publish a release directly
39+
const draftReleaseOptions = {...release, draft: true};
40+
41+
// When there are no assets, we publish a release directly.
42+
// If draftRelease is true we publish a draft release instead.
3743
if (!assets || assets.length === 0) {
3844
const {
3945
data: {html_url: url, id: releaseId},
40-
} = await github.repos.createRelease(release);
46+
} = await github.repos.createRelease(draftRelease ? draftReleaseOptions : release);
4147

4248
logger.log('Published GitHub release: %s', url);
4349
return {url, name: RELEASE_NAME, id: releaseId};
4450
}
4551

4652
// We'll create a draft release, append the assets to it, and then publish it.
4753
// This is so that the assets are available when we get a Github release event.
48-
const draftRelease = {...release, draft: true};
49-
5054
const {
5155
data: {upload_url: uploadUrl, id: releaseId},
52-
} = await github.repos.createRelease(draftRelease);
56+
} = await github.repos.createRelease(draftReleaseOptions);
5357

5458
// Append assets to the release
5559
const globbedAssets = await globAssets(context, assets);

lib/resolve-config.js

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports = (
1313
assignees,
1414
releasedLabels,
1515
addReleases,
16+
draftRelease,
1617
},
1718
{env}
1819
) => ({
@@ -32,4 +33,5 @@ module.exports = (
3233
? false
3334
: castArray(releasedLabels),
3435
addReleases: isNil(addReleases) ? false : addReleases,
36+
draftRelease: isNil(draftRelease) ? false : draftRelease,
3537
});

lib/verify.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const {isString, isPlainObject, isNil, isArray, isNumber} = require('lodash');
1+
const {isString, isPlainObject, isNil, isArray, isNumber, isBoolean} = require('lodash');
22
const urlJoin = require('url-join');
33
const AggregateError = require('aggregate-error');
44
const parseGithubUrl = require('./parse-github-url');
@@ -27,6 +27,7 @@ const VALIDATORS = {
2727
assignees: isArrayOf(isNonEmptyString),
2828
releasedLabels: canBeDisabled(isArrayOf(isNonEmptyString)),
2929
addReleases: canBeDisabled(oneOf(['bottom', 'top'])),
30+
draftRelease: isBoolean,
3031
};
3132

3233
module.exports = async (pluginConfig, context) => {

test/publish.test.js

+38
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,44 @@ test.serial('Publish a release with an array of missing assets', async (t) => {
372372
t.true(github.isDone());
373373
});
374374

375+
test.serial('Publish a draft release', async (t) => {
376+
const owner = 'test_user';
377+
const repo = 'test_repo';
378+
const env = {GITHUB_TOKEN: 'github_token'};
379+
const pluginConfig = {draftRelease: true};
380+
const nextRelease = {gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'};
381+
const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`};
382+
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
383+
const releaseId = 1;
384+
const uploadUri = `/api/uploads/repos/${owner}/${repo}/releases/${releaseId}/assets`;
385+
const uploadUrl = `https://github.com${uploadUri}{?name,label}`;
386+
const branch = 'test_branch';
387+
388+
const github = authenticate(env)
389+
.post(`/repos/${owner}/${repo}/releases`, {
390+
tag_name: nextRelease.gitTag,
391+
target_commitish: branch,
392+
name: nextRelease.name,
393+
body: nextRelease.notes,
394+
draft: true,
395+
prerelease: false,
396+
})
397+
.reply(200, {upload_url: uploadUrl, html_url: releaseUrl});
398+
399+
const result = await publish(pluginConfig, {
400+
cwd,
401+
env,
402+
options,
403+
branch: {name: branch, type: 'release', main: true},
404+
nextRelease,
405+
logger: t.context.logger,
406+
});
407+
408+
t.is(result.url, releaseUrl);
409+
t.deepEqual(t.context.log.args[0], ['Published GitHub release: %s', releaseUrl]);
410+
t.true(github.isDone());
411+
});
412+
375413
test.serial('Throw error without retries for 400 error', async (t) => {
376414
const owner = 'test_user';
377415
const repo = 'test_repo';

test/verify.test.js

+60
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,44 @@ test.serial('Verify "addReleases" is valid (false)', async (t) => {
416416
t.true(github.isDone());
417417
});
418418

419+
test.serial('Verify "draftRelease" is valid (true)', async (t) => {
420+
const owner = 'test_user';
421+
const repo = 'test_repo';
422+
const env = {GH_TOKEN: 'github_token'};
423+
const draftRelease = true;
424+
const github = authenticate(env)
425+
.get(`/repos/${owner}/${repo}`)
426+
.reply(200, {permissions: {push: true}});
427+
428+
await t.notThrowsAsync(
429+
verify(
430+
{draftRelease},
431+
{env, options: {repositoryUrl: `[email protected]:${owner}/${repo}.git`}, logger: t.context.logger}
432+
)
433+
);
434+
435+
t.true(github.isDone());
436+
});
437+
438+
test.serial('Verify "draftRelease" is valid (false)', async (t) => {
439+
const owner = 'test_user';
440+
const repo = 'test_repo';
441+
const env = {GH_TOKEN: 'github_token'};
442+
const draftRelease = false;
443+
const github = authenticate(env)
444+
.get(`/repos/${owner}/${repo}`)
445+
.reply(200, {permissions: {push: true}});
446+
447+
await t.notThrowsAsync(
448+
verify(
449+
{draftRelease},
450+
{env, options: {repositoryUrl: `[email protected]:${owner}/${repo}.git`}, logger: t.context.logger}
451+
)
452+
);
453+
454+
t.true(github.isDone());
455+
});
456+
419457
// https://github.com/semantic-release/github/issues/182
420458
test.serial('Verify if run in GitHub Action', async (t) => {
421459
const owner = 'test_user';
@@ -1139,3 +1177,25 @@ test.serial('Throw SemanticReleaseError if "addReleases" option is not a valid s
11391177
t.is(error.code, 'EINVALIDADDRELEASES');
11401178
t.true(github.isDone());
11411179
});
1180+
1181+
test.serial('Throw SemanticReleaseError if "draftRelease" option is not a valid boolean (string)', async (t) => {
1182+
const owner = 'test_user';
1183+
const repo = 'test_repo';
1184+
const env = {GH_TOKEN: 'github_token'};
1185+
const draftRelease = 'test';
1186+
const github = authenticate(env)
1187+
.get(`/repos/${owner}/${repo}`)
1188+
.reply(200, {permissions: {push: true}});
1189+
1190+
const [error, ...errors] = await t.throwsAsync(
1191+
verify(
1192+
{draftRelease},
1193+
{env, options: {repositoryUrl: `https://github.com/${owner}/${repo}.git`}, logger: t.context.logger}
1194+
)
1195+
);
1196+
1197+
t.is(errors.length, 0);
1198+
t.is(error.name, 'SemanticReleaseError');
1199+
t.is(error.code, 'EINVALIDDRAFTRELEASE');
1200+
t.true(github.isDone());
1201+
});

0 commit comments

Comments
 (0)