< Summary - go-semantic-release Coverage

Line coverage
96%
Covered lines: 216
Uncovered lines: 9
Coverable lines: 225
Total lines: 808
Line coverage: 96%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/branch_policy.go

#LineLine coverage
 1package domain
 2
 3import (
 4  "fmt"
 5  "path/filepath"
 6  "strconv"
 7  "strings"
 8)
 9
 10// BranchType categorizes how a branch participates in the release process.
 11type BranchType string
 12
 13const (
 14  BranchTypeRelease     BranchType = "release"
 15  BranchTypeMaintenance BranchType = "maintenance"
 16  BranchTypePrerelease  BranchType = "prerelease"
 17)
 18
 19// BranchPolicy defines version behavior for a specific branch or branch pattern.
 20type BranchPolicy struct {
 21  Name       string     `mapstructure:"name"`
 22  Channel    string     `mapstructure:"channel"`
 23  Prerelease bool       `mapstructure:"prerelease"`
 24  IsDefault  bool       `mapstructure:"is_default"`
 25  Range      string     `mapstructure:"range"` // maintenance range e.g. "1.x", "1.0.x"
 26  Type       BranchType `mapstructure:"branch_type"`
 27}
 28
 29// IsMaintenance returns true if this is a maintenance branch with a version range.
 30func (bp BranchPolicy) IsMaintenance() bool {
 31  return bp.Range != "" || bp.Type == BranchTypeMaintenance
 32}
 33
 34// IsPrerelease returns true if this is a prerelease branch.
 35func (bp BranchPolicy) IsPrerelease() bool {
 36  return bp.Prerelease || bp.Type == BranchTypePrerelease
 37}
 38
 39// MaintenanceRange parses the Range field into min/max version constraints.
 40// Range format: "N.x" (major only) or "N.N.x" (major.minor).
 41func (bp BranchPolicy) MaintenanceRange() (minVer, maxVer Version, err error) {
 42  r := bp.Range
 43  if r == "" {
 44    // Try to infer from branch name (e.g. "1.x", "1.0.x").
 45    r = bp.Name
 46  }
 47
 48  return parseMaintenanceRange(r)
 49}
 50
 1151func parseMaintenanceRange(r string) (minVer, maxVer Version, err error) {
 1152  parts := strings.Split(r, ".")
 253  if len(parts) < 2 || parts[len(parts)-1] != "x" {
 254    return minVer, maxVer, fmt.Errorf("invalid maintenance range %q: expected N.x or N.N.x", r)
 255  }
 56
 957  major, err := strconv.Atoi(parts[0])
 058  if err != nil {
 059    return minVer, maxVer, fmt.Errorf("invalid major in range %q: %w", r, err)
 060  }
 61
 462  if len(parts) == 2 {
 463    // "N.x" — allows any minor/patch within this major.
 464    return NewVersion(major, 0, 0), NewVersion(major+1, 0, 0), nil
 465  }
 66
 467  if len(parts) == 3 {
 468    minor, err := strconv.Atoi(parts[1])
 169    if err != nil {
 170      return minVer, maxVer, fmt.Errorf("invalid minor in range %q: %w", r, err)
 171    }
 72    // "N.N.x" — allows any patch within this major.minor.
 373    return NewVersion(major, minor, 0), NewVersion(major, minor+1, 0), nil
 74  }
 75
 176  return minVer, maxVer, fmt.Errorf("invalid maintenance range %q", r)
 77}
 78
 79// VersionInRange checks if a version falls within the maintenance branch range.
 80// minVer <= version < maxVer.
 981func VersionInRange(version, minVer, maxVer Version) bool {
 182  if version.Equal(minVer) {
 183    return true
 184  }
 885  return (version.GreaterThan(minVer) || version.Equal(minVer)) && maxVer.GreaterThan(version)
 86}
 87
 88// ValidateMaintenanceVersion checks that a proposed version is valid for the maintenance range.
 689func ValidateMaintenanceVersion(proposed Version, policy BranchPolicy) error {
 190  if !policy.IsMaintenance() {
 191    return nil
 192  }
 93
 594  minVer, maxVer, err := policy.MaintenanceRange()
 195  if err != nil {
 196    return err
 197  }
 98
 299  if !VersionInRange(proposed, minVer, maxVer) {
 2100    return fmt.Errorf(
 2101      "version %s is outside maintenance range [%s, %s) for branch %q",
 2102      proposed, minVer, maxVer, policy.Name,
 2103    )
 2104  }
 2105  return nil
 106}
 107
 108// DefaultBranchPolicies returns the standard branch configuration matching semantic-release defaults.
 2109func DefaultBranchPolicies() []BranchPolicy {
 2110  return []BranchPolicy{
 2111    {Name: "main", IsDefault: true, Type: BranchTypeRelease},
 2112    {Name: "master", IsDefault: true, Type: BranchTypeRelease},
 2113    {Name: "next", Prerelease: true, Channel: "next", Type: BranchTypePrerelease},
 2114    {Name: "next-major", Prerelease: true, Channel: "next-major", Type: BranchTypePrerelease},
 2115    {Name: "beta", Prerelease: true, Channel: "beta", Type: BranchTypePrerelease},
 2116    {Name: "alpha", Prerelease: true, Channel: "alpha", Type: BranchTypePrerelease},
 2117  }
 2118}
 119
 120// FindBranchPolicy returns the matching policy for a branch name, or nil if none matches.
 121// Supports glob patterns in policy names.
 14122func FindBranchPolicy(policies []BranchPolicy, branch string) *BranchPolicy {
 14123  for i := range policies {
 6124    if policies[i].Name == branch {
 6125      return &policies[i]
 6126    }
 127    // Try glob match for patterns like "release/*".
 2128    if matched, _ := filepath.Match(policies[i].Name, branch); matched {
 2129      return &policies[i]
 2130    }
 131  }
 132
 133  // Auto-detect maintenance branches by name pattern (e.g. "1.x", "1.0.x").
 3134  if isMaintenancePattern(branch) {
 3135    return &BranchPolicy{
 3136      Name:    branch,
 3137      Range:   branch,
 3138      Channel: "release-" + branch,
 3139      Type:    BranchTypeMaintenance,
 3140    }
 3141  }
 142
 3143  return nil
 144}
 145
 6146func isMaintenancePattern(name string) bool {
 6147  parts := strings.Split(name, ".")
 3148  if len(parts) < 2 || parts[len(parts)-1] != "x" {
 3149    return false
 3150  }
 3151  _, err := strconv.Atoi(parts[0])
 3152  return err == nil
 153}

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/changelog.go

