Use charmbracelet/huh for an interactive add flow. Add a preview subcommand to enter/exit per-project pre-release tags. Move stamp.toml into .stamp/ and add ConfigPath helper. Prefer --snapshot then project PreTag when computing versions and promote bumps to pre-release when appropriate. Export CurrentVersion and add required TUI deps to go.mod.
158 lines
4.0 KiB
Go
158 lines
4.0 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
const (
|
|
DefaultChangesetDir = ".stamp"
|
|
ConfigFileName = "stamp.toml"
|
|
)
|
|
|
|
// ConfigPath returns the path to stamp.toml inside the changeset directory.
|
|
func ConfigPath(repoRoot string) string {
|
|
return filepath.Join(repoRoot, DefaultChangesetDir, ConfigFileName)
|
|
}
|
|
|
|
// Config is the root configuration parsed from stamp.toml.
|
|
type Config struct {
|
|
Config GlobalConfig `toml:"config"`
|
|
Projects []Project `toml:"projects"`
|
|
}
|
|
|
|
// GlobalConfig holds repo-level settings.
|
|
type GlobalConfig struct {
|
|
BaseBranch string `toml:"base_branch,omitempty"`
|
|
}
|
|
|
|
// Project represents a single project in the monorepo.
|
|
type Project struct {
|
|
Name string `toml:"name"`
|
|
Path string `toml:"path"`
|
|
Version string `toml:"version"`
|
|
Changelog string `toml:"changelog,omitempty"`
|
|
PreTag string `toml:"pre_tag,omitempty"`
|
|
Publish PublishConfig `toml:"publish,omitempty"`
|
|
}
|
|
|
|
// PublishConfig controls what happens during stamp publish.
|
|
type PublishConfig struct {
|
|
Tags *bool `toml:"tags"`
|
|
Releases *bool `toml:"releases"`
|
|
Artifacts []string `toml:"artifacts"`
|
|
}
|
|
|
|
// ChangesetDir always returns the default changeset directory.
|
|
// The config file lives inside this directory, so it is always known.
|
|
func (c *Config) ChangesetDir() string {
|
|
return DefaultChangesetDir
|
|
}
|
|
|
|
// BaseBranch returns the configured base branch, defaulting to "main".
|
|
func (c *Config) BaseBranch() string {
|
|
if c.Config.BaseBranch != "" {
|
|
return c.Config.BaseBranch
|
|
}
|
|
return "main"
|
|
}
|
|
|
|
// FindProject returns the project with the given name, or nil if not found.
|
|
func (c *Config) FindProject(name string) *Project {
|
|
for i := range c.Projects {
|
|
if c.Projects[i].Name == name {
|
|
return &c.Projects[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ProjectNames returns all project names.
|
|
func (c *Config) ProjectNames() []string {
|
|
names := make([]string, len(c.Projects))
|
|
for i, p := range c.Projects {
|
|
names[i] = p.Name
|
|
}
|
|
return names
|
|
}
|
|
|
|
// ChangelogPath returns the path to CHANGELOG.md for a project, relative to repoRoot.
|
|
func (p *Project) ChangelogPath(repoRoot string) string {
|
|
changelog := p.Changelog
|
|
if changelog == "" {
|
|
changelog = "CHANGELOG.md"
|
|
}
|
|
return filepath.Join(repoRoot, p.Path, changelog)
|
|
}
|
|
|
|
// PublishTags returns true unless explicitly disabled.
|
|
func (p *PublishConfig) PublishTags() bool {
|
|
return p.Tags == nil || *p.Tags
|
|
}
|
|
|
|
// PublishReleases returns true unless explicitly disabled.
|
|
func (p *PublishConfig) PublishReleases() bool {
|
|
return p.Releases == nil || *p.Releases
|
|
}
|
|
|
|
// Load reads and validates stamp.toml from inside the changeset directory.
|
|
func Load(repoRoot string) (*Config, error) {
|
|
path := ConfigPath(repoRoot)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading %s: %w", path, err)
|
|
}
|
|
|
|
var cfg Config
|
|
if err := toml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parsing %s: %w", path, err)
|
|
}
|
|
|
|
if err := validate(&cfg); err != nil {
|
|
return nil, fmt.Errorf("invalid %s: %w", path, err)
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
// Save writes the config back to stamp.toml inside the changeset directory.
|
|
func Save(repoRoot string, cfg *Config) error {
|
|
dir := filepath.Join(repoRoot, DefaultChangesetDir)
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return fmt.Errorf("creating %s: %w", dir, err)
|
|
}
|
|
path := ConfigPath(repoRoot)
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
return fmt.Errorf("creating %s: %w", path, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
enc := toml.NewEncoder(f)
|
|
return enc.Encode(cfg)
|
|
}
|
|
|
|
func validate(cfg *Config) error {
|
|
if len(cfg.Projects) == 0 {
|
|
return fmt.Errorf("at least one [[projects]] entry is required")
|
|
}
|
|
|
|
seen := map[string]bool{}
|
|
for i, p := range cfg.Projects {
|
|
if p.Name == "" {
|
|
return fmt.Errorf("projects[%d]: name is required", i)
|
|
}
|
|
if seen[p.Name] {
|
|
return fmt.Errorf("projects[%d]: duplicate project name %q", i, p.Name)
|
|
}
|
|
seen[p.Name] = true
|
|
if p.Version == "" {
|
|
return fmt.Errorf("project %q: version is required", p.Name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|