Skip to content

Commit 87ebd7e

Browse files
authored
List multiple authors in release notes (#94)
1 parent cfcf265 commit 87ebd7e

8 files changed

Lines changed: 180 additions & 21 deletions

File tree

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
* text=auto
22

33
# don't diff machine generated files
4-
lib/index.js -diff
4+
lib/index.mjs -diff
55
package-lock.json -diff

.husky/pre-commit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#!/bin/sh
2+
13
# Format the code base
24
npm run format
35
git add -u

__mocks__/@actions/github.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ const PRs = [
106106
number: 507,
107107
merged_at: '2011-01-26T19:01:12Z',
108108
base: { ref: 'develop' },
109-
user: { login: 'mondeja' },
109+
user: { login: ['mondeja', 'LitoMore'] },
110110
},
111111
{
112112
// 9: PR that modifies an SVG and modifies that icon's color and source

lib/index.mjs

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32816,8 +32816,8 @@ module.exports = parseParams
3281632816
var __webpack_exports__ = {};
3281732817

3281832818
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js
32819-
var core = __nccwpck_require__(7484);
32820-
var core_namespaceObject = /*#__PURE__*/__nccwpck_require__.t(core, 2);
32819+
var lib_core = __nccwpck_require__(7484);
32820+
var core_namespaceObject = /*#__PURE__*/__nccwpck_require__.t(lib_core, 2);
3282132821
// EXTERNAL MODULE: ./node_modules/@actions/github/lib/github.js
3282232822
var github = __nccwpck_require__(3228);
3282332823
var github_namespaceObject = /*#__PURE__*/__nccwpck_require__.t(github, 2);
@@ -33010,7 +33010,8 @@ function prNumbersToString(prNumbers) {
3301033010
}
3301133011

3301233012
function authorsToString(authors) {
33013-
return authors.map((author) => `@${author}`).join(', ');
33013+
const flatAuthors = [...new Set(authors.flat())];
33014+
return flatAuthors.map((author) => `@${author}`).join(', ');
3301433015
}
3301533016

3301633017
// GitHub API
@@ -33175,11 +33176,13 @@ async function getFilesSinceLastRelease(core, client, context) {
3317533176
);
3317633177
}
3317733178

33179+
const authors = await getFinalCommitAuthors(client, context, pr);
33180+
3317833181
for (let file of await getPrFiles(core, client, context, pr.number)) {
3317933182
core.info(`found '${file.path}' in PR #${pr.number}`);
3318033183
file.prNumber = pr.number;
33181-
file.author = pr.user.login;
3318233184
file.merged_at = pr.merged_at;
33185+
file.authors = authors;
3318333186
files.push(file);
3318433187
}
3318533188
}
@@ -33193,6 +33196,83 @@ async function getFilesSinceLastRelease(core, client, context) {
3319333196
}
3319433197
}
3319533198

