< Summary - go-semantic-release Coverage

Line coverage
83%
Covered lines: 71
Uncovered lines: 14
Coverable lines: 85
Total lines: 157
Line coverage: 83.5%
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
Name0%00100%
VerifyConditions0%00100%
AnalyzeCommits0%0064.29%
VerifyRelease0%00100%
GenerateNotes0%0050%
Prepare0%00100%
Publish0%0050%
AddChannel0%00100%
Success0%00100%
Fail0%00100%
invoke0%0091.18%

File(s)

/home/runner/work/go-semantic-release/go-semantic-release/internal/adapters/plugins/external.go

#LineLine coverage
 1package plugins
 2
 3import (
 4  "bytes"
 5  "context"
 6  "encoding/json"
 7  "fmt"
 8  "os/exec"
 9  "strings"
 10
 11  "github.com/jedi-knights/go-semantic-release/internal/domain"
 12  "github.com/jedi-knights/go-semantic-release/internal/ports"
 13)
 14
 15// Compile-time interface compliance checks.
 16var (
 17  _ ports.Plugin                 = (*ExternalPlugin)(nil)
 18  _ ports.VerifyConditionsPlugin = (*ExternalPlugin)(nil)
 19  _ ports.AnalyzeCommitsPlugin   = (*ExternalPlugin)(nil)
 20  _ ports.VerifyReleasePlugin    = (*ExternalPlugin)(nil)
 21  _ ports.GenerateNotesPlugin    = (*ExternalPlugin)(nil)
 22  _ ports.PreparePlugin          = (*ExternalPlugin)(nil)
 23  _ ports.PublishPlugin          = (*ExternalPlugin)(nil)
 24  _ ports.AddChannelPlugin       = (*ExternalPlugin)(nil)
 25  _ ports.SuccessPlugin          = (*ExternalPlugin)(nil)
 26  _ ports.FailPlugin             = (*ExternalPlugin)(nil)
 27)
 28
 29// ExternalPlugin wraps an external executable as a lifecycle plugin.
 30// Communication uses JSON over stdin/stdout.
 31type ExternalPlugin struct {
 32  name       string
 33  executable string
 34}
 35
 36// NewExternalPlugin creates an external plugin adapter.
 37func NewExternalPlugin(name, executable string) *ExternalPlugin {
 38  return &ExternalPlugin{name: name, executable: executable}
 39}
 40
 141func (p *ExternalPlugin) Name() string { return p.name }
 42
 43// VerifyConditions calls the external plugin's verifyConditions step.
 544func (p *ExternalPlugin) VerifyConditions(ctx context.Context, rc *domain.ReleaseContext) error {
 545  _, err := p.invoke(ctx, string(domain.StepVerifyConditions), rc)
 546  return err
 547}
 48
 49// AnalyzeCommits calls the external plugin's analyzeCommits step.
 350func (p *ExternalPlugin) AnalyzeCommits(ctx context.Context, rc *domain.ReleaseContext) (domain.ReleaseType, error) {
 351  resp, err := p.invoke(ctx, string(domain.StepAnalyzeCommits), rc)
 052  if err != nil {
 053    return domain.ReleaseNone, err
 054  }
 55
 356  switch strings.ToLower(resp.ReleaseType) {
 157  case "major":
 158    return domain.ReleaseMajor, nil
 159  case "minor":
 160    return domain.ReleaseMinor, nil
 061  case "patch":
 062    return domain.ReleasePatch, nil
 163  default:
 164    return domain.ReleaseNone, nil
 65  }
 66}
 67
 68// VerifyRelease calls the external plugin's verifyRelease step.
 269func (p *ExternalPlugin) VerifyRelease(ctx context.Context, rc *domain.ReleaseContext) error {
 270  _, err := p.invoke(ctx, string(domain.StepVerifyRelease), rc)
 271  return err
 272}
 73
 74// GenerateNotes calls the external plugin's generateNotes step.
 175func (p *ExternalPlugin) GenerateNotes(ctx context.Context, rc *domain.ReleaseContext) (string, error) {
 176  resp, err := p.invoke(ctx, string(domain.StepGenerateNotes), rc)
 077  if err != nil {
 078    return "", err
 079  }
 180  return resp.Notes, nil
 81}
 82
 83// Prepare calls the external plugin's prepare step.
 184func (p *ExternalPlugin) Prepare(ctx context.Context, rc *domain.ReleaseContext) error {
 185  _, err := p.invoke(ctx, string(domain.StepPrepare), rc)
 186  return err
 187}
 88
 89// Publish calls the external plugin's publish step.
 190func (p *ExternalPlugin) Publish(ctx context.Context, rc *domain.ReleaseContext) (*domain.ProjectReleaseResult, error) {
 191  _, err := p.invoke(ctx, string(domain.StepPublish), rc)
 092  if err != nil {
 093    return nil, err
 094  }
 195  return nil, nil
 96}
 97
 98// AddChannel calls the external plugin's addChannel step.
 199func (p *ExternalPlugin) AddChannel(ctx context.Context, rc *domain.ReleaseContext) error {
 1100  _, err := p.invoke(ctx, string(domain.StepAddChannel), rc)
 1101  return err
 1102}
 103
 104// Success calls the external plugin's success step.
 1105func (p *ExternalPlugin) Success(ctx context.Context, rc *domain.ReleaseContext) error {
 1106  _, err := p.invoke(ctx, string(domain.StepSuccess), rc)
 1107  return err
 1108}
 109
 110// Fail calls the external plugin's fail step.
 1111func (p *ExternalPlugin) Fail(ctx context.Context, rc *domain.ReleaseContext) error {
 1112  _, err := p.invoke(ctx, string(domain.StepFail), rc)
 1113  return err
 1114}
 115
 16116func (p *ExternalPlugin) invoke(ctx context.Context, step string, rc *domain.ReleaseContext) (*ExternalPluginResponse, e
 16117  request := ExternalPluginRequest{
 16118    Step:    step,
 16119    Context: toExternalContext(rc),
 16120  }
 16121
 16122  inputData, err := json.Marshal(request)
 0123  if err != nil {
 0124    return nil, fmt.Errorf("marshaling plugin request: %w", err)
 0125  }
 126
 16127  cmd := exec.CommandContext(ctx, p.executable, "--step", step)
 16128  cmd.Stdin = bytes.NewReader(inputData)
 16129
 16130  var stdout, stderr bytes.Buffer
 16131  cmd.Stdout = &stdout
 16132  cmd.Stderr = &stderr
 16133
 3134  if err := cmd.Run(); err != nil {
 3135    errMsg := strings.TrimSpace(stderr.String())
 1136    if errMsg == "" {
 1137      errMsg = err.Error()
 1138    }
 3139    return nil, fmt.Errorf("external plugin %q step %s failed: %s", p.name, step, errMsg)
 140  }
 141
 142  // If no output, the plugin doesn't implement this step — that's OK.
 7143  if stdout.Len() == 0 {
 7144    return &ExternalPluginResponse{}, nil
 7145  }
 146
 6147  var resp ExternalPluginResponse
 1148  if err := json.Unmarshal(stdout.Bytes(), &resp); err != nil {
 1149    return nil, fmt.Errorf("parsing plugin %q response: %w", p.name, err)
 1150  }
 151
 1152  if resp.Error != "" {
 1153    return nil, fmt.Errorf("external plugin %q: %s", p.name, resp.Error)
 1154  }
 155
 4156  return &resp, nil
 157}