Files
releaser/internal/config/config.go
T
k3nny 5af107b06d
ci / vet, staticcheck, test, build (push) Successful in 3m54s
release / Build and publish release (push) Successful in 4m11s
feat(releaser): release v1.4.0 — GitHub, SSH agent, configurable bumps
- GitHub release support: internal/ghclient (no SDK, minimal HTTP client);
  GITHUB_TOKEN env var; github.token/github.repo config; GitHub takes
  precedence over GitLab when both are configured; publisher interface
  (releasePublisher) in cmd/main.go makes providers interchangeable
- SSH agent push: gitutil.Push() now tries gitssh.NewSSHAgentAuth for
  git@/ssh:// remotes before falling back to the system git binary
- --release-env-file flag: override dotenv artifact path; pass "" to disable
- git.releasable_types config: filter which commit types trigger a bump
  (defaults to fix, feat, breaking); useful for maintenance branches
- commits.Group(), ExtractSubject(), ReleasableSet() exported helpers:
  notes.go and changelog.go now delegate to these instead of duplicating
- CHANGELOG deduplication guard: changelog.Update() is idempotent — skips
  write if ## [version] section already exists
- Always load config sources: LoadWithSources() called unconditionally;
  removes the dual config path that previously only tracked sources in
  --verbose mode
- .releaser.gitlab-ci.yml: adds artifacts: reports: dotenv: release.env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-11 00:15:09 +02:00

198 lines
5.0 KiB
Go

package config
import (
"errors"
"fmt"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
"git.k3nny.fr/releaser/internal/branch"
)
const filename = ".releaser.yml"
type Config struct {
Git GitConfig `yaml:"git"`
Maven MavenConfig `yaml:"maven"`
GitLab GitLabConfig `yaml:"gitlab"`
GitHub GitHubConfig `yaml:"github"`
}
type GitConfig struct {
TagPrefix string `yaml:"tag_prefix"`
BranchPattern string `yaml:"branch_pattern"`
CommitMessage string `yaml:"commit_message"`
AuthorName string `yaml:"author_name"`
AuthorEmail string `yaml:"author_email"`
ReleasableTypes []string `yaml:"releasable_types"`
}
type MavenConfig struct {
PomPath string `yaml:"pom_path"`
}
type GitLabConfig struct {
URL string `yaml:"url"`
Token string `yaml:"token"`
Project string `yaml:"project"`
}
type GitHubConfig struct {
Token string `yaml:"token"`
Repo string `yaml:"repo"` // "owner/repo"
}
func defaults() Config {
return Config{
Git: GitConfig{
TagPrefix: "",
BranchPattern: branch.DefaultBranchPattern,
CommitMessage: "chore(release): {version} [skip ci]",
},
Maven: MavenConfig{
PomPath: "pom.xml",
},
}
}
// Sources records where each config value came from.
// Keys are "section.field" (e.g. "git.tag_prefix").
// Values are one of: "default", "config file", "env: VARNAME", "flag: --flag-name".
type Sources map[string]string
func defaultSources() Sources {
return Sources{
"git.tag_prefix": "default",
"git.branch_pattern": "default",
"git.commit_message": "default",
"git.author_name": "default",
"git.author_email": "default",
"git.releasable_types": "default",
"maven.pom_path": "default",
"gitlab.url": "default",
"gitlab.token": "default",
"gitlab.project": "default",
"github.token": "default",
"github.repo": "default",
}
}
// Load reads .releaser.yml from dir and merges it over the defaults.
// Missing file is not an error — defaults are returned as-is.
func Load(dir string) (Config, error) {
cfg, _, err := LoadWithSources(dir)
return cfg, err
}
// LoadWithSources is like Load but also returns a Sources map recording where each
// value came from ("default" or "config file").
func LoadWithSources(dir string) (Config, Sources, error) {
cfg := defaults()
src := defaultSources()
data, err := os.ReadFile(filepath.Join(dir, filename))
if errors.Is(err, os.ErrNotExist) {
return cfg, src, nil
}
if err != nil {
return cfg, src, fmt.Errorf("read %s: %w", filename, err)
}
// Unmarshal into cfg (merges over defaults).
if err := yaml.Unmarshal(data, &cfg); err != nil {
return cfg, src, fmt.Errorf("parse %s: %w", filename, err)
}
// Detect which fields the file explicitly set by unmarshaling into a zero overlay.
var overlay Config
_ = yaml.Unmarshal(data, &overlay)
if overlay.Git.TagPrefix != "" {
src["git.tag_prefix"] = "config file"
}
if overlay.Git.BranchPattern != "" {
src["git.branch_pattern"] = "config file"
}
if overlay.Git.CommitMessage != "" {
src["git.commit_message"] = "config file"
}
if overlay.Git.AuthorName != "" {
src["git.author_name"] = "config file"
}
if overlay.Git.AuthorEmail != "" {
src["git.author_email"] = "config file"
}
if len(overlay.Git.ReleasableTypes) > 0 {
src["git.releasable_types"] = "config file"
}
if overlay.Maven.PomPath != "" {
src["maven.pom_path"] = "config file"
}
if overlay.GitLab.URL != "" {
src["gitlab.url"] = "config file"
}
if overlay.GitLab.Token != "" {
src["gitlab.token"] = "config file"
}
if overlay.GitLab.Project != "" {
src["gitlab.project"] = "config file"
}
if overlay.GitHub.Token != "" {
src["github.token"] = "config file"
}
if overlay.GitHub.Repo != "" {
src["github.repo"] = "config file"
}
return cfg, src, nil
}
// ApplyEnv fills empty GitLab and GitHub fields from environment variables.
// Values already set in the config file are never overwritten.
func (c *Config) ApplyEnv() {
c.ApplyEnvWithSources(nil)
}
// ApplyEnvWithSources is like ApplyEnv but records the env var name in src for each
// field it fills. src may be nil.
func (c *Config) ApplyEnvWithSources(src Sources) {
if c.GitLab.Token == "" {
if v := os.Getenv("GITLAB_TOKEN"); v != "" {
c.GitLab.Token = v
if src != nil {
src["gitlab.token"] = "env: GITLAB_TOKEN"
}
}
}
if c.GitLab.URL == "" {
if v := os.Getenv("CI_SERVER_URL"); v != "" {
c.GitLab.URL = v
if src != nil {
src["gitlab.url"] = "env: CI_SERVER_URL"
}
}
}
if c.GitLab.Project == "" {
if id := os.Getenv("CI_PROJECT_ID"); id != "" {
c.GitLab.Project = id
if src != nil {
src["gitlab.project"] = "env: CI_PROJECT_ID"
}
} else if p := os.Getenv("CI_PROJECT_PATH"); p != "" {
c.GitLab.Project = p
if src != nil {
src["gitlab.project"] = "env: CI_PROJECT_PATH"
}
}
}
if c.GitHub.Token == "" {
if v := os.Getenv("GITHUB_TOKEN"); v != "" {
c.GitHub.Token = v
if src != nil {
src["github.token"] = "env: GITHUB_TOKEN"
}
}
}
}