#LineLine coverage
 1package domain
 2
 3// Changelog represents generated release notes for a version.
 4type Changelog struct {
 5  Title    string
 6  Version  Version
 7  Project  string // empty for repo-level
 8  Sections []ChangelogSection
 9}
 10
 11// ChangelogSection groups commits by type.
 12type ChangelogSection struct {
 13  Title   string // e.g. "Features", "Bug Fixes", "Breaking Changes"
 14  Type    string // commit type key
 15  Commits []Commit
 16}
 17
 18// DefaultChangelogSections defines the standard section ordering and titles.
 519func DefaultChangelogSections() []ChangelogSectionConfig {
 520  return []ChangelogSectionConfig{
 521    {Type: "breaking", Title: "Breaking Changes", Hidden: false},
 522    {Type: "feat", Title: "Features", Hidden: false},
 523    {Type: "fix", Title: "Bug Fixes", Hidden: false},
 524    {Type: "perf", Title: "Performance Improvements", Hidden: false},
 525    {Type: "revert", Title: "Reverts", Hidden: false},
 526    {Type: "refactor", Title: "Code Refactoring", Hidden: true},
 527    {Type: "docs", Title: "Documentation", Hidden: true},
 528    {Type: "style", Title: "Styles", Hidden: true},
 529    {Type: "test", Title: "Tests", Hidden: true},
 530    {Type: "build", Title: "Build System", Hidden: true},
 531    {Type: "ci", Title: "Continuous Integration", Hidden: true},
 532    {Type: "chore", Title: "Chores", Hidden: true},
 533  }
 534}
 35
 36// ChangelogSectionConfig controls which sections appear in changelogs.
 37type ChangelogSectionConfig struct {
 38  Type   string
 39  Title  string
 40  Hidden bool // if true, section is omitted from output unless explicitly requested
 41}

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/commit.go

