You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
5.7 KiB
199 lines
5.7 KiB
1 week ago
|
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)
|
||
|
}
|