33199+
/**
33200+
* Gets all authors from a PR, including the main author and co-authors
33201+
* from the final merge commit message.
33202+
*
33203+
* @param {Object} client - GitHub client (Octokit)
33204+
* @param {Object} context - GitHub Actions context
33205+
* @param {Object} pr - Pull Request object
33206+
* @returns {Promise<string[]>} Array of GitHub usernames of all authors
33207+
*/
33208+
async function getFinalCommitAuthors(client, context, pr) {
33209+
const coAuthorExpr = /^Co-authored-by:\s*(.+?)\s*<(.+?)>\s*$/gim;
33210+
33211+
try {
33212+
if (!pr.merge_commit_sha) {
33213+
return [pr.user.login];
33214+
}
33215+
33216+
const { data: commitDetails } = await client.rest.git.getCommit({
33217+
owner: context.repo.owner,
33218+
repo: context.repo.repo,
33219+
commit_sha: pr.merge_commit_sha,
33220+
});
33221+
33222+
const authors = new Set([pr.user.login]);
33223+
33224+
if (commitDetails.author?.name) {
33225+
const authorEmail = commitDetails.author.email;
33226+
const githubUserMatch = authorEmail?.match(
33227+
/^(\d+\+)?(.+?)@users\.noreply\.github\.com$/,
33228+
);
33229+
33230+
if (githubUserMatch) {
33231+
authors.add(githubUserMatch[2]);
33232+
} else if (pr.merged_by?.login) {
33233+
authors.add(pr.merged_by.login);
33234+
}
33235+
} else if (pr.merged_by?.login) {
33236+
authors.add(pr.merged_by.login);
33237+
}
33238+
33239+
const commitMessage = commitDetails.message;
33240+
const coAuthorMatches = commitMessage.matchAll(coAuthorExpr);
33241+
33242+
for (const match of coAuthorMatches) {
33243+
const email = match[2];
33244+
33245+
const githubUserMatch = email.match(
33246+
/^(\d+\+)?(.+?)@users\.noreply\.github\.com$/,
33247+
);
33248+
33249+
if (githubUserMatch) {
33250+
authors.add(githubUserMatch[2]);
33251+
} else {
33252+
const name = match[1].trim();
33253+
try {
33254+
const { data: searchResults } = await client.rest.search.users({
33255+
q: `${email} in:email`,
33256+
});
33257+
33258+
if (searchResults.items.length > 0) {
33259+
authors.add(searchResults.items[0].login);
33260+
} else {
33261+
authors.add(name);
33262+
}
33263+
} catch (error) {
33264+
authors.add(name);
33265+
}
33266+
}
33267+
}
33268+
33269+
return Array.from(authors);
33270+
} catch (error) {
33271+
core.error(`Error getting authors for PR #${pr.number}:`, error);
33272+
return [pr.user?.login || 'unknown'];
33273+
}
33274+
}
33275+
3319633276
// Logic determining changes
3319733277
async function getChangesFromFile(core, file, client, context, id) {
3319833278
if (isIconFile(file.path) && file.status === STATUS_ADDED) {
@@ -33206,7 +33286,7 @@ async function getChangesFromFile(core, file, client, context, id) {
3320633286
name: he.decode(svgTitleMatch[1]),
3320733287
path: file.path,
3320833288
prNumbers: [file.prNumber],
33209-
authors: [file.author],
33289+
authors: file.authors,
3321033290
},
3321133291
];
3321233292
} else if (isIconFile(file.path) && file.status === STATUS_MODIFIED) {
@@ -33221,7 +33301,7 @@ async function getChangesFromFile(core, file, client, context, id) {
3322133301
name: he.decode(svgTitleMatch[1]),
3322233302
path: file.path,
3322333303
prNumbers: [file.prNumber],
33224-
authors: [file.author],
33304+
authors: file.authors,
3322533305
},
3322633306
];
3322733307
} else if (isIconFile(file.path) && file.status === STATUS_REMOVED) {
@@ -33235,7 +33315,7 @@ async function getChangesFromFile(core, file, client, context, id) {
3323533315
name: he.decode(svgTitleMatch[1]),
3323633316
path: file.path,
3323733317
prNumbers: [file.prNumber],
33238-
authors: [file.author],
33318+
authors: file.authors,
3323933319
},
3324033320
];
3324133321
} else if (isSimpleIconsDataFile(file.path)) {
@@ -33270,7 +33350,7 @@ async function getChangesFromFile(core, file, client, context, id) {
3327033350
changeType: CHANGE_TYPE_UPDATE,
3327133351
name: name,
3327233352
prNumbers: [file.prNumber],
33273-
authors: [file.author],
33353+
authors: file.authors,
3327433354
});
3327533355
}
3327633356

package-lock.json

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "si-releaser",
3-
"version": "2.0.0",
3+
"version": "2.0.1",
44
"private": true,
55
"description": "Release Action for Simple Icons",
66
"license": "CC0-1.0",

src/create.js

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ function prNumbersToString(prNumbers) {
8888
}
8989

9090
function authorsToString(authors) {
91-
return authors.map((author) => `@${author}`).join(', ');
91+
const flatAuthors = [...new Set(authors.flat())];
92+
return flatAuthors.map((author) => `@${author}`).join(', ');
9293
}
9394

9495
// GitHub API
@@ -253,11 +254,13 @@ async function getFilesSinceLastRelease(core, client, context) {
253254
);
254255
}
255256

257+
const authors = await getFinalCommitAuthors(client, context, pr);
258+
256259
for (let file of await getPrFiles(core, client, context, pr.number)) {
257260
core.info(`found '${file.path}' in PR #${pr.number}`);
258261
file.prNumber = pr.number;
259-
file.author = pr.user.login;
260262
file.merged_at = pr.merged_at;
263+
file.authors = authors;
261264
files.push(file);
262265
}
263266
}
@@ -271,6 +274,83 @@ async function getFilesSinceLastRelease(core, client, context) {
271274
}
272275
}
273276