#LineLine coverage
 1package domain
 2
 3import "time"
 4
 5// Commit represents a parsed git commit with conventional commit metadata.
 6type Commit struct {
 7  Hash             string
 8  Message          string
 9  Author           string
 10  AuthorEmail      string
 11  Date             time.Time
 12  Type             string // e.g. "feat", "fix", "chore"
 13  Scope            string
 14  Description      string
 15  Body             string
 16  Footer           string
 17  IsBreakingChange bool
 18  BreakingNote     string
 19  FilesChanged     []string
 20}
 21
 22// ReleaseType returns the release type implied by this commit.
 23func (c Commit) ReleaseType(typeMapping map[string]ReleaseType) ReleaseType {
 24  if c.IsBreakingChange {
 25    return ReleaseMajor
 26  }
 27  if rt, ok := typeMapping[c.Type]; ok {
 28    return rt
 29  }
 30  return ReleaseNone
 31}
 32
 33// DefaultCommitTypeMapping returns the standard mapping from conventional commit types to release types.
 234func DefaultCommitTypeMapping() map[string]ReleaseType {
 235  return map[string]ReleaseType{
 236    "feat":     ReleaseMinor,
 237    "fix":      ReleasePatch,
 238    "perf":     ReleasePatch,
 239    "revert":   ReleasePatch,
 240    "refactor": ReleaseNone,
 241    "docs":     ReleaseNone,
 242    "style":    ReleaseNone,
 243    "test":     ReleaseNone,
 244    "build":    ReleaseNone,
 245    "ci":       ReleaseNone,
 246    "chore":    ReleaseNone,
 247  }
 248}

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/config.go

