-
Notifications
You must be signed in to change notification settings - Fork 31
fix(release): use GitHub API logins for contributors deduplication #205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -191,11 +191,59 @@ jobs: | |||||||||||||||||||||||||||||||||||||||||||
| cli.js cli/ lib/ plugins/ web-ui.html web-ui/ \ | ||||||||||||||||||||||||||||||||||||||||||||
| node_modules/ package.json LICENSE README.md README.zh.md | ||||||||||||||||||||||||||||||||||||||||||||
| echo "STANDALONE_TGZ=$name" >> "$GITHUB_ENV" | ||||||||||||||||||||||||||||||||||||||||||||
| - name: Fetch contributors from GitHub API | ||||||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||||||
| GH_TOKEN: ${{ github.token }} | ||||||||||||||||||||||||||||||||||||||||||||
| RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }} | ||||||||||||||||||||||||||||||||||||||||||||
| LATEST_TAG: ${{ steps.resolve.outputs.latest_tag }} | ||||||||||||||||||||||||||||||||||||||||||||
| CONTRIBUTORS_FILE: release-contributors.txt | ||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||
| if [ -z "${LATEST_TAG}" ]; then | ||||||||||||||||||||||||||||||||||||||||||||
| echo "::notice title=No previous tag::Skipping contributors fetch for initial release." | ||||||||||||||||||||||||||||||||||||||||||||
| echo "" > "${CONTRIBUTORS_FILE}" | ||||||||||||||||||||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if ! command -v gh >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||||||
| echo "::error title=gh CLI not found::GitHub CLI is required." | ||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| tmp_logins=$(mktemp) | ||||||||||||||||||||||||||||||||||||||||||||
| trap 'rm -f "${tmp_logins}"' EXIT | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # Fetch PR authors in range using base...head comparison | ||||||||||||||||||||||||||||||||||||||||||||
| gh pr list \ | ||||||||||||||||||||||||||||||||||||||||||||
| --repo "${GITHUB_REPOSITORY}" \ | ||||||||||||||||||||||||||||||||||||||||||||
| --limit 500 \ | ||||||||||||||||||||||||||||||||||||||||||||
| --json author \ | ||||||||||||||||||||||||||||||||||||||||||||
| --jq '.[].author.login' 2>/dev/null | sort -u > "${tmp_logins}" || true | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+216
to
+220
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: The default state for the gh pr list command is open [1][2][3]. Regarding your specific questions: Default Filters: The gh pr list command filters by state, and its default value is open [1][2][4]. The available states you can filter by are open, closed, merged, and all [1][3][4]. There is no scope parameter in the command itself; however, you can constrain results to a specific repository using the -R or --repo flag [2][3]. Constraint by Git Tags: The gh pr list command does not natively support constraining results to commits between two git tags [1][2][4]. While you can use the --search flag to pass advanced GitHub search queries—such as searching by a specific commit SHA—it does not support range-based filtering using git tags directly [2][5][4]. To filter pull requests based on commits or time ranges associated with tags, you would typically need to identify the commit SHAs corresponding to your tags (using standard git commands like git rev-list or git show) and then use those SHAs or the associated dates within a --search query [2][4][6]. For example, if you have the commit SHAs, you might use the search syntax within the -S flag, though this requires manual translation of your git tags into inputs the search API accepts [2][4]. Citations:
🏁 Script executed: sed -n '200,250p' .github/workflows/release.ymlRepository: SakuraByteCore/codexmate Length of output: 2220 Fix the unscoped contributor fallback in release workflow. The Replace the fallback to keep the contributor list empty with a notice instead: Suggested patch- elif [ -s "${tmp_logins}" ]; then
- cat "${tmp_logins}" > "${CONTRIBUTORS_FILE}"
+ elif [ -s "${tmp_logins}" ]; then
+ echo "::notice title=No range-scoped PR contributors::Using empty contributor list."
+ : > "${CONTRIBUTORS_FILE}"🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # Fetch merged PRs in range using commits | ||||||||||||||||||||||||||||||||||||||||||||
| tmp_merged=$(mktemp) | ||||||||||||||||||||||||||||||||||||||||||||
| git log "${LATEST_TAG}...${RELEASE_TAG}" --pretty=format:%s \ | ||||||||||||||||||||||||||||||||||||||||||||
| | grep -oE '#[0-9]+' \ | ||||||||||||||||||||||||||||||||||||||||||||
| | sed 's/^#//' \ | ||||||||||||||||||||||||||||||||||||||||||||
| | sort -u \ | ||||||||||||||||||||||||||||||||||||||||||||
| | while read -r pr_number; do | ||||||||||||||||||||||||||||||||||||||||||||
| gh pr view "${pr_number}" --repo "${GITHUB_REPOSITORY}" --json author --jq '.author.login' 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||
| done \ | ||||||||||||||||||||||||||||||||||||||||||||
| | sort -u > "${tmp_merged}" || true | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+224
to
+231
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use an existing ref for the commit-range upper bound. On Line 224, Suggested fix+ range_head="${RELEASE_TAG}"
+ if ! git rev-parse --verify --quiet "refs/tags/${RELEASE_TAG}" >/dev/null; then
+ range_head="HEAD"
+ fi
+
- git log "${LATEST_TAG}...${RELEASE_TAG}" --pretty=format:%s \
+ git log "${LATEST_TAG}...${range_head}" --pretty=format:%s \
| grep -oE '#[0-9]+' \
| sed 's/^`#//`' \
| sort -u \
| while read -r pr_number; do
gh pr view "${pr_number}" --repo "${GITHUB_REPOSITORY}" --json author --jq '.author.login' 2>/dev/null || true
done \
| sort -u > "${tmp_merged}" || true📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if [ -s "${tmp_merged}" ]; then | ||||||||||||||||||||||||||||||||||||||||||||
| cat "${tmp_merged}" > "${CONTRIBUTORS_FILE}" | ||||||||||||||||||||||||||||||||||||||||||||
| elif [ -s "${tmp_logins}" ]; then | ||||||||||||||||||||||||||||||||||||||||||||
| cat "${tmp_logins}" > "${CONTRIBUTORS_FILE}" | ||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||
| echo "::notice title=No contributors found::No contributors in this range." | ||||||||||||||||||||||||||||||||||||||||||||
| echo "" > "${CONTRIBUTORS_FILE}" | ||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||
| - name: Generate release notes from actual commit range | ||||||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||||||
| RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }} | ||||||||||||||||||||||||||||||||||||||||||||
| TAG_EXISTS: ${{ steps.resolve.outputs.tag_exists }} | ||||||||||||||||||||||||||||||||||||||||||||
| RELEASE_CHANGELOG_FILE: release-changelog.md | ||||||||||||||||||||||||||||||||||||||||||||
| CONTRIBUTORS_FILE: release-contributors.txt | ||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||
| if [ "${TAG_EXISTS}" = "true" ] && [ ! -f tools/release/changelog.js ]; then | ||||||||||||||||||||||||||||||||||||||||||||
| echo "::notice title=Release changelog skipped::tools/release/changelog.js is not present in existing tag ${RELEASE_TAG}." | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,8 +117,13 @@ function contributorProfile(author) { | |
| return { login: displayName, displayName }; | ||
| } | ||
|
|
||
| function formatContributorCard(author) { | ||
| const { login, displayName } = contributorProfile(author); | ||
| function formatContributorCard(authorOrObj) { | ||
| let login, displayName; | ||
| if (typeof authorOrObj === 'object' && authorOrObj !== null) { | ||
| ({ login, displayName } = authorOrObj); | ||
| } else { | ||
| ({ login, displayName } = contributorProfile(authorOrObj)); | ||
| } | ||
| const safeLogin = encodeURIComponent(login); | ||
| const safeDisplayName = escapeHtml(displayName); | ||
| const githubAvatarUrl = `https://github.com/${safeLogin}.png?size=96`; | ||
|
|
@@ -130,16 +135,26 @@ function formatContributorCard(author) { | |
| ].join('\n'); | ||
| } | ||
|
|
||
| function listContributors(commits) { | ||
| function listContributors(commits, externalLogins = []) { | ||
| const seen = new Set(); | ||
| const contributors = []; | ||
|
|
||
| // Use external logins from GitHub API if available | ||
| for (const login of externalLogins) { | ||
| const safeLogin = String(login || '').trim(); | ||
| if (!safeLogin || seen.has(safeLogin)) continue; | ||
| seen.add(safeLogin); | ||
| contributors.push({ login: safeLogin, displayName: safeLogin }); | ||
| } | ||
|
|
||
| // Fallback to commit authors for missing entries | ||
| for (const commit of commits) { | ||
| const contributor = formatContributorName(commit.author); | ||
| const key = contributor.toLowerCase(); | ||
| if (seen.has(key)) continue; | ||
| seen.add(key); | ||
| contributors.push(contributor); | ||
| const { login, displayName } = contributorProfile(commit.author); | ||
| if (!login || seen.has(login)) continue; | ||
| seen.add(login); | ||
|
Comment on lines
+143
to
+154
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Normalize login keys before deduplication. On Line 145 and Line 153, dedupe uses case-sensitive keys. This can still emit duplicates when the same GitHub login appears with different casing across sources. Suggested patch for (const login of externalLogins) {
const safeLogin = String(login || '').trim();
- if (!safeLogin || seen.has(safeLogin)) continue;
- seen.add(safeLogin);
+ const loginKey = safeLogin.toLowerCase();
+ if (!safeLogin || seen.has(loginKey)) continue;
+ seen.add(loginKey);
contributors.push({ login: safeLogin, displayName: safeLogin });
}
...
for (const commit of commits) {
const { login, displayName } = contributorProfile(commit.author);
- if (!login || seen.has(login)) continue;
- seen.add(login);
+ const loginKey = String(login || '').toLowerCase();
+ if (!login || seen.has(loginKey)) continue;
+ seen.add(loginKey);
contributors.push({ login, displayName });
}🤖 Prompt for AI Agents |
||
| contributors.push({ login, displayName }); | ||
| } | ||
|
|
||
| return contributors; | ||
| } | ||
|
|
||
|
|
@@ -179,7 +194,7 @@ function formatChangeSummary(commits) { | |
| return lines; | ||
| } | ||
|
|
||
| function formatChangelog({ repository = '', previousTag = '', currentTag = '', currentRef = 'HEAD', commits = [] }) { | ||
| function formatChangelog({ repository = '', previousTag = '', currentTag = '', currentRef = 'HEAD', commits = [], externalLogins = [] }) { | ||
| const lines = []; | ||
|
|
||
| if (!previousTag) { | ||
|
|
@@ -217,7 +232,7 @@ function formatChangelog({ repository = '', previousTag = '', currentTag = '', c | |
| } | ||
|
|
||
| lines.push('### Contributors'); | ||
| const contributors = listContributors(commits); | ||
| const contributors = listContributors(commits, externalLogins); | ||
| if (!contributors.length) { | ||
| lines.push('- Unknown contributor'); | ||
| } else { | ||
|
|
@@ -248,12 +263,26 @@ function main(env = process.env) { | |
| const previousTag = selectPreviousSemverTag(tags, currentTag); | ||
| const currentRef = resolveCurrentRef(currentTag); | ||
| const commits = previousTag ? readCommits(previousTag, currentRef) : []; | ||
|
|
||
| // Load external logins from GitHub API if available | ||
| let externalLogins = []; | ||
| const contributorsFile = env.CONTRIBUTORS_FILE; | ||
| if (contributorsFile && fs.existsSync(contributorsFile)) { | ||
| try { | ||
| const content = fs.readFileSync(contributorsFile, 'utf8'); | ||
| externalLogins = content.trim().split(/\r?\n/).map(line => line.trim()).filter(Boolean); | ||
| } catch (e) { | ||
| console.warn(`Failed to read contributors file: ${e.message}`); | ||
| } | ||
| } | ||
|
|
||
| const changelog = formatChangelog({ | ||
| repository: env.GITHUB_REPOSITORY || '', | ||
| previousTag, | ||
| currentTag, | ||
| currentRef, | ||
| commits | ||
| commits, | ||
| externalLogins | ||
| }); | ||
|
|
||
| console.log(changelog.trimEnd()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the same baseline tag as changelog generation.
Line 224 derives PRs from
LATEST_TAG...RELEASE_TAG, but changelog generation uses the previous semver tag for the target release. This mismatch can produce contributor data for a different range.Also applies to: 224-224
🤖 Prompt for AI Agents