Add TUI add and preview commands
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.
This commit is contained in:
@@ -8,8 +8,15 @@ import (
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
const DefaultChangesetDir = ".stamp"
|
||||
const ConfigFileName = "stamp.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 {
|
||||
@@ -19,8 +26,7 @@ type Config struct {
|
||||
|
||||
// GlobalConfig holds repo-level settings.
|
||||
type GlobalConfig struct {
|
||||
BaseBranch string `toml:"base_branch,omitempty"`
|
||||
ChangesetDir string `toml:"changeset_dir,omitempty"`
|
||||
BaseBranch string `toml:"base_branch,omitempty"`
|
||||
}
|
||||
|
||||
// Project represents a single project in the monorepo.
|
||||
@@ -29,6 +35,7 @@ type Project struct {
|
||||
Path string `toml:"path"`
|
||||
Version string `toml:"version"`
|
||||
Changelog string `toml:"changelog,omitempty"`
|
||||
PreTag string `toml:"pre_tag,omitempty"`
|
||||
Publish PublishConfig `toml:"publish,omitempty"`
|
||||
}
|
||||
|
||||
@@ -39,11 +46,9 @@ type PublishConfig struct {
|
||||
Artifacts []string `toml:"artifacts"`
|
||||
}
|
||||
|
||||
// ChangesetDir returns the configured changeset directory, defaulting to .stamp.
|
||||
// ChangesetDir always returns the default changeset directory.
|
||||
// The config file lives inside this directory, so it is always known.
|
||||
func (c *Config) ChangesetDir() string {
|
||||
if c.Config.ChangesetDir != "" {
|
||||
return c.Config.ChangesetDir
|
||||
}
|
||||
return DefaultChangesetDir
|
||||
}
|
||||
|
||||
@@ -93,9 +98,9 @@ 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)
|
||||
// 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)
|
||||
@@ -113,9 +118,13 @@ func Load(dir string) (*Config, error) {
|
||||
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)
|
||||
// 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)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
const validTOML = `
|
||||
[config]
|
||||
base_branch = "main"
|
||||
changeset_dir = ".stamp"
|
||||
|
||||
[[projects]]
|
||||
name = "my-app"
|
||||
@@ -24,11 +23,20 @@ path = "libs/my-lib"
|
||||
version = "0.1.0"
|
||||
`
|
||||
|
||||
func TestLoad_Valid(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "stamp.toml"), []byte(validTOML), 0o644); err != nil {
|
||||
func writeConfig(t *testing.T, dir, content string) {
|
||||
t.Helper()
|
||||
stampDir := filepath.Join(dir, ".stamp")
|
||||
if err := os.MkdirAll(stampDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(stampDir, "stamp.toml"), []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_Valid(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeConfig(t, dir, validTOML)
|
||||
|
||||
cfg, err := config.Load(dir)
|
||||
if err != nil {
|
||||
@@ -42,16 +50,14 @@ func TestLoad_Valid(t *testing.T) {
|
||||
t.Errorf("expected base_branch=main, got %s", cfg.BaseBranch())
|
||||
}
|
||||
if cfg.ChangesetDir() != ".stamp" {
|
||||
t.Errorf("expected changeset_dir=.stamp, got %s", cfg.ChangesetDir())
|
||||
t.Errorf("expected ChangesetDir=.stamp, got %s", cfg.ChangesetDir())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_Defaults(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
minimal := "[[projects]]\nname = \"app\"\npath = \".\"\nversion = \"1.0.0\"\n"
|
||||
if err := os.WriteFile(filepath.Join(dir, "stamp.toml"), []byte(minimal), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeConfig(t, dir, minimal)
|
||||
|
||||
cfg, err := config.Load(dir)
|
||||
if err != nil {
|
||||
@@ -75,9 +81,7 @@ func TestLoad_MissingFile(t *testing.T) {
|
||||
|
||||
func TestLoad_NoProjects(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "stamp.toml"), []byte("[config]\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeConfig(t, dir, "[config]\n")
|
||||
_, err := config.Load(dir)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for config with no projects")
|
||||
@@ -97,9 +101,7 @@ name = "app"
|
||||
path = "other"
|
||||
version = "2.0.0"
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "stamp.toml"), []byte(dup), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeConfig(t, dir, dup)
|
||||
_, err := config.Load(dir)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for duplicate project name")
|
||||
@@ -108,9 +110,7 @@ version = "2.0.0"
|
||||
|
||||
func TestFindProject(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "stamp.toml"), []byte(validTOML), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeConfig(t, dir, validTOML)
|
||||
cfg, _ := config.Load(dir)
|
||||
|
||||
if p := cfg.FindProject("my-app"); p == nil {
|
||||
|
||||
@@ -9,6 +9,12 @@ import (
|
||||
"git.thokra.dev/thokra/stamp/internal/changeset"
|
||||
)
|
||||
|
||||
// CurrentVersion parses a version string and returns the semver object.
|
||||
// It is exported so callers can inspect pre-release state without re-importing the semver library.
|
||||
func CurrentVersion(version string) (*goSemver.Version, error) {
|
||||
return goSemver.NewVersion(version)
|
||||
}
|
||||
|
||||
// Bump computes the next version given the current version string and a bump type.
|
||||
// preID is the pre-release identifier (e.g. "alpha", "beta", "rc") used for pre-* bumps.
|
||||
func Bump(current string, bump changeset.BumpType, preID string) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user