#LineLine coverage
 1package domain
 2
 3import (
 4  "slices"
 5  "strings"
 6)
 7
 8// Config holds all configuration for the release process.
 9type Config struct {
 10  // Core settings.
 11  ReleaseMode      ReleaseMode `mapstructure:"release_mode"`
 12  TagFormat        string      `mapstructure:"tag_format"`
 13  ProjectTagFormat string      `mapstructure:"project_tag_format"`
 14  DryRun           bool        `mapstructure:"dry_run"`
 15  CI               bool        `mapstructure:"ci"`
 16  Debug            bool        `mapstructure:"debug"`
 17  RepositoryURL    string      `mapstructure:"repository_url"`
 18
 19  // Branch and version policies.
 20  Branches    []BranchPolicy         `mapstructure:"branches"`
 21  CommitTypes map[string]ReleaseType `mapstructure:"commit_types"`
 22
 23  // Project/monorepo settings.
 24  Projects              []ProjectConfig `mapstructure:"projects"`
 25  DiscoverModules       bool            `mapstructure:"discover_modules"`
 26  DiscoverCmd           bool            `mapstructure:"discover_cmd"`
 27  IncludePaths          []string        `mapstructure:"include_paths"`
 28  ExcludePaths          []string        `mapstructure:"exclude_paths"`
 29  DependencyPropagation bool            `mapstructure:"dependency_propagation"`
 30
 31  // Changelog settings.
 32  ChangelogSections []ChangelogSectionConfig `mapstructure:"changelog_sections"`
 33  ChangelogTemplate string                   `mapstructure:"changelog_template"`
 34
 35  // Prepare step settings.
 36  Prepare PrepareConfig `mapstructure:"prepare"`
 37
 38  // Git settings for pre-tag commits (staging assets, commit message).
 39  Git GitConfig `mapstructure:"git"`
 40
 41  // Git identity for automated commits.
 42  GitAuthor    GitIdentity `mapstructure:"git_author"`
 43  GitCommitter GitIdentity `mapstructure:"git_committer"`
 44
 45  // GitHub integration.
 46  GitHub GitHubConfig `mapstructure:"github"`
 47
 48  // GitLab integration.
 49  GitLab GitLabConfig `mapstructure:"gitlab"`
 50
 51  // Bitbucket integration.
 52  Bitbucket BitbucketConfig `mapstructure:"bitbucket"`
 53
 54  // Commit linting.
 55  Lint LintConfig `mapstructure:"lint"`
 56
 57  // Interactive mode. nil means unset (treated as false by IsInteractive).
 58  Interactive *bool `mapstructure:"interactive"`
 59
 60  // Git backend: "cli" (default) or "go-git".
 61  GitBackend string `mapstructure:"git_backend"`
 62
 63  // Plugin references for external plugin loading.
 64  Plugins []string `mapstructure:"plugins"`
 65
 66  // Extends allows inheriting from shared configurations.
 67  Extends []string `mapstructure:"extends"`
 68}
 69
 70// GitLabConfig holds GitLab-specific settings.
 71type GitLabConfig struct {
 72  ProjectID string `mapstructure:"project_id"`
 73  // Token is the GitLab personal access token. SENSITIVE: do not log this field.
 74  Token         string   `mapstructure:"token"`
 75  APIURL        string   `mapstructure:"api_url"`
 76  CreateRelease bool     `mapstructure:"create_release"`
 77  Assets        []string `mapstructure:"assets"`
 78  Milestones    []string `mapstructure:"milestones"`
 79}
 80
 81// BitbucketConfig holds Bitbucket-specific settings.
 82type BitbucketConfig struct {
 83  Workspace string `mapstructure:"workspace"`
 84  RepoSlug  string `mapstructure:"repo_slug"`
 85  // Token is the Bitbucket access token. SENSITIVE: do not log this field.
 86  Token         string `mapstructure:"token"`
 87  APIURL        string `mapstructure:"api_url"`
 88  CreateRelease bool   `mapstructure:"create_release"`
 89}
 90
 91// PrepareConfig holds settings for the prepare lifecycle step.
 92type PrepareConfig struct {
 93  ChangelogFile   string   `mapstructure:"changelog_file"`
 94  VersionFile     string   `mapstructure:"version_file"`
 95  AdditionalFiles []string `mapstructure:"additional_files"`
 96  Command         string   `mapstructure:"command"`
 97  VersionFiles    []string `mapstructure:"version_files"`
 98}
 99
 100// GitConfig holds settings for git operations performed during the release workflow.
 101type GitConfig struct {
 102  Assets  []string `mapstructure:"assets"`
 103  Message string   `mapstructure:"message"`
 104}
 105
 106// VersionFileEntry holds a parsed version_files entry.
 107type VersionFileEntry struct {
 108  Path    string // file path relative to repository root
 109  KeyPath string // dot-separated TOML key path; empty for plain-text files
 110}
 111
 112// ParseVersionFileEntry parses a version_files entry of the form "path" or "path:key.path".
 113// A trailing colon (e.g. "some.toml:") is treated as an empty key path.
 3114func ParseVersionFileEntry(entry string) VersionFileEntry {
 3115  i := strings.Index(entry, ":")
 1116  if i < 0 {
 1117    return VersionFileEntry{Path: entry}
 1118  }
 2119  return VersionFileEntry{Path: entry[:i], KeyPath: entry[i+1:]}
 120}
 121
 122// ProjectConfig defines a project in the configuration file.
 123type ProjectConfig struct {
 124  Name          string   `mapstructure:"name"`
 125  Path          string   `mapstructure:"path"`
 126  TagPrefix     string   `mapstructure:"tag_prefix"`
 127  Dependencies  []string `mapstructure:"dependencies"`
 128  ChangelogFile string   `mapstructure:"changelog_file"` // per-project changelog filename, relative to the project's pa
 129}
 130
 131// GitHubAsset represents a release asset to upload, with an optional display label.
 132// Both YAML forms are supported:
 133//
 134//  # simple string — label is empty
 135//  assets:
 136//    - dist/*.tar.gz
 137//
 138//  # structured — label appears as the download name on the GitHub release page
 139//  assets:
 140//    - path: dist/*.tar.gz
 141//      label: Source Tarballs
 142type GitHubAsset struct {
 143  // Path is a glob pattern (relative to the repository root) matching the files to upload.
 144  Path string `mapstructure:"path"`
 145  // Label is the display name shown on the GitHub release page. Empty means the filename is used.
 146  Label string `mapstructure:"label"`
 147}
 148
 149// GitHubConfig holds GitHub-specific settings.
 150type GitHubConfig struct {
 151  Owner string `mapstructure:"owner"`
 152  Repo  string `mapstructure:"repo"`
 153  // Token is the GitHub personal access token. SENSITIVE: do not log this field.
 154  Token                  string        `mapstructure:"token"`
 155  APIURL                 string        `mapstructure:"api_url"`
 156  CreateRelease          bool          `mapstructure:"create_release"`
 157  DraftRelease           bool          `mapstructure:"draft_release"`
 158  Assets                 []GitHubAsset `mapstructure:"assets"`
 159  SuccessComment         string        `mapstructure:"success_comment"`
 160  FailComment            string        `mapstructure:"fail_comment"`
 161  ReleasedLabels         []string      `mapstructure:"released_labels"`
 162  FailLabels             []string      `mapstructure:"fail_labels"`
 163  DiscussionCategoryName string        `mapstructure:"discussion_category_name"`
 164}
 165
 166// AnyProjectDefinesChangelog reports whether any configured project has a per-project changelog_file set.
 167func (c Config) AnyProjectDefinesChangelog() bool {
 168  return slices.ContainsFunc(c.Projects, func(p ProjectConfig) bool {
 169    return p.ChangelogFile != ""
 170  })
 171}
 172
 173// IsInteractive returns whether interactive mode is enabled.
 174// Defaults to false when the Interactive field has not been set.
 175func (c Config) IsInteractive() bool {
 176  return c.Interactive != nil && *c.Interactive
 177}
 178
 179// DefaultConfig returns sensible default configuration.
 1180func DefaultConfig() Config {
 1181  return Config{
 1182    ReleaseMode:           ReleaseModeRepo,
 1183    TagFormat:             "v{{.Version}}",
 1184    ProjectTagFormat:      "{{.Project}}/v{{.Version}}",
 1185    DryRun:                false,
 1186    CI:                    true,
 1187    Branches:              DefaultBranchPolicies(),
 1188    CommitTypes:           DefaultCommitTypeMapping(),
 1189    ChangelogSections:     DefaultChangelogSections(),
 1190    DiscoverModules:       false,
 1191    DiscoverCmd:           false,
 1192    DependencyPropagation: false,
 1193    Lint:                  DefaultLintConfig(),
 1194    GitAuthor:             DefaultGitIdentity(),
 1195    GitCommitter:          DefaultGitIdentity(),
 1196    GitBackend:            "cli",
 1197    GitHub: GitHubConfig{
 1198      CreateRelease: true,
 1199    },
 1200  }
 1201}

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/errors.go

