diff --git a/README.md b/README.md index 27389da9..5c88d1c3 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ If you need to bypass the proxy for some hosts, configure the `NO_PROXY` environ | `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. | - | @@ -188,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 no create any comments at all: `"<% 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 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 + ## 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 18ac6fa6..708c4453 100644 --- a/lib/fail.js +++ b/lib/fail.js @@ -14,10 +14,8 @@ 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 } }; @@ -36,32 +34,39 @@ Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed 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 c3995c87..295fce3a 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -12,6 +12,7 @@ export default ( successCommentCondition, failTitle, failComment, + failCommentCondition, labels, assignee, }, @@ -60,6 +61,7 @@ export default ( 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/test/fail.test.js b/test/fail.test.js index dfa7e57c..4668a504 100644 --- a/test/fail.test.js +++ b/test/fail.test.js @@ -243,3 +243,130 @@ 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 = { 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: `## :rotating_light: The automated release from the \`main\` branch failed. :rotating_light: + +I recommend you give this issue a high priority, so other packages depending on you can benefit from your bug fixes and new features again. + +You can find below the list of errors reported by **semantic-release**. Each one of them has to be resolved in order to automatically publish your package. I'm sure you can fix this 💪. + +Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it. + +Once all the errors are resolved, **semantic-release** will release your package the next time you push a commit to the \`main\` branch. You can also manually restart the failed CI job that runs **semantic-release**. + +If you are not sure how to resolve this, here are some links that can help you: +- [Usage documentation](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/README.md) +- [Frequently Asked Questions](https://github.com/semantic-release/semantic-release/blob/master/docs/support/FAQ.md) +- [Support channels](https://github.com/semantic-release/semantic-release#get-help) + +If those don't help, or if this issue is reporting something you think isn't right, you can always ask the humans behind **[semantic-release](https://github.com/semantic-release/semantic-release/issues/new)**. + +--- + +### An error occured + +Unfortunately this error doesn't have any additional information. + +--- + +Good luck with your project ✨ + +Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package: :rocket:`, + 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", + ]); +}); diff --git a/test/resolve-config.test.js b/test/resolve-config.test.js index e40c5fbc..91efa766 100644 --- a/test/resolve-config.test.js +++ b/test/resolve-config.test.js @@ -13,6 +13,7 @@ const defaultOptions = { successCommentCondition: undefined, failTitle: "The automated release is failing 🚨", failComment: undefined, + failCommentCondition: undefined, labels: "semantic-release", assignee: undefined, proxy: {},