diff --git a/README.md b/README.md index 129df9f8..12c7b21d 100644 --- a/README.md +++ b/README.md @@ -82,17 +82,19 @@ If you need to bypass the proxy for some hosts, configure the `NO_PROXY` environ ### 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). | - | -| `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](gitlab_release_url) | -| `failComment` | The content of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. See [failComment](#failcomment). | Friendly message with links to **semantic-release** documentation and support, with the list of errors that caused the release to fail. | -| `failTitle` | The title of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. | `The automated release is failing 🚨` | -| `labels` | The [labels](https://docs.gitlab.com/ee/user/project/labels.html#labels) to add to the issue created when a release fails. Set to `false` to not add any label. Labels should be comma-separated as described in the [official docs](https://docs.gitlab.com/ee/api/issues.html#new-issue), e.g. `"semantic-release,bot"`. | `semantic-release` | -| `assignee` | The [assignee](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#assignee) to add to the issue created when a release fails. | - | +| 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. See [successComment](#successComment). | :tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitLab release](gitlab_release_url) | +| `successCommentCondition` | Use this as condition, when to comment on issues or merge requests. See [successCommentCondition](#successCommentCondition). | - | +| `failComment` | The content of the issue created when a release fails. See [failComment](#failcomment). | Friendly message with links to **semantic-release** documentation and support, with the list of errors that caused the release to fail. | +| `failTitle` | The title of the issue created when a release fails. | `The automated release is failing 🚨` | +| `failCommentCondition` | Use this as condition, when to comment on or create an issues in case of failures. See [failCommentCondition](#failCommentCondition). | - | +| `labels` | The [labels](https://docs.gitlab.com/ee/user/project/labels.html#labels) to add to the issue created when a release fails. Set to `false` to not add any label. Labels should be comma-separated as described in the [official docs](https://docs.gitlab.com/ee/api/issues.html#new-issue), e.g. `"semantic-release,bot"`. | `semantic-release` | +| `assignee` | The [assignee](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#assignee) to add to the issue created when a release fails. | - | #### assets @@ -146,6 +148,29 @@ The message for the issue comments is generated with [Lodash template](https://l | `issue` | 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. | | `mergeRequest` | A [GitLab 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. | +#### successCommentCondition + +The success comment condition 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`. | +| `issue` | A [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) the comment will be posted to. | +| `mergeRequest` | A [GitLab API Merge Request object](https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr) the comment will be posted to. | + +##### successCommentCondition example + +- do not create any comments at all: set to `false` or templating: `"<% return false; %>"` +- to only comment on issues: `"<% return issue %>"` +- to only comment on merge requests: `"<% return mergeRequest %>"` +- you can use labels to filter issues: `"<% return issue.labels?.includes('semantic-release-relevant') %>"` + +> check the [GitLab API Merge Request object](https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr) or the [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) for properties which can be used for the filter + #### failComment The message for the issue content is generated with [Lodash template](https://lodash.com/docs#template). The following variables are available: @@ -164,6 +189,27 @@ The `failComment` `This release from branch ${branch.name} had failed due to the > - Error message 1 > - Error message 2 +#### failCommentCondition + +The fail comment condition 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`. | +| `issue` | A [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) the comment will be posted to - only available if an open issue exists. | + +##### failCommentCondition example + +- do not create any comments at all: set to `false` or templating: `"<% return false; %>"` +- to only comment on main branch: `"<% return branch.name === 'main' %>"` +- you can use labels to filter issues, i.e. to not comment if the issue is labeled with `wip`: `"<% return !issue.labels?.includes('wip') %>"` + +> check the [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) for properties which can be used for the filter + ## 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. diff --git a/lib/fail.js b/lib/fail.js index d2706236..018f4519 100644 --- a/lib/fail.js +++ b/lib/fail.js @@ -14,16 +14,18 @@ export default async (pluginConfig, context) => { errors, logger, } = context; - const { gitlabToken, gitlabUrl, gitlabApiUrl, failComment, failTitle, labels, assignee } = resolveConfig( - pluginConfig, - context - ); + const { gitlabToken, gitlabUrl, gitlabApiUrl, failComment, failTitle, failCommentCondition, labels, assignee } = + resolveConfig(pluginConfig, context); const repoId = getRepoId(context, gitlabUrl, repositoryUrl); const encodedRepoId = encodeURIComponent(repoId); const apiOptions = { headers: { "PRIVATE-TOKEN": gitlabToken } }; if (failComment === false || failTitle === false) { logger.log("Skip issue creation."); + logger.error(`Failure reporting should be disabled via 'failCommentCondition'. +Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed in a future major version.`); + } else if (failCommentCondition === false) { + logger.log("Skip issue creation."); } else { const encodedFailTitle = encodeURIComponent(failTitle); const description = failComment ? template(failComment)({ branch, errors }) : getFailComment(branch, errors); @@ -34,32 +36,39 @@ export default async (pluginConfig, context) => { const openFailTitleIssues = await got(openFailTitleIssueEndpoint, { ...apiOptions }).json(); const existingIssue = openFailTitleIssues.find((openFailTitleIssue) => openFailTitleIssue.title === failTitle); - if (existingIssue) { - debug("comment on issue: %O", existingIssue); + const canCommentOnOrCreateIssue = failCommentCondition + ? template(failCommentCondition)({ ...context, issue: existingIssue }) + : true; + if (canCommentOnOrCreateIssue) { + if (existingIssue) { + debug("comment on issue: %O", existingIssue); - const issueNotesEndpoint = urlJoin( - gitlabApiUrl, - `/projects/${existingIssue.project_id}/issues/${existingIssue.iid}/notes` - ); - await got.post(issueNotesEndpoint, { - ...apiOptions, - json: { body: description }, - }); + const issueNotesEndpoint = urlJoin( + gitlabApiUrl, + `/projects/${existingIssue.project_id}/issues/${existingIssue.iid}/notes` + ); + await got.post(issueNotesEndpoint, { + ...apiOptions, + json: { body: description }, + }); - const { id, web_url } = existingIssue; - logger.log("Commented on issue #%d: %s.", id, web_url); - } else { - const newIssue = { id: encodedRepoId, description, labels, title: failTitle, assignee_id: assignee }; - debug("create issue: %O", newIssue); + const { id, web_url } = existingIssue; + logger.log("Commented on issue #%d: %s.", id, web_url); + } else { + const newIssue = { id: encodedRepoId, description, labels, title: failTitle, assignee_id: assignee }; + debug("create issue: %O", newIssue); - /* eslint camelcase: off */ - const { id, web_url } = await got - .post(issuesEndpoint, { - ...apiOptions, - json: newIssue, - }) - .json(); - logger.log("Created issue #%d: %s.", id, web_url); + /* eslint camelcase: off */ + const { id, web_url } = await got + .post(issuesEndpoint, { + ...apiOptions, + json: newIssue, + }) + .json(); + logger.log("Created issue #%d: %s.", id, web_url); + } + } else { + logger.log("Skip commenting on or creating an issue."); } } }; diff --git a/lib/resolve-config.js b/lib/resolve-config.js index e28a3aeb..295fce3a 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -3,7 +3,19 @@ import urlJoin from "url-join"; import { HttpProxyAgent, HttpsProxyAgent } from "hpagent"; export default ( - { gitlabUrl, gitlabApiPathPrefix, assets, milestones, successComment, failTitle, failComment, labels, assignee }, + { + gitlabUrl, + gitlabApiPathPrefix, + assets, + milestones, + successComment, + successCommentCondition, + failTitle, + failComment, + failCommentCondition, + labels, + assignee, + }, { envCi: { service } = {}, env: { @@ -45,9 +57,11 @@ export default ( assets: assets ? castArray(assets) : assets, milestones: milestones ? castArray(milestones) : milestones, successComment, + successCommentCondition, proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY), failTitle: isNil(failTitle) ? "The automated release is failing 🚨" : failTitle, failComment, + failCommentCondition, labels: isNil(labels) ? "semantic-release" : labels === false ? false : labels, assignee, }; diff --git a/lib/success.js b/lib/success.js index bc697b90..910f0201 100644 --- a/lib/success.js +++ b/lib/success.js @@ -15,43 +15,64 @@ export default async (pluginConfig, context) => { commits, releases, } = context; - const { gitlabToken, gitlabUrl, gitlabApiUrl, successComment, proxy } = resolveConfig(pluginConfig, context); + const { gitlabToken, gitlabUrl, gitlabApiUrl, successComment, successCommentCondition, proxy } = resolveConfig( + pluginConfig, + context + ); const repoId = getRepoId(context, gitlabUrl, repositoryUrl); const encodedRepoId = encodeURIComponent(repoId); const apiOptions = { headers: { "PRIVATE-TOKEN": gitlabToken } }; if (successComment === false) { logger.log("Skip commenting on issues and pull requests."); + logger.error(`Issue and pull request comments should be disabled via 'successCommentCondition'. +Using 'false' for 'successComment' is deprecated and will be removed in a future major version.`); + } else if (successCommentCondition === 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, mergeRequest: false }) - : getSuccessComment(issue, releaseInfos, nextRelease); - return got.post(issueNotesEndpoint, { - ...apiOptions, - ...proxy, - json: { body }, - }); + const canCommentOnIssue = successCommentCondition + ? template(successCommentCondition)({ ...context, issue, mergeRequest: false }) + : true; + if (canCommentOnIssue) { + 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, mergeRequest: false }) + : getSuccessComment(issue, releaseInfos, nextRelease); + return got.post(issueNotesEndpoint, { + ...apiOptions, + ...proxy, + json: { body }, + }); + } else { + logger.log("Skip commenting on issue #%d.", issue.id); + } }; const postCommentToMergeRequest = (mergeRequest) => { - const mergeRequestNotesEndpoint = urlJoin( - gitlabApiUrl, - `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/notes` - ); - debug("Posting MR note to %s", mergeRequestNotesEndpoint); - const body = successComment - ? template(successComment)({ ...context, issue: false, mergeRequest }) - : getSuccessComment({ isMergeRequest: true, ...mergeRequest }, releaseInfos, nextRelease); - return got.post(mergeRequestNotesEndpoint, { - ...apiOptions, - ...proxy, - json: { body }, - }); + const canCommentOnMergeRequest = successCommentCondition + ? template(successCommentCondition)({ ...context, issue: false, mergeRequest }) + : true; + if (canCommentOnMergeRequest) { + const mergeRequestNotesEndpoint = urlJoin( + gitlabApiUrl, + `/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/notes` + ); + debug("Posting MR note to %s", mergeRequestNotesEndpoint); + const body = successComment + ? template(successComment)({ ...context, issue: false, mergeRequest }) + : getSuccessComment({ isMergeRequest: true, ...mergeRequest }, releaseInfos, nextRelease); + return got.post(mergeRequestNotesEndpoint, { + ...apiOptions, + ...proxy, + json: { body }, + }); + } else { + logger.log("Skip commenting on merge request #%d.", mergeRequest.iid); + } }; const getRelatedMergeRequests = async (commitHash) => { diff --git a/test/fail.test.js b/test/fail.test.js index dfa7e57c..9c6f52df 100644 --- a/test/fail.test.js +++ b/test/fail.test.js @@ -243,3 +243,121 @@ test.serial("Does not post comments when failComment is set to false", async (t) t.true(gitlab.isDone()); }); + +test.serial("Does not post comments when failCommentCondition disables it", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { failCommentCondition: "<% return false; %>" }; + const branch = { name: "main" }; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const errors = [{ message: "An error occured" }]; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨"); + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`) + .reply(200, [ + { + id: 2, + iid: 2, + project_id: 1, + web_url: "https://gitlab.com/test_user/test_repo/issues/2", + title: "API should implemented authentication", + }, + ]); + + await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger }); + + t.true(gitlab.isDone()); +}); + +test.serial("Does not post comments on existing issues when failCommentCondition disables this", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { failCommentCondition: "<% return !issue; %>" }; + const branch = { name: "main" }; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const errors = [{ message: "An error occured" }]; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨"); + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`) + .reply(200, [ + { + id: 1, + iid: 1, + project_id: 1, + web_url: "https://gitlab.com/test_user%2Ftest_repo/issues/1", + title: "The automated release is failing 🚨", + }, + { + id: 2, + iid: 2, + project_id: 1, + web_url: "https://gitlab.com/test_user%2Ftest_repo/issues/2", + title: "API should implemented authentication", + }, + ]); + + await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger }); + + t.true(gitlab.isDone()); +}); + +test.serial("Post new issue if none exists yet with disabled comment on existing issues", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { + failComment: `Error: Release for branch \${branch.name} failed with error: \${errors.map(error => error.message).join(';')}`, + failCommentCondition: "<% return !issue; %>", + }; + const branch = { name: "main" }; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const errors = [{ message: "An error occured" }]; + const encodedRepoId = encodeURIComponent(`${owner}/${repo}`); + const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨"); + const gitlab = authenticate(env) + .get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`) + .reply(200, [ + { + id: 2, + iid: 2, + project_id: 1, + web_url: "https://gitlab.com/test_user/test_repo/issues/2", + title: "API should implemented authentication", + }, + ]) + .post(`/projects/${encodedRepoId}/issues`, { + id: "test_user%2Ftest_repo", + description: `Error: Release for branch main failed with error: An error occured`, + labels: "semantic-release", + title: "The automated release is failing 🚨", + }) + .reply(200, { id: 3, web_url: "https://gitlab.com/test_user/test_repo/-/issues/3" }); + + await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger }); + + t.true(gitlab.isDone()); + t.deepEqual(t.context.log.args[0], [ + "Created issue #%d: %s.", + 3, + "https://gitlab.com/test_user/test_repo/-/issues/3", + ]); +}); + +test.serial("Does not post comments when failCommentCondition is set to false", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { failCommentCondition: false }; + const branch = { name: "main" }; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const errors = [{ message: "An error occured" }]; + const gitlab = authenticate(env); + + await fail(pluginConfig, { env, options, branch, errors, logger: t.context.logger }); + + t.true(gitlab.isDone()); +}); diff --git a/test/resolve-config.test.js b/test/resolve-config.test.js index 475e41dd..91efa766 100644 --- a/test/resolve-config.test.js +++ b/test/resolve-config.test.js @@ -10,8 +10,10 @@ const defaultOptions = { assets: undefined, milestones: undefined, successComment: undefined, + successCommentCondition: undefined, failTitle: "The automated release is failing 🚨", failComment: undefined, + failCommentCondition: undefined, labels: "semantic-release", assignee: undefined, proxy: {}, diff --git a/test/success.test.js b/test/success.test.js index 47d0c392..9e6272f3 100644 --- a/test/success.test.js +++ b/test/success.test.js @@ -138,3 +138,170 @@ test.serial("Does not post comments when successComment is set to false", async t.true(gitlab.isDone()); }); + +test.serial("Does not post comments when successCommentCondition disables it", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { successCommentCondition: "<% return false; %>" }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_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, []); + + await success(pluginConfig, { env, options, nextRelease, logger: t.context.logger, commits, releases }); + + t.true(gitlab.isDone()); +}); + +test.serial("Does not post comments on issues when successCommentCondition disables issue commenting", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { successCommentCondition: "<% return !issue; %>" }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_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 1.0.0 :tada:\n\nThe release is available on [GitLab release](https://gitlab.com/test_user/test_repo/-/releases/v1.0.0).\n\nYour **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package: :rocket:", + }) + .reply(200) + .post(`/projects/300/merge_requests/3/notes`) + .reply(200); + + await success(pluginConfig, { env, options, nextRelease, logger: t.context.logger, commits, releases }); + + t.true(gitlab.isDone()); +}); + +test.serial("Only posts comments on issues which are found using the successCommentCondition", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { successCommentCondition: "<% return issue.labels?.includes('semantic-release-relevant'); %>" }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_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, labels: "doing,bug", state: "closed" }, + { project_id: 100, iid: 12, labels: "todo,feature", state: "open" }, + { project_id: 100, iid: 13, labels: "testing,semantic-release-relevant,critical", state: "closed" }, + ]) + .get(`/projects/300/merge_requests/3/closes_issues`) + .reply(200, []) + .post(`/projects/100/issues/13/notes`, { + body: ":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/test_repo/-/releases/v1.0.0).\n\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 on merge requets when successCommentCondition disables merge request commenting", + async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { successCommentCondition: "<% return !mergeRequest; %>" }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_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/issues/11/notes`, { + body: ":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/test_repo/-/releases/v1.0.0).\n\nYour **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package: :rocket:", + }) + .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()); + } +); + +test.serial("Does not post comments when successCommentCondition is set to false", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { successCommentCondition: false }; + const nextRelease = { version: "1.0.0" }; + const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_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()); +});