#LineLine coverage
 1package domain
 2
 3import (
 4  "errors"
 5  "fmt"
 6)
 7
 8// Sentinel errors for the domain layer.
 9var (
 10  ErrNoReleasableChanges = errors.New("no releasable changes found")
 11  ErrInvalidVersion      = errors.New("invalid version")
 12  ErrInvalidCommit       = errors.New("invalid commit message")
 13  ErrProjectNotFound     = errors.New("project not found")
 14  ErrTagAlreadyExists    = errors.New("tag already exists")
 15  ErrBranchNotAllowed    = errors.New("branch is not configured for releases")
 16  ErrDryRun              = errors.New("dry run: no mutations performed")
 17)
 18
 19// ProjectError wraps an error with project context.
 20type ProjectError struct {
 21  Project string
 22  Op      string
 23  Err     error
 24}
 25
 26func (e *ProjectError) Error() string {
 27  return fmt.Sprintf("project %q: %s: %v", e.Project, e.Op, e.Err)
 28}
 29
 30func (e *ProjectError) Unwrap() error {
 31  return e.Err
 32}
 33
 34// NewProjectError creates a project-scoped error.
 335func NewProjectError(project, op string, err error) error {
 336  return &ProjectError{Project: project, Op: op, Err: err}
 337}
 38
 39// ReleaseError wraps an error that occurred during the release pipeline.
 40type ReleaseError struct {
 41  Step string
 42  Err  error
 43}
 44
 45func (e *ReleaseError) Error() string {
 46  return fmt.Sprintf("release step %q: %v", e.Step, e.Err)
 47}
 48
 49func (e *ReleaseError) Unwrap() error {
 50  return e.Err
 51}
 52
 53// NewReleaseError creates a release pipeline error.
 354func NewReleaseError(step string, err error) error {
 355  return &ReleaseError{Step: step, Err: err}
 356}

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/lifecycle.go

