Skip to content
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
6 changes: 6 additions & 0 deletions internal/cli/command/release/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ func createReleaseAction(releaseService ports.ReleaseService, trans *i18n.Transl
release.Version = version
}

if err := releaseService.EnrichReleaseContext(ctx, release); err != nil {
fmt.Printf("⚠️ %s\n", trans.GetMessage("release.warning_enrich_context", 0, map[string]interface{}{
"Error": err.Error(),
}))
}

notes, err := releaseService.GenerateReleaseNotes(ctx, release)
if err != nil {
return fmt.Errorf("%s", trans.GetMessage("release.error_generating_notes", 0,
Expand Down
10 changes: 10 additions & 0 deletions internal/cli/command/release/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func TestCreateCommand_Success(t *testing.T) {
}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
mockService.On("CreateTag", mock.Anything, "v1.1.0", "Release v1.1.0\n\nSummary of release").Return(nil)

Expand All @@ -83,6 +84,7 @@ func TestCreateCommand_WithVersionOverride(t *testing.T) {
}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, mock.MatchedBy(func(r *models.Release) bool {
return r.Version == "v2.0.0"
})).Return(notes, nil)
Expand All @@ -100,6 +102,7 @@ func TestCreateCommand_AutoConfirm(t *testing.T) {
notes := &models.ReleaseNotes{Title: "Fix"}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
mockService.On("CreateTag", mock.Anything, "v1.0.1", mock.Anything).Return(nil)

Expand All @@ -113,6 +116,7 @@ func TestCreateCommand_Cancelled(t *testing.T) {
notes := &models.ReleaseNotes{Title: "Fix"}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
err := runCreateTest(t, "no\n", []string{}, mockService)
assert.NoError(t, err)
Expand All @@ -123,6 +127,7 @@ func TestCreateCommand_Cancelled(t *testing.T) {
func TestCreateCommand_AnalyzeError(t *testing.T) {
mockService := new(MockReleaseService)
mockService.On("AnalyzeNextRelease", mock.Anything).Return((*models.Release)(nil), errors.New("git error"))
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)

err := runCreateTest(t, "", []string{}, mockService)
assert.Error(t, err)
Expand All @@ -134,6 +139,7 @@ func TestCreateCommand_GenerateNotesError(t *testing.T) {
mockService := new(MockReleaseService)
release := &models.Release{Version: "v1.0.0"}
mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return((*models.ReleaseNotes)(nil), errors.New("ai error"))

err := runCreateTest(t, "", []string{}, mockService)
Expand All @@ -149,6 +155,7 @@ func TestCreateCommand_CreateTagError(t *testing.T) {
notes := &models.ReleaseNotes{Title: "Title"}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
mockService.On("CreateTag", mock.Anything, "v1.0.0", mock.Anything).Return(errors.New("tag error"))

Expand All @@ -165,6 +172,7 @@ func TestCreateCommand_WithPublish(t *testing.T) {
notes := &models.ReleaseNotes{Title: "Title"}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
mockService.On("CreateTag", mock.Anything, "v1.0.0", mock.Anything).Return(nil)

Expand All @@ -183,6 +191,7 @@ func TestCreateCommand_WithPublishDraft(t *testing.T) {
notes := &models.ReleaseNotes{Title: "Title"}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
mockService.On("CreateTag", mock.Anything, "v1.0.0", mock.Anything).Return(nil)

Expand All @@ -201,6 +210,7 @@ func TestCreateCommand_PublishError(t *testing.T) {
notes := &models.ReleaseNotes{Title: "Title"}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
mockService.On("CreateTag", mock.Anything, "v1.0.0", mock.Anything).Return(nil)

Expand Down
6 changes: 6 additions & 0 deletions internal/cli/command/release/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ func editReleaseAction(releaseService ports.ReleaseService, gitService ports.Git
AllCommits: commits,
}

if err := releaseService.EnrichReleaseContext(ctx, release); err != nil {
fmt.Printf("⚠️ %s\n", trans.GetMessage("release.warning_enrich_context", 0, map[string]interface{}{
"Error": err.Error(),
}))
}

notes, err := releaseService.GenerateReleaseNotes(ctx, release)
if err != nil {
fmt.Printf("⚠️ %s\n", trans.GetMessage("release.error_generating_for_regen", 0, map[string]interface{}{
Expand Down
46 changes: 45 additions & 1 deletion internal/cli/command/release/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package release

import (
"fmt"
"strings"

"github.com/Tomas-vilte/MateCommit/internal/domain/models"
"github.com/Tomas-vilte/MateCommit/internal/i18n"
Expand All @@ -10,6 +11,7 @@ import (
// FormatReleaseMarkdown genera el markdown completo de una release con todas las secciones
func FormatReleaseMarkdown(release *models.Release, notes *models.ReleaseNotes, trans *i18n.Translations) string {
content := fmt.Sprintf("# %s\n\n", notes.Title)
var md strings.Builder

content += "![Version](https://img.shields.io/badge/version-" + release.Version + "-blue)\n"
content += "![Status](https://img.shields.io/badge/status-released-success)\n\n"
Expand Down Expand Up @@ -94,5 +96,47 @@ func FormatReleaseMarkdown(release *models.Release, notes *models.ReleaseNotes,
}
}

return content
if len(release.Contributors) > 0 {
md.WriteString("## ")
md.WriteString(trans.GetMessage("release.md_contributors", 0, nil))
md.WriteString("\n\n")

if len(release.NewContributors) > 0 {
md.WriteString(trans.GetMessage("release.new_contributors", 0, map[string]interface{}{
"Count": len(release.NewContributors),
}))
md.WriteString(" ")
for i, contributor := range release.NewContributors {
md.WriteString(fmt.Sprintf("@%s", contributor))
if i < len(release.NewContributors)-1 {
md.WriteString(", ")
}
}
md.WriteString("\n\n")
}

md.WriteString(trans.GetMessage("release.all_contributors", 0, nil))
md.WriteString("\n")
for _, contributor := range release.Contributors {
md.WriteString(fmt.Sprintf("- @%s\n", contributor))
}
md.WriteString("\n")
}

if release.FileStats.FilesChanged > 0 {
md.WriteString("## ")
md.WriteString(trans.GetMessage("release.md_stats", 0, nil))
md.WriteString("\n\n")
md.WriteString(fmt.Sprintf("- %s: **%d**\n",
trans.GetMessage("release.files_changed", 0, nil),
release.FileStats.FilesChanged))
md.WriteString(fmt.Sprintf("- %s: **+%d**\n",
trans.GetMessage("release.insertions", 0, nil),
release.FileStats.Insertions))
md.WriteString(fmt.Sprintf("- %s: **-%d**\n",
trans.GetMessage("release.deletions", 0, nil),
release.FileStats.Deletions))
md.WriteString("\n")
}
return content + md.String()
}
6 changes: 6 additions & 0 deletions internal/cli/command/release/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ func generateReleaseAction(releaseService ports.ReleaseService, trans *i18n.Tran
}))
}

if err := releaseService.EnrichReleaseContext(ctx, release); err != nil {
fmt.Printf("⚠️ %s\n", trans.GetMessage("release.warning_enrich_context", 0, map[string]interface{}{
"Error": err.Error(),
}))
}

notes, err := releaseService.GenerateReleaseNotes(ctx, release)
if err != nil {
return fmt.Errorf("%s", trans.GetMessage("release.error_generating_notes", 0,
Expand Down
4 changes: 4 additions & 0 deletions internal/cli/command/release/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func TestGenerateCommand_Success(t *testing.T) {
}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)

err := runGenerateTest(t, []string{"--output", outputFile}, mockService)
Expand All @@ -75,6 +76,7 @@ func TestGenerateCommand_Success(t *testing.T) {
func TestGenerateCommand_AnalyzeError(t *testing.T) {
mockService := new(MockReleaseService)
mockService.On("AnalyzeNextRelease", mock.Anything).Return((*models.Release)(nil), errors.New("git error"))
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)

err := runGenerateTest(t, []string{}, mockService)
assert.Error(t, err)
Expand All @@ -87,6 +89,7 @@ func TestGenerateCommand_GenerateError(t *testing.T) {
mockService := new(MockReleaseService)
release := &models.Release{Version: "v1.0.0"}
mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return((*models.ReleaseNotes)(nil), errors.New("ai error"))

err := runGenerateTest(t, []string{}, mockService)
Expand All @@ -102,6 +105,7 @@ func TestGenerateCommand_WriteError(t *testing.T) {
notes := &models.ReleaseNotes{Title: "Title"}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)

invalidPath := "/path/to/non/existent/dir/file.md"
Expand Down
5 changes: 5 additions & 0 deletions internal/cli/command/release/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ func (m *MockReleaseService) UpdateRelease(ctx context.Context, version, body st
return args.Error(0)
}

func (m *MockReleaseService) EnrichReleaseContext(ctx context.Context, release *models.Release) error {
args := m.Called(ctx, release)
return args.Error(0)
}

type MockGitService struct {
mock.Mock
}
Expand Down
6 changes: 6 additions & 0 deletions internal/cli/command/release/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ func previewReleaseAction(releaseService ports.ReleaseService, trans *i18n.Trans
}))
fmt.Println()

if err := releaseService.EnrichReleaseContext(ctx, release); err != nil {
fmt.Printf("⚠️ %s\n", trans.GetMessage("release.warning_enrich_context", 0, map[string]interface{}{
"Error": err.Error(),
}))
}

notes, err := releaseService.GenerateReleaseNotes(ctx, release)
if err != nil {
return fmt.Errorf("%s", trans.GetMessage("release.error_generating_notes", 0,
Expand Down
3 changes: 3 additions & 0 deletions internal/cli/command/release/preview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func TestPreviewCommand_Success(t *testing.T) {
}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)

err := runPreviewTest(t, []string{}, mockService)
Expand All @@ -62,6 +63,7 @@ func TestPreviewCommand_Success(t *testing.T) {
func TestPreviewCommand_AnalyzeError(t *testing.T) {
mockService := new(MockReleaseService)
mockService.On("AnalyzeNextRelease", mock.Anything).Return((*models.Release)(nil), errors.New("git error"))
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)

err := runPreviewTest(t, []string{}, mockService)
assert.Error(t, err)
Expand All @@ -74,6 +76,7 @@ func TestPreviewCommand_GenerateError(t *testing.T) {
mockService := new(MockReleaseService)
release := &models.Release{Version: "v1.0.0"}
mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return((*models.ReleaseNotes)(nil), errors.New("ai error"))

err := runPreviewTest(t, []string{}, mockService)
Expand Down
6 changes: 6 additions & 0 deletions internal/cli/command/release/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ func publishReleaseAction(releaseService ports.ReleaseService, trans *i18n.Trans
release.Version = version
}

if err := releaseService.EnrichReleaseContext(ctx, release); err != nil {
fmt.Printf("⚠️ %s\n", trans.GetMessage("release.warning_enrich_context", 0, map[string]interface{}{
"Error": err.Error(),
}))
}

notes, err := releaseService.GenerateReleaseNotes(ctx, release)
if err != nil {
return fmt.Errorf("%s", trans.GetMessage("release.error_generating_notes", 0, map[string]interface{}{
Expand Down
5 changes: 5 additions & 0 deletions internal/cli/command/release/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func TestPublishCommand_Success(t *testing.T) {
}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
mockService.On("PublishRelease", mock.Anything, release, notes, false).Return(nil)

Expand All @@ -77,6 +78,7 @@ func TestPublishCommand_WithDraftFlag(t *testing.T) {
}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
mockService.On("PublishRelease", mock.Anything, release, notes, true).Return(nil)

Expand All @@ -101,6 +103,7 @@ func TestPublishCommand_WithVersionOverride(t *testing.T) {
}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, mock.MatchedBy(func(r *models.Release) bool {
return r.Version == "v2.0.0"
})).Return(notes, nil)
Expand Down Expand Up @@ -136,6 +139,7 @@ func TestPublishCommand_GenerateNotesError(t *testing.T) {
}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return((*models.ReleaseNotes)(nil), errors.New("generate failed"))

err := runPublishTest(t, []string{}, mockService)
Expand All @@ -160,6 +164,7 @@ func TestPublishCommand_PublishError(t *testing.T) {
}

mockService.On("AnalyzeNextRelease", mock.Anything).Return(release, nil)
mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil)
mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil)
mockService.On("PublishRelease", mock.Anything, release, notes, false).Return(errors.New("publish failed"))

Expand Down
64 changes: 64 additions & 0 deletions internal/domain/models/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,57 @@ type (
Other []ReleaseItem
AllCommits []Commit
VersionBump VersionBump
ClosedIssues []Issue
MergedPRs []PullRequest
Contributors []string
NewContributors []string
Dependencies []DependencyChange
FileStats FileStatistics
}

Issue struct {
Number int
Title string
Labels []string
Author string
URL string
}

PullRequest struct {
Number int
Title string
Description string
Author string
Labels []string
URL string
}

// DependencyChange representa un cambio en una dependencia del proyecto
DependencyChange struct {
Name string // Nombre del paquete (ej: "github.com/user/repo", "react")
OldVersion string // Versión anterior (vacío si Type == DependencyAdded)
NewVersion string // Versión nueva (vacío si Type == DependencyRemoved)
Type DependencyChangeType // Tipo de cambio
Manager string // Gestor: "go.mod", "package.json", "Cargo.toml", etc.
Severity ChangeSeverity // Severidad: major/minor/patch para detectar breaking changes
IsDirect bool // true = dependencia directa, false = dev/indirect
}

DependencyChangeType string

ChangeSeverity string

FileStatistics struct {
FilesChanged int
Insertions int
Deletions int
TopFiles []FileChange
}

FileChange struct {
Path string
Additions int
Deletions int
}

// ReleaseItem representa un item en el changelog
Expand Down Expand Up @@ -77,3 +128,16 @@ const (
PatchBump VersionBump = "patch"
NoBump VersionBump = "none"
)

const (
DependencyAdded DependencyChangeType = "added"
DependencyUpdated DependencyChangeType = "updated"
DependencyRemoved DependencyChangeType = "removed"
)

const (
MajorChange ChangeSeverity = "major"
MinorChange ChangeSeverity = "minor"
PatchChange ChangeSeverity = "patch"
UnknownChange ChangeSeverity = "unknown"
)
Loading
Loading