Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 57 additions & 11 deletions README.md

Large diffs are not rendered by default.

63 changes: 36 additions & 27 deletions lib/fail.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.");
}
}
};
16 changes: 15 additions & 1 deletion lib/resolve-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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,
};
Expand Down
69 changes: 45 additions & 24 deletions lib/success.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
118 changes: 118 additions & 0 deletions test/fail.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
});
2 changes: 2 additions & 0 deletions test/resolve-config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
Expand Down
Loading