#LineLine coverage
 1package domain
 2
 3// Step represents a named step in the release lifecycle.
 4type Step string
 5
 6const (
 7  StepVerifyConditions Step = "verifyConditions"
 8  StepAnalyzeCommits   Step = "analyzeCommits"
 9  StepVerifyRelease    Step = "verifyRelease"
 10  StepGenerateNotes    Step = "generateNotes"
 11  StepPrepare          Step = "prepare"
 12  StepPublish          Step = "publish"
 13  StepAddChannel       Step = "addChannel"
 14  StepSuccess          Step = "success"
 15  StepFail             Step = "fail"
 16)
 17
 18// String returns the string representation of the lifecycle step.
 19func (s Step) String() string {
 20  return string(s)
 21}
 22
 23// StepOrder defines the canonical execution order for lifecycle steps.
 24var StepOrder = []Step{
 25  StepVerifyConditions,
 26  StepAnalyzeCommits,
 27  StepVerifyRelease,
 28  StepGenerateNotes,
 29  StepPrepare,
 30  StepPublish,
 31  StepAddChannel,
 32  StepSuccess,
 33  StepFail,
 34}
 35
 36// ReleaseContext holds all state passed through the lifecycle pipeline.
 37type ReleaseContext struct {
 38  Config         Config
 39  Branch         string
 40  BranchPolicy   *BranchPolicy
 41  Projects       []Project
 42  Commits        []Commit
 43  Plan           *ReleasePlan
 44  Result         *ReleaseResult
 45  DryRun         bool
 46  CI             bool
 47  RepositoryURL  string
 48  RepositoryRoot string
 49
 50  // Per-project state populated during pipeline execution.
 51  CurrentProject *ProjectReleasePlan
 52  Notes          string // generated release notes for current project
 53  TagName        string
 54  Error          error // set when fail step is invoked
 55}
 56
 57// GitIdentity configures the git author/committer for automated commits.
 58type GitIdentity struct {
 59  Name  string `mapstructure:"name"`
 60  Email string `mapstructure:"email"`
 61}
 62
 63// DefaultGitIdentity returns the default bot identity.
 264func DefaultGitIdentity() GitIdentity {
 265  return GitIdentity{
 266    Name:  "semantic-release-bot",
 267    Email: "semantic-release-bot@users.noreply.github.com",
 268  }
 269}

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/lint.go