277+
/**
278+
* Gets all authors from a PR, including the main author and co-authors
279+
* from the final merge commit message.
280+
*
281+
* @param {Object} client - GitHub client (Octokit)
282+
* @param {Object} context - GitHub Actions context
283+
* @param {Object} pr - Pull Request object
284+
* @returns {Promise<string[]>} Array of GitHub usernames of all authors
285+
*/
286+
async function getFinalCommitAuthors(client, context, pr) {
287+
const coAuthorExpr = /^Co-authored-by:\s*(.+?)\s*<(.+?)>\s*$/gim;
288+
289+
try {
290+
if (!pr.merge_commit_sha) {
291+
return [pr.user.login];
292+
}
293+
294+
const { data: commitDetails } = await client.rest.git.getCommit({
295+
owner: context.repo.owner,
296+
repo: context.repo.repo,
297+
commit_sha: pr.merge_commit_sha,
298+
});
299+
300+
const authors = new Set([pr.user.login]);
301+
302+
if (commitDetails.author?.name) {
303+
const authorEmail = commitDetails.author.email;
304+
const githubUserMatch = authorEmail?.match(
305+
/^(\d+\+)?(.+?)@users\.noreply\.github\.com$/,
306+
);
307+
308+
if (githubUserMatch) {
309+
authors.add(githubUserMatch[2]);
310+
} else if (pr.merged_by?.login) {
311+
authors.add(pr.merged_by.login);
312+
}
313+
} else if (pr.merged_by?.login) {
314+
authors.add(pr.merged_by.login);
315+
}
316+
317+
const commitMessage = commitDetails.message;
318+
const coAuthorMatches = commitMessage.matchAll(coAuthorExpr);
319+
320+
for (const match of coAuthorMatches) {
321+
const email = match[2];
322+
323+
const githubUserMatch = email.match(
324+
/^(\d+\+)?(.+?)@users\.noreply\.github\.com$/,
325+
);
326+
327+
if (githubUserMatch) {
328+
authors.add(githubUserMatch[2]);
329+
} else {
330+
const name = match[1].trim();
331+
try {
332+
const { data: searchResults } = await client.rest.search.users({
333+
q: `${email} in:email`,
334+
});
335+
336+
if (searchResults.items.length > 0) {
337+
authors.add(searchResults.items[0].login);
338+
} else {
339+
authors.add(name);
340+
}
341+
} catch (error) {
342+
authors.add(name);
343+
}
344+
}
345+
}
346+
347+
return Array.from(authors);
348+
} catch (error) {
349+
core.error(`Error getting authors for PR #${pr.number}:`, error);
350+
return [pr.user?.login || 'unknown'];
351+
}
352+
}
353+
274354
// Logic determining changes
275355
async function getChangesFromFile(core, file, client, context, id) {
276356
if (isIconFile(file.path) && file.status === STATUS_ADDED) {
@@ -284,7 +364,7 @@ async function getChangesFromFile(core, file, client, context, id) {
284364
name: he.decode(svgTitleMatch[1]),
285365
path: file.path,
286366
prNumbers: [file.prNumber],
287-
authors: [file.author],
367+
authors: file.authors,
288368
},
289369
];
290370
} else if (isIconFile(file.path) && file.status === STATUS_MODIFIED) {
@@ -299,7 +379,7 @@ async function getChangesFromFile(core, file, client, context, id) {
299379
name: he.decode(svgTitleMatch[1]),
300380
path: file.path,
301381
prNumbers: [file.prNumber],
302-
authors: [file.author],
382+
authors: file.authors,
303383
},
304384
];
305385
} else if (isIconFile(file.path) && file.status === STATUS_REMOVED) {
@@ -313,7 +393,7 @@ async function getChangesFromFile(core, file, client, context, id) {
313393
name: he.decode(svgTitleMatch[1]),
314394
path: file.path,
315395
prNumbers: [file.prNumber],
316-
authors: [file.author],
396+
authors: file.authors,
317397
},
318398
];
319399
} else if (isSimpleIconsDataFile(file.path)) {
@@ -348,7 +428,7 @@ async function getChangesFromFile(core, file, client, context, id) {
348428
changeType: CHANGE_TYPE_UPDATE,
349429
name: name,
350430
prNumbers: [file.prNumber],
351-
authors: [file.author],
431+
authors: file.authors,
352432
});
353433
}
354434

test/create.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ The new version will be: **v2.0.0**
2424
- AddThis (#508) (@PeterShaggyNoble)
2525
- Adobe (#504) (@fbernhart)
2626
- Feedly (#502) (@mondeja)
27-
- Intel (#507) (@mondeja)
27+
- Intel (#507) (@mondeja, @LitoMore)
2828
- Mozilla (#505) (@adamrusted)
2929
- Opera (#510) (@fbernhart)
3030
- Postman (#506) (@LitoMore)

0 commit comments

Comments
 (0)