Skip to content

fix: ensure retrieving username from email uses correct var #70

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

Merged
merged 1 commit into from
Oct 24, 2024
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
61 changes: 37 additions & 24 deletions .github/scripts/changelog.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,32 @@ Ignore merge commits and minor changes. For each commit, use only the first line
Translate Conventional Commit messages into professional, human-readable language, avoiding technical jargon.

For each commit, use this format:
- **Bold 3-5 word Summary** (with related GitHub emoji): Continuation with 1-3 sentence description. @author (optional #PR)
- **Bold 3-5 word Summary** {optional related GitHub emoji}: Continuation with 1-3 sentence description. @author (optional #PR)
- Sub-bullets for key details (include only if necessary)

Important formatting rules:
- Place PR/issue numbers matching the exact pattern #\d+ (e.g., #123) at the end of the section in parentheses.
- Do not use commit hashes as PR numbers
- If no PR/issue number is found matching #\\d+, omit the parenthetical reference entirely
- If the author is specified, include their GitHub username at the end of the section, just before the PR/issue number with a "@" symbol - e.g. @author.
- If the author is not specified, omit the GitHub username.
- Only include sub-bullets if they are necessary to clarify the change.
- Avoid level 4 headings.
- Use level 3 (###) for sections.
- Omit sections with no content silently - do not add any notes or explanations about omitted sections.
Place PR/issue numbers matching the exact pattern #\d+ (e.g., #123) at the end of the section in parentheses.

Do not use commit hashes as PR numbers.

If no PR/issue number is found matching #\\d+, omit the parenthetical reference entirely.

If the author is specified, include their GitHub username at the end of the section, just before the PR/issue number with a "@" symbol - e.g. @author.

If the author is not specified, omit the GitHub username.

Only include sub-bullets if they are necessary to clarify the change.

Do not include any sections with no content.

Do not include sections where there are no grouped changes.

Do not include sections where content is similar to "No breaking changes in this release".

Avoid level 4 headings; use level 3 (###) for sections.

Attempt to add an emoji into the {optional related GitHub emoji} section of the summary that relates to the bold-3-5 word summary and 1-3 sentence description.

Omit sections with no content silently - do not add any notes or explanations about omitted sections.
`;

// In-memory cache for username lookups
Expand Down Expand Up @@ -153,11 +166,11 @@ async function githubApiRequestWithRetry(path, retries = 2) {
* Attempts to resolve a GitHub username from a commit email address
* using multiple GitHub API endpoints.
*
* @param {string} commitEmail - The email address from the git commit
* @param {string} email - The email address from the git commit
* @returns {Promise<string|null>} - GitHub username if found, null otherwise
*/
async function resolveGitHubUsername(commitEmail) {
console.log('Attempting to resolve username:', commitEmail);
async function resolveGitHubUsername(email) {
console.log('Attempting to resolve username:', email);

// Local resolution - Handle various GitHub email patterns
const emailMatches = email.match(/^(?:(?:[^@]+)?@)?([^@]+)$/);
Expand Down Expand Up @@ -194,38 +207,38 @@ async function resolveGitHubUsername(commitEmail) {

try {
// First attempt: Direct API search for user by email
console.log(`[${commitEmail}] Querying user API`);
console.log(`[${email}] Querying user API`);
const searchResponse = await githubApiRequestWithRetry(
`https://api.github.com/search/users?q=${encodeURIComponent(commitEmail)}+in:email`,
`https://api.github.com/search/users?q=${encodeURIComponent(email)}+in:email`,
);
if (searchResponse?.items && searchResponse.items.length > 0) {
console.log(`[${commitEmail}] Found username`);
console.log(`[${email}] Found username`);
// Get the first matching user
return searchResponse.items[0].login;
}
console.log(`[${commitEmail}] No username found via user API`);
console.log(`[${email}] No username found via user API`);
} catch (error) {
console.error(`[${commitEmail}] Error resolving GitHub username via user API:`, error);
console.error(`[${email}] Error resolving GitHub username via user API:`, error);
}

try {
console.log(`[${commitEmail}] Querying commit API`);
console.log(`[${email}] Querying commit API`);
// Second attempt: Check commit API for associated username
const commitSearchResponse = await githubApiRequestWithRetry(
`https://api.github.com/search/commits?q=author-email:${encodeURIComponent(commitEmail)}&per_page=25`,
`https://api.github.com/search/commits?q=author-email:${encodeURIComponent(email)}&per_page=25`,
);
if (commitSearchResponse?.items?.length > 0) {
// Loop through all items looking for first commit with an author
for (const commit of commitSearchResponse.items) {
if (commit.author) {
console.log(`[${commitEmail}] Found username from commit ${commit.sha}`);
console.log(`[${email}] Found username from commit ${commit.sha}`);
return commit.author.login;
}
}
console.log(`[${commitEmail}] No commits with author found in ${commitSearchResponse.items.length} results`);
console.log(`[${email}] No commits with author found in ${commitSearchResponse.items.length} results`);
}
} catch (error) {
console.error(`[${commitEmail}] Error resolving GitHub username via commit API:`, error);
console.error(`[${email}] Error resolving GitHub username via commit API:`, error);
}

return null;
Expand Down
128 changes: 44 additions & 84 deletions .github/workflows/release-start.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ jobs:
name: release
runs-on: ubuntu-latest
permissions:
contents: write # Required to create a new pull request
pull-requests: write # Required to comment on pull requests
actions: write # Required to trigger workflows
contents: read # Required to read repo contents. Note: We leverage release-preview app for PR + commit generation
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Get all history
fetch-depth: 0 # Get all history which is required for parsing commits

- name: Setup Node.js
id: setup-node
Expand All @@ -41,71 +39,64 @@ jobs:
id: npm-ci
run: npm ci --no-fund

# In order to create signed commits, we need to ensure that we commit without an author name and email.
# However, this can't be done via git as this is required. We need to leverage the GitHub REST/GraphQL
# API endpoints.
# https://github.com/orgs/community/discussions/24664#discussioncomment-5084236
- name: Setup ghup [GitHub API Client]
uses: nexthink-oss/ghup/actions/setup@main
with:
version: v0.11.2

- name: Create new release branch
run: |
# Delete the branch if it exists on remote
if git ls-remote --exit-code --heads origin ${{ env.BRANCH_NAME }}; then
echo "Deleting existing branch ${{ env.BRANCH_NAME }}."
git push origin --delete ${{ env.BRANCH_NAME }}
fi

# Create a new branch and checkout
git checkout -b ${{ env.BRANCH_NAME }}

# Rebase the branch onto main (or whatever the base branch is)
git rebase origin/main

- name: Update package.json version
run: npm version ${{ env.VERSION }} --no-git-tag-version

- name: Build the package
run: npm run package

- name: Commit Changes (via API) using ghup
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GHUP_MESSAGE: "chore(release): bump version to ${{ env.VERSION }}"
run: |
ghup content dist/* package.json package-lock.json \
--trailer "Release-Initiated-By=${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>" \
--trailer "Build-Logs=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--trailer "Co-Authored-By=github-actions[bot] <github-actions[bot]@users.noreply.github.com>" \
--trailer "Release=v${{ env.VERSION }}"

- name: Generate Changelog
uses: actions/github-script@v7
id: changelog
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
with:
result-encoding: json
script: |
const { generateChangelog } = await import('${{ github.workspace }}/.github/scripts/changelog.js');

try {
const changelog = await generateChangelog("${{ env.VERSION }}");
console.log('Generated changelog:', changelog);

return changelog;
} catch (error) {
console.error('Error generating changelog:', error);
core.setFailed(error.message);
}

# Pull requests created by the action using the default GITHUB_TOKEN cannot trigger other workflows.
# If you have on: pull_request or on: push workflows acting as checks on pull requests, they will not run.
#
# See below for additional documentation on workarounds:
#
# https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs

- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.RELEASE_PREVIEW_APP_ID }}
private-key: ${{ secrets.RELEASE_PREVIEW_APP_PRIVATE_KEY }}

- name: Get GitHub App User Details
id: app-user
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
user_name="${{ steps.app-token.outputs.app-slug }}[bot]"
user_id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)
{
echo "user-name=${user_name}"
echo "user-id=${user_id}"
echo "email=${user_id}+${user_name}@users.noreply.github.com"
} >> "$GITHUB_OUTPUT"

# Note: We can't change the head branch once a PR is opened. Thus we need to delete any branches
# that exist from any existing open pull requests.
# that exist from any existing open pull requests. (App Perm = Pull Request: Read + Write)
- name: Close existing release pull requests
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const prTitleRegex = /^chore\(release\): v\d+\.\d+\.\d+$/;

Expand All @@ -122,7 +113,7 @@ jobs:
console.log('Analyzing PR', pr.number, pr.title, pr.user.login);

// Check if the title matches the format and it's created by the correct user
if (prTitleRegex.test(pr.title) && pr.user.login === 'github-actions[bot]') {
if (prTitleRegex.test(pr.title) && pr.user.login === '${{ steps.app-user.outputs.user-name }}') {
console.log(`PR #${pr.number} has a valid title: ${pr.title}`);

// Close the existing pull request
Expand All @@ -145,47 +136,16 @@ jobs:
}
}

# Additional caveat:
# When you use the repository's GITHUB_TOKEN to perform tasks, events triggered by the GITHUB_TOKEN,
# with the exception of workflow_dispatch and repository_dispatch, will not create a new workflow run.
# This prevents you from accidentally creating recursive workflow runs.
#
# There is no way to even trigger this with a repository_dispatch. Therefore, currently the only way
# is to use a separate PAT. In the future we could release a bot to help automate a lot of this.
#
# https://github.com/orgs/community/discussions/65321
- name: Create new pull request
uses: actions/github-script@v7
id: pull-request
- name: Create Branch and Pull Request
uses: peter-evans/create-pull-request@v7
with:
github-token: ${{ secrets.GH_TOKEN_RELEASE_AUTOMATION }}
script: |
const version = '${{ env.VERSION }}';
const prTitle = `chore(release): v${version}`;
const branchName = `release-v${version}`;
const changelog = ${{ steps.changelog.outputs.result }};

const prCreateData = {
owner: context.repo.owner,
repo: context.repo.repo,
title: prTitle,
head: '${{ env.BRANCH_NAME }}',
base: 'main',
body: changelog,
};
console.log('Creating new PR. Context:');
console.dir(prCreateData);

const { data: pr } = await github.rest.pulls.create(prCreateData);
console.log(`Created new PR #${pr.number}`);

// Add labels if they don't exist
console.log('Creating PR labels')
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['release']
});

return pr.number;
token: ${{ steps.app-token.outputs.token }}
base: main
branch: ${{ env.BRANCH_NAME }}
title: "chore(release): v${{ env.VERSION }}"
body: ${{ fromJSON(steps.changelog.outputs.result) }}
commit-message: "chore(release): v${{ env.VERSION }}"
sign-commits: true # Note: When setting sign-commits: true the action will ignore the committer and author inputs.
delete-branch: true
labels: release
signoff: true
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
if: |
startsWith(github.event.pull_request.title, 'chore(release):') &&
contains(github.event.pull_request.body, '<!-- RELEASE-NOTES-MARKER-START -->') &&
github.event.pull_request.user.login == 'github-actions[bot]' &&
github.event.pull_request.user.login == 'release-preview[bot]' &&
github.event.pull_request.merged == true

steps:
Expand Down