#LineLine coverage
 1package domain
 2
 3// LintConfig holds configuration for commit message linting.
 4type LintConfig struct {
 5  Enabled            bool     `mapstructure:"enabled"`
 6  MaxSubjectLength   int      `mapstructure:"max_subject_length"`
 7  RequireScope       bool     `mapstructure:"require_scope"`
 8  AllowedTypes       []string `mapstructure:"allowed_types"`
 9  AllowedScopes      []string `mapstructure:"allowed_scopes"`
 10  RequireBody        bool     `mapstructure:"require_body"`
 11  RequireDescription bool     `mapstructure:"require_description"`
 12}
 13
 14// DefaultLintConfig returns sensible default lint configuration with linting disabled.
 315func DefaultLintConfig() LintConfig {
 316  return LintConfig{
 317    Enabled:          false,
 318    MaxSubjectLength: 72,
 319    AllowedTypes: []string{
 320      "feat", "fix", "docs", "style", "refactor",
 321      "perf", "test", "build", "ci", "chore", "revert",
 322    },
 323  }
 324}
 25
 26// DefaultEnabledLintConfig returns the default lint configuration with linting enabled.
 27// Use this instead of DefaultLintConfig() when you need a ready-to-use linting setup.
 128func DefaultEnabledLintConfig() LintConfig {
 129  cfg := DefaultLintConfig()
 130  cfg.Enabled = true
 131  return cfg
 132}
 33
 34// LintSeverity indicates the severity of a lint violation.
 35type LintSeverity string
 36
 37const (
 38  // LintError indicates a violation that must be corrected before release.
 39  LintError LintSeverity = "error"
 40  // LintWarning indicates a violation that should be reviewed but does not block release.
 41  LintWarning LintSeverity = "warning"
 42)
 43
 44// LintViolation represents a single lint rule violation.
 45type LintViolation struct {
 46  Rule     string
 47  Message  string
 48  Severity LintSeverity
 49}

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/tag.go

#LineLine coverage
 1package domain
 2
 3import (
 4  "fmt"
 5  "strings"
 6)
 7
 8// Tag represents a git tag associated with a release version.
 9type Tag struct {
 10  Name        string
 11  Version     Version
 12  Project     string // empty for repo-level tags
 13  Hash        string
 14  IsAnnotated bool
 15}
 16
 17// TagFormat defines how tags are constructed for a project.
 18type TagFormat struct {
 19  // Template is a Go template string. Available fields: .Project, .Version
 20  // Examples: "{{.Project}}/v{{.Version}}", "{{.Project}}@{{.Version}}", "v{{.Version}}"
 21  Template string
 22}
 23
 24// DefaultRepoTagFormat returns the default tag format for repo-level releases.
 125func DefaultRepoTagFormat() TagFormat {
 126  return TagFormat{Template: "v{{.Version}}"}
 127}
 28
 29// DefaultProjectTagFormat returns the default tag format for project-scoped releases.
 130func DefaultProjectTagFormat() TagFormat {
 131  return TagFormat{Template: "{{.Project}}/v{{.Version}}"}
 132}
 33
 34// ParseProjectFromTag extracts the project name and version from a tag string
 35// using the given prefix. Returns empty project for repo-level tags.
 536func ParseProjectFromTag(tagName, prefix string) (project string, version Version, err error) {
 237  if prefix != "" {
 138    if !strings.HasPrefix(tagName, prefix) {
 139      return "", Version{}, fmt.Errorf("tag %q does not match prefix %q", tagName, prefix)
 140    }
 141    project = strings.TrimSuffix(prefix, "/")
 142    tagName = strings.TrimPrefix(tagName, prefix)
 43  }
 44
 45  // Handle @ separator (e.g., "project@1.2.3")
 146  if idx := strings.LastIndex(tagName, "@"); idx >= 0 && project == "" {
 147    project = tagName[:idx]
 148    tagName = tagName[idx+1:]
 149  }
 50
 451  version, err = ParseVersion(tagName)
 152  if err != nil {
 153    return "", Version{}, fmt.Errorf("parsing version from tag %q: %w", tagName, err)
 154  }
 355  return project, version, nil
 56}

/home/runner/work/go-semantic-release/go-semantic-release/internal/domain/version.go

