summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-07-09 00:37:58 +0300
committerPaul Buetow <paul@buetow.org>2025-07-09 00:37:58 +0300
commit48b54ced8735b2e92daa836e918435e0a18fe82b (patch)
treed5fdda66eab333ef80280b2d0fec3ed8ed33e56f /internal
parent403183718b1adbc244fe5e23c15b5dcc120888d6 (diff)
feat: add latest release version and date to project summaries
- Display latest release tag and date for each project - Filter tags to only show version-like tags (e.g., v1.0, 2.3, 1.0.0) - Projects with releases show "🏷️ Latest Release: v1.0.0 (2024-01-15)" - Projects without releases show "🧪 Status: Experimental (no releases yet)" - Added LatestTagDate field to metadata structure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/showcase/metadata.go81
-rw-r--r--internal/showcase/showcase.go10
2 files changed, 82 insertions, 9 deletions
diff --git a/internal/showcase/metadata.go b/internal/showcase/metadata.go
index 9627910..ca8af05 100644
--- a/internal/showcase/metadata.go
+++ b/internal/showcase/metadata.go
@@ -29,6 +29,7 @@ type RepoMetadata struct {
License string
AvgCommitAge float64 // Average age of last 42 commits in days
LatestTag string // Latest version tag (empty if no tags)
+ LatestTagDate string // Date of the latest tag (empty if no tags)
HasReleases bool // Whether the project has any releases/tags
}
@@ -89,11 +90,12 @@ func extractRepoMetadata(repoPath string) (*RepoMetadata, error) {
metadata.AvgCommitAge = avgAge
// Get latest tag and check for releases
- latestTag, hasReleases, err := getLatestTag(repoPath)
+ latestTag, latestTagDate, hasReleases, err := getLatestTag(repoPath)
if err != nil {
fmt.Printf("Warning: Failed to get latest tag: %v\n", err)
}
metadata.LatestTag = latestTag
+ metadata.LatestTagDate = latestTagDate
metadata.HasReleases = hasReleases
return metadata, nil
@@ -280,8 +282,8 @@ func getAverageCommitAge(repoPath string, commitCount int) (float64, error) {
return totalAge / float64(validCommits), nil
}
-// getLatestTag returns the latest git tag and whether the repo has any releases
-func getLatestTag(repoPath string) (string, bool, error) {
+// getLatestTag returns the latest git tag, its date, and whether the repo has any releases
+func getLatestTag(repoPath string) (string, string, bool, error) {
// First try to get tags sorted by version
cmd := exec.Command("git", "-C", repoPath, "tag", "-l", "--sort=-version:refname")
output, err := cmd.Output()
@@ -291,15 +293,80 @@ func getLatestTag(repoPath string) (string, bool, error) {
output, err = cmd.Output()
if err != nil {
// No tags at all
- return "", false, nil
+ return "", "", false, nil
}
}
tags := strings.Split(strings.TrimSpace(string(output)), "\n")
if len(tags) == 0 || tags[0] == "" {
- return "", false, nil
+ return "", "", false, nil
}
- // Return the latest tag
- return tags[0], true, nil
+ // Find the first tag that looks like a version number
+ latestTag := ""
+ for _, tag := range tags {
+ if isVersionTag(tag) {
+ latestTag = tag
+ break
+ }
+ }
+
+ if latestTag == "" {
+ // No version-like tags found
+ return "", "", false, nil
+ }
+
+ // Get the date of the latest tag
+ cmd = exec.Command("git", "-C", repoPath, "log", "-1", "--format=%ai", latestTag)
+ dateOutput, err := cmd.Output()
+ if err != nil {
+ // Tag exists but couldn't get date
+ return latestTag, "", true, nil
+ }
+
+ // Extract just the date part (YYYY-MM-DD)
+ parts := strings.Fields(string(dateOutput))
+ tagDate := ""
+ if len(parts) > 0 {
+ tagDate = parts[0]
+ }
+
+ // Return the latest tag and its date
+ return latestTag, tagDate, true, nil
+}
+
+// isVersionTag checks if a tag looks like a version number
+func isVersionTag(tag string) bool {
+ // Remove 'v' prefix if present
+ versionStr := strings.TrimPrefix(tag, "v")
+
+ // Check if the remaining string contains at least one digit and one dot
+ hasDigit := false
+ hasDot := false
+
+ for _, ch := range versionStr {
+ if ch >= '0' && ch <= '9' {
+ hasDigit = true
+ } else if ch == '.' {
+ hasDot = true
+ } else if ch != '-' && ch != '+' && ch != '_' &&
+ (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') {
+ // Allow alphanumeric characters and common separators
+ // but anything else makes it not a version
+ return false
+ }
+ }
+
+ // Must have at least one digit, and either:
+ // - have a dot (e.g., 1.0, 0.1.2)
+ // - be just digits (e.g., 2, 2024)
+ // - start with a digit (e.g., 1-beta)
+ if hasDigit && len(versionStr) > 0 {
+ firstChar := versionStr[0]
+ if firstChar >= '0' && firstChar <= '9' {
+ return true
+ }
+ }
+
+ return hasDigit && hasDot
} \ No newline at end of file
diff --git a/internal/showcase/showcase.go b/internal/showcase/showcase.go
index d3c6b2c..9dc46dc 100644
--- a/internal/showcase/showcase.go
+++ b/internal/showcase/showcase.go
@@ -446,8 +446,14 @@ func (g *Generator) formatGemtext(summaries []ProjectSummary) string {
builder.WriteString(fmt.Sprintf("* 🔥 Recent Activity: %.1f days (avg. age of last 42 commits)\n", summary.Metadata.AvgCommitAge))
builder.WriteString(fmt.Sprintf("* ⚖️ License: %s\n", summary.Metadata.License))
- // Add experimental status if no releases
- if !summary.Metadata.HasReleases {
+ // Add release information or experimental status
+ if summary.Metadata.HasReleases && summary.Metadata.LatestTag != "" {
+ if summary.Metadata.LatestTagDate != "" {
+ builder.WriteString(fmt.Sprintf("* 🏷️ Latest Release: %s (%s)\n", summary.Metadata.LatestTag, summary.Metadata.LatestTagDate))
+ } else {
+ builder.WriteString(fmt.Sprintf("* 🏷️ Latest Release: %s\n", summary.Metadata.LatestTag))
+ }
+ } else {
builder.WriteString("* 🧪 Status: Experimental (no releases yet)\n")
}