From 63383addc01743f339828afbb8874d5b5519f1bc Mon Sep 17 00:00:00 2001 From: Florian Greinacher Date: Fri, 4 Feb 2022 17:42:46 +0100 Subject: [PATCH 1/6] feat: add release comments to issues --- README.md | 1 + lib/publish.js | 79 +++++++++++++++++++++++++++++++++++-- lib/resolve-config.js | 3 +- test/publish.test.js | 54 +++++++++++++++++++++++++ test/resolve-config.test.js | 29 ++++++++++---- 5 files changed, 155 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7dc60f5a..2cb213f7 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ Create a [personal access token](https://docs.gitlab.com/ce/user/profile/persona | `gitlabApiPathPrefix` | The GitLab API prefix. | `GL_PREFIX` or `GITLAB_PREFIX` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `/api/v4`. | | `assets` | An array of files to upload to the release. See [assets](#assets). | - | | `milestones` | An array of milestone titles to associate to the release. See [GitLab Release API](https://docs.gitlab.com/ee/api/releases/#create-a-release). | - | +| `postComments` | Post comments to issues and MRs associated with a release. | `false` | #### assets diff --git a/lib/publish.js b/lib/publish.js index d2384766..ee1e2e07 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -1,7 +1,7 @@ const {createReadStream} = require('fs'); const {resolve} = require('path'); const {stat} = require('fs-extra'); -const {isPlainObject} = require('lodash'); +const {isPlainObject, uniqWith, isEqual} = require('lodash'); const FormData = require('form-data'); const urlJoin = require('url-join'); const got = require('got'); @@ -16,8 +16,9 @@ module.exports = async (pluginConfig, context) => { options: {repositoryUrl}, nextRelease: {gitTag, gitHead, notes}, logger, + commits, } = context; - const {gitlabToken, gitlabUrl, gitlabApiUrl, assets, milestones} = resolveConfig(pluginConfig, context); + const {gitlabToken, gitlabUrl, gitlabApiUrl, assets, milestones, postComments} = resolveConfig(pluginConfig, context); const assetsList = []; const repoId = getRepoId(context, gitlabUrl, repositoryUrl); const encodedRepoId = encodeURIComponent(repoId); @@ -117,5 +118,77 @@ module.exports = async (pluginConfig, context) => { logger.log('Published GitLab release: %s', gitTag); - return {url: urlJoin(gitlabUrl, encodedRepoId, `/-/releases/${encodedGitTag}`), name: 'GitLab release'}; + const releaseUrl = urlJoin(gitlabUrl, encodedRepoId, `/-/releases/${encodedGitTag}`); + + if (postComments) { + try { + const postCommentToIssue = issue => { + const issueNotesEndpoint = urlJoin(gitlabApiUrl, `/projects/${issue.project_id}/issues/${issue.iid}/notes`); + debug('Posting issue note to %s', issueNotesEndpoint); + return got.post(issueNotesEndpoint, { + ...apiOptions, + json: { + body: `:tada: This issue has been resolved in version ${gitTag} :tada:\n\nThe release is available on [Gitlab Release](${releaseUrl})`, + }, + }); + }; + + const postCommentToMergeRequest = mergeRequest => { + const mergeRequestNotesEndpoint = urlJoin( + gitlabApiUrl, + `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/notes` + ); + debug('Posting MR note to %s', mergeRequestNotesEndpoint); + return got.post(mergeRequestNotesEndpoint, { + ...apiOptions, + json: { + body: `:tada: This MR is included in version ${gitTag} :tada:\n\nThe release is available on [Gitlab Release](${releaseUrl})`, + }, + }); + }; + + const getRelatedMergeRequests = async commitHash => { + const relatedMergeRequestsEndpoint = urlJoin( + gitlabApiUrl, + `/projects/${encodedRepoId}/repository/commits/${commitHash}/merge_requests` + ); + debug('Getting MRs from %s', relatedMergeRequestsEndpoint); + const relatedMergeRequests = await got + .get(relatedMergeRequestsEndpoint, { + ...apiOptions, + }) + .json(); + + return relatedMergeRequests.filter(x => x.state === 'merged'); + }; + + const getRelatedIssues = async mergeRequest => { + const relatedIssuesEndpoint = urlJoin( + gitlabApiUrl, + `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/closes_issues` + ); + debug('Getting related issues from %s', relatedIssuesEndpoint); + const relatedIssues = await got + .get(relatedIssuesEndpoint, { + ...apiOptions, + }) + .json(); + + return relatedIssues.filter(x => x.state === 'closed'); + }; + + const relatedMergeRequests = uniqWith( + (await Promise.all(commits.map(x => x.hash).map(getRelatedMergeRequests))).flat(), + isEqual + ); + const relatedIssues = uniqWith((await Promise.all(relatedMergeRequests.map(getRelatedIssues))).flat(), isEqual); + await Promise.all(relatedIssues.map(postCommentToIssue)); + await Promise.all(relatedMergeRequests.map(postCommentToMergeRequest)); + } catch (error) { + logger.error('An error occurred while posting comments to related issues and merge requests:\n%O', error); + throw error; + } + } + + return {url: releaseUrl, name: 'GitLab release'}; }; diff --git a/lib/resolve-config.js b/lib/resolve-config.js index 8fc3a413..053260dc 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -2,7 +2,7 @@ const {castArray, isNil} = require('lodash'); const urlJoin = require('url-join'); module.exports = ( - {gitlabUrl, gitlabApiPathPrefix, assets, milestones}, + {gitlabUrl, gitlabApiPathPrefix, assets, milestones, postComments}, { envCi: {service} = {}, env: { @@ -41,5 +41,6 @@ module.exports = ( : urlJoin(defaultedGitlabUrl, isNil(userGitlabApiPathPrefix) ? '/api/v4' : userGitlabApiPathPrefix), assets: assets ? castArray(assets) : assets, milestones: milestones ? castArray(milestones) : milestones, + postComments: postComments === undefined ? false : postComments, }; }; diff --git a/test/publish.test.js b/test/publish.test.js index af1f795b..b97406f1 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -45,6 +45,60 @@ test.serial('Publish a release', async t => { t.true(gitlab.isDone()); }); +test.serial('Publish a release and post comments', async t => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITLAB_TOKEN: 'gitlab_token'}; + const pluginConfig = {postComments: true}; + const nextRelease = {gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'}; + const options = {repositoryUrl: `https://gitlab.com/${owner}/${repo}.git`}; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const commits = [{hash: 'abcdef'}, {hash: 'fedcba'}]; + const gitlab = authenticate(env) + .post(`/projects/${encodedRepoId}/releases`, { + tag_name: nextRelease.gitTag, + description: nextRelease.notes, + assets: { + links: [], + }, + }) + .reply(200) + .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) + .reply(200, [ + {project_id: 100, iid: 1, state: 'merged'}, + {project_id: 200, iid: 2, state: 'closed'}, + {project_id: 300, iid: 3, state: 'merged'}, + ]) + .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`) + .reply(200, [{project_id: 100, iid: 1, state: 'merged'}]) + .get(`/projects/100/merge_requests/1/closes_issues`) + .reply(200, [ + {project_id: 100, iid: 11, state: 'closed'}, + {project_id: 100, iid: 12, state: 'open'}, + {project_id: 100, iid: 13, state: 'closed'}, + ]) + .get(`/projects/300/merge_requests/3/closes_issues`) + .reply(200, []) + .post(`/projects/100/merge_requests/1/notes`, { + body: + ':tada: This MR is included in version v1.0.0 :tada:\n\nThe release is available on [Gitlab Release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)', + }) + .reply(200) + .post(`/projects/300/merge_requests/3/notes`) + .reply(200) + .post(`/projects/100/issues/11/notes`, { + body: + ':tada: This issue has been resolved in version v1.0.0 :tada:\n\nThe release is available on [Gitlab Release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)', + }) + .reply(200) + .post(`/projects/100/issues/13/notes`) + .reply(200); + + await publish(pluginConfig, {env, options, nextRelease, logger: t.context.logger, commits}); + + t.true(gitlab.isDone()); +}); + test.serial('Publish a release with assets', async t => { const cwd = 'test/fixtures/files'; const owner = 'test_user'; diff --git a/test/resolve-config.test.js b/test/resolve-config.test.js index 3d0ddfd5..0014822c 100644 --- a/test/resolve-config.test.js +++ b/test/resolve-config.test.js @@ -7,14 +7,19 @@ test('Returns user config', t => { const gitlabUrl = 'https://host.com'; const gitlabApiPathPrefix = '/api/prefix'; const assets = ['file.js']; + const postComments = true; - t.deepEqual(resolveConfig({gitlabUrl, gitlabApiPathPrefix, assets}, {env: {GITLAB_TOKEN: gitlabToken}}), { - gitlabToken, - gitlabUrl, - gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), - assets, - milestones: undefined, - }); + t.deepEqual( + resolveConfig({gitlabUrl, gitlabApiPathPrefix, assets, postComments}, {env: {GITLAB_TOKEN: gitlabToken}}), + { + gitlabToken, + gitlabUrl, + gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), + assets, + milestones: undefined, + postComments, + } + ); }); test('Returns user config via environment variables', t => { @@ -35,6 +40,7 @@ test('Returns user config via environment variables', t => { gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets, milestones, + postComments: false, } ); }); @@ -53,6 +59,7 @@ test('Returns user config via alternative environment variables', t => { gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets, milestones: undefined, + postComments: false, } ); }); @@ -68,6 +75,7 @@ test('Returns default config', t => { gitlabApiUrl: urlJoin('https://gitlab.com', '/api/v4'), assets: undefined, milestones: undefined, + postComments: false, }); t.deepEqual(resolveConfig({gitlabApiPathPrefix}, {env: {GL_TOKEN: gitlabToken}}), { @@ -76,6 +84,7 @@ test('Returns default config', t => { gitlabApiUrl: urlJoin('https://gitlab.com', gitlabApiPathPrefix), assets: undefined, milestones: undefined, + postComments: false, }); t.deepEqual(resolveConfig({gitlabUrl}, {env: {GL_TOKEN: gitlabToken}}), { @@ -84,6 +93,7 @@ test('Returns default config', t => { gitlabApiUrl: urlJoin(gitlabUrl, '/api/v4'), assets: undefined, milestones: undefined, + postComments: false, }); }); @@ -107,6 +117,7 @@ test('Returns default config via GitLab CI/CD environment variables', t => { gitlabApiUrl: CI_API_V4_URL, assets: undefined, milestones: undefined, + postComments: false, } ); }); @@ -134,6 +145,7 @@ test('Returns user config over GitLab CI/CD environment variables', t => { gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets, milestones: undefined, + postComments: false, } ); }); @@ -167,6 +179,7 @@ test('Returns user config via environment variables over GitLab CI/CD environmen gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets: undefined, milestones: undefined, + postComments: false, } ); }); @@ -200,6 +213,7 @@ test('Returns user config via alternative environment variables over GitLab CI/C gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets: undefined, milestones: undefined, + postComments: false, } ); }); @@ -224,6 +238,7 @@ test('Ignore GitLab CI/CD environment variables if not running on GitLab CI/CD', gitlabApiUrl: urlJoin('https://gitlab.com', '/api/v4'), assets: undefined, milestones: undefined, + postComments: false, } ); }); From 3aa3a18f71c002743d43c35e39f5274ea7e97af8 Mon Sep 17 00:00:00 2001 From: Florian Greinacher Date: Fri, 18 Mar 2022 15:46:22 +0000 Subject: [PATCH 2/6] refactor: move commenting to "success" lifecycle --- index.js | 12 ++++- lib/definitions/constants.js | 3 ++ lib/publish.js | 78 ++---------------------------- lib/success.js | 93 ++++++++++++++++++++++++++++++++++++ test/publish.test.js | 54 --------------------- test/success.test.js | 67 ++++++++++++++++++++++++++ 6 files changed, 178 insertions(+), 129 deletions(-) create mode 100644 lib/definitions/constants.js create mode 100644 lib/success.js create mode 100644 test/success.test.js diff --git a/index.js b/index.js index 0ab1255b..13f184a1 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const verifyGitLab = require('./lib/verify'); const publishGitLab = require('./lib/publish'); +const successGitLab = require('./lib/success'); let verified; @@ -19,4 +20,13 @@ async function publish(pluginConfig, context) { return publishGitLab(pluginConfig, context); } -module.exports = {verifyConditions, publish}; +async function success(pluginConfig, context) { + if (!verified) { + await verifyGitLab(pluginConfig, context); + verified = true; + } + + return successGitLab(pluginConfig, context); +} + +module.exports = {verifyConditions, publish, success}; diff --git a/lib/definitions/constants.js b/lib/definitions/constants.js new file mode 100644 index 00000000..8e157727 --- /dev/null +++ b/lib/definitions/constants.js @@ -0,0 +1,3 @@ +const RELEASE_NAME = 'GitLab release'; + +module.exports = {RELEASE_NAME}; diff --git a/lib/publish.js b/lib/publish.js index ee1e2e07..783b5cb7 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -1,7 +1,7 @@ const {createReadStream} = require('fs'); const {resolve} = require('path'); const {stat} = require('fs-extra'); -const {isPlainObject, uniqWith, isEqual} = require('lodash'); +const {isPlainObject} = require('lodash'); const FormData = require('form-data'); const urlJoin = require('url-join'); const got = require('got'); @@ -9,6 +9,7 @@ const debug = require('debug')('semantic-release:gitlab'); const resolveConfig = require('./resolve-config'); const getRepoId = require('./get-repo-id'); const getAssets = require('./glob-assets'); +const {RELEASE_NAME} = require('./definitions/constants'); module.exports = async (pluginConfig, context) => { const { @@ -16,9 +17,8 @@ module.exports = async (pluginConfig, context) => { options: {repositoryUrl}, nextRelease: {gitTag, gitHead, notes}, logger, - commits, } = context; - const {gitlabToken, gitlabUrl, gitlabApiUrl, assets, milestones, postComments} = resolveConfig(pluginConfig, context); + const {gitlabToken, gitlabUrl, gitlabApiUrl, assets, milestones} = resolveConfig(pluginConfig, context); const assetsList = []; const repoId = getRepoId(context, gitlabUrl, repositoryUrl); const encodedRepoId = encodeURIComponent(repoId); @@ -120,75 +120,5 @@ module.exports = async (pluginConfig, context) => { const releaseUrl = urlJoin(gitlabUrl, encodedRepoId, `/-/releases/${encodedGitTag}`); - if (postComments) { - try { - const postCommentToIssue = issue => { - const issueNotesEndpoint = urlJoin(gitlabApiUrl, `/projects/${issue.project_id}/issues/${issue.iid}/notes`); - debug('Posting issue note to %s', issueNotesEndpoint); - return got.post(issueNotesEndpoint, { - ...apiOptions, - json: { - body: `:tada: This issue has been resolved in version ${gitTag} :tada:\n\nThe release is available on [Gitlab Release](${releaseUrl})`, - }, - }); - }; - - const postCommentToMergeRequest = mergeRequest => { - const mergeRequestNotesEndpoint = urlJoin( - gitlabApiUrl, - `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/notes` - ); - debug('Posting MR note to %s', mergeRequestNotesEndpoint); - return got.post(mergeRequestNotesEndpoint, { - ...apiOptions, - json: { - body: `:tada: This MR is included in version ${gitTag} :tada:\n\nThe release is available on [Gitlab Release](${releaseUrl})`, - }, - }); - }; - - const getRelatedMergeRequests = async commitHash => { - const relatedMergeRequestsEndpoint = urlJoin( - gitlabApiUrl, - `/projects/${encodedRepoId}/repository/commits/${commitHash}/merge_requests` - ); - debug('Getting MRs from %s', relatedMergeRequestsEndpoint); - const relatedMergeRequests = await got - .get(relatedMergeRequestsEndpoint, { - ...apiOptions, - }) - .json(); - - return relatedMergeRequests.filter(x => x.state === 'merged'); - }; - - const getRelatedIssues = async mergeRequest => { - const relatedIssuesEndpoint = urlJoin( - gitlabApiUrl, - `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/closes_issues` - ); - debug('Getting related issues from %s', relatedIssuesEndpoint); - const relatedIssues = await got - .get(relatedIssuesEndpoint, { - ...apiOptions, - }) - .json(); - - return relatedIssues.filter(x => x.state === 'closed'); - }; - - const relatedMergeRequests = uniqWith( - (await Promise.all(commits.map(x => x.hash).map(getRelatedMergeRequests))).flat(), - isEqual - ); - const relatedIssues = uniqWith((await Promise.all(relatedMergeRequests.map(getRelatedIssues))).flat(), isEqual); - await Promise.all(relatedIssues.map(postCommentToIssue)); - await Promise.all(relatedMergeRequests.map(postCommentToMergeRequest)); - } catch (error) { - logger.error('An error occurred while posting comments to related issues and merge requests:\n%O', error); - throw error; - } - } - - return {url: releaseUrl, name: 'GitLab release'}; + return {name: RELEASE_NAME, url: releaseUrl}; }; diff --git a/lib/success.js b/lib/success.js new file mode 100644 index 00000000..237b97b2 --- /dev/null +++ b/lib/success.js @@ -0,0 +1,93 @@ +const {uniqWith, isEqual} = require('lodash'); +const urlJoin = require('url-join'); +const got = require('got'); +const debug = require('debug')('semantic-release:gitlab'); +const resolveConfig = require('./resolve-config'); +const getRepoId = require('./get-repo-id'); +const {RELEASE_NAME} = require('./definitions/constants'); + +module.exports = async (pluginConfig, context) => { + const { + options: {repositoryUrl}, + nextRelease: {gitTag}, + logger, + commits, + releases, + } = context; + const {gitlabToken, gitlabUrl, gitlabApiUrl, postComments} = resolveConfig(pluginConfig, context); + const repoId = getRepoId(context, gitlabUrl, repositoryUrl); + const encodedRepoId = encodeURIComponent(repoId); + const apiOptions = {headers: {'PRIVATE-TOKEN': gitlabToken}}; + + const release = releases.find(release => release.name && release.name === RELEASE_NAME); + + if (postComments) { + try { + const postCommentToIssue = issue => { + const issueNotesEndpoint = urlJoin(gitlabApiUrl, `/projects/${issue.project_id}/issues/${issue.iid}/notes`); + debug('Posting issue note to %s', issueNotesEndpoint); + return got.post(issueNotesEndpoint, { + ...apiOptions, + json: { + body: `:tada: This issue has been resolved in version ${gitTag} :tada:\n\nThe release is available on [Gitlab Release](${release.url})`, + }, + }); + }; + + const postCommentToMergeRequest = mergeRequest => { + const mergeRequestNotesEndpoint = urlJoin( + gitlabApiUrl, + `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/notes` + ); + debug('Posting MR note to %s', mergeRequestNotesEndpoint); + return got.post(mergeRequestNotesEndpoint, { + ...apiOptions, + json: { + body: `:tada: This MR is included in version ${gitTag} :tada:\n\nThe release is available on [Gitlab Release](${release.url})`, + }, + }); + }; + + const getRelatedMergeRequests = async commitHash => { + const relatedMergeRequestsEndpoint = urlJoin( + gitlabApiUrl, + `/projects/${encodedRepoId}/repository/commits/${commitHash}/merge_requests` + ); + debug('Getting MRs from %s', relatedMergeRequestsEndpoint); + const relatedMergeRequests = await got + .get(relatedMergeRequestsEndpoint, { + ...apiOptions, + }) + .json(); + + return relatedMergeRequests.filter(x => x.state === 'merged'); + }; + + const getRelatedIssues = async mergeRequest => { + const relatedIssuesEndpoint = urlJoin( + gitlabApiUrl, + `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/closes_issues` + ); + debug('Getting related issues from %s', relatedIssuesEndpoint); + const relatedIssues = await got + .get(relatedIssuesEndpoint, { + ...apiOptions, + }) + .json(); + + return relatedIssues.filter(x => x.state === 'closed'); + }; + + const relatedMergeRequests = uniqWith( + (await Promise.all(commits.map(x => x.hash).map(getRelatedMergeRequests))).flat(), + isEqual + ); + const relatedIssues = uniqWith((await Promise.all(relatedMergeRequests.map(getRelatedIssues))).flat(), isEqual); + await Promise.all(relatedIssues.map(postCommentToIssue)); + await Promise.all(relatedMergeRequests.map(postCommentToMergeRequest)); + } catch (error) { + logger.error('An error occurred while posting comments to related issues and merge requests:\n%O', error); + throw error; + } + } +}; diff --git a/test/publish.test.js b/test/publish.test.js index b97406f1..af1f795b 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -45,60 +45,6 @@ test.serial('Publish a release', async t => { t.true(gitlab.isDone()); }); -test.serial('Publish a release and post comments', async t => { - const owner = 'test_user'; - const repo = 'test_repo'; - const env = {GITLAB_TOKEN: 'gitlab_token'}; - const pluginConfig = {postComments: true}; - const nextRelease = {gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'}; - const options = {repositoryUrl: `https://gitlab.com/${owner}/${repo}.git`}; - const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); - const commits = [{hash: 'abcdef'}, {hash: 'fedcba'}]; - const gitlab = authenticate(env) - .post(`/projects/${encodedRepoId}/releases`, { - tag_name: nextRelease.gitTag, - description: nextRelease.notes, - assets: { - links: [], - }, - }) - .reply(200) - .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) - .reply(200, [ - {project_id: 100, iid: 1, state: 'merged'}, - {project_id: 200, iid: 2, state: 'closed'}, - {project_id: 300, iid: 3, state: 'merged'}, - ]) - .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`) - .reply(200, [{project_id: 100, iid: 1, state: 'merged'}]) - .get(`/projects/100/merge_requests/1/closes_issues`) - .reply(200, [ - {project_id: 100, iid: 11, state: 'closed'}, - {project_id: 100, iid: 12, state: 'open'}, - {project_id: 100, iid: 13, state: 'closed'}, - ]) - .get(`/projects/300/merge_requests/3/closes_issues`) - .reply(200, []) - .post(`/projects/100/merge_requests/1/notes`, { - body: - ':tada: This MR is included in version v1.0.0 :tada:\n\nThe release is available on [Gitlab Release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)', - }) - .reply(200) - .post(`/projects/300/merge_requests/3/notes`) - .reply(200) - .post(`/projects/100/issues/11/notes`, { - body: - ':tada: This issue has been resolved in version v1.0.0 :tada:\n\nThe release is available on [Gitlab Release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)', - }) - .reply(200) - .post(`/projects/100/issues/13/notes`) - .reply(200); - - await publish(pluginConfig, {env, options, nextRelease, logger: t.context.logger, commits}); - - t.true(gitlab.isDone()); -}); - test.serial('Publish a release with assets', async t => { const cwd = 'test/fixtures/files'; const owner = 'test_user'; diff --git a/test/success.test.js b/test/success.test.js new file mode 100644 index 00000000..da1c8dcc --- /dev/null +++ b/test/success.test.js @@ -0,0 +1,67 @@ +const test = require('ava'); +const nock = require('nock'); +const {stub} = require('sinon'); +const success = require('../lib/success'); +const authenticate = require('./helpers/mock-gitlab'); +const {RELEASE_NAME} = require('../lib/definitions/constants'); + +/* eslint camelcase: ["error", {properties: "never"}] */ + +test.beforeEach(t => { + // Mock logger + t.context.log = stub(); + t.context.error = stub(); + t.context.logger = {log: t.context.log, error: t.context.error}; +}); + +test.afterEach.always(() => { + // Clear nock + nock.cleanAll(); +}); + +test.serial('Post comments to related issues and MRs', async t => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITLAB_TOKEN: 'gitlab_token'}; + const pluginConfig = {postComments: true}; + const nextRelease = {gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'}; + const releases = [{name: RELEASE_NAME, url: 'https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0'}]; + const options = {repositoryUrl: `https://gitlab.com/${owner}/${repo}.git`}; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const commits = [{hash: 'abcdef'}, {hash: 'fedcba'}]; + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) + .reply(200, [ + {project_id: 100, iid: 1, state: 'merged'}, + {project_id: 200, iid: 2, state: 'closed'}, + {project_id: 300, iid: 3, state: 'merged'}, + ]) + .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`) + .reply(200, [{project_id: 100, iid: 1, state: 'merged'}]) + .get(`/projects/100/merge_requests/1/closes_issues`) + .reply(200, [ + {project_id: 100, iid: 11, state: 'closed'}, + {project_id: 100, iid: 12, state: 'open'}, + {project_id: 100, iid: 13, state: 'closed'}, + ]) + .get(`/projects/300/merge_requests/3/closes_issues`) + .reply(200, []) + .post(`/projects/100/merge_requests/1/notes`, { + body: + ':tada: This MR is included in version v1.0.0 :tada:\n\nThe release is available on [Gitlab Release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)', + }) + .reply(200) + .post(`/projects/300/merge_requests/3/notes`) + .reply(200) + .post(`/projects/100/issues/11/notes`, { + body: + ':tada: This issue has been resolved in version v1.0.0 :tada:\n\nThe release is available on [Gitlab Release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)', + }) + .reply(200) + .post(`/projects/100/issues/13/notes`) + .reply(200); + + await success(pluginConfig, {env, options, nextRelease, logger: t.context.logger, commits, releases}); + + t.true(gitlab.isDone()); +}); From edfbd377df3c1f0030d022f13724f41e6e112674 Mon Sep 17 00:00:00 2001 From: Florian Greinacher Date: Fri, 18 Mar 2022 16:46:27 +0000 Subject: [PATCH 3/6] refactor: switch from `postComments` to `successComment` option --- lib/get-success-comment.js | 17 +++++++++++++++++ lib/resolve-config.js | 5 ++--- lib/success.js | 29 ++++++++++++++++------------- test/resolve-config.test.js | 22 +++++++++++----------- test/success.test.js | 24 ++++++++++++++++++++---- 5 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 lib/get-success-comment.js diff --git a/lib/get-success-comment.js b/lib/get-success-comment.js new file mode 100644 index 00000000..0c6527a6 --- /dev/null +++ b/lib/get-success-comment.js @@ -0,0 +1,17 @@ +const HOME_URL = 'https://github.com/semantic-release/semantic-release'; +const linkify = releaseInfo => + `${releaseInfo.url ? `[${releaseInfo.name}](${releaseInfo.url})` : `\`${releaseInfo.name}\``}`; + +module.exports = (issueOrMergeRequest, releaseInfos, nextRelease) => + `:tada: This ${issueOrMergeRequest.isMergeRequest ? 'MR is included' : 'issue has been resolved'} in version ${ + nextRelease.version + } :tada:${ + releaseInfos.length > 0 + ? `\n\nThe release is available on${ + releaseInfos.length === 1 + ? ` ${linkify(releaseInfos[0])}` + : `:\n${releaseInfos.map(releaseInfo => `- ${linkify(releaseInfo)}`).join('\n')}` + }` + : '' + } +Your **[semantic-release](${HOME_URL})** bot :package::rocket:`; diff --git a/lib/resolve-config.js b/lib/resolve-config.js index 053260dc..201a59c7 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -2,7 +2,7 @@ const {castArray, isNil} = require('lodash'); const urlJoin = require('url-join'); module.exports = ( - {gitlabUrl, gitlabApiPathPrefix, assets, milestones, postComments}, + {gitlabUrl, gitlabApiPathPrefix, assets, milestones, successComment}, { envCi: {service} = {}, env: { @@ -29,7 +29,6 @@ module.exports = ( (service === 'gitlab' && CI_PROJECT_URL && CI_PROJECT_PATH ? CI_PROJECT_URL.replace(new RegExp(`/${CI_PROJECT_PATH}$`), '') : 'https://gitlab.com'); - return { gitlabToken: GL_TOKEN || GITLAB_TOKEN, gitlabUrl: defaultedGitlabUrl, @@ -41,6 +40,6 @@ module.exports = ( : urlJoin(defaultedGitlabUrl, isNil(userGitlabApiPathPrefix) ? '/api/v4' : userGitlabApiPathPrefix), assets: assets ? castArray(assets) : assets, milestones: milestones ? castArray(milestones) : milestones, - postComments: postComments === undefined ? false : postComments, + successComment, }; }; diff --git a/lib/success.js b/lib/success.js index 237b97b2..59d8c950 100644 --- a/lib/success.js +++ b/lib/success.js @@ -1,36 +1,38 @@ -const {uniqWith, isEqual} = require('lodash'); +const {uniqWith, isEqual, template} = require('lodash'); const urlJoin = require('url-join'); const got = require('got'); const debug = require('debug')('semantic-release:gitlab'); const resolveConfig = require('./resolve-config'); const getRepoId = require('./get-repo-id'); -const {RELEASE_NAME} = require('./definitions/constants'); +const getSuccessComment = require('./get-success-comment'); module.exports = async (pluginConfig, context) => { const { options: {repositoryUrl}, - nextRelease: {gitTag}, + nextRelease, logger, commits, releases, } = context; - const {gitlabToken, gitlabUrl, gitlabApiUrl, postComments} = resolveConfig(pluginConfig, context); + const {gitlabToken, gitlabUrl, gitlabApiUrl, successComment} = resolveConfig(pluginConfig, context); const repoId = getRepoId(context, gitlabUrl, repositoryUrl); const encodedRepoId = encodeURIComponent(repoId); const apiOptions = {headers: {'PRIVATE-TOKEN': gitlabToken}}; - const release = releases.find(release => release.name && release.name === RELEASE_NAME); - - if (postComments) { + if (successComment === false) { + logger.log('Skip commenting on issues and pull requests.'); + } else { + const releaseInfos = releases.filter(release => Boolean(release.name)); try { const postCommentToIssue = issue => { const issueNotesEndpoint = urlJoin(gitlabApiUrl, `/projects/${issue.project_id}/issues/${issue.iid}/notes`); debug('Posting issue note to %s', issueNotesEndpoint); + const body = successComment + ? template(successComment)({...context, issue}) + : getSuccessComment(issue, releaseInfos, nextRelease); return got.post(issueNotesEndpoint, { ...apiOptions, - json: { - body: `:tada: This issue has been resolved in version ${gitTag} :tada:\n\nThe release is available on [Gitlab Release](${release.url})`, - }, + json: {body}, }); }; @@ -40,11 +42,12 @@ module.exports = async (pluginConfig, context) => { `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/notes` ); debug('Posting MR note to %s', mergeRequestNotesEndpoint); + const body = successComment + ? template(successComment)({...context, mergeRequest}) + : getSuccessComment({isMergeRequest: true, ...mergeRequest}, releaseInfos, nextRelease); return got.post(mergeRequestNotesEndpoint, { ...apiOptions, - json: { - body: `:tada: This MR is included in version ${gitTag} :tada:\n\nThe release is available on [Gitlab Release](${release.url})`, - }, + json: {body}, }); }; diff --git a/test/resolve-config.test.js b/test/resolve-config.test.js index 0014822c..d91bcbd2 100644 --- a/test/resolve-config.test.js +++ b/test/resolve-config.test.js @@ -17,7 +17,7 @@ test('Returns user config', t => { gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets, milestones: undefined, - postComments, + successComment: undefined, } ); }); @@ -40,7 +40,7 @@ test('Returns user config via environment variables', t => { gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets, milestones, - postComments: false, + successComment: undefined, } ); }); @@ -59,7 +59,7 @@ test('Returns user config via alternative environment variables', t => { gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets, milestones: undefined, - postComments: false, + successComment: undefined, } ); }); @@ -75,7 +75,7 @@ test('Returns default config', t => { gitlabApiUrl: urlJoin('https://gitlab.com', '/api/v4'), assets: undefined, milestones: undefined, - postComments: false, + successComment: undefined, }); t.deepEqual(resolveConfig({gitlabApiPathPrefix}, {env: {GL_TOKEN: gitlabToken}}), { @@ -84,7 +84,7 @@ test('Returns default config', t => { gitlabApiUrl: urlJoin('https://gitlab.com', gitlabApiPathPrefix), assets: undefined, milestones: undefined, - postComments: false, + successComment: undefined, }); t.deepEqual(resolveConfig({gitlabUrl}, {env: {GL_TOKEN: gitlabToken}}), { @@ -93,7 +93,7 @@ test('Returns default config', t => { gitlabApiUrl: urlJoin(gitlabUrl, '/api/v4'), assets: undefined, milestones: undefined, - postComments: false, + successComment: undefined, }); }); @@ -117,7 +117,7 @@ test('Returns default config via GitLab CI/CD environment variables', t => { gitlabApiUrl: CI_API_V4_URL, assets: undefined, milestones: undefined, - postComments: false, + successComment: undefined, } ); }); @@ -145,7 +145,7 @@ test('Returns user config over GitLab CI/CD environment variables', t => { gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets, milestones: undefined, - postComments: false, + successComment: undefined, } ); }); @@ -179,7 +179,7 @@ test('Returns user config via environment variables over GitLab CI/CD environmen gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets: undefined, milestones: undefined, - postComments: false, + successComment: undefined, } ); }); @@ -213,7 +213,7 @@ test('Returns user config via alternative environment variables over GitLab CI/C gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), assets: undefined, milestones: undefined, - postComments: false, + successComment: undefined, } ); }); @@ -238,7 +238,7 @@ test('Ignore GitLab CI/CD environment variables if not running on GitLab CI/CD', gitlabApiUrl: urlJoin('https://gitlab.com', '/api/v4'), assets: undefined, milestones: undefined, - postComments: false, + successComment: undefined, } ); }); diff --git a/test/success.test.js b/test/success.test.js index da1c8dcc..feb6edea 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -23,8 +23,8 @@ test.serial('Post comments to related issues and MRs', async t => { const owner = 'test_user'; const repo = 'test_repo'; const env = {GITLAB_TOKEN: 'gitlab_token'}; - const pluginConfig = {postComments: true}; - const nextRelease = {gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'}; + const pluginConfig = {}; + const nextRelease = {version: '1.0.0'}; const releases = [{name: RELEASE_NAME, url: 'https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0'}]; const options = {repositoryUrl: `https://gitlab.com/${owner}/${repo}.git`}; const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); @@ -48,14 +48,14 @@ test.serial('Post comments to related issues and MRs', async t => { .reply(200, []) .post(`/projects/100/merge_requests/1/notes`, { body: - ':tada: This MR is included in version v1.0.0 :tada:\n\nThe release is available on [Gitlab Release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)', + ':tada: This MR is included in version 1.0.0 :tada:\n\nThe release is available on [GitLab release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)\nYour **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:', }) .reply(200) .post(`/projects/300/merge_requests/3/notes`) .reply(200) .post(`/projects/100/issues/11/notes`, { body: - ':tada: This issue has been resolved in version v1.0.0 :tada:\n\nThe release is available on [Gitlab Release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)', + ':tada: This issue has been resolved in version 1.0.0 :tada:\n\nThe release is available on [GitLab release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)\nYour **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:', }) .reply(200) .post(`/projects/100/issues/13/notes`) @@ -65,3 +65,19 @@ test.serial('Post comments to related issues and MRs', async t => { t.true(gitlab.isDone()); }); + +test.serial('Does not post comments when successComment is set to false', async t => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITLAB_TOKEN: 'gitlab_token'}; + const pluginConfig = {successComment: false}; + const nextRelease = {version: '1.0.0'}; + const releases = [{name: RELEASE_NAME, url: 'https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0'}]; + const options = {repositoryUrl: `https://gitlab.com/${owner}/${repo}.git`}; + const commits = [{hash: 'abcdef'}, {hash: 'fedcba'}]; + const gitlab = authenticate(env); + + await success(pluginConfig, {env, options, nextRelease, logger: t.context.logger, commits, releases}); + + t.true(gitlab.isDone()); +}); From b9824059e26e2dff84693cf25fd02fb36e644528 Mon Sep 17 00:00:00 2001 From: Florian Greinacher Date: Sat, 19 Mar 2022 12:47:45 +0000 Subject: [PATCH 4/6] test: add test for custom success template --- lib/success.js | 4 ++-- test/success.test.js | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/success.js b/lib/success.js index 59d8c950..2f63c725 100644 --- a/lib/success.js +++ b/lib/success.js @@ -28,7 +28,7 @@ module.exports = async (pluginConfig, context) => { const issueNotesEndpoint = urlJoin(gitlabApiUrl, `/projects/${issue.project_id}/issues/${issue.iid}/notes`); debug('Posting issue note to %s', issueNotesEndpoint); const body = successComment - ? template(successComment)({...context, issue}) + ? template(successComment)({...context, issue, mergeRequest: false}) : getSuccessComment(issue, releaseInfos, nextRelease); return got.post(issueNotesEndpoint, { ...apiOptions, @@ -43,7 +43,7 @@ module.exports = async (pluginConfig, context) => { ); debug('Posting MR note to %s', mergeRequestNotesEndpoint); const body = successComment - ? template(successComment)({...context, mergeRequest}) + ? template(successComment)({...context, issue: false, mergeRequest}) : getSuccessComment({isMergeRequest: true, ...mergeRequest}, releaseInfos, nextRelease); return got.post(mergeRequestNotesEndpoint, { ...apiOptions, diff --git a/test/success.test.js b/test/success.test.js index feb6edea..dfdbab2c 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -66,11 +66,42 @@ test.serial('Post comments to related issues and MRs', async t => { t.true(gitlab.isDone()); }); -test.serial('Does not post comments when successComment is set to false', async t => { +test.serial('Post comments with custom template', async t => { const owner = 'test_user'; const repo = 'test_repo'; const env = {GITLAB_TOKEN: 'gitlab_token'}; + const pluginConfig = { + successComment: `nextRelease: \${nextRelease.version} commits: \${commits.length} releases: \${releases.length} \${issue ? "issue" : "MR"} ID: \${issue ? issue.iid : mergeRequest.iid}`, + }; + const nextRelease = {version: '1.0.0'}; + const releases = [{name: RELEASE_NAME, url: 'https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0'}]; + const options = {repositoryUrl: `https://gitlab.com/${owner}/${repo}.git`}; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const commits = [{hash: 'abcdef'}]; + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) + .reply(200, [{project_id: 100, iid: 1, state: 'merged'}]) + .get(`/projects/100/merge_requests/1/closes_issues`) + .reply(200, [{project_id: 100, iid: 11, state: 'closed'}]) + .post(`/projects/100/merge_requests/1/notes`, { + body: 'nextRelease: 1.0.0 commits: 1 releases: 1 MR ID: 1', + }) + .reply(200) + .post(`/projects/100/issues/11/notes`, { + body: 'nextRelease: 1.0.0 commits: 1 releases: 1 issue ID: 11', + }) + .reply(200); + + await success(pluginConfig, {env, options, nextRelease, logger: t.context.logger, commits, releases}); + + t.true(gitlab.isDone()); +}); + +test.serial('Does not post comments when successComment is set to false', async t => { const pluginConfig = {successComment: false}; + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITLAB_TOKEN: 'gitlab_token'}; const nextRelease = {version: '1.0.0'}; const releases = [{name: RELEASE_NAME, url: 'https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0'}]; const options = {repositoryUrl: `https://gitlab.com/${owner}/${repo}.git`}; From 54a28c34ee78932b4fc906d2431cb4a4617b9cbb Mon Sep 17 00:00:00 2001 From: Florian Greinacher Date: Sat, 19 Mar 2022 12:58:32 +0000 Subject: [PATCH 5/6] docs: update readme --- README.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2cb213f7..1569fd78 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # @semantic-release/gitlab +# @semantic-release/gitlab [**semantic-release**](https://github.com/semantic-release/semantic-release) plugin to publish a [GitLab release](https://docs.gitlab.com/ee/user/project/releases/). @@ -10,6 +11,7 @@ |--------------------|-----------------------------------------------------------------------------------------------------------------------| | `verifyConditions` | Verify the presence and the validity of the authentication (set via [environment variables](#environment-variables)). | | `publish` | Publish a [GitLab release](https://docs.gitlab.com/ee/user/project/releases/). | +| `success` | Add a comment to each GitLab Issue or Merge Request resolved by the release. | ## Install @@ -58,13 +60,13 @@ Create a [personal access token](https://docs.gitlab.com/ce/user/profile/persona ### Options -| Option | Description | Default | -|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `gitlabUrl` | The GitLab endpoint. | `GL_URL` or `GITLAB_URL` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `https://gitlab.com`. | -| `gitlabApiPathPrefix` | The GitLab API prefix. | `GL_PREFIX` or `GITLAB_PREFIX` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `/api/v4`. | -| `assets` | An array of files to upload to the release. See [assets](#assets). | - | -| `milestones` | An array of milestone titles to associate to the release. See [GitLab Release API](https://docs.gitlab.com/ee/api/releases/#create-a-release). | - | -| `postComments` | Post comments to issues and MRs associated with a release. | `false` | +| Option | Description | Default | +|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `gitlabUrl` | The GitLab endpoint. | `GL_URL` or `GITLAB_URL` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `https://gitlab.com`. | +| `gitlabApiPathPrefix` | The GitLab API prefix. | `GL_PREFIX` or `GITLAB_PREFIX` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `/api/v4`. | +| `assets` | An array of files to upload to the release. See [assets](#assets). | - | +| `milestones` | An array of milestone titles to associate to the release. See [GitLab Release API](https://docs.gitlab.com/ee/api/releases/#create-a-release). | - | +| `successComment` | The comment to add to each Issue and Merge Request resolved by the release. Set to false to disable commenting. See [successComment](#successComment). | :tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitLab release]() | #### assets @@ -101,6 +103,20 @@ distribution` and `MyLibrary CSS distribution` in the GitLab release. `css` files in the `dist` directory and its sub-directories excluding the minified version, plus the `build/MyLibrary.zip` file and label it `MyLibrary` in the GitLab release. +#### successComment + +The message for the issue comments is generated with [Lodash template](https://lodash.com/docs#template). The following variables are available: + +| Parameter | Description | +|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `branch` | `Object` with `name`, `type`, `channel`, `range` and `prerelease` properties of the branch from which the release is done. | +| `lastRelease` | `Object` with `version`, `channel`, `gitTag` and `gitHead` of the last release. | +| `nextRelease` | `Object` with `version`, `channel`, `gitTag`, `gitHead` and `notes` of the release being done. | +| `commits` | `Array` of commit `Object`s with `hash`, `subject`, `body` `message` and `author`. | +| `releases` | `Array` with a release `Object`s for each release published, with optional release data such as `name` and `url`. | +| `mergeRequest` | A [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) the comment will be posted to, or `false` when commenting Merge Requests. +| `issue` | A [GitHub API Merge Request object](https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr) the comment will be posted to, or `false` when commenting Issues. + ## Compatibility The latest version of this plugin is compatible with all currently-supported versions of GitLab, [which is the current major version and previous two major versions](https://about.gitlab.com/support/statement-of-support.html#version-support). This plugin is not guaranteed to work with unsupported versions of GitLab. From 428b49b24c19baa3de034dba3fb0ca939ee70dcb Mon Sep 17 00:00:00 2001 From: Florian Greinacher Date: Sat, 19 Mar 2022 13:20:04 +0000 Subject: [PATCH 6/6] test: close some coverage gaps --- test/success.test.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/success.test.js b/test/success.test.js index dfdbab2c..a652681f 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -97,6 +97,35 @@ test.serial('Post comments with custom template', async t => { t.true(gitlab.isDone()); }); +test.serial('Post comments for multiple releases', async t => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GITLAB_TOKEN: 'gitlab_token'}; + const pluginConfig = {}; + const nextRelease = {version: '1.0.0'}; + const releases = [ + {name: RELEASE_NAME, url: 'https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0'}, + {name: 'Other release'}, + ]; + const options = {repositoryUrl: `https://gitlab.com/${owner}/${repo}.git`}; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const commits = [{hash: 'abcdef'}]; + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`) + .reply(200, [{project_id: 100, iid: 1, state: 'merged'}]) + .get(`/projects/100/merge_requests/1/closes_issues`) + .reply(200, []) + .post(`/projects/100/merge_requests/1/notes`, { + body: + ':tada: This MR is included in version 1.0.0 :tada:\n\nThe release is available on:\n- [GitLab release](https://gitlab.com/test_user%2Ftest_repo/-/releases/v1.0.0)\n- `Other release`\nYour **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:', + }) + .reply(200); + + await success(pluginConfig, {env, options, nextRelease, logger: t.context.logger, commits, releases}); + + t.true(gitlab.isDone()); +}); + test.serial('Does not post comments when successComment is set to false', async t => { const pluginConfig = {successComment: false}; const owner = 'test_user';