Skip to content

Commit e097d1c

Browse files
babblebeyJonasSchubertgr2m
authoredSep 2, 2024··
feat: allow conditional skip on success and fail comments (#874)
* feat: add `failCommentCondition` to `fail` script * feat: add `successCommentCondition` and `failCommentCondition` to `resolve-config` * feat: add `successCommentCondition` and `failCommentCondition` to `success` scipt * fix(build): lint * test: add `fail` case `Does not post comments if "failCommentCondition" is "false"` * test: add `fail` case for `Does not post comments on existing issues when "failCommentCondition" is "false"` * fix(build): lint * test(fail): add case for `Post new issue if none exists yet, but don't comment on existing issues when "failCommentCondition" is disallows it` * test(success): add case `Does not comment on issues/PR if "successCommentCondition" is "false"` * Update test/fail.test.js Co-authored-by: Jonas Schubert <jonas.schubert.projects@web.de> * test(success): add case for `Add comment and label to found issues/associatedPR using the "successCommentCondition"` * nits * test(success): add case for `Does not comment/label found associatedPR when "successCommentCondition" disables it` * test(success): improve case `Does not comment/label found associatedPR when "successCommentCondition" disables it` * doc: add documentation for `successCommentCondition` and `failCommentCondition` * Update lib/success.js Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com> * refactor: modify `failTitle`, `failComment` and `successComment` false deprecation message * refator: implement early return in `fail` and `success` lifecyccle helper function * Update README.md Co-authored-by: Jonas Schubert <jonas.schubert.projects@web.de> * remove `failCommentCondition` example wrong description * build: fix lint * feat: add validators for `successCommentCondition` and `failCommentCondition` * feat: add `buildAssociatedPRs` to create pr object in form of issue object with pull_request property * doc: update README.md * build: fix lint * build: fix failing test `Add custom comment and labels` * feat: request more field for `associatedPRs` via graphql and improve `buildAssociatedPRs` response object with * test: modify integration tests * test: modify `success` unit tests * build: fix lint * build: fix failing tests * feat: add `__typename` to `issue.user` object * test: add new case `Does not comment/label associatedPR created by "Bots"` * test: modify `pull_request` mock value to `boolean` * chore(test): clean debug comments * feat: re-integrate `buildAssociatedPRs` * feat: re-introduced and modifed `loadSingleCommitAssociatedPRs` * refactor: introduce `parsedIssues` as returned value from `prs**.body` and `commits**.message` * feat: added `buildRelatedIssuesQuery` util graphql query builder * feat: implement computation for `responseRelatedIssues` * fix: correct `number` arg type in `buildRelatedIssuesQuery` * refactor: extract common field accross graphql queries to `baseFields` * refactor: transform `buildAssociatedPRs` to `buildIssuesOrPRsFromResponseNode` with ability to build both `PRs` and `Issues` object * feat: integrate `buildIssuesOrPRsFromResponseNode` * feat: implement `issueOrPR` for correctly addressing issues and pr in logs * feat: implement improved chunk operation helper `inChunks` and integrate in pr and issues fetch * build: fix lints * test: update `integrations` test * test: address PR and Issue naming in logs in `success` * refactor: why the `Promise.all()`? Removed it haha * feat: set default `type` param in `buildIssuesOrPRsFromResponseNode` * feat: address edge cases * test: fixed matchers in graphql request in `success` units * build: lint * docs: add ignore bots pr/issues example * fix: user issue `number` over `id` * test: improve case `'Does not comment/label associatedPR and relatedIssues created by "Bots"'` * doc: modify `buildIssuesOrPRsFromResponseNode` documentation --------- Co-authored-by: Jonas Schubert <jonas.schubert.projects@web.de> Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
1 parent be071a2 commit e097d1c

8 files changed

+1873
-202
lines changed
 

‎README.md

+64-18
Large diffs are not rendered by default.

‎lib/fail.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,21 @@ export default async function fail(pluginConfig, context, { Octokit }) {
2323
githubApiPathPrefix,
2424
githubApiUrl,
2525
proxy,
26-
failComment,
2726
failTitle,
27+
failComment,
28+
failCommentCondition,
2829
labels,
2930
assignees,
3031
} = resolveConfig(pluginConfig, context);
3132

3233
if (failComment === false || failTitle === false) {
3334
logger.log("Skip issue creation.");
35+
// TODO: use logger.warn() instead of logger.log()
36+
logger.log(
37+
`DEPRECATION: 'false' for 'failComment' or 'failTitle' is deprecated and will be removed in a future major version. Use 'failCommentCondition' instead.`,
38+
);
39+
} else if (failCommentCondition === false) {
40+
logger.log("Skip issue creation.");
3441
} else {
3542
const octokit = new Octokit(
3643
toOctokitOptions({
@@ -52,6 +59,15 @@ export default async function fail(pluginConfig, context, { Octokit }) {
5259
: getFailComment(branch, errors);
5360
const [srIssue] = await findSRIssues(octokit, failTitle, owner, repo);
5461

62+
const canCommentOnOrCreateIssue = failCommentCondition
63+
? template(failCommentCondition)({ ...context, issue: srIssue })
64+
: true;
65+
66+
if (!canCommentOnOrCreateIssue) {
67+
logger.log("Skip commenting on or creating an issue.");
68+
return;
69+
}
70+
5571
if (srIssue) {
5672
logger.log("Found existing semantic-release issue #%d.", srIssue.number);
5773
const comment = { owner, repo, issue_number: srIssue.number, body };

‎lib/resolve-config.js

+4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ export default function resolveConfig(
88
proxy,
99
assets,
1010
successComment,
11+
successCommentCondition,
1112
failTitle,
1213
failComment,
14+
failCommentCondition,
1315
labels,
1416
assignees,
1517
releasedLabels,
@@ -30,10 +32,12 @@ export default function resolveConfig(
3032
proxy: isNil(proxy) ? env.http_proxy || env.HTTP_PROXY || false : proxy,
3133
assets: assets ? castArray(assets) : assets,
3234
successComment,
35+
successCommentCondition,
3336
failTitle: isNil(failTitle)
3437
? "The automated release is failing 🚨"
3538
: failTitle,
3639
failComment,
40+
failCommentCondition,
3741
labels: isNil(labels)
3842
? ["semantic-release"]
3943
: labels === false

‎lib/success.js

+267-28
Large diffs are not rendered by default.

‎lib/verify.js

+2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ const VALIDATORS = {
3838
(isPlainObject(asset) && isStringOrStringArray(asset.path)),
3939
),
4040
successComment: canBeDisabled(isNonEmptyString),
41+
successCommentCondition: canBeDisabled(isNonEmptyString),
4142
failTitle: canBeDisabled(isNonEmptyString),
4243
failComment: canBeDisabled(isNonEmptyString),
44+
failCommentCondition: canBeDisabled(isNonEmptyString),
4345
labels: canBeDisabled(isArrayOf(isNonEmptyString)),
4446
assignees: isArrayOf(isNonEmptyString),
4547
releasedLabels: canBeDisabled(isArrayOf(isNonEmptyString)),

‎test/fail.test.js

+146
Original file line numberDiff line numberDiff line change
@@ -451,3 +451,149 @@ test('Skip if "failTitle" is "false"', async (t) => {
451451

452452
t.true(t.context.log.calledWith("Skip issue creation."));
453453
});
454+
455+
test('Does not post comments if "failCommentCondition" is "false"', async (t) => {
456+
const owner = "test_user";
457+
const repo = "test_repo";
458+
const env = { GITHUB_TOKEN: "github_token" };
459+
const pluginConfig = { failCommentCondition: false };
460+
const options = { repositoryUrl: `https://github.com/${owner}/${repo}.git` };
461+
462+
await fail(
463+
pluginConfig,
464+
{
465+
env,
466+
options,
467+
branch: { name: "master" },
468+
logger: t.context.logger,
469+
},
470+
{
471+
Octokit: TestOctokit.defaults((options) => ({
472+
...options,
473+
request: { ...options.request, fetch },
474+
})),
475+
},
476+
);
477+
478+
t.true(t.context.log.calledWith("Skip issue creation."));
479+
});
480+
481+
test('Does not post comments on existing issues when "failCommentCondition" is "false"', async (t) => {
482+
const owner = "test_user";
483+
const repo = "test_repo";
484+
const failTitle = "The automated release is failing 🚨";
485+
const env = { GITHUB_TOKEN: "github_token" };
486+
const pluginConfig = { failCommentCondition: "<% return false; %>" };
487+
const options = { repositoryUrl: `https://github.com/${owner}/${repo}.git` };
488+
const errors = [
489+
new SemanticReleaseError("Error message 1", "ERR1", "Error 1 details"),
490+
new SemanticReleaseError("Error message 2", "ERR2", "Error 2 details"),
491+
new SemanticReleaseError("Error message 3", "ERR3", "Error 3 details"),
492+
];
493+
const issues = [
494+
{ number: 1, body: "Issue 1 body", title: failTitle },
495+
{ number: 2, body: `Issue 2 body\n\n${ISSUE_ID}`, title: failTitle },
496+
{ number: 3, body: `Issue 3 body\n\n${ISSUE_ID}`, title: failTitle },
497+
];
498+
499+
const fetch = fetchMock
500+
.sandbox()
501+
.getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
502+
full_name: `${owner}/${repo}`,
503+
})
504+
.getOnce(
505+
`https://api.github.local/search/issues?q=${encodeURIComponent(
506+
"in:title",
507+
)}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent(
508+
"type:issue",
509+
)}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`,
510+
{ items: issues },
511+
);
512+
513+
await fail(
514+
pluginConfig,
515+
{
516+
env,
517+
options,
518+
branch: { name: "master" },
519+
errors,
520+
logger: t.context.logger,
521+
},
522+
{
523+
Octokit: TestOctokit.defaults((options) => ({
524+
...options,
525+
request: { ...options.request, fetch },
526+
})),
527+
},
528+
);
529+
530+
t.true(fetch.done());
531+
t.true(t.context.log.calledWith("Skip commenting on or creating an issue."));
532+
});
533+
534+
test(`Post new issue if none exists yet, but don't comment on existing issues when "failCommentCondition" disallows it`, async (t) => {
535+
const owner = "test_user";
536+
const repo = "test_repo";
537+
const env = { GITHUB_TOKEN: "github_token" };
538+
const errors = [{ message: "An error occured" }];
539+
const failTitle = "The automated release is failing 🚨";
540+
const pluginConfig = {
541+
failTitle,
542+
failComment: `Error: Release for branch \${branch.name} failed with error: \${errors.map(error => error.message).join(';')}`,
543+
failCommentCondition: "<% return !issue; %>",
544+
};
545+
const options = {
546+
repositoryUrl: `https://github.com/${owner}/${repo}.git`,
547+
};
548+
549+
const fetch = fetchMock
550+
.sandbox()
551+
.getOnce(`https://api.github.local/repos/${owner}/${repo}`, {
552+
full_name: `${owner}/${repo}`,
553+
})
554+
.getOnce(
555+
`https://api.github.local/search/issues?q=${encodeURIComponent(
556+
"in:title",
557+
)}+${encodeURIComponent(`repo:${owner}/${repo}`)}+${encodeURIComponent(
558+
"type:issue",
559+
)}+${encodeURIComponent("state:open")}+${encodeURIComponent(failTitle)}`,
560+
{ items: [] },
561+
)
562+
.postOnce(
563+
(url, { body }) => {
564+
t.is(url, `https://api.github.local/repos/${owner}/${repo}/issues`);
565+
t.regex(
566+
JSON.parse(body).body,
567+
/Error: Release for branch master failed with error: An error occured\n\n<!-- semantic-release:github -->/,
568+
);
569+
return true;
570+
},
571+
{ html_url: "https://github.com/issues/2", number: 2 },
572+
);
573+
574+
await fail(
575+
pluginConfig,
576+
{
577+
env,
578+
options,
579+
branch: { name: "master" },
580+
errors,
581+
logger: t.context.logger,
582+
},
583+
{
584+
Octokit: TestOctokit.defaults((options) => ({
585+
...options,
586+
request: { ...options.request, fetch },
587+
})),
588+
},
589+
);
590+
591+
t.true(fetch.done());
592+
t.true(
593+
t.context.log.calledWith(
594+
"Created issue #%d: %s.",
595+
2,
596+
"https://github.com/issues/2",
597+
),
598+
);
599+
});

‎test/integration.test.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ test("Comment and add labels on PR included in the releases", async (t) => {
470470
const repo = "test_repo";
471471
const env = { GITHUB_TOKEN: "github_token" };
472472
const failTitle = "The automated release is failing 🚨";
473-
const prs = [{ number: 1, pull_request: {}, state: "closed" }];
473+
const prs = [{ number: 1, pull_request: true, state: "closed" }];
474474
const options = {
475475
repositoryUrl: `https://github.com/${owner}/${repo}.git`,
476476
};
@@ -568,13 +568,13 @@ test("Comment and add labels on PR included in the releases", async (t) => {
568568
t.deepEqual(t.context.log.args[0], ["Verify GitHub authentication"]);
569569
t.true(
570570
t.context.log.calledWith(
571-
"Added comment to issue #%d: %s",
571+
"Added comment to PR #%d: %s",
572572
1,
573573
"https://github.com/successcomment-1",
574574
),
575575
);
576576
t.true(
577-
t.context.log.calledWith("Added labels %O to issue #%d", ["released"], 1),
577+
t.context.log.calledWith("Added labels %O to PR #%d", ["released"], 1),
578578
);
579579
t.true(fetch.done());
580580
});
@@ -686,7 +686,7 @@ test("Verify, release and notify success", async (t) => {
686686
const uploadOrigin = "https://github.com";
687687
const uploadUri = `/api/uploads/repos/${owner}/${repo}/releases/${releaseId}/assets`;
688688
const uploadUrl = `${uploadOrigin}${uploadUri}{?name,label}`;
689-
const prs = [{ number: 1, pull_request: {}, state: "closed" }];
689+
const prs = [{ number: 1, pull_request: true, state: "closed" }];
690690
const commits = [{ hash: "123", message: "Commit 1 message" }];
691691

692692
const fetch = fetchMock
@@ -853,7 +853,7 @@ test("Verify, update release and notify success", async (t) => {
853853
};
854854
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
855855
const releaseId = 1;
856-
const prs = [{ number: 1, pull_request: {}, state: "closed" }];
856+
const prs = [{ number: 1, pull_request: true, state: "closed" }];
857857
const commits = [
858858
{ hash: "123", message: "Commit 1 message", tree: { long: "aaa" } },
859859
];

‎test/success.test.js

+1,368-150
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.