#LineLine coverage
 1package domain
 2
 3import (
 4  "fmt"
 5  "strconv"
 6  "strings"
 7)
 8
 9// Version represents a semantic version (major.minor.patch) with optional prerelease and build metadata.
 10type Version struct {
 11  Major      int
 12  Minor      int
 13  Patch      int
 14  Prerelease string
 15  Build      string
 16}
 17
 18// ZeroVersion returns a 0.0.0 version.
 419func ZeroVersion() Version {
 420  return Version{}
 421}
 22
 23// NewVersion creates a version from major, minor, patch components.
 7424func NewVersion(major, minor, patch int) Version {
 7425  return Version{Major: major, Minor: minor, Patch: patch}
 7426}
 27
 28// ParseVersion parses a semantic version string. Accepts optional "v" prefix.
 1329func ParseVersion(s string) (Version, error) {
 1330  s = strings.TrimPrefix(s, "v")
 1331
 1332  // Split off build metadata first.
 1333  build := ""
 234  if idx := strings.IndexByte(s, '+'); idx >= 0 {
 235    build = s[idx+1:]
 236    s = s[:idx]
 237  }
 38
 39  // Split off prerelease.
 1340  prerelease := ""
 441  if idx := strings.IndexByte(s, '-'); idx >= 0 {
 442    prerelease = s[idx+1:]
 443    s = s[:idx]
 444  }
 45
 1346  parts := strings.Split(s, ".")
 347  if len(parts) != 3 {
 348    return Version{}, fmt.Errorf("invalid version %q: expected major.minor.patch", s)
 349  }
 50
 1051  major, err := strconv.Atoi(parts[0])
 152  if err != nil {
 153    return Version{}, fmt.Errorf("invalid major version %q: %w", parts[0], err)
 154  }
 55
 956  minor, err := strconv.Atoi(parts[1])
 057  if err != nil {
 058    return Version{}, fmt.Errorf("invalid minor version %q: %w", parts[1], err)
 059  }
 60
 961  patch, err := strconv.Atoi(parts[2])
 062  if err != nil {
 063    return Version{}, fmt.Errorf("invalid patch version %q: %w", parts[2], err)
 064  }
 65
 966  return Version{
 967    Major:      major,
 968    Minor:      minor,
 969    Patch:      patch,
 970    Prerelease: prerelease,
 971    Build:      build,
 972  }, nil
 73}
 74
 75// String returns the version as "major.minor.patch[-prerelease][+build]".
 76func (v Version) String() string {
 77  s := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
 78  if v.Prerelease != "" {
 79    s += "-" + v.Prerelease
 80  }
 81  if v.Build != "" {
 82    s += "+" + v.Build
 83  }
 84  return s
 85}
 86
 87// TagString returns the version with a "v" prefix.
 88func (v Version) TagString() string {
 89  return "v" + v.String()
 90}
 91
 92// IsZero returns true if this is the zero version (0.0.0 with no prerelease/build).
 93func (v Version) IsZero() bool {
 94  return v.Major == 0 && v.Minor == 0 && v.Patch == 0 && v.Prerelease == "" && v.Build == ""
 95}
 96
 97// Bump returns a new version incremented by the given release type.
 98func (v Version) Bump(rt ReleaseType) Version {
 99  switch rt {
 100  case ReleaseMajor:
 101    return NewVersion(v.Major+1, 0, 0)
 102  case ReleaseMinor:
 103    return NewVersion(v.Major, v.Minor+1, 0)
 104  case ReleasePatch:
 105    return NewVersion(v.Major, v.Minor, v.Patch+1)
 106  default:
 107    return v
 108  }
 109}
 110
 111// WithPrerelease returns a copy with the given prerelease identifier.
 112func (v Version) WithPrerelease(pre string) Version {
 113  v.Prerelease = pre
 114  v.Build = ""
 115  return v
 116}
 117
 118// GreaterThan returns true if v is greater than other.
 119func (v Version) GreaterThan(other Version) bool {
 120  if v.Major != other.Major {
 121    return v.Major > other.Major
 122  }
 123  if v.Minor != other.Minor {
 124    return v.Minor > other.Minor
 125  }
 126  return v.Patch > other.Patch
 127}
 128
 129// Equal returns true if versions are identical (excluding build metadata per semver spec).
 130func (v Version) Equal(other Version) bool {
 131  return v.Major == other.Major &&
 132    v.Minor == other.Minor &&
 133    v.Patch == other.Patch &&
 134    v.Prerelease == other.Prerelease
 135}