From dea56d87ee16a341e5ddc0b21fdc65b2bcc19aff Mon Sep 17 00:00:00 2001 From: Nils Plaschke Date: Tue, 1 Sep 2020 03:10:24 +0200 Subject: [PATCH] feat: add additional release links to the release A new option `addReleases` has been added. Setting this option will add links to other releases to the Github release body. The option can be one of `false|"top"|"bottom"`. The default is `false` to be backward compatible. Closes #281 --- README.md | 10 ++ lib/definitions/errors.js | 6 + lib/get-release-links.js | 22 +++ lib/publish.js | 6 +- lib/resolve-config.js | 2 + lib/success.js | 21 ++- lib/verify.js | 2 + test/get-release-links.test.js | 74 ++++++++ test/success.test.js | 311 +++++++++++++++++++++++++++++++++ test/verify.test.js | 123 +++++++++++++ 10 files changed, 573 insertions(+), 4 deletions(-) create mode 100644 lib/get-release-links.js create mode 100644 test/get-release-links.test.js diff --git a/README.md b/README.md index 1615c05e..eba76e74 100644 --- a/README.md +++ b/README.md @@ -204,3 +204,13 @@ Each label name is generated with [Lodash template](https://lodash.com/docs#temp The `releasedLabels` ```['released<%= nextRelease.channel ? ` on @\${nextRelease.channel}` : "" %> from <%= branch.name %>']``` will generate the label: > released on @next from branch next + +#### addReleases + +Add links to other releases to the GitHub release body. + +Valid values for this option are `false`, `"top"` or `"bottom"`. + +##### addReleases example + +See [The introducing PR](https://github.com/semantic-release/github/pull/282) for an example on how it will look. \ No newline at end of file diff --git a/lib/definitions/errors.js b/lib/definitions/errors.js index 0ce17ee8..952ff5cf 100644 --- a/lib/definitions/errors.js +++ b/lib/definitions/errors.js @@ -57,6 +57,12 @@ Your configuration for the \`assignees\` option is \`${stringify(assignees)}\`.` )}) if defined, must be an \`Array\` of non empty \`String\`. Your configuration for the \`releasedLabels\` option is \`${stringify(releasedLabels)}\`.`, + }), + EINVALIDADDRELEASES: ({addReleases}) => ({ + message: 'Invalid `addReleases` option.', + details: `The [addReleases option](${linkify('README.md#options')}) if defined, must be one of \`false|top|bottom\`. + +Your configuration for the \`addReleases\` option is \`${stringify(addReleases)}\`.`, }), EINVALIDGITHUBURL: () => ({ message: 'The git repository URL is not a valid GitHub URL.', diff --git a/lib/get-release-links.js b/lib/get-release-links.js new file mode 100644 index 00000000..a9daf489 --- /dev/null +++ b/lib/get-release-links.js @@ -0,0 +1,22 @@ +const {RELEASE_NAME} = require('./definitions/constants'); + +const linkify = (releaseInfo) => + `${ + releaseInfo.url + ? releaseInfo.url.startsWith('http') + ? `[${releaseInfo.name}](${releaseInfo.url})` + : `${releaseInfo.name}: \`${releaseInfo.url}\`` + : `\`${releaseInfo.name}\`` + }`; + +const filterReleases = (releaseInfos) => + releaseInfos.filter((releaseInfo) => releaseInfo.name && releaseInfo.name !== RELEASE_NAME); + +module.exports = (releaseInfos) => + `${ + filterReleases(releaseInfos).length > 0 + ? `This release is also available on:\n${filterReleases(releaseInfos) + .map((releaseInfo) => `- ${linkify(releaseInfo)}`) + .join('\n')}` + : '' + }`; diff --git a/lib/publish.js b/lib/publish.js index 7c87fc61..01cfeff4 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -28,11 +28,11 @@ module.exports = async (pluginConfig, context) => { // When there are no assets, we publish a release directly if (!assets || assets.length === 0) { const { - data: {html_url: url}, + data: {html_url: url, id: releaseId}, } = await github.repos.createRelease(release); logger.log('Published GitHub release: %s', url); - return {url, name: RELEASE_NAME}; + return {url, name: RELEASE_NAME, id: releaseId}; } // We'll create a draft release, append the assets to it, and then publish it. @@ -94,5 +94,5 @@ module.exports = async (pluginConfig, context) => { } = await github.repos.updateRelease({owner, repo, release_id: releaseId, draft: false}); logger.log('Published GitHub release: %s', url); - return {url, name: RELEASE_NAME}; + return {url, name: RELEASE_NAME, id: releaseId}; }; diff --git a/lib/resolve-config.js b/lib/resolve-config.js index 22c1eae7..c4635814 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -12,6 +12,7 @@ module.exports = ( labels, assignees, releasedLabels, + addReleases, }, {env} ) => ({ @@ -30,4 +31,5 @@ module.exports = ( : releasedLabels === false ? false : castArray(releasedLabels), + addReleases: isNil(addReleases) ? false : addReleases, }); diff --git a/lib/success.js b/lib/success.js index 3fb1c61f..2cf32e07 100644 --- a/lib/success.js +++ b/lib/success.js @@ -1,4 +1,4 @@ -const {isNil, uniqBy, template, flatten} = require('lodash'); +const {isNil, uniqBy, template, flatten, isEmpty} = require('lodash'); const pFilter = require('p-filter'); const AggregateError = require('aggregate-error'); const issueParser = require('issue-parser'); @@ -9,6 +9,8 @@ const getClient = require('./get-client'); const getSearchQueries = require('./get-search-queries'); const getSuccessComment = require('./get-success-comment'); const findSRIssues = require('./find-sr-issues'); +const {RELEASE_NAME} = require('./definitions/constants'); +const getReleaseLinks = require('./get-release-links'); module.exports = async (pluginConfig, context) => { const { @@ -17,6 +19,7 @@ module.exports = async (pluginConfig, context) => { nextRelease, releases, logger, + notes, } = context; const { githubToken, @@ -27,6 +30,7 @@ module.exports = async (pluginConfig, context) => { failComment, failTitle, releasedLabels, + addReleases, } = resolveConfig(pluginConfig, context); const github = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy}); @@ -140,6 +144,21 @@ module.exports = async (pluginConfig, context) => { ); } + if (addReleases !== false && errors.length === 0) { + const ghRelease = releases.find((release) => release.name && release.name === RELEASE_NAME); + if (!isNil(ghRelease)) { + const ghRelaseId = ghRelease.id; + const additionalReleases = getReleaseLinks(releases); + if (!isEmpty(additionalReleases) && !isNil(ghRelaseId)) { + const newBody = + addReleases === 'top' + ? additionalReleases.concat('\n---\n', notes) + : notes.concat('\n---\n', additionalReleases); + await github.repos.updateRelease({owner, repo, release_id: ghRelaseId, body: newBody}); + } + } + } + if (errors.length > 0) { throw new AggregateError(errors); } diff --git a/lib/verify.js b/lib/verify.js index 5ee7cd4b..465e1665 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -7,6 +7,7 @@ const getClient = require('./get-client'); const getError = require('./get-error'); const isNonEmptyString = (value) => isString(value) && value.trim(); +const oneOf = (enumArray) => (value) => enumArray.some((element) => element === value); const isStringOrStringArray = (value) => isNonEmptyString(value) || (isArray(value) && value.every((string) => isNonEmptyString(string))); const isArrayOf = (validator) => (array) => isArray(array) && array.every((value) => validator(value)); @@ -24,6 +25,7 @@ const VALIDATORS = { labels: canBeDisabled(isArrayOf(isNonEmptyString)), assignees: isArrayOf(isNonEmptyString), releasedLabels: canBeDisabled(isArrayOf(isNonEmptyString)), + addReleases: canBeDisabled(oneOf(['bottom', 'top'])), }; module.exports = async (pluginConfig, context) => { diff --git a/test/get-release-links.test.js b/test/get-release-links.test.js new file mode 100644 index 00000000..6096dfbd --- /dev/null +++ b/test/get-release-links.test.js @@ -0,0 +1,74 @@ +const test = require('ava'); +const getReleaseLinks = require('../lib/get-release-links'); +const {RELEASE_NAME} = require('../lib/definitions/constants'); + +test('Comment for release with multiple releases', (t) => { + const releaseInfos = [ + {name: RELEASE_NAME, url: 'https://github.com/release'}, + {name: 'Http release', url: 'https://release.com/release'}, + {name: 'npm release', url: 'https://npm.com/release'}, + ]; + const comment = getReleaseLinks(releaseInfos); + + t.is( + comment, + `This release is also available on: +- [Http release](https://release.com/release) +- [npm release](https://npm.com/release)` + ); +}); + +test('Release with missing release URL', (t) => { + const releaseInfos = [ + {name: RELEASE_NAME, url: 'https://github.com/release'}, + {name: 'Http release', url: 'https://release.com/release'}, + {name: 'npm release'}, + ]; + const comment = getReleaseLinks(releaseInfos); + + t.is( + comment, + `This release is also available on: +- [Http release](https://release.com/release) +- \`npm release\`` + ); +}); + +test('Release with one release', (t) => { + const releaseInfos = [ + {name: RELEASE_NAME, url: 'https://github.com/release'}, + {name: 'Http release', url: 'https://release.com/release'}, + ]; + const comment = getReleaseLinks(releaseInfos); + + t.is( + comment, + `This release is also available on: +- [Http release](https://release.com/release)` + ); +}); + +test('Release with non http releases', (t) => { + const releaseInfos = [{name: 'S3', url: 's3://my-bucket/release-asset'}]; + const comment = getReleaseLinks(releaseInfos); + + t.is( + comment, + `This release is also available on: +- S3: \`s3://my-bucket/release-asset\`` + ); +}); + +test('Release with only github release', (t) => { + const releaseInfos = [{name: RELEASE_NAME, url: 'https://github.com/release'}]; + const comment = getReleaseLinks(releaseInfos); + + t.is(comment, ''); +}); + +test('Comment with no release object', (t) => { + const releaseInfos = []; + const comment = getReleaseLinks(releaseInfos); + + t.is(comment, ''); +}); diff --git a/test/success.test.js b/test/success.test.js index 02a3397c..a9f3cd57 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -7,6 +7,7 @@ const proxyquire = require('proxyquire'); const {ISSUE_ID} = require('../lib/definitions/constants'); const {authenticate} = require('./helpers/mock-github'); const rateLimit = require('./helpers/rate-limit'); +const getReleaseLinks = require('../lib/get-release-links'); /* eslint camelcase: ["error", {properties: "never"}] */ @@ -624,6 +625,316 @@ test.serial('Comment on issue/PR without ading a label', async (t) => { t.true(github.isDone()); }); +test.serial('Editing the release to include all release links at the bottom', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITHUB_TOKEN: 'github_token'}; + const failTitle = 'The automated release is failing 🚨'; + const pluginConfig = {releasedLabels: false, addReleases: 'bottom'}; + const prs = [{number: 1, pull_request: {}, state: 'closed'}]; + const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`}; + const nextRelease = {version: '2.0.0', gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'}; + const lastRelease = {version: '1.0.0'}; + const commits = [{hash: '123', message: 'Commit 1 message'}]; + const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`; + const releaseId = 1; + const releases = [ + {name: 'GitHub release', url: 'https://github.com/release', id: releaseId}, + {name: 'S3', url: 's3://my-bucket/release-asset'}, + {name: 'Docker: docker.io/python:slim'}, + ]; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {full_name: `${owner}/${repo}`}) + .get( + `/search/issues?q=${escape(`repo:${owner}/${repo}`)}+${escape('type:pr')}+${escape('is:merged')}+${commits + .map((commit) => commit.hash) + .join('+')}` + ) + .reply(200, {items: prs}) + .get(`/repos/${owner}/${repo}/pulls/1/commits`) + .reply(200, [{sha: commits[0].hash}]) + .post(`/repos/${owner}/${repo}/issues/1/comments`, {body: /This PR is included/}) + .reply(200, {html_url: 'https://github.com/successcomment-1'}) + .get( + `/search/issues?q=${escape('in:title')}+${escape(`repo:${owner}/${repo}`)}+${escape('type:issue')}+${escape( + 'state:open' + )}+${escape(failTitle)}` + ) + .reply(200, {items: []}) + .patch(`/repos/${owner}/${repo}/releases/${releaseId}`, { + body: nextRelease.notes.concat('\n---\n', getReleaseLinks(releases)), + }) + .reply(200, {html_url: releaseUrl}); + + await success(pluginConfig, { + env, + options, + branch: {name: 'master'}, + lastRelease, + commits, + nextRelease, + releases, + notes: nextRelease.notes, + logger: t.context.logger, + }); + + t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://github.com/successcomment-1')); + t.true(github.isDone()); +}); + +test.serial('Editing the release to include all release links at the top', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITHUB_TOKEN: 'github_token'}; + const failTitle = 'The automated release is failing 🚨'; + const pluginConfig = {releasedLabels: false, addReleases: 'top'}; + const prs = [{number: 1, pull_request: {}, state: 'closed'}]; + const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`}; + const nextRelease = {version: '2.0.0', gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'}; + const lastRelease = {version: '1.0.0'}; + const commits = [{hash: '123', message: 'Commit 1 message'}]; + const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`; + const releaseId = 1; + const releases = [ + {name: 'GitHub release', url: 'https://github.com/release', id: releaseId}, + {name: 'S3', url: 's3://my-bucket/release-asset'}, + {name: 'Docker: docker.io/python:slim'}, + ]; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {full_name: `${owner}/${repo}`}) + .get( + `/search/issues?q=${escape(`repo:${owner}/${repo}`)}+${escape('type:pr')}+${escape('is:merged')}+${commits + .map((commit) => commit.hash) + .join('+')}` + ) + .reply(200, {items: prs}) + .get(`/repos/${owner}/${repo}/pulls/1/commits`) + .reply(200, [{sha: commits[0].hash}]) + .post(`/repos/${owner}/${repo}/issues/1/comments`, {body: /This PR is included/}) + .reply(200, {html_url: 'https://github.com/successcomment-1'}) + .get( + `/search/issues?q=${escape('in:title')}+${escape(`repo:${owner}/${repo}`)}+${escape('type:issue')}+${escape( + 'state:open' + )}+${escape(failTitle)}` + ) + .reply(200, {items: []}) + .patch(`/repos/${owner}/${repo}/releases/${releaseId}`, { + body: getReleaseLinks(releases).concat('\n---\n', nextRelease.notes), + }) + .reply(200, {html_url: releaseUrl}); + + await success(pluginConfig, { + env, + options, + branch: {name: 'master'}, + lastRelease, + commits, + nextRelease, + releases, + notes: nextRelease.notes, + logger: t.context.logger, + }); + + t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://github.com/successcomment-1')); + t.true(github.isDone()); +}); + +test.serial('Editing the release to include all release links with no additional releases (top)', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITHUB_TOKEN: 'github_token'}; + const failTitle = 'The automated release is failing 🚨'; + const pluginConfig = {releasedLabels: false, addReleases: 'top'}; + const prs = [{number: 1, pull_request: {}, state: 'closed'}]; + const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`}; + const nextRelease = {version: '2.0.0', gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'}; + const lastRelease = {version: '1.0.0'}; + const commits = [{hash: '123', message: 'Commit 1 message'}]; + const releaseId = 1; + const releases = [{name: 'GitHub release', url: 'https://github.com/release', id: releaseId}]; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {full_name: `${owner}/${repo}`}) + .get( + `/search/issues?q=${escape(`repo:${owner}/${repo}`)}+${escape('type:pr')}+${escape('is:merged')}+${commits + .map((commit) => commit.hash) + .join('+')}` + ) + .reply(200, {items: prs}) + .get(`/repos/${owner}/${repo}/pulls/1/commits`) + .reply(200, [{sha: commits[0].hash}]) + .post(`/repos/${owner}/${repo}/issues/1/comments`, {body: /This PR is included/}) + .reply(200, {html_url: 'https://github.com/successcomment-1'}) + .get( + `/search/issues?q=${escape('in:title')}+${escape(`repo:${owner}/${repo}`)}+${escape('type:issue')}+${escape( + 'state:open' + )}+${escape(failTitle)}` + ) + .reply(200, {items: []}); + + await success(pluginConfig, { + env, + options, + branch: {name: 'master'}, + lastRelease, + commits, + nextRelease, + releases, + logger: t.context.logger, + }); + + t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://github.com/successcomment-1')); + t.true(github.isDone()); +}); + +test.serial('Editing the release to include all release links with no additional releases (bottom)', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITHUB_TOKEN: 'github_token'}; + const failTitle = 'The automated release is failing 🚨'; + const pluginConfig = {releasedLabels: false, addReleases: 'bottom'}; + const prs = [{number: 1, pull_request: {}, state: 'closed'}]; + const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`}; + const nextRelease = {version: '2.0.0', gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'}; + const lastRelease = {version: '1.0.0'}; + const commits = [{hash: '123', message: 'Commit 1 message'}]; + const releaseId = 1; + const releases = [{name: 'GitHub release', url: 'https://github.com/release', id: releaseId}]; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {full_name: `${owner}/${repo}`}) + .get( + `/search/issues?q=${escape(`repo:${owner}/${repo}`)}+${escape('type:pr')}+${escape('is:merged')}+${commits + .map((commit) => commit.hash) + .join('+')}` + ) + .reply(200, {items: prs}) + .get(`/repos/${owner}/${repo}/pulls/1/commits`) + .reply(200, [{sha: commits[0].hash}]) + .post(`/repos/${owner}/${repo}/issues/1/comments`, {body: /This PR is included/}) + .reply(200, {html_url: 'https://github.com/successcomment-1'}) + .get( + `/search/issues?q=${escape('in:title')}+${escape(`repo:${owner}/${repo}`)}+${escape('type:issue')}+${escape( + 'state:open' + )}+${escape(failTitle)}` + ) + .reply(200, {items: []}); + + await success(pluginConfig, { + env, + options, + branch: {name: 'master'}, + lastRelease, + commits, + nextRelease, + releases, + logger: t.context.logger, + }); + + t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://github.com/successcomment-1')); + t.true(github.isDone()); +}); + +test.serial('Editing the release to include all release links with no releases', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITHUB_TOKEN: 'github_token'}; + const failTitle = 'The automated release is failing 🚨'; + const pluginConfig = {releasedLabels: false, addReleases: 'bottom'}; + const prs = [{number: 1, pull_request: {}, state: 'closed'}]; + const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`}; + const nextRelease = {version: '2.0.0', gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'}; + const lastRelease = {version: '1.0.0'}; + const commits = [{hash: '123', message: 'Commit 1 message'}]; + const releases = []; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {full_name: `${owner}/${repo}`}) + .get( + `/search/issues?q=${escape(`repo:${owner}/${repo}`)}+${escape('type:pr')}+${escape('is:merged')}+${commits + .map((commit) => commit.hash) + .join('+')}` + ) + .reply(200, {items: prs}) + .get(`/repos/${owner}/${repo}/pulls/1/commits`) + .reply(200, [{sha: commits[0].hash}]) + .post(`/repos/${owner}/${repo}/issues/1/comments`, {body: /This PR is included/}) + .reply(200, {html_url: 'https://github.com/successcomment-1'}) + .get( + `/search/issues?q=${escape('in:title')}+${escape(`repo:${owner}/${repo}`)}+${escape('type:issue')}+${escape( + 'state:open' + )}+${escape(failTitle)}` + ) + .reply(200, {items: []}); + + await success(pluginConfig, { + env, + options, + branch: {name: 'master'}, + lastRelease, + commits, + nextRelease, + releases, + logger: t.context.logger, + }); + + t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://github.com/successcomment-1')); + t.true(github.isDone()); +}); + +test.serial('Editing the release with no ID in the release', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITHUB_TOKEN: 'github_token'}; + const failTitle = 'The automated release is failing 🚨'; + const pluginConfig = {releasedLabels: false, addReleases: 'bottom'}; + const prs = [{number: 1, pull_request: {}, state: 'closed'}]; + const options = {repositoryUrl: `https://github.com/${owner}/${repo}.git`}; + const nextRelease = {version: '2.0.0', gitTag: 'v1.0.0', name: 'v1.0.0', notes: 'Test release note body'}; + const lastRelease = {version: '1.0.0'}; + const commits = [{hash: '123', message: 'Commit 1 message'}]; + const releases = [ + {name: 'GitHub release', url: 'https://github.com/release'}, + {name: 'S3', url: 's3://my-bucket/release-asset'}, + {name: 'Docker: docker.io/python:slim'}, + ]; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {full_name: `${owner}/${repo}`}) + .get( + `/search/issues?q=${escape(`repo:${owner}/${repo}`)}+${escape('type:pr')}+${escape('is:merged')}+${commits + .map((commit) => commit.hash) + .join('+')}` + ) + .reply(200, {items: prs}) + .get(`/repos/${owner}/${repo}/pulls/1/commits`) + .reply(200, [{sha: commits[0].hash}]) + .post(`/repos/${owner}/${repo}/issues/1/comments`, {body: /This PR is included/}) + .reply(200, {html_url: 'https://github.com/successcomment-1'}) + .get( + `/search/issues?q=${escape('in:title')}+${escape(`repo:${owner}/${repo}`)}+${escape('type:issue')}+${escape( + 'state:open' + )}+${escape(failTitle)}` + ) + .reply(200, {items: []}); + + await success(pluginConfig, { + env, + options, + branch: {name: 'master'}, + lastRelease, + commits, + nextRelease, + releases, + logger: t.context.logger, + }); + + t.true(t.context.log.calledWith('Added comment to issue #%d: %s', 1, 'https://github.com/successcomment-1')); + t.true(github.isDone()); +}); + test.serial('Ignore errors when adding comments and closing issues', async (t) => { const owner = 'test_user'; const repo = 'test_repo'; diff --git a/test/verify.test.js b/test/verify.test.js index a01a2186..9b62accf 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -340,6 +340,63 @@ test.serial('Verify "assignees" is a String', async (t) => { t.true(github.isDone()); }); +test.serial('Verify "addReleases" is a valid string (top)', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GH_TOKEN: 'github_token'}; + const addReleases = 'top'; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {permissions: {push: true}}); + + await t.notThrowsAsync( + verify( + {addReleases}, + {env, options: {repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git`}, logger: t.context.logger} + ) + ); + + t.true(github.isDone()); +}); + +test.serial('Verify "addReleases" is a valid string (bottom)', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GH_TOKEN: 'github_token'}; + const addReleases = 'bottom'; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {permissions: {push: true}}); + + await t.notThrowsAsync( + verify( + {addReleases}, + {env, options: {repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git`}, logger: t.context.logger} + ) + ); + + t.true(github.isDone()); +}); + +test.serial('Verify "addReleases" is valid (false)', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GH_TOKEN: 'github_token'}; + const addReleases = false; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {permissions: {push: true}}); + + await t.notThrowsAsync( + verify( + {addReleases}, + {env, options: {repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git`}, logger: t.context.logger} + ) + ); + + t.true(github.isDone()); +}); + // https://github.com/semantic-release/github/issues/182 test.serial('Verify if run in GitHub Action', async (t) => { const owner = 'test_user'; @@ -997,3 +1054,69 @@ test.serial('Throw SemanticReleaseError if "releasedLabels" option is a whitespa t.is(error.code, 'EINVALIDRELEASEDLABELS'); t.true(github.isDone()); }); + +test.serial('Throw SemanticReleaseError if "addReleases" option is not a valid string (botom)', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GH_TOKEN: 'github_token'}; + const addReleases = 'botom'; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {permissions: {push: true}}); + + const [error, ...errors] = await t.throwsAsync( + verify( + {addReleases}, + {env, options: {repositoryUrl: `https://github.com/${owner}/${repo}.git`}, logger: t.context.logger} + ) + ); + + t.is(errors.length, 0); + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDADDRELEASES'); + t.true(github.isDone()); +}); + +test.serial('Throw SemanticReleaseError if "addReleases" option is not a valid string (true)', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GH_TOKEN: 'github_token'}; + const addReleases = true; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {permissions: {push: true}}); + + const [error, ...errors] = await t.throwsAsync( + verify( + {addReleases}, + {env, options: {repositoryUrl: `https://github.com/${owner}/${repo}.git`}, logger: t.context.logger} + ) + ); + + t.is(errors.length, 0); + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDADDRELEASES'); + t.true(github.isDone()); +}); + +test.serial('Throw SemanticReleaseError if "addReleases" option is not a valid string (number)', async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GH_TOKEN: 'github_token'}; + const addReleases = 42; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {permissions: {push: true}}); + + const [error, ...errors] = await t.throwsAsync( + verify( + {addReleases}, + {env, options: {repositoryUrl: `https://github.com/${owner}/${repo}.git`}, logger: t.context.logger} + ) + ); + + t.is(errors.length, 0); + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EINVALIDADDRELEASES'); + t.true(github.isDone()); +});