- Add GET /api/changelog endpoint to fetch changelog from CHANGELOG.md - Create service/changelog.go to parse CHANGELOG.md markdown file - Add handler/changelog.go to handle changelog requests - Update ChangelogDrawer component to fetch from API instead of hardcoded data - Export apiFetch from lib/api.ts for public use - Add changelog parser tests with 14 version entries verified
126 lines
3.0 KiB
Go
126 lines
3.0 KiB
Go
package service
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ChangelogEntry 变更日志条目
|
|
type ChangelogEntry struct {
|
|
Version string `json:"version"`
|
|
Date string `json:"date"`
|
|
Changes map[string][]string `json:"changes"`
|
|
}
|
|
|
|
// ParseChangelog 解析 CHANGELOG.md 文件
|
|
func ParseChangelog() ([]ChangelogEntry, error) {
|
|
// 获取项目根目录
|
|
rootDir := os.Getenv("PROJECT_ROOT")
|
|
if rootDir == "" {
|
|
// 如果未设置,使用相对路径推测
|
|
rootDir = ".."
|
|
}
|
|
|
|
changelogPath := filepath.Join(rootDir, "CHANGELOG.md")
|
|
|
|
file, err := os.Open(changelogPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("打开 CHANGELOG.md 失败: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
var entries []ChangelogEntry
|
|
var currentEntry *ChangelogEntry
|
|
var currentCategory string
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
// 匹配版本号行 ## [1.4.0] - 2026-03-23
|
|
if strings.HasPrefix(line, "## [") && strings.Contains(line, "]") {
|
|
// 保存前一个 entry
|
|
if currentEntry != nil {
|
|
entries = append(entries, *currentEntry)
|
|
}
|
|
|
|
// 解析版本号和日期
|
|
version, date := parseVersionLine(line)
|
|
if version != "" && version != "Unreleased" {
|
|
currentEntry = &ChangelogEntry{
|
|
Version: version,
|
|
Date: date,
|
|
Changes: make(map[string][]string),
|
|
}
|
|
currentCategory = ""
|
|
} else {
|
|
currentEntry = nil
|
|
}
|
|
continue
|
|
}
|
|
|
|
// 跳过 Unreleased 和其他非版本行
|
|
if currentEntry == nil {
|
|
continue
|
|
}
|
|
|
|
// 匹配分类行 ### 新增、### 优化等
|
|
if strings.HasPrefix(line, "### ") {
|
|
currentCategory = strings.TrimPrefix(line, "### ")
|
|
if currentEntry.Changes[currentCategory] == nil {
|
|
currentEntry.Changes[currentCategory] = []string{}
|
|
}
|
|
continue
|
|
}
|
|
|
|
// 匹配项目行 - 项目描述
|
|
if strings.HasPrefix(line, "- ") && currentCategory != "" {
|
|
item := strings.TrimPrefix(line, "- ")
|
|
// 移除加粗标记和链接等
|
|
item = cleanItem(item)
|
|
currentEntry.Changes[currentCategory] = append(currentEntry.Changes[currentCategory], item)
|
|
}
|
|
}
|
|
|
|
// 保存最后一个 entry
|
|
if currentEntry != nil {
|
|
entries = append(entries, *currentEntry)
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("扫描文件失败: %w", err)
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
// parseVersionLine 解析版本行 ## [1.4.0] - 2026-03-23
|
|
func parseVersionLine(line string) (version, date string) {
|
|
// 提取版本号
|
|
startIdx := strings.Index(line, "[")
|
|
endIdx := strings.Index(line, "]")
|
|
if startIdx >= 0 && endIdx > startIdx {
|
|
version = line[startIdx+1 : endIdx]
|
|
}
|
|
|
|
// 提取日期
|
|
dateStartIdx := strings.LastIndex(line, "- ") + 2
|
|
if dateStartIdx > 1 {
|
|
date = strings.TrimSpace(line[dateStartIdx:])
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// cleanItem 清理项目描述(移除加粗标记等)
|
|
func cleanItem(item string) string {
|
|
// 移除加粗标记 **text**
|
|
item = strings.ReplaceAll(item, "**", "")
|
|
// 移除代码标记 `text`
|
|
item = strings.ReplaceAll(item, "`", "")
|
|
return strings.TrimSpace(item)
|
|
}
|