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:
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"git.thokra.dev/thokra/stamp/internal/changeset"
|
||||
@@ -101,46 +102,61 @@ func addCmd() *cli.Command {
|
||||
}
|
||||
}
|
||||
|
||||
// promptAdd runs an interactive prompt to collect add parameters.
|
||||
// promptAdd runs an interactive TUI prompt to collect add parameters.
|
||||
func promptAdd(cfg *config.Config) ([]string, changeset.BumpType, string, error) {
|
||||
projectNames := cfg.ProjectNames()
|
||||
|
||||
fmt.Println("Which projects are affected? (enter comma-separated names)")
|
||||
for _, name := range projectNames {
|
||||
fmt.Printf(" - %s\n", name)
|
||||
}
|
||||
fmt.Print("> ")
|
||||
|
||||
var input string
|
||||
if _, err := fmt.Scanln(&input); err != nil {
|
||||
return nil, "", "", fmt.Errorf("reading input: %w", err)
|
||||
}
|
||||
|
||||
var projects []string
|
||||
for _, p := range splitComma(input) {
|
||||
if cfg.FindProject(p) == nil {
|
||||
return nil, "", "", fmt.Errorf("project %q not found in stamp.toml", p)
|
||||
projectOptions := make([]huh.Option[string], len(cfg.Projects))
|
||||
for i, p := range cfg.Projects {
|
||||
label := p.Name
|
||||
if p.PreTag != "" {
|
||||
label += " (preview: " + p.PreTag + ")"
|
||||
}
|
||||
projects = append(projects, p)
|
||||
}
|
||||
if len(projects) == 0 {
|
||||
return nil, "", "", fmt.Errorf("at least one project must be specified")
|
||||
projectOptions[i] = huh.NewOption(label, p.Name)
|
||||
}
|
||||
|
||||
fmt.Println("Bump type? (major, minor, patch, premajor, preminor, prepatch, prerelease)")
|
||||
fmt.Print("> ")
|
||||
var selectedProjects []string
|
||||
var bumpStr string
|
||||
if _, err := fmt.Scanln(&bumpStr); err != nil {
|
||||
return nil, "", "", fmt.Errorf("reading input: %w", err)
|
||||
}
|
||||
bumpType := changeset.BumpType(bumpStr)
|
||||
|
||||
fmt.Println("Description of the change:")
|
||||
fmt.Print("> ")
|
||||
var message string
|
||||
if _, err := fmt.Scanln(&message); err != nil {
|
||||
return nil, "", "", fmt.Errorf("reading input: %w", err)
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewMultiSelect[string]().
|
||||
Title("Which projects are affected?").
|
||||
Options(projectOptions...).
|
||||
Validate(func(v []string) error {
|
||||
if len(v) == 0 {
|
||||
return fmt.Errorf("select at least one project")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Value(&selectedProjects),
|
||||
),
|
||||
huh.NewGroup(
|
||||
huh.NewSelect[string]().
|
||||
Title("Bump type").
|
||||
Options(
|
||||
huh.NewOption("patch — bug fix (1.2.3 → 1.2.4)", string(changeset.BumpPatch)),
|
||||
huh.NewOption("minor — new feature (1.2.3 → 1.3.0)", string(changeset.BumpMinor)),
|
||||
huh.NewOption("major — breaking change (1.2.3 → 2.0.0)", string(changeset.BumpMajor)),
|
||||
).
|
||||
Value(&bumpStr),
|
||||
),
|
||||
huh.NewGroup(
|
||||
huh.NewText().
|
||||
Title("Describe the change").
|
||||
CharLimit(500).
|
||||
Validate(func(v string) error {
|
||||
if len(v) == 0 {
|
||||
return fmt.Errorf("description is required")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Value(&message),
|
||||
),
|
||||
)
|
||||
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
return projects, bumpType, message, nil
|
||||
return selectedProjects, changeset.BumpType(bumpStr), message, nil
|
||||
}
|
||||
|
||||
98
cmd/stamp/cmd_preview.go
Normal file
98
cmd/stamp/cmd_preview.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"git.thokra.dev/thokra/stamp/internal/config"
|
||||
)
|
||||
|
||||
func previewCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "preview",
|
||||
Usage: "manage pre-release mode for a project",
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "enter",
|
||||
Usage: "put a project into pre-release mode",
|
||||
ArgsUsage: "<project> <tag>",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Len() < 2 {
|
||||
return fmt.Errorf("usage: stamp preview enter <project> <tag>")
|
||||
}
|
||||
projectName := cmd.Args().Get(0)
|
||||
tag := cmd.Args().Get(1)
|
||||
|
||||
repoRoot, err := findRepoRoot(".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := config.Load(repoRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project := cfg.FindProject(projectName)
|
||||
if project == nil {
|
||||
return fmt.Errorf("project %q not found in stamp.toml", projectName)
|
||||
}
|
||||
|
||||
if project.PreTag != "" {
|
||||
return fmt.Errorf("project %q is already in pre-release mode with tag %q — run `stamp preview exit %s` first",
|
||||
projectName, project.PreTag, projectName)
|
||||
}
|
||||
|
||||
project.PreTag = tag
|
||||
|
||||
if err := config.Save(repoRoot, cfg); err != nil {
|
||||
return fmt.Errorf("saving stamp.toml: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("🔬 %s is now in pre-release mode (tag: %s).\n", projectName, tag)
|
||||
fmt.Printf(" `stamp version` will produce versions like 1.2.3-%s.0\n", tag)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "exit",
|
||||
Usage: "take a project out of pre-release mode",
|
||||
ArgsUsage: "<project>",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Len() == 0 {
|
||||
return fmt.Errorf("usage: stamp preview exit <project>")
|
||||
}
|
||||
projectName := cmd.Args().First()
|
||||
|
||||
repoRoot, err := findRepoRoot(".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := config.Load(repoRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project := cfg.FindProject(projectName)
|
||||
if project == nil {
|
||||
return fmt.Errorf("project %q not found in stamp.toml", projectName)
|
||||
}
|
||||
|
||||
if project.PreTag == "" {
|
||||
return fmt.Errorf("project %q is not in pre-release mode", projectName)
|
||||
}
|
||||
|
||||
project.PreTag = ""
|
||||
|
||||
if err := config.Save(repoRoot, cfg); err != nil {
|
||||
return fmt.Errorf("saving stamp.toml: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✓ %s is no longer in pre-release mode. The next `stamp version` will produce a normal release.\n", projectName)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func versionCmd() *cli.Command {
|
||||
return nil
|
||||
}
|
||||
|
||||
preID := cmd.String("snapshot")
|
||||
snapshotID := cmd.String("snapshot")
|
||||
projectBumps := semver.ProjectBumps(changesets)
|
||||
now := time.Now()
|
||||
|
||||
@@ -65,8 +65,32 @@ func versionCmd() *cli.Command {
|
||||
}
|
||||
|
||||
highest := semver.HighestBump(bumps)
|
||||
|
||||
// Determine the effective pre-release identifier, in priority order:
|
||||
// 1. --snapshot flag (repo-wide, one-off)
|
||||
// 2. project.PreTag set by `stamp preview enter` (persistent, per-project)
|
||||
preID := snapshotID
|
||||
if preID == "" {
|
||||
preID = project.PreTag
|
||||
}
|
||||
|
||||
if preID != "" {
|
||||
highest = changeset.BumpPreRelease
|
||||
// If the project is already on a pre-release version, just
|
||||
// increment it. Otherwise promote the highest regular bump
|
||||
// to its pre-release equivalent so we don't skip a version.
|
||||
v, _ := semver.CurrentVersion(project.Version)
|
||||
if v != nil && v.Prerelease() != "" {
|
||||
highest = changeset.BumpPreRelease
|
||||
} else {
|
||||
switch highest {
|
||||
case changeset.BumpMajor:
|
||||
highest = changeset.BumpPreMajor
|
||||
case changeset.BumpMinor:
|
||||
highest = changeset.BumpPreMinor
|
||||
default: // patch or anything lower
|
||||
highest = changeset.BumpPrePatch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextVer, err := semver.Bump(project.Version, highest, preID)
|
||||
@@ -105,7 +129,7 @@ func versionCmd() *cli.Command {
|
||||
}
|
||||
|
||||
// Update stamp.toml with new versions.
|
||||
cfgPath := filepath.Join(repoRoot, config.ConfigFileName)
|
||||
cfgPath := config.ConfigPath(repoRoot)
|
||||
if err := config.Save(repoRoot, cfg); err != nil {
|
||||
return fmt.Errorf("saving stamp.toml: %w", err)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ func main() {
|
||||
versionCmd(),
|
||||
publishCmd(),
|
||||
commentCmd(),
|
||||
previewCmd(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user