Add profile loading and process execution
This commit is contained in:
117
cmd/releasekit/internal/cli/config.go
Normal file
117
cmd/releasekit/internal/cli/config.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// Package cli owns command parsing, profile settings, and child-command
|
||||
// adaptation for the ReleaseKit executable.
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/process"
|
||||
)
|
||||
|
||||
const Usage = "usage: releasekit version"
|
||||
|
||||
type Command string
|
||||
|
||||
const VersionCommand Command = "version"
|
||||
|
||||
// Invocation contains parsed command settings owned by the CLI adapter.
|
||||
type Invocation struct {
|
||||
Command Command
|
||||
}
|
||||
|
||||
// Parse accepts only commands that have been implemented by the executable.
|
||||
func Parse(args []string) (Invocation, error) {
|
||||
if len(args) == 1 && args[0] == string(VersionCommand) {
|
||||
return Invocation{Command: VersionCommand}, nil
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return Invocation{}, errors.New("a command is required")
|
||||
}
|
||||
return Invocation{}, fmt.Errorf("unknown command %q", args[0])
|
||||
}
|
||||
|
||||
// CheckCommand contains one direct child-process invocation from a profile.
|
||||
type CheckCommand struct {
|
||||
Args []string `json:"args"`
|
||||
Env map[string]string `json:"env,omitempty"`
|
||||
}
|
||||
|
||||
// Settings contains the product, remote, and check commands read from a
|
||||
// releasekit.json profile.
|
||||
type Settings struct {
|
||||
Product string `json:"product"`
|
||||
Remote string `json:"remote,omitempty"`
|
||||
Checks []CheckCommand `json:"checks,omitempty"`
|
||||
}
|
||||
|
||||
// LoadSettings reads a profile path relative to repoDir unless it is absolute.
|
||||
// An empty configPath selects <repoDir>/releasekit.json.
|
||||
func LoadSettings(repoDir, configPath string) (Settings, error) {
|
||||
resolvedRepo, err := filepath.Abs(repoDir)
|
||||
if err != nil {
|
||||
return Settings{}, fmt.Errorf("resolve repository directory %q: %w", repoDir, err)
|
||||
}
|
||||
if configPath == "" {
|
||||
configPath = "releasekit.json"
|
||||
}
|
||||
if !filepath.IsAbs(configPath) {
|
||||
configPath = filepath.Join(resolvedRepo, configPath)
|
||||
}
|
||||
|
||||
file, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
return Settings{}, fmt.Errorf("open profile %q: %w", configPath, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var settings Settings
|
||||
decoder := json.NewDecoder(file)
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&settings); err != nil {
|
||||
return Settings{}, fmt.Errorf("decode profile %q: %w", configPath, err)
|
||||
}
|
||||
var extra any
|
||||
if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
|
||||
if err == nil {
|
||||
return Settings{}, fmt.Errorf("decode profile %q: multiple JSON values", configPath)
|
||||
}
|
||||
return Settings{}, fmt.Errorf("decode profile %q: %w", configPath, err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(settings.Product) == "" {
|
||||
return Settings{}, fmt.Errorf("profile %q: product is required", configPath)
|
||||
}
|
||||
if settings.Remote == "" {
|
||||
settings.Remote = "origin"
|
||||
}
|
||||
for i, check := range settings.Checks {
|
||||
if len(check.Args) == 0 || check.Args[0] == "" {
|
||||
return Settings{}, fmt.Errorf("profile %q: checks[%d].args must include a command", configPath, i)
|
||||
}
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// RunChecks runs configured argument vectors in repoDir. Each command receives
|
||||
// its own environment overlay and inherits the remaining process environment.
|
||||
func RunChecks(ctx context.Context, repoDir string, checks []CheckCommand, stdout, stderr io.Writer) error {
|
||||
for i, check := range checks {
|
||||
if err := process.Run(ctx, process.Invocation{
|
||||
Args: check.Args,
|
||||
Dir: repoDir,
|
||||
Env: check.Env,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("run check %d: %w", i+1, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user