Plugin SDK
The Plugin SDK (github.com/GoSemantics/semrel-plugins) provides the generated Protobuf types and helper scaffolding for writing semrel plugins in Go. Plugins in other languages can use the raw .proto file from the main repository.
Prerequisites
Section titled “Prerequisites”- Go ≥ 1.24
- Familiarity with gRPC and Protobuf
⚠️ Critical: stdout is reserved
Section titled “⚠️ Critical: stdout is reserved”Creating a Go plugin
Section titled “Creating a Go plugin”-
Create a new module
Terminal window mkdir my-semrel-plugincd my-semrel-plugingo mod init github.com/myorg/my-semrel-plugingo get github.com/GoSemantics/semrel-plugins -
Implement the plugin interface
Each plugin type corresponds to a gRPC service defined in
semantic_release.proto.The following example implements a
CommitAnalyzerPluginthat bumpsminorfor anyfeat:commit andpatchfor everything else:package mainimport ("context""strings"semrelv1 "github.com/GoSemantics/semrel-plugins/gen/v1")type myAnalyzer struct {semrelv1.UnimplementedCommitAnalyzerPluginServer}func (a *myAnalyzer) AnalyzeCommits(ctx context.Context,req *semrelv1.AnalyzeCommitsRequest,) (*semrelv1.AnalyzeCommitsResponse, error) {bump := semrelv1.BumpLevel_BUMP_LEVEL_NONEfor _, c := range req.Ctx.Commits {msg := c.RawMessageif strings.Contains(msg, "BREAKING CHANGE") {return &semrelv1.AnalyzeCommitsResponse{Bump: semrelv1.BumpLevel_BUMP_LEVEL_MAJOR,Reason: "breaking change detected",}, nil}if strings.HasPrefix(msg, "feat") {bump = semrelv1.BumpLevel_BUMP_LEVEL_MINOR} else if strings.HasPrefix(msg, "fix") && bump < semrelv1.BumpLevel_BUMP_LEVEL_MINOR {bump = semrelv1.BumpLevel_BUMP_LEVEL_PATCH}}return &semrelv1.AnalyzeCommitsResponse{Bump: bump}, nil} -
Serve via go-plugin
Use
plugin.Serve()— go-plugin manages the handshake protocol over stdout automatically. Usehclogfor all logging so output goes to stderr:package mainimport ("os"hclog "github.com/hashicorp/go-hclog"goplugin "github.com/hashicorp/go-plugin"semrelv1 "github.com/GoSemantics/semrel-plugins/gen/v1")func main() {logger := hclog.New(&hclog.LoggerOptions{Name: "my-analyzer",Output: os.Stderr, // MUST be stderr — stdout is reserved for go-plugin handshakeLevel: hclog.Info,})goplugin.Serve(&goplugin.ServeConfig{HandshakeConfig: semrelv1.HandshakeConfig,Plugins: map[string]goplugin.Plugin{"commit-analyzer": &semrelv1.CommitAnalyzerPlugin{Impl: &myAnalyzer{}},},GRPCServer: goplugin.DefaultGRPCServer,Logger: logger,})} -
Build and install the plugin
Terminal window go build -o .semrel/my-analyzer . -
Register it in
.semrel.yamlplugins:- name: my-analyzerpath: ./.semrel/my-analyzer
Implementing multiple interfaces
Section titled “Implementing multiple interfaces”A single plugin binary can register multiple gRPC services on the same server:
semrelv1.RegisterCommitAnalyzerPluginServer(srv, &myAnalyzer{})semrelv1.RegisterChangelogGeneratorPluginServer(srv, &myChangelog{})ReleaseContext
Section titled “ReleaseContext”Every RPC receives a ReleaseContext in its request. It contains:
| Field | Type | Description |
|---|---|---|
repo_owner / repo_name | string | Repository identity |
branch | string | Branch being released |
last_version | SemanticVersion | Previous release tag |
next_version | SemanticVersion | Calculated next version |
commits | []Commit | Commits since last release |
dry_run | bool | When true, no side-effects should be applied |
config | map<string, string> | Plugin args from .semrel.yaml |