-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
build: create changelog tool and workflows.
- Loading branch information
Showing
3 changed files
with
276 additions
and
308 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
name: Release Changelog | ||
|
||
on: | ||
release: | ||
types: [released] | ||
|
||
permissions: | ||
contents: write | ||
pull-requests: write | ||
|
||
jobs: | ||
update-changelog: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v4 | ||
|
||
- name: Run Go Changelog Generator | ||
run: | | ||
# Run the Go changelog generator, passing the release tag if available | ||
if [ "${{ github.event.release.tag_name }}" = "latest" ]; then | ||
go run tools/changelog/changelog.go > "${{ github.event.release.tag_name }}-changelog.md" | ||
else | ||
go run tools/changelog/changelog.go "${{ github.event.release.tag_name }}" > "${{ github.event.release.tag_name }}-changelog.md" | ||
fi | ||
- name: Handle changelog files | ||
run: | | ||
# Ensure that the CHANGELOG directory exists | ||
mkdir -p CHANGELOG | ||
# Extract Major.Minor version by removing the 'v' prefix from the tag name | ||
TAG_NAME=${{ github.event.release.tag_name }} | ||
CHANGELOG_VERSION_NUMBER=$(echo "$TAG_NAME" | sed 's/^v//' | grep -oP '^\d+\.\d+') | ||
# Define the new changelog file path | ||
CHANGELOG_FILENAME="CHANGELOG-$CHANGELOG_VERSION_NUMBER.md" | ||
CHANGELOG_PATH="CHANGELOG/$CHANGELOG_FILENAME" | ||
# Check if the changelog file for the current release already exists | ||
if [ -f "$CHANGELOG_PATH" ]; then | ||
# If the file exists, append the new changelog to the existing one | ||
cat "$CHANGELOG_PATH" >> "${TAG_NAME}-changelog.md" | ||
# Overwrite the existing changelog with the updated content | ||
mv "${TAG_NAME}-changelog.md" "$CHANGELOG_PATH" | ||
else | ||
# If the changelog file doesn't exist, rename the temp changelog file to the new changelog file | ||
mv "${TAG_NAME}-changelog.md" "$CHANGELOG_PATH" | ||
# Ensure that README.md exists | ||
if [ ! -f "CHANGELOG/README.md" ]; then | ||
echo -e "# CHANGELOGs\n\n" > CHANGELOG/README.md | ||
fi | ||
# Add the new changelog entry at the top of the README.md | ||
if ! grep -q "\[$CHANGELOG_FILENAME\]" CHANGELOG/README.md; then | ||
sed -i "3i- [$CHANGELOG_FILENAME](./$CHANGELOG_FILENAME)" CHANGELOG/README.md | ||
# Remove the extra newline character added by sed | ||
# sed -i '4d' CHANGELOG/README.md | ||
fi | ||
fi | ||
- name: Clean up | ||
run: | | ||
# Remove any temporary files that were created during the process | ||
rm -f "${{ github.event.release.tag_name }}-changelog.md" | ||
- name: Create Pull Request | ||
uses: peter-evans/[email protected] | ||
with: | ||
token: ${{ secrets.GITHUB_TOKEN }} | ||
commit-message: "Update CHANGELOG for release ${{ github.event.release.tag_name }}" | ||
title: "Update CHANGELOG for release ${{ github.event.release.tag_name }}" | ||
body: "This PR updates the CHANGELOG files for release ${{ github.event.release.tag_name }}" | ||
branch: changelog-${{ github.event.release.tag_name }} | ||
base: main | ||
delete-branch: true | ||
labels: changelog |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
// You can specify a tag as a command line argument to generate the changelog for a specific version. | ||
// Example: go run tools/changelog/changelog.go v0.0.33 | ||
// If no tag is provided, the latest release will be used. | ||
|
||
// Setting repo owner and repo name by generate changelog | ||
const ( | ||
repoOwner = "openimsdk" | ||
repoName = "open-im-server" | ||
) | ||
|
||
// GitHubRepo struct represents the repo details. | ||
type GitHubRepo struct { | ||
Owner string | ||
Repo string | ||
FullChangelog string | ||
} | ||
|
||
// ReleaseData represents the JSON structure for release data. | ||
type ReleaseData struct { | ||
TagName string `json:"tag_name"` | ||
Body string `json:"body"` | ||
HtmlUrl string `json:"html_url"` | ||
Published string `json:"published_at"` | ||
} | ||
|
||
// Method to classify and format release notes. | ||
func (g *GitHubRepo) classifyReleaseNotes(body string) map[string][]string { | ||
result := map[string][]string{ | ||
"feat": {}, | ||
"fix": {}, | ||
"chore": {}, | ||
"refactor": {}, | ||
"build": {}, | ||
"other": {}, | ||
} | ||
|
||
// Regular expression to extract PR number and URL (case insensitive) | ||
rePR := regexp.MustCompile(`(?i)in (https://github\.com/[^\s]+/pull/(\d+))`) | ||
|
||
// Split the body into individual lines. | ||
lines := strings.Split(body, "\n") | ||
|
||
for _, line := range lines { | ||
// Skip lines that contain "deps: Merge" | ||
if strings.Contains(strings.ToLower(line), "deps: merge #") { | ||
continue | ||
} | ||
|
||
// Use a regular expression to extract Full Changelog link and its title (case insensitive). | ||
if strings.Contains(strings.ToLower(line), "**full changelog**") { | ||
matches := regexp.MustCompile(`(?i)\*\*full changelog\*\*: (https://github\.com/[^\s]+/compare/([^\s]+))`).FindStringSubmatch(line) | ||
if len(matches) > 2 { | ||
// Format the Full Changelog link with title | ||
g.FullChangelog = fmt.Sprintf("[%s](%s)", matches[2], matches[1]) | ||
} | ||
continue // Skip further processing for this line. | ||
} | ||
|
||
if strings.HasPrefix(line, "*") { | ||
var category string | ||
|
||
// Use strings.ToLower to make the matching case insensitive | ||
lowerLine := strings.ToLower(line) | ||
|
||
// Determine the category based on the prefix (case insensitive). | ||
if strings.HasPrefix(lowerLine, "* feat") { | ||
category = "feat" | ||
} else if strings.HasPrefix(lowerLine, "* fix") { | ||
category = "fix" | ||
} else if strings.HasPrefix(lowerLine, "* chore") { | ||
category = "chore" | ||
} else if strings.HasPrefix(lowerLine, "* refactor") { | ||
category = "refactor" | ||
} else if strings.HasPrefix(lowerLine, "* build") { | ||
category = "build" | ||
} else { | ||
category = "other" | ||
} | ||
|
||
// Extract PR number and URL (case insensitive) | ||
matches := rePR.FindStringSubmatch(line) | ||
if len(matches) == 3 { | ||
prURL := matches[1] | ||
prNumber := matches[2] | ||
// Format the line with the PR link and use original content for the final result | ||
formattedLine := fmt.Sprintf("* %s [#%s](%s)", strings.Split(line, " by ")[0][2:], prNumber, prURL) | ||
result[category] = append(result[category], formattedLine) | ||
} else { | ||
// If no PR link is found, just add the line as is | ||
result[category] = append(result[category], line) | ||
} | ||
} | ||
} | ||
|
||
return result | ||
} | ||
|
||
// Method to generate the final changelog. | ||
func (g *GitHubRepo) generateChangelog(tag, date, htmlURL, body string) string { | ||
sections := g.classifyReleaseNotes(body) | ||
|
||
// Convert ISO 8601 date to simpler format (YYYY-MM-DD) | ||
formattedDate := date[:10] | ||
|
||
// Changelog header with tag, date, and links. | ||
changelog := fmt.Sprintf("## [%s](%s) \t(%s)\n\n", tag, htmlURL, formattedDate) | ||
|
||
if len(sections["feat"]) > 0 { | ||
changelog += "### New Features\n" + strings.Join(sections["feat"], "\n") + "\n\n" | ||
} | ||
if len(sections["fix"]) > 0 { | ||
changelog += "### Bug Fixes\n" + strings.Join(sections["fix"], "\n") + "\n\n" | ||
} | ||
if len(sections["chore"]) > 0 { | ||
changelog += "### Chores\n" + strings.Join(sections["chore"], "\n") + "\n\n" | ||
} | ||
if len(sections["refactor"]) > 0 { | ||
changelog += "### Refactors\n" + strings.Join(sections["refactor"], "\n") + "\n\n" | ||
} | ||
if len(sections["build"]) > 0 { | ||
changelog += "### Builds\n" + strings.Join(sections["build"], "\n") + "\n\n" | ||
} | ||
if len(sections["other"]) > 0 { | ||
changelog += "### Others\n" + strings.Join(sections["other"], "\n") + "\n\n" | ||
} | ||
|
||
if g.FullChangelog != "" { | ||
changelog += fmt.Sprintf("**Full Changelog**: %s\n", g.FullChangelog) | ||
} | ||
|
||
return changelog | ||
} | ||
|
||
// Method to fetch release data from GitHub API. | ||
func (g *GitHubRepo) fetchReleaseData(version string) (*ReleaseData, error) { | ||
var apiURL string | ||
|
||
if version == "" { | ||
// Fetch the latest release. | ||
apiURL = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", g.Owner, g.Repo) | ||
} else { | ||
// Fetch a specific version. | ||
apiURL = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", g.Owner, g.Repo, version) | ||
} | ||
|
||
resp, err := http.Get(apiURL) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
|
||
body, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var releaseData ReleaseData | ||
err = json.Unmarshal(body, &releaseData) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &releaseData, nil | ||
} | ||
|
||
func main() { | ||
repo := &GitHubRepo{Owner: repoOwner, Repo: repoName} | ||
|
||
// Get the version from command line arguments, if provided | ||
var version string // Default is use latest | ||
|
||
if len(os.Args) > 1 { | ||
version = os.Args[1] // Use the provided version | ||
} | ||
|
||
// Fetch release data (either for latest or specific version) | ||
releaseData, err := repo.fetchReleaseData(version) | ||
if err != nil { | ||
fmt.Println("Error fetching release data:", err) | ||
return | ||
} | ||
|
||
// Generate and print the formatted changelog | ||
changelog := repo.generateChangelog(releaseData.TagName, releaseData.Published, releaseData.HtmlUrl, releaseData.Body) | ||
fmt.Println(changelog) | ||
} |
Oops, something went wrong.