- stamp add: create .stamp/*.md changeset files (interactive + --no-interactive) - stamp status: show pending stamps and projected next versions (plain + --json) - stamp version: consume stamps, bump semver, update CHANGELOGs, commit - stamp publish: create git tags, GitHub/Gitea releases, upload artifacts - stamp comment: post/update idempotent PR comments via GitHub/Gitea API - Supports monorepos via stamp.toml [[projects]] blocks - Changeset files support YAML (---) and TOML (+++) frontmatter - Semver + pre-release support (alpha, beta, rc) - Examples and workflow documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
149 lines
3.7 KiB
Go
149 lines
3.7 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
const DefaultChangesetDir = ".stamp"
|
|
const ConfigFileName = "stamp.toml"
|
|
|
|
// 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"`
|
|
ChangesetDir string `toml:"changeset_dir,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"`
|
|
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 returns the configured changeset directory, defaulting to .stamp.
|
|
func (c *Config) ChangesetDir() string {
|
|
if c.Config.ChangesetDir != "" {
|
|
return c.Config.ChangesetDir
|
|
}
|
|
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 the given directory.
|
|
func Load(dir string) (*Config, error) {
|
|
path := filepath.Join(dir, ConfigFileName)
|
|
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 in the given directory.
|
|
func Save(dir string, cfg *Config) error {
|
|
path := filepath.Join(dir, ConfigFileName)
|
|
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
|
|
}
|