Files
stamp/cmd/stamp/cmd_version.go
Thomas fb347eaa54 Stamp
2026-03-11 20:55:31 +01:00

139 lines
3.5 KiB
Go

package main
import (
"context"
"fmt"
"os"
"path/filepath"
"time"
"github.com/urfave/cli/v3"
"git.thokra.dev/thokra/stamp/internal/changelog"
"git.thokra.dev/thokra/stamp/internal/changeset"
"git.thokra.dev/thokra/stamp/internal/config"
"git.thokra.dev/thokra/stamp/internal/git"
"git.thokra.dev/thokra/stamp/internal/semver"
)
func versionCmd() *cli.Command {
return &cli.Command{
Name: "version",
Usage: "consume stamps, bump versions, and update changelogs",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "snapshot",
Usage: "pre-release identifier (e.g. alpha, beta, rc) for snapshot releases",
},
&cli.BoolFlag{
Name: "no-commit",
Usage: "skip the git commit after versioning",
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
repoRoot, err := findRepoRoot(".")
if err != nil {
return err
}
cfg, err := config.Load(repoRoot)
if err != nil {
return err
}
stampDir := filepath.Join(repoRoot, cfg.ChangesetDir())
changesets, err := changeset.ReadAll(stampDir)
if err != nil && !os.IsNotExist(err) {
return err
}
if len(changesets) == 0 {
fmt.Println("No pending stamps found.")
return nil
}
preID := cmd.String("snapshot")
projectBumps := semver.ProjectBumps(changesets)
now := time.Now()
var changedFiles []string
var bumped []string
for i := range cfg.Projects {
project := &cfg.Projects[i]
bumps := projectBumps[project.Name]
if len(bumps) == 0 {
continue
}
highest := semver.HighestBump(bumps)
if preID != "" {
highest = changeset.BumpPreRelease
}
nextVer, err := semver.Bump(project.Version, highest, preID)
if err != nil {
return fmt.Errorf("bumping %s: %w", project.Name, err)
}
// Build changelog body from all matching changesets.
var body string
for _, cs := range changesets {
if _, ok := cs.Bumps[project.Name]; ok {
if cs.Description != "" {
body += "- " + cs.Description + "\n"
}
}
}
clPath := project.ChangelogPath(repoRoot)
if err := changelog.Append(clPath, changelog.Entry{
Version: nextVer,
Date: now,
Description: body,
}); err != nil {
return fmt.Errorf("updating changelog for %s: %w", project.Name, err)
}
changedFiles = append(changedFiles, clPath)
fmt.Printf(" %s: %s → %s\n", project.Name, project.Version, nextVer)
project.Version = nextVer
bumped = append(bumped, project.Name)
}
if len(bumped) == 0 {
fmt.Println("No projects were bumped.")
return nil
}
// Update stamp.toml with new versions.
cfgPath := filepath.Join(repoRoot, config.ConfigFileName)
if err := config.Save(repoRoot, cfg); err != nil {
return fmt.Errorf("saving stamp.toml: %w", err)
}
changedFiles = append(changedFiles, cfgPath)
// Delete consumed stamp files.
if err := changeset.DeleteAll(stampDir); err != nil {
return fmt.Errorf("deleting stamp files: %w", err)
}
changedFiles = append(changedFiles, stampDir)
if cmd.Bool("no-commit") {
fmt.Println("\nVersioning complete (no commit).")
return nil
}
// Stage and commit.
if err := git.Add(repoRoot, changedFiles...); err != nil {
return fmt.Errorf("staging files: %w", err)
}
commitMsg := fmt.Sprintf("chore: version packages\n\nBumped: %v", bumped)
if err := git.Commit(repoRoot, commitMsg); err != nil {
return fmt.Errorf("committing: %w", err)
}
fmt.Println("\n✓ Committed version bump.")
return nil
},
}
}