< Summary - go-semantic-release Coverage

Line coverage
87%
Covered lines: 107
Uncovered lines: 15
Coverable lines: 122
Total lines: 190
Line coverage: 87.7%
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

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Execute0%0083.33%
runVerifyConditions0%00100%
runAnalyzeCommits0%0072.73%
runVerifyRelease0%00100%
runGenerateNotes0%00100%
runPrepare0%00100%
runPublish0%0075%
runAddChannel0%00100%
runSuccess0%00100%
handleFailure0%0066.67%

File(s)

/home/runner/work/go-semantic-release/go-semantic-release/internal/app/pipeline.go

#LineLine coverage
 1package app
 2
 3import (
 4  "context"
 5  "fmt"
 6  "strings"
 7
 8  "github.com/jedi-knights/go-semantic-release/internal/domain"
 9  "github.com/jedi-knights/go-semantic-release/internal/ports"
 10)
 11
 12// Pipeline orchestrates the semantic release lifecycle.
 13type Pipeline struct {
 14  plugins []ports.Plugin
 15  logger  ports.Logger
 16}
 17
 18// NewPipeline creates a new lifecycle pipeline with the given plugins.
 19func NewPipeline(plugins []ports.Plugin, logger ports.Logger) *Pipeline {
 20  return &Pipeline{plugins: plugins, logger: logger}
 21}
 22
 23// Execute runs all lifecycle steps in order against the release context.
 1024func (p *Pipeline) Execute(ctx context.Context, rc *domain.ReleaseContext) error {
 125  if err := p.runVerifyConditions(ctx, rc); err != nil {
 126    return p.handleFailure(ctx, rc, err)
 127  }
 28
 929  releaseType, err := p.runAnalyzeCommits(ctx, rc)
 030  if err != nil {
 031    return p.handleFailure(ctx, rc, err)
 032  }
 33
 134  if !releaseType.IsReleasable() {
 135    p.logger.Info("no releasable changes found")
 136    return nil
 137  }
 38
 139  if verifyErr := p.runVerifyRelease(ctx, rc); verifyErr != nil {
 140    return p.handleFailure(ctx, rc, verifyErr)
 141  }
 42
 743  notes, err := p.runGenerateNotes(ctx, rc)
 144  if err != nil {
 145    return p.handleFailure(ctx, rc, err)
 146  }
 647  rc.Notes = notes
 648
 249  if rc.DryRun {
 250    p.logger.Info("dry run complete, skipping prepare/publish/addChannel/success steps")
 251    return nil
 252  }
 53
 154  if err := p.runPrepare(ctx, rc); err != nil {
 155    return p.handleFailure(ctx, rc, err)
 156  }
 57
 058  if err := p.runPublish(ctx, rc); err != nil {
 059    return p.handleFailure(ctx, rc, err)
 060  }
 61
 162  if err := p.runAddChannel(ctx, rc); err != nil {
 163    p.logger.Warn("addChannel failed", "error", err)
 164    // Non-fatal — continue to success notification.
 165  }
 66
 367  return p.runSuccess(ctx, rc)
 68}
 69
 1070func (p *Pipeline) runVerifyConditions(ctx context.Context, rc *domain.ReleaseContext) error {
 1071  for _, plugin := range p.plugins {
 1072    if vp, ok := plugin.(ports.VerifyConditionsPlugin); ok {
 1073      p.logger.Debug("running verifyConditions", "plugin", plugin.Name())
 174      if err := vp.VerifyConditions(ctx, rc); err != nil {
 175        return domain.NewReleaseError("verifyConditions:"+plugin.Name(), err)
 176      }
 77    }
 78  }
 979  return nil
 80}
 81
 982func (p *Pipeline) runAnalyzeCommits(ctx context.Context, rc *domain.ReleaseContext) (domain.ReleaseType, error) {
 983  highest := domain.ReleaseNone
 984  for _, plugin := range p.plugins {
 1085    if ap, ok := plugin.(ports.AnalyzeCommitsPlugin); ok {
 1086      p.logger.Debug("running analyzeCommits", "plugin", plugin.Name())
 1087      rt, err := ap.AnalyzeCommits(ctx, rc)
 088      if err != nil {
 089        return domain.ReleaseNone, domain.NewReleaseError("analyzeCommits:"+plugin.Name(), err)
 090      }
 1091      highest = highest.Higher(rt)
 92    }
 93  }
 994  return highest, nil
 95}
 96
 897func (p *Pipeline) runVerifyRelease(ctx context.Context, rc *domain.ReleaseContext) error {
 898  for _, plugin := range p.plugins {
 199    if vp, ok := plugin.(ports.VerifyReleasePlugin); ok {
 1100      p.logger.Debug("running verifyRelease", "plugin", plugin.Name())
 1101      if err := vp.VerifyRelease(ctx, rc); err != nil {
 1102        return domain.NewReleaseError("verifyRelease:"+plugin.Name(), err)
 1103      }
 104    }
 105  }
 7106  return nil
 107}
 108
 7109func (p *Pipeline) runGenerateNotes(ctx context.Context, rc *domain.ReleaseContext) (string, error) {
 7110  var parts []string
 7111  for _, plugin := range p.plugins {
 8112    if gp, ok := plugin.(ports.GenerateNotesPlugin); ok {
 8113      p.logger.Debug("running generateNotes", "plugin", plugin.Name())
 8114      notes, err := gp.GenerateNotes(ctx, rc)
 1115      if err != nil {
 1116        return "", domain.NewReleaseError("generateNotes:"+plugin.Name(), err)
 1117      }
 5118      if notes != "" {
 5119        parts = append(parts, notes)
 5120      }
 121    }
 122  }
 6123  return strings.Join(parts, "\n\n"), nil
 124}
 125
 4126func (p *Pipeline) runPrepare(ctx context.Context, rc *domain.ReleaseContext) error {
 4127  for _, plugin := range p.plugins {
 4128    if pp, ok := plugin.(ports.PreparePlugin); ok {
 4129      p.logger.Debug("running prepare", "plugin", plugin.Name())
 1130      if err := pp.Prepare(ctx, rc); err != nil {
 1131        return domain.NewReleaseError("prepare:"+plugin.Name(), err)
 1132      }
 133    }
 134  }
 3135  return nil
 136}
 137
 3138func (p *Pipeline) runPublish(ctx context.Context, rc *domain.ReleaseContext) error {
 3139  for _, plugin := range p.plugins {
 3140    if pp, ok := plugin.(ports.PublishPlugin); ok {
 3141      p.logger.Debug("running publish", "plugin", plugin.Name())
 3142      result, err := pp.Publish(ctx, rc)
 0143      if err != nil {
 0144        return domain.NewReleaseError("publish:"+plugin.Name(), err)
 0145      }
 1146      if result != nil && rc.Result != nil {
 1147        rc.Result.Projects = append(rc.Result.Projects, *result)
 1148      }
 149    }
 150  }
 3151  return nil
 152}
 153
 3154func (p *Pipeline) runAddChannel(ctx context.Context, rc *domain.ReleaseContext) error {
 3155  for _, plugin := range p.plugins {
 1156    if ap, ok := plugin.(ports.AddChannelPlugin); ok {
 1157      p.logger.Debug("running addChannel", "plugin", plugin.Name())
 1158      if err := ap.AddChannel(ctx, rc); err != nil {
 1159        return fmt.Errorf("addChannel:%s: %w", plugin.Name(), err)
 1160      }
 161    }
 162  }
 2163  return nil
 164}
 165
 3166func (p *Pipeline) runSuccess(ctx context.Context, rc *domain.ReleaseContext) error {
 3167  for _, plugin := range p.plugins {
 3168    if sp, ok := plugin.(ports.SuccessPlugin); ok {
 3169      p.logger.Debug("running success", "plugin", plugin.Name())
 1170      if err := sp.Success(ctx, rc); err != nil {
 1171        p.logger.Warn("success notification failed", "plugin", plugin.Name(), "error", err)
 1172        // Non-fatal — don't fail the release for notification failures.
 1173      }
 174    }
 175  }
 3176  return nil
 177}
 178
 4179func (p *Pipeline) handleFailure(ctx context.Context, rc *domain.ReleaseContext, releaseErr error) error {
 4180  rc.Error = releaseErr
 4181  for _, plugin := range p.plugins {
 4182    if fp, ok := plugin.(ports.FailPlugin); ok {
 4183      p.logger.Debug("running fail", "plugin", plugin.Name())
 0184      if err := fp.Fail(ctx, rc); err != nil {
 0185        p.logger.Warn("fail notification failed", "plugin", plugin.Name(), "error", err)
 0186      }
 187    }
 188  }
 4189  return releaseErr
 190}