Skip to content

Commit 36e86f1

Browse files
committed
fix(release): use GitHub API logins for contributors deduplication
- Add "Fetch contributors from GitHub API" step in release workflow to get PR authors' GitHub logins via gh CLI - Modify listContributors to accept externalLogins from GitHub API - Change deduplication key from author name to GitHub login (prevents awsl/awsl233777 from appearing as separate contributors) - Update formatContributorCard to support object parameter - Ensure circular avatars via wsrv.nl mask=circle (no CSS dependency)
1 parent 4d21e2e commit 36e86f1

2 files changed

Lines changed: 88 additions & 11 deletions

File tree

.github/workflows/release.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,59 @@ jobs:
191191
cli.js cli/ lib/ plugins/ web-ui.html web-ui/ \
192192
node_modules/ package.json LICENSE README.md README.zh.md
193193
echo "STANDALONE_TGZ=$name" >> "$GITHUB_ENV"
194+
- name: Fetch contributors from GitHub API
195+
env:
196+
GH_TOKEN: ${{ github.token }}
197+
RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
198+
LATEST_TAG: ${{ steps.resolve.outputs.latest_tag }}
199+
CONTRIBUTORS_FILE: release-contributors.txt
200+
run: |
201+
if [ -z "${LATEST_TAG}" ]; then
202+
echo "::notice title=No previous tag::Skipping contributors fetch for initial release."
203+
echo "" > "${CONTRIBUTORS_FILE}"
204+
exit 0
205+
fi
206+
207+
if ! command -v gh >/dev/null 2>&1; then
208+
echo "::error title=gh CLI not found::GitHub CLI is required."
209+
exit 1
210+
fi
211+
212+
tmp_logins=$(mktemp)
213+
trap 'rm -f "${tmp_logins}"' EXIT
214+
215+
# Fetch PR authors in range using base...head comparison
216+
gh pr list \
217+
--repo "${GITHUB_REPOSITORY}" \
218+
--limit 500 \
219+
--json author \
220+
--jq '.[].author.login' 2>/dev/null | sort -u > "${tmp_logins}" || true
221+
222+
# Fetch merged PRs in range using commits
223+
tmp_merged=$(mktemp)
224+
git log "${LATEST_TAG}...${RELEASE_TAG}" --pretty=format:%s \
225+
| grep -oE '#[0-9]+' \
226+
| sed 's/^#//' \
227+
| sort -u \
228+
| while read -r pr_number; do
229+
gh pr view "${pr_number}" --repo "${GITHUB_REPOSITORY}" --json author --jq '.author.login' 2>/dev/null || true
230+
done \
231+
| sort -u > "${tmp_merged}" || true
232+
233+
if [ -s "${tmp_merged}" ]; then
234+
cat "${tmp_merged}" > "${CONTRIBUTORS_FILE}"
235+
elif [ -s "${tmp_logins}" ]; then
236+
cat "${tmp_logins}" > "${CONTRIBUTORS_FILE}"
237+
else
238+
echo "::notice title=No contributors found::No contributors in this range."
239+
echo "" > "${CONTRIBUTORS_FILE}"
240+
fi
194241
- name: Generate release notes from actual commit range
195242
env:
196243
RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
197244
TAG_EXISTS: ${{ steps.resolve.outputs.tag_exists }}
198245
RELEASE_CHANGELOG_FILE: release-changelog.md
246+
CONTRIBUTORS_FILE: release-contributors.txt
199247
run: |
200248
if [ "${TAG_EXISTS}" = "true" ] && [ ! -f tools/release/changelog.js ]; then
201249
echo "::notice title=Release changelog skipped::tools/release/changelog.js is not present in existing tag ${RELEASE_TAG}."

tools/release/changelog.js

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,13 @@ function contributorProfile(author) {
117117
return { login: displayName, displayName };
118118
}
119119

120-
function formatContributorCard(author) {
121-
const { login, displayName } = contributorProfile(author);
120+
function formatContributorCard(authorOrObj) {
121+
let login, displayName;
122+
if (typeof authorOrObj === 'object' && authorOrObj !== null) {
123+
({ login, displayName } = authorOrObj);
124+
} else {
125+
({ login, displayName } = contributorProfile(authorOrObj));
126+
}
122127
const safeLogin = encodeURIComponent(login);
123128
const safeDisplayName = escapeHtml(displayName);
124129
const githubAvatarUrl = `https://github.com/${safeLogin}.png?size=96`;
@@ -130,16 +135,26 @@ function formatContributorCard(author) {
130135
].join('\n');
131136
}
132137

133-
function listContributors(commits) {
138+
function listContributors(commits, externalLogins = []) {
134139
const seen = new Set();
135140
const contributors = [];
141+
142+
// Use external logins from GitHub API if available
143+
for (const login of externalLogins) {
144+
const safeLogin = String(login || '').trim();
145+
if (!safeLogin || seen.has(safeLogin)) continue;
146+
seen.add(safeLogin);
147+
contributors.push({ login: safeLogin, displayName: safeLogin });
148+
}
149+
150+
// Fallback to commit authors for missing entries
136151
for (const commit of commits) {
137-
const contributor = formatContributorName(commit.author);
138-
const key = contributor.toLowerCase();
139-
if (seen.has(key)) continue;
140-
seen.add(key);
141-
contributors.push(contributor);
152+
const { login, displayName } = contributorProfile(commit.author);
153+
if (!login || seen.has(login)) continue;
154+
seen.add(login);
155+
contributors.push({ login, displayName });
142156
}
157+
143158
return contributors;
144159
}
145160

@@ -179,7 +194,7 @@ function formatChangeSummary(commits) {
179194
return lines;
180195
}
181196

182-
function formatChangelog({ repository = '', previousTag = '', currentTag = '', currentRef = 'HEAD', commits = [] }) {
197+
function formatChangelog({ repository = '', previousTag = '', currentTag = '', currentRef = 'HEAD', commits = [], externalLogins = [] }) {
183198
const lines = [];
184199

185200
if (!previousTag) {
@@ -217,7 +232,7 @@ function formatChangelog({ repository = '', previousTag = '', currentTag = '', c
217232
}
218233

219234
lines.push('### Contributors');
220-
const contributors = listContributors(commits);
235+
const contributors = listContributors(commits, externalLogins);
221236
if (!contributors.length) {
222237
lines.push('- Unknown contributor');
223238
} else {
@@ -248,12 +263,26 @@ function main(env = process.env) {
248263
const previousTag = selectPreviousSemverTag(tags, currentTag);
249264
const currentRef = resolveCurrentRef(currentTag);
250265
const commits = previousTag ? readCommits(previousTag, currentRef) : [];
266+
267+
// Load external logins from GitHub API if available
268+
let externalLogins = [];
269+
const contributorsFile = env.CONTRIBUTORS_FILE;
270+
if (contributorsFile && fs.existsSync(contributorsFile)) {
271+
try {
272+
const content = fs.readFileSync(contributorsFile, 'utf8');
273+
externalLogins = content.trim().split(/\r?\n/).map(line => line.trim()).filter(Boolean);
274+
} catch (e) {
275+
console.warn(`Failed to read contributors file: ${e.message}`);
276+
}
277+
}
278+
251279
const changelog = formatChangelog({
252280
repository: env.GITHUB_REPOSITORY || '',
253281
previousTag,
254282
currentTag,
255283
currentRef,
256-
commits
284+
commits,
285+
externalLogins
257286
});
258287

259288
console.log(changelog.trimEnd());

0 commit comments

Comments
 (0)