Add the runReleaseSDK function that calls release.RunSDK() to enable SDK-only releases via `core release --target sdk`. The function loads configuration, applies CLI overrides, and displays styled output for the SDK generation process. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
292 lines
7.9 KiB
Go
292 lines
7.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/host-uk/core/pkg/release"
|
|
"github.com/leaanthony/clir"
|
|
)
|
|
|
|
// Release command styles
|
|
var (
|
|
releaseHeaderStyle = lipgloss.NewStyle().
|
|
Bold(true).
|
|
Foreground(lipgloss.Color("#3b82f6")) // blue-500
|
|
|
|
releaseSuccessStyle = lipgloss.NewStyle().
|
|
Bold(true).
|
|
Foreground(lipgloss.Color("#22c55e")) // green-500
|
|
|
|
releaseErrorStyle = lipgloss.NewStyle().
|
|
Bold(true).
|
|
Foreground(lipgloss.Color("#ef4444")) // red-500
|
|
|
|
releaseDimStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#6b7280")) // gray-500
|
|
|
|
releaseValueStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("#e2e8f0")) // gray-200
|
|
)
|
|
|
|
// AddReleaseCommand adds the release command and its subcommands.
|
|
func AddReleaseCommand(app *clir.Cli) {
|
|
releaseCmd := app.NewSubCommand("release", "Build and publish releases")
|
|
releaseCmd.LongDescription("Builds release artifacts, generates changelog, and publishes to GitHub.\n" +
|
|
"Configuration can be provided via .core/release.yaml or command-line flags.")
|
|
|
|
// Flags for the main release command
|
|
var dryRun bool
|
|
var version string
|
|
var draft bool
|
|
var prerelease bool
|
|
var target string
|
|
|
|
releaseCmd.BoolFlag("dry-run", "Preview release without publishing", &dryRun)
|
|
releaseCmd.StringFlag("version", "Version to release (e.g., v1.2.3)", &version)
|
|
releaseCmd.BoolFlag("draft", "Create release as a draft", &draft)
|
|
releaseCmd.BoolFlag("prerelease", "Mark release as a prerelease", &prerelease)
|
|
releaseCmd.StringFlag("target", "Release target (sdk)", &target)
|
|
|
|
// Default action for `core release`
|
|
releaseCmd.Action(func() error {
|
|
if target == "sdk" {
|
|
return runReleaseSDK(dryRun, version)
|
|
}
|
|
return runRelease(dryRun, version, draft, prerelease)
|
|
})
|
|
|
|
// `release init` subcommand
|
|
initCmd := releaseCmd.NewSubCommand("init", "Initialize release configuration")
|
|
initCmd.LongDescription("Creates a .core/release.yaml configuration file interactively.")
|
|
initCmd.Action(func() error {
|
|
return runReleaseInit()
|
|
})
|
|
|
|
// `release changelog` subcommand
|
|
changelogCmd := releaseCmd.NewSubCommand("changelog", "Generate changelog")
|
|
changelogCmd.LongDescription("Generates a changelog from conventional commits.")
|
|
var fromRef, toRef string
|
|
changelogCmd.StringFlag("from", "Starting ref (default: previous tag)", &fromRef)
|
|
changelogCmd.StringFlag("to", "Ending ref (default: HEAD)", &toRef)
|
|
changelogCmd.Action(func() error {
|
|
return runChangelog(fromRef, toRef)
|
|
})
|
|
|
|
// `release version` subcommand
|
|
versionCmd := releaseCmd.NewSubCommand("version", "Show or set version")
|
|
versionCmd.LongDescription("Shows the determined version or validates a version string.")
|
|
versionCmd.Action(func() error {
|
|
return runReleaseVersion()
|
|
})
|
|
}
|
|
|
|
// runRelease executes the main release workflow.
|
|
func runRelease(dryRun bool, version string, draft, prerelease bool) error {
|
|
ctx := context.Background()
|
|
|
|
// Get current directory
|
|
projectDir, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get working directory: %w", err)
|
|
}
|
|
|
|
// Load configuration
|
|
cfg, err := release.LoadConfig(projectDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
// Apply CLI overrides
|
|
if version != "" {
|
|
cfg.SetVersion(version)
|
|
}
|
|
|
|
// Apply draft/prerelease overrides to all publishers
|
|
if draft || prerelease {
|
|
for i := range cfg.Publishers {
|
|
if draft {
|
|
cfg.Publishers[i].Draft = true
|
|
}
|
|
if prerelease {
|
|
cfg.Publishers[i].Prerelease = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Print header
|
|
fmt.Printf("%s Starting release process\n", releaseHeaderStyle.Render("Release:"))
|
|
if dryRun {
|
|
fmt.Printf(" %s\n", releaseDimStyle.Render("(dry-run mode)"))
|
|
}
|
|
fmt.Println()
|
|
|
|
// Run the release
|
|
rel, err := release.Run(ctx, cfg, dryRun)
|
|
if err != nil {
|
|
fmt.Printf("%s %v\n", releaseErrorStyle.Render("Error:"), err)
|
|
return err
|
|
}
|
|
|
|
// Print summary
|
|
fmt.Println()
|
|
fmt.Printf("%s Release completed!\n", releaseSuccessStyle.Render("Success:"))
|
|
fmt.Printf(" Version: %s\n", releaseValueStyle.Render(rel.Version))
|
|
fmt.Printf(" Artifacts: %d\n", len(rel.Artifacts))
|
|
|
|
if !dryRun && len(cfg.Publishers) > 0 {
|
|
for _, pub := range cfg.Publishers {
|
|
fmt.Printf(" Published: %s\n", releaseValueStyle.Render(pub.Type))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// runReleaseSDK executes SDK-only release.
|
|
func runReleaseSDK(dryRun bool, version string) error {
|
|
ctx := context.Background()
|
|
|
|
projectDir, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get working directory: %w", err)
|
|
}
|
|
|
|
// Load configuration
|
|
cfg, err := release.LoadConfig(projectDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
// Apply CLI overrides
|
|
if version != "" {
|
|
cfg.SetVersion(version)
|
|
}
|
|
|
|
// Print header
|
|
fmt.Printf("%s Generating SDKs\n", releaseHeaderStyle.Render("SDK Release:"))
|
|
if dryRun {
|
|
fmt.Printf(" %s\n", releaseDimStyle.Render("(dry-run mode)"))
|
|
}
|
|
fmt.Println()
|
|
|
|
// Run SDK release
|
|
result, err := release.RunSDK(ctx, cfg, dryRun)
|
|
if err != nil {
|
|
fmt.Printf("%s %v\n", releaseErrorStyle.Render("Error:"), err)
|
|
return err
|
|
}
|
|
|
|
// Print summary
|
|
fmt.Println()
|
|
fmt.Printf("%s SDK generation complete!\n", releaseSuccessStyle.Render("Success:"))
|
|
fmt.Printf(" Version: %s\n", releaseValueStyle.Render(result.Version))
|
|
fmt.Printf(" Languages: %v\n", result.Languages)
|
|
fmt.Printf(" Output: %s/\n", releaseValueStyle.Render(result.Output))
|
|
|
|
return nil
|
|
}
|
|
|
|
// runReleaseInit creates a release configuration interactively.
|
|
func runReleaseInit() error {
|
|
projectDir, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get working directory: %w", err)
|
|
}
|
|
|
|
// Check if config already exists
|
|
if release.ConfigExists(projectDir) {
|
|
fmt.Printf("%s Configuration already exists at %s\n",
|
|
releaseDimStyle.Render("Note:"),
|
|
release.ConfigPath(projectDir))
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
fmt.Print("Overwrite? [y/N]: ")
|
|
response, _ := reader.ReadString('\n')
|
|
response = strings.TrimSpace(strings.ToLower(response))
|
|
if response != "y" && response != "yes" {
|
|
fmt.Println("Aborted.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fmt.Printf("%s Creating release configuration\n", releaseHeaderStyle.Render("Init:"))
|
|
fmt.Println()
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
|
|
// Project name
|
|
defaultName := filepath.Base(projectDir)
|
|
fmt.Printf("Project name [%s]: ", defaultName)
|
|
name, _ := reader.ReadString('\n')
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
name = defaultName
|
|
}
|
|
|
|
// Repository
|
|
fmt.Print("GitHub repository (owner/repo): ")
|
|
repo, _ := reader.ReadString('\n')
|
|
repo = strings.TrimSpace(repo)
|
|
|
|
// Create config
|
|
cfg := release.DefaultConfig()
|
|
cfg.Project.Name = name
|
|
cfg.Project.Repository = repo
|
|
|
|
// Write config
|
|
if err := release.WriteConfig(cfg, projectDir); err != nil {
|
|
return fmt.Errorf("failed to write config: %w", err)
|
|
}
|
|
|
|
fmt.Println()
|
|
fmt.Printf("%s Configuration written to %s\n",
|
|
releaseSuccessStyle.Render("Success:"),
|
|
release.ConfigPath(projectDir))
|
|
|
|
return nil
|
|
}
|
|
|
|
// runChangelog generates and prints a changelog.
|
|
func runChangelog(fromRef, toRef string) error {
|
|
projectDir, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get working directory: %w", err)
|
|
}
|
|
|
|
// Load config for changelog settings
|
|
cfg, err := release.LoadConfig(projectDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
// Generate changelog
|
|
changelog, err := release.GenerateWithConfig(projectDir, fromRef, toRef, &cfg.Changelog)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate changelog: %w", err)
|
|
}
|
|
|
|
fmt.Println(changelog)
|
|
return nil
|
|
}
|
|
|
|
// runReleaseVersion shows the determined version.
|
|
func runReleaseVersion() error {
|
|
projectDir, err := os.Getwd()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get working directory: %w", err)
|
|
}
|
|
|
|
version, err := release.DetermineVersion(projectDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to determine version: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Version: %s\n", releaseValueStyle.Render(version))
|
|
return nil
|
|
}
|