feat(releaser): add --verbose flag for configuration and decision tracing
ci / vet, staticcheck, test, build (push) Successful in 3m10s
ci / vet, staticcheck, test, build (push) Successful in 3m10s
- Prints a configuration table on startup showing each key, its value, and the source (default / config file / env: VARNAME / flag: --name) - Lists every commit since the last tag with its parsed type and the version-bump decision (feat/fix/breaking → patch bump, or ignored) - Explains the final version choice: highest commit type → next tag - All verbose output goes to stderr so it never pollutes stdout captures - Sources tracking wired through config.LoadWithSources and ApplyEnvWithSources; LoadWithSources uses a two-pass approach to detect which YAML fields were explicitly set vs defaulted Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,42 +50,123 @@ func defaults() Config {
|
||||
}
|
||||
}
|
||||
|
||||
// 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",
|
||||
"maven.pom_path": "default",
|
||||
"gitlab.url": "default",
|
||||
"gitlab.token": "default",
|
||||
"gitlab.project": "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, nil
|
||||
return cfg, src, nil
|
||||
}
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("read %s: %w", filename, err)
|
||||
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, fmt.Errorf("parse %s: %w", filename, err)
|
||||
return cfg, src, fmt.Errorf("parse %s: %w", filename, err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
// 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 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"
|
||||
}
|
||||
|
||||
return cfg, src, nil
|
||||
}
|
||||
|
||||
// ApplyEnv fills empty GitLab fields from the standard GitLab CI 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 == "" {
|
||||
c.GitLab.Token = os.Getenv("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 == "" {
|
||||
// CI_SERVER_URL is the cleanest source ("https://gitlab.example.com")
|
||||
c.GitLab.URL = os.Getenv("CI_SERVER_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 == "" {
|
||||
// Prefer numeric ID; fall back to namespace/project path
|
||||
if id := os.Getenv("CI_PROJECT_ID"); id != "" {
|
||||
c.GitLab.Project = id
|
||||
} else {
|
||||
c.GitLab.Project = os.Getenv("CI_PROJECT_PATH")
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user